|
|
@@ -41,6 +41,156 @@ namespace Avalonia.Media.TextFormatting
|
|
|
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)
|
|
|
+ {
|
|
|
+ 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
|
|
|
+ {
|
|
|
+ foreach (var advance in glyphRun.GlyphAdvances)
|
|
|
+ {
|
|
|
+ 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>
|
|
|
+ /// 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];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Fetches text runs.
|
|
|
/// </summary>
|
|
|
@@ -188,7 +338,7 @@ 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;
|
|
|
|
|
|
@@ -256,77 +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>
|
|
|
- internal 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
|
|
|
- {
|
|
|
- foreach (var advance in glyphRun.GlyphAdvances)
|
|
|
- {
|
|
|
- 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>
|
|
|
/// Gets the text range that is covered by the text runs.
|
|
|
/// </summary>
|
|
|
@@ -353,85 +432,6 @@ 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>
|
|
|
- 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];
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- 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);
|
|
|
- }
|
|
|
-
|
|
|
internal readonly struct SplitTextRunsResult
|
|
|
{
|
|
|
public SplitTextRunsResult(IReadOnlyList<ShapedTextCharacters> first, IReadOnlyList<ShapedTextCharacters> second)
|