|
|
@@ -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<DrawableTextRun> drawableTextRuns;
|
|
|
+ IReadOnlyList<TextRun> 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
|
|
|
/// <param name="textRuns">The text run's.</param>
|
|
|
/// <param name="length">The length to split at.</param>
|
|
|
/// <returns>The split text runs.</returns>
|
|
|
- internal static SplitResult<List<DrawableTextRun>> SplitDrawableRuns(List<DrawableTextRun> textRuns, int length)
|
|
|
+ internal static SplitResult<IReadOnlyList<TextRun>> SplitTextRuns(IReadOnlyList<TextRun> 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<DrawableTextRun>(firstCount);
|
|
|
+ var first = new List<TextRun>(firstCount);
|
|
|
|
|
|
if (firstCount > 1)
|
|
|
{
|
|
|
@@ -102,7 +101,7 @@ namespace Avalonia.Media.TextFormatting
|
|
|
|
|
|
if (currentLength + currentRun.Length == length)
|
|
|
{
|
|
|
- var second = secondCount > 0 ? new List<DrawableTextRun>(secondCount) : null;
|
|
|
+ var second = secondCount > 0 ? new List<TextRun>(secondCount) : null;
|
|
|
|
|
|
if (second != null)
|
|
|
{
|
|
|
@@ -116,13 +115,13 @@ namespace Avalonia.Media.TextFormatting
|
|
|
|
|
|
first.Add(currentRun);
|
|
|
|
|
|
- return new SplitResult<List<DrawableTextRun>>(first, second);
|
|
|
+ return new SplitResult<IReadOnlyList<TextRun>>(first, second);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
secondCount++;
|
|
|
|
|
|
- var second = new List<DrawableTextRun>(secondCount);
|
|
|
+ var second = new List<TextRun>(secondCount);
|
|
|
|
|
|
if (currentRun is ShapedTextRun shapedTextCharacters)
|
|
|
{
|
|
|
@@ -131,18 +130,18 @@ namespace Avalonia.Media.TextFormatting
|
|
|
first.Add(split.First);
|
|
|
|
|
|
second.Add(split.Second!);
|
|
|
- }
|
|
|
+ }
|
|
|
|
|
|
for (var j = 1; j < secondCount; j++)
|
|
|
{
|
|
|
second.Add(textRuns[i + j]);
|
|
|
}
|
|
|
|
|
|
- return new SplitResult<List<DrawableTextRun>>(first, second);
|
|
|
+ return new SplitResult<IReadOnlyList<TextRun>>(first, second);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- return new SplitResult<List<DrawableTextRun>>(textRuns, null);
|
|
|
+ return new SplitResult<IReadOnlyList<TextRun>>(textRuns, null);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -154,11 +153,11 @@ namespace Avalonia.Media.TextFormatting
|
|
|
/// <returns>
|
|
|
/// A list of shaped text characters.
|
|
|
/// </returns>
|
|
|
- private static List<DrawableTextRun> ShapeTextRuns(List<TextRun> textRuns, TextParagraphProperties paragraphProperties,
|
|
|
+ private static List<TextRun> ShapeTextRuns(List<TextRun> textRuns, TextParagraphProperties paragraphProperties,
|
|
|
out FlowDirection resolvedFlowDirection)
|
|
|
{
|
|
|
var flowDirection = paragraphProperties.FlowDirection;
|
|
|
- var drawableTextRuns = new List<DrawableTextRun>();
|
|
|
+ var shapedRuns = new List<TextRun>();
|
|
|
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 UnshapedTextRun shapeableRun:
|
|
|
{
|
|
|
var groupedRuns = new List<UnshapedTextRun>(2) { shapeableRun };
|
|
|
@@ -245,17 +237,23 @@ namespace Avalonia.Media.TextFormatting
|
|
|
|
|
|
var shaperOptions = new TextShaperOptions(currentRun.Properties!.Typeface.GlyphTypeface,
|
|
|
currentRun.Properties.FontRenderingEmSize,
|
|
|
- shapeableRun.BidiLevel, currentRun.Properties.CultureInfo,
|
|
|
+ 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<ShapedTextRun> 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<DrawableTextRun> textRuns, double paragraphWidth, out int measuredLength)
|
|
|
+ private static bool TryMeasureLength(IReadOnlyList<TextRun> textRuns, double paragraphWidth, out int measuredLength)
|
|
|
{
|
|
|
measuredLength = 0;
|
|
|
var currentWidth = 0.0;
|
|
|
@@ -476,7 +478,7 @@ namespace Avalonia.Media.TextFormatting
|
|
|
{
|
|
|
case ShapedTextRun shapedTextCharacters:
|
|
|
{
|
|
|
- if(shapedTextCharacters.ShapedBuffer.Length > 0)
|
|
|
+ if (shapedTextCharacters.ShapedBuffer.Length > 0)
|
|
|
{
|
|
|
var firstCluster = shapedTextCharacters.ShapedBuffer.GlyphInfos[0].GlyphCluster;
|
|
|
var lastCluster = firstCluster;
|
|
|
@@ -497,12 +499,12 @@ namespace Avalonia.Media.TextFormatting
|
|
|
}
|
|
|
|
|
|
measuredLength += currentRun.Length;
|
|
|
- }
|
|
|
+ }
|
|
|
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
- case { } drawableTextRun:
|
|
|
+ case DrawableTextRun drawableTextRun:
|
|
|
{
|
|
|
if (currentWidth + drawableTextRun.Size.Width >= paragraphWidth)
|
|
|
{
|
|
|
@@ -510,14 +512,20 @@ namespace Avalonia.Media.TextFormatting
|
|
|
}
|
|
|
|
|
|
measuredLength += currentRun.Length;
|
|
|
- currentWidth += currentRun.Size.Width;
|
|
|
+ currentWidth += drawableTextRun.Size.Width;
|
|
|
+
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ {
|
|
|
+ measuredLength += currentRun.Length;
|
|
|
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- found:
|
|
|
+ found:
|
|
|
|
|
|
return measuredLength != 0;
|
|
|
}
|
|
|
@@ -553,13 +561,13 @@ namespace Avalonia.Media.TextFormatting
|
|
|
/// <param name="resolvedFlowDirection"></param>
|
|
|
/// <param name="currentLineBreak">The current line break if the line was explicitly broken.</param>
|
|
|
/// <returns>The wrapped text line.</returns>
|
|
|
- private static TextLineImpl PerformTextWrapping(List<DrawableTextRun> textRuns, int firstTextSourceIndex,
|
|
|
+ private static TextLineImpl PerformTextWrapping(IReadOnlyList<TextRun> textRuns, int firstTextSourceIndex,
|
|
|
double paragraphWidth, TextParagraphProperties paragraphProperties, FlowDirection resolvedFlowDirection,
|
|
|
TextLineBreak? currentLineBreak)
|
|
|
{
|
|
|
- if(textRuns.Count == 0)
|
|
|
+ if (textRuns.Count == 0)
|
|
|
{
|
|
|
- return CreateEmptyTextLine(firstTextSourceIndex,paragraphWidth, paragraphProperties);
|
|
|
+ return CreateEmptyTextLine(firstTextSourceIndex, paragraphWidth, paragraphProperties);
|
|
|
}
|
|
|
|
|
|
if (!TryMeasureLength(textRuns, paragraphWidth, out var measuredLength))
|
|
|
@@ -575,46 +583,24 @@ namespace Avalonia.Media.TextFormatting
|
|
|
|
|
|
for (var index = 0; index < textRuns.Count; index++)
|
|
|
{
|
|
|
- var currentRun = textRuns[index];
|
|
|
-
|
|
|
- var runText = new CharacterBufferRange(currentRun.CharacterBufferReference, currentRun.Length);
|
|
|
-
|
|
|
- var lineBreaker = new LineBreakEnumerator(runText);
|
|
|
-
|
|
|
var breakFound = false;
|
|
|
|
|
|
- while (lineBreaker.MoveNext())
|
|
|
- {
|
|
|
- if (lineBreaker.Current.Required &&
|
|
|
- currentLength + lineBreaker.Current.PositionMeasure <= measuredLength)
|
|
|
- {
|
|
|
- //Explicit break found
|
|
|
- breakFound = true;
|
|
|
-
|
|
|
- currentPosition = currentLength + lineBreaker.Current.PositionWrap;
|
|
|
-
|
|
|
- break;
|
|
|
- }
|
|
|
+ var currentRun = textRuns[index];
|
|
|
|
|
|
- if (currentLength + lineBreaker.Current.PositionMeasure > measuredLength)
|
|
|
- {
|
|
|
- if (paragraphProperties.TextWrapping == TextWrapping.WrapWithOverflow)
|
|
|
+ switch (currentRun)
|
|
|
+ {
|
|
|
+ case ShapedTextRun:
|
|
|
{
|
|
|
- if (lastWrapPosition > 0)
|
|
|
- {
|
|
|
- currentPosition = lastWrapPosition;
|
|
|
+ var runText = new CharacterBufferRange(currentRun.CharacterBufferReference, currentRun.Length);
|
|
|
|
|
|
- breakFound = true;
|
|
|
+ var lineBreaker = new LineBreakEnumerator(runText);
|
|
|
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- //Find next possible wrap position (overflow)
|
|
|
- if (index < textRuns.Count - 1)
|
|
|
+ while (lineBreaker.MoveNext())
|
|
|
{
|
|
|
- if (lineBreaker.Current.PositionWrap != currentRun.Length)
|
|
|
+ if (lineBreaker.Current.Required &&
|
|
|
+ currentLength + lineBreaker.Current.PositionMeasure <= measuredLength)
|
|
|
{
|
|
|
- //We already found the next possible wrap position.
|
|
|
+ //Explicit break found
|
|
|
breakFound = true;
|
|
|
|
|
|
currentPosition = currentLength + lineBreaker.Current.PositionWrap;
|
|
|
@@ -622,51 +608,81 @@ namespace Avalonia.Media.TextFormatting
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
- while (lineBreaker.MoveNext() && index < textRuns.Count)
|
|
|
+ if (currentLength + lineBreaker.Current.PositionMeasure > measuredLength)
|
|
|
{
|
|
|
- currentPosition += lineBreaker.Current.PositionWrap;
|
|
|
-
|
|
|
- if (lineBreaker.Current.PositionWrap != currentRun.Length)
|
|
|
+ if (paragraphProperties.TextWrapping == TextWrapping.WrapWithOverflow)
|
|
|
{
|
|
|
- break;
|
|
|
- }
|
|
|
+ if (lastWrapPosition > 0)
|
|
|
+ {
|
|
|
+ currentPosition = lastWrapPosition;
|
|
|
|
|
|
- index++;
|
|
|
+ breakFound = true;
|
|
|
+
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ //Find next possible wrap position (overflow)
|
|
|
+ if (index < textRuns.Count - 1)
|
|
|
+ {
|
|
|
+ if (lineBreaker.Current.PositionWrap != currentRun.Length)
|
|
|
+ {
|
|
|
+ //We already found the next possible wrap position.
|
|
|
+ breakFound = true;
|
|
|
+
|
|
|
+ currentPosition = currentLength + lineBreaker.Current.PositionWrap;
|
|
|
+
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ while (lineBreaker.MoveNext() && index < textRuns.Count)
|
|
|
+ {
|
|
|
+ currentPosition += lineBreaker.Current.PositionWrap;
|
|
|
+
|
|
|
+ if (lineBreaker.Current.PositionWrap != currentRun.Length)
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ index++;
|
|
|
+
|
|
|
+ if (index >= textRuns.Count)
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ currentRun = textRuns[index];
|
|
|
+
|
|
|
+ runText = new CharacterBufferRange(currentRun.CharacterBufferReference, currentRun.Length);
|
|
|
+
|
|
|
+ lineBreaker = new LineBreakEnumerator(runText);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ currentPosition = currentLength + lineBreaker.Current.PositionWrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ breakFound = true;
|
|
|
|
|
|
- if (index >= textRuns.Count)
|
|
|
- {
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
- currentRun = textRuns[index];
|
|
|
+ //We overflowed so we use the last available wrap position.
|
|
|
+ currentPosition = lastWrapPosition == 0 ? measuredLength : lastWrapPosition;
|
|
|
|
|
|
- runText = new CharacterBufferRange(currentRun.CharacterBufferReference, currentRun.Length);
|
|
|
+ breakFound = true;
|
|
|
|
|
|
- lineBreaker = new LineBreakEnumerator(runText);
|
|
|
+ break;
|
|
|
}
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- currentPosition = currentLength + lineBreaker.Current.PositionWrap;
|
|
|
- }
|
|
|
|
|
|
- breakFound = true;
|
|
|
+ if (lineBreaker.Current.PositionMeasure != lineBreaker.Current.PositionWrap)
|
|
|
+ {
|
|
|
+ lastWrapPosition = currentLength + lineBreaker.Current.PositionWrap;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
break;
|
|
|
}
|
|
|
-
|
|
|
- //We overflowed so we use the last available wrap position.
|
|
|
- currentPosition = lastWrapPosition == 0 ? measuredLength : lastWrapPosition;
|
|
|
-
|
|
|
- breakFound = true;
|
|
|
-
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- if (lineBreaker.Current.PositionMeasure != lineBreaker.Current.PositionWrap)
|
|
|
- {
|
|
|
- lastWrapPosition = currentLength + lineBreaker.Current.PositionWrap;
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
if (!breakFound)
|
|
|
@@ -681,12 +697,12 @@ namespace Avalonia.Media.TextFormatting
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
- var splitResult = SplitDrawableRuns(textRuns, measuredLength);
|
|
|
+ var splitResult = SplitTextRuns(textRuns, measuredLength);
|
|
|
|
|
|
var remainingCharacters = splitResult.Second;
|
|
|
|
|
|
var lineBreak = remainingCharacters?.Count > 0 ?
|
|
|
- new TextLineBreak(currentLineBreak?.TextEndOfLine, resolvedFlowDirection, remainingCharacters) :
|
|
|
+ new TextLineBreak(null, resolvedFlowDirection, remainingCharacters) :
|
|
|
null;
|
|
|
|
|
|
if (lineBreak is null && currentLineBreak?.TextEndOfLine != null)
|