Browse Source

Do not shape and render null terminator (#17119)

* Ignore null terminator and replace them with a zero space before shaping

* Replace multiple null terminators at random positions
pull/17140/head
Benedikt Stebner 1 year ago
committed by GitHub
parent
commit
432fbe8a9e
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 24
      src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs
  2. 14
      src/Skia/Avalonia.Skia/TextShaperImpl.cs
  3. 25
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

24
src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs

@ -9,6 +9,8 @@ namespace Avalonia.Media.TextFormatting
/// </summary>
public class TextCharacters : TextRun
{
private static char ZeroWidthSpace = '\u200b';
/// <summary>
/// Constructs a run for text content from a string.
/// </summary>
@ -82,7 +84,21 @@ namespace Avalonia.Media.TextFormatting
var previousGlyphTypeface = previousProperties?.CachedGlyphTypeface;
var textSpan = text.Span;
if (TryGetShapeableLength(textSpan, defaultGlyphTypeface, null, out var count))
var count = 0;
var codepoints = new CodepointEnumerator(textSpan);
while(codepoints.MoveNext(out var firstCodepoint) && firstCodepoint.Value == 0)
{
count++;
}
//Detect null terminator
if (count > 0)
{
return new UnshapedTextRun(new string(ZeroWidthSpace, count).AsMemory(), defaultProperties, biDiLevel);
}
if (TryGetShapeableLength(textSpan, defaultGlyphTypeface, null, out count))
{
return new UnshapedTextRun(text.Slice(0, count), defaultProperties.WithTypeface(defaultTypeface),
biDiLevel);
@ -177,6 +193,12 @@ namespace Avalonia.Media.TextFormatting
var currentCodepoint = currentGrapheme.FirstCodepoint;
var currentScript = currentCodepoint.Script;
if(currentCodepoint.Value == 0)
{
//Do not include null terminators
break;
}
if (!currentCodepoint.IsWhiteSpace
&& defaultGlyphTypeface != null
&& defaultGlyphTypeface.TryGetGlyph(currentCodepoint, out _))

14
src/Skia/Avalonia.Skia/TextShaperImpl.cs

@ -14,8 +14,6 @@ namespace Avalonia.Skia
{
internal class TextShaperImpl : ITextShaperImpl
{
private const uint ZeroWidthSpace = '\u200b';
private static readonly ConcurrentDictionary<int, Language> s_cachedLanguage = new();
public ShapedBuffer ShapeText(ReadOnlyMemory<char> text, TextShaperOptions options)
@ -69,17 +67,7 @@ namespace Avalonia.Skia
var glyphIndex = (ushort)sourceInfo.Codepoint;
var glyphCluster = (int)(sourceInfo.Cluster);
if (glyphIndex == 0)
{
var codepoint = Codepoint.ReadAt(textSpan, glyphCluster, out _);
if (codepoint.GeneralCategory == GeneralCategory.Control)
{
glyphIndex = options.Typeface.GetGlyph(ZeroWidthSpace);
}
}
var glyphCluster = (int)sourceInfo.Cluster;
var glyphAdvance = GetGlyphAdvance(glyphPositions, i, textScale) + options.LetterSpacing;

25
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

@ -1374,6 +1374,31 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
[Theory]
[InlineData("\0", 0.0)]
[InlineData("\0\0\0", 0.0)]
[InlineData("\0A\0\0", 7.201171875)]
[InlineData("\0AA\0AA\0", 28.8046875)]
public void Should_Ignore_Null_Terminator(string text, double width)
{
using (Start())
{
var defaultProperties = new GenericTextRunProperties(Typeface.Default);
var textSource = new SingleBufferTextSource(text, defaultProperties, true);
var formatter = new TextFormatterImpl();
var textLine =
formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(FlowDirection.LeftToRight, TextAlignment.Left,
true, true, defaultProperties, TextWrapping.NoWrap, 0, 0, 0));
Assert.NotNull(textLine);
Assert.Equal(width, textLine.Width);
}
}
private class FixedRunsTextSource : ITextSource
{
private readonly IReadOnlyList<TextRun> _textRuns;

Loading…
Cancel
Save