diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs index 6433a37b22..23a1e4a275 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs @@ -102,7 +102,7 @@ namespace Avalonia.Media.TextFormatting.Unicode (1UL << (int)GeneralCategory.SpaceSeparator) | (1UL << (int)GeneralCategory.SpacingMark); - return ((1UL << (int)GeneralCategory) & whiteSpaceMask) != 0L; + return ((1UL << (int)GeneralCategory) & whiteSpaceMask) != 0UL; } } diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/LineBreakEnumerator.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/LineBreakEnumerator.cs index 877ab76ce5..31ef47f47b 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/LineBreakEnumerator.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/LineBreakEnumerator.cs @@ -3,6 +3,7 @@ // Ported from: https://github.com/SixLabors/Fonts/ using System; +using System.Runtime.CompilerServices; namespace Avalonia.Media.TextFormatting.Unicode { @@ -118,13 +119,14 @@ namespace Avalonia.Media.TextFormatting.Unicode return false; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static LineBreakClass MapClass(Codepoint cp) { if (cp.Value == 327685) { return LineBreakClass.Alphabetic; } - + // LB 1 // ========================================== // Resolved Original General_Category @@ -133,26 +135,38 @@ namespace Avalonia.Media.TextFormatting.Unicode // CM SA Only Mn or Mc // AL SA Any except Mn and Mc // NS CJ Any - switch (cp.LineBreakClass) - { - case LineBreakClass.Ambiguous: - case LineBreakClass.Surrogate: - case LineBreakClass.Unknown: - return LineBreakClass.Alphabetic; + var cls = cp.LineBreakClass; - case LineBreakClass.ComplexContext: - return cp.GeneralCategory == GeneralCategory.NonspacingMark || cp.GeneralCategory == GeneralCategory.SpacingMark - ? LineBreakClass.CombiningMark - : LineBreakClass.Alphabetic; + const ulong specialMask = + (1UL << (int)LineBreakClass.Ambiguous) | + (1UL << (int)LineBreakClass.Surrogate) | + (1UL << (int)LineBreakClass.Unknown) | + (1UL << (int)LineBreakClass.ComplexContext) | + (1UL << (int)LineBreakClass.ConditionalJapaneseStarter); - case LineBreakClass.ConditionalJapaneseStarter: - return LineBreakClass.Nonstarter; - - default: - return cp.LineBreakClass; + if (((1UL << (int)cls) & specialMask) != 0UL) + { + switch (cls) + { + case LineBreakClass.Ambiguous: + case LineBreakClass.Surrogate: + case LineBreakClass.Unknown: + return LineBreakClass.Alphabetic; + + case LineBreakClass.ComplexContext: + return cp.GeneralCategory is GeneralCategory.NonspacingMark or GeneralCategory.SpacingMark + ? LineBreakClass.CombiningMark + : LineBreakClass.Alphabetic; + + case LineBreakClass.ConditionalJapaneseStarter: + return LineBreakClass.Nonstarter; + } } + + return cls; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static LineBreakClass MapFirst(LineBreakClass c) { switch (c) @@ -169,10 +183,80 @@ namespace Avalonia.Media.TextFormatting.Unicode } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsAlphaNumeric(LineBreakClass cls) - => cls == LineBreakClass.Alphabetic - || cls == LineBreakClass.HebrewLetter - || cls == LineBreakClass.Numeric; + { + const ulong mask = + (1UL << (int)LineBreakClass.Alphabetic) | + (1UL << (int)LineBreakClass.HebrewLetter) | + (1UL << (int)LineBreakClass.Numeric); + + return ((1UL << (int)cls) & mask) != 0UL; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsPrefixPostfixNumericOrSpace(LineBreakClass cls) + { + const ulong mask = + (1UL << (int)LineBreakClass.PostfixNumeric) | + (1UL << (int)LineBreakClass.PrefixNumeric) | + (1UL << (int)LineBreakClass.Space); + + return ((1UL << (int)cls) & mask) != 0UL; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsPrefixPostfixNumeric(LineBreakClass cls) + { + const ulong mask = + (1UL << (int)LineBreakClass.PostfixNumeric) | + (1UL << (int)LineBreakClass.PrefixNumeric); + + return ((1UL << (int)cls) & mask) != 0UL; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsClosePunctuationOrParenthesis(LineBreakClass cls) + { + const ulong mask = + (1UL << (int)LineBreakClass.ClosePunctuation) | + (1UL << (int)LineBreakClass.CloseParenthesis); + + return ((1UL << (int)cls) & mask) != 0UL; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsClosePunctuationOrInfixNumericOrBreakSymbols(LineBreakClass cls) + { + const ulong mask = + (1UL << (int)LineBreakClass.ClosePunctuation) | + (1UL << (int)LineBreakClass.InfixNumeric) | + (1UL << (int)LineBreakClass.BreakSymbols); + + return ((1UL << (int)cls) & mask) != 0UL; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSpaceOrWordJoinerOrAlphabetic(LineBreakClass cls) + { + const ulong mask = + (1UL << (int)LineBreakClass.Space) | + (1UL << (int)LineBreakClass.WordJoiner) | + (1UL << (int)LineBreakClass.Alphabetic); + + return ((1UL << (int)cls) & mask) != 0UL; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsMandatoryBreakOrLineFeedOrCarriageReturn(LineBreakClass cls) + { + const ulong mask = + (1UL << (int)LineBreakClass.MandatoryBreak) | + (1UL << (int)LineBreakClass.LineFeed) | + (1UL << (int)LineBreakClass.CarriageReturn); + + return ((1UL << (int)cls) & mask) != 0UL; + } private LineBreakClass PeekNextCharClass() { @@ -198,83 +282,77 @@ namespace Avalonia.Media.TextFormatting.Unicode // Track combining mark exceptions. LB22 if (cls == LineBreakClass.CombiningMark) { - switch (_currentClass) + const ulong lb22ExMask = + (1UL << (int)LineBreakClass.MandatoryBreak) | + (1UL << (int)LineBreakClass.ContingentBreak) | + (1UL << (int)LineBreakClass.Exclamation) | + (1UL << (int)LineBreakClass.LineFeed) | + (1UL << (int)LineBreakClass.NextLine) | + (1UL << (int)LineBreakClass.Space) | + (1UL << (int)LineBreakClass.ZWSpace) | + (1UL << (int)LineBreakClass.CarriageReturn); + + if (((1UL << (int)_currentClass) & lb22ExMask) != 0UL) { - case LineBreakClass.MandatoryBreak: - case LineBreakClass.ContingentBreak: - case LineBreakClass.Exclamation: - case LineBreakClass.LineFeed: - case LineBreakClass.NextLine: - case LineBreakClass.Space: - case LineBreakClass.ZWSpace: - case LineBreakClass.CarriageReturn: - _lb22ex = true; - break; + _lb22ex = true; } - } - // Track combining mark exceptions. LB31 - if (_first && cls == LineBreakClass.CombiningMark) - { - _lb31 = true; + const ulong lb31Mask = + (1UL << (int)LineBreakClass.MandatoryBreak) | + (1UL << (int)LineBreakClass.ContingentBreak) | + (1UL << (int)LineBreakClass.Exclamation) | + (1UL << (int)LineBreakClass.LineFeed) | + (1UL << (int)LineBreakClass.NextLine) | + (1UL << (int)LineBreakClass.Space) | + (1UL << (int)LineBreakClass.ZWSpace) | + (1UL << (int)LineBreakClass.CarriageReturn) | + (1UL << (int)LineBreakClass.ZWJ); + + // Track combining mark exceptions. LB31 + if (_first || ((1UL << (int)_currentClass) & lb31Mask) != 0UL) + { + _lb31 = true; + } } - if (cls == LineBreakClass.CombiningMark) + if (_first) { - switch (_currentClass) + // Rule LB24 + if (IsClosePunctuationOrParenthesis(cls)) { - case LineBreakClass.MandatoryBreak: - case LineBreakClass.ContingentBreak: - case LineBreakClass.Exclamation: - case LineBreakClass.LineFeed: - case LineBreakClass.NextLine: - case LineBreakClass.Space: - case LineBreakClass.ZWSpace: - case LineBreakClass.CarriageReturn: - case LineBreakClass.ZWJ: - _lb31 = true; - break; + _lb24ex = true; } - } - if (_first - && (cls == LineBreakClass.PostfixNumeric || cls == LineBreakClass.PrefixNumeric || cls == LineBreakClass.Space)) - { - _lb31 = true; + // Rule LB25 + if (IsClosePunctuationOrInfixNumericOrBreakSymbols(cls)) + { + _lb25ex = true; + } + + if (IsPrefixPostfixNumericOrSpace(cls)) + { + _lb31 = true; + } } - if (_currentClass == LineBreakClass.Alphabetic && - (cls == LineBreakClass.PostfixNumeric || cls == LineBreakClass.PrefixNumeric || cls == LineBreakClass.Space)) + if (_currentClass == LineBreakClass.Alphabetic && IsPrefixPostfixNumericOrSpace(cls)) { _lb31 = true; } // Reset LB31 if next is U+0028 (Left Opening Parenthesis) if (_lb31 - && _currentClass != LineBreakClass.PostfixNumeric - && _currentClass != LineBreakClass.PrefixNumeric - && cls == LineBreakClass.OpenPunctuation && cp.Value == 0x0028) + && !IsPrefixPostfixNumeric(_currentClass) + && cls == LineBreakClass.OpenPunctuation + && cp.Value == 0x0028) { _lb31 = false; } - // Rule LB24 - if (_first && (cls == LineBreakClass.ClosePunctuation || cls == LineBreakClass.CloseParenthesis)) - { - _lb24ex = true; - } - - // Rule LB25 - if (_first - && (cls == LineBreakClass.ClosePunctuation || cls == LineBreakClass.InfixNumeric || cls == LineBreakClass.BreakSymbols)) - { - _lb25ex = true; - } - - if (cls == LineBreakClass.Space || cls == LineBreakClass.WordJoiner || cls == LineBreakClass.Alphabetic) + if (IsSpaceOrWordJoinerOrAlphabetic(cls)) { var next = PeekNextCharClass(); - if (next == LineBreakClass.ClosePunctuation || next == LineBreakClass.InfixNumeric || next == LineBreakClass.BreakSymbols) + if (IsClosePunctuationOrInfixNumericOrBreakSymbols(next)) { _lb25ex = true; } @@ -295,6 +373,7 @@ namespace Avalonia.Media.TextFormatting.Unicode return cls; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool? GetSimpleBreak() { // handle classes not handled by the pair table @@ -317,6 +396,7 @@ namespace Avalonia.Media.TextFormatting.Unicode return null; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] // quite long but only one usage private bool GetPairTableBreak(LineBreakClass lastClass) { // If not handled already, use the pair table @@ -477,8 +557,7 @@ namespace Avalonia.Media.TextFormatting.Unicode var cls = cp.LineBreakClass; - if (cls == LineBreakClass.MandatoryBreak || cls == LineBreakClass.LineFeed || - cls == LineBreakClass.CarriageReturn) + if (IsMandatoryBreakOrLineFeedOrCarriageReturn(cls)) { from -= count; }