Skip to content

Commit 233b04e

Browse files
Add new text measuring tests
1 parent 63dbd46 commit 233b04e

5 files changed

Lines changed: 329 additions & 30 deletions

File tree

src/ImageSharp.Drawing/ImageSharp.Drawing.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
<None Include="..\..\shared-infrastructure\branding\icons\imagesharp.drawing\sixlabors.imagesharp.drawing.128.png" Pack="true" PackagePath="" />
4646
</ItemGroup>
4747
<ItemGroup>
48-
<PackageReference Include="SixLabors.Fonts" Version="3.0.0-alpha.0.23" />
48+
<PackageReference Include="SixLabors.Fonts" Version="3.0.0-alpha.0.25" />
4949
<PackageReference Include="SixLabors.ImageSharp" Version="4.0.0-alpha.0.78" />
5050
<PackageReference Include="SixLabors.PolygonClipper" Version="1.0.0-alpha.0.52" />
5151
</ItemGroup>

src/ImageSharp.Drawing/Processing/DrawingCanvas{TPixel}.cs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -610,7 +610,7 @@ public RectangleF MeasureTextAdvance(RichTextOptions textOptions, string text)
610610
Guard.NotNull(text, nameof(text));
611611

612612
FontRectangle advance = TextMeasurer.MeasureAdvance(text, textOptions);
613-
return RectangleF.FromLTRB(0, 0, advance.Width, advance.Height);
613+
return RectangleF.FromLTRB(advance.Left, advance.Top, advance.Right, advance.Bottom);
614614
}
615615

616616
/// <inheritdoc />
@@ -624,6 +624,17 @@ public RectangleF MeasureTextBounds(RichTextOptions textOptions, string text)
624624
return RectangleF.FromLTRB(bounds.Left, bounds.Top, bounds.Right, bounds.Bottom);
625625
}
626626

627+
/// <inheritdoc />
628+
public RectangleF MeasureTextRenderableBounds(RichTextOptions textOptions, string text)
629+
{
630+
this.EnsureNotDisposed();
631+
Guard.NotNull(textOptions, nameof(textOptions));
632+
Guard.NotNull(text, nameof(text));
633+
634+
FontRectangle renderableBounds = TextMeasurer.MeasureRenderableBounds(text, textOptions);
635+
return RectangleF.FromLTRB(renderableBounds.Left, renderableBounds.Top, renderableBounds.Right, renderableBounds.Bottom);
636+
}
637+
627638
/// <inheritdoc />
628639
public RectangleF MeasureTextSize(RichTextOptions textOptions, string text)
629640
{
@@ -632,7 +643,7 @@ public RectangleF MeasureTextSize(RichTextOptions textOptions, string text)
632643
Guard.NotNull(text, nameof(text));
633644

634645
FontRectangle size = TextMeasurer.MeasureSize(text, textOptions);
635-
return RectangleF.FromLTRB(0, 0, size.Width, size.Height);
646+
return RectangleF.FromLTRB(size.Left, size.Top, size.Right, size.Bottom);
636647
}
637648

638649
/// <inheritdoc />
@@ -655,6 +666,16 @@ public bool TryMeasureCharacterBounds(RichTextOptions textOptions, string text,
655666
return TextMeasurer.TryMeasureCharacterBounds(text, textOptions, out bounds);
656667
}
657668

669+
/// <inheritdoc />
670+
public bool TryMeasureCharacterRenderableBounds(RichTextOptions textOptions, string text, out ReadOnlySpan<GlyphBounds> bounds)
671+
{
672+
this.EnsureNotDisposed();
673+
Guard.NotNull(textOptions, nameof(textOptions));
674+
Guard.NotNull(text, nameof(text));
675+
676+
return TextMeasurer.TryMeasureCharacterRenderableBounds(text, textOptions, out bounds);
677+
}
678+
658679
/// <inheritdoc />
659680
public bool TryMeasureCharacterSizes(RichTextOptions textOptions, string text, out ReadOnlySpan<GlyphBounds> sizes)
660681
{

src/ImageSharp.Drawing/Processing/IDrawingCanvas.cs

Lines changed: 102 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -236,70 +236,145 @@ public void DrawGlyphs(
236236
IReadOnlyList<GlyphPathCollection> glyphs);
237237

238238
/// <summary>
239-
/// Measures the advance box of the specified text.
239+
/// Measures the logical advance of the text in pixel units.
240240
/// </summary>
241-
/// <param name="textOptions">Text layout options.</param>
241+
/// <param name="textOptions">The text shaping and layout options.</param>
242242
/// <param name="text">The text to measure.</param>
243-
/// <returns>The measured advance as a rectangle in px units.</returns>
243+
/// <returns>The logical advance rectangle of the text if it was to be rendered.</returns>
244+
/// <remarks>
245+
/// This measurement reflects line-box height and horizontal or vertical text advance from the layout model.
246+
/// It does not guarantee that all rendered glyph pixels fit within the returned rectangle.
247+
/// Use <see cref="MeasureTextBounds"/> for glyph ink bounds or
248+
/// <see cref="MeasureTextRenderableBounds"/> for the union of logical advance and rendered bounds.
249+
/// </remarks>
244250
public RectangleF MeasureTextAdvance(RichTextOptions textOptions, string text);
245251

246252
/// <summary>
247-
/// Measures the tight bounds of the specified text.
253+
/// Measures the rendered glyph bounds of the text in pixel units.
248254
/// </summary>
249-
/// <param name="textOptions">Text layout options.</param>
255+
/// <param name="textOptions">The text shaping and layout options.</param>
250256
/// <param name="text">The text to measure.</param>
251-
/// <returns>The measured bounds rectangle in px units.</returns>
257+
/// <returns>The rendered glyph bounds of the text if it was to be rendered.</returns>
258+
/// <remarks>
259+
/// This measures the tight ink bounds enclosing all rendered glyphs. The returned rectangle
260+
/// may be smaller or larger than the logical advance and may have a non-zero origin.
261+
/// Use <see cref="MeasureTextAdvance"/> for the logical layout box or
262+
/// <see cref="MeasureTextRenderableBounds"/> for the union of both.
263+
/// </remarks>
252264
public RectangleF MeasureTextBounds(RichTextOptions textOptions, string text);
253265

254266
/// <summary>
255-
/// Measures the size of the specified text.
267+
/// Measures the full renderable bounds of the text in pixel units.
256268
/// </summary>
257-
/// <param name="textOptions">Text layout options.</param>
269+
/// <param name="textOptions">The text shaping and layout options.</param>
258270
/// <param name="text">The text to measure.</param>
259-
/// <returns>The measured size as a rectangle in px units.</returns>
271+
/// <returns>
272+
/// The union of the logical advance rectangle and the rendered glyph bounds if the text was to be rendered.
273+
/// </returns>
274+
/// <remarks>
275+
/// The returned rectangle is in absolute coordinates and is large enough to contain both the logical advance
276+
/// rectangle and the rendered glyph bounds.
277+
/// Use this method when both typographic advance and rendered glyph overshoot must fit within the same rectangle.
278+
/// </remarks>
279+
public RectangleF MeasureTextRenderableBounds(RichTextOptions textOptions, string text);
280+
281+
/// <summary>
282+
/// Measures the normalized rendered size of the text in pixel units.
283+
/// </summary>
284+
/// <param name="textOptions">The text shaping and layout options.</param>
285+
/// <param name="text">The text to measure.</param>
286+
/// <returns>The rendered size of the text with the origin normalized to <c>(0, 0)</c>.</returns>
287+
/// <remarks>
288+
/// This is equivalent to measuring the rendered bounds and returning only the width and height.
289+
/// Use <see cref="MeasureTextBounds"/> when the returned X and Y offset are also required.
290+
/// </remarks>
260291
public RectangleF MeasureTextSize(RichTextOptions textOptions, string text);
261292

262293
/// <summary>
263-
/// Tries to measure per-character advances for the specified text.
294+
/// Measures the logical advance of each laid-out character entry in pixel units.
264295
/// </summary>
265-
/// <param name="textOptions">Text layout options.</param>
296+
/// <param name="textOptions">The text shaping and layout options.</param>
266297
/// <param name="text">The text to measure.</param>
267-
/// <param name="advances">Receives per-character advance metrics in px units.</param>
268-
/// <returns><see langword="true"/> if all character advances were measured; otherwise <see langword="false"/>.</returns>
298+
/// <param name="advances">The list of per-entry logical advances of the text if it was to be rendered.</param>
299+
/// <returns>Whether any of the entries had non-empty advances.</returns>
300+
/// <remarks>
301+
/// Each entry reflects the typographic advance width and height for one character.
302+
/// Use <see cref="TryMeasureCharacterBounds"/> for per-character ink bounds or
303+
/// <see cref="TryMeasureCharacterRenderableBounds"/> for the union of both.
304+
/// </remarks>
269305
public bool TryMeasureCharacterAdvances(RichTextOptions textOptions, string text, out ReadOnlySpan<GlyphBounds> advances);
270306

271307
/// <summary>
272-
/// Tries to measure per-character bounds for the specified text.
308+
/// Measures the rendered glyph bounds of each laid-out character entry in pixel units.
273309
/// </summary>
274-
/// <param name="textOptions">Text layout options.</param>
310+
/// <param name="textOptions">The text shaping and layout options.</param>
275311
/// <param name="text">The text to measure.</param>
276-
/// <param name="bounds">Receives per-character bounds in px units.</param>
277-
/// <returns><see langword="true"/> if all character bounds were measured; otherwise <see langword="false"/>.</returns>
312+
/// <param name="bounds">The list of per-entry rendered glyph bounds of the text if it was to be rendered.</param>
313+
/// <returns>Whether any of the entries had non-empty bounds.</returns>
314+
/// <remarks>
315+
/// Each entry reflects the tight ink bounds of one rendered glyph.
316+
/// Use <see cref="TryMeasureCharacterAdvances"/> for per-character logical advances or
317+
/// <see cref="TryMeasureCharacterRenderableBounds"/> for the union of both.
318+
/// </remarks>
278319
public bool TryMeasureCharacterBounds(RichTextOptions textOptions, string text, out ReadOnlySpan<GlyphBounds> bounds);
279320

280321
/// <summary>
281-
/// Tries to measure per-character sizes for the specified text.
322+
/// Measures the full renderable bounds of each laid-out character entry in pixel units.
282323
/// </summary>
283-
/// <param name="textOptions">Text layout options.</param>
324+
/// <param name="textOptions">The text shaping and layout options.</param>
284325
/// <param name="text">The text to measure.</param>
285-
/// <param name="sizes">Receives per-character sizes in px units.</param>
286-
/// <returns><see langword="true"/> if all character sizes were measured; otherwise <see langword="false"/>.</returns>
326+
/// <param name="bounds">The list of per-entry renderable bounds of the text if it was to be rendered.</param>
327+
/// <returns>Whether any of the entries had non-empty bounds.</returns>
328+
/// <remarks>
329+
/// Each returned rectangle is in absolute coordinates and is large enough to contain both the logical advance
330+
/// rectangle and the rendered glyph bounds for the corresponding laid-out entry.
331+
/// Use this when both typographic advance and rendered glyph overshoot must fit within the same rectangle.
332+
/// </remarks>
333+
public bool TryMeasureCharacterRenderableBounds(RichTextOptions textOptions, string text, out ReadOnlySpan<GlyphBounds> bounds);
334+
335+
/// <summary>
336+
/// Measures the normalized rendered size of each laid-out character entry in pixel units.
337+
/// </summary>
338+
/// <param name="textOptions">The text shaping and layout options.</param>
339+
/// <param name="text">The text to measure.</param>
340+
/// <param name="sizes">The list of per-entry rendered sizes with the origin normalized to <c>(0, 0)</c>.</param>
341+
/// <returns>Whether any of the entries had non-empty dimensions.</returns>
342+
/// <remarks>
343+
/// This is equivalent to measuring per-character bounds and returning only the width and height.
344+
/// Use <see cref="TryMeasureCharacterBounds"/> when the returned X and Y offset are also required.
345+
/// </remarks>
287346
public bool TryMeasureCharacterSizes(RichTextOptions textOptions, string text, out ReadOnlySpan<GlyphBounds> sizes);
288347

289348
/// <summary>
290-
/// Counts the rendered text lines for the specified text.
349+
/// Gets the number of laid-out lines contained within the text.
291350
/// </summary>
292-
/// <param name="textOptions">Text layout options.</param>
351+
/// <param name="textOptions">The text shaping and layout options.</param>
293352
/// <param name="text">The text to measure.</param>
294-
/// <returns>The number of rendered lines.</returns>
353+
/// <returns>The laid-out line count.</returns>
295354
public int CountTextLines(RichTextOptions textOptions, string text);
296355

297356
/// <summary>
298-
/// Gets line metrics for the specified text.
357+
/// Gets per-line layout metrics for the supplied text.
299358
/// </summary>
300-
/// <param name="textOptions">Text layout options.</param>
359+
/// <param name="textOptions">The text shaping and layout options.</param>
301360
/// <param name="text">The text to measure.</param>
302-
/// <returns>An array of line metrics in px units.</returns>
361+
/// <returns>
362+
/// An array of <see cref="LineMetrics"/> in pixel units, one entry per laid-out line.
363+
/// </returns>
364+
/// <remarks>
365+
/// <para>
366+
/// The returned <see cref="LineMetrics.Start"/> and <see cref="LineMetrics.Extent"/> are expressed
367+
/// in the primary flow direction for the active layout mode.
368+
/// </para>
369+
/// <para>
370+
/// <see cref="LineMetrics.Ascender"/>, <see cref="LineMetrics.Baseline"/>, and <see cref="LineMetrics.Descender"/>
371+
/// are line-box positions relative to the current line origin and are suitable for drawing guide lines.
372+
/// </para>
373+
/// <list type="bullet">
374+
/// <item><description>Horizontal layouts: Start = X position, Extent = width.</description></item>
375+
/// <item><description>Vertical layouts: Start = Y position, Extent = height.</description></item>
376+
/// </list>
377+
/// </remarks>
303378
public LineMetrics[] GetTextLineMetrics(RichTextOptions textOptions, string text);
304379

305380
/// <summary>

0 commit comments

Comments
 (0)