Skip to content

Commit 3c237b6

Browse files
swmalswmal
authored andcommitted
wip
1 parent 3eb7d9e commit 3c237b6

6 files changed

Lines changed: 76 additions & 11 deletions

File tree

src/EPPlus.Fonts.OpenType.Tests/FontMeasurerPerformanceTest.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,8 @@ public class FontMeasurerPerformanceTest : FontTestBase
3636
/// Performance test for text wrapping.
3737
/// Fixed kerning pairs major bottle-neck.
3838
/// </summary>
39-
[TestMethod]
40-
//[TestMethod, Ignore("This test should not run in a multithreaded test run. If we want to keep it, it should be moved to a separate benchmark project.")]
41-
//[TestCategory("Benchmark")]
39+
[TestMethod, Ignore("This test should not run in a multithreaded test run. If we want to keep it, it should be moved to a separate benchmark project.")]
40+
[TestCategory("Benchmark")]
4241
public void Wrap20Paragraphs100Times()
4342
{
4443
List<string> longTexts = new List<string>();

src/EPPlus.Fonts.OpenType.Tests/FontTestBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ protected static void DeleteOutputFont(string fileName)
112112
[TestInitialize]
113113
public void ClearAllCaches()
114114
{
115-
OpenTypeFonts.ClearFontCache();
115+
//OpenTypeFonts.ClearFontCache();
116116
}
117117

118118
[ClassInitialize(InheritanceBehavior.BeforeEachDerivedClass)]

src/EPPlus.Fonts.OpenType.Tests/TextShaping/MarkToBaseTests.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,13 @@ public class MarkToBaseTests : FontTestBase
1717
[TestMethod]
1818
public void MarkToBaseTest()
1919
{
20-
var font = OpenTypeFonts.GetFontData(null, "Roboto", FontSubFamily.Regular, true);
20+
var font = OpenTypeFonts.GetFontData(null, "Roboto", FontSubFamily.Regular, true, true);
21+
Debug.WriteLine("=== MarkToBaseTest ===");
22+
Debug.WriteLine($"Font instance: {font.GetHashCode()}");
23+
Debug.WriteLine($"CmapTable instance: {font.CmapTable.GetHashCode()}");
24+
Debug.WriteLine($"SubTables count: {font.CmapTable.SubTables.Count}");
25+
for (int i = 0; i < font.CmapTable.SubTables.Count; i++)
26+
Debug.WriteLine($" SubTable[{i}]: Format={font.CmapTable.SubTables[i].Format} HashCode={font.CmapTable.SubTables[i].GetHashCode()}");
2127
var shaper = new TextShaper(font);
2228

2329
string test = "A\u0302\u0309";
@@ -31,6 +37,9 @@ public void MarkToBaseTest()
3137
Debug.WriteLine($"GID={g.GlyphId,-4} XAdv={g.XAdvance,-5} YOff={g.YOffset,-4}");
3238
}
3339

40+
Debug.WriteLine($"GPOS null? {font.GposTable == null}");
41+
Debug.WriteLine($"FullyLoaded? {font.FullyLoaded}"); //
42+
3443
Assert.IsTrue(shaped.Glyphs.Any(x => x.YOffset > 0),
3544
$"Expected YOffset > 0. Got: {string.Join(", ", shaped.Glyphs.Select(g => $"Y={g.YOffset}"))}");
3645
}

src/EPPlus.Fonts.OpenType/OpenTypeFont.cs

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,55 @@ internal OpenTypeFont(byte[] fontBytes, long startOffset, FontFormat format)
126126

127127
internal GlyfTableLoader _glyfTableLoader;
128128
internal KernTableLoader _kernTableLoader;
129+
private volatile bool _fullyLoaded = false;
130+
131+
public bool FullyLoaded => _fullyLoaded;
132+
internal bool IsReadOnly { get; set; }
133+
134+
internal void EnsureFullyLoaded()
135+
{
136+
if (_fullyLoaded)
137+
return;
138+
139+
lock (_loaderCache.SyncLock)
140+
{
141+
142+
// --- Required tables (always present in valid fonts) ---
143+
// Each property accessor calls its TableLoader.Load() which
144+
// reads from the byte[] stream and caches the result.
145+
// By accessing them all here under the font-level lock,
146+
// we guarantee no concurrent reader access.
147+
FontTableBase _ = CmapTable;
148+
_ = HeadTable;
149+
_ = HheaTable;
150+
_ = HmtxTable;
151+
_ = MaxpTable;
152+
_ = NameTable;
153+
_ = Os2Table;
154+
_ = PostTable;
155+
_ = LocaTable;
156+
157+
// --- Optional tables (only if present in font) ---
158+
if (_gsubTableLoader != null)
159+
{
160+
var gsub = GsubTable; // Forces full GSUB parse
161+
}
162+
if (_gposTableLoader != null)
163+
{
164+
var gpos = GposTable; // Forces full GPOS parse (incl. MarkToBase subtables)
165+
}
166+
if (_glyfTableLoader != null)
167+
{
168+
var glyf = GlyfTable; // Forces full glyph outline parse
169+
}
170+
if (_kernTableLoader != null)
171+
{
172+
var kern = KernTable; // Forces legacy kern table parse
173+
}
174+
175+
_fullyLoaded = true;
176+
}
177+
}
129178

130179
internal FontSerializationContext GetSerializationContext()
131180
{
@@ -488,6 +537,10 @@ private string GetNameString(NameRecordTypes recordType)
488537
internal void AddOrReplaceTable<T>(T table)
489538
where T : FontTableBase
490539
{
540+
if (IsReadOnly)
541+
throw new InvalidOperationException(
542+
$"Cannot modify a cached font instance. Table: {table.Name}. " +
543+
"Use CreateSubset() or create a new OpenTypeFont instance.");
491544
_localTableCache.AddOrReplace(table.Name, table);
492545

493546

@@ -509,25 +562,21 @@ internal void AddOrReplaceTable<T>(T table)
509562

510563
public OpenTypeFont CreateSubset(IEnumerable<char> usedChars)
511564
{
512-
// Validate input
513565
if (usedChars == null)
514566
throw new ArgumentNullException(nameof(usedChars));
515-
516567
var charArray = usedChars.ToArray();
517568
if (charArray.Length == 0)
518569
throw new ArgumentException("Text cannot be empty", nameof(usedChars));
519570

520571
var subsetBuilder = new SubsetFontBuilder();
521-
522-
// Convert chars to Unicode code points (handling surrogate pairs)
523572
var codePoints = CharacterUtil.ExtractCodePointsFromChars(charArray);
524573

525-
// Create subset font
574+
// Använd "this" direkt — IsReadOnly-skyddet på AddOrReplaceTable
575+
// garanterar att denna instans aldrig modifieras av subsetting.
526576
var newFont = subsetBuilder.CreateSubset(this, codePoints);
527577

528578
var postProcessor = new SubsetPostProcessor();
529579
postProcessor.PostProcessSubset(newFont);
530-
531580
return newFont;
532581
}
533582

src/EPPlus.Fonts.OpenType/OpenTypeFonts.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Date Author Change
1919
using EPPlus.Fonts.OpenType.Utils.Platform;
2020
using System;
2121
using System.Collections.Generic;
22+
using System.Diagnostics;
2223
using System.IO;
2324
using System.Linq;
2425

@@ -150,6 +151,8 @@ public static OpenTypeFont GetFontDataOpen(
150151
var cached = OpenTypeFontCache.GetFromCache(fontName, subFamily);
151152
if (cached != null && cached.Font != null && cached.IsLoaded)
152153
{
154+
cached.Font.EnsureFullyLoaded();
155+
Debug.WriteLine($"[CACHE HIT] {fontName}_{subFamily} → Font={cached.Font.GetHashCode()}, CmapSubTables={cached.Font.CmapTable?.SubTables?.Count}");
153156
return cached.Font;
154157
}
155158
}
@@ -168,10 +171,12 @@ public static OpenTypeFont GetFontDataOpen(
168171

169172
// Load the font from file
170173
var font = OpenTypeFontFactory.CreateFromFace(face);
174+
font.EnsureFullyLoaded();
171175

172176
// Add to cache and signal waiting threads
173177
if (!ignoreCache)
174178
{
179+
font.IsReadOnly = true;
175180
OpenTypeFontCache.AddToCache(font, fontName, subFamily);
176181
}
177182

src/EPPlus.Fonts.OpenType/TextShaping/TextShaper.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Date Author Change
2020
using OfficeOpenXml.Interfaces.Drawing.Text;
2121
using System;
2222
using System.Collections.Generic;
23+
using static System.Net.Mime.MediaTypeNames;
2324

2425
namespace EPPlus.Fonts.OpenType.TextShaping
2526
{
@@ -70,6 +71,8 @@ public TextShaper(IFontProvider fontProvider)
7071
if (fontProvider.PrimaryFont == null)
7172
throw new ArgumentException("Primary font cannot be null in font provider", "fontProvider");
7273

74+
var gposTable = fontProvider.PrimaryFont.GposTable; // Force load now - thread-safe via TableLoader
75+
7376
_fontProvider = fontProvider;
7477
_primaryFont = fontProvider.PrimaryFont;
7578

0 commit comments

Comments
 (0)