Skip to content

Commit aefb967

Browse files
Merge pull request #478 from SixLabors/js/fix-475
Choose correct shaper and avoid infinite loop
2 parents 3a21143 + 2fa5202 commit aefb967

File tree

14 files changed

+118
-21
lines changed

14 files changed

+118
-21
lines changed

src/SixLabors.Fonts/GlyphPositioningCollection.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ public bool TryUpdate(Font font, GlyphSubstitutionCollection collection)
199199

200200
if (metrics.GlyphType != GlyphType.Fallback)
201201
{
202-
if (j == 0)
202+
if (replacementCount == 0)
203203
{
204204
// There should only be a single fallback glyph at this position from the previous collection.
205205
this.glyphs.RemoveAt(i);

src/SixLabors.Fonts/Tables/AdvancedTypographic/GPosTable.cs

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,11 @@ internal static GPosTable Load(BigEndianBinaryReader reader)
8989
uint featureVariationsOffset = (minorVersion == 1) ? reader.ReadOffset32() : 0;
9090

9191
// TODO: Optimization. Allow only reading the scriptList.
92-
var scriptList = ScriptList.Load(reader, scriptListOffset);
92+
ScriptList? scriptList = ScriptList.Load(reader, scriptListOffset);
9393

94-
var featureList = FeatureListTable.Load(reader, featureListOffset);
94+
FeatureListTable featureList = FeatureListTable.Load(reader, featureListOffset);
9595

96-
var lookupList = LookupListTable.Load(reader, lookupListOffset);
96+
LookupListTable lookupList = LookupListTable.Load(reader, lookupListOffset);
9797

9898
// TODO: Feature Variations.
9999
return new GPosTable(scriptList, featureList, lookupList);
@@ -116,7 +116,7 @@ public bool TryUpdatePositions(FontMetrics fontMetrics, GlyphPositioningCollecti
116116
continue;
117117
}
118118

119-
ScriptClass current = CodePoint.GetScriptClass(collection[i].CodePoint);
119+
ScriptClass current = this.GetScriptClass(CodePoint.GetScriptClass(collection[i].CodePoint));
120120

121121
int index = i;
122122
int count = 1;
@@ -132,7 +132,7 @@ public bool TryUpdatePositions(FontMetrics fontMetrics, GlyphPositioningCollecti
132132
break;
133133
}
134134

135-
ScriptClass next = CodePoint.GetScriptClass(nextData.CodePoint);
135+
ScriptClass next = this.GetScriptClass(CodePoint.GetScriptClass(nextData.CodePoint));
136136
if (next != current &&
137137
current is not ScriptClass.Common and not ScriptClass.Unknown and not ScriptClass.Inherited &&
138138
next is not ScriptClass.Common and not ScriptClass.Unknown and not ScriptClass.Inherited)
@@ -278,7 +278,7 @@ private Tag GetUnicodeScriptTag(ScriptClass script)
278278

279279
private List<(Tag Feature, ushort Index, LookupTable LookupTable)> GetFeatureLookups(in Tag stageFeature, params LangSysTable[] langSysTables)
280280
{
281-
List<(Tag Feature, ushort Index, LookupTable LookupTable)> lookups = new();
281+
List<(Tag Feature, ushort Index, LookupTable LookupTable)> lookups = [];
282282
for (int i = 0; i < langSysTables.Length; i++)
283283
{
284284
ushort[] featureIndices = langSysTables[i].FeatureIndices;
@@ -306,6 +306,32 @@ private Tag GetUnicodeScriptTag(ScriptClass script)
306306
return lookups;
307307
}
308308

309+
private ScriptClass GetScriptClass(ScriptClass current)
310+
{
311+
if (current is ScriptClass.Common or ScriptClass.Unknown or ScriptClass.Inherited)
312+
{
313+
return current;
314+
}
315+
316+
if (this.ScriptList is null)
317+
{
318+
return ScriptClass.Default;
319+
}
320+
321+
Tag[] tags = UnicodeScriptTagMap.Instance[current];
322+
323+
for (int i = 0; i < tags.Length; i++)
324+
{
325+
if (this.ScriptList.TryGetValue(tags[i].Value, out ScriptListTable? _))
326+
{
327+
return current;
328+
}
329+
}
330+
331+
// Script for `current` not present in the font: use default shaper.
332+
return ScriptClass.Default;
333+
}
334+
309335
private static bool HasFeature(List<TagEntry> glyphFeatures, in Tag feature)
310336
{
311337
for (int i = 0; i < glyphFeatures.Count; i++)

src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType2SubTable.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public static LookupSubTable Load(BigEndianBinaryReader reader, long offset, Loo
2323
}
2424
}
2525

26-
internal class LookupType2Format1SubTable : LookupSubTable
26+
internal sealed class LookupType2Format1SubTable : LookupSubTable
2727
{
2828
private readonly SequenceTable[] sequenceTables;
2929
private readonly CoverageTable coverageTable;
@@ -58,7 +58,7 @@ public static LookupType2Format1SubTable Load(BigEndianBinaryReader reader, long
5858
Span<ushort> sequenceOffsets = sequenceOffsetsBuffer.GetSpan();
5959
reader.ReadUInt16Array(sequenceOffsets);
6060

61-
var sequenceTables = new SequenceTable[sequenceCount];
61+
SequenceTable[] sequenceTables = new SequenceTable[sequenceCount];
6262
for (int i = 0; i < sequenceTables.Length; i++)
6363
{
6464
// Sequence Table
@@ -75,7 +75,7 @@ public static LookupType2Format1SubTable Load(BigEndianBinaryReader reader, long
7575
sequenceTables[i] = new SequenceTable(reader.ReadUInt16Array(glyphCount));
7676
}
7777

78-
var coverageTable = CoverageTable.Load(reader, offset + coverageOffset);
78+
CoverageTable coverageTable = CoverageTable.Load(reader, offset + coverageOffset);
7979

8080
return new LookupType2Format1SubTable(sequenceTables, coverageTable, lookupFlags);
8181
}

src/SixLabors.Fonts/Tables/AdvancedTypographic/GSubTable.cs

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,11 @@ internal static GSubTable Load(BigEndianBinaryReader reader)
8585
uint featureVariationsOffset = (minorVersion == 1) ? reader.ReadOffset32() : 0;
8686

8787
// TODO: Optimization. Allow only reading the scriptList.
88-
var scriptList = ScriptList.Load(reader, scriptListOffset);
88+
ScriptList? scriptList = ScriptList.Load(reader, scriptListOffset);
8989

90-
var featureList = FeatureListTable.Load(reader, featureListOffset);
90+
FeatureListTable featureList = FeatureListTable.Load(reader, featureListOffset);
9191

92-
var lookupList = LookupListTable.Load(reader, lookupListOffset);
92+
LookupListTable lookupList = LookupListTable.Load(reader, lookupListOffset);
9393

9494
// TODO: Feature Variations.
9595
return new GSubTable(scriptList, featureList, lookupList);
@@ -106,7 +106,7 @@ public void ApplySubstitution(FontMetrics fontMetrics, GlyphSubstitutionCollecti
106106
{
107107
// Choose a shaper based on the script.
108108
// This determines which features to apply to which glyphs.
109-
ScriptClass current = CodePoint.GetScriptClass(collection[i].CodePoint);
109+
ScriptClass current = this.GetScriptClass(CodePoint.GetScriptClass(collection[i].CodePoint));
110110

111111
int index = i;
112112
int count = 1;
@@ -115,7 +115,7 @@ public void ApplySubstitution(FontMetrics fontMetrics, GlyphSubstitutionCollecti
115115
// We want to assign the same feature lookups to individual sections of the text rather
116116
// than the text as a whole to ensure that different language shapers do not interfere
117117
// with each other when the text contains multiple languages.
118-
ScriptClass next = CodePoint.GetScriptClass(collection[i + 1].CodePoint);
118+
ScriptClass next = this.GetScriptClass(CodePoint.GetScriptClass(collection[i + 1].CodePoint));
119119
if (next != current &&
120120
current is not ScriptClass.Common and not ScriptClass.Unknown and not ScriptClass.Inherited &&
121121
next is not ScriptClass.Common and not ScriptClass.Unknown and not ScriptClass.Inherited)
@@ -291,7 +291,7 @@ private Tag GetUnicodeScriptTag(ScriptClass script)
291291

292292
private List<(Tag Feature, ushort Index, LookupTable LookupTable)> GetFeatureLookups(in Tag stageFeature, params LangSysTable[] langSysTables)
293293
{
294-
List<(Tag Feature, ushort Index, LookupTable LookupTable)> lookups = new();
294+
List<(Tag Feature, ushort Index, LookupTable LookupTable)> lookups = [];
295295
for (int i = 0; i < langSysTables.Length; i++)
296296
{
297297
ushort[] featureIndices = langSysTables[i].FeatureIndices;
@@ -319,6 +319,32 @@ private Tag GetUnicodeScriptTag(ScriptClass script)
319319
return lookups;
320320
}
321321

322+
private ScriptClass GetScriptClass(ScriptClass current)
323+
{
324+
if (current is ScriptClass.Common or ScriptClass.Unknown or ScriptClass.Inherited)
325+
{
326+
return current;
327+
}
328+
329+
if (this.ScriptList is null)
330+
{
331+
return ScriptClass.Default;
332+
}
333+
334+
Tag[] tags = UnicodeScriptTagMap.Instance[current];
335+
336+
for (int i = 0; i < tags.Length; i++)
337+
{
338+
if (this.ScriptList.TryGetValue(tags[i].Value, out ScriptListTable? _))
339+
{
340+
return current;
341+
}
342+
}
343+
344+
// Script for `current` not present in the font: use default shaper.
345+
return ScriptClass.Default;
346+
}
347+
322348
private static bool HasFeature(List<TagEntry> glyphFeatures, in Tag feature)
323349
{
324350
for (int i = 0; i < glyphFeatures.Count; i++)

src/SixLabors.Fonts/Tables/AdvancedTypographic/UnicodeScriptTagMap.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace SixLabors.Fonts.Tables.AdvancedTypographic;
1111
/// </summary>
1212
internal sealed class UnicodeScriptTagMap : Dictionary<ScriptClass, Tag[]>
1313
{
14-
private static readonly Lazy<UnicodeScriptTagMap> Lazy = new(() => CreateMap(), true);
14+
private static readonly Lazy<UnicodeScriptTagMap> Lazy = new(CreateMap, true);
1515

1616
/// <summary>
1717
/// Prevents a default instance of the <see cref="UnicodeScriptTagMap"/> class from being created.
@@ -58,6 +58,7 @@ private static UnicodeScriptTagMap CreateMap()
5858
{ ScriptClass.CyproMinoan, new[] { Tag.Parse("cpmn") } },
5959
{ ScriptClass.Cypriot, new[] { Tag.Parse("cprt") } },
6060
{ ScriptClass.Cyrillic, new[] { Tag.Parse("cyrl") } },
61+
{ ScriptClass.Default, new[] { Tag.Parse("DFLT"), Tag.Parse("dflt"), Tag.Parse("latn") } },
6162
{ ScriptClass.Devanagari, new[] { Tag.Parse("dev2"), Tag.Parse("deva") } },
6263
{ ScriptClass.DivesAkuru, new[] { Tag.Parse("diak") } },
6364
{ ScriptClass.Dogra, new[] { Tag.Parse("dogr") } },

src/SixLabors.Fonts/Unicode/ScriptClass.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -822,4 +822,9 @@ public enum ScriptClass
822822
/// Shortcode: Zanb
823823
/// </summary>
824824
ZanabazarSquare,
825+
826+
/// <summary>
827+
/// Shortcode: DFLT
828+
/// </summary>
829+
Default = 999
825830
}

tests/SixLabors.Fonts.Tests/FontMetricsTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public void FontMetricsVerticalFontMatchesReference()
5353
// Compared to EveryFonts TTFDump metrics
5454
// https://everythingfonts.com/ttfdump
5555
FontCollection collection = new();
56-
FontFamily family = collection.Add(TestFonts.NotoSansSCThinFile);
56+
FontFamily family = collection.Add(TestFonts.NotoSansSCThinBad);
5757
Font font = family.CreateFont(12);
5858

5959
Assert.Equal(1000, font.FontMetrics.UnitsPerEm);
@@ -240,7 +240,7 @@ public void GlyphMetricsVerticalMatchesReference()
240240
// Compared to EveryFonts TTFDump metrics
241241
// https://everythingfonts.com/ttfdump
242242
FontCollection collection = new();
243-
FontFamily family = collection.Add(TestFonts.NotoSansSCThinFile);
243+
FontFamily family = collection.Add(TestFonts.NotoSansSCThinBad);
244244
Font font = family.CreateFont(12);
245245

246246
CodePoint codePoint = new('A');
45.2 KB
Binary file not shown.
-6.91 MB
Binary file not shown.
6.28 KB
Binary file not shown.

0 commit comments

Comments
 (0)