diff --git a/src/Avalonia.Base/Media/TextFormatting/TextCollapsingProperties.cs b/src/Avalonia.Base/Media/TextFormatting/TextCollapsingProperties.cs
index f677617b14..01804e1ce3 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextCollapsingProperties.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextCollapsingProperties.cs
@@ -21,6 +21,6 @@ namespace Avalonia.Media.TextFormatting
/// Collapses given text line.
///
/// Text line to collapse.
- public abstract List? Collapse(TextLine textLine);
+ public abstract List? Collapse(TextLine textLine);
}
}
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs b/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs
index a1b8985b43..574f92568c 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs
@@ -5,9 +5,11 @@ namespace Avalonia.Media.TextFormatting
{
internal static class TextEllipsisHelper
{
- public static List? Collapse(TextLine textLine, TextCollapsingProperties properties, bool isWordEllipsis)
+ public static List? Collapse(TextLine textLine, TextCollapsingProperties properties, bool isWordEllipsis)
{
- if (textLine.TextRuns is not List textRuns || textRuns.Count == 0)
+ var textRuns = textLine.TextRuns;
+
+ if (textRuns == null || textRuns.Count == 0)
{
return null;
}
@@ -20,7 +22,7 @@ namespace Avalonia.Media.TextFormatting
if (properties.Width < shapedSymbol.GlyphRun.Size.Width)
{
//Not enough space to fit in the symbol
- return new List(0);
+ return new List(0);
}
var availableWidth = properties.Width - shapedSymbol.Size.Width;
@@ -70,11 +72,11 @@ namespace Avalonia.Media.TextFormatting
collapsedLength += measuredLength;
- var collapsedRuns = new List(textRuns.Count);
+ var collapsedRuns = new List(textRuns.Count);
if (collapsedLength > 0)
{
- var splitResult = TextFormatterImpl.SplitDrawableRuns(textRuns, collapsedLength);
+ var splitResult = TextFormatterImpl.SplitTextRuns(textRuns, collapsedLength);
collapsedRuns.AddRange(splitResult.First);
}
@@ -84,22 +86,21 @@ namespace Avalonia.Media.TextFormatting
return collapsedRuns;
}
- availableWidth -= currentRun.Size.Width;
-
+ availableWidth -= shapedRun.Size.Width;
break;
}
- case { } drawableRun:
+ case DrawableTextRun drawableRun:
{
//The whole run needs to fit into available space
if (currentWidth + drawableRun.Size.Width > availableWidth)
{
- var collapsedRuns = new List(textRuns.Count);
+ var collapsedRuns = new List(textRuns.Count);
if (collapsedLength > 0)
{
- var splitResult = TextFormatterImpl.SplitDrawableRuns(textRuns, collapsedLength);
+ var splitResult = TextFormatterImpl.SplitTextRuns(textRuns, collapsedLength);
collapsedRuns.AddRange(splitResult.First);
}
@@ -109,6 +110,8 @@ namespace Avalonia.Media.TextFormatting
return collapsedRuns;
}
+ availableWidth -= drawableRun.Size.Width;
+
break;
}
}
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
index 93eb4811b9..07aa5e1550 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Utilities;
@@ -17,20 +16,20 @@ namespace Avalonia.Media.TextFormatting
var textWrapping = paragraphProperties.TextWrapping;
FlowDirection resolvedFlowDirection;
TextLineBreak? nextLineBreak = null;
- List drawableTextRuns;
+ IReadOnlyList textRuns;
- var textRuns = FetchTextRuns(textSource, firstTextSourceIndex,
+ var fetchedRuns = FetchTextRuns(textSource, firstTextSourceIndex,
out var textEndOfLine, out var textSourceLength);
if (previousLineBreak?.RemainingRuns != null)
{
resolvedFlowDirection = previousLineBreak.FlowDirection;
- drawableTextRuns = previousLineBreak.RemainingRuns.ToList();
+ textRuns = previousLineBreak.RemainingRuns;
nextLineBreak = previousLineBreak;
}
else
{
- drawableTextRuns = ShapeTextRuns(textRuns, paragraphProperties, out resolvedFlowDirection);
+ textRuns = ShapeTextRuns(fetchedRuns, paragraphProperties, out resolvedFlowDirection);
if (nextLineBreak == null && textEndOfLine != null)
{
@@ -44,7 +43,7 @@ namespace Avalonia.Media.TextFormatting
{
case TextWrapping.NoWrap:
{
- textLine = new TextLineImpl(drawableTextRuns, firstTextSourceIndex, textSourceLength,
+ textLine = new TextLineImpl(textRuns, firstTextSourceIndex, textSourceLength,
paragraphWidth, paragraphProperties, resolvedFlowDirection, nextLineBreak);
textLine.FinalizeLine();
@@ -54,7 +53,7 @@ namespace Avalonia.Media.TextFormatting
case TextWrapping.WrapWithOverflow:
case TextWrapping.Wrap:
{
- textLine = PerformTextWrapping(drawableTextRuns, firstTextSourceIndex, paragraphWidth, paragraphProperties,
+ textLine = PerformTextWrapping(textRuns, firstTextSourceIndex, paragraphWidth, paragraphProperties,
resolvedFlowDirection, nextLineBreak);
break;
}
@@ -71,7 +70,7 @@ namespace Avalonia.Media.TextFormatting
/// The text run's.
/// The length to split at.
/// The split text runs.
- internal static SplitResult> SplitDrawableRuns(List textRuns, int length)
+ internal static SplitResult> SplitTextRuns(IReadOnlyList textRuns, int length)
{
var currentLength = 0;
@@ -88,7 +87,7 @@ namespace Avalonia.Media.TextFormatting
var firstCount = currentRun.Length >= 1 ? i + 1 : i;
- var first = new List(firstCount);
+ var first = new List(firstCount);
if (firstCount > 1)
{
@@ -102,7 +101,7 @@ namespace Avalonia.Media.TextFormatting
if (currentLength + currentRun.Length == length)
{
- var second = secondCount > 0 ? new List(secondCount) : null;
+ var second = secondCount > 0 ? new List(secondCount) : null;
if (second != null)
{
@@ -116,13 +115,13 @@ namespace Avalonia.Media.TextFormatting
first.Add(currentRun);
- return new SplitResult>(first, second);
+ return new SplitResult>(first, second);
}
else
{
secondCount++;
- var second = new List(secondCount);
+ var second = new List(secondCount);
if (currentRun is ShapedTextCharacters shapedTextCharacters)
{
@@ -138,11 +137,11 @@ namespace Avalonia.Media.TextFormatting
second.Add(textRuns[i + j]);
}
- return new SplitResult>(first, second);
+ return new SplitResult>(first, second);
}
}
- return new SplitResult>(textRuns, null);
+ return new SplitResult>(textRuns, null);
}
///
@@ -154,11 +153,11 @@ namespace Avalonia.Media.TextFormatting
///
/// A list of shaped text characters.
///
- private static List ShapeTextRuns(List textRuns, TextParagraphProperties paragraphProperties,
+ private static List ShapeTextRuns(List textRuns, TextParagraphProperties paragraphProperties,
out FlowDirection resolvedFlowDirection)
{
var flowDirection = paragraphProperties.FlowDirection;
- var drawableTextRuns = new List();
+ var shapedRuns = new List();
var biDiData = new BidiData((sbyte)flowDirection);
foreach (var textRun in textRuns)
@@ -199,13 +198,6 @@ namespace Avalonia.Media.TextFormatting
switch (currentRun)
{
- case DrawableTextRun drawableRun:
- {
- drawableTextRuns.Add(drawableRun);
-
- break;
- }
-
case ShapeableTextCharacters shapeableRun:
{
var groupedRuns = new List(2) { shapeableRun };
@@ -248,14 +240,20 @@ namespace Avalonia.Media.TextFormatting
shapeableRun.BidiLevel, currentRun.Properties.CultureInfo,
paragraphProperties.DefaultIncrementalTab, paragraphProperties.LetterSpacing);
- drawableTextRuns.AddRange(ShapeTogether(groupedRuns, characterBufferReference, length, shaperOptions));
+ shapedRuns.AddRange(ShapeTogether(groupedRuns, characterBufferReference, length, shaperOptions));
+
+ break;
+ }
+ default:
+ {
+ shapedRuns.Add(currentRun);
break;
}
}
}
- return drawableTextRuns;
+ return shapedRuns;
}
private static IReadOnlyList ShapeTogether(
@@ -390,6 +388,10 @@ namespace Avalonia.Media.TextFormatting
if (textRun == null)
{
+ textRuns.Add(new TextEndOfParagraph());
+
+ textSourceLength += TextRun.DefaultTextSourceLength;
+
break;
}
@@ -465,7 +467,7 @@ namespace Avalonia.Media.TextFormatting
return false;
}
- private static bool TryMeasureLength(IReadOnlyList textRuns, double paragraphWidth, out int measuredLength)
+ private static bool TryMeasureLength(IReadOnlyList textRuns, double paragraphWidth, out int measuredLength)
{
measuredLength = 0;
var currentWidth = 0.0;
@@ -502,7 +504,7 @@ namespace Avalonia.Media.TextFormatting
break;
}
- case { } drawableTextRun:
+ case DrawableTextRun drawableTextRun:
{
if (currentWidth + drawableTextRun.Size.Width >= paragraphWidth)
{
@@ -510,7 +512,13 @@ namespace Avalonia.Media.TextFormatting
}
measuredLength += currentRun.Length;
- currentWidth += currentRun.Size.Width;
+ currentWidth += drawableTextRun.Size.Width;
+
+ break;
+ }
+ default:
+ {
+ measuredLength += currentRun.Length;
break;
}
@@ -553,7 +561,7 @@ namespace Avalonia.Media.TextFormatting
///
/// The current line break if the line was explicitly broken.
/// The wrapped text line.
- private static TextLineImpl PerformTextWrapping(List textRuns, int firstTextSourceIndex,
+ private static TextLineImpl PerformTextWrapping(IReadOnlyList textRuns, int firstTextSourceIndex,
double paragraphWidth, TextParagraphProperties paragraphProperties, FlowDirection resolvedFlowDirection,
TextLineBreak? currentLineBreak)
{
@@ -681,7 +689,7 @@ namespace Avalonia.Media.TextFormatting
break;
}
- var splitResult = SplitDrawableRuns(textRuns, measuredLength);
+ var splitResult = SplitTextRuns(textRuns, measuredLength);
var remainingCharacters = splitResult.Second;
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
index f803001481..ef0c726793 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
@@ -448,7 +448,7 @@ namespace Avalonia.Media.TextFormatting
var textLine = TextFormatter.Current.FormatLine(_textSource, _textSourceLength, MaxWidth,
_paragraphProperties, previousLine?.TextLineBreak);
- if (textLine == null || textLine.Length == 0 || textLine.TextRuns.Count == 0 && textLine.TextLineBreak?.TextEndOfLine is TextEndOfParagraph)
+ if(textLine == null || textLine.Length == 0)
{
if (previousLine != null && previousLine.NewLineLength > 0)
{
@@ -501,6 +501,11 @@ namespace Avalonia.Media.TextFormatting
break;
}
+
+ if (textLine.TextLineBreak?.TextEndOfLine is TextEndOfParagraph)
+ {
+ break;
+ }
}
//Make sure the TextLayout always contains at least on empty line
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs b/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs
index 2752af8f0c..9c085eba17 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs
@@ -39,9 +39,11 @@ namespace Avalonia.Media.TextFormatting
///
public override TextRun Symbol { get; }
- public override List? Collapse(TextLine textLine)
+ public override List? Collapse(TextLine textLine)
{
- if (textLine.TextRuns is not List textRuns || textRuns.Count == 0)
+ var textRuns = textLine.TextRuns;
+
+ if (textRuns == null || textRuns.Count == 0)
{
return null;
}
@@ -52,7 +54,7 @@ namespace Avalonia.Media.TextFormatting
if (Width < shapedSymbol.GlyphRun.Size.Width)
{
- return new List(0);
+ return new List(0);
}
// Overview of ellipsis structure
@@ -66,92 +68,101 @@ namespace Avalonia.Media.TextFormatting
switch (currentRun)
{
case ShapedTextCharacters shapedRun:
- {
- currentWidth += currentRun.Size.Width;
-
- if (currentWidth > availableWidth)
{
- shapedRun.TryMeasureCharacters(availableWidth, out var measuredLength);
-
- var collapsedRuns = new List(textRuns.Count);
+ currentWidth += shapedRun.Size.Width;
- if (measuredLength > 0)
+ if (currentWidth > availableWidth)
{
- List? preSplitRuns = null;
- List? postSplitRuns;
-
- if (_prefixLength > 0)
- {
- var splitResult = TextFormatterImpl.SplitDrawableRuns(textRuns,
- Math.Min(_prefixLength, measuredLength));
+ shapedRun.TryMeasureCharacters(availableWidth, out var measuredLength);
- collapsedRuns.AddRange(splitResult.First);
+ var collapsedRuns = new List(textRuns.Count);
- preSplitRuns = splitResult.First;
- postSplitRuns = splitResult.Second;
- }
- else
+ if (measuredLength > 0)
{
- postSplitRuns = textRuns;
- }
+ IReadOnlyList? preSplitRuns = null;
+ IReadOnlyList? postSplitRuns;
- collapsedRuns.Add(shapedSymbol);
+ if (_prefixLength > 0)
+ {
+ var splitResult = TextFormatterImpl.SplitTextRuns(textRuns,
+ Math.Min(_prefixLength, measuredLength));
- if (measuredLength <= _prefixLength || postSplitRuns is null)
- {
- return collapsedRuns;
- }
+ collapsedRuns.AddRange(splitResult.First);
- var availableSuffixWidth = availableWidth;
+ preSplitRuns = splitResult.First;
+ postSplitRuns = splitResult.Second;
+ }
+ else
+ {
+ postSplitRuns = textRuns;
+ }
- if (preSplitRuns is not null)
- {
- foreach (var run in preSplitRuns)
+ collapsedRuns.Add(shapedSymbol);
+
+ if (measuredLength <= _prefixLength || postSplitRuns is null)
{
- availableSuffixWidth -= run.Size.Width;
+ return collapsedRuns;
}
- }
- for (var i = postSplitRuns.Count - 1; i >= 0; i--)
- {
- var run = postSplitRuns[i];
+ var availableSuffixWidth = availableWidth;
- switch (run)
+ if (preSplitRuns is not null)
{
- case ShapedTextCharacters endShapedRun:
+ foreach (var run in preSplitRuns)
{
- if (endShapedRun.TryMeasureCharactersBackwards(availableSuffixWidth,
- out var suffixCount, out var suffixWidth))
+ if (run is DrawableTextRun drawableTextRun)
{
- availableSuffixWidth -= suffixWidth;
+ availableSuffixWidth -= drawableTextRun.Size.Width;
+ }
+ }
+ }
- if (suffixCount > 0)
+ for (var i = postSplitRuns.Count - 1; i >= 0; i--)
+ {
+ var run = postSplitRuns[i];
+
+ switch (run)
+ {
+ case ShapedTextCharacters endShapedRun:
{
- var splitSuffix =
- endShapedRun.Split(run.Length - suffixCount);
+ if (endShapedRun.TryMeasureCharactersBackwards(availableSuffixWidth,
+ out var suffixCount, out var suffixWidth))
+ {
+ availableSuffixWidth -= suffixWidth;
- collapsedRuns.Add(splitSuffix.Second!);
- }
- }
+ if (suffixCount > 0)
+ {
+ var splitSuffix =
+ endShapedRun.Split(run.Length - suffixCount);
+
+ collapsedRuns.Add(splitSuffix.Second!);
+ }
+ }
- break;
+ break;
+ }
}
}
}
- }
- else
- {
- collapsedRuns.Add(shapedSymbol);
+ else
+ {
+ collapsedRuns.Add(shapedSymbol);
+ }
+
+ return collapsedRuns;
}
- return collapsedRuns;
- }
+ availableWidth -= shapedRun.Size.Width;
- break;
- }
- }
+ break;
+ }
+ case DrawableTextRun drawableTextRun:
+ {
+ availableWidth -= drawableTextRun.Size.Width;
- availableWidth -= currentRun.Size.Width;
+ break;
+ }
+ }
runIndex++;
}
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineBreak.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineBreak.cs
index ce35e47fbd..bf26ac5df4 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextLineBreak.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextLineBreak.cs
@@ -5,7 +5,7 @@ namespace Avalonia.Media.TextFormatting
public class TextLineBreak
{
public TextLineBreak(TextEndOfLine? textEndOfLine = null, FlowDirection flowDirection = FlowDirection.LeftToRight,
- IReadOnlyList? remainingRuns = null)
+ IReadOnlyList? remainingRuns = null)
{
TextEndOfLine = textEndOfLine;
FlowDirection = flowDirection;
@@ -25,6 +25,6 @@ namespace Avalonia.Media.TextFormatting
///
/// Get the remaining runs that were split up by the during the formatting process.
///
- public IReadOnlyList? RemainingRuns { get; }
+ public IReadOnlyList? RemainingRuns { get; }
}
}
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
index 3241dfd12b..48f5fec2c0 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
@@ -6,13 +6,13 @@ namespace Avalonia.Media.TextFormatting
{
internal class TextLineImpl : TextLine
{
- private readonly List _textRuns;
+ private IReadOnlyList _textRuns;
private readonly double _paragraphWidth;
private readonly TextParagraphProperties _paragraphProperties;
private TextLineMetrics _textLineMetrics;
private readonly FlowDirection _resolvedFlowDirection;
- public TextLineImpl(List textRuns, int firstTextSourceIndex, int length, double paragraphWidth,
+ public TextLineImpl(IReadOnlyList textRuns, int firstTextSourceIndex, int length, double paragraphWidth,
TextParagraphProperties paragraphProperties, FlowDirection resolvedFlowDirection = FlowDirection.LeftToRight,
TextLineBreak? lineBreak = null, bool hasCollapsed = false)
{
@@ -86,11 +86,14 @@ namespace Avalonia.Media.TextFormatting
foreach (var textRun in _textRuns)
{
- var offsetY = GetBaselineOffset(this, textRun);
+ if (textRun is DrawableTextRun drawable)
+ {
+ var offsetY = GetBaselineOffset(this, drawable);
- textRun.Draw(drawingContext, new Point(currentX, currentY + offsetY));
+ drawable.Draw(drawingContext, new Point(currentX, currentY + offsetY));
- currentX += textRun.Size.Width;
+ currentX += drawable.Size.Width;
+ }
}
}
@@ -180,7 +183,14 @@ namespace Avalonia.Media.TextFormatting
{
var lastRun = _textRuns[_textRuns.Count - 1];
- return GetRunCharacterHit(lastRun, FirstTextSourceIndex + Length - lastRun.Length, lastRun.Size.Width);
+ var size = 0.0;
+
+ if (lastRun is DrawableTextRun drawableTextRun)
+ {
+ size = drawableTextRun.Size.Width;
+ }
+
+ return GetRunCharacterHit(lastRun, FirstTextSourceIndex + Length - lastRun.Length, size);
}
// process hit that happens within the line
@@ -220,9 +230,16 @@ namespace Avalonia.Media.TextFormatting
currentRun = _textRuns[j];
- if (currentDistance + currentRun.Size.Width <= distance)
+ if(currentRun is not ShapedTextCharacters)
+ {
+ continue;
+ }
+
+ shapedRun = (ShapedTextCharacters)currentRun;
+
+ if (currentDistance + shapedRun.Size.Width <= distance)
{
- currentDistance += currentRun.Size.Width;
+ currentDistance += shapedRun.Size.Width;
currentPosition -= currentRun.Length;
continue;
@@ -234,12 +251,19 @@ namespace Avalonia.Media.TextFormatting
characterHit = GetRunCharacterHit(currentRun, currentPosition, distance - currentDistance);
- if (i < _textRuns.Count - 1 && currentDistance + currentRun.Size.Width < distance)
+ if (currentRun is DrawableTextRun drawableTextRun)
{
- currentDistance += currentRun.Size.Width;
+ if (i < _textRuns.Count - 1 && currentDistance + drawableTextRun.Size.Width < distance)
+ {
+ currentDistance += drawableTextRun.Size.Width;
- currentPosition += currentRun.Length;
+ currentPosition += currentRun.Length;
+ continue;
+ }
+ }
+ else
+ {
continue;
}
@@ -249,7 +273,7 @@ namespace Avalonia.Media.TextFormatting
return characterHit;
}
- private static CharacterHit GetRunCharacterHit(DrawableTextRun run, int currentPosition, double distance)
+ private static CharacterHit GetRunCharacterHit(TextRun run, int currentPosition, double distance)
{
CharacterHit characterHit;
@@ -270,9 +294,9 @@ namespace Avalonia.Media.TextFormatting
break;
}
- default:
+ case DrawableTextRun drawableTextRun:
{
- if (distance < run.Size.Width / 2)
+ if (distance < drawableTextRun.Size.Width / 2)
{
characterHit = new CharacterHit(currentPosition);
}
@@ -282,6 +306,10 @@ namespace Avalonia.Media.TextFormatting
}
break;
}
+ default:
+ characterHit = new CharacterHit(currentPosition, run.Length);
+
+ break;
}
return characterHit;
@@ -307,7 +335,7 @@ namespace Avalonia.Media.TextFormatting
{
var i = index;
- var rightToLeftWidth = currentRun.Size.Width;
+ var rightToLeftWidth = shapedRun.Size.Width;
while (i + 1 <= _textRuns.Count - 1)
{
@@ -317,7 +345,7 @@ namespace Avalonia.Media.TextFormatting
{
i++;
- rightToLeftWidth += nextRun.Size.Width;
+ rightToLeftWidth += nextShapedRun.Size.Width;
continue;
}
@@ -331,7 +359,10 @@ namespace Avalonia.Media.TextFormatting
{
currentRun = _textRuns[i];
- rightToLeftWidth -= currentRun.Size.Width;
+ if (currentRun is DrawableTextRun drawable)
+ {
+ rightToLeftWidth -= drawable.Size.Width;
+ }
if (currentPosition + currentRun.Length >= characterIndex)
{
@@ -355,8 +386,13 @@ namespace Avalonia.Media.TextFormatting
return Math.Max(0, currentDistance + distance);
}
+ if (currentRun is DrawableTextRun drawableTextRun)
+ {
+ currentDistance += drawableTextRun.Size.Width;
+ }
+
//No hit hit found so we add the full width
- currentDistance += currentRun.Size.Width;
+
currentPosition += currentRun.Length;
remainingLength -= currentRun.Length;
}
@@ -380,8 +416,12 @@ namespace Avalonia.Media.TextFormatting
return Math.Max(0, currentDistance - distance);
}
+ if (currentRun is DrawableTextRun drawableTextRun)
+ {
+ currentDistance -= drawableTextRun.Size.Width;
+ }
+
//No hit hit found so we add the full width
- currentDistance -= currentRun.Size.Width;
currentPosition += currentRun.Length;
remainingLength -= currentRun.Length;
}
@@ -391,7 +431,7 @@ namespace Avalonia.Media.TextFormatting
}
private static bool TryGetDistanceFromCharacterHit(
- DrawableTextRun currentRun,
+ TextRun currentRun,
CharacterHit characterHit,
int currentPosition,
int remainingLength,
@@ -432,7 +472,7 @@ namespace Avalonia.Media.TextFormatting
break;
}
- default:
+ case DrawableTextRun drawableTextRun:
{
if (characterIndex == currentPosition)
{
@@ -441,7 +481,7 @@ namespace Avalonia.Media.TextFormatting
if (characterIndex == currentPosition + currentRun.Length)
{
- distance = currentRun.Size.Width;
+ distance = drawableTextRun.Size.Width;
return true;
@@ -449,6 +489,10 @@ namespace Avalonia.Media.TextFormatting
break;
}
+ default:
+ {
+ return false;
+ }
}
return false;
@@ -943,7 +987,7 @@ namespace Avalonia.Media.TextFormatting
return this;
}
- private static sbyte GetRunBidiLevel(DrawableTextRun run, FlowDirection flowDirection)
+ private static sbyte GetRunBidiLevel(TextRun run, FlowDirection flowDirection)
{
if (run is ShapedTextCharacters shapedTextCharacters)
{
@@ -1039,16 +1083,18 @@ namespace Avalonia.Media.TextFormatting
minLevelToReverse--;
}
- _textRuns.Clear();
+ var textRuns = new List(_textRuns.Count);
current = orderedRun;
while (current != null)
{
- _textRuns.Add(current.Run);
+ textRuns.Add(current.Run);
current = current.Next;
}
+
+ _textRuns = textRuns;
}
///
@@ -1286,7 +1332,7 @@ namespace Avalonia.Media.TextFormatting
{
var runIndex = 0;
textPosition = FirstTextSourceIndex;
- DrawableTextRun? previousRun = null;
+ TextRun? previousRun = null;
while (runIndex < _textRuns.Count)
{
@@ -1346,7 +1392,6 @@ namespace Avalonia.Media.TextFormatting
break;
}
-
default:
{
if (codepointIndex == textPosition)
@@ -1363,6 +1408,7 @@ namespace Avalonia.Media.TextFormatting
break;
}
+
}
runIndex++;
@@ -1436,7 +1482,7 @@ namespace Avalonia.Media.TextFormatting
break;
}
- case { } drawableTextRun:
+ case DrawableTextRun drawableTextRun:
{
widthIncludingWhitespace += drawableTextRun.Size.Width;
@@ -1558,7 +1604,7 @@ namespace Avalonia.Media.TextFormatting
private sealed class OrderedBidiRun
{
- public OrderedBidiRun(DrawableTextRun run, sbyte level)
+ public OrderedBidiRun(TextRun run, sbyte level)
{
Run = run;
Level = level;
@@ -1566,7 +1612,7 @@ namespace Avalonia.Media.TextFormatting
public sbyte Level { get; }
- public DrawableTextRun Run { get; }
+ public TextRun Run { get; }
public OrderedBidiRun? Next { get; set; }
}
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextRun.cs b/src/Avalonia.Base/Media/TextFormatting/TextRun.cs
index 0306054767..b55a0d627b 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextRun.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextRun.cs
@@ -40,7 +40,7 @@ namespace Avalonia.Media.TextFormatting
{
unsafe
{
- var characterBuffer = _textRun.CharacterBufferReference.CharacterBuffer;
+ var characterBuffer = new CharacterBufferRange(_textRun.CharacterBufferReference, _textRun.Length);
fixed (char* charsPtr = characterBuffer.Span)
{
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextTrailingCharacterEllipsis.cs b/src/Avalonia.Base/Media/TextFormatting/TextTrailingCharacterEllipsis.cs
index 1de04ad061..deecbbe476 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextTrailingCharacterEllipsis.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextTrailingCharacterEllipsis.cs
@@ -26,7 +26,7 @@ namespace Avalonia.Media.TextFormatting
///
public override TextRun Symbol { get; }
- public override List? Collapse(TextLine textLine)
+ public override List? Collapse(TextLine textLine)
{
return TextEllipsisHelper.Collapse(textLine, this, false);
}
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextTrailingWordEllipsis.cs b/src/Avalonia.Base/Media/TextFormatting/TextTrailingWordEllipsis.cs
index 7c94715aa4..c291e1dfb9 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextTrailingWordEllipsis.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextTrailingWordEllipsis.cs
@@ -31,7 +31,7 @@ namespace Avalonia.Media.TextFormatting
///
public override TextRun Symbol { get; }
- public override List? Collapse(TextLine textLine)
+ public override List? Collapse(TextLine textLine)
{
return TextEllipsisHelper.Collapse(textLine, this, true);
}
diff --git a/src/Avalonia.Controls/Documents/Run.cs b/src/Avalonia.Controls/Documents/Run.cs
index 5d7b8998e6..f64cf79127 100644
--- a/src/Avalonia.Controls/Documents/Run.cs
+++ b/src/Avalonia.Controls/Documents/Run.cs
@@ -54,6 +54,11 @@ namespace Avalonia.Controls.Documents
{
var text = Text ?? "";
+ if (string.IsNullOrEmpty(text))
+ {
+ return;
+ }
+
var textRunProperties = CreateTextRunProperties();
var textCharacters = new TextCharacters(text, textRunProperties);
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/MultiBufferTextSource.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/MultiBufferTextSource.cs
index aa499bb135..81d7b1854b 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/MultiBufferTextSource.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/MultiBufferTextSource.cs
@@ -1,6 +1,4 @@
-using System;
-using Avalonia.Media.TextFormatting;
-using Avalonia.Utilities;
+using Avalonia.Media.TextFormatting;
namespace Avalonia.Skia.UnitTests.Media.TextFormatting
{
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
index 33d4fba5f1..643bf513d9 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
@@ -62,6 +62,69 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
+ private class TextSourceWithDummyRuns : ITextSource
+ {
+ private readonly TextRunProperties _properties;
+ private readonly List> _textRuns;
+
+ public TextSourceWithDummyRuns(TextRunProperties properties)
+ {
+ _properties = properties;
+
+ _textRuns = new List>
+ {
+ new ValueSpan(0, 5, new TextCharacters("Hello", _properties)),
+ new ValueSpan(5, 1, new DummyRun()),
+ new ValueSpan(6, 1, new DummyRun()),
+ new ValueSpan(7, 6, new TextCharacters(" World", _properties))
+ };
+ }
+
+ public TextRun GetTextRun(int textSourceIndex)
+ {
+ foreach (var run in _textRuns)
+ {
+ if (textSourceIndex < run.Start + run.Length)
+ {
+ return run.Value;
+ }
+ }
+
+ return new TextEndOfParagraph();
+ }
+
+ private class DummyRun : TextRun
+ {
+ public DummyRun()
+ {
+ Length = DefaultTextSourceLength;
+ }
+
+ public override int Length { get; }
+ }
+ }
+
+ [Fact]
+ public void Should_Format_TextLine_With_Non_Text_TextRuns()
+ {
+ using (Start())
+ {
+ var defaultProperties =
+ new GenericTextRunProperties(Typeface.Default, 12, foregroundBrush: Brushes.Black);
+
+ var textSource = new TextSourceWithDummyRuns(defaultProperties);
+
+ var formatter = new TextFormatterImpl();
+
+ var textLine = formatter.FormatLine(textSource, 0, double.PositiveInfinity,
+ new GenericTextParagraphProperties(defaultProperties));
+
+ Assert.Equal(5, textLine.TextRuns.Count);
+
+ Assert.Equal(14, textLine.Length);
+ }
+ }
+
[Fact]
public void Should_Format_TextRuns_With_TextRunStyles()
{
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
index d6257a0de8..f8e287d0a7 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
@@ -326,7 +326,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
- Assert.Equal(currentDistance, textLine.GetDistanceFromCharacterHit(new CharacterHit(s_multiLineText.Length)));
+ var actualDistance = textLine.GetDistanceFromCharacterHit(new CharacterHit(s_multiLineText.Length));
+
+ Assert.Equal(currentDistance, actualDistance);
}
}