Skip to content

Commit fd8f253

Browse files
Merge pull request #491 from SixLabors/js/fix-488
Fix various shaping issues.
2 parents dd78e5d + 3a08b3d commit fd8f253

File tree

104 files changed

+3613
-1737
lines changed

Some content is hidden

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

104 files changed

+3613
-1737
lines changed

src/SixLabors.Fonts/FileFontMetrics.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ internal override bool TryGetGlyphClass(ushort glyphId, [NotNullWhen(true)] out
119119
internal override bool TryGetMarkAttachmentClass(ushort glyphId, [NotNullWhen(true)] out GlyphClassDef? markAttachmentClass)
120120
=> this.fontMetrics.Value.TryGetMarkAttachmentClass(glyphId, out markAttachmentClass);
121121

122+
/// <inheritdoc/>
123+
internal override bool IsInMarkFilteringSet(ushort markGlyphSetIndex, ushort glyphId)
124+
=> this.fontMetrics.Value.IsInMarkFilteringSet(markGlyphSetIndex, glyphId);
125+
122126
/// <inheritdoc />
123127
public override bool TryGetGlyphMetrics(
124128
CodePoint codePoint,

src/SixLabors.Fonts/FontMetrics.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,17 @@ internal FontMetrics()
172172
/// <returns>true, if the mark attachment class could be retrieved.</returns>
173173
internal abstract bool TryGetMarkAttachmentClass(ushort glyphId, [NotNullWhen(true)] out GlyphClassDef? markAttachmentClass);
174174

175+
/// <summary>
176+
/// Returns a value indicating whether the specified glyph is in the given mark filtering set.
177+
/// The font needs to have a GDEF table defined.
178+
/// </summary>
179+
/// <param name="markGlyphSetIndex">The mark glyph set index.</param>
180+
/// <param name="glyphId">The glyph identifier.</param>
181+
/// <returns>
182+
/// true, if the glyph is in the mark filtering set.
183+
/// </returns>
184+
internal abstract bool IsInMarkFilteringSet(ushort markGlyphSetIndex, ushort glyphId);
185+
175186
/// <summary>
176187
/// Gets the glyph metrics for a given code point.
177188
/// </summary>

src/SixLabors.Fonts/GlyphLayout.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ internal GlyphLayout(
100100

101101
internal FontRectangle BoundingBox(float dpi)
102102
{
103-
// Same logic as in TrueTypeGlyphMetrics.RenderTo
103+
// Same logic as in GlyphMetrics.RenderTo
104104
Vector2 location = this.PenLocation;
105105
Vector2 offset = this.Offset;
106106

src/SixLabors.Fonts/GlyphShapingData.cs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
using System.Diagnostics;
55
using SixLabors.Fonts.Tables.AdvancedTypographic;
66
using SixLabors.Fonts.Unicode;
7-
using SixLabors.Fonts.Unicode.Resources;
7+
using static SixLabors.Fonts.Unicode.Resources.IndicShapingData;
88

99
namespace SixLabors.Fonts;
1010

@@ -188,16 +188,16 @@ public UniversalShapingEngineInfo(string category, string syllableType, int syll
188188

189189
public string Category { get; set; }
190190

191-
public string SyllableType { get; }
191+
public string SyllableType { get; set; }
192192

193-
public int Syllable { get; }
193+
public int Syllable { get; set; }
194194
}
195195

196196
internal class IndicShapingEngineInfo
197197
{
198198
public IndicShapingEngineInfo(
199-
IndicShapingData.Categories category,
200-
IndicShapingData.Positions position,
199+
Categories category,
200+
Positions position,
201201
string syllableType,
202202
int syllable)
203203
{
@@ -207,11 +207,13 @@ public IndicShapingEngineInfo(
207207
this.Syllable = syllable;
208208
}
209209

210-
public IndicShapingData.Categories Category { get; set; }
210+
public Categories Category { get; set; }
211211

212-
public IndicShapingData.Positions Position { get; set; }
212+
public MyanmarCategories MyanmarCategory => (MyanmarCategories)this.Category;
213213

214-
public string SyllableType { get; }
214+
public Positions Position { get; set; }
215215

216-
public int Syllable { get; }
216+
public string SyllableType { get; set; }
217+
218+
public int Syllable { get; set; }
217219
}

src/SixLabors.Fonts/GlyphSubstitutionCollection.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,41 @@ public void MoveGlyph(int fromIndex, int toIndex)
152152
this.glyphs[toIndex].Data = data;
153153
}
154154

155+
/// <summary>
156+
/// Reverses the order of elements in the specified range of the collection.
157+
/// </summary>
158+
/// <remarks>
159+
/// The range is interpreted as half-open, from <paramref name="startIndex"/> (inclusive)
160+
/// to <paramref name="endIndex"/> (exclusive).
161+
///
162+
/// Both indices are clamped to the valid range [0, <see cref="Count"/>].
163+
/// If the resulting range contains fewer than two elements, the method performs no action.
164+
/// The method does not throw if either index is equal to <see cref="Count"/>; in such
165+
/// cases the range is considered valid but may be empty.
166+
/// </remarks>
167+
/// <param name="startIndex">
168+
/// The zero-based index at which to start reversing (inclusive). This value should be
169+
/// greater than or equal to 0. Values greater than <see cref="Count"/> are treated as
170+
/// <see cref="Count"/>.
171+
/// </param>
172+
/// <param name="endIndex">
173+
/// The zero-based index at which to stop reversing (exclusive). This value should be
174+
/// greater than or equal to <paramref name="startIndex"/>. Values greater than
175+
/// <see cref="Count"/> are treated as <see cref="Count"/>.
176+
/// </param>
177+
public void ReverseRange(int startIndex, int endIndex)
178+
{
179+
int s = Math.Min(startIndex, this.Count);
180+
int e = Math.Min(endIndex, this.Count);
181+
182+
if (e < s + 2)
183+
{
184+
return;
185+
}
186+
187+
this.glyphs.Reverse(s, e - s);
188+
}
189+
155190
/// <summary>
156191
/// Performs a stable sort of the glyphs by the comparison delegate starting at the specified index.
157192
/// </summary>
@@ -375,6 +410,12 @@ public void Replace(int index, ReadOnlySpan<ushort> glyphIds, Tag feature)
375410
}
376411
}
377412

413+
public void Insert(int index, GlyphShapingData data)
414+
{
415+
OffsetGlyphDataPair pair = this.glyphs[index];
416+
this.glyphs.Insert(index, new(pair.Offset, data));
417+
}
418+
378419
[DebuggerDisplay("{DebuggerDisplay,nq}")]
379420
private class OffsetGlyphDataPair
380421
{

src/SixLabors.Fonts/StreamFontMetrics.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,16 @@ internal override bool TryGetMarkAttachmentClass(ushort glyphId, [NotNullWhen(tr
212212
return gdef is not null && gdef.TryGetMarkAttachmentClass(glyphId, out markAttachmentClass);
213213
}
214214

215+
/// <inheritdoc/>
216+
internal override bool IsInMarkFilteringSet(ushort markGlyphSetIndex, ushort glyphId)
217+
{
218+
GlyphDefinitionTable? gdef = this.outlineType == OutlineType.TrueType
219+
? this.trueTypeFontTables!.Gdef
220+
: this.compactFontTables!.Gdef;
221+
222+
return gdef is not null && gdef.IsInMarkGlyphSet(markGlyphSetIndex, glyphId);
223+
}
224+
215225
/// <inheritdoc/>
216226
public override bool TryGetGlyphMetrics(
217227
CodePoint codePoint,

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

Lines changed: 111 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ internal static class AdvancedTypographicUtils
1818
private const int MaxOperationsMinimum = 16384;
1919
private const int MaxShapingCharsLength = 0x3FFFFFFF; // Half int max.
2020

21+
internal enum MatchDirection
22+
{
23+
Forward,
24+
Backward
25+
}
26+
2127
/// <summary>
2228
/// Gets a value indicating whether the glyph represented by the codepoint should be interpreted vertically.
2329
/// </summary>
@@ -46,12 +52,13 @@ public static bool ApplyLookupList(
4652
GSubTable table,
4753
Tag feature,
4854
LookupFlags lookupFlags,
55+
ushort markFilteringSet,
4956
SequenceLookupRecord[] records,
5057
GlyphSubstitutionCollection collection,
5158
int index,
5259
int count)
5360
{
54-
SkippingGlyphIterator iterator = new(fontMetrics, collection, index, lookupFlags);
61+
SkippingGlyphIterator iterator = new(fontMetrics, collection, index, lookupFlags, markFilteringSet);
5562
int currentCount = collection.Count;
5663

5764
foreach (SequenceLookupRecord lookupRecord in records)
@@ -79,12 +86,13 @@ public static bool ApplyLookupList(
7986
GPosTable table,
8087
Tag feature,
8188
LookupFlags lookupFlags,
89+
ushort markFilteringSet,
8290
SequenceLookupRecord[] records,
8391
GlyphPositioningCollection collection,
8492
int index,
8593
int count)
8694
{
87-
SkippingGlyphIterator iterator = new(fontMetrics, collection, index, lookupFlags);
95+
SkippingGlyphIterator iterator = new(fontMetrics, collection, index, lookupFlags, markFilteringSet);
8896
foreach (SequenceLookupRecord lookupRecord in records)
8997
{
9098
ushort sequenceIndex = lookupRecord.SequenceIndex;
@@ -150,11 +158,29 @@ public static bool MatchClassSequence(
150158
public static bool MatchCoverageSequence(
151159
SkippingGlyphIterator iterator,
152160
CoverageTable[] coverageTable,
153-
int increment)
161+
int startIndex,
162+
int endExclusive)
154163
=> Match(
155-
increment,
164+
iterator,
165+
startIndex,
156166
coverageTable,
167+
MatchDirection.Forward,
168+
endExclusive,
169+
(component, data) => component.CoverageIndexOf(data.GlyphId) >= 0,
170+
default);
171+
172+
// Backtrack variant (spec: backtrack[0] matches i-1, then i-2...)
173+
public static bool MatchBacktrackCoverageSequence(
174+
SkippingGlyphIterator iterator,
175+
CoverageTable[] backtrack,
176+
int startIndex,
177+
int endExclusive)
178+
=> Match(
157179
iterator,
180+
startIndex,
181+
backtrack,
182+
MatchDirection.Backward,
183+
endExclusive,
158184
(component, data) => component.CoverageIndexOf(data.GlyphId) >= 0,
159185
default);
160186

@@ -212,32 +238,50 @@ public static bool ApplyChainedClassSequenceRule(
212238
public static bool CheckAllCoverages(
213239
FontMetrics fontMetrics,
214240
LookupFlags lookupFlags,
241+
ushort markFilteringSet,
215242
IGlyphShapingCollection collection,
216243
int index,
217244
int count,
218245
CoverageTable[] input,
219246
CoverageTable[] backtrack,
220247
CoverageTable[] lookahead)
221248
{
222-
// Check that there are enough context glyphs.
223-
if (index < backtrack.Length || input.Length + lookahead.Length > count)
249+
int endExclusive = index + count;
250+
251+
SkippingGlyphIterator iterator = new(fontMetrics, collection, index, lookupFlags, markFilteringSet);
252+
253+
// Compute backtrack start using skippy prev(), not index-1.
254+
int backtrackStart = index;
255+
if (backtrack.Length > 0)
224256
{
225-
return false;
257+
SkippingGlyphIterator backIt = iterator;
258+
backIt.Index = index;
259+
backtrackStart = backIt.Prev(); // first backtrack glyph (i-1 in skippy space)
226260
}
227261

228-
// Check all coverages: if any of them does not match, abort update.
229-
SkippingGlyphIterator iterator = new(fontMetrics, collection, index, lookupFlags);
230-
if (!MatchCoverageSequence(iterator, backtrack, -backtrack.Length))
262+
if (!MatchBacktrackCoverageSequence(iterator, backtrack, backtrackStart, endExclusive))
231263
{
232264
return false;
233265
}
234266

235-
if (!MatchCoverageSequence(iterator, input, 0))
267+
// Input starts at the current glyph position.
268+
if (!MatchCoverageSequence(iterator, input, index, endExclusive))
236269
{
237270
return false;
238271
}
239272

240-
if (!MatchCoverageSequence(iterator, lookahead, input.Length))
273+
// Compute lookahead start by advancing through the input sequence using skippy Next(),
274+
// not by raw index arithmetic.
275+
int lookaheadStart = index;
276+
if (lookahead.Length > 0)
277+
{
278+
SkippingGlyphIterator fwdIt = iterator;
279+
fwdIt.Index = index;
280+
fwdIt.Increment(input.Length); // advance input.Length steps in skippy space
281+
lookaheadStart = fwdIt.Index;
282+
}
283+
284+
if (!MatchCoverageSequence(iterator, lookahead, lookaheadStart, endExclusive))
241285
{
242286
return false;
243287
}
@@ -331,6 +375,9 @@ public static GlyphShapingClass GetGlyphShapingClass(FontMetrics fontMetrics, us
331375
return new GlyphShapingClass(isMark, isBase, isLigature, markAttachmentType);
332376
}
333377

378+
public static bool IsInMarkFilteringSet(FontMetrics fontMetrics, ushort markFilteringSet, ushort glyphId)
379+
=> fontMetrics.IsInMarkFilteringSet(markFilteringSet, glyphId);
380+
334381
private static bool Match<T>(
335382
int increment,
336383
T[] sequence,
@@ -367,4 +414,56 @@ private static bool Match<T>(
367414
iterator.Index = position;
368415
return i == sequence.Length;
369416
}
417+
418+
private static bool Match<T>(
419+
SkippingGlyphIterator iterator,
420+
int startIndex,
421+
T[] sequence,
422+
MatchDirection direction,
423+
int endExclusive,
424+
Func<T, GlyphShapingData, bool> condition,
425+
Span<int> matches)
426+
{
427+
if (sequence.Length == 0)
428+
{
429+
return true;
430+
}
431+
432+
int saved = iterator.Index;
433+
iterator.Index = startIndex;
434+
435+
IGlyphShapingCollection collection = iterator.Collection;
436+
int limit = Math.Min(endExclusive, collection.Count);
437+
438+
for (int i = 0; i < sequence.Length && i < MaxContextLength; i++)
439+
{
440+
if (iterator.Index < 0 || iterator.Index >= limit)
441+
{
442+
iterator.Index = saved;
443+
return false;
444+
}
445+
446+
GlyphShapingData data = collection[iterator.Index];
447+
if (!condition(sequence[i], data))
448+
{
449+
iterator.Index = saved;
450+
return false;
451+
}
452+
453+
if (matches.Length == MaxContextLength)
454+
{
455+
matches[i] = iterator.Index;
456+
}
457+
458+
if (i + 1 < sequence.Length)
459+
{
460+
iterator.Index = direction == MatchDirection.Forward
461+
? iterator.Next()
462+
: iterator.Prev();
463+
}
464+
}
465+
466+
iterator.Index = saved;
467+
return true;
468+
}
370469
}

0 commit comments

Comments
 (0)