diff --git a/src/SixLabors.Fonts/FileFontMetrics.cs b/src/SixLabors.Fonts/FileFontMetrics.cs index 6b322d5..e5f8c76 100644 --- a/src/SixLabors.Fonts/FileFontMetrics.cs +++ b/src/SixLabors.Fonts/FileFontMetrics.cs @@ -33,7 +33,7 @@ internal sealed class FileFontMetrics : FontMetrics { this.Description = description; this.Path = path; - this.fontMetrics = new Lazy(() => StreamFontMetrics.LoadFont(path, offset)); + this.fontMetrics = new Lazy(() => StreamFontMetrics.LoadFont(path, offset), true); } /// diff --git a/src/SixLabors.Fonts/Font.cs b/src/SixLabors.Fonts/Font.cs index 4521e69..8ff39ce 100644 --- a/src/SixLabors.Fonts/Font.cs +++ b/src/SixLabors.Fonts/Font.cs @@ -42,8 +42,8 @@ public sealed class Font this.Family = family; this.RequestedStyle = style; this.Size = size; - this.metrics = new Lazy(this.LoadInstanceInternal); - this.fontName = new Lazy(this.LoadFontName); + this.metrics = new Lazy(this.LoadInstanceInternal, true); + this.fontName = new Lazy(this.LoadFontName, true); } /// @@ -95,6 +95,7 @@ public sealed class Font /// /// Gets the font metrics. /// + /// Font instance not found. public FontMetrics FontMetrics => this.metrics.Value ?? throw new FontException("Font instance not found."); /// @@ -194,7 +195,7 @@ public sealed class Font /// /// The code point of the character. /// The text attributes to apply to the glyphs. - /// The layout mode to apply to thte glyphs. + /// The layout mode to apply to the glyphs. /// Options for enabling color font support during layout and rendering. /// /// When this method returns, contains the glyphs for the given codepoint, attributes, and color support if the glyphs @@ -217,7 +218,7 @@ public sealed class Font /// The code point of the character. /// The text attributes to apply to the glyphs. /// The text decorations to apply to the glyphs. - /// The layout mode to apply to thte glyphs. + /// The layout mode to apply to the glyphs. /// Options for enabling color font support during layout and rendering. /// /// When this method returns, contains the glyphs for the given codepoint, attributes, and color support if the glyphs diff --git a/src/SixLabors.Fonts/StreamFontMetrics.Cff.cs b/src/SixLabors.Fonts/StreamFontMetrics.Cff.cs index 82103f8..42dfc9b 100644 --- a/src/SixLabors.Fonts/StreamFontMetrics.Cff.cs +++ b/src/SixLabors.Fonts/StreamFontMetrics.Cff.cs @@ -66,7 +66,7 @@ internal partial class StreamFontMetrics } private GlyphMetrics CreateCffGlyphMetrics( - CodePoint codePoint, + in CodePoint codePoint, ushort glyphId, GlyphType glyphType, TextAttributes textAttributes, diff --git a/src/SixLabors.Fonts/StreamFontMetrics.TrueType.cs b/src/SixLabors.Fonts/StreamFontMetrics.TrueType.cs index 02d581c..416e5af 100644 --- a/src/SixLabors.Fonts/StreamFontMetrics.TrueType.cs +++ b/src/SixLabors.Fonts/StreamFontMetrics.TrueType.cs @@ -115,7 +115,7 @@ internal partial class StreamFontMetrics } private GlyphMetrics CreateTrueTypeGlyphMetrics( - CodePoint codePoint, + in CodePoint codePoint, ushort glyphId, GlyphType glyphType, TextAttributes textAttributes, diff --git a/src/SixLabors.Fonts/StreamFontMetrics.cs b/src/SixLabors.Fonts/StreamFontMetrics.cs index fde6aaf..d9fed0f 100644 --- a/src/SixLabors.Fonts/StreamFontMetrics.cs +++ b/src/SixLabors.Fonts/StreamFontMetrics.cs @@ -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; @@ -59,6 +60,7 @@ internal partial class StreamFontMetrics : FontMetrics 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) { @@ -79,6 +81,7 @@ internal partial class StreamFontMetrics : FontMetrics 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) { @@ -157,7 +160,18 @@ internal partial class StreamFontMetrics : FontMetrics ? 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; } /// @@ -206,14 +220,6 @@ internal partial class StreamFontMetrics : FontMetrics 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)) { @@ -222,17 +228,18 @@ internal partial class StreamFontMetrics : FontMetrics // 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)); } /// @@ -530,7 +537,7 @@ internal partial class StreamFontMetrics : FontMetrics } private static (int CodePoint, ushort Id, TextAttributes Attributes, bool IsVerticalLayout) CreateCacheKey( - CodePoint codePoint, + in CodePoint codePoint, ushort glyphId, TextAttributes textAttributes, LayoutMode layoutMode) @@ -555,28 +562,31 @@ internal partial class StreamFontMetrics : FontMetrics } // 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(); - Span 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(); + Span 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, @@ -585,8 +595,8 @@ internal partial class StreamFontMetrics : FontMetrics 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(), }; } diff --git a/src/SixLabors.Fonts/SystemFonts.cs b/src/SixLabors.Fonts/SystemFonts.cs index 86f7e14..cedd71c 100644 --- a/src/SixLabors.Fonts/SystemFonts.cs +++ b/src/SixLabors.Fonts/SystemFonts.cs @@ -10,7 +10,7 @@ namespace SixLabors.Fonts; /// public static class SystemFonts { - private static readonly Lazy LazySystemFonts = new(() => new SystemFontCollection()); + private static readonly Lazy LazySystemFonts = new(() => new SystemFontCollection(), true); /// /// Gets the collection containing the globally installed system fonts. diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/UnicodeScriptTagMap.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/UnicodeScriptTagMap.cs index 40d9250..affbdd5 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/UnicodeScriptTagMap.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/UnicodeScriptTagMap.cs @@ -11,7 +11,7 @@ namespace SixLabors.Fonts.Tables.AdvancedTypographic; /// internal sealed class UnicodeScriptTagMap : Dictionary { - private static readonly Lazy Lazy = new(() => CreateMap()); + private static readonly Lazy Lazy = new(() => CreateMap(), true); /// /// Prevents a default instance of the class from being created. diff --git a/src/SixLabors.Fonts/Tables/Cff/CffGlyphMetrics.cs b/src/SixLabors.Fonts/Tables/Cff/CffGlyphMetrics.cs index 331b756..11be31c 100644 --- a/src/SixLabors.Fonts/Tables/Cff/CffGlyphMetrics.cs +++ b/src/SixLabors.Fonts/Tables/Cff/CffGlyphMetrics.cs @@ -11,7 +11,6 @@ namespace SixLabors.Fonts.Tables.Cff; /// internal class CffGlyphMetrics : GlyphMetrics { - private static readonly Vector2 YInverter = new(1, -1); private CffGlyphData glyphData; internal CffGlyphMetrics( diff --git a/src/SixLabors.Fonts/Tables/Cff/CffOperator.cs b/src/SixLabors.Fonts/Tables/Cff/CffOperator.cs index d6167cb..0f9514d 100644 --- a/src/SixLabors.Fonts/Tables/Cff/CffOperator.cs +++ b/src/SixLabors.Fonts/Tables/Cff/CffOperator.cs @@ -5,7 +5,7 @@ namespace SixLabors.Fonts.Tables.Cff; internal sealed class CFFOperator { - private static readonly Lazy> RegisteredOperators = new(() => CreateDictionary()); + private static readonly Lazy> RegisteredOperators = new(() => CreateDictionary(), true); private readonly byte b0; private readonly byte b1; private readonly OperatorOperandKind operatorOperandKind; diff --git a/src/SixLabors.Fonts/TextLayout.cs b/src/SixLabors.Fonts/TextLayout.cs index 4b2b0c1..84d4af3 100644 --- a/src/SixLabors.Fonts/TextLayout.cs +++ b/src/SixLabors.Fonts/TextLayout.cs @@ -1066,7 +1066,7 @@ internal static class TextLayout else if (shouldWrap && lineAdvance + glyphAdvance >= wrappingLength) { // Forced wordbreak - if (breakAll) + if (breakAll && textLine.Count > 0) { textLines.Add(textLine.Finalize()); glyphCount += textLine.Count; @@ -1086,7 +1086,7 @@ internal static class TextLayout lineAdvance = split.ScaledLineAdvance; } } - else + else if (textLine.Count > 0) { textLines.Add(textLine.Finalize()); glyphCount += textLine.Count; @@ -1123,7 +1123,7 @@ internal static class TextLayout textLine = split; lineAdvance = split.ScaledLineAdvance; } - else if (breakWord) + else if (breakWord && textLine.Count > 0) { textLines.Add(textLine.Finalize()); glyphCount += textLine.Count; @@ -1131,7 +1131,7 @@ internal static class TextLayout lineAdvance = 0; } } - else if (breakWord) + else if (breakWord && textLine.Count > 0) { textLines.Add(textLine.Finalize()); glyphCount += textLine.Count; @@ -1335,10 +1335,24 @@ internal static class TextLayout // 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); @@ -1361,10 +1375,23 @@ internal static class TextLayout } // 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; } diff --git a/src/SixLabors.Fonts/Unicode/UnicodeData.cs b/src/SixLabors.Fonts/Unicode/UnicodeData.cs index 44ad54f..98977c7 100644 --- a/src/SixLabors.Fonts/Unicode/UnicodeData.cs +++ b/src/SixLabors.Fonts/Unicode/UnicodeData.cs @@ -9,18 +9,18 @@ namespace SixLabors.Fonts.Unicode; internal static class UnicodeData { - private static readonly Lazy LazyBidiTrie = new(() => GetBidiTrie()); - private static readonly Lazy LazyBidiMirrorTrie = new(() => GetBidiMirrorTrie()); - private static readonly Lazy LazyGraphemeTrie = new(() => GetGraphemeTrie()); - private static readonly Lazy LazyLineBreakTrie = new(() => GetLineBreakTrie()); - private static readonly Lazy LazyScriptTrie = new(() => GetScriptTrie()); - private static readonly Lazy LazyCategoryTrie = new(() => GetCategoryTrie()); - private static readonly Lazy LazyArabicShapingTrie = new(() => GetArabicShapingTrie()); - private static readonly Lazy LazyIndicSyllabicCategoryTrie = new(() => GetIndicSyllabicCategoryTrie()); - private static readonly Lazy LazyIndicPositionalCategoryTrie = new(() => GetIndicPositionalCategoryTrie()); - private static readonly Lazy LazyVerticalOrientationTrie = new(() => GetVerticalOrientationTrie()); - private static readonly Lazy LazyUniversalShapingTrie = new(() => GetUniversalShapingTrie()); - private static readonly Lazy LazyIndicShapingTrie = new(() => GetIndicShapingTrie()); + private static readonly Lazy LazyBidiTrie = new(() => GetBidiTrie(), true); + private static readonly Lazy LazyBidiMirrorTrie = new(() => GetBidiMirrorTrie(), true); + private static readonly Lazy LazyGraphemeTrie = new(() => GetGraphemeTrie(), true); + private static readonly Lazy LazyLineBreakTrie = new(() => GetLineBreakTrie(), true); + private static readonly Lazy LazyScriptTrie = new(() => GetScriptTrie(), true); + private static readonly Lazy LazyCategoryTrie = new(() => GetCategoryTrie(), true); + private static readonly Lazy LazyArabicShapingTrie = new(() => GetArabicShapingTrie(), true); + private static readonly Lazy LazyIndicSyllabicCategoryTrie = new(() => GetIndicSyllabicCategoryTrie(), true); + private static readonly Lazy LazyIndicPositionalCategoryTrie = new(() => GetIndicPositionalCategoryTrie(), true); + private static readonly Lazy LazyVerticalOrientationTrie = new(() => GetVerticalOrientationTrie(), true); + private static readonly Lazy LazyUniversalShapingTrie = new(() => GetUniversalShapingTrie(), true); + private static readonly Lazy LazyIndicShapingTrie = new(() => GetIndicShapingTrie(), true); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint GetBidiData(uint codePoint) => LazyBidiTrie.Value.Get(codePoint); diff --git a/tests/SixLabors.Fonts.Benchmarks/SixLabors.Fonts.Benchmarks/MeasureTextBenchmark.cs b/tests/SixLabors.Fonts.Benchmarks/SixLabors.Fonts.Benchmarks/MeasureTextBenchmark.cs index 2818030..04495cb 100644 --- a/tests/SixLabors.Fonts.Benchmarks/SixLabors.Fonts.Benchmarks/MeasureTextBenchmark.cs +++ b/tests/SixLabors.Fonts.Benchmarks/SixLabors.Fonts.Benchmarks/MeasureTextBenchmark.cs @@ -14,7 +14,7 @@ namespace SixLabors.Fonts.Benchmarks; /// /// We should see if we can include the Skia HarfBuzz extensions to see how we compare. /// -[MediumRunJob] +[ShortRunJob] public class MeasureTextBenchmark : IDisposable { private readonly TextOptions textOptions; @@ -22,12 +22,42 @@ public class MeasureTextBenchmark : IDisposable private readonly SKFont font; private readonly SKPaint paint; + private const string LoremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + + "Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. " + + "Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. " + + "Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. " + + "Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. " + + "Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. " + + "Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. " + + "Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; " + + "Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit. " + + "\n\n" + + "Ut velit mauris, egestas sed, gravida nec, ornare ut, mi. Aenean ut orci vel massa suscipit pulvinar. " + + "Nulla sollicitudin. Fusce varius, ligula non tempus aliquam, nunc turpis ullamcorper nibh, in tempus sapien eros vitae ligula. " + + "Pellentesque rhoncus nunc et augue. Integer id felis. Curabitur aliquet pellentesque diam. " + + "Integer quis metus vitae elit lobortis egestas. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. " + + "Morbi vel erat non mauris convallis vehicula. Nulla et sapien. Integer tortor tellus, aliquam faucibus, " + + "convallis id, congue eu, quam. Mauris ullamcorper felis vitae erat. Proin feugiat, augue non elementum posuere, " + + "metus purus iaculis lectus, et tristique ligula justo vitae magna.\n\n" + + "Aliquam convallis sollicitudin purus. Praesent aliquam, enim at fermentum mollis, ligula massa adipiscing nisl, " + + "ac euismod nibh nisl eu lectus. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, " + + "nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. " + + "Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. " + + "Nam pretium turpis et arcu. Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. " + + "Sed aliquam ultrices mauris. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. " + + "Praesent adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. " + + "Vestibulum volutpat pretium libero. Cras id dui. Aenean ut eros et nisl sagittis vestibulum. " + + "Nullam nulla eros, ultricies sit amet, nonummy id, imperdiet feugiat, pede. Sed lectus."; + public MeasureTextBenchmark() { const string fontFamilyName = "Arial"; const int fontSize = 16; - this.textOptions = new TextOptions(SystemFonts.Get(fontFamilyName).CreateFont(fontSize, FontStyle.Regular)); + this.textOptions = new TextOptions(SystemFonts.Get(fontFamilyName).CreateFont(fontSize, FontStyle.Regular)) + { + WrappingLength = 400 + }; this.arialTypeface = SKTypeface.FromFamilyName(fontFamilyName, SKFontStyle.Normal); this.font = new SKFont(this.arialTypeface, fontSize); @@ -41,12 +71,12 @@ public class MeasureTextBenchmark : IDisposable this.paint.Dispose(); } - [Params("a", "Hello world", "The quick brown fox jumps over the lazy dog")] + [Params("a", "Hello world", "The quick brown fox jumps over the lazy dog", LoremIpsum)] public string Text { get; set; } = string.Empty; [Benchmark] public void SixLaborsFonts() => TextMeasurer.MeasureSize(this.Text, this.textOptions); - [Benchmark] - public void SkiaSharp() => this.paint.MeasureText(this.Text); + // [Benchmark] + // public void SkiaSharp() => this.paint.MeasureText(this.Text); } diff --git a/tests/SixLabors.Fonts.Tests/Issues/Issues_383.cs b/tests/SixLabors.Fonts.Tests/Issues/Issues_383.cs new file mode 100644 index 0000000..73ff6e8 --- /dev/null +++ b/tests/SixLabors.Fonts.Tests/Issues/Issues_383.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +#if OS_WINDOWS +using System.Numerics; + +namespace SixLabors.Fonts.Tests.Issues; + +public class Issues_383 +{ + [Fact] + public void CanBreakLinesWithShortWrappingLength() + { + FontFamily fontFamily = SystemFonts.Get("Yu Gothic"); + Font font = fontFamily.CreateFont(20.0F); + + TextOptions textOption = new(font) + { + WrappingLength = 10.0F, + WordBreaking = WordBreaking.BreakAll + }; + + // OK + TextRenderer.RenderTextTo(new DummyGlyphRenderer(), "i", textOption); + + // OK + TextRenderer.RenderTextTo(new DummyGlyphRenderer(), "v", textOption); + + // raise ArgumentOutOfRangeException + TextRenderer.RenderTextTo(new DummyGlyphRenderer(), "a", textOption); + + textOption.WrappingLength = 9.0F; + + // OK + TextRenderer.RenderTextTo(new DummyGlyphRenderer(), "i", textOption); + + // raise ArgumentOutOfRangeException + TextRenderer.RenderTextTo(new DummyGlyphRenderer(), "v", textOption); + + // OK + TextRenderer.RenderTextTo(new DummyGlyphRenderer(), "i\r\nv", textOption); + + // raise ArgumentOutOfRangeException + TextRenderer.RenderTextTo(new DummyGlyphRenderer(), "v\r\ni", textOption); + } +} + +internal class DummyGlyphRenderer : IGlyphRenderer +{ + public void BeginFigure() + { + } + + public bool BeginGlyph(in FontRectangle bounds, in GlyphRendererParameters parameters) => true; + + public void BeginText(in FontRectangle bounds) + { + } + + public void CubicBezierTo(Vector2 secondControlPoint, Vector2 thirdControlPoint, Vector2 point) + { + } + + public TextDecorations EnabledDecorations() => TextDecorations.None; + + public void EndFigure() + { + } + + public void EndGlyph() + { + } + + public void EndText() + { + } + + public void LineTo(Vector2 point) + { + } + + public void MoveTo(Vector2 point) + { + } + + public void QuadraticBezierTo(Vector2 secondControlPoint, Vector2 point) + { + } + + public void SetDecoration(TextDecorations textDecorations, Vector2 start, Vector2 end, float thickness) + { + } +} +#endif