diff --git a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs
index 7d4fac337d6..d001f8f3701 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs
@@ -9,6 +9,8 @@ namespace Avalonia.Media.TextFormatting
///
public class TextCharacters : TextRun
{
+ private static char ZeroWidthSpace = '\u200b';
+
///
/// Constructs a run for text content from a string.
///
@@ -82,7 +84,21 @@ private static UnshapedTextRun CreateShapeableRun(ReadOnlyMemory 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);
@@ -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 _))
diff --git a/src/Skia/Avalonia.Skia/TextShaperImpl.cs b/src/Skia/Avalonia.Skia/TextShaperImpl.cs
index 768e8f3a2f3..cedb2f63cf1 100644
--- a/src/Skia/Avalonia.Skia/TextShaperImpl.cs
+++ b/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 s_cachedLanguage = new();
public ShapedBuffer ShapeText(ReadOnlyMemory text, TextShaperOptions options)
@@ -69,17 +67,7 @@ public ShapedBuffer ShapeText(ReadOnlyMemory 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;
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
index f933ffb17be..6f8b2c407fd 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
@@ -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 _textRuns;