Skip to content

Commit

Permalink
Merge pull request #382 from SixLabors/js/low-hanging-fruit
Browse files Browse the repository at this point in the history
Optimize low hanging fruit
  • Loading branch information
JimBobSquarePants authored Feb 15, 2024
2 parents cda8d06 + 83d24b7 commit 18dfeb4
Show file tree
Hide file tree
Showing 13 changed files with 237 additions and 76 deletions.
2 changes: 1 addition & 1 deletion src/SixLabors.Fonts/FileFontMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ internal FileFontMetrics(FontDescription description, string path, long offset)
{
this.Description = description;
this.Path = path;
this.fontMetrics = new Lazy<StreamFontMetrics>(() => StreamFontMetrics.LoadFont(path, offset));
this.fontMetrics = new Lazy<StreamFontMetrics>(() => StreamFontMetrics.LoadFont(path, offset), true);
}

/// <inheritdoc cref="FontMetrics.Description"/>
Expand Down
9 changes: 5 additions & 4 deletions src/SixLabors.Fonts/Font.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ public Font(FontFamily family, float size, FontStyle style)
this.Family = family;
this.RequestedStyle = style;
this.Size = size;
this.metrics = new Lazy<FontMetrics?>(this.LoadInstanceInternal);
this.fontName = new Lazy<string>(this.LoadFontName);
this.metrics = new Lazy<FontMetrics?>(this.LoadInstanceInternal, true);
this.fontName = new Lazy<string>(this.LoadFontName, true);
}

/// <summary>
Expand Down Expand Up @@ -95,6 +95,7 @@ public Font(Font prototype, float size)
/// <summary>
/// Gets the font metrics.
/// </summary>
/// <exception cref="FontException">Font instance not found.</exception>
public FontMetrics FontMetrics => this.metrics.Value ?? throw new FontException("Font instance not found.");

/// <summary>
Expand Down Expand Up @@ -194,7 +195,7 @@ public bool TryGetGlyphs(
/// </summary>
/// <param name="codePoint">The code point of the character.</param>
/// <param name="textAttributes">The text attributes to apply to the glyphs.</param>
/// <param name="layoutMode">The layout mode to apply to thte glyphs.</param>
/// <param name="layoutMode">The layout mode to apply to the glyphs.</param>
/// <param name="support">Options for enabling color font support during layout and rendering.</param>
/// <param name="glyphs">
/// When this method returns, contains the glyphs for the given codepoint, attributes, and color support if the glyphs
Expand All @@ -217,7 +218,7 @@ public bool TryGetGlyphs(
/// <param name="codePoint">The code point of the character.</param>
/// <param name="textAttributes">The text attributes to apply to the glyphs.</param>
/// <param name="textDecorations">The text decorations to apply to the glyphs.</param>
/// <param name="layoutMode">The layout mode to apply to thte glyphs.</param>
/// <param name="layoutMode">The layout mode to apply to the glyphs.</param>
/// <param name="support">Options for enabling color font support during layout and rendering.</param>
/// <param name="glyphs">
/// When this method returns, contains the glyphs for the given codepoint, attributes, and color support if the glyphs
Expand Down
2 changes: 1 addition & 1 deletion src/SixLabors.Fonts/StreamFontMetrics.Cff.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ private static StreamFontMetrics LoadCompactFont(FontReader reader)
}

private GlyphMetrics CreateCffGlyphMetrics(
CodePoint codePoint,
in CodePoint codePoint,
ushort glyphId,
GlyphType glyphType,
TextAttributes textAttributes,
Expand Down
2 changes: 1 addition & 1 deletion src/SixLabors.Fonts/StreamFontMetrics.TrueType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ private static StreamFontMetrics LoadTrueTypeFont(FontReader reader)
}

private GlyphMetrics CreateTrueTypeGlyphMetrics(
CodePoint codePoint,
in CodePoint codePoint,
ushort glyphId,
GlyphType glyphType,
TextAttributes textAttributes,
Expand Down
82 changes: 46 additions & 36 deletions src/SixLabors.Fonts/StreamFontMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ internal partial class StreamFontMetrics : FontMetrics
// https://docs.microsoft.com/en-us/typography/opentype/spec/otff#font-tables
private readonly ConcurrentDictionary<(int CodePoint, ushort Id, TextAttributes Attributes, bool IsVerticalLayout), GlyphMetrics[]> glyphCache;
private readonly ConcurrentDictionary<(int CodePoint, ushort Id, TextAttributes Attributes, bool IsVerticalLayout), GlyphMetrics[]>? colorGlyphCache;
private readonly ConcurrentDictionary<(int CodePoint, int NextCodePoint), (bool Success, ushort GlyphId, bool SkipNextCodePoint)> glyphIdCache;
private readonly FontDescription description;
private readonly HorizontalMetrics horizontalMetrics;
private readonly VerticalMetrics verticalMetrics;
Expand Down Expand Up @@ -59,6 +60,7 @@ internal StreamFontMetrics(TrueTypeFontTables tables)
this.trueTypeFontTables = tables;
this.outlineType = OutlineType.TrueType;
this.description = new FontDescription(tables.Name, tables.Os2, tables.Head);
this.glyphIdCache = new();
this.glyphCache = new();
if (tables.Colr is not null)
{
Expand All @@ -79,6 +81,7 @@ internal StreamFontMetrics(CompactFontTables tables)
this.compactFontTables = tables;
this.outlineType = OutlineType.CFF;
this.description = new FontDescription(tables.Name, tables.Os2, tables.Head);
this.glyphIdCache = new();
this.glyphCache = new();
if (tables.Colr is not null)
{
Expand Down Expand Up @@ -157,7 +160,18 @@ internal override bool TryGetGlyphId(CodePoint codePoint, CodePoint? nextCodePoi
? this.trueTypeFontTables!.Cmap
: this.compactFontTables!.Cmap;

return cmap.TryGetGlyphId(codePoint, nextCodePoint, out glyphId, out skipNextCodePoint);
(bool success, ushort id, bool skip) = this.glyphIdCache.GetOrAdd(
(codePoint.Value, nextCodePoint?.Value ?? -1),
static (_, arg) =>
{
bool success = arg.cmap.TryGetGlyphId(arg.codePoint, arg.nextCodePoint, out ushort id, out bool skip);
return (success, id, skip);
},
(cmap, codePoint, nextCodePoint));

glyphId = id;
skipNextCodePoint = skip;
return success;
}

/// <inheritdoc/>
Expand Down Expand Up @@ -206,14 +220,6 @@ internal override IReadOnlyList<GlyphMetrics> GetGlyphMetrics(
LayoutMode layoutMode,
ColorFontSupport support)
{
GlyphType glyphType = GlyphType.Standard;
if (glyphId == 0)
{
// A glyph was not found in this face for the previously matched
// codepoint. Set to fallback.
glyphType = GlyphType.Fallback;
}

if (support == ColorFontSupport.MicrosoftColrFormat
&& this.TryGetColoredMetrics(codePoint, glyphId, textAttributes, textDecorations, layoutMode, out GlyphMetrics[]? metrics))
{
Expand All @@ -222,17 +228,18 @@ internal override IReadOnlyList<GlyphMetrics> GetGlyphMetrics(

// We overwrite the cache entry for this type should the attributes change.
return this.glyphCache.GetOrAdd(
CreateCacheKey(codePoint, glyphId, textAttributes, layoutMode),
key => new[]
{
this.CreateGlyphMetrics(
codePoint,
key.Id,
glyphType,
key.Attributes,
textDecorations,
key.IsVerticalLayout)
});
CreateCacheKey(in codePoint, glyphId, textAttributes, layoutMode),
static (key, arg) => new[]
{
arg.Item3.CreateGlyphMetrics(
in arg.codePoint,
key.Id,
key.Id == 0 ? GlyphType.Fallback : GlyphType.Standard,
key.Attributes,
arg.textDecorations,
key.IsVerticalLayout)
},
(textDecorations, codePoint, this));
}

/// <inheritdoc />
Expand Down Expand Up @@ -530,7 +537,7 @@ public static StreamFontMetrics[] LoadFontCollection(Stream stream)
}

private static (int CodePoint, ushort Id, TextAttributes Attributes, bool IsVerticalLayout) CreateCacheKey(
CodePoint codePoint,
in CodePoint codePoint,
ushort glyphId,
TextAttributes textAttributes,
LayoutMode layoutMode)
Expand All @@ -555,28 +562,31 @@ private bool TryGetColoredMetrics(
}

// We overwrite the cache entry for this type should the attributes change.
metrics = this.colorGlyphCache.GetOrAdd(CreateCacheKey(codePoint, glyphId, textAttributes, layoutMode), key =>
{
GlyphMetrics[] m = Array.Empty<GlyphMetrics>();
Span<LayerRecord> indexes = colr.GetLayers(key.Id);
if (indexes.Length > 0)
metrics = this.colorGlyphCache.GetOrAdd(
CreateCacheKey(in codePoint, glyphId, textAttributes, layoutMode),
(key, args) =>
{
m = new GlyphMetrics[indexes.Length];
for (int i = 0; i < indexes.Length; i++)
GlyphMetrics[] m = Array.Empty<GlyphMetrics>();
Span<LayerRecord> indexes = colr.GetLayers(key.Id);
if (indexes.Length > 0)
{
LayerRecord layer = indexes[i];
m[i] = this.CreateGlyphMetrics(codePoint, layer.GlyphId, GlyphType.ColrLayer, key.Attributes, textDecorations, key.IsVerticalLayout, layer.PaletteIndex);
m = new GlyphMetrics[indexes.Length];
for (int i = 0; i < indexes.Length; i++)
{
LayerRecord layer = indexes[i];
m[i] = args.Item2.CreateGlyphMetrics(in args.codePoint, layer.GlyphId, GlyphType.ColrLayer, key.Attributes, textDecorations, key.IsVerticalLayout, layer.PaletteIndex);
}
}
}

return m;
});
return m;
},
(codePoint, this));

return metrics.Length > 0;
}

private GlyphMetrics CreateGlyphMetrics(
CodePoint codePoint,
in CodePoint codePoint,
ushort glyphId,
GlyphType glyphType,
TextAttributes textAttributes,
Expand All @@ -585,8 +595,8 @@ private GlyphMetrics CreateGlyphMetrics(
ushort paletteIndex = 0)
=> this.outlineType switch
{
OutlineType.TrueType => this.CreateTrueTypeGlyphMetrics(codePoint, glyphId, glyphType, textAttributes, textDecorations, isVerticalLayout, paletteIndex),
OutlineType.CFF => this.CreateCffGlyphMetrics(codePoint, glyphId, glyphType, textAttributes, textDecorations, isVerticalLayout, paletteIndex),
OutlineType.TrueType => this.CreateTrueTypeGlyphMetrics(in codePoint, glyphId, glyphType, textAttributes, textDecorations, isVerticalLayout, paletteIndex),
OutlineType.CFF => this.CreateCffGlyphMetrics(in codePoint, glyphId, glyphType, textAttributes, textDecorations, isVerticalLayout, paletteIndex),
_ => throw new NotSupportedException(),
};
}
2 changes: 1 addition & 1 deletion src/SixLabors.Fonts/SystemFonts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace SixLabors.Fonts;
/// </summary>
public static class SystemFonts
{
private static readonly Lazy<SystemFontCollection> LazySystemFonts = new(() => new SystemFontCollection());
private static readonly Lazy<SystemFontCollection> LazySystemFonts = new(() => new SystemFontCollection(), true);

/// <summary>
/// Gets the collection containing the globally installed system fonts.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace SixLabors.Fonts.Tables.AdvancedTypographic;
/// </summary>
internal sealed class UnicodeScriptTagMap : Dictionary<ScriptClass, Tag[]>
{
private static readonly Lazy<UnicodeScriptTagMap> Lazy = new(() => CreateMap());
private static readonly Lazy<UnicodeScriptTagMap> Lazy = new(() => CreateMap(), true);

/// <summary>
/// Prevents a default instance of the <see cref="UnicodeScriptTagMap"/> class from being created.
Expand Down
1 change: 0 additions & 1 deletion src/SixLabors.Fonts/Tables/Cff/CffGlyphMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ namespace SixLabors.Fonts.Tables.Cff;
/// </summary>
internal class CffGlyphMetrics : GlyphMetrics
{
private static readonly Vector2 YInverter = new(1, -1);
private CffGlyphData glyphData;

internal CffGlyphMetrics(
Expand Down
2 changes: 1 addition & 1 deletion src/SixLabors.Fonts/Tables/Cff/CffOperator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace SixLabors.Fonts.Tables.Cff;

internal sealed class CFFOperator
{
private static readonly Lazy<Dictionary<int, CFFOperator>> RegisteredOperators = new(() => CreateDictionary());
private static readonly Lazy<Dictionary<int, CFFOperator>> RegisteredOperators = new(() => CreateDictionary(), true);
private readonly byte b0;
private readonly byte b1;
private readonly OperatorOperandKind operatorOperandKind;
Expand Down
51 changes: 39 additions & 12 deletions src/SixLabors.Fonts/TextLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1066,7 +1066,7 @@ private static TextBox BreakLines(
else if (shouldWrap && lineAdvance + glyphAdvance >= wrappingLength)
{
// Forced wordbreak
if (breakAll)
if (breakAll && textLine.Count > 0)
{
textLines.Add(textLine.Finalize());
glyphCount += textLine.Count;
Expand All @@ -1086,7 +1086,7 @@ private static TextBox BreakLines(
lineAdvance = split.ScaledLineAdvance;
}
}
else
else if (textLine.Count > 0)
{
textLines.Add(textLine.Finalize());
glyphCount += textLine.Count;
Expand Down Expand Up @@ -1123,15 +1123,15 @@ private static TextBox BreakLines(
textLine = split;
lineAdvance = split.ScaledLineAdvance;
}
else if (breakWord)
else if (breakWord && textLine.Count > 0)
{
textLines.Add(textLine.Finalize());
glyphCount += textLine.Count;
textLine = new();
lineAdvance = 0;
}
}
else if (breakWord)
else if (breakWord && textLine.Count > 0)
{
textLines.Add(textLine.Finalize());
glyphCount += textLine.Count;
Expand Down Expand Up @@ -1335,10 +1335,24 @@ public TextLine SplitAt(LineBreak lineBreak, bool keepAll)
// Create a new line ensuring we capture the initial metrics.
TextLine result = new();
result.data.AddRange(this.data.GetRange(index, this.data.Count - index));
result.ScaledLineAdvance = result.data.Sum(x => x.ScaledAdvance);
result.ScaledMaxAscender = result.data.Max(x => x.ScaledAscender);
result.ScaledMaxDescender = result.data.Max(x => x.ScaledDescender);
result.ScaledMaxLineHeight = result.data.Max(x => x.ScaledLineHeight);

float advance = 0;
float ascender = 0;
float descender = 0;
float lineHeight = 0;
for (int i = 0; i < result.data.Count; i++)
{
GlyphLayoutData glyph = result.data[i];
advance += glyph.ScaledAdvance;
ascender = MathF.Max(ascender, glyph.ScaledAscender);
descender = MathF.Max(descender, glyph.ScaledDescender);
lineHeight = MathF.Max(lineHeight, glyph.ScaledLineHeight);
}

result.ScaledLineAdvance = advance;
result.ScaledMaxAscender = ascender;
result.ScaledMaxDescender = descender;
result.ScaledMaxLineHeight = lineHeight;

// Remove those items from this line.
this.data.RemoveRange(index, this.data.Count - index);
Expand All @@ -1361,10 +1375,23 @@ public TextLine SplitAt(LineBreak lineBreak, bool keepAll)
}

// Lastly recalculate this line metrics.
this.ScaledLineAdvance = this.data.Sum(x => x.ScaledAdvance);
this.ScaledMaxAscender = this.data.Max(x => x.ScaledAscender);
this.ScaledMaxDescender = this.data.Max(x => x.ScaledDescender);
this.ScaledMaxLineHeight = this.data.Max(x => x.ScaledLineHeight);
advance = 0;
ascender = 0;
descender = 0;
lineHeight = 0;
for (int i = 0; i < this.data.Count; i++)
{
GlyphLayoutData glyph = this.data[i];
advance += glyph.ScaledAdvance;
ascender = MathF.Max(ascender, glyph.ScaledAscender);
descender = MathF.Max(descender, glyph.ScaledDescender);
lineHeight = MathF.Max(lineHeight, glyph.ScaledLineHeight);
}

this.ScaledLineAdvance = advance;
this.ScaledMaxAscender = ascender;
this.ScaledMaxDescender = descender;
this.ScaledMaxLineHeight = lineHeight;

return result;
}
Expand Down
24 changes: 12 additions & 12 deletions src/SixLabors.Fonts/Unicode/UnicodeData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ namespace SixLabors.Fonts.Unicode;

internal static class UnicodeData
{
private static readonly Lazy<UnicodeTrie> LazyBidiTrie = new(() => GetBidiTrie());
private static readonly Lazy<UnicodeTrie> LazyBidiMirrorTrie = new(() => GetBidiMirrorTrie());
private static readonly Lazy<UnicodeTrie> LazyGraphemeTrie = new(() => GetGraphemeTrie());
private static readonly Lazy<UnicodeTrie> LazyLineBreakTrie = new(() => GetLineBreakTrie());
private static readonly Lazy<UnicodeTrie> LazyScriptTrie = new(() => GetScriptTrie());
private static readonly Lazy<UnicodeTrie> LazyCategoryTrie = new(() => GetCategoryTrie());
private static readonly Lazy<UnicodeTrie> LazyArabicShapingTrie = new(() => GetArabicShapingTrie());
private static readonly Lazy<UnicodeTrie> LazyIndicSyllabicCategoryTrie = new(() => GetIndicSyllabicCategoryTrie());
private static readonly Lazy<UnicodeTrie> LazyIndicPositionalCategoryTrie = new(() => GetIndicPositionalCategoryTrie());
private static readonly Lazy<UnicodeTrie> LazyVerticalOrientationTrie = new(() => GetVerticalOrientationTrie());
private static readonly Lazy<UnicodeTrie> LazyUniversalShapingTrie = new(() => GetUniversalShapingTrie());
private static readonly Lazy<UnicodeTrie> LazyIndicShapingTrie = new(() => GetIndicShapingTrie());
private static readonly Lazy<UnicodeTrie> LazyBidiTrie = new(() => GetBidiTrie(), true);
private static readonly Lazy<UnicodeTrie> LazyBidiMirrorTrie = new(() => GetBidiMirrorTrie(), true);
private static readonly Lazy<UnicodeTrie> LazyGraphemeTrie = new(() => GetGraphemeTrie(), true);
private static readonly Lazy<UnicodeTrie> LazyLineBreakTrie = new(() => GetLineBreakTrie(), true);
private static readonly Lazy<UnicodeTrie> LazyScriptTrie = new(() => GetScriptTrie(), true);
private static readonly Lazy<UnicodeTrie> LazyCategoryTrie = new(() => GetCategoryTrie(), true);
private static readonly Lazy<UnicodeTrie> LazyArabicShapingTrie = new(() => GetArabicShapingTrie(), true);
private static readonly Lazy<UnicodeTrie> LazyIndicSyllabicCategoryTrie = new(() => GetIndicSyllabicCategoryTrie(), true);
private static readonly Lazy<UnicodeTrie> LazyIndicPositionalCategoryTrie = new(() => GetIndicPositionalCategoryTrie(), true);
private static readonly Lazy<UnicodeTrie> LazyVerticalOrientationTrie = new(() => GetVerticalOrientationTrie(), true);
private static readonly Lazy<UnicodeTrie> LazyUniversalShapingTrie = new(() => GetUniversalShapingTrie(), true);
private static readonly Lazy<UnicodeTrie> LazyIndicShapingTrie = new(() => GetIndicShapingTrie(), true);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint GetBidiData(uint codePoint) => LazyBidiTrie.Value.Get(codePoint);
Expand Down
Loading

0 comments on commit 18dfeb4

Please sign in to comment.