diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs index 061949a5c9..9318fcc68e 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs @@ -41,6 +41,156 @@ namespace Avalonia.Media.TextFormatting return textLine; } + /// + /// Measures the number of characters that fits into available width. + /// + /// The text run. + /// The available width. + /// + internal static int MeasureCharacters(ShapedTextCharacters textCharacters, double availableWidth) + { + var glyphRun = textCharacters.GlyphRun; + + if (glyphRun.Bounds.Width < availableWidth) + { + return glyphRun.Characters.Length; + } + + var glyphCount = 0; + + var currentWidth = 0.0; + + if (glyphRun.GlyphAdvances.IsEmpty) + { + var glyphTypeface = glyphRun.GlyphTypeface; + + for (var i = 0; i < glyphRun.GlyphClusters.Length; i++) + { + var glyph = glyphRun.GlyphIndices[i]; + + var advance = glyphTypeface.GetGlyphAdvance(glyph) * glyphRun.Scale; + + if (currentWidth + advance > availableWidth) + { + break; + } + + currentWidth += advance; + + glyphCount++; + } + } + else + { + foreach (var advance in glyphRun.GlyphAdvances) + { + if (currentWidth + advance > availableWidth) + { + break; + } + + currentWidth += advance; + + glyphCount++; + } + } + + if (glyphCount == glyphRun.GlyphIndices.Length) + { + return glyphRun.Characters.Length; + } + + if (glyphRun.GlyphClusters.IsEmpty) + { + return glyphCount; + } + + var firstCluster = glyphRun.GlyphClusters[0]; + + var lastCluster = glyphRun.GlyphClusters[glyphCount]; + + return lastCluster - firstCluster; + } + + /// + /// Split a sequence of runs into two segments at specified length. + /// + /// The text run's. + /// The length to split at. + /// The split text runs. + internal static SplitTextRunsResult SplitTextRuns(IReadOnlyList textRuns, int length) + { + var currentLength = 0; + + for (var i = 0; i < textRuns.Count; i++) + { + var currentRun = textRuns[i]; + + if (currentLength + currentRun.GlyphRun.Characters.Length < length) + { + currentLength += currentRun.GlyphRun.Characters.Length; + continue; + } + + var firstCount = currentRun.GlyphRun.Characters.Length >= 1 ? i + 1 : i; + + var first = new ShapedTextCharacters[firstCount]; + + if (firstCount > 1) + { + for (var j = 0; j < i; j++) + { + first[j] = textRuns[j]; + } + } + + var secondCount = textRuns.Count - firstCount; + + if (currentLength + currentRun.GlyphRun.Characters.Length == length) + { + var second = new ShapedTextCharacters[secondCount]; + + var offset = currentRun.GlyphRun.Characters.Length > 1 ? 1 : 0; + + if (secondCount > 0) + { + for (var j = 0; j < secondCount; j++) + { + second[j] = textRuns[i + j + offset]; + } + } + + first[i] = currentRun; + + return new SplitTextRunsResult(first, second); + } + else + { + secondCount++; + + var second = new ShapedTextCharacters[secondCount]; + + if (secondCount > 0) + { + for (var j = 1; j < secondCount; j++) + { + second[j] = textRuns[i + j]; + } + } + + var split = currentRun.Split(length - currentLength); + + first[i] = split.First; + + second[0] = split.Second; + + return new SplitTextRunsResult(first, second); + } + } + + return new SplitTextRunsResult(textRuns, null); + } + /// /// Fetches text runs. /// @@ -188,7 +338,7 @@ namespace Avalonia.Media.TextFormatting if (currentWidth + currentRun.GlyphRun.Bounds.Width > availableWidth) { - var measuredLength = MeasureText(currentRun, paragraphWidth - currentWidth); + var measuredLength = MeasureCharacters(currentRun, paragraphWidth - currentWidth); var breakFound = false; @@ -256,77 +406,6 @@ namespace Avalonia.Media.TextFormatting TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties)); } - /// - /// Measures the number of characters that fits into available width. - /// - /// The text run. - /// The available width. - /// - internal static int MeasureText(ShapedTextCharacters textCharacters, double availableWidth) - { - var glyphRun = textCharacters.GlyphRun; - - if (glyphRun.Bounds.Width < availableWidth) - { - return glyphRun.Characters.Length; - } - - var glyphCount = 0; - - var currentWidth = 0.0; - - if (glyphRun.GlyphAdvances.IsEmpty) - { - var glyphTypeface = glyphRun.GlyphTypeface; - - for (var i = 0; i < glyphRun.GlyphClusters.Length; i++) - { - var glyph = glyphRun.GlyphIndices[i]; - - var advance = glyphTypeface.GetGlyphAdvance(glyph) * glyphRun.Scale; - - if (currentWidth + advance > availableWidth) - { - break; - } - - currentWidth += advance; - - glyphCount++; - } - } - else - { - foreach (var advance in glyphRun.GlyphAdvances) - { - if (currentWidth + advance > availableWidth) - { - break; - } - - currentWidth += advance; - - glyphCount++; - } - } - - if (glyphCount == glyphRun.GlyphIndices.Length) - { - return glyphRun.Characters.Length; - } - - if (glyphRun.GlyphClusters.IsEmpty) - { - return glyphCount; - } - - var firstCluster = glyphRun.GlyphClusters[0]; - - var lastCluster = glyphRun.GlyphClusters[glyphCount]; - - return lastCluster - firstCluster; - } - /// /// Gets the text range that is covered by the text runs. /// @@ -353,85 +432,6 @@ namespace Avalonia.Media.TextFormatting return new TextRange(start, end - start); } - /// - /// Split a sequence of runs into two segments at specified length. - /// - /// The text run's. - /// The length to split at. - /// The split text runs. - internal static SplitTextRunsResult SplitTextRuns(IReadOnlyList textRuns, int length) - { - var currentLength = 0; - - for (var i = 0; i < textRuns.Count; i++) - { - var currentRun = textRuns[i]; - - if (currentLength + currentRun.GlyphRun.Characters.Length < length) - { - currentLength += currentRun.GlyphRun.Characters.Length; - continue; - } - - var firstCount = currentRun.GlyphRun.Characters.Length >= 1 ? i + 1 : i; - - var first = new ShapedTextCharacters[firstCount]; - - if (firstCount > 1) - { - for (var j = 0; j < i; j++) - { - first[j] = textRuns[j]; - } - } - - var secondCount = textRuns.Count - firstCount; - - if (currentLength + currentRun.GlyphRun.Characters.Length == length) - { - var second = new ShapedTextCharacters[secondCount]; - - var offset = currentRun.GlyphRun.Characters.Length > 1 ? 1 : 0; - - if (secondCount > 0) - { - for (var j = 0; j < secondCount; j++) - { - second[j] = textRuns[i + j + offset]; - } - } - - first[i] = currentRun; - - return new SplitTextRunsResult(first, second); - } - else - { - secondCount++; - - var second = new ShapedTextCharacters[secondCount]; - - if (secondCount > 0) - { - for (var j = 1; j < secondCount; j++) - { - second[j] = textRuns[i + j]; - } - } - - var split = currentRun.Split(length - currentLength); - - first[i] = split.First; - - second[0] = split.Second; - - return new SplitTextRunsResult(first, second); - } - } - - return new SplitTextRunsResult(textRuns, null); - } - internal readonly struct SplitTextRunsResult { public SplitTextRunsResult(IReadOnlyList first, IReadOnlyList second) diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs index 92db6b69c4..14602a2560 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs @@ -268,6 +268,11 @@ namespace Avalonia.Media.TextFormatting } } + /// + /// Gets the for current text trimming mode. + /// + /// The collapsing width. + /// The . private TextCollapsingProperties GetCollapsingProperties(double width) { return _textTrimming switch diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs index 3e3258f38a..423ca9fb7f 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs @@ -40,8 +40,11 @@ namespace Avalonia.Media.TextFormatting public abstract TextLineBreak LineBreak { get; } /// - /// Client to get a boolean value indicates whether a line has been collapsed + /// Gets a value that indicates whether the line is collapsed. /// + /// + /// true, if the line is collapsed; otherwise, false. + /// public abstract bool HasCollapsed { get; } /// @@ -52,46 +55,49 @@ namespace Avalonia.Media.TextFormatting public abstract void Draw(DrawingContext drawingContext, Point origin); /// - /// Client to collapse the line and get a collapsed line that fits for display + /// Create a collapsed line based on collapsed text properties. /// - /// a list of collapsing properties + /// A list of + /// objects that represent the collapsed text properties. + /// + /// A value that represents a collapsed line that can be displayed. + /// public abstract TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList); /// - /// Client to get the character hit corresponding to the specified - /// distance from the beginning of the line. + /// Gets the character hit corresponding to the specified distance from the beginning of the line. /// - /// distance in text flow direction from the beginning of the line - /// The + /// A value that represents the distance from the beginning of the line. + /// The object at the specified distance from the beginning of the line. public abstract CharacterHit GetCharacterHitFromDistance(double distance); /// - /// Client to get the distance from the beginning of the line from the specified + /// Gets the distance from the beginning of the line to the specified character hit. /// . /// - /// of the character to query the distance. - /// Distance in text flow direction from the beginning of the line. + /// The object whose distance you want to query. + /// A that represents the distance from the beginning of the line. public abstract double GetDistanceFromCharacterHit(CharacterHit characterHit); /// - /// Client to get the next for caret navigation. + /// Gets the next character hit for caret navigation. /// /// The current . /// The next . public abstract CharacterHit GetNextCaretCharacterHit(CharacterHit characterHit); /// - /// Client to get the previous character hit for caret navigation + /// Gets the previous character hit for caret navigation. /// - /// the current character hit - /// The previous + /// The current . + /// The previous . public abstract CharacterHit GetPreviousCaretCharacterHit(CharacterHit characterHit); /// - /// Client to get the previous character hit after backspacing + /// Gets the previous character hit after backspacing. /// - /// the current character hit - /// The after backspacing + /// The current . + /// The after backspacing. public abstract CharacterHit GetBackspaceCaretCharacterHit(CharacterHit characterHit); /// diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs index 820c943aea..980b1a2d40 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs @@ -47,6 +47,7 @@ namespace Avalonia.Media.TextFormatting } } + /// public override TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList) { if (collapsingPropertiesList == null || collapsingPropertiesList.Length == 0) @@ -73,7 +74,7 @@ namespace Avalonia.Media.TextFormatting if (currentWidth > availableWidth) { - var measuredLength = TextFormatterImpl.MeasureText(currentRun, availableWidth); + var measuredLength = TextFormatterImpl.MeasureCharacters(currentRun, availableWidth); var currentBreakPosition = 0; diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextRunProperties.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextRunProperties.cs index bbcdfe2d8e..c4f9443c3d 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextRunProperties.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextRunProperties.cs @@ -4,12 +4,11 @@ using System.Globalization; namespace Avalonia.Media.TextFormatting { /// - /// Properties that can change from one run to the next, such as typeface or foreground brush. + /// Provides a set of properties, such as typeface or foreground brush, that can be applied to a TextRun object. This is an abstract class. /// /// - /// The client provides a concrete implementation of this abstract run properties class. This - /// allows client to implement their run properties the way that fits with their run formatting - /// store. + /// The text layout client provides a concrete implementation of this abstract class. + /// This enables the client to implement text run properties in a way that corresponds with the associated formatting store. /// public abstract class TextRunProperties : IEquatable {