ImageSharp/SixLabors.Fonts/GlyphLayout.cs
2026-08-03 22:31:27 +02:00

193 lines
6.4 KiB
C#

// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.Fonts.Unicode;
namespace SixLabors.Fonts {
/// <summary>
/// Represents the layout positions of a glyph entry emitted from a laid-out <see cref="TextBlock"/>.
/// </summary>
internal readonly struct GlyphLayout
{
internal GlyphLayout(
Glyph glyph,
Font font,
Vector2 advanceOrigin,
Vector2 glyphOrigin,
Vector2 decorationOrigin,
float advanceWidth,
float advanceHeight,
GlyphLayoutMode layoutMode,
int bidiLevel,
bool isStartOfLine,
int graphemeIndex,
int stringIndex)
{
this.Glyph = glyph;
this.Font = font;
this.CodePoint = glyph.GlyphMetrics.CodePoint;
this.AdvanceOrigin = advanceOrigin;
this.GlyphOrigin = glyphOrigin;
this.DecorationOrigin = decorationOrigin;
this.AdvanceX = advanceWidth;
this.AdvanceY = advanceHeight;
this.LayoutMode = layoutMode;
this.BidiLevel = bidiLevel;
this.IsStartOfLine = isStartOfLine;
this.GraphemeIndex = graphemeIndex;
this.StringIndex = stringIndex;
}
/// <summary>
/// Gets the font-specific glyph for this laid-out glyph entry.
/// </summary>
public Glyph Glyph { get; }
/// <summary>
/// Gets the font used to shape and render this laid-out glyph entry.
/// </summary>
public Font Font { get; }
/// <summary>
/// Gets the code point represented by this glyph.
/// </summary>
public CodePoint CodePoint { get; }
/// <summary>
/// Gets the origin of the logical advance box in DPI-normalized layout units.
/// </summary>
/// <remarks>
/// Multiply by the target DPI to convert to device pixels.
/// </remarks>
public Vector2 AdvanceOrigin { get; }
/// <summary>
/// Gets the origin used to render the glyph outline in DPI-normalized layout units.
/// </summary>
/// <remarks>
/// Multiply by the target DPI to convert to device pixels.
/// </remarks>
public Vector2 GlyphOrigin { get; }
/// <summary>
/// Gets the origin used to render text decorations in DPI-normalized layout units.
/// </summary>
/// <remarks>
/// Multiply by the target DPI to convert to device pixels.
/// </remarks>
public Vector2 DecorationOrigin { get; }
/// <summary>
/// Gets the advance in the x direction in DPI-normalized layout units.
/// </summary>
/// <remarks>
/// Multiply by the target DPI to convert to device pixels.
/// </remarks>
public float AdvanceX { get; }
/// <summary>
/// Gets the advance in the y direction in DPI-normalized layout units.
/// </summary>
/// <remarks>
/// Multiply by the target DPI to convert to device pixels.
/// </remarks>
public float AdvanceY { get; }
/// <summary>
/// Gets the glyph layout mode.
/// </summary>
public GlyphLayoutMode LayoutMode { get; }
/// <summary>
/// Gets the resolved bidi embedding level.
/// </summary>
internal int BidiLevel { get; }
/// <summary>
/// Gets a value indicating whether this glyph is the first glyph on a new line.
/// </summary>
public bool IsStartOfLine { get; }
/// <summary>
/// Gets the zero-based grapheme index in the original text.
/// </summary>
public int GraphemeIndex { get; }
/// <summary>
/// Gets the zero-based UTF-16 code unit index in the original text.
/// </summary>
public int StringIndex { get; }
/// <summary>
/// Gets a value indicating whether the glyph represents a whitespace character.
/// </summary>
/// <returns>The <see cref="bool"/>.</returns>
public bool IsWhiteSpace() => UnicodeUtility.ShouldRenderWhiteSpaceOnly(this.CodePoint);
/// <summary>
/// Measures the positioned logical advance rectangle in pixel units.
/// </summary>
/// <param name="dpi">The target DPI.</param>
/// <returns>The measured advance rectangle.</returns>
internal FontRectangle MeasureAdvance(float dpi)
=> new(
this.AdvanceOrigin.X * dpi,
this.AdvanceOrigin.Y * dpi,
this.AdvanceX * dpi,
this.AdvanceY * dpi);
/// <summary>
/// Measures the rendered glyph bounds in pixel units.
/// </summary>
/// <param name="dpi">The target DPI.</param>
/// <returns>The measured rendered bounds.</returns>
internal FontRectangle MeasureBounds(float dpi)
{
// Same logic as in GlyphMetrics.RenderTo.
Vector2 glyphOrigin = this.GlyphOrigin * dpi;
FontRectangle box = this.Glyph.BoundingBox(this.LayoutMode, glyphOrigin, dpi);
// Whitespace uses the layout advance because it occupies measurable
// text space even though the renderer suppresses its outline.
if (this.IsWhiteSpace())
{
if (this.LayoutMode == GlyphLayoutMode.Vertical)
{
return new FontRectangle(
box.X,
box.Y,
box.Width,
this.AdvanceY * dpi);
}
if (this.LayoutMode == GlyphLayoutMode.VerticalRotated)
{
return new FontRectangle(
box.X,
box.Y,
0,
this.AdvanceY * dpi);
}
return new FontRectangle(
box.X,
box.Y,
this.AdvanceX * dpi,
box.Height);
}
return box;
}
/// <inheritdoc/>
public override string ToString()
{
string s = this.IsStartOfLine ? "@ " : string.Empty;
string ws = this.IsWhiteSpace() ? "!" : string.Empty;
Vector2 l = this.GlyphOrigin;
return $"{s}{ws}{this.CodePoint.ToDebuggerDisplay()} {l.X},{l.Y} {this.AdvanceX}x{this.AdvanceY}";
}
}
}