| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186 |
- using Avalonia.Media.TextFormatting.Unicode;
- using Avalonia.Utility;
- namespace Avalonia.Media.TextFormatting
- {
- /// <summary>
- /// Represents a base class for text formatting.
- /// </summary>
- public abstract class TextFormatter
- {
- /// <summary>
- /// Gets the current <see cref="TextFormatter"/> that is used for non complex text formatting.
- /// </summary>
- public static TextFormatter Current
- {
- get
- {
- var current = AvaloniaLocator.Current.GetService<TextFormatter>();
- if (current != null)
- {
- return current;
- }
- current = new SimpleTextFormatter();
- AvaloniaLocator.CurrentMutable.Bind<TextFormatter>().ToConstant(current);
- return current;
- }
- }
- /// <summary>
- /// Formats a text line.
- /// </summary>
- /// <param name="textSource">The text source.</param>
- /// <param name="firstTextSourceIndex">The first character index to start the text line from.</param>
- /// <param name="paragraphWidth">A <see cref="double"/> value that specifies the width of the paragraph that the line fills.</param>
- /// <param name="paragraphProperties">A <see cref="TextParagraphProperties"/> value that represents paragraph properties,
- /// such as TextWrapping, TextAlignment, or TextStyle.</param>
- /// <returns>The formatted line.</returns>
- public abstract TextLine FormatLine(ITextSource textSource, int firstTextSourceIndex, double paragraphWidth,
- TextParagraphProperties paragraphProperties);
- /// <summary>
- /// Creates a text style run with unique properties.
- /// </summary>
- /// <param name="text">The text to create text runs from.</param>
- /// <param name="defaultStyle"></param>
- /// <returns>A list of text runs.</returns>
- protected TextStyleRun CreateShapableTextStyleRun(ReadOnlySlice<char> text, TextStyle defaultStyle)
- {
- var defaultTypeface = defaultStyle.TextFormat.Typeface;
- var currentTypeface = defaultTypeface;
- if (TryGetRunProperties(text, currentTypeface, defaultTypeface, out var count))
- {
- return new TextStyleRun(new TextPointer(text.Start, count), new TextStyle(currentTypeface,
- defaultStyle.TextFormat.FontRenderingEmSize,
- defaultStyle.Foreground, defaultStyle.TextDecorations));
- }
- var codepoint = Codepoint.ReadAt(text, count, out _);
- //ToDo: Fix FontFamily fallback
- currentTypeface =
- FontManager.Current.MatchCharacter(codepoint, defaultTypeface.Weight, defaultTypeface.Style);
- if (currentTypeface != null && TryGetRunProperties(text, currentTypeface, defaultTypeface, out count))
- {
- //Fallback found
- return new TextStyleRun(new TextPointer(text.Start, count), new TextStyle(currentTypeface,
- defaultStyle.TextFormat.FontRenderingEmSize,
- defaultStyle.Foreground, defaultStyle.TextDecorations));
- }
- // 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 TextStyleRun(new TextPointer(text.Start, count),
- new TextStyle(currentTypeface, defaultStyle.TextFormat.FontRenderingEmSize,
- defaultStyle.Foreground, defaultStyle.TextDecorations));
- }
- /// <summary>
- /// Tries to get run properties.
- /// </summary>
- /// <param name="defaultTypeface"></param>
- /// <param name="text"></param>
- /// <param name="typeface">The typeface that is used to find matching characters.</param>
- /// <param name="count"></param>
- /// <returns></returns>
- protected bool TryGetRunProperties(ReadOnlySlice<char> 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;
- }
- }
- }
|