Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a9ec47f
Milestone: first version with subsetting support using GSUB and GPOS
swmal Jan 14, 2026
1fc2b15
Merge branch 'develop9' into feature/embeddable-fonts-v3
swmal Jan 14, 2026
70dde68
Started work on text shaper
Jan 15, 2026
aba33a1
First working version of TextShaper
Jan 16, 2026
498abad
Performance improvements for TextShaping
swmal Jan 19, 2026
4db7060
GPOS Text shaping WIP
swmal Jan 19, 2026
c7feb88
Added Benchmark test
swmal Jan 20, 2026
f5ab967
Work on TextLayoutEngine
swmal Jan 21, 2026
668934d
memory optimizations in textwrapper
swmal Jan 22, 2026
690188e
Updated benchmarks
swmal Jan 22, 2026
5929c08
memory allocation improvements
Jan 23, 2026
ee0d454
Memoryoptimizations TextLayoutEngine/TextShaper
Jan 23, 2026
2c371ea
Some more memoryoptimizations
Jan 24, 2026
ead3f20
Performance improvements
swmal Jan 26, 2026
0aa9fea
Fixes for subsetted fonts
swmal Jan 28, 2026
bbdada2
First working version with TextShaping and subsetting
swmal Jan 29, 2026
8b656ea
Fixed some comments
swmal Jan 29, 2026
660c05b
Merged develop9 -> feature/embeddable-fonts-v3
swmal Jan 29, 2026
156de8b
Fixed some comments in code
swmal Jan 29, 2026
ff9ec97
Performance improvements for kerning values on shaped glyphs
Jan 31, 2026
0f6f7a1
Added failing test
OssianEPPlus Feb 2, 2026
7dd1a0b
Merge branch 'feature/embeddable-fonts-v3' of https://github.com/EPPl…
OssianEPPlus Feb 2, 2026
de43266
Refactored TextLayoutEngine.WrapParagraph and added support for lineb…
swmal Feb 2, 2026
4db367a
Merge branch 'feature/embeddable-fonts-v3' of https://github.com/EPPl…
swmal Feb 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="BenchmarkDotNet" Version="0.15.8" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.1" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
<PackageVersion Include="MSTest" Version="3.10.4" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\EPPlus.Fonts.OpenType\EPPlus.Fonts.OpenType.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="Fonts\Roboto-Regular.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
70 changes: 70 additions & 0 deletions src/EPPlus.Fonts.OpenType.Benchmarks/ExtractCharWidthsBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using BenchmarkDotNet.Attributes;
using EPPlus.Fonts.OpenType;
using EPPlus.Fonts.OpenType.TextShaping;
using OfficeOpenXml.Interfaces.Drawing.Text;

[MemoryDiagnoser]
[SimpleJob(warmupCount: 1, iterationCount: 3)]
public class ExtractCharWidthsBenchmark
{
private ITextShaper _shaper;
private string _shortText;
private string _mediumText;
private string _longText;
private ShapingOptions _options;

[GlobalSetup]
public void Setup()
{
var fontFolders = new List<string> { /* your font paths */ };
var font = OpenTypeFonts.GetFontData(fontFolders, "Calibri", FontSubFamily.Regular);
_shaper = new TextShaper(font);
_options = ShapingOptions.Default;

// Short: typical Excel cell
_shortText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; // 56 chars

// Medium: single paragraph
_mediumText = new string('x', 550); // Simulate 550 char paragraph

// Long: full 20 paragraphs
_longText = new string('x', 11000); // Simulate 11k chars
}

[Benchmark]
public double[] ExtractCharWidths_Short()
{
return _shaper.ExtractCharWidths(_shortText, 11f, _options);
}

[Benchmark]
public double[] ExtractCharWidths_Medium()
{
return _shaper.ExtractCharWidths(_mediumText, 11f, _options);
}

[Benchmark]
public double[] ExtractCharWidths_Long()
{
return _shaper.ExtractCharWidths(_longText, 11f, _options);
}

// For comparison: what does Shape() alone allocate?
[Benchmark]
public ShapedText ShapeOnly_Short()
{
return _shaper.Shape(_shortText, _options);
}

[Benchmark]
public ShapedText ShapeOnly_Medium()
{
return _shaper.Shape(_mediumText, _options);
}

[Benchmark]
public ShapedText ShapeOnly_Long()
{
return _shaper.Shape(_longText, _options);
}
}
56 changes: 56 additions & 0 deletions src/EPPlus.Fonts.OpenType.Benchmarks/FontCacheBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using BenchmarkDotNet.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EPPlus.Fonts.OpenType.Benchmarks
{
/// <summary>
/// Separate benchmark class to measure cache performance without ClearCache in IterationSetup
/// </summary>
[MemoryDiagnoser]
[SimpleJob(warmupCount: 3, iterationCount: 5)]
public class FontCacheBenchmarks
{
private List<string> _fontFolders;

[GlobalSetup]
public void Setup()
{
var fontsPath = Path.Combine(System.AppContext.BaseDirectory, "Fonts");

if (!Directory.Exists(fontsPath))
{
throw new DirectoryNotFoundException($"Fonts directory not found: {fontsPath}");
}

_fontFolders = new List<string> { fontsPath };

// Pre-load font into cache
OpenTypeFonts.ClearFontCache();
OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.Regular);
}

[Benchmark]
public OpenTypeFont Load_FromCache_SingleThread()
{
// This should be extremely fast - just cache lookup
return OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.Regular);
}

[Benchmark]
public OpenTypeFont[] Load_FromCache_MultipleFonts()
{
// Simulates loading multiple font styles (like for a document)
return new[]
{
OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.Regular),
OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.Bold),
OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.Italic),
OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.BoldItalic)
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using BenchmarkDotNet.Attributes;
using EPPlus.Fonts.OpenType;

/// <summary>
/// Benchmarks for repeated cache clearing scenarios
/// </summary>
[MemoryDiagnoser]
[SimpleJob(warmupCount: 3, iterationCount: 5)]
public class FontCacheClearingBenchmarks
{
private List<string> _fontFolders;

[GlobalSetup]
public void Setup()
{
var fontsPath = Path.Combine(System.AppContext.BaseDirectory, "Fonts");

if (!Directory.Exists(fontsPath))
{
throw new DirectoryNotFoundException($"Fonts directory not found: {fontsPath}");
}

_fontFolders = new List<string> { fontsPath };
}

[Benchmark]
public OpenTypeFont Load_Clear_Load_Pattern()
{
// Simulates pattern where cache is cleared between operations
OpenTypeFonts.ClearFontCache();
var font1 = OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.Regular);

OpenTypeFonts.ClearFontCache();
var font2 = OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.Regular);

return font2;
}

[Benchmark]
public OpenTypeFont Load_Reuse_Pattern()
{
// Simulates pattern where cache is NOT cleared (optimal)
var font1 = OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.Regular);
var font2 = OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.Regular);

return font2;
}
}
74 changes: 74 additions & 0 deletions src/EPPlus.Fonts.OpenType.Benchmarks/FontLoadingBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*************************************************************************************************
Required Notice: Copyright (C) EPPlus Software AB.
This software is licensed under PolyForm Noncommercial License 1.0.0
and may only be used for noncommercial purposes
https://polyformproject.org/licenses/noncommercial/1.0.0/

A commercial license to use this software can be purchased at https://epplussoftware.com
*************************************************************************************************
Date Author Change
*************************************************************************************************
01/20/2025 EPPlus Software AB Initial implementation
*************************************************************************************************/
using BenchmarkDotNet.Attributes;
using EPPlus.Fonts.OpenType;
using System.Collections.Generic;
using System.IO;

namespace EPPlus.Fonts.Benchmarks
{
[MemoryDiagnoser]
[SimpleJob(warmupCount: 3, iterationCount: 5)]
public class FontLoadingBenchmarks
{
private List<string> _fontFolders;

[GlobalSetup]
public void Setup()
{
var fontsPath = Path.Combine(System.AppContext.BaseDirectory, "Fonts");

if (!Directory.Exists(fontsPath))
{
throw new DirectoryNotFoundException($"Fonts directory not found: {fontsPath}");
}

_fontFolders = new List<string> { fontsPath };
}

[Benchmark]
public OpenTypeFont Load_Roboto_Regular_ColdCache()
{
OpenTypeFonts.ClearFontCache(); // Clear INNE i benchmark
return OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.Regular);
}

[Benchmark]
public OpenTypeFont Load_Roboto_Regular_WarmCache()
{
// Load UTAN att cleara - använder cache från GlobalSetup eller warmup
return OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.Regular);
}

[Benchmark]
public OpenTypeFont Load_Roboto_Bold_ColdCache()
{
OpenTypeFonts.ClearFontCache();
return OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.Bold);
}

[Benchmark]
public OpenTypeFont Load_Roboto_Italic_ColdCache()
{
OpenTypeFonts.ClearFontCache();
return OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.Italic);
}

[Benchmark]
public OpenTypeFont Load_Roboto_BoldItalic_ColdCache()
{
OpenTypeFonts.ClearFontCache();
return OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.BoldItalic);
}
}
}
Binary file not shown.
14 changes: 14 additions & 0 deletions src/EPPlus.Fonts.OpenType.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using BenchmarkDotNet.Running;

namespace EPPlus.Fonts.OpenType.Benchmarks
{
// Program.cs - Entry point
public class Program
{
public static void Main(string[] args)
{
// Kör alla benchmark-klasser i assembly
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
}
}
}
Loading