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

167 lines
7.4 KiB
C#

// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Numerics;
using SixLabors.Fonts.Rendering;
using SixLabors.Fonts.Unicode;
namespace SixLabors.Fonts {
/// <summary>
/// Represents synthetic glyph metrics for an atomic inline placeholder.
/// </summary>
internal sealed class PlaceholderGlyphMetrics : FontGlyphMetrics
{
private readonly TextPlaceholder placeholder;
private readonly float pointSize;
private readonly float dpi;
/// <summary>
/// Initializes a new instance of the <see cref="PlaceholderGlyphMetrics"/> class.
/// </summary>
/// <param name="font">The font metrics used for shared line metrics and decoration settings.</param>
/// <param name="placeholder">The placeholder dimensions and alignment settings.</param>
/// <param name="pointSize">The point size used for layout.</param>
/// <param name="dpi">The resolution used to convert placeholder pixels into layout units.</param>
/// <param name="textRun">The text run this placeholder belongs to.</param>
internal PlaceholderGlyphMetrics(
StreamFontMetrics font,
TextPlaceholder placeholder,
float pointSize,
float dpi,
TextRun textRun)
: base(
font,
0,
CodePoint.ObjectReplacementChar,
GetBounds(placeholder, pointSize, dpi, font),
ToGlyphUnits(placeholder.Width, pointSize, dpi, font.ScaleFactor),
ToGlyphUnits(placeholder.Height, pointSize, dpi, font.ScaleFactor),
0,
0,
font.UnitsPerEm,
Vector2.Zero,
new Vector2(font.ScaleFactor),
textRun,
GlyphType.Placeholder)
{
this.placeholder = placeholder;
this.pointSize = pointSize;
this.dpi = dpi;
}
/// <inheritdoc/>
internal override FontGlyphMetrics CloneForRendering(TextRun textRun)
=> new PlaceholderGlyphMetrics(
this.FontMetrics,
this.placeholder,
this.pointSize,
this.dpi,
textRun);
/// <inheritdoc/>
internal override void RenderTo(
IGlyphRenderer renderer,
int graphemeIndex,
Vector2 glyphOrigin,
Vector2 decorationOrigin,
GlyphLayoutMode mode,
TextOptions options)
{
// Placeholders reserve layout space only; the caller owns the object rendering.
}
/// <summary>
/// Converts the placeholder box into glyph bounds in the same synthetic font-unit space as its advances.
/// </summary>
/// <param name="placeholder">The placeholder dimensions and baseline offset.</param>
/// <param name="pointSize">The point size used for layout.</param>
/// <param name="dpi">The resolution used to convert placeholder pixels into layout units.</param>
/// <param name="font">The font metrics used by glyph layout.</param>
/// <returns>The placeholder bounds expressed in synthetic font units.</returns>
private static Bounds GetBounds(TextPlaceholder placeholder, float pointSize, float dpi, StreamFontMetrics font)
{
float scaleFactor = font.ScaleFactor;
float width = ToGlyphUnitsFloat(placeholder.Width, pointSize, dpi, scaleFactor);
float height = ToGlyphUnitsFloat(placeholder.Height, pointSize, dpi, scaleFactor);
float baselineOffset = ToGlyphUnitsFloat(placeholder.BaselineOffset, pointSize, dpi, scaleFactor);
float lineHeight = font.UnitsPerEm;
float metricsDelta = (font.HorizontalMetrics.LineHeight - lineHeight) * .5F;
float ascender = font.HorizontalMetrics.Ascender - metricsDelta;
float descender = Math.Abs(font.HorizontalMetrics.Descender) - metricsDelta;
float coreHeight = ascender + descender + (2 * metricsDelta);
float extra = lineHeight - coreHeight;
// Top/middle/bottom align against the surrounding run font's normal
// line box, expressed relative to the text baseline in Y-up font units.
float lineTop = ascender + metricsDelta + (extra * .5F);
float lineBottom = lineTop - lineHeight;
float top = baselineOffset;
float bottom = baselineOffset - height;
switch (placeholder.Alignment)
{
case TextPlaceholderAlignment.AboveBaseline:
top = height;
bottom = 0;
break;
case TextPlaceholderAlignment.BelowBaseline:
top = 0;
bottom = -height;
break;
case TextPlaceholderAlignment.Top:
top = lineTop;
bottom = top - height;
break;
case TextPlaceholderAlignment.Bottom:
// Top, middle, and bottom align against the full line-height
// box, not just the ascender/descender band.
bottom = lineBottom;
top = bottom + height;
break;
case TextPlaceholderAlignment.Middle:
float center = (lineTop + lineBottom) * .5F;
top = center + (height * .5F);
bottom = center - (height * .5F);
break;
default:
top = baselineOffset;
bottom = baselineOffset - height;
break;
}
// Placeholder bounds are authored in device pixels and converted into
// synthetic font units so the normal glyph scaling path maps them back
// to device-space size while preserving the requested baseline alignment.
return new Bounds(0, top, width, bottom);
}
/// <summary>
/// Converts a placeholder pixel measurement into glyph units for the current layout scale.
/// </summary>
/// <param name="pixels">The placeholder measurement in pixels.</param>
/// <param name="pointSize">The point size used for layout.</param>
/// <param name="dpi">The resolution used to convert placeholder pixels into layout units.</param>
/// <param name="scaleFactor">The font scale factor used by glyph layout.</param>
/// <returns>The measurement expressed in synthetic font units.</returns>
private static ushort ToGlyphUnits(float pixels, float pointSize, float dpi, float scaleFactor)
=> (ushort)MathF.Round(ToGlyphUnitsFloat(pixels, pointSize, dpi, scaleFactor));
/// <summary>
/// Converts a placeholder pixel measurement into fractional glyph units for bounds placement.
/// </summary>
/// <param name="pixels">The placeholder measurement in pixels.</param>
/// <param name="pointSize">The point size used for layout.</param>
/// <param name="dpi">The resolution used to convert placeholder pixels into layout units.</param>
/// <param name="scaleFactor">The font scale factor used by glyph layout.</param>
/// <returns>The measurement expressed in synthetic font units.</returns>
private static float ToGlyphUnitsFloat(float pixels, float pointSize, float dpi, float scaleFactor)
=> pixels * scaleFactor / (pointSize * dpi);
}
}