Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not shape and render null terminator #17119

Merged
merged 3 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -82,7 +84,21 @@ private static UnshapedTextRun CreateShapeableRun(ReadOnlyMemory<char> text,
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);
Expand Down Expand Up @@ -177,6 +193,12 @@ internal static bool TryGetShapeableLength(
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 _))
Expand Down
14 changes: 1 addition & 13 deletions src/Skia/Avalonia.Skia/TextShaperImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -69,17 +67,7 @@ public ShapedBuffer ShapeText(ReadOnlyMemory<char> text, TextShaperOptions optio

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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1374,6 +1374,31 @@ public void Should_GetPreviousCharacterHit_Non_Trailing()
}
}

[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;
Expand Down
Loading