зеркало из https://github.com/SixLabors/Fonts.git
Fix bounds and size measurement
This commit is contained in:
Родитель
5930793285
Коммит
203dac4c62
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче