|
|
|
@ -1,4 +1,5 @@ |
|
|
|
using System.Collections.Generic; |
|
|
|
using System; |
|
|
|
using System.Collections.Generic; |
|
|
|
using Avalonia.Media.TextFormatting.Unicode; |
|
|
|
using Avalonia.Utilities; |
|
|
|
|
|
|
|
@ -37,17 +38,20 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
/// Gets a list of <see cref="ShapeableTextCharacters"/>.
|
|
|
|
/// </summary>
|
|
|
|
/// <returns>The shapeable text characters.</returns>
|
|
|
|
internal IList<ShapeableTextCharacters> GetShapeableCharacters(ReadOnlySlice<char> runText, sbyte biDiLevel) |
|
|
|
internal IList<ShapeableTextCharacters> GetShapeableCharacters(ReadOnlySlice<char> runText, sbyte biDiLevel, |
|
|
|
ref TextRunProperties? previousProperties) |
|
|
|
{ |
|
|
|
var shapeableCharacters = new List<ShapeableTextCharacters>(2); |
|
|
|
|
|
|
|
while (!runText.IsEmpty) |
|
|
|
{ |
|
|
|
var shapeableRun = CreateShapeableRun(runText, Properties, biDiLevel); |
|
|
|
var shapeableRun = CreateShapeableRun(runText, Properties, biDiLevel, ref previousProperties); |
|
|
|
|
|
|
|
shapeableCharacters.Add(shapeableRun); |
|
|
|
|
|
|
|
runText = runText.Skip(shapeableRun.Text.Length); |
|
|
|
|
|
|
|
previousProperties = shapeableRun.Properties; |
|
|
|
} |
|
|
|
|
|
|
|
return shapeableCharacters; |
|
|
|
@ -59,15 +63,29 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
/// <param name="text">The text to create text runs from.</param>
|
|
|
|
/// <param name="defaultProperties">The default text run properties.</param>
|
|
|
|
/// <param name="biDiLevel">The bidi level of the run.</param>
|
|
|
|
/// <param name="previousProperties"></param>
|
|
|
|
/// <returns>A list of shapeable text runs.</returns>
|
|
|
|
private ShapeableTextCharacters CreateShapeableRun(ReadOnlySlice<char> text, TextRunProperties defaultProperties, sbyte biDiLevel) |
|
|
|
private static ShapeableTextCharacters CreateShapeableRun(ReadOnlySlice<char> text, |
|
|
|
TextRunProperties defaultProperties, sbyte biDiLevel, ref TextRunProperties? previousProperties) |
|
|
|
{ |
|
|
|
var defaultTypeface = defaultProperties.Typeface; |
|
|
|
|
|
|
|
var currentTypeface = defaultTypeface; |
|
|
|
|
|
|
|
if (TryGetShapeableLength(text, currentTypeface, defaultTypeface, out var count)) |
|
|
|
if (TryGetShapeableLength(text, currentTypeface, out var count, out var script)) |
|
|
|
{ |
|
|
|
var previousTypeface = previousProperties?.Typeface; |
|
|
|
|
|
|
|
if (script == Script.Common && previousTypeface is not null) |
|
|
|
{ |
|
|
|
if(TryGetShapeableLength(text, previousTypeface.Value, out var fallbackCount, out _)) |
|
|
|
{ |
|
|
|
return new ShapeableTextCharacters(text.Take(fallbackCount), |
|
|
|
new GenericTextRunProperties(previousTypeface.Value, defaultProperties.FontRenderingEmSize, |
|
|
|
defaultProperties.TextDecorations, defaultProperties.ForegroundBrush), biDiLevel); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return new ShapeableTextCharacters(text.Take(count), |
|
|
|
new GenericTextRunProperties(currentTypeface, defaultProperties.FontRenderingEmSize, |
|
|
|
defaultProperties.TextDecorations, defaultProperties.ForegroundBrush), biDiLevel); |
|
|
|
@ -94,7 +112,7 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
FontManager.Current.TryMatchCharacter(codepoint, defaultTypeface.Style, defaultTypeface.Weight, |
|
|
|
defaultTypeface.FontFamily, defaultProperties.CultureInfo, out currentTypeface); |
|
|
|
|
|
|
|
if (matchFound && TextCharacters.TryGetShapeableLength(text, currentTypeface, defaultTypeface, out count)) |
|
|
|
if (matchFound && TryGetShapeableLength(text, currentTypeface, out count, out _)) |
|
|
|
{ |
|
|
|
//Fallback found
|
|
|
|
return new ShapeableTextCharacters(text.Take(count), |
|
|
|
@ -127,30 +145,26 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Tries to get run properties.
|
|
|
|
/// Tries to get a shapeable length that is supported by the specified typeface.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="defaultTypeface"></param>
|
|
|
|
/// <param name="text"></param>
|
|
|
|
/// <param name="text">The text.</param>
|
|
|
|
/// <param name="typeface">The typeface that is used to find matching characters.</param>
|
|
|
|
/// <param name="length"></param>
|
|
|
|
/// <param name="length">The shapeable length.</param>
|
|
|
|
/// <param name="script"></param>
|
|
|
|
/// <returns></returns>
|
|
|
|
protected static bool TryGetShapeableLength(ReadOnlySlice<char> text, Typeface typeface, Typeface defaultTypeface, |
|
|
|
out int length) |
|
|
|
protected static bool TryGetShapeableLength(ReadOnlySlice<char> text, Typeface typeface, out int length, |
|
|
|
out Script script) |
|
|
|
{ |
|
|
|
length = 0; |
|
|
|
script = Script.Unknown; |
|
|
|
|
|
|
|
if (text.Length == 0) |
|
|
|
{ |
|
|
|
length = 0; |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
var isFallback = typeface != defaultTypeface; |
|
|
|
|
|
|
|
length = 0; |
|
|
|
var script = Script.Unknown; |
|
|
|
|
|
|
|
var font = typeface.GlyphTypeface; |
|
|
|
var defaultFont = defaultTypeface.GlyphTypeface; |
|
|
|
|
|
|
|
|
|
|
|
var enumerator = new GraphemeEnumerator(text); |
|
|
|
|
|
|
|
while (enumerator.MoveNext()) |
|
|
|
@ -161,7 +175,8 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
|
|
|
|
if (currentScript != script) |
|
|
|
{ |
|
|
|
if (script is Script.Unknown || currentScript != Script.Common && (script is Script.Common || script is Script.Inherited)) |
|
|
|
if (script is Script.Unknown || currentScript != Script.Common && |
|
|
|
(script is Script.Common || script is Script.Inherited)) |
|
|
|
{ |
|
|
|
script = currentScript; |
|
|
|
} |
|
|
|
@ -174,23 +189,8 @@ namespace Avalonia.Media.TextFormatting |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
//Only handle non whitespace here
|
|
|
|
if(!currentGrapheme.FirstCodepoint.IsWhiteSpace) |
|
|
|
{ |
|
|
|
//Stop at the first glyph that is present in the default typeface.
|
|
|
|
if (isFallback && defaultFont.TryGetGlyph(currentGrapheme.FirstCodepoint, out _)) |
|
|
|
{ |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
//Stop at the first missing glyph
|
|
|
|
if (!font.TryGetGlyph(currentGrapheme.FirstCodepoint, out _)) |
|
|
|
{ |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (!currentGrapheme.FirstCodepoint.IsWhiteSpace && !font.TryGetGlyph(currentGrapheme.FirstCodepoint, out _)) |
|
|
|
//Stop at the first missing glyph
|
|
|
|
if (!font.TryGetGlyph(currentGrapheme.FirstCodepoint, out _)) |
|
|
|
{ |
|
|
|
break; |
|
|
|
} |
|
|
|
|