Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/SixLabors.Fonts/GlyphSubstitutionCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ internal sealed class GlyphSubstitutionCollection : IGlyphShapingCollection
/// <summary>
/// Contains a map the index of a map within the collection, non-sequential codepoint offsets, and their glyph ids.
/// </summary>
private readonly List<OffsetGlyphDataPair> glyphs = new();
private readonly List<OffsetGlyphDataPair> glyphs = [];

/// <summary>
/// Initializes a new instance of the <see cref="GlyphSubstitutionCollection"/> class.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ current is not ScriptClass.Common and not ScriptClass.Unknown and not ScriptClas
}

Tag unicodeScriptTag = this.GetUnicodeScriptTag(current);
BaseShaper shaper = ShaperFactory.Create(current, unicodeScriptTag, collection.TextOptions);
BaseShaper shaper = ShaperFactory.Create(current, unicodeScriptTag, fontMetrics, collection.TextOptions);

if (shaper.MarkZeroingMode == MarkZeroingMode.PreGPos)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ current is not ScriptClass.Common and not ScriptClass.Unknown and not ScriptClas
}

Tag unicodeScriptTag = this.GetUnicodeScriptTag(current);
BaseShaper shaper = ShaperFactory.Create(current, unicodeScriptTag, collection.TextOptions);
BaseShaper shaper = ShaperFactory.Create(current, unicodeScriptTag, fontMetrics, collection.TextOptions);

// Plan substitution features for each glyph.
// Shapers can adjust the count during initialization and feature processing so we must capture
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,11 @@ internal sealed class HangulShaper : DefaultShaper
{ new byte[] { None, 0 }, new byte[] { None, 1 }, new byte[] { None, 0 }, new byte[] { None, 0 }, new byte[] { Decompose, 2 }, new byte[] { Decompose, 3 }, new byte[] { ToneMark, 0 } },
};

public HangulShaper(ScriptClass script, TextOptions textOptions)
private readonly FontMetrics fontMetrics;

public HangulShaper(ScriptClass script, TextOptions textOptions, FontMetrics fontMetrics)
: base(script, MarkZeroingMode.None, textOptions)
{
}
=> this.fontMetrics = fontMetrics;

/// <inheritdoc/>
protected override void PlanFeatures(IGlyphShapingCollection collection, int index, int count)
Expand Down Expand Up @@ -121,13 +122,13 @@ protected override void AssignFeatures(IGlyphShapingCollection collection, int i
case Compose:

// Found a decomposed syllable. Try to compose if supported by the font.
i = this.ComposeGlyph(substitutionCollection, data, i, type);
i = this.ComposeGlyph(substitutionCollection, i, type);
break;

case ToneMark:

// Got a valid syllable, followed by a tone mark. Move the tone mark to the beginning of the syllable.
ReOrderToneMark(substitutionCollection, data, i);
this.ReOrderToneMark(substitutionCollection, data, i);
break;

case Invalid:
Expand Down Expand Up @@ -172,8 +173,6 @@ protected override void AssignFeatures(IGlyphShapingCollection collection, int i
collection.EnableShapingFeature(i, VjmoTag);
collection.EnableShapingFeature(i, TjmoTag);
break;
default:
break;
}
}
}
Expand Down Expand Up @@ -216,7 +215,7 @@ private int DecomposeGlyph(GlyphSubstitutionCollection collection, GlyphShapingD
int l = (LBase + (s / VCount)) | 0;
int v = VBase + (s % VCount);

FontMetrics metrics = data.TextRun.Font!.FontMetrics;
FontMetrics metrics = this.fontMetrics;

// Don't decompose if all of the components are not available
if (!metrics.TryGetGlyphId(new(l), out ushort ljmo) ||
Expand Down Expand Up @@ -252,7 +251,7 @@ private int DecomposeGlyph(GlyphSubstitutionCollection collection, GlyphShapingD
return index + 2;
}

private int ComposeGlyph(GlyphSubstitutionCollection collection, GlyphShapingData data, int index, int type)
private int ComposeGlyph(GlyphSubstitutionCollection collection, int index, int type)
{
if (index == 0)
{
Expand Down Expand Up @@ -306,8 +305,7 @@ private int ComposeGlyph(GlyphSubstitutionCollection collection, GlyphShapingDat

// Replace with a composed glyph if supported by the font,
// otherwise apply the proper OpenType features to each component.
FontMetrics metrics = data.TextRun.Font!.FontMetrics;
if (metrics.TryGetGlyphId(s, out ushort id))
if (this.fontMetrics.TryGetGlyphId(s, out ushort id))
{
int del = prevType == V ? 3 : 2;
int idx = index - del + 1;
Expand Down Expand Up @@ -338,15 +336,14 @@ private int ComposeGlyph(GlyphSubstitutionCollection collection, GlyphShapingDat
// Sequence was originally <L,V>, which got combined earlier.
// Either the T was non-combining, or the LVT glyph wasn't supported.
// Decompose the glyph again and apply OT features.
data = collection[index - 1];
this.DecomposeGlyph(collection, data, index - 1);
this.DecomposeGlyph(collection, collection[index - 1], index - 1);
return index + 1;
}

return index;
}

private static void ReOrderToneMark(GlyphSubstitutionCollection collection, GlyphShapingData data, int index)
private void ReOrderToneMark(GlyphSubstitutionCollection collection, GlyphShapingData data, int index)
{
if (index == 0)
{
Expand All @@ -355,17 +352,15 @@ private static void ReOrderToneMark(GlyphSubstitutionCollection collection, Glyp

// Move tone mark to the beginning of the previous syllable, unless it is zero width
// We don't have access to the glyphs metrics as an array when substituting so we have to loop.
FontMetrics fontMetrics = data.TextRun.Font!.FontMetrics;
FontMetrics fontMetrics = this.fontMetrics;
TextAttributes textAttributes = data.TextRun.TextAttributes;
TextDecorations textDecorations = data.TextRun.TextDecorations;
LayoutMode layoutMode = collection.TextOptions.LayoutMode;
ColorFontSupport colorFontSupport = collection.TextOptions.ColorFontSupport;
if (fontMetrics.TryGetGlyphMetrics(data.CodePoint, textAttributes, textDecorations, layoutMode, colorFontSupport, out GlyphMetrics? metrics))
if (fontMetrics.TryGetGlyphMetrics(data.CodePoint, textAttributes, textDecorations, layoutMode, colorFontSupport, out GlyphMetrics? metrics)
&& metrics.AdvanceWidth == 0)
{
if (metrics.AdvanceWidth == 0)
{
return;
}
return;
}

GlyphShapingData prev = collection[index - 1];
Expand All @@ -376,20 +371,18 @@ private static void ReOrderToneMark(GlyphSubstitutionCollection collection, Glyp
private int InsertDottedCircle(GlyphSubstitutionCollection collection, GlyphShapingData data, int index)
{
bool after = false;
FontMetrics fontMetrics = data.TextRun.Font!.FontMetrics;
FontMetrics fontMetrics = this.fontMetrics;

if (fontMetrics.TryGetGlyphId(new(DottedCircle), out ushort id))
{
TextAttributes textAttributes = data.TextRun.TextAttributes;
TextDecorations textDecorations = data.TextRun.TextDecorations;
LayoutMode layoutMode = collection.TextOptions.LayoutMode;
ColorFontSupport colorFontSupport = collection.TextOptions.ColorFontSupport;
if (fontMetrics.TryGetGlyphMetrics(data.CodePoint, textAttributes, textDecorations, layoutMode, colorFontSupport, out GlyphMetrics? metrics))
if (fontMetrics.TryGetGlyphMetrics(data.CodePoint, textAttributes, textDecorations, layoutMode, colorFontSupport, out GlyphMetrics? metrics)
&& metrics.AdvanceWidth != 0)
{
if (metrics.AdvanceWidth != 0)
{
after = true;
}
after = true;
}

// If the tone mark is zero width, insert the dotted circle before, otherwise after
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,15 @@ internal sealed class IndicShaper : DefaultShaper
private const int DottedCircle = 0x25cc;

private readonly TextOptions textOptions;
private readonly FontMetrics fontMetrics;
private ShapingConfiguration indicConfiguration;
private readonly bool isOldSpec;

public IndicShaper(ScriptClass script, Tag unicodeScriptTag, TextOptions textOptions)
public IndicShaper(ScriptClass script, Tag unicodeScriptTag, TextOptions textOptions, FontMetrics fontMetrics)
: base(script, MarkZeroingMode.None, textOptions)
{
this.textOptions = textOptions;
this.fontMetrics = fontMetrics;

if (IndicConfigurations.TryGetValue(script, out ShapingConfiguration value))
{
Expand Down Expand Up @@ -101,23 +103,24 @@ protected override void AssignFeatures(IGlyphShapingCollection collection, int i
return;
}

FontMetrics fontMetrics = this.fontMetrics;

// Decompose split matras
Span<ushort> buffer = stackalloc ushort[16];
int end = index + count;
for (int i = end - 1; i >= 0; i--)
{
GlyphShapingData data = substitutionCollection[i];
FontMetrics fontMetrics = data.TextRun.Font!.FontMetrics;

if ((Decompositions.TryGetValue(data.CodePoint.Value, out int[]? decompositions) ||
UniversalShapingData.Decompositions.TryGetValue(data.CodePoint.Value, out decompositions)) &&
decompositions != null)
{
Span<ushort> ids = buffer[..decompositions.Length];
for (int j = 0; j < decompositions.Length; j++)
{
// Font should always contain the decomposed glyph.
fontMetrics.TryGetGlyphId(new CodePoint(decompositions[j]), out ushort id);
// Font should always contain the decomposed glyph since the shaper
// is assigned based on features supported by the font.
_ = fontMetrics.TryGetGlyphId(new CodePoint(decompositions[j]), out ushort id);
ids[j] = id;
}

Expand Down Expand Up @@ -207,23 +210,22 @@ private void InitialReorder(IGlyphShapingCollection collection, int index, int c
Span<GlyphShapingData> tempBuffer = new GlyphShapingData[3];

ShapingConfiguration indicConfiguration = this.indicConfiguration;
for (int i = 0; i < count; i++)
{
GlyphShapingData data = substitutionCollection[i + index];
FontMetrics fontMetrics = data.TextRun.Font!.FontMetrics;
FontMetrics fontMetrics = this.fontMetrics;
CodePoint viramaPoint = new(indicConfiguration.Virama);

fontMetrics.TryGetGlyphId(new(0x0020), out ushort spc);

IndicShapingEngineInfo? info = data.IndicShapingEngineInfo;
if (info?.Position == Positions.Base_C)
if (fontMetrics.TryGetGlyphId(viramaPoint, out ushort viramaId))
{
for (int i = 0; i < count; i++)
{
CodePoint cp = new(indicConfiguration.Virama);
if (fontMetrics.TryGetGlyphId(cp, out ushort id))
GlyphShapingData data = substitutionCollection[i + index];
IndicShapingEngineInfo? info = data.IndicShapingEngineInfo;

if (info?.Position == Positions.Base_C)
{
GlyphShapingData virama = new(data, false)
{
GlyphId = id,
CodePoint = cp
GlyphId = viramaId,
CodePoint = viramaPoint
};

tempBuffer[2] = virama;
Expand All @@ -235,6 +237,8 @@ private void InitialReorder(IGlyphShapingCollection collection, int index, int c
}
}

bool hasDottedCircle = fontMetrics.TryGetGlyphId(new(DottedCircle), out ushort circleId);
_ = fontMetrics.TryGetGSubTable(out GSubTable? gSubTable);
int max = index + count;
int start = index;
int end = NextSyllable(substitutionCollection, index, max);
Expand All @@ -250,13 +254,7 @@ private void InitialReorder(IGlyphShapingCollection collection, int index, int c
goto Increment;
}

FontMetrics fontMetrics = data.TextRun.Font!.FontMetrics;
if (!fontMetrics.TryGetGSubTable(out GSubTable? gSubTable))
{
break;
}

if (dataInfo != null && type == "broken_cluster" && fontMetrics.TryGetGlyphId(new(DottedCircle), out ushort id))
if (dataInfo != null && hasDottedCircle && type == "broken_cluster")
{
// Insert after possible Repha.
int i = start;
Expand All @@ -272,7 +270,7 @@ private void InitialReorder(IGlyphShapingCollection collection, int index, int c
}

glyphs[0] = current.GlyphId;
glyphs[1] = id;
glyphs[1] = circleId;

substitutionCollection.Replace(i, glyphs, FeatureTags.GlyphCompositionDecomposition);

Expand Down Expand Up @@ -303,7 +301,7 @@ private void InitialReorder(IGlyphShapingCollection collection, int index, int c
// base consonants.
if (start + 3 <= end &&
indicConfiguration.RephPosition != Positions.Ra_To_Become_Reph &&
gSubTable.TryGetFeatureLookups(in RphfTag, this.ScriptClass, out _) &&
gSubTable?.TryGetFeatureLookups(in RphfTag, this.ScriptClass, out _) == true &&
((indicConfiguration.RephMode == RephMode.Implicit && !IsJoiner(substitutionCollection[start + 2])) ||
(indicConfiguration.RephMode == RephMode.Explicit && substitutionCollection[start + 2].IndicShapingEngineInfo?.Category == Categories.ZWJ)))
{
Expand Down Expand Up @@ -692,7 +690,7 @@ private void InitialReorder(IGlyphShapingCollection collection, int index, int c

const int prefLen = 2;
if (basePosition + prefLen < end &&
gSubTable.TryGetFeatureLookups(in PrefTag, this.ScriptClass, out _))
gSubTable?.TryGetFeatureLookups(in PrefTag, this.ScriptClass, out _) == true)
{
// Find a Halant,Ra sequence and mark it for pre-base reordering processing.
for (int i = basePosition + 1; i + prefLen - 1 < end; i++)
Expand Down Expand Up @@ -790,9 +788,7 @@ private bool WouldSubstitute(GlyphSubstitutionCollection collection, in Tag feat
collection.EnableShapingFeature(i, featureTag);
}

GlyphShapingData data = buffer[0];
FontMetrics fontMetrics = data.TextRun.Font!.FontMetrics;

FontMetrics fontMetrics = this.fontMetrics;
if (fontMetrics.TryGetGSubTable(out GSubTable? gSubTable))
{
const int index = 0;
Expand Down Expand Up @@ -865,6 +861,8 @@ private void FinalReorder(IGlyphShapingCollection collection, int index, int cou
int max = index + count;
int start = index;
int end = NextSyllable(substitutionCollection, index, max);
FontMetrics fontMetrics = this.fontMetrics;
_ = fontMetrics.TryGetGSubTable(out GSubTable? gSubTable);
while (start < max)
{
// 4. Final reordering:
Expand All @@ -873,14 +871,7 @@ private void FinalReorder(IGlyphShapingCollection collection, int index, int cou
// applied (see below), the shaping engine performs some final glyph
// reordering before applying all the remaining font features to the entire
// cluster.
GlyphShapingData data = substitutionCollection[start];
FontMetrics fontMetrics = data.TextRun.Font!.FontMetrics;
if (!fontMetrics.TryGetGSubTable(out GSubTable? gSubTable))
{
break;
}

bool tryPref = gSubTable.TryGetFeatureLookups(in PrefTag, this.ScriptClass, out _);
bool tryPref = gSubTable?.TryGetFeatureLookups(in PrefTag, this.ScriptClass, out _) == true;

// Find base consonant again.
int basePosition = start;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@ namespace SixLabors.Fonts.Tables.AdvancedTypographic.Shapers;
internal static class ShaperFactory
{
/// <summary>
/// Creates a Shaper based on the given script language.
/// Creates a shaper based on the given script language.
/// </summary>
/// <param name="script">The script language.</param>
/// <param name="unicodeScriptTag">The unicode script tag found in the font matching the script.</param>
/// <param name="textOptions">The text options.</param>
/// <param name="fontMetrics">The current font metrics.</param>
/// <param name="textOptions">The global text options.</param>
/// <returns>A shaper for the given script.</returns>
public static BaseShaper Create(ScriptClass script, Tag unicodeScriptTag, TextOptions textOptions)
public static BaseShaper Create(
ScriptClass script,
Tag unicodeScriptTag,
FontMetrics fontMetrics,
TextOptions textOptions)
=> script switch
{
// Arabic
Expand All @@ -28,7 +33,7 @@ or ScriptClass.Manichaean
or ScriptClass.PsalterPahlavi => new ArabicShaper(script, textOptions),

// Hangul
ScriptClass.Hangul => new HangulShaper(script, textOptions),
ScriptClass.Hangul => new HangulShaper(script, textOptions, fontMetrics),

// Indic
ScriptClass.Bengali
Expand All @@ -40,7 +45,7 @@ or ScriptClass.Malayalam
or ScriptClass.Oriya
or ScriptClass.Tamil
or ScriptClass.Telugu
or ScriptClass.Khmer => new IndicShaper(script, unicodeScriptTag, textOptions),
or ScriptClass.Khmer => new IndicShaper(script, unicodeScriptTag, textOptions, fontMetrics),

// Universal
ScriptClass.Balinese
Expand Down Expand Up @@ -82,7 +87,7 @@ or ScriptClass.Takri
or ScriptClass.Tibetan
or ScriptClass.Tifinagh
or ScriptClass.Tirhuta
=> new UniversalShaper(script, textOptions),
=> new UniversalShaper(script, textOptions, fontMetrics),
_ => new DefaultShaper(script, textOptions),
};
}
Loading
Loading