// 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); } } }