Skip to content

Commit 3d24ea3

Browse files
authored
Merge pull request #2263 from EPPlusSoftware/feature/embeddable-fonts-v3
Feature/embeddable fonts v3
2 parents c113886 + 4db367a commit 3d24ea3

115 files changed

Lines changed: 10470 additions & 533 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
44
</PropertyGroup>
55
<ItemGroup>
6+
<PackageVersion Include="BenchmarkDotNet" Version="0.15.8" />
67
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.1" />
78
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
89
<PackageVersion Include="MSTest" Version="3.10.4" />
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="BenchmarkDotNet" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<ProjectReference Include="..\EPPlus.Fonts.OpenType\EPPlus.Fonts.OpenType.csproj" />
16+
</ItemGroup>
17+
18+
<ItemGroup>
19+
<None Update="Fonts\Roboto-Regular.ttf">
20+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
21+
</None>
22+
</ItemGroup>
23+
24+
</Project>
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using BenchmarkDotNet.Attributes;
2+
using EPPlus.Fonts.OpenType;
3+
using EPPlus.Fonts.OpenType.TextShaping;
4+
using OfficeOpenXml.Interfaces.Drawing.Text;
5+
6+
[MemoryDiagnoser]
7+
[SimpleJob(warmupCount: 1, iterationCount: 3)]
8+
public class ExtractCharWidthsBenchmark
9+
{
10+
private ITextShaper _shaper;
11+
private string _shortText;
12+
private string _mediumText;
13+
private string _longText;
14+
private ShapingOptions _options;
15+
16+
[GlobalSetup]
17+
public void Setup()
18+
{
19+
var fontFolders = new List<string> { /* your font paths */ };
20+
var font = OpenTypeFonts.GetFontData(fontFolders, "Calibri", FontSubFamily.Regular);
21+
_shaper = new TextShaper(font);
22+
_options = ShapingOptions.Default;
23+
24+
// Short: typical Excel cell
25+
_shortText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; // 56 chars
26+
27+
// Medium: single paragraph
28+
_mediumText = new string('x', 550); // Simulate 550 char paragraph
29+
30+
// Long: full 20 paragraphs
31+
_longText = new string('x', 11000); // Simulate 11k chars
32+
}
33+
34+
[Benchmark]
35+
public double[] ExtractCharWidths_Short()
36+
{
37+
return _shaper.ExtractCharWidths(_shortText, 11f, _options);
38+
}
39+
40+
[Benchmark]
41+
public double[] ExtractCharWidths_Medium()
42+
{
43+
return _shaper.ExtractCharWidths(_mediumText, 11f, _options);
44+
}
45+
46+
[Benchmark]
47+
public double[] ExtractCharWidths_Long()
48+
{
49+
return _shaper.ExtractCharWidths(_longText, 11f, _options);
50+
}
51+
52+
// For comparison: what does Shape() alone allocate?
53+
[Benchmark]
54+
public ShapedText ShapeOnly_Short()
55+
{
56+
return _shaper.Shape(_shortText, _options);
57+
}
58+
59+
[Benchmark]
60+
public ShapedText ShapeOnly_Medium()
61+
{
62+
return _shaper.Shape(_mediumText, _options);
63+
}
64+
65+
[Benchmark]
66+
public ShapedText ShapeOnly_Long()
67+
{
68+
return _shaper.Shape(_longText, _options);
69+
}
70+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using BenchmarkDotNet.Attributes;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
8+
namespace EPPlus.Fonts.OpenType.Benchmarks
9+
{
10+
/// <summary>
11+
/// Separate benchmark class to measure cache performance without ClearCache in IterationSetup
12+
/// </summary>
13+
[MemoryDiagnoser]
14+
[SimpleJob(warmupCount: 3, iterationCount: 5)]
15+
public class FontCacheBenchmarks
16+
{
17+
private List<string> _fontFolders;
18+
19+
[GlobalSetup]
20+
public void Setup()
21+
{
22+
var fontsPath = Path.Combine(System.AppContext.BaseDirectory, "Fonts");
23+
24+
if (!Directory.Exists(fontsPath))
25+
{
26+
throw new DirectoryNotFoundException($"Fonts directory not found: {fontsPath}");
27+
}
28+
29+
_fontFolders = new List<string> { fontsPath };
30+
31+
// Pre-load font into cache
32+
OpenTypeFonts.ClearFontCache();
33+
OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.Regular);
34+
}
35+
36+
[Benchmark]
37+
public OpenTypeFont Load_FromCache_SingleThread()
38+
{
39+
// This should be extremely fast - just cache lookup
40+
return OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.Regular);
41+
}
42+
43+
[Benchmark]
44+
public OpenTypeFont[] Load_FromCache_MultipleFonts()
45+
{
46+
// Simulates loading multiple font styles (like for a document)
47+
return new[]
48+
{
49+
OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.Regular),
50+
OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.Bold),
51+
OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.Italic),
52+
OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.BoldItalic)
53+
};
54+
}
55+
}
56+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using BenchmarkDotNet.Attributes;
2+
using EPPlus.Fonts.OpenType;
3+
4+
/// <summary>
5+
/// Benchmarks for repeated cache clearing scenarios
6+
/// </summary>
7+
[MemoryDiagnoser]
8+
[SimpleJob(warmupCount: 3, iterationCount: 5)]
9+
public class FontCacheClearingBenchmarks
10+
{
11+
private List<string> _fontFolders;
12+
13+
[GlobalSetup]
14+
public void Setup()
15+
{
16+
var fontsPath = Path.Combine(System.AppContext.BaseDirectory, "Fonts");
17+
18+
if (!Directory.Exists(fontsPath))
19+
{
20+
throw new DirectoryNotFoundException($"Fonts directory not found: {fontsPath}");
21+
}
22+
23+
_fontFolders = new List<string> { fontsPath };
24+
}
25+
26+
[Benchmark]
27+
public OpenTypeFont Load_Clear_Load_Pattern()
28+
{
29+
// Simulates pattern where cache is cleared between operations
30+
OpenTypeFonts.ClearFontCache();
31+
var font1 = OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.Regular);
32+
33+
OpenTypeFonts.ClearFontCache();
34+
var font2 = OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.Regular);
35+
36+
return font2;
37+
}
38+
39+
[Benchmark]
40+
public OpenTypeFont Load_Reuse_Pattern()
41+
{
42+
// Simulates pattern where cache is NOT cleared (optimal)
43+
var font1 = OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.Regular);
44+
var font2 = OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.Regular);
45+
46+
return font2;
47+
}
48+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*************************************************************************************************
2+
Required Notice: Copyright (C) EPPlus Software AB.
3+
This software is licensed under PolyForm Noncommercial License 1.0.0
4+
and may only be used for noncommercial purposes
5+
https://polyformproject.org/licenses/noncommercial/1.0.0/
6+
7+
A commercial license to use this software can be purchased at https://epplussoftware.com
8+
*************************************************************************************************
9+
Date Author Change
10+
*************************************************************************************************
11+
01/20/2025 EPPlus Software AB Initial implementation
12+
*************************************************************************************************/
13+
using BenchmarkDotNet.Attributes;
14+
using EPPlus.Fonts.OpenType;
15+
using System.Collections.Generic;
16+
using System.IO;
17+
18+
namespace EPPlus.Fonts.Benchmarks
19+
{
20+
[MemoryDiagnoser]
21+
[SimpleJob(warmupCount: 3, iterationCount: 5)]
22+
public class FontLoadingBenchmarks
23+
{
24+
private List<string> _fontFolders;
25+
26+
[GlobalSetup]
27+
public void Setup()
28+
{
29+
var fontsPath = Path.Combine(System.AppContext.BaseDirectory, "Fonts");
30+
31+
if (!Directory.Exists(fontsPath))
32+
{
33+
throw new DirectoryNotFoundException($"Fonts directory not found: {fontsPath}");
34+
}
35+
36+
_fontFolders = new List<string> { fontsPath };
37+
}
38+
39+
[Benchmark]
40+
public OpenTypeFont Load_Roboto_Regular_ColdCache()
41+
{
42+
OpenTypeFonts.ClearFontCache(); // Clear INNE i benchmark
43+
return OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.Regular);
44+
}
45+
46+
[Benchmark]
47+
public OpenTypeFont Load_Roboto_Regular_WarmCache()
48+
{
49+
// Load UTAN att cleara - använder cache från GlobalSetup eller warmup
50+
return OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.Regular);
51+
}
52+
53+
[Benchmark]
54+
public OpenTypeFont Load_Roboto_Bold_ColdCache()
55+
{
56+
OpenTypeFonts.ClearFontCache();
57+
return OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.Bold);
58+
}
59+
60+
[Benchmark]
61+
public OpenTypeFont Load_Roboto_Italic_ColdCache()
62+
{
63+
OpenTypeFonts.ClearFontCache();
64+
return OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.Italic);
65+
}
66+
67+
[Benchmark]
68+
public OpenTypeFont Load_Roboto_BoldItalic_ColdCache()
69+
{
70+
OpenTypeFonts.ClearFontCache();
71+
return OpenTypeFonts.GetFontData(_fontFolders, "Roboto", FontSubFamily.BoldItalic);
72+
}
73+
}
74+
}
164 KB
Binary file not shown.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using BenchmarkDotNet.Running;
2+
3+
namespace EPPlus.Fonts.OpenType.Benchmarks
4+
{
5+
// Program.cs - Entry point
6+
public class Program
7+
{
8+
public static void Main(string[] args)
9+
{
10+
// Kör alla benchmark-klasser i assembly
11+
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
12+
}
13+
}
14+
}

0 commit comments

Comments
 (0)