Skip to content

Commit 8600aa6

Browse files
Merge branch 'main' into js/fix-line-break
2 parents b64822e + 1ee650a commit 8600aa6

26 files changed

+533
-78
lines changed

src/SixLabors.Fonts/FileFontMetrics.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ internal override bool TryGetGlyphId(
107107
out bool skipNextCodePoint)
108108
=> this.fontMetrics.Value.TryGetGlyphId(codePoint, nextCodePoint, out glyphId, out skipNextCodePoint);
109109

110+
/// <inheritdoc/>
111+
internal override bool TryGetCodePoint(ushort glyphId, out CodePoint codePoint)
112+
=> this.fontMetrics.Value.TryGetCodePoint(glyphId, out codePoint);
113+
110114
/// <inheritdoc/>
111115
internal override bool TryGetGlyphClass(ushort glyphId, [NotNullWhen(true)] out GlyphClassDef? glyphClass)
112116
=> this.fontMetrics.Value.TryGetGlyphClass(glyphId, out glyphClass);

src/SixLabors.Fonts/FontMetrics.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,19 @@ internal FontMetrics()
141141
/// </returns>
142142
internal abstract bool TryGetGlyphId(CodePoint codePoint, CodePoint? nextCodePoint, out ushort glyphId, out bool skipNextCodePoint);
143143

144+
/// <summary>
145+
/// Gets the specified glyph id matching the codepoint.
146+
/// </summary>
147+
/// <param name="glyphId">The glyph identifier.</param>
148+
/// <param name="codePoint">
149+
/// When this method returns, contains the codepoint associated with the specified glyph id,
150+
/// if the glyph id is found; otherwise, <value>default</value>.
151+
/// </param>
152+
/// <returns>
153+
/// <see langword="true"/> if the face contains a codepoint for the specified glyph id; otherwise, <see langword="false"/>.
154+
/// </returns>
155+
internal abstract bool TryGetCodePoint(ushort glyphId, out CodePoint codePoint);
156+
144157
/// <summary>
145158
/// Tries to get the glyph class for a given glyph id.
146159
/// The font needs to have a GDEF table defined.

src/SixLabors.Fonts/GlyphPositioningCollection.cs

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,24 +79,48 @@ public void DisableShapingFeature(int index, Tag feature)
7979
/// </summary>
8080
/// <param name="offset">The zero-based index within the input codepoint collection.</param>
8181
/// <param name="pointSize">The font size in PT units of the font containing this glyph.</param>
82+
/// <param name="isSubstituted">Whether the glyph is the result of a substitution.</param>
83+
/// <param name="isVerticalSubstitution">Whether the glyph is the result of a vertical substitution.</param>
8284
/// <param name="isDecomposed">Whether the glyph is the result of a decomposition substitution.</param>
8385
/// <param name="metrics">
8486
/// When this method returns, contains the glyph metrics associated with the specified offset,
8587
/// if the value is found; otherwise, the default value for the type of the metrics parameter.
8688
/// This parameter is passed uninitialized.
8789
/// </param>
8890
/// <returns>The metrics.</returns>
89-
public bool TryGetGlyphMetricsAtOffset(int offset, out float pointSize, out bool isDecomposed, [NotNullWhen(true)] out IReadOnlyList<GlyphMetrics>? metrics)
91+
public bool TryGetGlyphMetricsAtOffset(
92+
int offset,
93+
out float pointSize,
94+
out bool isSubstituted,
95+
out bool isVerticalSubstitution,
96+
out bool isDecomposed,
97+
[NotNullWhen(true)] out IReadOnlyList<GlyphMetrics>? metrics)
9098
{
9199
List<GlyphMetrics> match = new();
92100
pointSize = 0;
101+
isSubstituted = false;
102+
isVerticalSubstitution = false;
93103
isDecomposed = false;
104+
105+
Tag vert = FeatureTags.VerticalAlternates;
106+
Tag vrt2 = FeatureTags.VerticalAlternatesAndRotation;
107+
Tag vrtr = FeatureTags.VerticalAlternatesForRotation;
108+
94109
for (int i = 0; i < this.glyphs.Count; i++)
95110
{
96111
if (this.glyphs[i].Offset == offset)
97112
{
98113
GlyphPositioningData glyph = this.glyphs[i];
114+
isSubstituted = glyph.Data.IsSubstituted;
99115
isDecomposed = glyph.Data.IsDecomposed;
116+
117+
foreach (Tag feature in glyph.Data.AppliedFeatures)
118+
{
119+
isVerticalSubstitution |= feature == vert;
120+
isVerticalSubstitution |= feature == vrt2;
121+
isVerticalSubstitution |= feature == vrtr;
122+
}
123+
100124
pointSize = glyph.PointSize;
101125
match.AddRange(glyph.Metrics);
102126
}
@@ -147,7 +171,7 @@ public bool TryUpdate(Font font, GlyphSubstitutionCollection collection)
147171

148172
// Perform a semi-deep clone (FontMetrics is not cloned) so we can continue to
149173
// cache the original in the font metrics and only update our collection.
150-
var metrics = new List<GlyphMetrics>(data.Count);
174+
List<GlyphMetrics> metrics = new(data.Count);
151175
TextAttributes textAttributes = shape.TextRun.TextAttributes;
152176
TextDecorations textDecorations = shape.TextRun.TextDecorations;
153177
foreach (GlyphMetrics gm in fontMetrics.GetGlyphMetrics(codePoint, id, textAttributes, textDecorations, layoutMode, colorFontSupport))
@@ -216,6 +240,10 @@ public bool TryAdd(Font font, GlyphSubstitutionCollection collection)
216240
LayoutMode layoutMode = this.TextOptions.LayoutMode;
217241
ColorFontSupport colorFontSupport = this.TextOptions.ColorFontSupport;
218242

243+
Tag vert = FeatureTags.VerticalAlternates;
244+
Tag vrt2 = FeatureTags.VerticalAlternatesAndRotation;
245+
Tag vrtr = FeatureTags.VerticalAlternatesForRotation;
246+
219247
for (int i = 0; i < collection.Count; i++)
220248
{
221249
GlyphShapingData data = collection.GetGlyphShapingData(i, out int offset);
@@ -227,7 +255,14 @@ public bool TryAdd(Font font, GlyphSubstitutionCollection collection)
227255
// cache the original in the font metrics and only update our collection.
228256
TextAttributes textAttributes = data.TextRun.TextAttributes;
229257
TextDecorations textDecorations = data.TextRun.TextDecorations;
230-
bool isVerticalLayout = AdvancedTypographicUtils.IsVerticalGlyph(codePoint, layoutMode);
258+
259+
bool isVertical = AdvancedTypographicUtils.IsVerticalGlyph(codePoint, layoutMode);
260+
foreach (Tag feature in data.AppliedFeatures)
261+
{
262+
isVertical |= feature == vert;
263+
isVertical |= feature == vrt2;
264+
isVertical |= feature == vrtr;
265+
}
231266

232267
foreach (GlyphMetrics gm in fontMetrics.GetGlyphMetrics(codePoint, id, textAttributes, textDecorations, layoutMode, colorFontSupport))
233268
{
@@ -242,7 +277,7 @@ public bool TryAdd(Font font, GlyphSubstitutionCollection collection)
242277
if (metrics.Count > 0)
243278
{
244279
GlyphMetrics[] gm = metrics.ToArray();
245-
if (isVerticalLayout)
280+
if (isVertical)
246281
{
247282
this.glyphs.Add(new(offset, new(data, true) { Bounds = new(0, 0, 0, gm[0].AdvanceHeight) }, font.Size, gm));
248283
}
@@ -302,11 +337,25 @@ public void UpdatePosition(FontMetrics fontMetrics, int index)
302337
public void Advance(FontMetrics fontMetrics, int index, ushort glyphId, short dx, short dy)
303338
{
304339
LayoutMode layoutMode = this.TextOptions.LayoutMode;
305-
foreach (GlyphMetrics m in this.glyphs[index].Metrics)
340+
Tag vert = FeatureTags.VerticalAlternates;
341+
Tag vrt2 = FeatureTags.VerticalAlternatesAndRotation;
342+
Tag vrtr = FeatureTags.VerticalAlternatesForRotation;
343+
344+
GlyphPositioningData glyph = this.glyphs[index];
345+
foreach (GlyphMetrics m in glyph.Metrics)
306346
{
307347
if (m.GlyphId == glyphId && fontMetrics == m.FontMetrics)
308348
{
309-
m.ApplyAdvance(dx, AdvancedTypographicUtils.IsVerticalGlyph(m.CodePoint, layoutMode) ? dy : (short)0);
349+
bool isVertical = AdvancedTypographicUtils.IsVerticalGlyph(m.CodePoint, layoutMode);
350+
351+
foreach (Tag feature in glyph.Data.AppliedFeatures)
352+
{
353+
isVertical |= feature == vert;
354+
isVertical |= feature == vrt2;
355+
isVertical |= feature == vrtr;
356+
}
357+
358+
m.ApplyAdvance(dx, isVertical ? dy : (short)0);
310359
}
311360
}
312361
}

src/SixLabors.Fonts/GlyphShapingData.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public GlyphShapingData(GlyphShapingData data, bool clearFeatures = false)
3737
this.LigatureComponent = data.LigatureComponent;
3838
this.MarkAttachment = data.MarkAttachment;
3939
this.CursiveAttachment = data.CursiveAttachment;
40+
this.IsSubstituted = data.IsSubstituted;
4041
this.IsDecomposed = data.IsDecomposed;
4142
if (data.UniversalShapingEngineInfo != null)
4243
{
@@ -57,9 +58,11 @@ public GlyphShapingData(GlyphShapingData data, bool clearFeatures = false)
5758

5859
if (!clearFeatures)
5960
{
60-
this.Features = new(data.Features);
61+
this.Features.AddRange(data.Features);
6162
}
6263

64+
this.AppliedFeatures.AddRange(data.AppliedFeatures);
65+
6366
this.Bounds = data.Bounds;
6467
}
6568

@@ -116,7 +119,12 @@ public GlyphShapingData(GlyphShapingData data, bool clearFeatures = false)
116119
/// <summary>
117120
/// Gets or sets the collection of features.
118121
/// </summary>
119-
public List<TagEntry> Features { get; set; } = new List<TagEntry>();
122+
public List<TagEntry> Features { get; set; } = new();
123+
124+
/// <summary>
125+
/// Gets or sets the collection of applied features.
126+
/// </summary>
127+
public List<Tag> AppliedFeatures { get; set; } = new();
120128

121129
/// <summary>
122130
/// Gets or sets the shaping bounds.

src/SixLabors.Fonts/GlyphSubstitutionCollection.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,8 @@ public bool TryGetGlyphShapingDataAtOffset(int offset, [NotNullWhen(true)] out I
225225
/// </summary>
226226
/// <param name="index">The zero-based index of the element to replace.</param>
227227
/// <param name="glyphId">The replacement glyph id.</param>
228-
public void Replace(int index, ushort glyphId)
228+
/// <param name="feature">The feature to apply to the glyph at the specified index.</param>
229+
public void Replace(int index, ushort glyphId, Tag feature)
229230
{
230231
GlyphShapingData current = this.glyphs[index].Data;
231232
current.GlyphId = glyphId;
@@ -234,6 +235,7 @@ public void Replace(int index, ushort glyphId)
234235
current.MarkAttachment = -1;
235236
current.CursiveAttachment = -1;
236237
current.IsSubstituted = true;
238+
current.AppliedFeatures.Add(feature);
237239
}
238240

239241
/// <summary>
@@ -243,7 +245,8 @@ public void Replace(int index, ushort glyphId)
243245
/// <param name="removalIndices">The indices at which to remove elements.</param>
244246
/// <param name="glyphId">The replacement glyph id.</param>
245247
/// <param name="ligatureId">The ligature id.</param>
246-
public void Replace(int index, ReadOnlySpan<int> removalIndices, ushort glyphId, int ligatureId)
248+
/// <param name="feature">The feature to apply to the glyph at the specified index.</param>
249+
public void Replace(int index, ReadOnlySpan<int> removalIndices, ushort glyphId, int ligatureId, Tag feature)
247250
{
248251
// Remove the glyphs at each index.
249252
int codePointCount = 0;
@@ -279,6 +282,7 @@ public void Replace(int index, ReadOnlySpan<int> removalIndices, ushort glyphId,
279282
current.MarkAttachment = -1;
280283
current.CursiveAttachment = -1;
281284
current.IsSubstituted = true;
285+
current.AppliedFeatures.Add(feature);
282286
}
283287

284288
/// <summary>
@@ -287,7 +291,8 @@ public void Replace(int index, ReadOnlySpan<int> removalIndices, ushort glyphId,
287291
/// <param name="index">The zero-based index of the element to replace.</param>
288292
/// <param name="count">The number of glyphs to remove.</param>
289293
/// <param name="glyphId">The replacement glyph id.</param>
290-
public void Replace(int index, int count, ushort glyphId)
294+
/// <param name="feature">The feature to apply to the glyph at the specified index.</param>
295+
public void Replace(int index, int count, ushort glyphId, Tag feature)
291296
{
292297
// Remove the glyphs at each index.
293298
int codePointCount = 0;
@@ -322,14 +327,16 @@ public void Replace(int index, int count, ushort glyphId)
322327
current.MarkAttachment = -1;
323328
current.CursiveAttachment = -1;
324329
current.IsSubstituted = true;
330+
current.AppliedFeatures.Add(feature);
325331
}
326332

327333
/// <summary>
328334
/// Replaces a single glyph id with a collection of glyph ids.
329335
/// </summary>
330336
/// <param name="index">The zero-based index of the element to replace.</param>
331337
/// <param name="glyphIds">The collection of replacement glyph ids.</param>
332-
public void Replace(int index, ReadOnlySpan<ushort> glyphIds)
338+
/// <param name="feature">The feature to apply to the glyph at the specified index.</param>
339+
public void Replace(int index, ReadOnlySpan<ushort> glyphIds, Tag feature)
333340
{
334341
if (glyphIds.Length > 0)
335342
{
@@ -345,7 +352,7 @@ public void Replace(int index, ReadOnlySpan<ushort> glyphIds)
345352
// Add additional glyphs from the rest of the sequence.
346353
if (glyphIds.Length > 1)
347354
{
348-
glyphIds = glyphIds.Slice(1);
355+
glyphIds = glyphIds[1..];
349356
for (int i = 0; i < glyphIds.Length; i++)
350357
{
351358
GlyphShapingData data = new(current, false)
@@ -354,6 +361,8 @@ public void Replace(int index, ReadOnlySpan<ushort> glyphIds)
354361
LigatureComponent = i + 1
355362
};
356363

364+
data.AppliedFeatures.Add(feature);
365+
357366
this.glyphs.Insert(++index, new(pair.Offset, data));
358367
}
359368
}

src/SixLabors.Fonts/StreamFontMetrics.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ internal partial class StreamFontMetrics : FontMetrics
3232
private readonly ConcurrentDictionary<(int CodePoint, ushort Id, TextAttributes Attributes, bool IsVerticalLayout), GlyphMetrics[]> glyphCache;
3333
private readonly ConcurrentDictionary<(int CodePoint, ushort Id, TextAttributes Attributes, bool IsVerticalLayout), GlyphMetrics[]>? colorGlyphCache;
3434
private readonly ConcurrentDictionary<(int CodePoint, int NextCodePoint), (bool Success, ushort GlyphId, bool SkipNextCodePoint)> glyphIdCache;
35+
private readonly ConcurrentDictionary<ushort, (bool Success, CodePoint CodePoint)> codePointCache;
3536
private readonly FontDescription description;
3637
private readonly HorizontalMetrics horizontalMetrics;
3738
private readonly VerticalMetrics verticalMetrics;
@@ -61,6 +62,7 @@ internal StreamFontMetrics(TrueTypeFontTables tables)
6162
this.outlineType = OutlineType.TrueType;
6263
this.description = new FontDescription(tables.Name, tables.Os2, tables.Head);
6364
this.glyphIdCache = new();
65+
this.codePointCache = new();
6466
this.glyphCache = new();
6567
if (tables.Colr is not null)
6668
{
@@ -82,6 +84,7 @@ internal StreamFontMetrics(CompactFontTables tables)
8284
this.outlineType = OutlineType.CFF;
8385
this.description = new FontDescription(tables.Name, tables.Os2, tables.Head);
8486
this.glyphIdCache = new();
87+
this.codePointCache = new();
8588
this.glyphCache = new();
8689
if (tables.Colr is not null)
8790
{
@@ -174,6 +177,26 @@ internal override bool TryGetGlyphId(CodePoint codePoint, CodePoint? nextCodePoi
174177
return success;
175178
}
176179

180+
/// <inheritdoc/>
181+
internal override bool TryGetCodePoint(ushort glyphId, out CodePoint codePoint)
182+
{
183+
CMapTable cmap = this.outlineType == OutlineType.TrueType
184+
? this.trueTypeFontTables!.Cmap
185+
: this.compactFontTables!.Cmap;
186+
187+
(bool success, CodePoint value) = this.codePointCache.GetOrAdd(
188+
glyphId,
189+
static (glyphId, arg) =>
190+
{
191+
bool success = arg.TryGetCodePoint(glyphId, out CodePoint codePoint);
192+
return (success, codePoint);
193+
},
194+
cmap);
195+
196+
codePoint = value;
197+
return success;
198+
}
199+
177200
/// <inheritdoc/>
178201
internal override bool TryGetGlyphClass(ushort glyphId, [NotNullWhen(true)] out GlyphClassDef? glyphClass)
179202
{

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public override bool TrySubstitution(
7474

7575
if (this.coverageTable.CoverageIndexOf(glyphId) > -1)
7676
{
77-
collection.Replace(index, (ushort)(glyphId + this.deltaGlyphId));
77+
collection.Replace(index, (ushort)(glyphId + this.deltaGlyphId), feature);
7878
return true;
7979
}
8080

@@ -135,7 +135,7 @@ public override bool TrySubstitution(
135135

136136
if (offset > -1)
137137
{
138-
collection.Replace(index, this.substituteGlyphs[offset]);
138+
collection.Replace(index, this.substituteGlyphs[offset], feature);
139139
return true;
140140
}
141141

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public override bool TrySubstitution(
9898

9999
if (offset > -1)
100100
{
101-
collection.Replace(index, this.sequenceTables[offset].SubstituteGlyphs);
101+
collection.Replace(index, this.sequenceTables[offset].SubstituteGlyphs, feature);
102102
return true;
103103
}
104104

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ public override bool TrySubstitution(
100100
// TODO: We're just choosing the first alternative here.
101101
// It looks like the choice is arbitrary and should be determined by
102102
// the client.
103-
collection.Replace(index, this.alternateSetTables[offset].AlternateGlyphs[0]);
103+
collection.Replace(index, this.alternateSetTables[offset].AlternateGlyphs[0], feature);
104104
return true;
105105
}
106106

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ public override bool TrySubstitution(
249249
}
250250

251251
// Delete the matched glyphs, and replace the current glyph with the ligature glyph
252-
collection.Replace(index, matches, ligatureTable.GlyphId, ligatureId);
252+
collection.Replace(index, matches, ligatureTable.GlyphId, ligatureId, feature);
253253
return true;
254254
}
255255

0 commit comments

Comments
 (0)