Browse Source

Fix RightToLeft TextWrapping

pull/6211/head
Benedikt Stebner 5 years ago
parent
commit
a55a6ec1ff
  1. 2
      src/Avalonia.Visuals/Media/GlyphRun.cs
  2. 87
      src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
  3. 9
      src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs
  4. 86
      src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
  5. 35
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs

2
src/Avalonia.Visuals/Media/GlyphRun.cs

@ -582,7 +582,7 @@ namespace Avalonia.Media
{
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 _);

87
src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs

@ -90,7 +90,9 @@ namespace Avalonia.Media.TextFormatting
/// <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)
{
@ -102,31 +104,64 @@ namespace Avalonia.Media.TextFormatting
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

9
src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs

@ -134,7 +134,7 @@ namespace Avalonia.Media.TextFormatting
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 @@ namespace Avalonia.Media.TextFormatting
if (currentScript != script)
{
if (script == Script.Inherited || script == Script.Common)
if (script is Script.Unknown)
{
script = currentScript;
}
@ -174,13 +174,16 @@ namespace Avalonia.Media.TextFormatting
}
}
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;

86
src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs

@ -70,34 +70,74 @@ namespace Avalonia.Media.TextFormatting
{
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 @@ namespace Avalonia.Media.TextFormatting
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>

35
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 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
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."

Loading…
Cancel
Save