Browse Source

Merge branch 'master' into fixes/ControlCatalog

pull/9054/head
Max Katz 4 years ago
committed by GitHub
parent
commit
42b3cfba4c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      samples/RenderDemo/Pages/FormattedTextPage.axaml.cs
  2. 1
      samples/Sandbox/MainWindow.axaml
  3. 106
      src/Avalonia.Base/Media/FormattedText.cs
  4. 13
      src/Avalonia.Base/Media/Geometry.cs
  5. 11
      src/Avalonia.Base/Media/GlyphRun.cs
  6. 253
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  7. 2
      src/Avalonia.Base/Media/TextFormatting/TextRunBounds.cs
  8. 14
      src/Avalonia.Controls/Documents/InlineCollection.cs
  9. 83
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

5
samples/RenderDemo/Pages/FormattedTextPage.axaml.cs

@ -3,6 +3,7 @@ using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Immutable;
namespace RenderDemo.Pages namespace RenderDemo.Pages
{ {
@ -59,6 +60,10 @@ namespace RenderDemo.Pages
var geometry = formattedText.BuildGeometry(new Point(10 + formattedText.Width + 10, 0)); var geometry = formattedText.BuildGeometry(new Point(10 + formattedText.Width + 10, 0));
context.DrawGeometry(gradient, null, geometry); context.DrawGeometry(gradient, null, geometry);
var highlightGeometry = formattedText.BuildHighlightGeometry(new Point(10 + formattedText.Width + 10, 0));
context.DrawGeometry(null, new ImmutablePen(gradient.ToImmutable(), 2), highlightGeometry);
} }
} }
} }

1
samples/Sandbox/MainWindow.axaml

@ -1,5 +1,4 @@
<Window xmlns="https://github.com/avaloniaui" <Window xmlns="https://github.com/avaloniaui"
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
x:Class="Sandbox.MainWindow"> x:Class="Sandbox.MainWindow">
<TextBox />
</Window> </Window>

106
src/Avalonia.Base/Media/FormattedText.cs

@ -1,8 +1,10 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using Avalonia.Controls;
using Avalonia.Media.TextFormatting; using Avalonia.Media.TextFormatting;
using Avalonia.Utilities; using Avalonia.Utilities;
@ -654,14 +656,16 @@ namespace Avalonia.Media
// line break before _currentLine, needed in case we have to reformat it with collapsing symbol // line break before _currentLine, needed in case we have to reformat it with collapsing symbol
private TextLineBreak? _previousLineBreak; private TextLineBreak? _previousLineBreak;
private int _position;
private int _length;
internal LineEnumerator(FormattedText text) internal LineEnumerator(FormattedText text)
{ {
_previousHeight = 0; _previousHeight = 0;
Length = 0; _length = 0;
_previousLineBreak = null; _previousLineBreak = null;
Position = 0; _position = 0;
_lineCount = 0; _lineCount = 0;
_totalHeight = 0; _totalHeight = 0;
Current = null; Current = null;
@ -678,9 +682,17 @@ namespace Avalonia.Media
_nextLine = null; _nextLine = null;
} }
private int Position { get; set; } public int Position
{
get => _position;
private set => _position = value;
}
private int Length { get; set; } public int Length
{
get => _length;
private set => _length = value;
}
/// <summary> /// <summary>
/// Gets the current text line in the collection /// Gets the current text line in the collection
@ -1292,6 +1304,92 @@ namespace Avalonia.Media
return accumulatedGeometry; return accumulatedGeometry;
} }
/// <summary>
/// Builds a highlight geometry object.
/// </summary>
/// <param name="origin">The origin of the highlight region</param>
/// <returns>Geometry that surrounds the text.</returns>
public Geometry? BuildHighlightGeometry(Point origin)
{
return BuildHighlightGeometry(origin, 0, _text.Length);
}
/// <summary>
/// Builds a highlight geometry object for a given character range.
/// </summary>
/// <param name="origin">The origin of the highlight region.</param>
/// <param name="startIndex">The start index of initial character the bounds should be obtained for.</param>
/// <param name="count">The number of characters the bounds should be obtained for.</param>
/// <returns>Geometry that surrounds the specified character range.</returns>
public Geometry? BuildHighlightGeometry(Point origin, int startIndex, int count)
{
ValidateRange(startIndex, count);
Geometry? accumulatedBounds = null;
using (var enumerator = GetEnumerator())
{
var lineOrigin = origin;
while (enumerator.MoveNext())
{
var currentLine = enumerator.Current!;
int x0 = Math.Max(enumerator.Position, startIndex);
int x1 = Math.Min(enumerator.Position + enumerator.Length, startIndex + count);
// check if this line is intersects with the specified character range
if (x0 < x1)
{
var highlightBounds = currentLine.GetTextBounds(x0,x1 - x0);
if (highlightBounds != null)
{
foreach (var bound in highlightBounds)
{
var rect = bound.Rectangle;
if (FlowDirection == FlowDirection.RightToLeft)
{
// Convert logical units (which extend leftward from the right edge
// of the paragraph) to physical units.
//
// Note that since rect is in logical units, rect.Right corresponds to
// the visual *left* edge of the rectangle in the RTL case. Specifically,
// is the distance leftward from the right edge of the formatting rectangle
// whose width is the paragraph width passed to FormatLine.
//
rect = rect.WithX(enumerator.CurrentParagraphWidth - rect.Right);
}
rect = new Rect(new Point(rect.X + lineOrigin.X, rect.Y + lineOrigin.Y), rect.Size);
RectangleGeometry rectangleGeometry = new RectangleGeometry(rect);
if (accumulatedBounds == null)
{
accumulatedBounds = rectangleGeometry;
}
else
{
accumulatedBounds = Geometry.Combine(accumulatedBounds, rectangleGeometry, GeometryCombineMode.Union);
}
}
}
}
AdvanceLineOrigin(ref lineOrigin, currentLine);
}
}
if (accumulatedBounds?.PlatformImpl == null || accumulatedBounds.PlatformImpl.Bounds.IsEmpty)
{
return null;
}
return accumulatedBounds;
}
/// <summary> /// <summary>
/// Draws the text object /// Draws the text object
/// </summary> /// </summary>

13
src/Avalonia.Base/Media/Geometry.cs

@ -185,5 +185,18 @@ namespace Avalonia.Media
var control = e.Sender as Geometry; var control = e.Sender as Geometry;
control?.InvalidateGeometry(); control?.InvalidateGeometry();
} }
/// <summary>
/// Combines the two geometries using the specified <see cref="GeometryCombineMode"/> and applies the specified transform to the resulting geometry.
/// </summary>
/// <param name="geometry1">The first geometry to combine.</param>
/// <param name="geometry2">The second geometry to combine.</param>
/// <param name="combineMode">One of the enumeration values that specifies how the geometries are combined.</param>
/// <param name="transform">A transformation to apply to the combined geometry, or <c>null</c>.</param>
/// <returns></returns>
public static Geometry Combine(Geometry geometry1, RectangleGeometry geometry2, GeometryCombineMode combineMode, Transform? transform = null)
{
return new CombinedGeometry(combineMode, geometry1, geometry2, transform);
}
} }
} }

11
src/Avalonia.Base/Media/GlyphRun.cs

@ -789,14 +789,15 @@ namespace Avalonia.Media
var clusterLength = 1; var clusterLength = 1;
while (i - 1 >= 0) var j = i;
while (j - 1 >= 0)
{ {
var nextCluster = GlyphClusters[i - 1]; var nextCluster = GlyphClusters[--j];
if (currentCluster == nextCluster) if (currentCluster == nextCluster)
{ {
clusterLength++; clusterLength++;
i--;
continue; continue;
} }
@ -811,7 +812,7 @@ namespace Avalonia.Media
trailingWhitespaceLength += clusterLength; trailingWhitespaceLength += clusterLength;
glyphCount++; glyphCount += clusterLength;
} }
} }

253
src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs

@ -128,7 +128,7 @@ namespace Avalonia.Media.TextFormatting
var collapsingProperties = collapsingPropertiesList[0]; var collapsingProperties = collapsingPropertiesList[0];
if(collapsingProperties is null) if (collapsingProperties is null)
{ {
return this; return this;
} }
@ -192,7 +192,7 @@ namespace Avalonia.Media.TextFormatting
{ {
var currentRun = _textRuns[i]; var currentRun = _textRuns[i];
if(currentRun is ShapedTextCharacters shapedRun && !shapedRun.ShapedBuffer.IsLeftToRight) if (currentRun is ShapedTextCharacters shapedRun && !shapedRun.ShapedBuffer.IsLeftToRight)
{ {
var rightToLeftIndex = i; var rightToLeftIndex = i;
currentPosition += currentRun.TextSourceLength; currentPosition += currentRun.TextSourceLength;
@ -213,14 +213,14 @@ namespace Avalonia.Media.TextFormatting
for (var j = i; i <= rightToLeftIndex; j++) for (var j = i; i <= rightToLeftIndex; j++)
{ {
if(j > _textRuns.Count - 1) if (j > _textRuns.Count - 1)
{ {
break; break;
} }
currentRun = _textRuns[j]; currentRun = _textRuns[j];
if(currentDistance + currentRun.Size.Width <= distance) if (currentDistance + currentRun.Size.Width <= distance)
{ {
currentDistance += currentRun.Size.Width; currentDistance += currentRun.Size.Width;
currentPosition -= currentRun.TextSourceLength; currentPosition -= currentRun.TextSourceLength;
@ -266,10 +266,6 @@ namespace Avalonia.Media.TextFormatting
{ {
offset = Math.Max(0, currentPosition - shapedRun.Text.Start); offset = Math.Max(0, currentPosition - shapedRun.Text.Start);
} }
//else
//{
// offset = Math.Max(0, currentPosition - shapedRun.Text.Start + shapedRun.Text.Length);
//}
characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength); characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength);
@ -326,11 +322,11 @@ namespace Avalonia.Media.TextFormatting
continue; continue;
} }
break; break;
} }
if(i > index) if (i > index)
{ {
while (i >= index) while (i >= index)
{ {
@ -354,7 +350,7 @@ namespace Avalonia.Media.TextFormatting
} }
} }
if (currentPosition + currentRun.TextSourceLength >= characterIndex && if (currentPosition + currentRun.TextSourceLength >= characterIndex &&
TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength, flowDirection, out var distance, out _)) TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength, flowDirection, out var distance, out _))
{ {
return Math.Max(0, currentDistance + distance); return Math.Max(0, currentDistance + distance);
@ -534,6 +530,8 @@ namespace Avalonia.Media.TextFormatting
double currentWidth = 0; double currentWidth = 0;
var currentRect = Rect.Empty; var currentRect = Rect.Empty;
TextRunBounds lastRunBounds = default;
for (var index = 0; index < TextRuns.Count; index++) for (var index = 0; index < TextRuns.Count; index++)
{ {
if (TextRuns[index] is not DrawableTextRun currentRun) if (TextRuns[index] is not DrawableTextRun currentRun)
@ -543,53 +541,93 @@ namespace Avalonia.Media.TextFormatting
var characterLength = 0; var characterLength = 0;
var endX = startX; var endX = startX;
var runWidth = 0.0;
TextRunBounds? currentRunBounds = null;
var currentShapedRun = currentRun as ShapedTextCharacters; var currentShapedRun = currentRun as ShapedTextCharacters;
TextRunBounds currentRunBounds;
double combinedWidth;
if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex)
{
startX += currentRun.Size.Width;
currentPosition += currentRun.TextSourceLength;
continue;
}
if (currentShapedRun != null && !currentShapedRun.ShapedBuffer.IsLeftToRight) if (currentShapedRun != null && !currentShapedRun.ShapedBuffer.IsLeftToRight)
{ {
var rightToLeftIndex = index; var rightToLeftIndex = index;
startX += currentShapedRun.Size.Width; var rightToLeftWidth = currentShapedRun.Size.Width;
while (rightToLeftIndex + 1 <= _textRuns.Count - 1) while (rightToLeftIndex + 1 <= _textRuns.Count - 1 && _textRuns[rightToLeftIndex + 1] is ShapedTextCharacters nextShapedRun)
{ {
var nextShapedRun = _textRuns[rightToLeftIndex + 1] as ShapedTextCharacters;
if (nextShapedRun == null || nextShapedRun.ShapedBuffer.IsLeftToRight) if (nextShapedRun == null || nextShapedRun.ShapedBuffer.IsLeftToRight)
{ {
break; break;
} }
startX += nextShapedRun.Size.Width;
rightToLeftIndex++; rightToLeftIndex++;
rightToLeftWidth += nextShapedRun.Size.Width;
if (currentPosition + nextShapedRun.TextSourceLength > firstTextSourceIndex + textLength)
{
break;
}
currentShapedRun = nextShapedRun;
} }
if (TryGetTextRunBoundsRightToLeft(startX, firstTextSourceIndex, characterIndex, rightToLeftIndex, ref currentPosition, ref remainingLength, out currentRunBounds)) startX = startX + rightToLeftWidth;
currentRunBounds = GetRightToLeftTextRunBounds(currentShapedRun, startX, firstTextSourceIndex, characterIndex, currentPosition, remainingLength);
remainingLength -= currentRunBounds.Length;
currentPosition = currentRunBounds.TextSourceCharacterIndex + currentRunBounds.Length;
endX = currentRunBounds.Rectangle.Right;
startX = currentRunBounds.Rectangle.Left;
var rightToLeftRunBounds = new List<TextRunBounds> { currentRunBounds };
for (int i = rightToLeftIndex - 1; i >= index; i--)
{ {
startX = currentRunBounds!.Rectangle.Left; currentShapedRun = TextRuns[i] as ShapedTextCharacters;
endX = currentRunBounds.Rectangle.Right;
if(currentShapedRun == null)
{
continue;
}
runWidth = currentRunBounds.Rectangle.Width; currentRunBounds = GetRightToLeftTextRunBounds(currentShapedRun, startX, firstTextSourceIndex, characterIndex, currentPosition, remainingLength);
rightToLeftRunBounds.Insert(0, currentRunBounds);
remainingLength -= currentRunBounds.Length;
startX = currentRunBounds.Rectangle.Left;
currentPosition += currentRunBounds.Length;
} }
combinedWidth = endX - startX;
currentRect = new Rect(startX, 0, combinedWidth, Height);
currentDirection = FlowDirection.RightToLeft; currentDirection = FlowDirection.RightToLeft;
if (!MathUtilities.IsZero(combinedWidth))
{
result.Add(new TextBounds(currentRect, currentDirection, rightToLeftRunBounds));
}
startX = endX;
} }
else else
{ {
if (currentShapedRun != null) if (currentShapedRun != null)
{ {
if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex)
{
startX += currentRun.Size.Width;
currentPosition += currentRun.TextSourceLength;
continue;
}
var offset = Math.Max(0, firstTextSourceIndex - currentPosition); var offset = Math.Max(0, firstTextSourceIndex - currentPosition);
currentPosition += offset; currentPosition += offset;
@ -665,43 +703,46 @@ namespace Avalonia.Media.TextFormatting
characterLength = NewLineLength; characterLength = NewLineLength;
} }
runWidth = endX - startX; combinedWidth = endX - startX;
currentRunBounds = new TextRunBounds(new Rect(startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
currentRunBounds = new TextRunBounds(new Rect(startX, 0, combinedWidth, Height), currentPosition, characterLength, currentRun);
currentPosition += characterLength; currentPosition += characterLength;
remainingLength -= characterLength; remainingLength -= characterLength;
}
if (currentRunBounds != null && !MathUtilities.IsZero(runWidth) || NewLineLength > 0) startX = endX;
{
if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX)) if (currentRunBounds.TextRun != null && !MathUtilities.IsZero(combinedWidth) || NewLineLength > 0)
{ {
currentRect = currentRect.WithWidth(currentWidth + runWidth); if (result.Count > 0 && lastDirection == currentDirection && MathUtilities.AreClose(currentRect.Left, lastRunBounds.Rectangle.Right))
{
currentRect = currentRect.WithWidth(currentWidth + combinedWidth);
var textBounds = result[result.Count - 1]; var textBounds = result[result.Count - 1];
textBounds.Rectangle = currentRect; textBounds.Rectangle = currentRect;
textBounds.TextRunBounds.Add(currentRunBounds!); textBounds.TextRunBounds.Add(currentRunBounds);
} }
else else
{ {
currentRect = currentRunBounds!.Rectangle; currentRect = currentRunBounds.Rectangle;
result.Add(new TextBounds(currentRect, currentDirection, new List<TextRunBounds> { currentRunBounds })); result.Add(new TextBounds(currentRect, currentDirection, new List<TextRunBounds> { currentRunBounds }));
}
} }
lastRunBounds = currentRunBounds;
} }
currentWidth += runWidth; currentWidth += combinedWidth;
if (remainingLength <= 0 || currentPosition >= characterIndex) if (remainingLength <= 0 || currentPosition >= characterIndex)
{ {
break; break;
} }
startX = endX;
lastDirection = currentDirection; lastDirection = currentDirection;
} }
@ -856,105 +897,45 @@ namespace Avalonia.Media.TextFormatting
return result; return result;
} }
private bool TryGetTextRunBoundsRightToLeft(double startX, int firstTextSourceIndex, int characterIndex, int runIndex, ref int currentPosition, ref int remainingLength, out TextRunBounds? textRunBounds) private TextRunBounds GetRightToLeftTextRunBounds(ShapedTextCharacters currentRun, double endX, int firstTextSourceIndex, int characterIndex, int currentPosition, int remainingLength)
{ {
textRunBounds = null; var startX = endX;
for (var index = runIndex; index >= 0; index--) var offset = Math.Max(0, firstTextSourceIndex - currentPosition);
{
if (TextRuns[index] is not DrawableTextRun currentRun)
{
continue;
}
if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex) currentPosition += offset;
{
startX -= currentRun.Size.Width;
currentPosition += currentRun.TextSourceLength; var startIndex = currentRun.Text.Start + offset;
continue; double startOffset;
} double endOffset;
var characterLength = 0; endOffset = currentRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
var endX = startX;
if (currentRun is ShapedTextCharacters currentShapedRun)
{
var offset = Math.Max(0, firstTextSourceIndex - currentPosition);
currentPosition += offset;
var startIndex = currentRun.Text.Start + offset;
double startOffset;
double endOffset;
if (currentShapedRun.ShapedBuffer.IsLeftToRight)
{
if (currentPosition < startIndex)
{
startOffset = endOffset = 0;
}
else
{
endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
}
}
else
{
endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
}
startX -= currentRun.Size.Width - startOffset;
endX -= currentRun.Size.Width - endOffset;
var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
characterLength = Math.Abs(startHit.FirstCharacterIndex + startHit.TrailingLength - endHit.FirstCharacterIndex - endHit.TrailingLength); startOffset = currentRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
}
else
{
if (currentPosition + currentRun.TextSourceLength <= characterIndex)
{
endX -= currentRun.Size.Width;
}
if (currentPosition < firstTextSourceIndex) startX -= currentRun.Size.Width - startOffset;
{ endX -= currentRun.Size.Width - endOffset;
startX -= currentRun.Size.Width;
characterLength = currentRun.TextSourceLength; var endHit = currentRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
} var startHit = currentRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
}
if (endX < startX) var characterLength = Math.Abs(startHit.FirstCharacterIndex + startHit.TrailingLength - endHit.FirstCharacterIndex - endHit.TrailingLength);
{
(endX, startX) = (startX, endX);
}
//Lines that only contain a linebreak need to be covered here if (endX < startX)
if (characterLength == 0) {
{ (endX, startX) = (startX, endX);
characterLength = NewLineLength; }
}
var runWidth = endX - startX;
remainingLength -= characterLength;
currentPosition += characterLength;
textRunBounds = new TextRunBounds(new Rect(Start + startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
return true; //Lines that only contain a linebreak need to be covered here
if (characterLength == 0)
{
characterLength = NewLineLength;
} }
return false; var runWidth = endX - startX;
return new TextRunBounds(new Rect(Start + startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
} }
public override IReadOnlyList<TextBounds> GetTextBounds(int firstTextSourceIndex, int textLength) public override IReadOnlyList<TextBounds> GetTextBounds(int firstTextSourceIndex, int textLength)
@ -1536,7 +1517,7 @@ namespace Avalonia.Media.TextFormatting
var textAlignment = _paragraphProperties.TextAlignment; var textAlignment = _paragraphProperties.TextAlignment;
var paragraphFlowDirection = _paragraphProperties.FlowDirection; var paragraphFlowDirection = _paragraphProperties.FlowDirection;
if(textAlignment == TextAlignment.Justify) if (textAlignment == TextAlignment.Justify)
{ {
textAlignment = TextAlignment.Start; textAlignment = TextAlignment.Start;
} }

2
src/Avalonia.Base/Media/TextFormatting/TextRunBounds.cs

@ -3,7 +3,7 @@
/// <summary> /// <summary>
/// The bounding rectangle of text run /// The bounding rectangle of text run
/// </summary> /// </summary>
public sealed class TextRunBounds public readonly struct TextRunBounds
{ {
/// <summary> /// <summary>
/// Constructing TextRunBounds /// Constructing TextRunBounds

14
src/Avalonia.Controls/Documents/InlineCollection.cs

@ -111,7 +111,7 @@ namespace Avalonia.Controls.Documents
private void AddText(string text) private void AddText(string text)
{ {
if(Parent is RichTextBlock textBlock && !textBlock.HasComplexContent) if (Parent is RichTextBlock textBlock && !textBlock.HasComplexContent)
{ {
textBlock._text += text; textBlock._text += text;
} }
@ -156,7 +156,17 @@ namespace Avalonia.Controls.Documents
{ {
foreach (var child in this) foreach (var child in this)
{ {
((ISetLogicalParent)child).SetParent(parent); var oldParent = child.Parent;
if (oldParent != parent)
{
if (oldParent != null)
{
((ISetLogicalParent)child).SetParent(null);
}
((ISetLogicalParent)child).SetParent(parent);
}
} }
} }

83
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

@ -597,21 +597,82 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
textBounds = textLine.GetTextBounds(0, 20); textBounds = textLine.GetTextBounds(0, 20);
Assert.Equal(1, textBounds.Count); Assert.Equal(2, textBounds.Count);
Assert.Equal(144.0234375, textBounds[0].Rectangle.Width); Assert.Equal(144.0234375, textBounds.Sum(x => x.Rectangle.Width));
textBounds = textLine.GetTextBounds(0, 30); textBounds = textLine.GetTextBounds(0, 30);
Assert.Equal(1, textBounds.Count); Assert.Equal(3, textBounds.Count);
Assert.Equal(216.03515625, textBounds[0].Rectangle.Width); Assert.Equal(216.03515625, textBounds.Sum(x => x.Rectangle.Width));
textBounds = textLine.GetTextBounds(0, 40); textBounds = textLine.GetTextBounds(0, 40);
Assert.Equal(1, textBounds.Count); Assert.Equal(4, textBounds.Count);
Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds.Sum(x => x.Rectangle.Width));
}
}
[Fact]
public void Should_GetTextRange()
{
var text = "שדגככעיחדגכAישדגשדגחייטYDASYWIWחיחלדשSAטויליHUHIUHUIDWKLאא'ק'קחליק/'וקןגגגלךשף'/קפוכדגכשדגשיח'/קטאגשד";
using (Start())
{
var defaultProperties = new GenericTextRunProperties(Typeface.Default);
var textSource = new SingleBufferTextSource(text, defaultProperties);
var formatter = new TextFormatterImpl();
var textLine =
formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties));
var textRuns = textLine.TextRuns.Cast<ShapedTextCharacters>().ToList();
Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds[0].Rectangle.Width); var lineWidth = textLine.WidthIncludingTrailingWhitespace;
var textBounds = textLine.GetTextBounds(0, text.Length);
TextBounds lastBounds = null;
var runBounds = textBounds.SelectMany(x => x.TextRunBounds).ToList();
Assert.Equal(textRuns.Count, runBounds.Count);
for (var i = 0; i < textRuns.Count; i++)
{
var run = textRuns[i];
var bounds = runBounds[i];
Assert.Equal(run.Text.Start, bounds.TextSourceCharacterIndex);
Assert.Equal(run, bounds.TextRun);
Assert.Equal(run.Size.Width, bounds.Rectangle.Width);
}
for (var i = 0; i < textBounds.Count; i++)
{
var currentBounds = textBounds[i];
if (lastBounds != null)
{
Assert.Equal(lastBounds.Rectangle.Right, currentBounds.Rectangle.Left);
}
var sumOfRunWidth = currentBounds.TextRunBounds.Sum(x => x.Rectangle.Width);
Assert.Equal(sumOfRunWidth, currentBounds.Rectangle.Width);
lastBounds = currentBounds;
}
var sumOfBoundsWidth = textBounds.Sum(x => x.Rectangle.Width);
Assert.Equal(lineWidth, sumOfBoundsWidth);
} }
} }
@ -779,7 +840,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textBounds = textLine.GetTextBounds(0, text.Length * 3 + 3); var textBounds = textLine.GetTextBounds(0, text.Length * 3 + 3);
Assert.Equal(1, textBounds.Count); Assert.Equal(6, textBounds.Count);
Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds.Sum(x => x.Rectangle.Width)); Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds.Sum(x => x.Rectangle.Width));
textBounds = textLine.GetTextBounds(0, 1); textBounds = textLine.GetTextBounds(0, 1);
@ -789,8 +850,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
textBounds = textLine.GetTextBounds(0, firstRun.Text.Length + 1); textBounds = textLine.GetTextBounds(0, firstRun.Text.Length + 1);
Assert.Equal(1, textBounds.Count); Assert.Equal(2, textBounds.Count);
Assert.Equal(firstRun.Size.Width + 14, textBounds[0].Rectangle.Width); Assert.Equal(firstRun.Size.Width + 14, textBounds.Sum(x => x.Rectangle.Width));
textBounds = textLine.GetTextBounds(1, firstRun.Text.Length); textBounds = textLine.GetTextBounds(1, firstRun.Text.Length);
@ -799,8 +860,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
textBounds = textLine.GetTextBounds(1, firstRun.Text.Length + 1); textBounds = textLine.GetTextBounds(1, firstRun.Text.Length + 1);
Assert.Equal(1, textBounds.Count); Assert.Equal(2, textBounds.Count);
Assert.Equal(firstRun.Size.Width + 14, textBounds[0].Rectangle.Width); Assert.Equal(firstRun.Size.Width + 14, textBounds.Sum(x => x.Rectangle.Width));
} }
} }

Loading…
Cancel
Save