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
107 changes: 107 additions & 0 deletions src/SixLabors.Fonts/TextLayout.Visitors.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

namespace SixLabors.Fonts;

/// <content>
/// Visitor types for streaming laid-out glyphs through <see cref="LayoutText{TVisitor}"/>.
/// </content>
internal static partial class TextLayout
{
/// <summary>
/// Receives laid-out glyphs streamed from <see cref="LayoutText{TVisitor}(TextBox, TextOptions, ref TVisitor)"/>.
/// Implementations are value types so the generic dispatch is specialized by the JIT and no boxing or
/// delegate allocation is required.
/// </summary>
internal interface IGlyphLayoutVisitor
{
/// <summary>
/// Invoked once for each laid-out glyph in layout order.
/// </summary>
/// <param name="glyph">The laid-out glyph.</param>
public void Visit(in GlyphLayout glyph);
}

/// <summary>
/// Collects streamed glyphs into a <see cref="List{T}"/>.
/// </summary>
internal readonly struct GlyphLayoutCollector : IGlyphLayoutVisitor
{
/// <summary>
/// Initializes a new instance of the <see cref="GlyphLayoutCollector"/> struct.
/// </summary>
/// <param name="glyphs">The list to collect streamed glyphs into.</param>
public GlyphLayoutCollector(List<GlyphLayout> glyphs) => this.Glyphs = glyphs;

/// <summary>
/// Gets the accumulated glyphs.
/// </summary>
public List<GlyphLayout> Glyphs { get; }

/// <inheritdoc/>
public readonly void Visit(in GlyphLayout glyph) => this.Glyphs.Add(glyph);
}

/// <summary>
/// Accumulates the union of glyph ink bounds as glyphs are streamed, avoiding the allocation
/// of a <see cref="List{T}"/> and a second iteration pass.
/// </summary>
internal struct GlyphBoundsAccumulator : IGlyphLayoutVisitor
{
private readonly float dpi;
private float left;
private float top;
private float right;
private float bottom;
private bool any;

/// <summary>
/// Initializes a new instance of the <see cref="GlyphBoundsAccumulator"/> struct.
/// </summary>
/// <param name="dpi">The device-independent pixels per unit for the containing <see cref="TextOptions"/>.</param>
public GlyphBoundsAccumulator(float dpi)
{
this.dpi = dpi;
this.left = float.MaxValue;
this.top = float.MaxValue;
this.right = float.MinValue;
this.bottom = float.MinValue;
this.any = false;
}

/// <inheritdoc/>
public void Visit(in GlyphLayout glyph)
{
FontRectangle box = glyph.BoundingBox(this.dpi);

if (box.Left < this.left)
{
this.left = box.Left;
}

if (box.Top < this.top)
{
this.top = box.Top;
}

if (box.Right > this.right)
{
this.right = box.Right;
}

if (box.Bottom > this.bottom)
{
this.bottom = box.Bottom;
}

this.any = true;
}

/// <summary>
/// Returns the accumulated ink bounds, or <see cref="FontRectangle.Empty"/> if no glyphs were visited.
/// </summary>
/// <returns>The union of the ink bounds of all visited glyphs.</returns>
public readonly FontRectangle Result()
=> this.any ? FontRectangle.FromLTRB(this.left, this.top, this.right, this.bottom) : FontRectangle.Empty;
}
}
Loading
Loading