Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 49 additions & 3 deletions src/SixLabors.Fonts/TextMeasurer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ public static FontRectangle MeasureSize(ReadOnlySpan<char> text, TextOptions opt
/// <param name="text">The text.</param>
/// <param name="options">The text shaping options.</param>
/// <returns>The rendered glyph bounds of the text if it was to be rendered.</returns>
/// <remarks>
/// This measures the tight ink bounds enclosing all rendered glyphs. The returned rectangle
/// may be smaller or larger than the logical advance and may have a non-zero origin.
/// Use <see cref="MeasureAdvance(string, TextOptions)"/> for the logical layout box or
/// <see cref="MeasureRenderableBounds(string, TextOptions)"/> for the union of both.
/// </remarks>
public static FontRectangle MeasureBounds(string text, TextOptions options)
=> MeasureBounds(text.AsSpan(), options);

Expand All @@ -89,6 +95,8 @@ public static FontRectangle MeasureBounds(string text, TextOptions options)
/// The union of the logical advance rectangle and the rendered glyph bounds if the text was to be rendered.
/// </returns>
/// <remarks>
/// The returned rectangle is in absolute coordinates and is large enough to contain both the logical advance
/// rectangle and the rendered glyph bounds.
/// Use this method when both typographic advance and rendered glyph overshoot must fit within the same rectangle.
/// </remarks>
public static FontRectangle MeasureRenderableBounds(string text, TextOptions options)
Expand All @@ -100,6 +108,12 @@ public static FontRectangle MeasureRenderableBounds(string text, TextOptions opt
/// <param name="text">The text.</param>
/// <param name="options">The text shaping options.</param>
/// <returns>The rendered glyph bounds of the text if it was to be rendered.</returns>
/// <remarks>
/// This measures the tight ink bounds enclosing all rendered glyphs. The returned rectangle
/// may be smaller or larger than the logical advance and may have a non-zero origin.
/// Use <see cref="MeasureAdvance(ReadOnlySpan{char}, TextOptions)"/> for the logical layout box or
/// <see cref="MeasureRenderableBounds(ReadOnlySpan{char}, TextOptions)"/> for the union of both.
/// </remarks>
public static FontRectangle MeasureBounds(ReadOnlySpan<char> text, TextOptions options)
=> GetBounds(TextLayout.GenerateLayout(text, options), options.Dpi);

Expand All @@ -112,6 +126,8 @@ public static FontRectangle MeasureBounds(ReadOnlySpan<char> text, TextOptions o
/// The union of the logical advance rectangle and the rendered glyph bounds if the text was to be rendered.
/// </returns>
/// <remarks>
/// The returned rectangle is in absolute coordinates and is large enough to contain both the logical advance
/// rectangle and the rendered glyph bounds.
/// Use this method when both typographic advance and rendered glyph overshoot must fit within the same rectangle.
/// </remarks>
public static FontRectangle MeasureRenderableBounds(ReadOnlySpan<char> text, TextOptions options)
Expand All @@ -122,8 +138,9 @@ public static FontRectangle MeasureRenderableBounds(ReadOnlySpan<char> text, Tex
}

FontRectangle advance = MeasureAdvance(text, options);
FontRectangle absoluteAdvance = new(options.Origin.X, options.Origin.Y, advance.Width, advance.Height);
FontRectangle bounds = MeasureBounds(text, options);
return FontRectangle.Union(advance, bounds);
return FontRectangle.Union(absoluteAdvance, bounds);
}

/// <summary>
Expand All @@ -133,6 +150,11 @@ public static FontRectangle MeasureRenderableBounds(ReadOnlySpan<char> text, Tex
/// <param name="options">The text shaping options.</param>
/// <param name="advances">The list of per-entry logical advances of the text if it was to be rendered.</param>
/// <returns>Whether any of the entries had non-empty advances.</returns>
/// <remarks>
/// Each entry reflects the typographic advance width and height for one character.
/// Use <see cref="TryMeasureCharacterBounds(string, TextOptions, out ReadOnlySpan{GlyphBounds})"/> for per-character ink bounds or
/// <see cref="TryMeasureCharacterRenderableBounds(string, TextOptions, out ReadOnlySpan{GlyphBounds})"/> for the union of both.
/// </remarks>
public static bool TryMeasureCharacterAdvances(string text, TextOptions options, out ReadOnlySpan<GlyphBounds> advances)
=> TryMeasureCharacterAdvances(text.AsSpan(), options, out advances);

Expand All @@ -143,6 +165,11 @@ public static bool TryMeasureCharacterAdvances(string text, TextOptions options,
/// <param name="options">The text shaping options.</param>
/// <param name="advances">The list of per-entry logical advances of the text if it was to be rendered.</param>
/// <returns>Whether any of the entries had non-empty advances.</returns>
/// <remarks>
/// Each entry reflects the typographic advance width and height for one character.
/// Use <see cref="TryMeasureCharacterBounds(ReadOnlySpan{char}, TextOptions, out ReadOnlySpan{GlyphBounds})"/> for per-character ink bounds or
/// <see cref="TryMeasureCharacterRenderableBounds(ReadOnlySpan{char}, TextOptions, out ReadOnlySpan{GlyphBounds})"/> for the union of both.
/// </remarks>
public static bool TryMeasureCharacterAdvances(ReadOnlySpan<char> text, TextOptions options, out ReadOnlySpan<GlyphBounds> advances)
=> TryGetCharacterAdvances(TextLayout.GenerateLayout(text, options), options.Dpi, out advances);

Expand Down Expand Up @@ -173,6 +200,11 @@ public static bool TryMeasureCharacterSizes(ReadOnlySpan<char> text, TextOptions
/// <param name="options">The text shaping options.</param>
/// <param name="bounds">The list of per-entry rendered glyph bounds of the text if it was to be rendered.</param>
/// <returns>Whether any of the entries had non-empty bounds.</returns>
/// <remarks>
/// Each entry reflects the tight ink bounds of one rendered glyph.
/// Use <see cref="TryMeasureCharacterAdvances(string, TextOptions, out ReadOnlySpan{GlyphBounds})"/> for per-character logical advances or
/// <see cref="TryMeasureCharacterRenderableBounds(string, TextOptions, out ReadOnlySpan{GlyphBounds})"/> for the union of both.
/// </remarks>
public static bool TryMeasureCharacterBounds(string text, TextOptions options, out ReadOnlySpan<GlyphBounds> bounds)
=> TryMeasureCharacterBounds(text.AsSpan(), options, out bounds);

Expand All @@ -183,6 +215,10 @@ public static bool TryMeasureCharacterBounds(string text, TextOptions options, o
/// <param name="options">The text shaping options.</param>
/// <param name="bounds">The list of per-entry renderable bounds of the text if it was to be rendered.</param>
/// <returns>Whether any of the entries had non-empty bounds.</returns>
/// <remarks>
/// Each returned rectangle is in absolute coordinates and is large enough to contain both the logical advance
/// rectangle and the rendered glyph bounds for the corresponding laid-out entry.
/// </remarks>
public static bool TryMeasureCharacterRenderableBounds(string text, TextOptions options, out ReadOnlySpan<GlyphBounds> bounds)
=> TryMeasureCharacterRenderableBounds(text.AsSpan(), options, out bounds);

Expand All @@ -193,6 +229,11 @@ public static bool TryMeasureCharacterRenderableBounds(string text, TextOptions
/// <param name="options">The text shaping options.</param>
/// <param name="bounds">The list of per-entry rendered glyph bounds of the text if it was to be rendered.</param>
/// <returns>Whether any of the entries had non-empty bounds.</returns>
/// <remarks>
/// Each entry reflects the tight ink bounds of one rendered glyph.
/// Use <see cref="TryMeasureCharacterAdvances(ReadOnlySpan{char}, TextOptions, out ReadOnlySpan{GlyphBounds})"/> for per-character logical advances or
/// <see cref="TryMeasureCharacterRenderableBounds(ReadOnlySpan{char}, TextOptions, out ReadOnlySpan{GlyphBounds})"/> for the union of both.
/// </remarks>
public static bool TryMeasureCharacterBounds(ReadOnlySpan<char> text, TextOptions options, out ReadOnlySpan<GlyphBounds> bounds)
=> TryGetCharacterBounds(TextLayout.GenerateLayout(text, options), options.Dpi, out bounds);

Expand All @@ -203,6 +244,10 @@ public static bool TryMeasureCharacterBounds(ReadOnlySpan<char> text, TextOption
/// <param name="options">The text shaping options.</param>
/// <param name="bounds">The list of per-entry renderable bounds of the text if it was to be rendered.</param>
/// <returns>Whether any of the entries had non-empty bounds.</returns>
/// <remarks>
/// Each returned rectangle is in absolute coordinates and is large enough to contain both the logical advance
/// rectangle and the rendered glyph bounds for the corresponding laid-out entry.
/// </remarks>
public static bool TryMeasureCharacterRenderableBounds(ReadOnlySpan<char> text, TextOptions options, out ReadOnlySpan<GlyphBounds> bounds)
=> TryGetCharacterRenderableBounds(TextLayout.GenerateLayout(text, options), options.Dpi, out bounds);

Expand Down Expand Up @@ -510,8 +555,9 @@ internal static bool TryGetCharacterRenderableBounds(IReadOnlyList<GlyphLayout>
for (int i = 0; i < glyphLayouts.Count; i++)
{
GlyphLayout g = glyphLayouts[i];
FontRectangle advance = new(0, 0, g.AdvanceX * dpi, g.AdvanceY * dpi);
FontRectangle bounds = FontRectangle.Union(advance, g.BoundingBox(dpi));
FontRectangle glyphBounds = g.BoundingBox(dpi);
FontRectangle advance = new(g.BoxLocation.X * dpi, g.BoxLocation.Y * dpi, g.AdvanceX * dpi, g.AdvanceY * dpi);
FontRectangle bounds = FontRectangle.Union(advance, glyphBounds);
hasSize |= bounds.Width > 0 || bounds.Height > 0;
characterBoundsList[i] = new GlyphBounds(g.Glyph.GlyphMetrics.CodePoint, in bounds, g.GraphemeIndex, g.StringIndex);
}
Expand Down
9 changes: 6 additions & 3 deletions tests/SixLabors.Fonts.Tests/TextLayoutTestUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@ public static void TestLayout(
params object[] properties)
{
#if SUPPORTS_DRAWING
FontRectangle renderBounds = TextMeasurer.MeasureRenderableBounds(text, options);
int width = (int)(Math.Ceiling(renderBounds.Width) + Math.Ceiling(options.Origin.X));
int height = (int)(Math.Ceiling(renderBounds.Height) + Math.Ceiling(options.Origin.Y));
FontRectangle advance = TextMeasurer.MeasureAdvance(text, options);
FontRectangle bounds = TextMeasurer.MeasureBounds(text, options);
FontRectangle renderBounds = FontRectangle.Union(advance, bounds);

int width = Math.Max(1, (int)Math.Ceiling(options.Origin.X + renderBounds.Right - Math.Min(0, renderBounds.Left)));
int height = Math.Max(1, (int)Math.Ceiling(options.Origin.Y + renderBounds.Bottom - Math.Min(0, renderBounds.Top)));

bool isVertical = !options.LayoutMode.IsHorizontal();
int wrappingLength = isVertical
Expand Down
Loading