Merge pull request #382 from SixLabors/js/low-hanging-fruit

Optimize low hanging fruit
This commit is contained in:
James Jackson-South 2024-02-15 20:51:13 +10:00 коммит произвёл GitHub
Родитель cda8d067ac 83d24b7cef
Коммит 18dfeb42f6
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
13 изменённых файлов: 237 добавлений и 76 удалений

Просмотреть файл

@ -33,7 +33,7 @@ internal sealed class FileFontMetrics : FontMetrics
{
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"/>

Просмотреть файл

@ -42,8 +42,8 @@ public sealed class Font
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>
@ -95,6 +95,7 @@ public sealed class Font
/// <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>
@ -194,7 +195,7 @@ public sealed class Font
/// </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
@ -217,7 +218,7 @@ public sealed class Font
/// <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

Просмотреть файл

@ -66,7 +66,7 @@ internal partial class StreamFontMetrics
}
private GlyphMetrics CreateCffGlyphMetrics(
CodePoint codePoint,
in CodePoint codePoint,
ushort glyphId,
GlyphType glyphType,
TextAttributes textAttributes,

Просмотреть файл

@ -115,7 +115,7 @@ internal partial class StreamFontMetrics
}
private GlyphMetrics CreateTrueTypeGlyphMetrics(
CodePoint codePoint,
in CodePoint codePoint,
ushort glyphId,
GlyphType glyphType,
TextAttributes textAttributes,

Просмотреть файл

@ -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;
}
/// <inheritdoc/>
@ -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));
}
/// <inheritdoc />
@ -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<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,
@ -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(),
};
}

Просмотреть файл

@ -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.

Просмотреть файл

@ -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.

Просмотреть файл

@ -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(

Просмотреть файл

@ -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;

Просмотреть файл

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

Просмотреть файл

@ -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);

Просмотреть файл

@ -14,7 +14,7 @@ namespace SixLabors.Fonts.Benchmarks;
/// </para>
/// <para>We should see if we can include the Skia HarfBuzz extensions to see how we compare.</para>
/// </summary>
[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);
}

Просмотреть файл

@ -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