Browse Source

Perf: improved LineBreakEnumerator by using less branches

pull/10047/head
Julien Lebosquain 4 years ago
parent
commit
900299b6a8
  1. 2
      src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs
  2. 229
      src/Avalonia.Base/Media/TextFormatting/Unicode/LineBreakEnumerator.cs

2
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;
}
}

229
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;
}

Loading…
Cancel
Save