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
4 changes: 4 additions & 0 deletions src/SixLabors.Fonts/FileFontMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ internal override bool TryGetGlyphClass(ushort glyphId, [NotNullWhen(true)] out
internal override bool TryGetMarkAttachmentClass(ushort glyphId, [NotNullWhen(true)] out GlyphClassDef? markAttachmentClass)
=> this.fontMetrics.Value.TryGetMarkAttachmentClass(glyphId, out markAttachmentClass);

/// <inheritdoc/>
internal override bool IsInMarkFilteringSet(ushort markGlyphSetIndex, ushort glyphId)
=> this.fontMetrics.Value.IsInMarkFilteringSet(markGlyphSetIndex, glyphId);

/// <inheritdoc />
public override bool TryGetGlyphMetrics(
CodePoint codePoint,
Expand Down
11 changes: 11 additions & 0 deletions src/SixLabors.Fonts/FontMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,17 @@ internal FontMetrics()
/// <returns>true, if the mark attachment class could be retrieved.</returns>
internal abstract bool TryGetMarkAttachmentClass(ushort glyphId, [NotNullWhen(true)] out GlyphClassDef? markAttachmentClass);

/// <summary>
/// Returns a value indicating whether the specified glyph is in the given mark filtering set.
/// The font needs to have a GDEF table defined.
/// </summary>
/// <param name="markGlyphSetIndex">The mark glyph set index.</param>
/// <param name="glyphId">The glyph identifier.</param>
/// <returns>
/// true, if the glyph is in the mark filtering set.
/// </returns>
internal abstract bool IsInMarkFilteringSet(ushort markGlyphSetIndex, ushort glyphId);

/// <summary>
/// Gets the glyph metrics for a given code point.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/SixLabors.Fonts/GlyphLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ internal GlyphLayout(

internal FontRectangle BoundingBox(float dpi)
{
// Same logic as in TrueTypeGlyphMetrics.RenderTo
// Same logic as in GlyphMetrics.RenderTo
Vector2 location = this.PenLocation;
Vector2 offset = this.Offset;

Expand Down
20 changes: 11 additions & 9 deletions src/SixLabors.Fonts/GlyphShapingData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using System.Diagnostics;
using SixLabors.Fonts.Tables.AdvancedTypographic;
using SixLabors.Fonts.Unicode;
using SixLabors.Fonts.Unicode.Resources;
using static SixLabors.Fonts.Unicode.Resources.IndicShapingData;

namespace SixLabors.Fonts;

Expand Down Expand Up @@ -188,16 +188,16 @@ public UniversalShapingEngineInfo(string category, string syllableType, int syll

public string Category { get; set; }

public string SyllableType { get; }
public string SyllableType { get; set; }

public int Syllable { get; }
public int Syllable { get; set; }
}

internal class IndicShapingEngineInfo
{
public IndicShapingEngineInfo(
IndicShapingData.Categories category,
IndicShapingData.Positions position,
Categories category,
Positions position,
string syllableType,
int syllable)
{
Expand All @@ -207,11 +207,13 @@ public IndicShapingEngineInfo(
this.Syllable = syllable;
}

public IndicShapingData.Categories Category { get; set; }
public Categories Category { get; set; }

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

public string SyllableType { get; }
public Positions Position { get; set; }

public int Syllable { get; }
public string SyllableType { get; set; }

public int Syllable { get; set; }
}
41 changes: 41 additions & 0 deletions src/SixLabors.Fonts/GlyphSubstitutionCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,41 @@ public void MoveGlyph(int fromIndex, int toIndex)
this.glyphs[toIndex].Data = data;
}

/// <summary>
/// Reverses the order of elements in the specified range of the collection.
/// </summary>
/// <remarks>
/// The range is interpreted as half-open, from <paramref name="startIndex"/> (inclusive)
/// to <paramref name="endIndex"/> (exclusive).
///
/// Both indices are clamped to the valid range [0, <see cref="Count"/>].
/// If the resulting range contains fewer than two elements, the method performs no action.
/// The method does not throw if either index is equal to <see cref="Count"/>; in such
/// cases the range is considered valid but may be empty.
/// </remarks>
/// <param name="startIndex">
/// The zero-based index at which to start reversing (inclusive). This value should be
/// greater than or equal to 0. Values greater than <see cref="Count"/> are treated as
/// <see cref="Count"/>.
/// </param>
/// <param name="endIndex">
/// The zero-based index at which to stop reversing (exclusive). This value should be
/// greater than or equal to <paramref name="startIndex"/>. Values greater than
/// <see cref="Count"/> are treated as <see cref="Count"/>.
/// </param>
public void ReverseRange(int startIndex, int endIndex)
{
int s = Math.Min(startIndex, this.Count);
int e = Math.Min(endIndex, this.Count);

if (e < s + 2)
{
return;
}

this.glyphs.Reverse(s, e - s);
}

/// <summary>
/// Performs a stable sort of the glyphs by the comparison delegate starting at the specified index.
/// </summary>
Expand Down Expand Up @@ -375,6 +410,12 @@ public void Replace(int index, ReadOnlySpan<ushort> glyphIds, Tag feature)
}
}

public void Insert(int index, GlyphShapingData data)
{
OffsetGlyphDataPair pair = this.glyphs[index];
this.glyphs.Insert(index, new(pair.Offset, data));
}

[DebuggerDisplay("{DebuggerDisplay,nq}")]
private class OffsetGlyphDataPair
{
Expand Down
10 changes: 10 additions & 0 deletions src/SixLabors.Fonts/StreamFontMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,16 @@ internal override bool TryGetMarkAttachmentClass(ushort glyphId, [NotNullWhen(tr
return gdef is not null && gdef.TryGetMarkAttachmentClass(glyphId, out markAttachmentClass);
}

/// <inheritdoc/>
internal override bool IsInMarkFilteringSet(ushort markGlyphSetIndex, ushort glyphId)
{
GlyphDefinitionTable? gdef = this.outlineType == OutlineType.TrueType
? this.trueTypeFontTables!.Gdef
: this.compactFontTables!.Gdef;

return gdef is not null && gdef.IsInMarkGlyphSet(markGlyphSetIndex, glyphId);
}

/// <inheritdoc/>
public override bool TryGetGlyphMetrics(
CodePoint codePoint,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ internal static class AdvancedTypographicUtils
private const int MaxOperationsMinimum = 16384;
private const int MaxShapingCharsLength = 0x3FFFFFFF; // Half int max.

internal enum MatchDirection
{
Forward,
Backward
}

/// <summary>
/// Gets a value indicating whether the glyph represented by the codepoint should be interpreted vertically.
/// </summary>
Expand Down Expand Up @@ -46,12 +52,13 @@ public static bool ApplyLookupList(
GSubTable table,
Tag feature,
LookupFlags lookupFlags,
ushort markFilteringSet,
SequenceLookupRecord[] records,
GlyphSubstitutionCollection collection,
int index,
int count)
{
SkippingGlyphIterator iterator = new(fontMetrics, collection, index, lookupFlags);
SkippingGlyphIterator iterator = new(fontMetrics, collection, index, lookupFlags, markFilteringSet);
int currentCount = collection.Count;

foreach (SequenceLookupRecord lookupRecord in records)
Expand Down Expand Up @@ -79,12 +86,13 @@ public static bool ApplyLookupList(
GPosTable table,
Tag feature,
LookupFlags lookupFlags,
ushort markFilteringSet,
SequenceLookupRecord[] records,
GlyphPositioningCollection collection,
int index,
int count)
{
SkippingGlyphIterator iterator = new(fontMetrics, collection, index, lookupFlags);
SkippingGlyphIterator iterator = new(fontMetrics, collection, index, lookupFlags, markFilteringSet);
foreach (SequenceLookupRecord lookupRecord in records)
{
ushort sequenceIndex = lookupRecord.SequenceIndex;
Expand Down Expand Up @@ -150,11 +158,29 @@ public static bool MatchClassSequence(
public static bool MatchCoverageSequence(
SkippingGlyphIterator iterator,
CoverageTable[] coverageTable,
int increment)
int startIndex,
int endExclusive)
=> Match(
increment,
iterator,
startIndex,
coverageTable,
MatchDirection.Forward,
endExclusive,
(component, data) => component.CoverageIndexOf(data.GlyphId) >= 0,
default);

// Backtrack variant (spec: backtrack[0] matches i-1, then i-2...)
public static bool MatchBacktrackCoverageSequence(
SkippingGlyphIterator iterator,
CoverageTable[] backtrack,
int startIndex,
int endExclusive)
=> Match(
iterator,
startIndex,
backtrack,
MatchDirection.Backward,
endExclusive,
(component, data) => component.CoverageIndexOf(data.GlyphId) >= 0,
default);

Expand Down Expand Up @@ -212,32 +238,50 @@ public static bool ApplyChainedClassSequenceRule(
public static bool CheckAllCoverages(
FontMetrics fontMetrics,
LookupFlags lookupFlags,
ushort markFilteringSet,
IGlyphShapingCollection collection,
int index,
int count,
CoverageTable[] input,
CoverageTable[] backtrack,
CoverageTable[] lookahead)
{
// Check that there are enough context glyphs.
if (index < backtrack.Length || input.Length + lookahead.Length > count)
int endExclusive = index + count;

SkippingGlyphIterator iterator = new(fontMetrics, collection, index, lookupFlags, markFilteringSet);

// Compute backtrack start using skippy prev(), not index-1.
int backtrackStart = index;
if (backtrack.Length > 0)
{
return false;
SkippingGlyphIterator backIt = iterator;
backIt.Index = index;
backtrackStart = backIt.Prev(); // first backtrack glyph (i-1 in skippy space)
}

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

if (!MatchCoverageSequence(iterator, input, 0))
// Input starts at the current glyph position.
if (!MatchCoverageSequence(iterator, input, index, endExclusive))
{
return false;
}

if (!MatchCoverageSequence(iterator, lookahead, input.Length))
// Compute lookahead start by advancing through the input sequence using skippy Next(),
// not by raw index arithmetic.
int lookaheadStart = index;
if (lookahead.Length > 0)
{
SkippingGlyphIterator fwdIt = iterator;
fwdIt.Index = index;
fwdIt.Increment(input.Length); // advance input.Length steps in skippy space
lookaheadStart = fwdIt.Index;
}

if (!MatchCoverageSequence(iterator, lookahead, lookaheadStart, endExclusive))
{
return false;
}
Expand Down Expand Up @@ -331,6 +375,9 @@ public static GlyphShapingClass GetGlyphShapingClass(FontMetrics fontMetrics, us
return new GlyphShapingClass(isMark, isBase, isLigature, markAttachmentType);
}

public static bool IsInMarkFilteringSet(FontMetrics fontMetrics, ushort markFilteringSet, ushort glyphId)
=> fontMetrics.IsInMarkFilteringSet(markFilteringSet, glyphId);

private static bool Match<T>(
int increment,
T[] sequence,
Expand Down Expand Up @@ -367,4 +414,56 @@ private static bool Match<T>(
iterator.Index = position;
return i == sequence.Length;
}

private static bool Match<T>(
SkippingGlyphIterator iterator,
int startIndex,
T[] sequence,
MatchDirection direction,
int endExclusive,
Func<T, GlyphShapingData, bool> condition,
Span<int> matches)
{
if (sequence.Length == 0)
{
return true;
}

int saved = iterator.Index;
iterator.Index = startIndex;

IGlyphShapingCollection collection = iterator.Collection;
int limit = Math.Min(endExclusive, collection.Count);

for (int i = 0; i < sequence.Length && i < MaxContextLength; i++)
{
if (iterator.Index < 0 || iterator.Index >= limit)
{
iterator.Index = saved;
return false;
}

GlyphShapingData data = collection[iterator.Index];
if (!condition(sequence[i], data))
{
iterator.Index = saved;
return false;
}

if (matches.Length == MaxContextLength)
{
matches[i] = iterator.Index;
}

if (i + 1 < sequence.Length)
{
iterator.Index = direction == MatchDirection.Forward
? iterator.Next()
: iterator.Prev();
}
}

iterator.Index = saved;
return true;
}
}
Loading
Loading