// 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 { /// /// Represents synthetic glyph metrics for an atomic inline placeholder. /// internal sealed class PlaceholderGlyphMetrics : FontGlyphMetrics { private readonly TextPlaceholder placeholder; private readonly float pointSize; private readonly float dpi; /// /// Initializes a new instance of the class. /// /// The font metrics used for shared line metrics and decoration settings. /// The placeholder dimensions and alignment settings. /// The point size used for layout. /// The resolution used to convert placeholder pixels into layout units. /// The text run this placeholder belongs to. 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; } /// internal override FontGlyphMetrics CloneForRendering(TextRun textRun) => new PlaceholderGlyphMetrics( this.FontMetrics, this.placeholder, this.pointSize, this.dpi, textRun); /// 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. } /// /// Converts the placeholder box into glyph bounds in the same synthetic font-unit space as its advances. /// /// The placeholder dimensions and baseline offset. /// The point size used for layout. /// The resolution used to convert placeholder pixels into layout units. /// The font metrics used by glyph layout. /// The placeholder bounds expressed in synthetic font units. 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); } /// /// Converts a placeholder pixel measurement into glyph units for the current layout scale. /// /// The placeholder measurement in pixels. /// The point size used for layout. /// The resolution used to convert placeholder pixels into layout units. /// The font scale factor used by glyph layout. /// The measurement expressed in synthetic font units. private static ushort ToGlyphUnits(float pixels, float pointSize, float dpi, float scaleFactor) => (ushort)MathF.Round(ToGlyphUnitsFloat(pixels, pointSize, dpi, scaleFactor)); /// /// Converts a placeholder pixel measurement into fractional glyph units for bounds placement. /// /// The placeholder measurement in pixels. /// The point size used for layout. /// The resolution used to convert placeholder pixels into layout units. /// The font scale factor used by glyph layout. /// The measurement expressed in synthetic font units. private static float ToGlyphUnitsFloat(float pixels, float pointSize, float dpi, float scaleFactor) => pixels * scaleFactor / (pointSize * dpi); } }