// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.Fonts;
using SixLabors.Fonts.Rendering;
namespace SixLabors.ImageSharp.Drawing.Text {
///
/// A rendering surface that Fonts can use to generate shapes by following a path.
/// Each glyph is positioned along the path and rotated to match the path tangent
/// at the glyph's horizontal center.
///
internal sealed class PathGlyphBuilder : GlyphBuilder
{
///
/// The path that glyphs are laid out along. Exposed as
/// to access the method for efficient
/// position + tangent queries.
///
private readonly IPathInternals path;
///
/// Initializes a new instance of the class.
///
/// The path to render the glyphs along.
public PathGlyphBuilder(IPath path)
{
if (path is IPathInternals internals)
{
this.path = internals;
}
else
{
// Wrap in ComplexPolygon to gain IPathInternals.
this.path = new ComplexPolygon(path);
}
}
///
protected override bool BeginGlyph(in FontRectangle bounds, in GlyphRendererParameters parameters)
{
// Translate + rotate the glyph to follow the path. Always returns true because
// path-based glyphs are never cached (each has a unique per-position transform).
this.TransformGlyph(in bounds);
return true;
}
///
/// Computes the translation + rotation matrix that places a glyph along the path.
/// The glyph's horizontal center is mapped to the path distance, and the glyph
/// is rotated to match the path tangent at that point.
///
/// The font-metric bounding rectangle of the glyph.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void TransformGlyph(in FontRectangle bounds)
{
// Query the path at the glyph's horizontal center.
Vector2 half = new(bounds.Width * .5F, 0);
SegmentInfo pathPoint = this.path.PointAlongPath(bounds.Left + half.X);
// Translate so the glyph's top-left aligns with the path point,
// then rotate around the path point to follow the tangent.
Vector2 translation = (Vector2)pathPoint.Point - bounds.Location - half + new Vector2(0, bounds.Top);
Matrix4x4 matrix = Matrix4x4.CreateTranslation(translation.X, translation.Y, 0) * new Matrix4x4(Matrix3x2.CreateRotation(pathPoint.Angle - MathF.PI, (Vector2)pathPoint.Point));
this.Builder.SetTransform(matrix);
}
}
}