Merge branch 'main' into fix-issue-393

This commit is contained in:
batwomankt 2024-03-19 07:00:15 -04:00 коммит произвёл GitHub
Родитель 8ec57078c4 0b60d5a25d
Коммит 89b4ce7c6c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
5 изменённых файлов: 130 добавлений и 18 удалений

Просмотреть файл

@ -15,10 +15,14 @@ public readonly struct GlyphBounds
/// </summary>
/// <param name="codePoint">The Unicode codepoint for the glyph.</param>
/// <param name="bounds">The glyph bounds.</param>
public GlyphBounds(CodePoint codePoint, in FontRectangle bounds)
/// <param name="graphemeIndex">The index of the grapheme in original text.</param>
/// <param name="stringIndex">The index of the codepoint in original text..</param>
public GlyphBounds(CodePoint codePoint, in FontRectangle bounds, int graphemeIndex, int stringIndex)
{
this.Codepoint = codePoint;
this.Bounds = bounds;
this.GraphemeIndex = graphemeIndex;
this.StringIndex = stringIndex;
}
/// <summary>
@ -31,6 +35,16 @@ public readonly struct GlyphBounds
/// </summary>
public FontRectangle Bounds { get; }
/// <summary>
/// Gets grapheme index of glyph in original text.
/// </summary>
public int GraphemeIndex { get; }
/// <summary>
/// Gets string index of glyph in original text.
/// </summary>
public int StringIndex { get; }
/// <inheritdoc/>
public override string ToString()
=> $"Codepoint: {this.Codepoint}, Bounds: {this.Bounds}.";

Просмотреть файл

@ -19,7 +19,9 @@ internal readonly struct GlyphLayout
float advanceWidth,
float advanceHeight,
GlyphLayoutMode layoutMode,
bool isStartOfLine)
bool isStartOfLine,
int graphemeIndex,
int stringIndex)
{
this.Glyph = glyph;
this.CodePoint = glyph.GlyphMetrics.CodePoint;
@ -30,6 +32,8 @@ internal readonly struct GlyphLayout
this.AdvanceY = advanceHeight;
this.LayoutMode = layoutMode;
this.IsStartOfLine = isStartOfLine;
this.GraphemeIndex = graphemeIndex;
this.StringIndex = stringIndex;
}
/// <summary>
@ -78,6 +82,16 @@ internal readonly struct GlyphLayout
/// </summary>
public bool IsStartOfLine { get; }
/// <summary>
/// Gets grapheme index of glyph in original text.
/// </summary>
public int GraphemeIndex { get; }
/// <summary>
/// Gets string index of glyph in original text.
/// </summary>
public int StringIndex { get; }
/// <summary>
/// Gets a value indicating whether the glyph represents a whitespace character.
/// </summary>

Просмотреть файл

@ -420,7 +420,9 @@ internal static class TextLayout
data.ScaledAdvance,
advanceY,
GlyphLayoutMode.Horizontal,
i == 0 && j == 0));
i == 0 && j == 0,
data.GraphemeIndex,
data.StringIndex));
j++;
}
@ -556,7 +558,9 @@ internal static class TextLayout
advanceX,
data.ScaledAdvance,
GlyphLayoutMode.Vertical,
i == 0 && j == 0));
i == 0 && j == 0,
data.GraphemeIndex,
data.StringIndex));
j++;
}
@ -689,7 +693,9 @@ internal static class TextLayout
advanceX,
data.ScaledAdvance,
GlyphLayoutMode.VerticalRotated,
i == 0 && j == 0));
i == 0 && j == 0,
data.GraphemeIndex,
data.StringIndex));
j++;
}
@ -712,7 +718,9 @@ internal static class TextLayout
advanceX,
data.ScaledAdvance,
GlyphLayoutMode.Vertical,
i == 0 && j == 0));
i == 0 && j == 0,
data.GraphemeIndex,
data.StringIndex));
j++;
}
@ -895,6 +903,7 @@ internal static class TextLayout
List<TextLine> textLines = new();
TextLine textLine = new();
int glyphCount = 0;
int stringIndex = 0;
// No glyph should contain more than 64 metrics.
// We do a sanity check below just in case.
@ -1205,12 +1214,15 @@ internal static class TextLayout
graphemeIndex,
codePointIndex,
isRotated,
isDecomposed);
isDecomposed,
stringIndex);
}
codePointIndex++;
graphemeCodePointIndex++;
}
stringIndex += graphemeEnumerator.Current.Length;
}
// Add the final line.
@ -1268,7 +1280,8 @@ internal static class TextLayout
int graphemeIndex,
int offset,
bool isRotated,
bool isDecomposed)
bool isDecomposed,
int stringIndex)
{
// Reset metrics.
// We track the maximum metrics for each line to ensure glyphs can be aligned.
@ -1288,7 +1301,8 @@ internal static class TextLayout
graphemeIndex,
offset,
isRotated,
isDecomposed));
isDecomposed,
stringIndex));
}
public TextLine SplitAt(LineBreak lineBreak, bool keepAll)
@ -1635,7 +1649,8 @@ internal static class TextLayout
int graphemeIndex,
int offset,
bool isRotated,
bool isDecomposed)
bool isDecomposed,
int stringIndex)
{
this.Metrics = metrics;
this.PointSize = pointSize;
@ -1648,6 +1663,7 @@ internal static class TextLayout
this.Offset = offset;
this.IsRotated = isRotated;
this.IsDecomposed = isDecomposed;
this.StringIndex = stringIndex;
}
public readonly CodePoint CodePoint => this.Metrics[0].CodePoint;
@ -1676,6 +1692,8 @@ internal static class TextLayout
public bool IsDecomposed { get; }
public int StringIndex { get; }
public readonly bool IsNewLine => CodePoint.IsNewLine(this.CodePoint);
private readonly string DebuggerDisplay => FormattableString

Просмотреть файл

@ -265,7 +265,7 @@ public static class TextMeasurer
GlyphLayout glyph = glyphLayouts[i];
FontRectangle bounds = new(0, 0, glyph.AdvanceX * dpi, glyph.AdvanceY * dpi);
hasSize |= bounds.Width > 0 || bounds.Height > 0;
characterBoundsList[i] = new GlyphBounds(glyph.Glyph.GlyphMetrics.CodePoint, in bounds);
characterBoundsList[i] = new GlyphBounds(glyph.Glyph.GlyphMetrics.CodePoint, in bounds, glyph.GraphemeIndex, glyph.StringIndex);
}
characterBounds = characterBoundsList;
@ -290,7 +290,7 @@ public static class TextMeasurer
bounds = new(0, 0, bounds.Width, bounds.Height);
hasSize |= bounds.Width > 0 || bounds.Height > 0;
characterBoundsList[i] = new GlyphBounds(g.Glyph.GlyphMetrics.CodePoint, in bounds);
characterBoundsList[i] = new GlyphBounds(g.Glyph.GlyphMetrics.CodePoint, in bounds, g.GraphemeIndex, g.StringIndex);
}
characterBounds = characterBoundsList;
@ -312,7 +312,7 @@ public static class TextMeasurer
GlyphLayout g = glyphLayouts[i];
FontRectangle bounds = g.BoundingBox(dpi);
hasSize |= bounds.Width > 0 || bounds.Height > 0;
characterBoundsList[i] = new GlyphBounds(g.Glyph.GlyphMetrics.CodePoint, in bounds);
characterBoundsList[i] = new GlyphBounds(g.Glyph.GlyphMetrics.CodePoint, in bounds, g.GraphemeIndex, g.StringIndex);
}
characterBounds = characterBoundsList;

Просмотреть файл

@ -3,7 +3,6 @@
using System.Globalization;
using System.Numerics;
using System.Text;
using SixLabors.Fonts.Tests.Fakes;
using SixLabors.Fonts.Unicode;
@ -241,10 +240,10 @@ public class TextLayoutTests
const string text = "a b\nc";
GlyphBounds[] expectedGlyphMetrics =
{
new(new CodePoint('a'), new FontRectangle(10, 0, 10, 10)),
new(new CodePoint(' '), new FontRectangle(40, 0, 30, 10)),
new(new CodePoint('b'), new FontRectangle(70, 0, 10, 10)),
new(new CodePoint('c'), new FontRectangle(10, 30, 10, 10)),
new(new CodePoint('a'), new FontRectangle(10, 0, 10, 10), 0, 0),
new(new CodePoint(' '), new FontRectangle(40, 0, 30, 10), 1, 1),
new(new CodePoint('b'), new FontRectangle(70, 0, 10, 10), 2, 2),
new(new CodePoint('c'), new FontRectangle(10, 30, 10, 10), 3, 3),
};
Font font = CreateFont(text);
@ -996,6 +995,73 @@ public class TextLayoutTests
}
}
[Fact]
public void DoesMeasureCharacterLayoutIncludeStringIndex()
{
FontFamily family = new FontCollection().Add(TestFonts.OpenSansFile);
family.TryGetMetrics(FontStyle.Regular, out FontMetrics metrics);
TextOptions options = new(family.CreateFont(metrics.UnitsPerEm))
{
LineSpacing = 1.5F
};
const string text = "The quick👩🏽🚒 brown fox jumps over \r\n the lazy dog";
Assert.True(TextMeasurer.TryMeasureCharacterAdvances(text, options, out ReadOnlySpan<GlyphBounds> advances));
Assert.True(TextMeasurer.TryMeasureCharacterSizes(text, options, out ReadOnlySpan<GlyphBounds> sizes));
Assert.True(TextMeasurer.TryMeasureCharacterBounds(text, options, out ReadOnlySpan<GlyphBounds> bounds));
Assert.Equal(advances.Length, sizes.Length);
Assert.Equal(advances.Length, bounds.Length);
int stringIndex = -1;
for (int i = 0; i < advances.Length; i++)
{
GlyphBounds advance = advances[i];
GlyphBounds size = sizes[i];
GlyphBounds bound = bounds[i];
Assert.Equal(bound.StringIndex, advance.StringIndex);
Assert.Equal(bound.StringIndex, size.StringIndex);
Assert.Equal(bound.GraphemeIndex, advance.GraphemeIndex);
Assert.Equal(bound.GraphemeIndex, size.GraphemeIndex);
if (bound.Codepoint == new CodePoint("k"[0]))
{
stringIndex = text.IndexOf("k", StringComparison.InvariantCulture);
Assert.Equal(stringIndex, bound.StringIndex);
Assert.Equal(stringIndex, bound.GraphemeIndex);
}
// after emoji
if (bound.Codepoint == new CodePoint("b"[0]))
{
stringIndex = text.IndexOf("b", StringComparison.InvariantCulture);
Assert.NotEqual(bound.StringIndex, bound.GraphemeIndex);
Assert.Equal(stringIndex, bound.StringIndex);
Assert.Equal(11, bound.GraphemeIndex);
}
}
SpanGraphemeEnumerator graphemeEnumerator = new(text);
int graphemeCount = 0;
while (graphemeEnumerator.MoveNext())
{
graphemeCount += 1;
}
GlyphBounds firstBound = bounds[0];
Assert.Equal(0, firstBound.StringIndex);
Assert.Equal(0, firstBound.GraphemeIndex);
GlyphBounds lastBound = bounds[^1];
Assert.Equal(text.Length - 1, lastBound.StringIndex);
Assert.Equal(graphemeCount - 1, lastBound.GraphemeIndex);
}
private static readonly Font OpenSansTTF = new FontCollection().Add(TestFonts.OpenSansFile).CreateFont(10);
private static readonly Font OpenSansWoff = new FontCollection().Add(TestFonts.OpenSansFile).CreateFont(10);