Browse Source

Fix text line hit testing

Fix some xml comments
pull/3438/head
Benedikt Schroeder 7 years ago
parent
commit
38bd8b8010
  1. 10
      src/Avalonia.Visuals/Media/TextFormatting/ShapedTextRun.cs
  2. 2
      src/Avalonia.Visuals/Media/TextFormatting/SimpleTextFormatter.cs
  3. 174
      src/Avalonia.Visuals/Media/TextFormatting/SimpleTextLine.cs
  4. 23
      src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs
  5. 18
      src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs
  6. 7
      src/Avalonia.Visuals/Media/TextFormatting/TextShaper.cs
  7. 70
      tests/Avalonia.Skia.UnitTests/SimpleTextFormatterTests.cs

10
src/Avalonia.Visuals/Media/TextFormatting/ShapedTextRun.cs

@ -22,9 +22,7 @@ namespace Avalonia.Media.TextFormatting
GlyphRun = glyphRun; GlyphRun = glyphRun;
} }
/// <summary> /// <inheritdoc/>
/// Gets the bounds.
/// </summary>
public override Rect Bounds => GlyphRun.Bounds; public override Rect Bounds => GlyphRun.Bounds;
/// <summary> /// <summary>
@ -35,11 +33,7 @@ namespace Avalonia.Media.TextFormatting
/// </value> /// </value>
public GlyphRun GlyphRun { get; } public GlyphRun GlyphRun { get; }
/// <summary> /// <inheritdoc/>
/// Draws the <see cref="TextRun"/> at the given origin.
/// </summary>
/// <param name="drawingContext">The drawing context.</param>
/// <param name="origin">The origin.</param>
public override void Draw(IDrawingContextImpl drawingContext, Point origin) public override void Draw(IDrawingContextImpl drawingContext, Point origin)
{ {
if (GlyphRun.GlyphIndices.Length == 0) if (GlyphRun.GlyphIndices.Length == 0)

2
src/Avalonia.Visuals/Media/TextFormatting/SimpleTextFormatter.cs

@ -166,7 +166,7 @@ namespace Avalonia.Media.TextFormatting
var splitResult = SplitTextRuns(textRuns, measuredLength); var splitResult = SplitTextRuns(textRuns, measuredLength);
var trimmedRuns = new List<TextRun>(splitResult.First.Count + 1); var trimmedRuns = new List<ShapedTextRun>(splitResult.First.Count + 1);
trimmedRuns.AddRange(splitResult.First); trimmedRuns.AddRange(splitResult.First);

174
src/Avalonia.Visuals/Media/TextFormatting/SimpleTextLine.cs

@ -6,42 +6,43 @@ namespace Avalonia.Media.TextFormatting
{ {
internal class SimpleTextLine : TextLine internal class SimpleTextLine : TextLine
{ {
public SimpleTextLine(TextPointer textPointer, IReadOnlyList<TextRun> textRuns, TextLineMetrics lineMetrics) : private readonly IReadOnlyList<ShapedTextRun> _textRuns;
base(textPointer, textRuns, lineMetrics)
{
public SimpleTextLine(TextPointer textPointer, IReadOnlyList<ShapedTextRun> textRuns, TextLineMetrics lineMetrics)
{
Text = textPointer;
_textRuns = textRuns;
LineMetrics = lineMetrics;
} }
/// <inheritdoc/>
public override TextPointer Text { get; }
/// <inheritdoc/>
public override IReadOnlyList<TextRun> TextRuns => _textRuns;
/// <inheritdoc/>
public override TextLineMetrics LineMetrics { get; }
/// <inheritdoc/>
public override void Draw(IDrawingContextImpl drawingContext, Point origin) public override void Draw(IDrawingContextImpl drawingContext, Point origin)
{ {
var currentX = origin.X; var currentX = origin.X;
foreach (var textRun in TextRuns) foreach (var textRun in _textRuns)
{ {
if (!(textRun is DrawableTextRun drawableRun))
{
continue;
}
var baselineOrigin = new Point(currentX + LineMetrics.BaselineOrigin.X, var baselineOrigin = new Point(currentX + LineMetrics.BaselineOrigin.X,
origin.Y + LineMetrics.BaselineOrigin.Y); origin.Y + LineMetrics.BaselineOrigin.Y);
drawableRun.Draw(drawingContext, baselineOrigin); textRun.Draw(drawingContext, baselineOrigin);
currentX += drawableRun.Bounds.Width; currentX += textRun.Bounds.Width;
} }
} }
/// <summary> /// <inheritdoc/>
/// Client to get the character hit corresponding to the specified
/// distance from the beginning of the line.
/// </summary>
/// <param name="distance">distance in text flow direction from the beginning of the line</param>
/// <returns>character hit</returns>
public override CharacterHit GetCharacterHitFromDistance(double distance) public override CharacterHit GetCharacterHitFromDistance(double distance)
{ {
var first = Text.Start;
if (distance < 0) if (distance < 0)
{ {
// hit happens before the line, return the first position // hit happens before the line, return the first position
@ -49,45 +50,30 @@ namespace Avalonia.Media.TextFormatting
} }
// process hit that happens within the line // process hit that happens within the line
var runIndex = new CharacterHit(); var characterHit = new CharacterHit();
foreach (var run in TextRuns) foreach (var run in _textRuns)
{ {
var shapedTextRun = (ShapedTextRun)run; characterHit = run.GlyphRun.GetCharacterHitFromDistance(distance, out _);
first += runIndex.TrailingLength;
runIndex = shapedTextRun.GlyphRun.GetCharacterHitFromDistance(distance, out _); if (distance <= run.Bounds.Width)
first += runIndex.FirstCharacterIndex;
if (distance <= shapedTextRun.Bounds.Width)
{ {
break; break;
} }
distance -= shapedTextRun.Bounds.Width; distance -= run.Bounds.Width;
} }
return new CharacterHit(first, runIndex.TrailingLength); return characterHit;
} }
/// <summary> /// <inheritdoc/>
/// Client to get the distance from the beginning of the line from the specified
/// character hit.
/// </summary>
/// <param name="characterHit">character hit of the character to query the distance.</param>
/// <returns>distance in text flow direction from the beginning of the line.</returns>
public override double GetDistanceFromCharacterHit(CharacterHit characterHit) public override double GetDistanceFromCharacterHit(CharacterHit characterHit)
{ {
return DistanceFromCp(characterHit.FirstCharacterIndex + (characterHit.TrailingLength != 0 ? 1 : 0)); return DistanceFromCodepointIndex(characterHit.FirstCharacterIndex + (characterHit.TrailingLength != 0 ? 1 : 0));
} }
/// <summary> /// <inheritdoc/>
/// Client to get the next character hit for caret navigation
/// </summary>
/// <param name="characterHit">the current character hit</param>
/// <returns>the next character hit</returns>
public override CharacterHit GetNextCaretCharacterHit(CharacterHit characterHit) public override CharacterHit GetNextCaretCharacterHit(CharacterHit characterHit)
{ {
int nextVisibleCp; int nextVisibleCp;
@ -95,7 +81,7 @@ namespace Avalonia.Media.TextFormatting
if (characterHit.TrailingLength == 0) if (characterHit.TrailingLength == 0)
{ {
navigableCpFound = FindNextVisibleCp(characterHit.FirstCharacterIndex, out nextVisibleCp); navigableCpFound = FindNextCodepointIndex(characterHit.FirstCharacterIndex, out nextVisibleCp);
if (navigableCpFound) if (navigableCpFound)
{ {
@ -104,7 +90,7 @@ namespace Avalonia.Media.TextFormatting
} }
} }
navigableCpFound = FindNextVisibleCp(characterHit.FirstCharacterIndex + 1, out nextVisibleCp); navigableCpFound = FindNextCodepointIndex(characterHit.FirstCharacterIndex + 1, out nextVisibleCp);
if (navigableCpFound) if (navigableCpFound)
{ {
@ -116,18 +102,14 @@ namespace Avalonia.Media.TextFormatting
return characterHit; return characterHit;
} }
/// <summary> /// <inheritdoc/>
/// Client to get the previous character hit for caret navigation
/// </summary>
/// <param name="characterHit">the current character hit</param>
/// <returns>the previous character hit</returns>
public override CharacterHit GetPreviousCaretCharacterHit(CharacterHit characterHit) public override CharacterHit GetPreviousCaretCharacterHit(CharacterHit characterHit)
{ {
int previousVisibleCp; int previousCodepointIndex;
bool navigableCpFound; bool codepointIndexFound;
int cpHit = characterHit.FirstCharacterIndex; var cpHit = characterHit.FirstCharacterIndex;
bool trailingHit = (characterHit.TrailingLength != 0); var trailingHit = characterHit.TrailingLength != 0;
// Input can be right after the end of the current line. Snap it to be at the end of the line. // Input can be right after the end of the current line. Snap it to be at the end of the line.
if (cpHit >= Text.Start + Text.Length) if (cpHit >= Text.Start + Text.Length)
@ -139,32 +121,28 @@ namespace Avalonia.Media.TextFormatting
if (trailingHit) if (trailingHit)
{ {
navigableCpFound = FindPreviousVisibleCp(cpHit, out previousVisibleCp); codepointIndexFound = FindPreviousCodepointIndex(cpHit, out previousCodepointIndex);
if (navigableCpFound) if (codepointIndexFound)
{ {
// Move from trailing to leading edge // Move from trailing to leading edge
return new CharacterHit(previousVisibleCp, 0); return new CharacterHit(previousCodepointIndex, 0);
} }
} }
navigableCpFound = FindPreviousVisibleCp(cpHit - 1, out previousVisibleCp); codepointIndexFound = FindPreviousCodepointIndex(cpHit - 1, out previousCodepointIndex);
if (navigableCpFound) if (codepointIndexFound)
{ {
// Move from leading edge of current character to leading edge of previous // Move from leading edge of current character to leading edge of previous
return new CharacterHit(previousVisibleCp, 0); return new CharacterHit(previousCodepointIndex, 0);
} }
// Can't move, we're before the first character // Can't move, we're before the first character
return characterHit; return characterHit;
} }
/// <summary> /// <inheritdoc/>
/// Client to get the previous character hit after backspacing
/// </summary>
/// <param name="characterHit">the current character hit</param>
/// <returns>the character hit after backspacing</returns>
public override CharacterHit GetBackspaceCaretCharacterHit(CharacterHit characterHit) public override CharacterHit GetBackspaceCaretCharacterHit(CharacterHit characterHit)
{ {
// same operation as move-to-previous // same operation as move-to-previous
@ -172,44 +150,41 @@ namespace Avalonia.Media.TextFormatting
} }
/// <summary> /// <summary>
/// Get distance from line start to the specified cp /// Get distance from line start to the specified codepoint index
/// </summary> /// </summary>
private double DistanceFromCp(int currentIndex) private double DistanceFromCodepointIndex(int codepointIndex)
{ {
var distance = 0.0; var currentDistance = 0.0;
var dcp = currentIndex - Text.Start;
foreach (var textRun in TextRuns) foreach (var textRun in _textRuns)
{ {
var run = (ShapedTextRun)textRun; if (codepointIndex > textRun.Text.End)
distance += run.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(dcp));
if (dcp <= run.Text.Length)
{ {
break; currentDistance += textRun.Bounds.Width;
continue;
} }
dcp -= run.Text.Length; return currentDistance + textRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(codepointIndex));
} }
return distance; return currentDistance;
} }
/// <summary> /// <summary>
/// Search forward from the given cp index (inclusive) to find the next navigable cp index. /// Search forward from the given codepoint index (inclusive) to find the next navigable codepoint index.
/// Return true if one such cp is found, false otherwise. /// Return true if one such codepoint index is found, false otherwise.
/// </summary> /// </summary>
private bool FindNextVisibleCp(int cp, out int cpVisible) private bool FindNextCodepointIndex(int codepointIndex, out int nextCodepointIndex)
{ {
cpVisible = cp; nextCodepointIndex = codepointIndex;
if (cp >= Text.Start + Text.Length) if (codepointIndex >= Text.Start + Text.Length)
{ {
return false; // Cannot go forward anymore return false; // Cannot go forward anymore
} }
GetRunIndexAtCp(cp, out var runIndex, out var cpRunStart); GetRunIndexAtCodepointIndex(codepointIndex, out var runIndex, out var cpRunStart);
while (runIndex < TextRuns.Count) while (runIndex < TextRuns.Count)
{ {
@ -217,7 +192,7 @@ namespace Avalonia.Media.TextFormatting
// navigable. // navigable.
if (runIndex < TextRuns.Count) if (runIndex < TextRuns.Count)
{ {
cpVisible = Math.Max(cpRunStart, cp); nextCodepointIndex = Math.Max(cpRunStart, codepointIndex);
return true; return true;
} }
@ -228,29 +203,29 @@ namespace Avalonia.Media.TextFormatting
} }
/// <summary> /// <summary>
/// Search backward from the given cp index (inclusive) to find the previous navigable cp index. /// Search backward from the given codepoint index (inclusive) to find the previous navigable codepoint index.
/// Return true if one such cp is found, false otherwise. /// Return true if one such codepoint is found, false otherwise.
/// </summary> /// </summary>
private bool FindPreviousVisibleCp(int cp, out int cpVisible) private bool FindPreviousCodepointIndex(int codepointIndex, out int previousCodepointIndex)
{ {
cpVisible = cp; previousCodepointIndex = codepointIndex;
if (cp < Text.Start) if (codepointIndex < Text.Start)
{ {
return false; // Cannot go backward anymore. return false; // Cannot go backward anymore.
} }
// Position the cpRunEnd at the end of the span that contains the given cp // Position the cpRunEnd at the end of the span that contains the given cp
GetRunIndexAtCp(cp, out var runIndex, out var cpRunEnd); GetRunIndexAtCodepointIndex(codepointIndex, out var runIndex, out var codepointIndexAtRunEnd);
cpRunEnd += TextRuns[runIndex].Text.End; codepointIndexAtRunEnd += TextRuns[runIndex].Text.End;
while (runIndex >= 0) while (runIndex >= 0)
{ {
// Visible content has caret stops at its leading edge. // Visible content has caret stops at its leading edge.
if (runIndex + 1 < TextRuns.Count) if (runIndex + 1 < TextRuns.Count)
{ {
cpVisible = Math.Min(cpRunEnd, cp); previousCodepointIndex = Math.Min(codepointIndexAtRunEnd, codepointIndex);
return true; return true;
} }
@ -258,25 +233,26 @@ namespace Avalonia.Media.TextFormatting
if (runIndex == TextRuns.Count) if (runIndex == TextRuns.Count)
{ {
// Get the cp index at the beginning of the newline sequence. // Get the cp index at the beginning of the newline sequence.
cpVisible = cpRunEnd - TextRuns[runIndex].Text.Length + 1; previousCodepointIndex = codepointIndexAtRunEnd - TextRuns[runIndex].Text.Length + 1;
return true; return true;
} }
cpRunEnd -= TextRuns[runIndex--].Text.Length; codepointIndexAtRunEnd -= TextRuns[runIndex--].Text.Length;
} }
return false; return false;
} }
private void GetRunIndexAtCp(int cp, out int runIndex, out int cpRunStart) private void GetRunIndexAtCodepointIndex(int codepointIndex, out int runIndex, out int codepointIndexAtRunStart)
{ {
cpRunStart = Text.Start; codepointIndexAtRunStart = Text.Start;
runIndex = 0; runIndex = 0;
// Find the span that contains the given cp // Find the span that contains the given cp
while (runIndex < TextRuns.Count && cpRunStart + TextRuns[runIndex].Text.Length <= cp) while (runIndex < TextRuns.Count &&
codepointIndexAtRunStart + TextRuns[runIndex].Text.Length <= codepointIndex)
{ {
cpRunStart += TextRuns[runIndex++].Text.Length; codepointIndexAtRunStart += TextRuns[runIndex++].Text.Length;
} }
} }
} }

23
src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs

@ -20,7 +20,7 @@ namespace Avalonia.Media.TextFormatting
private readonly ReadOnlySlice<char> _text; private readonly ReadOnlySlice<char> _text;
private readonly TextParagraphProperties _paragraphProperties; private readonly TextParagraphProperties _paragraphProperties;
private readonly TextStyleRun[] _textStyleOverrides; private readonly IReadOnlyList<TextStyleRun> _textStyleOverrides;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TextLayout" /> class. /// Initializes a new instance of the <see cref="TextLayout" /> class.
@ -47,7 +47,7 @@ namespace Avalonia.Media.TextFormatting
TextDecorationCollection textDecorations = null, TextDecorationCollection textDecorations = null,
double maxWidth = double.PositiveInfinity, double maxWidth = double.PositiveInfinity,
double maxHeight = double.PositiveInfinity, double maxHeight = double.PositiveInfinity,
TextStyleRun[] textStyleOverrides = null) IReadOnlyList<TextStyleRun> textStyleOverrides = null)
{ {
_text = string.IsNullOrEmpty(text) ? _text = string.IsNullOrEmpty(text) ?
new ReadOnlySlice<char>() : new ReadOnlySlice<char>() :
@ -276,10 +276,10 @@ namespace Avalonia.Media.TextFormatting
{ {
private readonly ReadOnlySlice<char> _text; private readonly ReadOnlySlice<char> _text;
private readonly TextStyle _defaultStyle; private readonly TextStyle _defaultStyle;
private readonly TextStyleRun[] _textStyleOverrides; private readonly IReadOnlyList<TextStyleRun> _textStyleOverrides;
public FormattedTextSource(ReadOnlySlice<char> text, TextStyle defaultStyle, public FormattedTextSource(ReadOnlySlice<char> text, TextStyle defaultStyle,
TextStyleRun[] textStyleOverrides) IReadOnlyList<TextStyleRun> textStyleOverrides)
{ {
_text = text; _text = text;
_defaultStyle = defaultStyle; _defaultStyle = defaultStyle;
@ -311,8 +311,13 @@ namespace Avalonia.Media.TextFormatting
/// The created text style run. /// The created text style run.
/// </returns> /// </returns>
private static TextStyleRun CreateTextStyleRunWithOverride(ReadOnlySlice<char> text, private static TextStyleRun CreateTextStyleRunWithOverride(ReadOnlySlice<char> text,
TextStyle defaultTextStyle, ReadOnlySpan<TextStyleRun> textStyleOverrides) TextStyle defaultTextStyle, IReadOnlyList<TextStyleRun> textStyleOverrides)
{ {
if(textStyleOverrides == null || textStyleOverrides.Count == 0)
{
return new TextStyleRun(new TextPointer(text.Start, text.Length), defaultTextStyle);
}
var currentTextStyle = defaultTextStyle; var currentTextStyle = defaultTextStyle;
var hasOverride = false; var hasOverride = false;
@ -321,7 +326,7 @@ namespace Avalonia.Media.TextFormatting
var length = 0; var length = 0;
for (; i < textStyleOverrides.Length; i++) for (; i < textStyleOverrides.Count; i++)
{ {
var styleOverride = textStyleOverrides[i]; var styleOverride = textStyleOverrides[i];
@ -341,7 +346,7 @@ namespace Avalonia.Media.TextFormatting
if (textPointer.Start > text.Start) if (textPointer.Start > text.Start)
{ {
if (styleOverride.Style.TextFormat != currentTextStyle.TextFormat || if (styleOverride.Style.TextFormat != currentTextStyle.TextFormat ||
currentTextStyle.Foreground != styleOverride.Style.Foreground) !currentTextStyle.Foreground.Equals(styleOverride.Style.Foreground))
{ {
length = Math.Min(Math.Abs(textPointer.Start - text.Start), text.Length); length = Math.Min(Math.Abs(textPointer.Start - text.Start), text.Length);
@ -361,9 +366,9 @@ namespace Avalonia.Media.TextFormatting
currentTextStyle = styleOverride.Style; currentTextStyle = styleOverride.Style;
} }
if (length < text.Length && i == textStyleOverrides.Length) if (length < text.Length && i == textStyleOverrides.Count)
{ {
if (currentTextStyle.Foreground == defaultTextStyle.Foreground && if (currentTextStyle.Foreground.Equals(defaultTextStyle.Foreground) &&
currentTextStyle.TextFormat == defaultTextStyle.TextFormat) currentTextStyle.TextFormat == defaultTextStyle.TextFormat)
{ {
length = text.Length; length = text.Length;

18
src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs

@ -11,25 +11,13 @@ namespace Avalonia.Media.TextFormatting
/// </summary> /// </summary>
public abstract class TextLine public abstract class TextLine
{ {
protected TextLine()
{
}
protected TextLine(TextPointer text, IReadOnlyList<TextRun> textRuns, TextLineMetrics lineMetrics)
{
Text = text;
TextRuns = textRuns;
LineMetrics = lineMetrics;
}
/// <summary> /// <summary>
/// Gets the text. /// Gets the text.
/// </summary> /// </summary>
/// <value> /// <value>
/// The text pointer. /// The text pointer.
/// </value> /// </value>
public TextPointer Text { get; protected set; } public abstract TextPointer Text { get; }
/// <summary> /// <summary>
/// Gets the text runs. /// Gets the text runs.
@ -37,7 +25,7 @@ namespace Avalonia.Media.TextFormatting
/// <value> /// <value>
/// The text runs. /// The text runs.
/// </value> /// </value>
public IReadOnlyList<TextRun> TextRuns { get; protected set; } public abstract IReadOnlyList<TextRun> TextRuns { get; }
/// <summary> /// <summary>
/// Gets the line metrics. /// Gets the line metrics.
@ -45,7 +33,7 @@ namespace Avalonia.Media.TextFormatting
/// <value> /// <value>
/// The line metrics. /// The line metrics.
/// </value> /// </value>
public TextLineMetrics LineMetrics { get; protected set; } public abstract TextLineMetrics LineMetrics { get; }
/// <summary> /// <summary>
/// Draws the <see cref="TextLine"/> at the given origin. /// Draws the <see cref="TextLine"/> at the given origin.

7
src/Avalonia.Visuals/Media/TextFormatting/TextShaper.cs

@ -43,12 +43,7 @@ namespace Avalonia.Media.TextFormatting
} }
} }
/// <summary> /// <inheritdoc cref="ITextShaperImpl.ShapeText"/>
/// Shapes the specified text and returns a resulting glyph run.
/// </summary>
/// <param name="text">The text.</param>
/// <param name="textFormat">The text format.</param>
/// <returns>A shaped glyph run.</returns>
public GlyphRun ShapeText(ReadOnlySlice<char> text, TextFormat textFormat) public GlyphRun ShapeText(ReadOnlySlice<char> text, TextFormat textFormat)
{ {
return _platformImpl.ShapeText(text, textFormat); return _platformImpl.ShapeText(text, textFormat);

70
tests/Avalonia.Skia.UnitTests/SimpleTextFormatterTests.cs

@ -43,7 +43,7 @@ namespace Avalonia.Skia.UnitTests
{ {
var defaultTextRunStyle = new TextStyle(Typeface.Default, 12, Brushes.Black); var defaultTextRunStyle = new TextStyle(Typeface.Default, 12, Brushes.Black);
var textSource = new MultipleBufferTextSource(defaultTextRunStyle); var textSource = new MultiBufferTextSource(defaultTextRunStyle);
var formatter = new SimpleTextFormatter(); var formatter = new SimpleTextFormatter();
@ -56,18 +56,20 @@ namespace Avalonia.Skia.UnitTests
} }
} }
private class MultipleBufferTextSource : ITextSource private class MultiBufferTextSource : ITextSource
{ {
private readonly string[] _runTexts; private readonly string[] _runTexts;
private readonly TextStyle _defaultStyle; private readonly TextStyle _defaultStyle;
public MultipleBufferTextSource(TextStyle defaultStyle) public MultiBufferTextSource(TextStyle defaultStyle)
{ {
_defaultStyle = defaultStyle; _defaultStyle = defaultStyle;
_runTexts = new[] { "A123456789", "B123456789", "C123456789", "D123456789", "E123456789" }; _runTexts = new[] { "A123456789", "B123456789", "C123456789", "D123456789", "E123456789" };
} }
public TextPointer TextPointer => new TextPointer(0, 50);
public TextRun GetTextRun(int textSourceIndex) public TextRun GetTextRun(int textSourceIndex)
{ {
if (textSourceIndex == 50) if (textSourceIndex == 50)
@ -197,7 +199,7 @@ namespace Avalonia.Skia.UnitTests
} }
[Fact] [Fact]
public void Should_Split_Run_On_Direction() public void Should_Split_Run_On_Script()
{ {
using (Start()) using (Start())
{ {
@ -219,18 +221,36 @@ namespace Avalonia.Skia.UnitTests
{ {
using (Start()) using (Start())
{ {
const string text = "0123456789"; var textSource = new MultiBufferTextSource(new TextStyle(Typeface.Default));
var textSource = new SimpleTextSource(text, new TextStyle(Typeface.Default));
var formatter = new SimpleTextFormatter(); var formatter = new SimpleTextFormatter();
var textLine = var textLine =
formatter.FormatLine(textSource, 0, double.PositiveInfinity, new TextParagraphProperties()); formatter.FormatLine(textSource, 0, double.PositiveInfinity, new TextParagraphProperties());
var distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(text.Length)); var currentDistance = 0.0;
foreach (var run in textLine.TextRuns)
{
var textRun = (ShapedTextRun)run;
var glyphRun = textRun.GlyphRun;
Assert.Equal(textLine.LineMetrics.Size.Width, distance); for (var i = 0; i < glyphRun.GlyphClusters.Length; i++)
{
var cluster = glyphRun.GlyphClusters[i];
var advance = glyphRun.GlyphAdvances[i];
var distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(cluster));
Assert.Equal(currentDistance, distance);
currentDistance += advance;
}
}
Assert.Equal(currentDistance, textLine.GetDistanceFromCharacterHit(new CharacterHit(textSource.TextPointer.Length)));
} }
} }
@ -239,18 +259,40 @@ namespace Avalonia.Skia.UnitTests
{ {
using (Start()) using (Start())
{ {
const string text = "0123456789"; var textSource = new MultiBufferTextSource(new TextStyle(Typeface.Default));
var textSource = new SimpleTextSource(text, new TextStyle(Typeface.Default));
var formatter = new SimpleTextFormatter(); var formatter = new SimpleTextFormatter();
var textLine = var textLine =
formatter.FormatLine(textSource, 0, double.PositiveInfinity, new TextParagraphProperties()); formatter.FormatLine(textSource, 0, double.PositiveInfinity, new TextParagraphProperties());
var characterHit = textLine.GetCharacterHitFromDistance(textLine.LineMetrics.Size.Width); var currentDistance = 0.0;
CharacterHit characterHit;
foreach (var run in textLine.TextRuns)
{
var textRun = (ShapedTextRun)run;
var glyphRun = textRun.GlyphRun;
for (var i = 0; i < glyphRun.GlyphClusters.Length; i++)
{
var cluster = glyphRun.GlyphClusters[i];
var advance = glyphRun.GlyphAdvances[i];
characterHit = textLine.GetCharacterHitFromDistance(currentDistance);
Assert.Equal(cluster, characterHit.FirstCharacterIndex + characterHit.TrailingLength);
currentDistance += advance;
}
}
characterHit = textLine.GetCharacterHitFromDistance(textLine.LineMetrics.Size.Width);
Assert.Equal(textLine.Text.Length, characterHit.FirstCharacterIndex + characterHit.TrailingLength); Assert.Equal(textSource.TextPointer.End, characterHit.FirstCharacterIndex);
} }
} }

Loading…
Cancel
Save