Skip to content

Commit

Permalink
Merge pull request #6211 from Gillibald/fixes/RightToLeftTextWrap
Browse files Browse the repository at this point in the history
Fix RightToLeft TextWrapping
  • Loading branch information
Gillibald authored Jul 15, 2021
2 parents fd656d5 + 52dc718 commit 898ed78
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 58 deletions.
2 changes: 1 addition & 1 deletion src/Avalonia.Visuals/Media/GlyphRun.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 _);

Expand Down
87 changes: 61 additions & 26 deletions src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ public override void Draw(DrawingContext drawingContext, Point origin)
/// <returns>The split result.</returns>
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)
{
Expand All @@ -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
Expand Down
9 changes: 6 additions & 3 deletions src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ protected bool TryGetRunProperties(ReadOnlySlice<char> 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;
Expand All @@ -161,7 +161,7 @@ protected bool TryGetRunProperties(ReadOnlySlice<char> text, Typeface typeface,

if (currentScript != script)
{
if (script == Script.Inherited || script == Script.Common)
if (script is Script.Unknown)
{
script = currentScript;
}
Expand All @@ -174,13 +174,16 @@ protected bool TryGetRunProperties(ReadOnlySlice<char> 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;
Expand Down
86 changes: 58 additions & 28 deletions src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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++;
}
}
}

Expand Down Expand Up @@ -475,24 +515,14 @@ private static TextLine PerformTextWrapping(List<ShapedTextCharacters> 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<ShapedTextCharacters>(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);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<ShapedTextCharacters>()
.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 &#8209;&#8209;auth."
Expand Down

0 comments on commit 898ed78

Please sign in to comment.