diff --git a/src/SixLabors.Fonts/GlyphShapingData.cs b/src/SixLabors.Fonts/GlyphShapingData.cs index a69ae3f1..8b7b2aab 100644 --- a/src/SixLabors.Fonts/GlyphShapingData.cs +++ b/src/SixLabors.Fonts/GlyphShapingData.cs @@ -61,7 +61,10 @@ public GlyphShapingData(GlyphShapingData data, bool clearFeatures = false) this.Features.AddRange(data.Features); } - this.AppliedFeatures.AddRange(data.AppliedFeatures); + foreach (Tag feature in data.AppliedFeatures) + { + this.AppliedFeatures.Add(feature); + } this.Bounds = data.Bounds; } @@ -124,7 +127,7 @@ public GlyphShapingData(GlyphShapingData data, bool clearFeatures = false) /// /// Gets or sets the collection of applied features. /// - public List AppliedFeatures { get; set; } = new(); + public HashSet AppliedFeatures { get; set; } = new(); /// /// Gets or sets the shaping bounds. diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/AdvancedTypographicUtils.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/AdvancedTypographicUtils.cs index 157a56a5..d6c5b636 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/AdvancedTypographicUtils.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/AdvancedTypographicUtils.cs @@ -51,7 +51,6 @@ public static bool ApplyLookupList( int index, int count) { - bool hasChanged = false; SkippingGlyphIterator iterator = new(fontMetrics, collection, index, lookupFlags); int currentCount = collection.Count; @@ -62,7 +61,7 @@ public static bool ApplyLookupList( iterator.Index = index; iterator.Increment(sequenceIndex); GSub.LookupTable lookup = table.LookupList.LookupTables[lookupIndex]; - hasChanged |= lookup.TrySubstitution(fontMetrics, table, collection, feature, iterator.Index, count - (iterator.Index - index)); + _ = lookup.TrySubstitution(fontMetrics, table, collection, feature, iterator.Index, count - (iterator.Index - index)); // Account for substitutions changing the length of the collection. if (collection.Count != currentCount) @@ -72,7 +71,7 @@ public static bool ApplyLookupList( } } - return hasChanged; + return true; } public static bool ApplyLookupList( @@ -85,7 +84,6 @@ public static bool ApplyLookupList( int index, int count) { - bool hasChanged = false; SkippingGlyphIterator iterator = new(fontMetrics, collection, index, lookupFlags); foreach (SequenceLookupRecord lookupRecord in records) { @@ -94,10 +92,10 @@ public static bool ApplyLookupList( iterator.Index = index; iterator.Increment(sequenceIndex); LookupTable lookup = table.LookupList.LookupTables[lookupIndex]; - hasChanged |= lookup.TryUpdatePosition(fontMetrics, table, collection, feature, iterator.Index, count - (iterator.Index - index)); + _ = lookup.TryUpdatePosition(fontMetrics, table, collection, feature, iterator.Index, count - (iterator.Index - index)); } - return hasChanged; + return true; } public static bool MatchInputSequence(SkippingGlyphIterator iterator, Tag feature, ushort increment, ushort[] sequence, Span matches) @@ -222,7 +220,7 @@ public static bool CheckAllCoverages( CoverageTable[] lookahead) { // Check that there are enough context glyphs. - if (index - backtrack.Length < 0 || input.Length + lookahead.Length > count) + if (index < backtrack.Length || input.Length + lookahead.Length > count) { return false; } @@ -253,7 +251,8 @@ public static void ApplyAnchor( int index, AnchorTable baseAnchor, MarkRecord markRecord, - int baseGlyphIndex) + int baseGlyphIndex, + Tag feature) { GlyphShapingData baseData = collection[baseGlyphIndex]; AnchorXY baseXY = baseAnchor.GetAnchor(fontMetrics, baseData, collection); @@ -264,18 +263,21 @@ public static void ApplyAnchor( markData.Bounds.X = baseXY.XCoordinate - markXY.XCoordinate; markData.Bounds.Y = baseXY.YCoordinate - markXY.YCoordinate; markData.MarkAttachment = baseGlyphIndex; + markData.AppliedFeatures.Add(feature); } public static void ApplyPosition( GlyphPositioningCollection collection, int index, - ValueRecord record) + ValueRecord record, + Tag feature) { GlyphShapingData current = collection[index]; current.Bounds.Width += record.XAdvance; current.Bounds.Height += record.YAdvance; current.Bounds.X += record.XPlacement; current.Bounds.Y += record.YPlacement; + current.AppliedFeatures.Add(feature); } public static bool IsMarkGlyph(FontMetrics fontMetrics, ushort glyphId, GlyphShapingData shapingData) diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType1SubTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType1SubTable.cs index 09d791dc..e5307d75 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType1SubTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType1SubTable.cs @@ -81,7 +81,7 @@ public override bool TryUpdatePosition( if (coverage > -1) { ValueRecord record = this.valueRecord; - AdvancedTypographicUtils.ApplyPosition(collection, index, record); + AdvancedTypographicUtils.ApplyPosition(collection, index, record, feature); return true; } @@ -152,7 +152,7 @@ public override bool TryUpdatePosition( if (coverage > -1) { ValueRecord record = this.valueRecords[coverage]; - AdvancedTypographicUtils.ApplyPosition(collection, index, record); + AdvancedTypographicUtils.ApplyPosition(collection, index, record, feature); return true; } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType2SubTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType2SubTable.cs index ed1f56aa..ecf296a7 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType2SubTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType2SubTable.cs @@ -117,10 +117,10 @@ public override bool TryUpdatePosition( if (pairSet.TryGetPairValueRecord(glyphId2, out PairValueRecord pairValueRecord)) { ValueRecord record1 = pairValueRecord.ValueRecord1; - AdvancedTypographicUtils.ApplyPosition(collection, index, record1); + AdvancedTypographicUtils.ApplyPosition(collection, index, record1, feature); ValueRecord record2 = pairValueRecord.ValueRecord2; - AdvancedTypographicUtils.ApplyPosition(collection, index + 1, record2); + AdvancedTypographicUtils.ApplyPosition(collection, index + 1, record2, feature); return true; } @@ -285,10 +285,10 @@ public override bool TryUpdatePosition( Class2Record class2Record = class1Record.Class2Records[classDef2]; ValueRecord record1 = class2Record.ValueRecord1; - AdvancedTypographicUtils.ApplyPosition(collection, index, record1); + AdvancedTypographicUtils.ApplyPosition(collection, index, record1, feature); ValueRecord record2 = class2Record.ValueRecord2; - AdvancedTypographicUtils.ApplyPosition(collection, index + 1, record2); + AdvancedTypographicUtils.ApplyPosition(collection, index + 1, record2, feature); return true; } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType4SubTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType4SubTable.cs index b4551407..24a05cb3 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType4SubTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType4SubTable.cs @@ -126,7 +126,7 @@ public override bool TryUpdatePosition( MarkRecord markRecord = this.markArrayTable.MarkRecords[markIndex]; AnchorTable baseAnchor = this.baseArrayTable.BaseRecords[baseIndex].BaseAnchorTables[markRecord.MarkClass]; - AdvancedTypographicUtils.ApplyAnchor(fontMetrics, collection, index, baseAnchor, markRecord, baseGlyphIndex); + AdvancedTypographicUtils.ApplyAnchor(fontMetrics, collection, index, baseAnchor, markRecord, baseGlyphIndex, feature); return true; } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType5SubTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType5SubTable.cs index 10631ece..4db3872e 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType5SubTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType5SubTable.cs @@ -140,7 +140,7 @@ public override bool TryUpdatePosition( MarkRecord markRecord = this.markArrayTable.MarkRecords[markIndex]; AnchorTable baseAnchor = ligatureAttach.ComponentRecords[compIndex].LigatureAnchorTables[markRecord.MarkClass]; - AdvancedTypographicUtils.ApplyAnchor(fontMetrics, collection, index, baseAnchor, markRecord, baseGlyphIndex); + AdvancedTypographicUtils.ApplyAnchor(fontMetrics, collection, index, baseAnchor, markRecord, baseGlyphIndex, feature); return true; } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType6SubTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType6SubTable.cs index 8799de54..2bc1f6d6 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType6SubTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType6SubTable.cs @@ -158,7 +158,7 @@ public override bool TryUpdatePosition( MarkRecord markRecord = this.mark1ArrayTable.MarkRecords[mark1Index]; AnchorTable baseAnchor = this.mark2ArrayTable.Mark2Records[mark2Index].MarkAnchorTable[markRecord.MarkClass]; - AdvancedTypographicUtils.ApplyAnchor(fontMetrics, collection, index, baseAnchor, markRecord, prevIdx); + AdvancedTypographicUtils.ApplyAnchor(fontMetrics, collection, index, baseAnchor, markRecord, prevIdx, feature); return true; } diff --git a/tests/Images/ReferenceOutput/Issue_451_A-.png b/tests/Images/ReferenceOutput/Issue_451_A-.png new file mode 100644 index 00000000..ab1a3ce6 --- /dev/null +++ b/tests/Images/ReferenceOutput/Issue_451_A-.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17b4d0c60a62c2a7d0dae921906c803f67d6885ea2b53cb58c7c62c25a1654dc +size 2213 diff --git a/tests/Images/ReferenceOutput/Issue_451_B-.png b/tests/Images/ReferenceOutput/Issue_451_B-.png new file mode 100644 index 00000000..28eedbd7 --- /dev/null +++ b/tests/Images/ReferenceOutput/Issue_451_B-.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8662ca4a1d837e5c0714e4deb36a679edccf3aeaab245d70494f5643c04a5475 +size 1843 diff --git a/tests/Images/ReferenceOutput/Issue_451_C-.png b/tests/Images/ReferenceOutput/Issue_451_C-.png new file mode 100644 index 00000000..6e754ab3 --- /dev/null +++ b/tests/Images/ReferenceOutput/Issue_451_C-.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cd9256bf5cd2dcd4a48a8ba6f6a9f4d10cfd5ee858dc99f3122cf340b14140ad +size 23102 diff --git a/tests/SixLabors.Fonts.Tests/Fonts/VeryBerryProRegular.ttf b/tests/SixLabors.Fonts.Tests/Fonts/VeryBerryProRegular.ttf new file mode 100644 index 00000000..2c10e070 Binary files /dev/null and b/tests/SixLabors.Fonts.Tests/Fonts/VeryBerryProRegular.ttf differ diff --git a/tests/SixLabors.Fonts.Tests/Issues/Issues_451.cs b/tests/SixLabors.Fonts.Tests/Issues/Issues_451.cs new file mode 100644 index 00000000..4fe1a766 --- /dev/null +++ b/tests/SixLabors.Fonts.Tests/Issues/Issues_451.cs @@ -0,0 +1,47 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; + +namespace SixLabors.Fonts.Tests.Issues; + +public class Issues_451 +{ + private readonly FontFamily berry = new FontCollection().Add(TestFonts.VeryBerryProRegular); + + [Fact] + public void Issue_451_A() + { + Font font = this.berry.CreateFont(85); + TextLayoutTestUtilities.TestLayout( + "The", + new TextOptions(font) + { + Origin = new Vector2(0, 50), + }); + } + + [Fact] + public void Issue_451_B() + { + Font font = this.berry.CreateFont(85); + TextLayoutTestUtilities.TestLayout( + "Th", + new TextOptions(font) + { + Origin = new Vector2(0, 50), + }); + } + + [Fact] + public void Issue_451_C() + { + Font font = this.berry.CreateFont(85); + TextLayoutTestUtilities.TestLayout( + "The quick brown fox jumps over the lazy dog", + new TextOptions(font) + { + Origin = new Vector2(0, 50), + }); + } +} diff --git a/tests/SixLabors.Fonts.Tests/TestFonts.cs b/tests/SixLabors.Fonts.Tests/TestFonts.cs index 54187cb1..b7a5e233 100644 --- a/tests/SixLabors.Fonts.Tests/TestFonts.cs +++ b/tests/SixLabors.Fonts.Tests/TestFonts.cs @@ -261,6 +261,8 @@ public static class TestFonts public static string CharisSILRegular => GetFullPath("CharisSIL-Regular.ttf"); + public static string VeryBerryProRegular => GetFullPath("VeryBerryProRegular.ttf"); + public static Stream TwemojiMozillaData() => OpenStream(TwemojiMozillaFile); public static Stream SegoeuiEmojiData() => OpenStream(SegoeuiEmojiFile);