diff --git a/src/Avalonia.Visuals/Media/GlyphRun.cs b/src/Avalonia.Visuals/Media/GlyphRun.cs
index 2b787462e4e..234122f6f5d 100644
--- a/src/Avalonia.Visuals/Media/GlyphRun.cs
+++ b/src/Avalonia.Visuals/Media/GlyphRun.cs
@@ -582,7 +582,7 @@ private int GetTrailingWhitespaceLength(out int newLineLength)
{
var cluster = _glyphClusters[i];
- var codepointIndex = cluster - _characters.Start;
+ var codepointIndex = IsLeftToRight ? cluster - _characters.Start : _characters.End - cluster;
var codepoint = Codepoint.ReadAt(_characters, codepointIndex, out _);
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs b/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
index b304b19910a..64befe2e5cb 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
@@ -90,7 +90,9 @@ public override void Draw(DrawingContext drawingContext, Point origin)
/// The split result.
public SplitTextCharactersResult Split(int length)
{
- var glyphCount = GlyphRun.FindGlyphIndex(GlyphRun.Characters.Start + length);
+ var glyphCount = GlyphRun.IsLeftToRight ?
+ GlyphRun.FindGlyphIndex(GlyphRun.Characters.Start + length) :
+ GlyphRun.FindGlyphIndex(GlyphRun.Characters.End - length);
if (GlyphRun.Characters.Length == length)
{
@@ -102,31 +104,64 @@ public SplitTextCharactersResult Split(int length)
return new SplitTextCharactersResult(this, null);
}
- var firstGlyphRun = new GlyphRun(
- Properties.Typeface.GlyphTypeface,
- Properties.FontRenderingEmSize,
- GlyphRun.GlyphIndices.Take(glyphCount),
- GlyphRun.GlyphAdvances.Take(glyphCount),
- GlyphRun.GlyphOffsets.Take(glyphCount),
- GlyphRun.Characters.Take(length),
- GlyphRun.GlyphClusters.Take(glyphCount),
- GlyphRun.BiDiLevel);
-
- var firstTextRun = new ShapedTextCharacters(firstGlyphRun, Properties);
-
- var secondGlyphRun = new GlyphRun(
- Properties.Typeface.GlyphTypeface,
- Properties.FontRenderingEmSize,
- GlyphRun.GlyphIndices.Skip(glyphCount),
- GlyphRun.GlyphAdvances.Skip(glyphCount),
- GlyphRun.GlyphOffsets.Skip(glyphCount),
- GlyphRun.Characters.Skip(length),
- GlyphRun.GlyphClusters.Skip(glyphCount),
- GlyphRun.BiDiLevel);
-
- var secondTextRun = new ShapedTextCharacters(secondGlyphRun, Properties);
-
- return new SplitTextCharactersResult(firstTextRun, secondTextRun);
+ if (GlyphRun.IsLeftToRight)
+ {
+ var firstGlyphRun = new GlyphRun(
+ Properties.Typeface.GlyphTypeface,
+ Properties.FontRenderingEmSize,
+ GlyphRun.GlyphIndices.Take(glyphCount),
+ GlyphRun.GlyphAdvances.Take(glyphCount),
+ GlyphRun.GlyphOffsets.Take(glyphCount),
+ GlyphRun.Characters.Take(length),
+ GlyphRun.GlyphClusters.Take(glyphCount),
+ GlyphRun.BiDiLevel);
+
+ var firstTextRun = new ShapedTextCharacters(firstGlyphRun, Properties);
+
+ var secondGlyphRun = new GlyphRun(
+ Properties.Typeface.GlyphTypeface,
+ Properties.FontRenderingEmSize,
+ GlyphRun.GlyphIndices.Skip(glyphCount),
+ GlyphRun.GlyphAdvances.Skip(glyphCount),
+ GlyphRun.GlyphOffsets.Skip(glyphCount),
+ GlyphRun.Characters.Skip(length),
+ GlyphRun.GlyphClusters.Skip(glyphCount),
+ GlyphRun.BiDiLevel);
+
+ var secondTextRun = new ShapedTextCharacters(secondGlyphRun, Properties);
+
+ return new SplitTextCharactersResult(firstTextRun, secondTextRun);
+ }
+ else
+ {
+ var take = GlyphRun.GlyphIndices.Length - glyphCount;
+
+ var firstGlyphRun = new GlyphRun(
+ Properties.Typeface.GlyphTypeface,
+ Properties.FontRenderingEmSize,
+ GlyphRun.GlyphIndices.Take(take),
+ GlyphRun.GlyphAdvances.Take(take),
+ GlyphRun.GlyphOffsets.Take(take),
+ GlyphRun.Characters.Skip(length),
+ GlyphRun.GlyphClusters.Take(take),
+ GlyphRun.BiDiLevel);
+
+ var firstTextRun = new ShapedTextCharacters(firstGlyphRun, Properties);
+
+ var secondGlyphRun = new GlyphRun(
+ Properties.Typeface.GlyphTypeface,
+ Properties.FontRenderingEmSize,
+ GlyphRun.GlyphIndices.Skip(take),
+ GlyphRun.GlyphAdvances.Skip(take),
+ GlyphRun.GlyphOffsets.Skip(take),
+ GlyphRun.Characters.Take(length),
+ GlyphRun.GlyphClusters.Skip(take),
+ GlyphRun.BiDiLevel);
+
+ var secondTextRun = new ShapedTextCharacters(secondGlyphRun, Properties);
+
+ return new SplitTextCharactersResult(secondTextRun,firstTextRun);
+ }
}
public readonly struct SplitTextCharactersResult
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs
index 0779716ec8b..cfca8f9ab2a 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs
@@ -134,7 +134,7 @@ protected bool TryGetRunProperties(ReadOnlySlice text, Typeface typeface,
var isFallback = typeface != defaultTypeface;
count = 0;
- var script = Script.Common;
+ var script = Script.Unknown;
var direction = BiDiClass.LeftToRight;
var font = typeface.GlyphTypeface;
@@ -161,7 +161,7 @@ protected bool TryGetRunProperties(ReadOnlySlice text, Typeface typeface,
if (currentScript != script)
{
- if (script == Script.Inherited || script == Script.Common)
+ if (script is Script.Unknown)
{
script = currentScript;
}
@@ -174,13 +174,16 @@ protected bool TryGetRunProperties(ReadOnlySlice text, Typeface typeface,
}
}
- if (currentScript != Script.Common && currentScript != Script.Inherited)
+ //Only handle non whitespace here
+ if (!currentGrapheme.FirstCodepoint.IsWhiteSpace)
{
+ //Stop at the first glyph that is present in the default typeface.
if (isFallback && defaultFont.TryGetGlyph(currentGrapheme.FirstCodepoint, out _))
{
break;
}
+ //Stop at the first missing glyph
if (!font.TryGetGlyph(currentGrapheme.FirstCodepoint, out _))
{
break;
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
index 6533c34ba0a..df63b00c25c 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
@@ -70,34 +70,74 @@ internal static bool TryMeasureCharacters(ShapedTextCharacters textCharacters, d
{
var glyphTypeface = glyphRun.GlyphTypeface;
- for (var i = 0; i < glyphRun.GlyphClusters.Length; i++)
+ if (glyphRun.IsLeftToRight)
{
- var glyph = glyphRun.GlyphIndices[i];
+ foreach (var glyph in glyphRun.GlyphIndices)
+ {
+ var advance = glyphTypeface.GetGlyphAdvance(glyph) * glyphRun.Scale;
- var advance = glyphTypeface.GetGlyphAdvance(glyph) * glyphRun.Scale;
+ if (currentWidth + advance > availableWidth)
+ {
+ break;
+ }
- if (currentWidth + advance > availableWidth)
- {
- break;
+ currentWidth += advance;
+
+ glyphCount++;
}
+ }
+ else
+ {
+ for (var index = glyphRun.GlyphClusters.Length - 1; index > 0; index--)
+ {
+ var glyph = glyphRun.GlyphIndices[index];
+
+ var advance = glyphTypeface.GetGlyphAdvance(glyph) * glyphRun.Scale;
- currentWidth += advance;
+ if (currentWidth + advance > availableWidth)
+ {
+ break;
+ }
- glyphCount++;
+ currentWidth += advance;
+
+ glyphCount++;
+ }
}
}
else
{
- foreach (var advance in glyphRun.GlyphAdvances)
+ if (glyphRun.IsLeftToRight)
{
- if (currentWidth + advance > availableWidth)
+ for (var index = 0; index < glyphRun.GlyphAdvances.Length; index++)
{
- break;
+ var advance = glyphRun.GlyphAdvances[index];
+
+ if (currentWidth + advance > availableWidth)
+ {
+ break;
+ }
+
+ currentWidth += advance;
+
+ glyphCount++;
}
+ }
+ else
+ {
+ for (var index = glyphRun.GlyphAdvances.Length - 1; index > 0; index--)
+ {
+ var advance = glyphRun.GlyphAdvances[index];
+
+ if (currentWidth + advance > availableWidth)
+ {
+ break;
+ }
- currentWidth += advance;
+ currentWidth += advance;
- glyphCount++;
+ glyphCount++;
+ }
}
}
@@ -475,24 +515,14 @@ private static TextLine PerformTextWrapping(List textRuns,
var remainingCharacters = splitResult.Second;
- if (currentLineBreak?.RemainingCharacters != null)
+ var lineBreak = remainingCharacters?.Count > 0 ? new TextLineBreak(remainingCharacters) : null;
+
+ if (lineBreak is null && currentLineBreak.TextEndOfLine != null)
{
- if (remainingCharacters != null)
- {
- remainingCharacters.AddRange(currentLineBreak.RemainingCharacters);
- }
- else
- {
- remainingCharacters = new List(currentLineBreak.RemainingCharacters);
- }
+ lineBreak = new TextLineBreak(currentLineBreak.TextEndOfLine);
}
- var lineBreak = remainingCharacters != null && remainingCharacters.Count > 0 ?
- new TextLineBreak(remainingCharacters) :
- null;
-
- return new TextLineImpl(splitResult.First, textRange, paragraphWidth, paragraphProperties,
- lineBreak);
+ return new TextLineImpl(splitResult.First, textRange, paragraphWidth, paragraphProperties, lineBreak);
}
///
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
index 9c2a1953f17..a19f97e74eb 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting.Unicode;
@@ -203,6 +204,40 @@ public void Should_Wrap_With_Overflow(string text, int expectedCharactersPerLine
Assert.Equal(expectedNumberOfLines, numberOfLines);
}
}
+
+ [Fact]
+ public void Should_Wrap_RightToLeft()
+ {
+ using (Start())
+ {
+ const string text =
+ "قطاعات الصناعة على الشبكة العالمية انترنيت ويونيكود، حيث ستتم، على الصعيدين الدولي والمحلي على حد سواء";
+
+ var defaultProperties = new GenericTextRunProperties(Typeface.Default);
+
+ var textSource = new SingleBufferTextSource(text, defaultProperties);
+
+ var formatter = new TextFormatterImpl();
+
+ var currentTextSourceIndex = 0;
+
+ while (currentTextSourceIndex < text.Length)
+ {
+ var textLine =
+ formatter.FormatLine(textSource, currentTextSourceIndex, 50,
+ new GenericTextParagraphProperties(defaultProperties, textWrap: TextWrapping.Wrap));
+
+ var glyphClusters = textLine.TextRuns.Cast()
+ .SelectMany(x => x.GlyphRun.GlyphClusters).ToArray();
+
+ Assert.True(glyphClusters[0] >= glyphClusters[^1]);
+
+ Assert.Equal(currentTextSourceIndex, glyphClusters[^1]);
+
+ currentTextSourceIndex += textLine.TextRange.Length;
+ }
+ }
+ }
[InlineData("Whether to turn off HTTPS. This option only applies if Individual, " +
"IndividualB2C, SingleOrg, or MultiOrg aren't used for ‑‑auth."