Skip to content

Commit

Permalink
Merge pull request #344 from SixLabors/js/indic-shaper
Browse files Browse the repository at this point in the history
Add Indic Shaper
  • Loading branch information
JimBobSquarePants committed Aug 9, 2023
2 parents 9b00ed7 + 5f763f5 commit 32bef42
Show file tree
Hide file tree
Showing 61 changed files with 5,517 additions and 312 deletions.
4 changes: 4 additions & 0 deletions src/SixLabors.Fonts/FileFontMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ internal override IReadOnlyList<GlyphMetrics> GetGlyphMetrics(
internal override IReadOnlyList<CodePoint> GetAvailableCodePoints()
=> this.fontMetrics.Value.GetAvailableCodePoints();

/// <inheritdoc/>
internal override bool TryGetGSubTable([NotNullWhen(true)] out GSubTable? gSubTable)
=> this.fontMetrics.Value.TryGetGSubTable(out gSubTable);

/// <inheritdoc/>
internal override void ApplySubstitution(GlyphSubstitutionCollection collection)
=> this.fontMetrics.Value.ApplySubstitution(collection);
Expand Down
7 changes: 7 additions & 0 deletions src/SixLabors.Fonts/FontMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,13 @@ internal abstract IReadOnlyList<GlyphMetrics> GetGlyphMetrics(
LayoutMode layoutMode,
ColorFontSupport support);

/// <summary>
/// Tries to get the GSUB table.
/// </summary>
/// <param name="gSubTable">The GSUB table.</param>
/// <returns>true, if the glyph class could be retrieved.</returns>
internal abstract bool TryGetGSubTable([NotNullWhen(true)] out GSubTable? gSubTable);

/// <summary>
/// Applies any available substitutions to the collection of glyphs.
/// </summary>
Expand Down
14 changes: 7 additions & 7 deletions src/SixLabors.Fonts/GlyphPositioningCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using SixLabors.Fonts.Tables.AdvancedTypographic;
Expand Down Expand Up @@ -33,16 +34,12 @@ internal sealed class GlyphPositioningCollection : IGlyphShapingCollection
public TextOptions TextOptions { get; }

/// <inheritdoc />
public ushort this[int index]
public GlyphShapingData this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.glyphs[index].Data.GlyphId;
get => this.glyphs[index].Data;
}

/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public GlyphShapingData GetGlyphShapingData(int index) => this.glyphs[index].Data;

/// <inheritdoc />
public void AddShapingFeature(int index, TagEntry feature)
=> this.glyphs[index].Data.Features.Add(feature);
Expand Down Expand Up @@ -268,7 +265,7 @@ public bool TryAdd(Font font, GlyphSubstitutionCollection collection)
/// <param name="index">The zero-based index of the element.</param>
public void UpdatePosition(FontMetrics fontMetrics, int index)
{
GlyphShapingData data = this.GetGlyphShapingData(index);
GlyphShapingData data = this[index];
bool isDirtyXY = data.Bounds.IsDirtyXY;
bool isDirtyWH = data.Bounds.IsDirtyWH;
if (!isDirtyXY && !isDirtyWH)
Expand Down Expand Up @@ -325,6 +322,7 @@ public void Advance(FontMetrics fontMetrics, int index, ushort glyphId, short dx
public bool ShouldProcess(FontMetrics fontMetrics, int index)
=> this.glyphs[index].Metrics[0].FontMetrics == fontMetrics;

[DebuggerDisplay("{DebuggerDisplay,nq}")]
private class GlyphPositioningData
{
public GlyphPositioningData(int offset, GlyphShapingData data, float pointSize, GlyphMetrics[] metrics)
Expand All @@ -342,6 +340,8 @@ public GlyphPositioningData(int offset, GlyphShapingData data, float pointSize,
public float PointSize { get; set; }

public GlyphMetrics[] Metrics { get; set; }

private string DebuggerDisplay => FormattableString.Invariant($"Offset: {this.Offset}, Data: {this.Data.ToDebuggerDisplay()}");
}
}
}
49 changes: 48 additions & 1 deletion src/SixLabors.Fonts/GlyphShapingData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Diagnostics;
using SixLabors.Fonts.Tables.AdvancedTypographic;
using SixLabors.Fonts.Unicode;
using SixLabors.Fonts.Unicode.Resources;

namespace SixLabors.Fonts
{
Expand Down Expand Up @@ -33,13 +34,26 @@ public GlyphShapingData(GlyphShapingData data, bool clearFeatures = false)
this.Direction = data.Direction;
this.TextRun = data.TextRun;
this.LigatureId = data.LigatureId;
this.IsLigated = data.IsLigated;
this.LigatureComponent = data.LigatureComponent;
this.MarkAttachment = data.MarkAttachment;
this.CursiveAttachment = data.CursiveAttachment;
this.IsDecomposed = data.IsDecomposed;
if (data.UniversalShapingEngineInfo != null)
{
this.UniversalShapingEngineInfo = new(data.UniversalShapingEngineInfo.Category, data.UniversalShapingEngineInfo.SyllableType, data.UniversalShapingEngineInfo.Syllable);
this.UniversalShapingEngineInfo = new(
data.UniversalShapingEngineInfo.Category,
data.UniversalShapingEngineInfo.SyllableType,
data.UniversalShapingEngineInfo.Syllable);
}

if (data.IndicShapingEngineInfo != null)
{
this.IndicShapingEngineInfo = new(
data.IndicShapingEngineInfo.Category,
data.IndicShapingEngineInfo.Position,
data.IndicShapingEngineInfo.SyllableType,
data.IndicShapingEngineInfo.Syllable);
}

if (!clearFeatures)
Expand Down Expand Up @@ -80,6 +94,11 @@ public GlyphShapingData(GlyphShapingData data, bool clearFeatures = false)
/// </summary>
public int LigatureId { get; set; } = 0;

/// <summary>
/// Gets or sets a value indicating whether the glyph is ligated.
/// </summary>
public bool IsLigated { get; set; } = false;

/// <summary>
/// Gets or sets the ligature component index of the glyph.
/// </summary>
Expand Down Expand Up @@ -120,6 +139,11 @@ public GlyphShapingData(GlyphShapingData data, bool clearFeatures = false)
/// </summary>
public UniversalShapingEngineInfo? UniversalShapingEngineInfo { get; set; }

/// <summary>
/// Gets or sets the Indic shaping information.
/// </summary>
public IndicShapingEngineInfo? IndicShapingEngineInfo { get; set; }

private string DebuggerDisplay
=> FormattableString
.Invariant($" {this.GlyphId} : {this.CodePoint.ToDebuggerDisplay()} : {CodePoint.GetScriptClass(this.CodePoint)} : {this.Direction} : {this.TextRun.TextAttributes} : {this.LigatureId} : {this.LigatureComponent} : {this.IsDecomposed}");
Expand All @@ -145,4 +169,27 @@ public UniversalShapingEngineInfo(string category, string syllableType, int syll

public int Syllable { get; }
}

internal class IndicShapingEngineInfo
{
public IndicShapingEngineInfo(
IndicShapingData.Categories category,
IndicShapingData.Positions position,
string syllableType,
int syllable)
{
this.Category = category;
this.Position = position;
this.SyllableType = syllableType;
this.Syllable = syllable;
}

public IndicShapingData.Categories Category { get; set; }

public IndicShapingData.Positions Position { get; set; }

public string SyllableType { get; }

public int Syllable { get; }
}
}
64 changes: 49 additions & 15 deletions src/SixLabors.Fonts/GlyphSubstitutionCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,12 @@ internal sealed class GlyphSubstitutionCollection : IGlyphShapingCollection
public int LigatureId { get; set; } = 1;

/// <inheritdoc />
public ushort this[int index]
public GlyphShapingData this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.glyphs[index].Data.GlyphId;
get => this.glyphs[index].Data;
}

/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public GlyphShapingData GetGlyphShapingData(int index) => this.glyphs[index].Data;

/// <summary>
/// Gets the shaping data at the specified position.
/// </summary>
Expand Down Expand Up @@ -101,6 +97,14 @@ public void DisableShapingFeature(int index, Tag feature)
}
}

/// <summary>
/// Adds a clone of the glyph shaping data to the collection at the specified offset.
/// </summary>
/// <param name="data">The data.</param>
/// <param name="offset">The zero-based index within the input codepoint collection.</param>
public void AddGlyph(GlyphShapingData data, int offset)
=> this.glyphs.Add(new(offset, new(data, false)));

/// <summary>
/// Adds the glyph id and the codepoint it represents to the collection.
/// </summary>
Expand All @@ -124,29 +128,58 @@ public void AddGlyph(ushort glyphId, CodePoint codePoint, TextDirection directio
/// <param name="toIndex">The index to move to.</param>
public void MoveGlyph(int fromIndex, int toIndex)
{
GlyphShapingData data = this.GetGlyphShapingData(fromIndex);
if (fromIndex == toIndex)
{
return;
}

GlyphShapingData data = this[fromIndex];
if (fromIndex > toIndex)
{
int idx = fromIndex;
while (idx > toIndex)
// Move item to the right
for (int i = fromIndex; i > toIndex; i--)
{
this.glyphs[idx].Data = this.glyphs[idx - 1].Data;
idx--;
this.glyphs[i].Data = this.glyphs[i - 1].Data;
}
}
else
{
int idx = toIndex;
while (idx > fromIndex)
// Move item to the left
for (int i = fromIndex; i < toIndex; i++)
{
this.glyphs[idx - 1].Data = this.glyphs[idx].Data;
idx--;
this.glyphs[i].Data = this.glyphs[i + 1].Data;
}
}

this.glyphs[toIndex].Data = data;
}

/// <summary>
/// Performs a stable sort of the glyphs by the comparison delegate starting at the specified index.
/// </summary>
/// <param name="startIndex">The start index.</param>
/// <param name="endIndex">The end index.</param>
/// <param name="comparer">The comparison delegate.</param>
public void Sort(int startIndex, int endIndex, Comparison<GlyphShapingData> comparer)
{
for (int i = startIndex + 1; i < endIndex; i++)
{
int j = i;
while (j > startIndex && comparer(this[j - 1], this[i]) > 0)
{
j--;
}

if (i == j)
{
continue;
}

// Move item i to occupy place for item j, shift what's in between.
this.MoveGlyph(i, j);
}
}

/// <summary>
/// Removes all elements from the collection.
/// </summary>
Expand Down Expand Up @@ -228,6 +261,7 @@ public void Replace(int index, ReadOnlySpan<int> removalIndices, ushort glyphId,
current.CodePointCount += codePointCount;
current.GlyphId = glyphId;
current.LigatureId = ligatureId;
current.IsLigated = true;
current.LigatureComponent = -1;
current.MarkAttachment = -1;
current.CursiveAttachment = -1;
Expand Down
11 changes: 2 additions & 9 deletions src/SixLabors.Fonts/IGlyphShapingCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,11 @@ internal interface IGlyphShapingCollection
TextOptions TextOptions { get; }

/// <summary>
/// Gets the glyph id at the specified index.
/// </summary>
/// <param name="index">The zero-based index of the elements to get.</param>
/// <returns>The <see cref="ushort"/>.</returns>
ushort this[int index] { get; }

/// <summary>
/// Gets the shaping data at the specified position.
/// Gets the glyph shaping data at the specified index.
/// </summary>
/// <param name="index">The zero-based index of the elements to get.</param>
/// <returns>The <see cref="GlyphShapingData"/>.</returns>
GlyphShapingData GetGlyphShapingData(int index);
GlyphShapingData this[int index] { get; }

/// <summary>
/// Adds the shaping feature to the collection which should be applied to the glyph at a specified index.
Expand Down
15 changes: 12 additions & 3 deletions src/SixLabors.Fonts/StreamFontMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -250,13 +250,22 @@ internal override IReadOnlyList<CodePoint> GetAvailableCodePoints()
}

/// <inheritdoc/>
internal override void ApplySubstitution(GlyphSubstitutionCollection collection)
internal override bool TryGetGSubTable([NotNullWhen(true)] out GSubTable? gSubTable)
{
GSubTable? gsub = this.outlineType == OutlineType.TrueType
gSubTable = this.outlineType == OutlineType.TrueType
? this.trueTypeFontTables!.GSub
: this.compactFontTables!.GSub;

gsub?.ApplySubstitution(this, collection);
return gSubTable is not null;
}

/// <inheritdoc/>
internal override void ApplySubstitution(GlyphSubstitutionCollection collection)
{
if (this.TryGetGSubTable(out GSubTable? gSubTable))
{
gSubTable.ApplySubstitution(this, collection);
}
}

/// <inheritdoc/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,10 +257,10 @@ public static void ApplyAnchor(
MarkRecord markRecord,
int baseGlyphIndex)
{
GlyphShapingData baseData = collection.GetGlyphShapingData(baseGlyphIndex);
GlyphShapingData baseData = collection[baseGlyphIndex];
AnchorXY baseXY = baseAnchor.GetAnchor(fontMetrics, baseData, collection);

GlyphShapingData markData = collection.GetGlyphShapingData(index);
GlyphShapingData markData = collection[index];
AnchorXY markXY = markRecord.MarkAnchorTable.GetAnchor(fontMetrics, markData, collection);

markData.Bounds.X = baseXY.XCoordinate - markXY.XCoordinate;
Expand All @@ -273,7 +273,7 @@ public static void ApplyPosition(
int index,
ValueRecord record)
{
GlyphShapingData current = collection.GetGlyphShapingData(index);
GlyphShapingData current = collection[index];
current.Bounds.Width += record.XAdvance;
current.Bounds.Height += record.YAdvance;
current.Bounds.X += record.XPlacement;
Expand Down Expand Up @@ -342,7 +342,7 @@ private static bool Match<T>(
int i = 0;
while (i < sequence.Length && i < MaxContextLength && offset < collection.Count)
{
if (!condition(sequence[i], collection.GetGlyphShapingData(offset)))
if (!condition(sequence[i], collection[offset]))
{
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public override bool TryUpdatePosition(
int index,
int count)
{
ushort glyphId = collection[index];
ushort glyphId = collection[index].GlyphId;
if (glyphId == 0)
{
return false;
Expand Down Expand Up @@ -144,7 +144,7 @@ public override bool TryUpdatePosition(
int index,
int count)
{
ushort glyphId = collection[index];
ushort glyphId = collection[index].GlyphId;
if (glyphId == 0)
{
return false;
Expand Down
Loading

0 comments on commit 32bef42

Please sign in to comment.