зеркало из https://github.com/SixLabors/Fonts.git
Merge branch 'main' into fix-issue-393
This commit is contained in:
Коммит
89b4ce7c6c
|
@ -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);
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче