Fix bounds and size measurement

This commit is contained in:
James Jackson-South 2024-02-08 20:08:22 +10:00
Родитель 5930793285
Коммит 203dac4c62
17 изменённых файлов: 385 добавлений и 317 удалений

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

@ -8,41 +8,49 @@ using SixLabors.ImageSharp.Drawing;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using IOPath = System.IO.Path;
namespace DrawWithImageSharp;
public static class BoundingBoxes
{
public static void Generate(string text, Font font)
public static void Generate(string text, TextOptions options)
{
using var img = new Image<Rgba32>(1000, 1000);
img.Mutate(x => x.Fill(Color.White));
FontRectangle bounds = TextMeasurer.MeasureBounds(text, options);
FontRectangle advance = TextMeasurer.MeasureAdvance(text, options);
using Image<Rgba32> image = new((int)Math.Ceiling(options.Origin.X + (Math.Max(advance.Width, bounds.Width) + 1)), (int)Math.Ceiling(options.Origin.Y + (Math.Max(advance.Height, bounds.Height) + 1)));
image.Mutate(x => x.Fill(Color.White));
Vector2 origin = options.Origin;
FontRectangle size = TextMeasurer.MeasureSize(text, options);
TextOptions options = new(font);
FontRectangle box = TextMeasurer.MeasureBounds(text, options);
(IPathCollection paths, IPathCollection boxes) = GenerateGlyphsWithBox(text, options);
image.Mutate(
x => x.Fill(Color.Black, paths)
.Draw(Color.Yellow, 1, boxes)
.Draw(Color.Purple, 1, new RectangularPolygon(bounds.X, bounds.Y, bounds.Width, bounds.Height))
.Draw(Color.Green, 1, new RectangularPolygon(size.X + bounds.X, size.Y + bounds.Y, size.Width, size.Height))
.Draw(Color.Red, 1, new RectangularPolygon(advance.X + origin.X, advance.Y + origin.Y, advance.Width, advance.Height)));
Rgba32 f = Color.Fuchsia;
f.A = 128;
string path = IOPath.GetInvalidFileNameChars().Aggregate(text, (x, c) => x.Replace($"{c}", "-"));
string fullPath = IOPath.GetFullPath(IOPath.Combine($"Output/Boxed/{options.Font.Name}", IOPath.Combine(path)));
Directory.CreateDirectory(IOPath.GetDirectoryName(fullPath));
img.Mutate(x => x.Fill(Color.Black, paths)
.Draw(f, 1, boxes)
.Draw(Color.Lime, 1, new RectangularPolygon(box.Location, box.Size)));
img.Save("Output/Boxed.png");
image.Save($"{fullPath}.png");
}
/// <summary>
/// Generates the shapes corresponding the glyphs described by the font and with the setting ing withing the FontSpan
/// Generates the shapes corresponding the glyphs described by the font and settings.
/// </summary>
/// <param name="text">The text to generate glyphs for</param>
/// <param name="options">The style and settings to use while rendering the glyphs</param>
/// <returns>The paths, boxes, and text box.</returns>
private static (IPathCollection Paths, IPathCollection Boxes) GenerateGlyphsWithBox(string text, TextOptions options)
{
var glyphBuilder = new CustomGlyphBuilder(Vector2.Zero);
CustomGlyphBuilder glyphBuilder = new();
var renderer = new TextRenderer(glyphBuilder);
TextRenderer renderer = new(glyphBuilder);
renderer.RenderText(text, options);

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

@ -27,17 +27,17 @@ internal class CustomGlyphBuilder : GlyphBuilder
/// <summary>
/// Gets the paths that have been rendered by this.
/// </summary>
public IPathCollection Boxes => new PathCollection(this.glyphBounds.Select(x => new RectangularPolygon(x.Location, x.Size)));
public IPathCollection Boxes => new PathCollection(this.glyphBounds.Select(x => new RectangularPolygon(x.X, x.Y, x.Width, x.Height)));
/// <summary>
/// Gets the paths that have been rendered by this builder.
/// </summary>
public IPath TextBox { get; private set; }
protected override void BeginText(in FontRectangle rect)
protected override void BeginText(in FontRectangle bounds)
{
this.TextBox = new RectangularPolygon(rect.Location, rect.Size);
base.BeginText(rect);
this.TextBox = new RectangularPolygon(bounds.X, bounds.Y, bounds.Width, bounds.Height);
base.BeginText(bounds);
}
protected override void BeginGlyph(in FontRectangle bounds, in GlyphRendererParameters parameters)

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

@ -28,11 +28,21 @@ public static class Program
FontFamily wendyOne = fonts.Add(IOPath.Combine("Fonts", "WendyOne-Regular.ttf"));
FontFamily whitneyBook = fonts.Add(IOPath.Combine("Fonts", "whitney-book.ttf"));
FontFamily colorEmoji = fonts.Add(IOPath.Combine("Fonts", "Twemoji Mozilla.ttf"));
FontFamily font2 = fonts.Add(IOPath.Combine("Fonts", "OpenSans-Regular.ttf"));
FontFamily openSans = fonts.Add(IOPath.Combine("Fonts", "OpenSans-Regular.ttf"));
FontFamily sunflower = fonts.Add(IOPath.Combine("Fonts", "Sunflower-Medium.ttf"));
FontFamily bugzilla = fonts.Add(IOPath.Combine("Fonts", "me_quran_volt_newmet.ttf"));
FontFamily notoKR = fonts.Add(IOPath.Combine("Fonts", "NotoSansKR-Regular.otf"));
FontFamily marker = fonts.Add(IOPath.Combine("Fonts", "PermanentMarker-Regular.ttf"));
FontFamily sEmji = fonts.Add(IOPath.Combine("Fonts", "seguiemj-win11.ttf"));
BoundingBoxes.Generate("\U0001F469\U0001F3FB\u200D\U0001F91D\u200D\U0001F469\U0001F3FC", new TextOptions(sEmji.CreateFont(72)) { LineSpacing = 1.4f });
BoundingBoxes.Generate("\U0001F46D\U0001F3FB", new TextOptions(sEmji.CreateFont(72)) { LineSpacing = 1.4f });
BoundingBoxes.Generate("È", new TextOptions(marker.CreateFont(142)) { LineSpacing = 1.4f });
BoundingBoxes.Generate("H", new TextOptions(whitneyBook.CreateFont(25)));
openSans.TryGetMetrics(FontStyle.Regular, out FontMetrics metrics);
BoundingBoxes.Generate("A\nA\nA\nA", new TextOptions(openSans.CreateFont(metrics.UnitsPerEm)) { LineSpacing = 1.5f });
RenderText(notoKR, "\uD734", pointSize: 72);
RenderText(notoKR, "Sphinx of black quartz, judge my vow!", pointSize: 72);
@ -47,16 +57,28 @@ public static class Program
#if OS_WINDOWS
FontFamily arial = SystemFonts.Get("Arial");
FontFamily jhengHei = SystemFonts.Get("Microsoft JhengHei");
FontFamily emojiFont = SystemFonts.Get("Segoe UI Emoji");
FontFamily uiFont = SystemFonts.Get("Segoe UI");
FontFamily arabicFont = SystemFonts.Get("Dubai");
FontFamily tahoma = SystemFonts.Get("Tahoma");
RenderText(SystemFonts.Get("Arial"), "abcdefghijklmnopqrstuvwxyz", pointSize: 30);
RenderText(SystemFonts.Get("Arial"), "abcdefghijklmnopqrstuvwxyz\r\nabcdefghijklmnopqrstuvwxyz", pointSize: 30);
RenderText(SystemFonts.Get("Arial"), "abcdef ghijk lmnopq rstuvwxyz", pointSize: 30);
BoundingBoxes.Generate(
"This is a long and Honorificabilitudinitatibus califragilisticexpialidocious Taumatawhakatangihangakoauauotamateaturipukakapikimaungahoronukupokaiwhenuakitanatahu グレートブリテンおよび北アイルランド連合王国という言葉は本当に長い言葉",
new TextOptions(arial.CreateFont(20))
{
WrappingLength = 400,
LayoutMode = LayoutMode.HorizontalBottomTop,
WordBreaking = WordBreaking.Standard,
FallbackFontFamilies = new[] { jhengHei }
});
return;
RenderText(arial, "abcdefghijklmnopqrstuvwxyz", pointSize: 30);
RenderText(arial, "abcdefghijklmnopqrstuvwxyz\r\nabcdefghijklmnopqrstuvwxyz", pointSize: 30);
RenderText(arial, "abcdef ghijk lmnopq rstuvwxyz", pointSize: 30);
// return;
var textRuns = new List<RichTextRun>
{
@ -80,7 +102,7 @@ public static class Program
};
RenderText(bugzilla, arabic, pointSize: 72, textRuns: textRuns);
RenderText(font2, "\uFB01", pointSize: 11.25F);
RenderText(openSans, "\uFB01", pointSize: 11.25F);
RenderText(fontWoff2, "\uFB01", pointSize: 11.25F);
RenderText(tahoma, "p", pointSize: 11.25F);
RenderText(tahoma, "Lorem ipsum dolor sit amet", pointSize: 11.25F);
@ -88,11 +110,11 @@ public static class Program
RenderText(uiFont, "Soft\u00ADHyphen", pointSize: 72);
RenderText(uiFont, "first\n\n\n\nl", pointSize: 20, fallbackFonts: new[] { font2 });
RenderText(uiFont, "first\n\n\n\nl", pointSize: 20, fallbackFonts: new[] { openSans });
RenderText(uiFont, "first\n\n\n\nlast", pointSize: 20, fallbackFonts: new[] { font2 });
RenderText(uiFont, "first\n\n\n\nlast", pointSize: 20, fallbackFonts: new[] { openSans });
RenderText(uiFont, "Testing", pointSize: 20);
RenderText(emojiFont, "👩🏽🚒a", pointSize: 72, fallbackFonts: new[] { font2 });
RenderText(emojiFont, "👩🏽🚒a", pointSize: 72, fallbackFonts: new[] { openSans });
RenderText(arabicFont, "English اَلْعَرَبِيَّةُ English", pointSize: 20);
RenderText(arabicFont, "English English", pointSize: 20);
RenderText(arabicFont, "اَلْعَرَبِيَّةُ اَلْعَرَبِيَّةُ", pointSize: 20);
@ -102,19 +124,19 @@ public static class Program
RenderText(arabicFont, "English اَلْعَرَبِيَّةُ", pointSize: 20);
RenderTextProcessorWithAlignment(emojiFont, "😀A😀", pointSize: 20, fallbackFonts: new[] { colorEmoji });
RenderTextProcessorWithAlignment(uiFont, "this\nis\na\ntest", pointSize: 20, fallbackFonts: new[] { font2 });
RenderTextProcessorWithAlignment(uiFont, "first\n\n\n\nlast", pointSize: 20, fallbackFonts: new[] { font2 });
RenderTextProcessorWithAlignment(uiFont, "this\nis\na\ntest", pointSize: 20, fallbackFonts: new[] { openSans });
RenderTextProcessorWithAlignment(uiFont, "first\n\n\n\nlast", pointSize: 20, fallbackFonts: new[] { openSans });
RenderText(emojiFont, "😀", pointSize: 72, fallbackFonts: new[] { font2 });
RenderText(font2, string.Empty, pointSize: 72, fallbackFonts: new[] { emojiFont });
RenderText(font2, "😀 Hello World! 😀", pointSize: 72, fallbackFonts: new[] { emojiFont });
RenderText(emojiFont, "😀", pointSize: 72, fallbackFonts: new[] { openSans });
RenderText(openSans, string.Empty, pointSize: 72, fallbackFonts: new[] { emojiFont });
RenderText(openSans, "😀 Hello World! 😀", pointSize: 72, fallbackFonts: new[] { emojiFont });
#endif
// fallback font tests
RenderTextProcessor(colorEmoji, "a😀d", pointSize: 72, fallbackFonts: new[] { font2 });
RenderText(colorEmoji, "a😀d", pointSize: 72, fallbackFonts: new[] { font2 });
RenderTextProcessor(colorEmoji, "a😀d", pointSize: 72, fallbackFonts: new[] { openSans });
RenderText(colorEmoji, "a😀d", pointSize: 72, fallbackFonts: new[] { openSans });
RenderText(colorEmoji, "😀", pointSize: 72, fallbackFonts: new[] { font2 });
RenderText(colorEmoji, "😀", pointSize: 72, fallbackFonts: new[] { openSans });
//// general
RenderText(font, "abc", 72);
@ -122,50 +144,46 @@ public static class Program
RenderText(fontWoff, "abe", 72);
RenderText(fontWoff, "ABf", 72);
RenderText(fontWoff2, "woff2", 72);
RenderText(font2, "ov", 72);
RenderText(font2, "a\ta", 72);
RenderText(font2, "aa\ta", 72);
RenderText(font2, "aaa\ta", 72);
RenderText(font2, "aaaa\ta", 72);
RenderText(font2, "aaaaa\ta", 72);
RenderText(font2, "aaaaaa\ta", 72);
RenderText(font2, "Hello\nWorld", 72);
RenderText(openSans, "ov", 72);
RenderText(openSans, "a\ta", 72);
RenderText(openSans, "aa\ta", 72);
RenderText(openSans, "aaa\ta", 72);
RenderText(openSans, "aaaa\ta", 72);
RenderText(openSans, "aaaaa\ta", 72);
RenderText(openSans, "aaaaaa\ta", 72);
RenderText(openSans, "Hello\nWorld", 72);
RenderText(carter, "Hello\0World", 72);
RenderText(wendyOne, "Hello\0World", 72);
RenderText(whitneyBook, "Hello\0World", 72);
RenderText(sunflower, "í", 30);
RenderText(new RichTextOptions(new Font(font2, 72)) { TabWidth = 4 }, "\t\tx");
RenderText(new RichTextOptions(new Font(font2, 72)) { TabWidth = 4 }, "\t\t\tx");
RenderText(new RichTextOptions(new Font(font2, 72)) { TabWidth = 4 }, "\t\t\t\tx");
RenderText(new RichTextOptions(new Font(font2, 72)) { TabWidth = 4 }, "\t\t\t\t\tx");
RenderText(new RichTextOptions(new Font(openSans, 72)) { TabWidth = 4 }, "\t\tx");
RenderText(new RichTextOptions(new Font(openSans, 72)) { TabWidth = 4 }, "\t\t\tx");
RenderText(new RichTextOptions(new Font(openSans, 72)) { TabWidth = 4 }, "\t\t\t\tx");
RenderText(new RichTextOptions(new Font(openSans, 72)) { TabWidth = 4 }, "\t\t\t\t\tx");
RenderText(new RichTextOptions(new Font(font2, 72)) { TabWidth = 0 }, "Zero\tTab");
RenderText(new RichTextOptions(new Font(openSans, 72)) { TabWidth = 0 }, "Zero\tTab");
RenderText(new RichTextOptions(new Font(font2, 72)) { TabWidth = 0 }, "Zero\tTab");
RenderText(new RichTextOptions(new Font(font2, 72)) { TabWidth = 1 }, "One\tTab");
RenderText(new RichTextOptions(new Font(font2, 72)) { TabWidth = 6 }, "\tTab Then Words");
RenderText(new RichTextOptions(new Font(font2, 72)) { TabWidth = 1 }, "Tab Then Words");
RenderText(new RichTextOptions(new Font(font2, 72)) { TabWidth = 1 }, "Words Then Tab\t");
RenderText(new RichTextOptions(new Font(font2, 72)) { TabWidth = 1 }, " Spaces Then Words");
RenderText(new RichTextOptions(new Font(font2, 72)) { TabWidth = 1 }, "Words Then Spaces ");
RenderText(new RichTextOptions(new Font(font2, 72)) { TabWidth = 1 }, "\naaaabbbbccccddddeeee\n\t\t\t3 tabs\n\t\t\t\t\t5 tabs");
RenderText(new RichTextOptions(new Font(openSans, 72)) { TabWidth = 0 }, "Zero\tTab");
RenderText(new RichTextOptions(new Font(openSans, 72)) { TabWidth = 1 }, "One\tTab");
RenderText(new RichTextOptions(new Font(openSans, 72)) { TabWidth = 6 }, "\tTab Then Words");
RenderText(new RichTextOptions(new Font(openSans, 72)) { TabWidth = 1 }, "Tab Then Words");
RenderText(new RichTextOptions(new Font(openSans, 72)) { TabWidth = 1 }, "Words Then Tab\t");
RenderText(new RichTextOptions(new Font(openSans, 72)) { TabWidth = 1 }, " Spaces Then Words");
RenderText(new RichTextOptions(new Font(openSans, 72)) { TabWidth = 1 }, "Words Then Spaces ");
RenderText(new RichTextOptions(new Font(openSans, 72)) { TabWidth = 1 }, "\naaaabbbbccccddddeeee\n\t\t\t3 tabs\n\t\t\t\t\t5 tabs");
#if OS_WINDOWS
RenderText(new Font(SystemFonts.Get("Arial"), 20f, FontStyle.Regular), "á é í ó ú ç ã õ", 200, 50);
RenderText(new Font(SystemFonts.Get("Arial"), 10f, FontStyle.Regular), "PGEP0JK867", 200, 50);
RenderText(new RichTextOptions(SystemFonts.CreateFont("consolas", 72)) { TabWidth = 4 }, "xxxxxxxxxxxxxxxx\n\txxxx\txxxx\n\t\txxxxxxxx\n\t\t\txxxx");
BoundingBoxes.Generate("a b c y q G H T", SystemFonts.CreateFont("arial", 40f));
BoundingBoxes.Generate("a b c y q G H T", new TextOptions(SystemFonts.CreateFont("arial", 40f)));
TextAlignmentSample.Generate(SystemFonts.CreateFont("arial", 50f));
TextAlignmentWrapped.Generate(SystemFonts.CreateFont("arial", 50f));
FontFamily simsum = SystemFonts.Get("SimSun");
RenderText(simsum, "这是一段长度超出设定的换行宽度的文本,但是没有在设定的宽度处换行。这段文本用于演示问题。希望可以修复。如果有需要可以联系我。", 16);
FontFamily jhengHei = SystemFonts.Get("Microsoft JhengHei");
RenderText(jhengHei, " ,;:!¥()?{}-=+\|~!@#%&", 16);
FontFamily arial = SystemFonts.Get("Arial");
RenderText(arial, "ìíîï", 72);
#endif
var sb = new StringBuilder();
@ -211,7 +229,7 @@ public static class Program
public static void RenderText(RichTextOptions options, string text)
{
FontRectangle size = TextMeasurer.MeasureSize(text, options);
FontRectangle size = TextMeasurer.MeasureAdvance(text, options);
if (size == FontRectangle.Empty)
{
return;
@ -253,7 +271,7 @@ public static class Program
textOptions.FallbackFontFamilies = fallbackFonts.ToArray();
}
FontRectangle textSize = TextMeasurer.MeasureSize(text, textOptions);
FontRectangle textSize = TextMeasurer.MeasureAdvance(text, textOptions);
textOptions.Origin = new PointF(5, 5);
using var img = new Image<Rgba32>((int)Math.Ceiling(textSize.Width) + 20, (int)Math.Ceiling(textSize.Height) + 20);

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

@ -82,12 +82,12 @@ internal readonly struct GlyphLayout
/// Gets a value indicating whether the glyph represents a whitespace character.
/// </summary>
/// <returns>The <see cref="bool"/>.</returns>
public bool IsWhiteSpace() => GlyphMetrics.ShouldRenderWhiteSpaceOnly(this.CodePoint);
public bool IsWhiteSpace() => UnicodeUtility.ShouldRenderWhiteSpaceOnly(this.CodePoint);
internal FontRectangle BoundingBox(float dpi)
{
Vector2 origin = (this.PenLocation + this.Offset) * dpi;
FontRectangle box = this.Glyph.BoundingBox(this.LayoutMode, Vector2.Zero, dpi);
FontRectangle box = this.Glyph.BoundingBox(this.LayoutMode, this.BoxLocation, dpi);
if (this.IsWhiteSpace())
{

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

@ -401,39 +401,7 @@ public abstract class GlyphMetrics
/// <returns>The <see cref="bool"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected internal static bool ShouldSkipGlyphRendering(CodePoint codePoint)
=> UnicodeUtility.IsDefaultIgnorableCodePoint((uint)codePoint.Value) && !ShouldRenderWhiteSpaceOnly(codePoint);
/// <summary>
/// Gets a value indicating whether the specified code point should be rendered as a white space only.
/// </summary>
/// <param name="codePoint">The code point.</param>
/// <returns>The <see cref="bool"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool ShouldRenderWhiteSpaceOnly(CodePoint codePoint)
{
if (CodePoint.IsWhiteSpace(codePoint))
{
return true;
}
// Note: While U+115F, U+1160, U+3164 and U+FFA0 are Default_Ignorable,
// we do NOT want to hide them, as the way Uniscribe has implemented them
// is with regular spacing glyphs, and that's the way fonts are made to work.
// As such, we make exceptions for those four.
// Also ignoring U+1BCA0..1BCA3. https://github.com/harfbuzz/harfbuzz/issues/503
uint value = (uint)codePoint.Value;
if (value is 0x115F or 0x1160 or 0x3164 or 0xFFA0)
{
return true;
}
if (UnicodeUtility.IsInRangeInclusive(value, 0x1BCA0, 0x1BCA3))
{
return true;
}
return false;
}
=> UnicodeUtility.IsDefaultIgnorableCodePoint((uint)codePoint.Value) && !UnicodeUtility.ShouldRenderWhiteSpaceOnly(codePoint);
/// <summary>
/// Returns the size to render/measure the glyph based on the given size and resolution in px units.

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

@ -247,15 +247,30 @@ internal sealed class GlyphSubstitutionCollection : IGlyphShapingCollection
{
// Remove the glyphs at each index.
int codePointCount = 0;
CodePoint codePoint = default;
for (int i = removalIndices.Length - 1; i >= 0; i--)
{
int match = removalIndices[i];
codePointCount += this.glyphs[match].Data.CodePointCount;
CodePoint currentCodePoint = this.glyphs[match].Data.CodePoint;
if (!UnicodeUtility.IsDefaultIgnorableCodePoint((uint)codePoint.Value) || UnicodeUtility.ShouldRenderWhiteSpaceOnly(codePoint))
{
if (!CodePoint.IsZeroWidthJoiner(currentCodePoint))
{
codePoint = currentCodePoint;
}
}
this.glyphs.RemoveAt(match);
}
// Assign our new id at the index.
GlyphShapingData current = this.glyphs[index].Data;
if (codePoint != default)
{
current.CodePoint = codePoint;
}
current.CodePointCount += codePointCount;
current.GlyphId = glyphId;
current.LigatureId = ligatureId;
@ -276,15 +291,30 @@ internal sealed class GlyphSubstitutionCollection : IGlyphShapingCollection
{
// Remove the glyphs at each index.
int codePointCount = 0;
CodePoint codePoint = default;
for (int i = count; i > 0; i--)
{
int match = index + i;
codePointCount += this.glyphs[match].Data.CodePointCount;
CodePoint currentCodePoint = this.glyphs[match].Data.CodePoint;
if (!UnicodeUtility.IsDefaultIgnorableCodePoint((uint)codePoint.Value) || UnicodeUtility.ShouldRenderWhiteSpaceOnly(codePoint))
{
if (!CodePoint.IsZeroWidthJoiner(currentCodePoint))
{
codePoint = currentCodePoint;
}
}
this.glyphs.RemoveAt(match);
}
// Assign our new id at the index.
GlyphShapingData current = this.glyphs[index].Data;
if (codePoint != default)
{
current.CodePoint = codePoint;
}
current.CodePointCount += codePointCount;
current.GlyphId = glyphId;
current.LigatureId = 0;

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

@ -123,7 +123,7 @@ internal class CffGlyphMetrics : GlyphMetrics
if (renderer.BeginGlyph(in box, in parameters))
{
if (!ShouldRenderWhiteSpaceOnly(this.CodePoint))
if (!UnicodeUtility.ShouldRenderWhiteSpaceOnly(this.CodePoint))
{
if (this.GlyphColor.HasValue && renderer is IColorGlyphRenderer colorSurface)
{

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

@ -129,7 +129,7 @@ public class TrueTypeGlyphMetrics : GlyphMetrics
if (renderer.BeginGlyph(in box, in parameters))
{
if (!ShouldRenderWhiteSpaceOnly(this.CodePoint))
if (!UnicodeUtility.ShouldRenderWhiteSpaceOnly(this.CodePoint))
{
if (this.GlyphColor.HasValue && renderer is IColorGlyphRenderer colorSurface)
{

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

@ -409,6 +409,7 @@ internal static class TextLayout
continue;
}
int j = 0;
foreach (GlyphMetrics metric in data.Metrics)
{
glyphs.Add(new GlyphLayout(
@ -419,7 +420,9 @@ internal static class TextLayout
data.ScaledAdvance,
advanceY,
GlyphLayoutMode.Horizontal,
i == 0));
i == 0 && j == 0));
j++;
}
boxLocation.X += data.ScaledAdvance;
@ -537,6 +540,7 @@ internal static class TextLayout
continue;
}
int j = 0;
foreach (GlyphMetrics metric in data.Metrics)
{
// Align the glyph horizontally and vertically centering horizontally around the baseline.
@ -552,7 +556,9 @@ internal static class TextLayout
advanceX,
data.ScaledAdvance,
GlyphLayoutMode.Vertical,
i == 0));
i == 0 && j == 0));
j++;
}
penLocation.Y += data.ScaledAdvance;
@ -671,6 +677,7 @@ internal static class TextLayout
if (data.IsRotated)
{
int j = 0;
foreach (GlyphMetrics metric in data.Metrics)
{
Vector2 scale = new Vector2(data.PointSize) / metric.ScaleFactor;
@ -682,11 +689,14 @@ internal static class TextLayout
advanceX,
data.ScaledAdvance,
GlyphLayoutMode.VerticalRotated,
i == 0));
i == 0 && j == 0));
j++;
}
}
else
{
int j = 0;
foreach (GlyphMetrics metric in data.Metrics)
{
// Align the glyph horizontally and vertically centering horizontally around the baseline.
@ -702,7 +712,9 @@ internal static class TextLayout
advanceX,
data.ScaledAdvance,
GlyphLayoutMode.Vertical,
i == 0));
i == 0 && j == 0));
j++;
}
}

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

@ -208,14 +208,8 @@ public static class TextMeasurer
internal static FontRectangle GetSize(IReadOnlyList<GlyphLayout> glyphLayouts, float dpi)
{
if (glyphLayouts.Count == 0)
{
return FontRectangle.Empty;
}
Vector2 topLeft = glyphLayouts[0].BoxLocation * dpi;
FontRectangle bounds = GetBounds(glyphLayouts, dpi);
return new FontRectangle(0, 0, MathF.Ceiling(bounds.Right - topLeft.X), MathF.Ceiling(bounds.Bottom - topLeft.Y));
return new FontRectangle(0, 0, MathF.Ceiling(bounds.Width), MathF.Ceiling(bounds.Height));
}
internal static FontRectangle GetBounds(IReadOnlyList<GlyphLayout> glyphLayouts, float dpi)
@ -265,7 +259,7 @@ public static class TextMeasurer
return hasSize;
}
var characterBoundsList = new GlyphBounds[glyphLayouts.Count];
GlyphBounds[] characterBoundsList = new GlyphBounds[glyphLayouts.Count];
for (int i = 0; i < glyphLayouts.Count; i++)
{
GlyphLayout glyph = glyphLayouts[i];
@ -287,14 +281,13 @@ public static class TextMeasurer
return hasSize;
}
var characterBoundsList = new GlyphBounds[glyphLayouts.Count];
GlyphBounds[] characterBoundsList = new GlyphBounds[glyphLayouts.Count];
for (int i = 0; i < glyphLayouts.Count; i++)
{
GlyphLayout g = glyphLayouts[i];
FontRectangle bounds = g.BoundingBox(dpi);
Vector2 location = g.BoxLocation * dpi;
bounds = new(0, 0, bounds.Right - location.X, bounds.Bottom - location.Y);
bounds = new(0, 0, bounds.Width, bounds.Height);
hasSize |= bounds.Width > 0 || bounds.Height > 0;
characterBoundsList[i] = new GlyphBounds(g.Glyph.GlyphMetrics.CodePoint, in bounds);
@ -313,7 +306,7 @@ public static class TextMeasurer
return hasSize;
}
var characterBoundsList = new GlyphBounds[glyphLayouts.Count];
GlyphBounds[] characterBoundsList = new GlyphBounds[glyphLayouts.Count];
for (int i = 0; i < glyphLayouts.Count; i++)
{
GlyphLayout g = glyphLayouts[i];

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

@ -232,6 +232,7 @@ internal static class UnicodeUtility
/// <summary>
/// Returns <see langword="true"/> if <paramref name="value"/> is a Default Ignorable Code Point.
/// </summary>
/// <param name="value">The codepoint value.</param>
/// <remarks>
/// <see href="http://www.unicode.org/reports/tr44/#Default_Ignorable_Code_Point"/>
/// <see href="https://www.unicode.org/Public/14.0.0/ucd/DerivedCoreProperties.txt"/>
@ -403,6 +404,38 @@ internal static class UnicodeUtility
return false;
}
/// <summary>
/// Gets a value indicating whether the specified code point should be rendered as a white space only.
/// </summary>
/// <param name="codePoint">The code point.</param>
/// <returns>The <see cref="bool"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ShouldRenderWhiteSpaceOnly(CodePoint codePoint)
{
if (CodePoint.IsWhiteSpace(codePoint))
{
return true;
}
// Note: While U+115F, U+1160, U+3164 and U+FFA0 are Default_Ignorable,
// we do NOT want to hide them, as the way Uniscribe has implemented them
// is with regular spacing glyphs, and that's the way fonts are made to work.
// As such, we make exceptions for those four.
// Also ignoring U+1BCA0..1BCA3. https://github.com/harfbuzz/harfbuzz/issues/503
uint value = (uint)codePoint.Value;
if (value is 0x115F or 0x1160 or 0x3164 or 0xFFA0)
{
return true;
}
if (IsInRangeInclusive(value, 0x1BCA0, 0x1BCA3))
{
return true;
}
return false;
}
/// <summary>
/// Returns the Unicode plane (0 through 16, inclusive) which contains this code point.
/// </summary>

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

@ -145,7 +145,7 @@ public class GlyphTests
{
Font font = new FontCollection().Add(TestFonts.TwemojiMozillaData()).CreateFont(12);
string text = "\u263A\uFE0F"; // Fully-qualified sequence for emoji 'smiling face'
const string text = "\u263A\uFE0F"; // Fully-qualified sequence for emoji 'smiling face'
IReadOnlyList<GlyphLayout> layout = TextLayout.GenerateLayout(text.AsSpan(), new TextOptions(font));
// Check that no glyphs were generated by the variation selector
@ -158,14 +158,14 @@ public class GlyphTests
{
Font font = new FontCollection().Add(TestFonts.SegoeuiEmojiData()).CreateFont(72);
string text = "\U0001F469\U0001F3FB\u200D\U0001F91D\u200D\U0001F469\U0001F3FC"; // women holding hands: light skin tone, medium-light skin tone
string text2 = "\U0001F46D\U0001F3FB"; // women holding hands: light skin tone
const string text = "\U0001F469\U0001F3FB\u200D\U0001F91D\u200D\U0001F469\U0001F3FC"; // women holding hands: light skin tone, medium-light skin tone
const string text2 = "\U0001F46D\U0001F3FB"; // women holding hands: light skin tone
FontRectangle size = TextMeasurer.MeasureSize(text, new TextOptions(font));
FontRectangle size2 = TextMeasurer.MeasureSize(text2, new TextOptions(font));
Assert.Equal(75F, size.Width);
Assert.Equal(75F, size2.Width);
Assert.Equal(52f, size.Width);
Assert.Equal(51f, size2.Width);
}
[Theory]

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

@ -13,7 +13,7 @@ public class Issues_180
FontRectangle size = TextMeasurer.MeasureSize("H", new TextOptions(font));
Assert.Equal(16, size.Width, 1F);
Assert.Equal(21, size.Height, 1F);
Assert.Equal(14, size.Width, 1F);
Assert.Equal(17, size.Height, 1F);
}
}

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

@ -13,6 +13,6 @@ public class Issues_269
FontRectangle size = TextMeasurer.MeasureSize("H", new TextOptions(font));
Assert.Equal(32, size.Width, 1F);
Assert.Equal(27, size.Height, 1F);
Assert.Equal(25, size.Height, 1F);
}
}

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

@ -11,7 +11,7 @@ public class Issues_32
[Fact]
public void TabWidth0CausesBadTabRendering()
{
string text = "Hello\tworld";
const string text = "Hello\tworld";
Font font = CreateFont(text);
FontRectangle size = TextMeasurer.MeasureSize(text, new TextOptions(font)
{
@ -21,12 +21,12 @@ public class Issues_32
// tab width of 0 should make tabs not render at all
Assert.Equal(10, size.Height, 4F);
Assert.Equal(320, size.Width, 4F);
Assert.Equal(311, size.Width, 4F);
}
public static Font CreateFont(string text)
{
var fc = (IFontMetricsCollection)new FontCollection();
IFontMetricsCollection fc = new FontCollection();
Font d = fc.AddMetrics(new FakeFontInstance(text), CultureInfo.InvariantCulture).CreateFont(12);
return new Font(d, 1F);
}

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

@ -249,6 +249,10 @@ public static class TestFonts
public static string PlantinStdRegularFile => GetFullPath("PlantinStdRegular.otf");
public static string PermanentMarkerRegularFile => GetFullPath("PermanentMarker-Regular.ttf");
public static string PermanentMarkerRegularWoff2File => GetFullPath("PermanentMarker-Regular.woff2");
public static Stream TwemojiMozillaData() => OpenStream(TwemojiMozillaFile);
public static Stream SegoeuiEmojiData() => OpenStream(SegoeuiEmojiFile);

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

@ -3,6 +3,7 @@
using System.Globalization;
using System.Numerics;
using System.Text;
using SixLabors.Fonts.Tests.Fakes;
using SixLabors.Fonts.Unicode;
@ -388,7 +389,7 @@ public class TextLayoutTests
FontFamily jhengHei = SystemFonts.Get("Microsoft JhengHei");
Font font = arial.CreateFont(20);
FontRectangle size = TextMeasurer.MeasureSize(
FontRectangle size = TextMeasurer.MeasureAdvance(
text,
new TextOptions(font)
{
@ -780,100 +781,100 @@ public class TextLayoutTests
public static TheoryData<char, FontRectangle> OpenSans_Data
= new()
{
{ '!', new(0, 0, 2, 10) },
{ '"', new(0, 0, 4, 5) },
{ '#', new(0, 0, 7, 9) },
{ '$', new(0, 0, 6, 10) },
{ '%', new(0, 0, 8, 9) },
{ '&', new(0, 0, 8, 9) },
{ '\'', new(0, 0, 2, 5) },
{ '(', new(0, 0, 3, 11) },
{ ')', new(0, 0, 3, 11) },
{ '*', new(0, 0, 6, 6) },
{ '+', new(0, 0, 6, 8) },
{ ',', new(0, 0, 2, 11) },
{ '-', new(0, 0, 3, 7) },
{ '.', new(0, 0, 2, 10) },
{ '/', new(0, 0, 4, 9) },
{ '0', new(0, 0, 6, 9) },
{ '1', new(0, 0, 4, 9) },
{ '2', new(0, 0, 6, 9) },
{ '3', new(0, 0, 6, 9) },
{ '4', new(0, 0, 6, 9) },
{ '5', new(0, 0, 6, 9) },
{ '6', new(0, 0, 6, 9) },
{ '7', new(0, 0, 6, 9) },
{ '8', new(0, 0, 6, 9) },
{ '9', new(0, 0, 6, 9) },
{ ':', new(0, 0, 2, 10) },
{ ';', new(0, 0, 2, 11) },
{ '<', new(0, 0, 6, 8) },
{ '=', new(0, 0, 6, 7) },
{ '>', new(0, 0, 6, 8) },
{ '?', new(0, 0, 5, 10) },
{ '@', new(0, 0, 9, 10) },
{ 'A', new(0, 0, 7, 9) },
{ 'B', new(0, 0, 6, 9) },
{ 'C', new(0, 0, 6, 9) },
{ 'D', new(0, 0, 7, 9) },
{ 'E', new(0, 0, 5, 9) },
{ 'F', new(0, 0, 5, 9) },
{ 'G', new(0, 0, 7, 9) },
{ 'H', new(0, 0, 7, 9) },
{ 'I', new(0, 0, 2, 9) },
{ 'J', new(0, 0, 2, 11) },
{ 'K', new(0, 0, 7, 9) },
{ 'L', new(0, 0, 5, 9) },
{ 'M', new(0, 0, 9, 9) },
{ 'N', new(0, 0, 7, 9) },
{ 'O', new(0, 0, 8, 9) },
{ 'P', new(0, 0, 6, 9) },
{ 'Q', new(0, 0, 8, 11) },
{ 'R', new(0, 0, 7, 9) },
{ 'S', new(0, 0, 6, 9) },
{ 'T', new(0, 0, 6, 9) },
{ 'U', new(0, 0, 7, 9) },
{ 'V', new(0, 0, 6, 9) },
{ 'W', new(0, 0, 10, 9) },
{ 'X', new(0, 0, 6, 9) },
{ 'Y', new(0, 0, 6, 9) },
{ 'Z', new(0, 0, 6, 9) },
{ '[', new(0, 0, 4, 11) },
{ '\\', new(0, 0, 4, 9) },
{ ']', new(0, 0, 3, 11) },
{ '^', new(0, 0, 6, 7) },
{ '_', new(0, 0, 5, 11) },
{ '`', new(0, 0, 3, 3) },
{ 'a', new(0, 0, 5, 9) },
{ 'b', new(0, 0, 6, 9) },
{ 'c', new(0, 0, 5, 9) },
{ 'd', new(0, 0, 6, 9) },
{ 'e', new(0, 0, 6, 9) },
{ 'f', new(0, 0, 4, 9) },
{ 'g', new(0, 0, 6, 12) },
{ 'h', new(0, 0, 6, 9) },
{ 'i', new(0, 0, 2, 9) },
{ 'j', new(0, 0, 2, 12) },
{ 'k', new(0, 0, 6, 9) },
{ 'l', new(0, 0, 2, 9) },
{ 'm', new(0, 0, 9, 9) },
{ 'n', new(0, 0, 6, 9) },
{ 'o', new(0, 0, 6, 9) },
{ 'p', new(0, 0, 6, 12) },
{ 'q', new(0, 0, 6, 12) },
{ 'r', new(0, 0, 4, 9) },
{ 's', new(0, 0, 5, 9) },
{ 't', new(0, 0, 4, 9) },
{ 'u', new(0, 0, 6, 9) },
{ 'v', new(0, 0, 5, 9) },
{ 'w', new(0, 0, 8, 9) },
{ 'x', new(0, 0, 6, 9) },
{ 'y', new(0, 0, 6, 12) },
{ 'z', new(0, 0, 5, 9) },
{ '{', new(0, 0, 4, 11) },
{ '|', new(0, 0, 4, 12) },
{ '}', new(0, 0, 4, 11) },
{ '~', new(0, 0, 6, 6) },
{ '!', new(0, 0, 2, 8) },
{ '"', new(0, 0, 3, 3) },
{ '#', new(0, 0, 6, 8) },
{ '$', new(0, 0, 5, 9) },
{ '%', new(0, 0, 8, 8) },
{ '&', new(0, 0, 7, 8) },
{ '\'', new(0, 0, 1, 3) },
{ '(', new(0, 0, 3, 9) },
{ ')', new(0, 0, 3, 9) },
{ '*', new(0, 0, 5, 5) },
{ '+', new(0, 0, 5, 5) },
{ ',', new(0, 0, 2, 3) },
{ '-', new(0, 0, 3, 1) },
{ '.', new(0, 0, 2, 2) },
{ '/', new(0, 0, 4, 8) },
{ '0', new(0, 0, 5, 8) },
{ '1', new(0, 0, 3, 8) },
{ '2', new(0, 0, 5, 8) },
{ '3', new(0, 0, 5, 8) },
{ '4', new(0, 0, 6, 8) },
{ '5', new(0, 0, 5, 8) },
{ '6', new(0, 0, 5, 8) },
{ '7', new(0, 0, 5, 8) },
{ '8', new(0, 0, 5, 8) },
{ '9', new(0, 0, 5, 8) },
{ ':', new(0, 0, 2, 6) },
{ ';', new(0, 0, 2, 7) },
{ '<', new(0, 0, 5, 5) },
{ '=', new(0, 0, 5, 3) },
{ '>', new(0, 0, 5, 5) },
{ '?', new(0, 0, 4, 8) },
{ '@', new(0, 0, 8, 9) },
{ 'A', new(0, 0, 7, 8) },
{ 'B', new(0, 0, 5, 8) },
{ 'C', new(0, 0, 6, 8) },
{ 'D', new(0, 0, 6, 8) },
{ 'E', new(0, 0, 4, 8) },
{ 'F', new(0, 0, 4, 8) },
{ 'G', new(0, 0, 6, 8) },
{ 'H', new(0, 0, 6, 8) },
{ 'I', new(0, 0, 1, 8) },
{ 'J', new(0, 0, 3, 10) },
{ 'K', new(0, 0, 6, 8) },
{ 'L', new(0, 0, 4, 8) },
{ 'M', new(0, 0, 8, 8) },
{ 'N', new(0, 0, 6, 8) },
{ 'O', new(0, 0, 7, 8) },
{ 'P', new(0, 0, 5, 8) },
{ 'Q', new(0, 0, 7, 9) },
{ 'R', new(0, 0, 6, 8) },
{ 'S', new(0, 0, 5, 8) },
{ 'T', new(0, 0, 6, 8) },
{ 'U', new(0, 0, 6, 8) },
{ 'V', new(0, 0, 6, 8) },
{ 'W', new(0, 0, 9, 8) },
{ 'X', new(0, 0, 6, 8) },
{ 'Y', new(0, 0, 6, 8) },
{ 'Z', new(0, 0, 5, 8) },
{ '[', new(0, 0, 3, 9) },
{ '\\', new(0, 0, 4, 8) },
{ ']', new(0, 0, 3, 9) },
{ '^', new(0, 0, 5, 5) },
{ '_', new(0, 0, 5, 1) },
{ '`', new(0, 0, 2, 2) },
{ 'a', new(0, 0, 5, 6) },
{ 'b', new(0, 0, 5, 8) },
{ 'c', new(0, 0, 4, 6) },
{ 'd', new(0, 0, 5, 8) },
{ 'e', new(0, 0, 5, 6) },
{ 'f', new(0, 0, 4, 8) },
{ 'g', new(0, 0, 6, 8) },
{ 'h', new(0, 0, 5, 8) },
{ 'i', new(0, 0, 1, 8) },
{ 'j', new(0, 0, 3, 10) },
{ 'k', new(0, 0, 5, 8) },
{ 'l', new(0, 0, 1, 8) },
{ 'm', new(0, 0, 8, 6) },
{ 'n', new(0, 0, 5, 6) },
{ 'o', new(0, 0, 5, 6) },
{ 'p', new(0, 0, 5, 8) },
{ 'q', new(0, 0, 5, 8) },
{ 'r', new(0, 0, 4, 6) },
{ 's', new(0, 0, 4, 6) },
{ 't', new(0, 0, 4, 7) },
{ 'u', new(0, 0, 5, 6) },
{ 'v', new(0, 0, 5, 6) },
{ 'w', new(0, 0, 8, 6) },
{ 'x', new(0, 0, 5, 6) },
{ 'y', new(0, 0, 5, 8) },
{ 'z', new(0, 0, 4, 6) },
{ '{', new(0, 0, 4, 9) },
{ '|', new(0, 0, 1, 11) },
{ '}', new(0, 0, 4, 9) },
{ '~', new(0, 0, 5, 2) },
};
[Theory]
@ -1001,100 +1002,100 @@ public class TextLayoutTests
public static TheoryData<char, FontRectangle> SegoeUi_Data
= new()
{
{ '!', new(0, 0, 2, 10) },
{ '"', new(0, 0, 4, 5) },
{ '#', new(0, 0, 6, 9) },
{ '$', new(0, 0, 5, 11) },
{ '%', new(0, 0, 8, 10) },
{ '&', new(0, 0, 8, 10) },
{ '\'', new(0, 0, 2, 5) },
{ '(', new(0, 0, 3, 11) },
{ ')', new(0, 0, 3, 11) },
{ '*', new(0, 0, 4, 6) },
{ '+', new(0, 0, 6, 9) },
{ ',', new(0, 0, 2, 11) },
{ '-', new(0, 0, 4, 7) },
{ '.', new(0, 0, 2, 10) },
{ '/', new(0, 0, 4, 11) },
{ '0', new(0, 0, 5, 10) },
{ '1', new(0, 0, 4, 10) },
{ '2', new(0, 0, 5, 10) },
{ '3', new(0, 0, 5, 10) },
{ '4', new(0, 0, 6, 10) },
{ '5', new(0, 0, 5, 10) },
{ '6', new(0, 0, 5, 10) },
{ '7', new(0, 0, 5, 10) },
{ '8', new(0, 0, 5, 10) },
{ '9', new(0, 0, 5, 10) },
{ ':', new(0, 0, 2, 10) },
{ ';', new(0, 0, 2, 11) },
{ '<', new(0, 0, 6, 9) },
{ '=', new(0, 0, 6, 8) },
{ '>', new(0, 0, 6, 9) },
{ '?', new(0, 0, 4, 10) },
{ '@', new(0, 0, 9, 11) },
{ 'A', new(0, 0, 7, 10) },
{ 'B', new(0, 0, 6, 10) },
{ 'C', new(0, 0, 6, 10) },
{ 'D', new(0, 0, 7, 10) },
{ 'E', new(0, 0, 5, 10) },
{ 'F', new(0, 0, 5, 10) },
{ 'G', new(0, 0, 7, 10) },
{ 'H', new(0, 0, 7, 10) },
{ 'I', new(0, 0, 2, 10) },
{ 'J', new(0, 0, 3, 10) },
{ 'K', new(0, 0, 6, 10) },
{ 'L', new(0, 0, 5, 10) },
{ 'M', new(0, 0, 9, 10) },
{ 'N', new(0, 0, 7, 10) },
{ 'O', new(0, 0, 8, 10) },
{ 'P', new(0, 0, 6, 10) },
{ 'Q', new(0, 0, 8, 11) },
{ 'R', new(0, 0, 6, 10) },
{ 'S', new(0, 0, 5, 10) },
{ 'T', new(0, 0, 6, 10) },
{ 'U', new(0, 0, 7, 10) },
{ 'V', new(0, 0, 7, 10) },
{ 'W', new(0, 0, 10, 10) },
{ 'X', new(0, 0, 6, 10) },
{ 'Y', new(0, 0, 6, 10) },
{ 'Z', new(0, 0, 6, 10) },
{ '[', new(0, 0, 3, 11) },
{ '\\', new(0, 0, 4, 11) },
{ ']', new(0, 0, 3, 11) },
{ '^', new(0, 0, 6, 7) },
{ '_', new(0, 0, 5, 11) },
{ '`', new(0, 0, 3, 4) },
{ 'a', new(0, 0, 5, 10) },
{ 'b', new(0, 0, 6, 10) },
{ 'c', new(0, 0, 5, 10) },
{ 'd', new(0, 0, 6, 10) },
{ 'e', new(0, 0, 5, 10) },
{ 'f', new(0, 0, 4, 10) },
{ 'g', new(0, 0, 6, 12) },
{ 'h', new(0, 0, 5, 10) },
{ 'i', new(0, 0, 2, 10) },
{ 'j', new(0, 0, 2, 12) },
{ 'k', new(0, 0, 5, 10) },
{ 'l', new(0, 0, 2, 10) },
{ 'm', new(0, 0, 8, 10) },
{ 'n', new(0, 0, 5, 10) },
{ 'o', new(0, 0, 6, 10) },
{ 'p', new(0, 0, 6, 12) },
{ 'q', new(0, 0, 6, 12) },
{ 'r', new(0, 0, 4, 10) },
{ 's', new(0, 0, 4, 10) },
{ 't', new(0, 0, 4, 10) },
{ 'u', new(0, 0, 5, 10) },
{ 'v', new(0, 0, 5, 10) },
{ 'w', new(0, 0, 8, 10) },
{ 'x', new(0, 0, 5, 10) },
{ 'y', new(0, 0, 5, 12) },
{ 'z', new(0, 0, 5, 10) },
{ '{', new(0, 0, 3, 11) },
{ '|', new(0, 0, 2, 12) },
{ '}', new(0, 0, 3, 11) },
{ '~', new(0, 0, 6, 7) }
{ '!', new(0, 0, 2, 8) },
{ '"', new(0, 0, 3, 3) },
{ '#', new(0, 0, 6, 7) },
{ '$', new(0, 0, 4, 9) },
{ '%', new(0, 0, 8, 8) },
{ '&', new(0, 0, 8, 8) },
{ '\'', new(0, 0, 1, 3) },
{ '(', new(0, 0, 3, 9) },
{ ')', new(0, 0, 3, 9) },
{ '*', new(0, 0, 4, 4) },
{ '+', new(0, 0, 5, 5) },
{ ',', new(0, 0, 2, 3) },
{ '-', new(0, 0, 3, 1) },
{ '.', new(0, 0, 2, 2) },
{ '/', new(0, 0, 5, 9) },
{ '0', new(0, 0, 5, 8) },
{ '1', new(0, 0, 3, 8) },
{ '2', new(0, 0, 5, 8) },
{ '3', new(0, 0, 5, 8) },
{ '4', new(0, 0, 5, 8) },
{ '5', new(0, 0, 4, 8) },
{ '6', new(0, 0, 5, 8) },
{ '7', new(0, 0, 5, 8) },
{ '8', new(0, 0, 5, 8) },
{ '9', new(0, 0, 5, 8) },
{ ':', new(0, 0, 2, 6) },
{ ';', new(0, 0, 2, 7) },
{ '<', new(0, 0, 5, 5) },
{ '=', new(0, 0, 5, 3) },
{ '>', new(0, 0, 5, 5) },
{ '?', new(0, 0, 4, 8) },
{ '@', new(0, 0, 8, 9) },
{ 'A', new(0, 0, 7, 8) },
{ 'B', new(0, 0, 5, 8) },
{ 'C', new(0, 0, 6, 8) },
{ 'D', new(0, 0, 6, 8) },
{ 'E', new(0, 0, 4, 8) },
{ 'F', new(0, 0, 4, 8) },
{ 'G', new(0, 0, 6, 8) },
{ 'H', new(0, 0, 6, 8) },
{ 'I', new(0, 0, 1, 8) },
{ 'J', new(0, 0, 3, 8) },
{ 'K', new(0, 0, 5, 8) },
{ 'L', new(0, 0, 4, 8) },
{ 'M', new(0, 0, 8, 8) },
{ 'N', new(0, 0, 6, 8) },
{ 'O', new(0, 0, 7, 8) },
{ 'P', new(0, 0, 5, 8) },
{ 'Q', new(0, 0, 8, 9) },
{ 'R', new(0, 0, 6, 8) },
{ 'S', new(0, 0, 5, 8) },
{ 'T', new(0, 0, 5, 8) },
{ 'U', new(0, 0, 6, 8) },
{ 'V', new(0, 0, 7, 8) },
{ 'W', new(0, 0, 10, 8) },
{ 'X', new(0, 0, 6, 8) },
{ 'Y', new(0, 0, 6, 8) },
{ 'Z', new(0, 0, 6, 8) },
{ '[', new(0, 0, 2, 9) },
{ '\\', new(0, 0, 5, 9) },
{ ']', new(0, 0, 2, 9) },
{ '^', new(0, 0, 5, 5) },
{ '_', new(0, 0, 5, 1) },
{ '`', new(0, 0, 2, 2) },
{ 'a', new(0, 0, 4, 6) },
{ 'b', new(0, 0, 5, 8) },
{ 'c', new(0, 0, 4, 6) },
{ 'd', new(0, 0, 5, 8) },
{ 'e', new(0, 0, 5, 6) },
{ 'f', new(0, 0, 4, 8) },
{ 'g', new(0, 0, 5, 8) },
{ 'h', new(0, 0, 5, 8) },
{ 'i', new(0, 0, 2, 8) },
{ 'j', new(0, 0, 3, 10) },
{ 'k', new(0, 0, 5, 8) },
{ 'l', new(0, 0, 1, 8) },
{ 'm', new(0, 0, 8, 6) },
{ 'n', new(0, 0, 5, 6) },
{ 'o', new(0, 0, 5, 6) },
{ 'p', new(0, 0, 5, 8) },
{ 'q', new(0, 0, 5, 8) },
{ 'r', new(0, 0, 3, 6) },
{ 's', new(0, 0, 4, 6) },
{ 't', new(0, 0, 3, 7) },
{ 'u', new(0, 0, 5, 6) },
{ 'v', new(0, 0, 5, 5) },
{ 'w', new(0, 0, 7, 5) },
{ 'x', new(0, 0, 5, 5) },
{ 'y', new(0, 0, 5, 8) },
{ 'z', new(0, 0, 5, 5) },
{ '{', new(0, 0, 3, 9) },
{ '|', new(0, 0, 1, 10) },
{ '}', new(0, 0, 3, 9) },
{ '~', new(0, 0, 5, 2) }
};
[Theory]
@ -1108,6 +1109,7 @@ public class TextLayoutTests
};
FontRectangle actual = TextMeasurer.MeasureSize(c.ToString(), options);
Assert.Equal(expected, actual);
}