using System.Collections.Generic; using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Utilities; namespace Avalonia.Media.TextFormatting { /// /// A text run that holds text characters. /// public class TextCharacters : TextRun { public TextCharacters(ReadOnlySlice text, TextRunProperties properties) { TextSourceLength = text.Length; Text = text; Properties = properties; } /// public override int TextSourceLength { get; } /// public override ReadOnlySlice Text { get; } /// public override TextRunProperties Properties { get; } /// /// Gets a list of . /// /// The shapeable text characters. internal IList GetShapeableCharacters() { var shapeableCharacters = new List(2); var runText = Text; while (!runText.IsEmpty) { var shapeableRun = CreateShapeableRun(runText, Properties); shapeableCharacters.Add(shapeableRun); runText = runText.Skip(shapeableRun.Text.Length); } return shapeableCharacters; } /// /// Creates a shapeable text run with unique properties. /// /// The text to create text runs from. /// The default text run properties. /// A list of shapeable text runs. private ShapeableTextCharacters CreateShapeableRun(ReadOnlySlice text, TextRunProperties defaultProperties) { var defaultTypeface = defaultProperties.Typeface; var currentTypeface = defaultTypeface; if (TryGetRunProperties(text, currentTypeface, defaultTypeface, out var count)) { return new ShapeableTextCharacters(text.Take(count), new GenericTextRunProperties(currentTypeface, defaultProperties.FontRenderingEmSize, defaultProperties.TextDecorations, defaultProperties.ForegroundBrush)); } var codepoint = Codepoint.ReadAt(text, count, out _); //ToDo: Fix FontFamily fallback currentTypeface = FontManager.Current.MatchCharacter(codepoint, defaultTypeface.Weight, defaultTypeface.Style, defaultTypeface.FontFamily); if (currentTypeface != null && TryGetRunProperties(text, currentTypeface, defaultTypeface, out count)) { //Fallback found return new ShapeableTextCharacters(text.Take(count), new GenericTextRunProperties(currentTypeface, defaultProperties.FontRenderingEmSize, defaultProperties.TextDecorations, defaultProperties.ForegroundBrush)); } // no fallback found currentTypeface = defaultTypeface; var glyphTypeface = currentTypeface.GlyphTypeface; var enumerator = new GraphemeEnumerator(text); while (enumerator.MoveNext()) { var grapheme = enumerator.Current; if (!grapheme.FirstCodepoint.IsWhiteSpace && glyphTypeface.TryGetGlyph(grapheme.FirstCodepoint, out _)) { break; } count += grapheme.Text.Length; } return new ShapeableTextCharacters(text.Take(count), new GenericTextRunProperties(currentTypeface, defaultProperties.FontRenderingEmSize, defaultProperties.TextDecorations, defaultProperties.ForegroundBrush)); } /// /// Tries to get run properties. /// /// /// /// The typeface that is used to find matching characters. /// /// protected bool TryGetRunProperties(ReadOnlySlice text, Typeface typeface, Typeface defaultTypeface, out int count) { if (text.Length == 0) { count = 0; return false; } var isFallback = typeface != defaultTypeface; count = 0; var script = Script.Common; //var direction = BiDiClass.LeftToRight; var font = typeface.GlyphTypeface; var defaultFont = defaultTypeface.GlyphTypeface; var enumerator = new GraphemeEnumerator(text); while (enumerator.MoveNext()) { var grapheme = enumerator.Current; var currentScript = grapheme.FirstCodepoint.Script; //var currentDirection = grapheme.FirstCodepoint.BiDiClass; //// ToDo: Implement BiDi algorithm //if (currentScript.HorizontalDirection != direction) //{ // if (!UnicodeUtility.IsWhiteSpace(grapheme.FirstCodepoint)) // { // break; // } //} if (currentScript != script) { if (currentScript != Script.Inherited && currentScript != Script.Common) { if (script == Script.Inherited || script == Script.Common) { script = currentScript; } else { break; } } } if (isFallback) { if (defaultFont.TryGetGlyph(grapheme.FirstCodepoint, out _)) { break; } } if (!font.TryGetGlyph(grapheme.FirstCodepoint, out _)) { if (!grapheme.FirstCodepoint.IsWhiteSpace) { break; } } count += grapheme.Text.Length; } return count > 0; } } }