|
|
|
@ -1,52 +1,194 @@ |
|
|
|
using System.Collections.Generic; |
|
|
|
using System; |
|
|
|
using System.Collections.Generic; |
|
|
|
using Avalonia.Media.TextFormatting.Unicode; |
|
|
|
using Avalonia.Platform; |
|
|
|
using Avalonia.Utilities; |
|
|
|
|
|
|
|
namespace Avalonia.Media.TextFormatting |
|
|
|
{ |
|
|
|
internal class TextFormatterImpl : TextFormatter |
|
|
|
{ |
|
|
|
private static readonly ReadOnlySlice<char> s_ellipsis = new ReadOnlySlice<char>(new[] { '\u2026' }); |
|
|
|
|
|
|
|
/// <inheritdoc cref="TextFormatter.FormatLine"/>
|
|
|
|
public override TextLine FormatLine(ITextSource textSource, int firstTextSourceIndex, double paragraphWidth, |
|
|
|
TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak = null) |
|
|
|
{ |
|
|
|
var textTrimming = paragraphProperties.TextTrimming; |
|
|
|
var textWrapping = paragraphProperties.TextWrapping; |
|
|
|
TextLine textLine = null; |
|
|
|
|
|
|
|
var textRuns = FetchTextRuns(textSource, firstTextSourceIndex, previousLineBreak, out var nextLineBreak); |
|
|
|
|
|
|
|
var textRange = GetTextRange(textRuns); |
|
|
|
|
|
|
|
if (textTrimming != TextTrimming.None) |
|
|
|
TextLine textLine; |
|
|
|
|
|
|
|
switch (textWrapping) |
|
|
|
{ |
|
|
|
case TextWrapping.NoWrap: |
|
|
|
{ |
|
|
|
var textLineMetrics = |
|
|
|
TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties); |
|
|
|
|
|
|
|
textLine = new TextLineImpl(textRuns, textLineMetrics, nextLineBreak); |
|
|
|
break; |
|
|
|
} |
|
|
|
case TextWrapping.WrapWithOverflow: |
|
|
|
case TextWrapping.Wrap: |
|
|
|
{ |
|
|
|
textLine = PerformTextWrapping(textRuns, textRange, paragraphWidth, paragraphProperties); |
|
|
|
break; |
|
|
|
} |
|
|
|
default: |
|
|
|
throw new ArgumentOutOfRangeException(); |
|
|
|
} |
|
|
|
|
|
|
|
return textLine; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Measures the number of characters that fits into available width.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="textCharacters">The text run.</param>
|
|
|
|
/// <param name="availableWidth">The available width.</param>
|
|
|
|
/// <returns></returns>
|
|
|
|
internal static int MeasureCharacters(ShapedTextCharacters textCharacters, double availableWidth) |
|
|
|
{ |
|
|
|
var glyphRun = textCharacters.GlyphRun; |
|
|
|
|
|
|
|
if (glyphRun.Bounds.Width < availableWidth) |
|
|
|
{ |
|
|
|
textLine = PerformTextTrimming(textRuns, textRange, paragraphWidth, paragraphProperties); |
|
|
|
return glyphRun.Characters.Length; |
|
|
|
} |
|
|
|
|
|
|
|
var glyphCount = 0; |
|
|
|
|
|
|
|
var currentWidth = 0.0; |
|
|
|
|
|
|
|
if (glyphRun.GlyphAdvances.IsEmpty) |
|
|
|
{ |
|
|
|
var glyphTypeface = glyphRun.GlyphTypeface; |
|
|
|
|
|
|
|
for (var i = 0; i < glyphRun.GlyphClusters.Length; i++) |
|
|
|
{ |
|
|
|
var glyph = glyphRun.GlyphIndices[i]; |
|
|
|
|
|
|
|
var advance = glyphTypeface.GetGlyphAdvance(glyph) * glyphRun.Scale; |
|
|
|
|
|
|
|
if (currentWidth + advance > availableWidth) |
|
|
|
{ |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
currentWidth += advance; |
|
|
|
|
|
|
|
glyphCount++; |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
switch (textWrapping) |
|
|
|
foreach (var advance in glyphRun.GlyphAdvances) |
|
|
|
{ |
|
|
|
case TextWrapping.NoWrap: |
|
|
|
{ |
|
|
|
var textLineMetrics = |
|
|
|
TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties); |
|
|
|
if (currentWidth + advance > availableWidth) |
|
|
|
{ |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
textLine = new TextLineImpl(textRuns, textLineMetrics, nextLineBreak); |
|
|
|
break; |
|
|
|
currentWidth += advance; |
|
|
|
|
|
|
|
glyphCount++; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (glyphCount == glyphRun.GlyphIndices.Length) |
|
|
|
{ |
|
|
|
return glyphRun.Characters.Length; |
|
|
|
} |
|
|
|
|
|
|
|
if (glyphRun.GlyphClusters.IsEmpty) |
|
|
|
{ |
|
|
|
return glyphCount; |
|
|
|
} |
|
|
|
|
|
|
|
var firstCluster = glyphRun.GlyphClusters[0]; |
|
|
|
|
|
|
|
var lastCluster = glyphRun.GlyphClusters[glyphCount]; |
|
|
|
|
|
|
|
return lastCluster - firstCluster; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Split a sequence of runs into two segments at specified length.
|
|
|
|
/// </summary>
|
|
|
|
/// <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 SplitTextRunsResult SplitTextRuns(IReadOnlyList<ShapedTextCharacters> textRuns, int length) |
|
|
|
{ |
|
|
|
var currentLength = 0; |
|
|
|
|
|
|
|
for (var i = 0; i < textRuns.Count; i++) |
|
|
|
{ |
|
|
|
var currentRun = textRuns[i]; |
|
|
|
|
|
|
|
if (currentLength + currentRun.GlyphRun.Characters.Length < length) |
|
|
|
{ |
|
|
|
currentLength += currentRun.GlyphRun.Characters.Length; |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
var firstCount = currentRun.GlyphRun.Characters.Length >= 1 ? i + 1 : i; |
|
|
|
|
|
|
|
var first = new ShapedTextCharacters[firstCount]; |
|
|
|
|
|
|
|
if (firstCount > 1) |
|
|
|
{ |
|
|
|
for (var j = 0; j < i; j++) |
|
|
|
{ |
|
|
|
first[j] = textRuns[j]; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
var secondCount = textRuns.Count - firstCount; |
|
|
|
|
|
|
|
if (currentLength + currentRun.GlyphRun.Characters.Length == length) |
|
|
|
{ |
|
|
|
var second = new ShapedTextCharacters[secondCount]; |
|
|
|
|
|
|
|
var offset = currentRun.GlyphRun.Characters.Length > 1 ? 1 : 0; |
|
|
|
|
|
|
|
if (secondCount > 0) |
|
|
|
{ |
|
|
|
for (var j = 0; j < secondCount; j++) |
|
|
|
{ |
|
|
|
second[j] = textRuns[i + j + offset]; |
|
|
|
} |
|
|
|
case TextWrapping.WrapWithOverflow: |
|
|
|
case TextWrapping.Wrap: |
|
|
|
} |
|
|
|
|
|
|
|
first[i] = currentRun; |
|
|
|
|
|
|
|
return new SplitTextRunsResult(first, second); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
secondCount++; |
|
|
|
|
|
|
|
var second = new ShapedTextCharacters[secondCount]; |
|
|
|
|
|
|
|
if (secondCount > 0) |
|
|
|
{ |
|
|
|
for (var j = 1; j < secondCount; j++) |
|
|
|
{ |
|
|
|
textLine = PerformTextWrapping(textRuns, textRange, paragraphWidth, paragraphProperties); |
|
|
|
break; |
|
|
|
second[j] = textRuns[i + j]; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
var split = currentRun.Split(length - currentLength); |
|
|
|
|
|
|
|
first[i] = split.First; |
|
|
|
|
|
|
|
second[0] = split.Second; |
|
|
|
|
|
|
|
return new SplitTextRunsResult(first, second); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return textLine; |
|
|
|
return new SplitTextRunsResult(textRuns, null); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
@ -174,87 +316,6 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Performs text trimming and returns a trimmed line.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="textRuns">The text runs to perform the trimming on.</param>
|
|
|
|
/// <param name="textRange">The text range that is covered by the text runs.</param>
|
|
|
|
/// <param name="paragraphWidth">A <see cref="double"/> value that specifies the width of the paragraph that the line fills.</param>
|
|
|
|
/// <param name="paragraphProperties">A <see cref="TextParagraphProperties"/> value that represents paragraph properties,
|
|
|
|
/// such as TextWrapping, TextAlignment, or TextStyle.</param>
|
|
|
|
/// <returns></returns>
|
|
|
|
private static TextLine PerformTextTrimming(IReadOnlyList<ShapedTextCharacters> textRuns, TextRange textRange, |
|
|
|
double paragraphWidth, TextParagraphProperties paragraphProperties) |
|
|
|
{ |
|
|
|
var textTrimming = paragraphProperties.TextTrimming; |
|
|
|
var availableWidth = paragraphWidth; |
|
|
|
var currentWidth = 0.0; |
|
|
|
var runIndex = 0; |
|
|
|
|
|
|
|
while (runIndex < textRuns.Count) |
|
|
|
{ |
|
|
|
var currentRun = textRuns[runIndex]; |
|
|
|
|
|
|
|
currentWidth += currentRun.GlyphRun.Bounds.Width; |
|
|
|
|
|
|
|
if (currentWidth > availableWidth) |
|
|
|
{ |
|
|
|
var ellipsisRun = CreateEllipsisRun(currentRun.Properties); |
|
|
|
|
|
|
|
var measuredLength = MeasureText(currentRun, availableWidth - ellipsisRun.GlyphRun.Bounds.Width); |
|
|
|
|
|
|
|
if (textTrimming == TextTrimming.WordEllipsis) |
|
|
|
{ |
|
|
|
if (measuredLength < textRange.End) |
|
|
|
{ |
|
|
|
var currentBreakPosition = 0; |
|
|
|
|
|
|
|
var lineBreaker = new LineBreakEnumerator(currentRun.Text); |
|
|
|
|
|
|
|
while (currentBreakPosition < measuredLength && lineBreaker.MoveNext()) |
|
|
|
{ |
|
|
|
var nextBreakPosition = lineBreaker.Current.PositionWrap; |
|
|
|
|
|
|
|
if (nextBreakPosition == 0) |
|
|
|
{ |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
if (nextBreakPosition > measuredLength) |
|
|
|
{ |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
currentBreakPosition = nextBreakPosition; |
|
|
|
} |
|
|
|
|
|
|
|
measuredLength = currentBreakPosition; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
var splitResult = SplitTextRuns(textRuns, measuredLength); |
|
|
|
|
|
|
|
var trimmedRuns = new List<ShapedTextCharacters>(splitResult.First.Count + 1); |
|
|
|
|
|
|
|
trimmedRuns.AddRange(splitResult.First); |
|
|
|
|
|
|
|
trimmedRuns.Add(ellipsisRun); |
|
|
|
|
|
|
|
var textLineMetrics = |
|
|
|
TextLineMetrics.Create(trimmedRuns, textRange, paragraphWidth, paragraphProperties); |
|
|
|
|
|
|
|
return new TextLineImpl(trimmedRuns, textLineMetrics); |
|
|
|
} |
|
|
|
|
|
|
|
availableWidth -= currentRun.GlyphRun.Bounds.Width; |
|
|
|
|
|
|
|
runIndex++; |
|
|
|
} |
|
|
|
|
|
|
|
return new TextLineImpl(textRuns, |
|
|
|
TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties)); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Performs text wrapping returns a list of text lines.
|
|
|
|
/// </summary>
|
|
|
|
@ -269,7 +330,7 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
var availableWidth = paragraphWidth; |
|
|
|
var currentWidth = 0.0; |
|
|
|
var runIndex = 0; |
|
|
|
var length = 0; |
|
|
|
var currentLength = 0; |
|
|
|
|
|
|
|
while (runIndex < textRuns.Count) |
|
|
|
{ |
|
|
|
@ -277,60 +338,55 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
|
|
|
|
if (currentWidth + currentRun.GlyphRun.Bounds.Width > availableWidth) |
|
|
|
{ |
|
|
|
var measuredLength = MeasureText(currentRun, paragraphWidth - currentWidth); |
|
|
|
var measuredLength = MeasureCharacters(currentRun, paragraphWidth - currentWidth); |
|
|
|
|
|
|
|
var breakFound = false; |
|
|
|
|
|
|
|
var currentBreakPosition = 0; |
|
|
|
|
|
|
|
if (measuredLength < currentRun.Text.Length) |
|
|
|
{ |
|
|
|
if (paragraphProperties.TextWrapping == TextWrapping.WrapWithOverflow) |
|
|
|
{ |
|
|
|
var lineBreaker = new LineBreakEnumerator(currentRun.Text.Skip(measuredLength)); |
|
|
|
var lineBreaker = new LineBreakEnumerator(currentRun.Text); |
|
|
|
|
|
|
|
if (lineBreaker.MoveNext()) |
|
|
|
{ |
|
|
|
measuredLength += lineBreaker.Current.PositionWrap; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
measuredLength = currentRun.Text.Length; |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
while (currentBreakPosition < measuredLength && lineBreaker.MoveNext()) |
|
|
|
{ |
|
|
|
var currentBreakPosition = -1; |
|
|
|
var nextBreakPosition = lineBreaker.Current.PositionWrap; |
|
|
|
|
|
|
|
var lineBreaker = new LineBreakEnumerator(currentRun.Text); |
|
|
|
|
|
|
|
while (currentBreakPosition < measuredLength && lineBreaker.MoveNext()) |
|
|
|
if (nextBreakPosition == 0 || nextBreakPosition > measuredLength) |
|
|
|
{ |
|
|
|
var nextBreakPosition = lineBreaker.Current.PositionWrap; |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
if (nextBreakPosition == 0) |
|
|
|
{ |
|
|
|
break; |
|
|
|
} |
|
|
|
breakFound = lineBreaker.Current.Required || |
|
|
|
lineBreaker.Current.PositionWrap != currentRun.Text.Length; |
|
|
|
|
|
|
|
if (nextBreakPosition > measuredLength) |
|
|
|
{ |
|
|
|
break; |
|
|
|
} |
|
|
|
currentBreakPosition = nextBreakPosition; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
currentBreakPosition = nextBreakPosition; |
|
|
|
} |
|
|
|
if (breakFound) |
|
|
|
{ |
|
|
|
measuredLength = currentBreakPosition; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
if (paragraphProperties.TextWrapping == TextWrapping.WrapWithOverflow) |
|
|
|
{ |
|
|
|
var lineBreaker = new LineBreakEnumerator(currentRun.Text.Skip(currentBreakPosition)); |
|
|
|
|
|
|
|
if (currentBreakPosition != -1) |
|
|
|
if (lineBreaker.MoveNext()) |
|
|
|
{ |
|
|
|
measuredLength = currentBreakPosition; |
|
|
|
measuredLength = currentBreakPosition + lineBreaker.Current.PositionWrap; |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
length += measuredLength; |
|
|
|
currentLength += measuredLength; |
|
|
|
|
|
|
|
var splitResult = SplitTextRuns(textRuns, length); |
|
|
|
var splitResult = SplitTextRuns(textRuns, currentLength); |
|
|
|
|
|
|
|
var textLineMetrics = TextLineMetrics.Create(splitResult.First, |
|
|
|
new TextRange(textRange.Start, length), paragraphWidth, paragraphProperties); |
|
|
|
new TextRange(textRange.Start, currentLength), paragraphWidth, paragraphProperties); |
|
|
|
|
|
|
|
var lineBreak = splitResult.Second != null && splitResult.Second.Count > 0 ? |
|
|
|
new TextLineBreak(splitResult.Second) : |
|
|
|
@ -341,7 +397,7 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
|
|
|
|
currentWidth += currentRun.GlyphRun.Bounds.Width; |
|
|
|
|
|
|
|
length += currentRun.GlyphRun.Characters.Length; |
|
|
|
currentLength += currentRun.GlyphRun.Characters.Length; |
|
|
|
|
|
|
|
runIndex++; |
|
|
|
} |
|
|
|
@ -350,94 +406,6 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties)); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Measures the number of characters that fits into available width.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="textCharacters">The text run.</param>
|
|
|
|
/// <param name="availableWidth">The available width.</param>
|
|
|
|
/// <returns></returns>
|
|
|
|
private static int MeasureText(ShapedTextCharacters textCharacters, double availableWidth) |
|
|
|
{ |
|
|
|
var glyphRun = textCharacters.GlyphRun; |
|
|
|
|
|
|
|
if (glyphRun.Bounds.Width < availableWidth) |
|
|
|
{ |
|
|
|
return glyphRun.Characters.Length; |
|
|
|
} |
|
|
|
|
|
|
|
var glyphCount = 0; |
|
|
|
|
|
|
|
var currentWidth = 0.0; |
|
|
|
|
|
|
|
if (glyphRun.GlyphAdvances.IsEmpty) |
|
|
|
{ |
|
|
|
var glyphTypeface = glyphRun.GlyphTypeface; |
|
|
|
|
|
|
|
for (var i = 0; i < glyphRun.GlyphClusters.Length; i++) |
|
|
|
{ |
|
|
|
var glyph = glyphRun.GlyphIndices[i]; |
|
|
|
|
|
|
|
var advance = glyphTypeface.GetGlyphAdvance(glyph) * glyphRun.Scale; |
|
|
|
|
|
|
|
if (currentWidth + advance > availableWidth) |
|
|
|
{ |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
currentWidth += advance; |
|
|
|
|
|
|
|
glyphCount++; |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
for (var i = 0; i < glyphRun.GlyphAdvances.Length; i++) |
|
|
|
{ |
|
|
|
var advance = glyphRun.GlyphAdvances[i]; |
|
|
|
|
|
|
|
if (currentWidth + advance > availableWidth) |
|
|
|
{ |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
currentWidth += advance; |
|
|
|
|
|
|
|
glyphCount++; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (glyphCount == glyphRun.GlyphIndices.Length) |
|
|
|
{ |
|
|
|
return glyphRun.Characters.Length; |
|
|
|
} |
|
|
|
|
|
|
|
if (glyphRun.GlyphClusters.IsEmpty) |
|
|
|
{ |
|
|
|
return glyphCount; |
|
|
|
} |
|
|
|
|
|
|
|
var firstCluster = glyphRun.GlyphClusters[0]; |
|
|
|
|
|
|
|
var lastCluster = glyphRun.GlyphClusters[glyphCount]; |
|
|
|
|
|
|
|
return lastCluster - firstCluster; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Creates an ellipsis.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="properties">The text run properties.</param>
|
|
|
|
/// <returns></returns>
|
|
|
|
private static ShapedTextCharacters CreateEllipsisRun(TextRunProperties properties) |
|
|
|
{ |
|
|
|
var formatterImpl = AvaloniaLocator.Current.GetService<ITextShaperImpl>(); |
|
|
|
|
|
|
|
var glyphRun = formatterImpl.ShapeText(s_ellipsis, properties.Typeface, properties.FontRenderingEmSize, |
|
|
|
properties.CultureInfo); |
|
|
|
|
|
|
|
return new ShapedTextCharacters(glyphRun, properties); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the text range that is covered by the text runs.
|
|
|
|
/// </summary>
|
|
|
|
@ -464,86 +432,7 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
return new TextRange(start, end - start); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Split a sequence of runs into two segments at specified length.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="textRuns">The text run's.</param>
|
|
|
|
/// <param name="length">The length to split at.</param>
|
|
|
|
/// <returns>The split text runs.</returns>
|
|
|
|
private static SplitTextRunsResult SplitTextRuns(IReadOnlyList<ShapedTextCharacters> textRuns, int length) |
|
|
|
{ |
|
|
|
var currentLength = 0; |
|
|
|
|
|
|
|
for (var i = 0; i < textRuns.Count; i++) |
|
|
|
{ |
|
|
|
var currentRun = textRuns[i]; |
|
|
|
|
|
|
|
if (currentLength + currentRun.GlyphRun.Characters.Length < length) |
|
|
|
{ |
|
|
|
currentLength += currentRun.GlyphRun.Characters.Length; |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
var firstCount = currentRun.GlyphRun.Characters.Length >= 1 ? i + 1 : i; |
|
|
|
|
|
|
|
var first = new ShapedTextCharacters[firstCount]; |
|
|
|
|
|
|
|
if (firstCount > 1) |
|
|
|
{ |
|
|
|
for (var j = 0; j < i; j++) |
|
|
|
{ |
|
|
|
first[j] = textRuns[j]; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
var secondCount = textRuns.Count - firstCount; |
|
|
|
|
|
|
|
if (currentLength + currentRun.GlyphRun.Characters.Length == length) |
|
|
|
{ |
|
|
|
var second = new ShapedTextCharacters[secondCount]; |
|
|
|
|
|
|
|
var offset = currentRun.GlyphRun.Characters.Length > 1 ? 1 : 0; |
|
|
|
|
|
|
|
if (secondCount > 0) |
|
|
|
{ |
|
|
|
for (var j = 0; j < secondCount; j++) |
|
|
|
{ |
|
|
|
second[j] = textRuns[i + j + offset]; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
first[i] = currentRun; |
|
|
|
|
|
|
|
return new SplitTextRunsResult(first, second); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
secondCount++; |
|
|
|
|
|
|
|
var second = new ShapedTextCharacters[secondCount]; |
|
|
|
|
|
|
|
if (secondCount > 0) |
|
|
|
{ |
|
|
|
for (var j = 1; j < secondCount; j++) |
|
|
|
{ |
|
|
|
second[j] = textRuns[i + j]; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
var split = currentRun.Split(length - currentLength); |
|
|
|
|
|
|
|
first[i] = split.First; |
|
|
|
|
|
|
|
second[0] = split.Second; |
|
|
|
|
|
|
|
return new SplitTextRunsResult(first, second); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return new SplitTextRunsResult(textRuns, null); |
|
|
|
} |
|
|
|
|
|
|
|
private readonly struct SplitTextRunsResult |
|
|
|
internal readonly struct SplitTextRunsResult |
|
|
|
{ |
|
|
|
public SplitTextRunsResult(IReadOnlyList<ShapedTextCharacters> first, IReadOnlyList<ShapedTextCharacters> second) |
|
|
|
{ |
|
|
|
|