Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize low hanging fruit #382

Merged
merged 3 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading