diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs index 9a2645f03d..22be8d8865 100644 --- a/src/Avalonia.Base/Media/GlyphRun.cs +++ b/src/Avalonia.Base/Media/GlyphRun.cs @@ -28,6 +28,8 @@ namespace Avalonia.Media private IReadOnlyList? _glyphOffsets; private IReadOnlyList? _glyphClusters; + private int _offsetToFirstCharacter; + /// /// Initializes a new instance of the class by specifying properties of the class. /// @@ -203,7 +205,7 @@ namespace Avalonia.Media /// public double GetDistanceFromCharacterHit(CharacterHit characterHit) { - var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength; + var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength - _offsetToFirstCharacter; var distance = 0.0; @@ -552,30 +554,20 @@ namespace Avalonia.Media } nextCluster = GlyphClusters[currentIndex]; - } - - if (nextCluster < Characters.Start) - { - nextCluster = Characters.Start; - } - - if (cluster < Characters.Start) - { - cluster = Characters.Start; - } + } int trailingLength; if (nextCluster == cluster) { - trailingLength = Characters.Start + Characters.Length - cluster; + trailingLength = Characters.Start + Characters.Length - _offsetToFirstCharacter - cluster; } else { trailingLength = nextCluster - cluster; } - return new CharacterHit(cluster, trailingLength); + return new CharacterHit(_offsetToFirstCharacter + cluster, trailingLength); } /// @@ -609,6 +601,13 @@ namespace Avalonia.Media private GlyphRunMetrics CreateGlyphRunMetrics() { + if (GlyphClusters != null && GlyphClusters.Count > 0) + { + var firstCluster = GlyphClusters[0]; + + _offsetToFirstCharacter = Math.Max(0, Characters.Start - firstCluster); + } + var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale; var widthIncludingTrailingWhitespace = 0d; @@ -680,34 +679,40 @@ namespace Avalonia.Media { for (var i = GlyphClusters.Count - 1; i >= 0; i--) { - var cluster = GlyphClusters[i]; - - var codepointIndex = IsLeftToRight ? cluster - _characters.Start : _characters.End - cluster; + var currentCluster = GlyphClusters[i]; + var characterIndex = Math.Max(0, currentCluster - _characters.BufferOffset); + var codepoint = Codepoint.ReadAt(_characters, characterIndex, out _); - if (codepointIndex < 0) + if (!codepoint.IsWhiteSpace) { - trailingWhitespaceLength = _characters.Length; - - glyphCount = GlyphClusters.Count; - break; } - var codepoint = Codepoint.ReadAt(_characters, codepointIndex, out _); + var clusterLength = 1; - if (!codepoint.IsWhiteSpace) + while(i - 1 >= 0) { + var nextCluster = GlyphClusters[i - 1]; + + if(currentCluster == nextCluster) + { + clusterLength++; + i--; + + continue; + } + break; } if (codepoint.IsBreakChar) { - newLineLength++; + newLineLength += clusterLength; } - trailingWhitespaceLength++; - - glyphCount++; + trailingWhitespaceLength += clusterLength; + + glyphCount++; } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index 67fba00ee8..7f0f204886 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs @@ -79,14 +79,14 @@ namespace Avalonia.Media.TextFormatting { var currentRun = textRuns[i]; - if (currentLength + currentRun.Text.Length < length) + if (currentLength + currentRun.TextSourceLength < length) { currentLength += currentRun.TextSourceLength; continue; } - var firstCount = currentRun.Text.Length >= 1 ? i + 1 : i; + var firstCount = currentRun.TextSourceLength >= 1 ? i + 1 : i; var first = new List(firstCount); @@ -100,13 +100,13 @@ namespace Avalonia.Media.TextFormatting var secondCount = textRuns.Count - firstCount; - if (currentLength + currentRun.Text.Length == length) + if (currentLength + currentRun.TextSourceLength == length) { var second = secondCount > 0 ? new List(secondCount) : null; if (second != null) { - var offset = currentRun.Text.Length >= 1 ? 1 : 0; + var offset = currentRun.TextSourceLength >= 1 ? 1 : 0; for (var j = 0; j < secondCount; j++) { @@ -124,16 +124,14 @@ namespace Avalonia.Media.TextFormatting var second = new List(secondCount); - if (currentRun is not ShapedTextCharacters shapedTextCharacters) + if (currentRun is ShapedTextCharacters shapedTextCharacters) { - throw new NotSupportedException("Only shaped runs can be split in between."); - } - - var split = shapedTextCharacters.Split(length - currentLength); + var split = shapedTextCharacters.Split(length - currentLength); - first.Add(split.First); + first.Add(split.First); - second.Add(split.Second!); + second.Add(split.Second!); + } for (var j = 1; j < secondCount; j++) { @@ -483,7 +481,7 @@ namespace Avalonia.Media.TextFormatting { case ShapedTextCharacters shapedTextCharacters: { - var firstCluster = shapedTextCharacters.Text.Start; + var firstCluster = shapedTextCharacters.ShapedBuffer.GlyphClusters[0]; var lastCluster = firstCluster; for (var i = 0; i < shapedTextCharacters.ShapedBuffer.Length; i++) @@ -492,7 +490,7 @@ namespace Avalonia.Media.TextFormatting if (currentWidth + glyphInfo.GlyphAdvance > paragraphWidth) { - measuredLength += Math.Max(0, lastCluster - firstCluster + 1); + measuredLength += Math.Max(0, lastCluster - firstCluster); goto found; } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index b480774d1d..6a704f6f3e 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -404,7 +404,7 @@ namespace Avalonia.Media.TextFormatting var result = new List(TextRuns.Count); var lastDirection = _flowDirection; var currentDirection = lastDirection; - var currentPosition = 0; + var currentPosition = FirstTextSourceIndex; var currentRect = Rect.Empty; var startX = Start; @@ -418,6 +418,11 @@ namespace Avalonia.Media.TextFormatting continue; } + if(currentPosition + currentRun.TextSourceLength <= firstTextSourceCharacterIndex) + { + continue; + } + TextRun? nextRun = null; if (index + 1 < TextRuns.Count) @@ -1018,31 +1023,21 @@ namespace Avalonia.Media.TextFormatting private TextLineMetrics CreateLineMetrics() { - var start = 0d; - var height = 0d; + var glyphTypeface = _paragraphProperties.DefaultTextRunProperties.Typeface.GlyphTypeface; + var fontRenderingEmSize = _paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize; + var scale = fontRenderingEmSize / glyphTypeface.DesignEmHeight; + var width = 0d; var widthIncludingWhitespace = 0d; var trailingWhitespaceLength = 0; var newLineLength = 0; - var ascent = 0d; - var descent = 0d; - var lineGap = 0d; - var fontRenderingEmSize = 0d; + var ascent = glyphTypeface.Ascent * scale; + var descent = glyphTypeface.Descent * scale; + var lineGap = glyphTypeface.LineGap * scale; - var lineHeight = _paragraphProperties.LineHeight; - - if (_textRuns.Count == 0) - { - var glyphTypeface = _paragraphProperties.DefaultTextRunProperties.Typeface.GlyphTypeface; - fontRenderingEmSize = _paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize; - var scale = fontRenderingEmSize / glyphTypeface.DesignEmHeight; - ascent = glyphTypeface.Ascent * scale; - height = double.IsNaN(lineHeight) || MathUtilities.IsZero(lineHeight) ? - descent - ascent + lineGap : - lineHeight; - - return new TextLineMetrics(false, height, 0, start, -ascent, 0, 0, 0); - } + var height = descent - ascent + lineGap; + + var lineHeight = _paragraphProperties.LineHeight; for (var index = 0; index < _textRuns.Count; index++) { @@ -1166,12 +1161,15 @@ namespace Avalonia.Media.TextFormatting } } - start = GetParagraphOffsetX(width, widthIncludingWhitespace, _paragraphWidth, + var start = GetParagraphOffsetX(width, widthIncludingWhitespace, _paragraphWidth, _paragraphProperties.TextAlignment, _paragraphProperties.FlowDirection); if (!double.IsNaN(lineHeight) && !MathUtilities.IsZero(lineHeight)) { - height = lineHeight; + if(lineHeight > height) + { + height = lineHeight; + } } return new TextLineMetrics(widthIncludingWhitespace > _paragraphWidth, height, newLineLength, start, diff --git a/src/Skia/Avalonia.Skia/TextShaperImpl.cs b/src/Skia/Avalonia.Skia/TextShaperImpl.cs index a0890262e7..ebaa247da8 100644 --- a/src/Skia/Avalonia.Skia/TextShaperImpl.cs +++ b/src/Skia/Avalonia.Skia/TextShaperImpl.cs @@ -1,6 +1,5 @@ using System; using System.Globalization; -using Avalonia.Media; using Avalonia.Media.TextFormatting; using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Platform; @@ -59,7 +58,7 @@ namespace Avalonia.Skia var glyphIndex = (ushort)sourceInfo.Codepoint; - var glyphCluster = (int)sourceInfo.Cluster; + var glyphCluster = (int)(sourceInfo.Cluster); var glyphAdvance = GetGlyphAdvance(glyphPositions, i, textScale); diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs index e3b9e5a8b1..a47638d2ec 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs @@ -665,17 +665,16 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var text = "0123".AsMemory(); var shaperOption = new TextShaperOptions(Typeface.Default.GlyphTypeface, 10, 0, CultureInfo.CurrentCulture); - var shapedBuffer = TextShaper.Current.ShapeText(new ReadOnlySlice(text), shaperOption); - var firstRun = new ShapedTextCharacters(shapedBuffer, defaultProperties); + var firstRun = new ShapedTextCharacters(TextShaper.Current.ShapeText(new ReadOnlySlice(text, 1, text.Length), shaperOption), defaultProperties); var textRuns = new List { new CustomDrawableRun(), firstRun, new CustomDrawableRun(), - new ShapedTextCharacters(shapedBuffer, defaultProperties), + new ShapedTextCharacters(TextShaper.Current.ShapeText(new ReadOnlySlice(text, text.Length + 2, text.Length), shaperOption), defaultProperties), new CustomDrawableRun(), - new ShapedTextCharacters(shapedBuffer, defaultProperties) + new ShapedTextCharacters(TextShaper.Current.ShapeText(new ReadOnlySlice(text, text.Length * 2 + 3, text.Length), shaperOption), defaultProperties) }; var textSource = new FixedRunsTextSource(textRuns); @@ -691,15 +690,25 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting Assert.Equal(1, textBounds.Count); Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds.Sum(x => x.Rectangle.Width)); - textBounds = textLine.GetTextBounds(0, firstRun.Text.Length); + textBounds = textLine.GetTextBounds(0, 1); Assert.Equal(1, textBounds.Count); - Assert.Equal(firstRun.Size.Width, textBounds[0].Rectangle.Width); + Assert.Equal(14, textBounds[0].Rectangle.Width); textBounds = textLine.GetTextBounds(0, firstRun.Text.Length + 1); Assert.Equal(1, textBounds.Count); Assert.Equal(firstRun.Size.Width + 14, textBounds[0].Rectangle.Width); + + textBounds = textLine.GetTextBounds(1, firstRun.Text.Length); + + Assert.Equal(1, textBounds.Count); + Assert.Equal(firstRun.Size.Width, textBounds[0].Rectangle.Width); + + textBounds = textLine.GetTextBounds(1, firstRun.Text.Length + 1); + + Assert.Equal(1, textBounds.Count); + Assert.Equal(firstRun.Size.Width + 14, textBounds[0].Rectangle.Width); } }