in binding/SkiaSharp/SKFont.cs [862:967]
public SKPath GetTextPathOnPath (ReadOnlySpan<ushort> glyphs, ReadOnlySpan<float> glyphWidths, ReadOnlySpan<SKPoint> glyphPositions, SKPath path, SKTextAlign textAlign = SKTextAlign.Left)
{
if (glyphs.Length != glyphWidths.Length)
throw new ArgumentException ("The number of glyphs and glyph widths must be the same.");
if (glyphs.Length != glyphPositions.Length)
throw new ArgumentException ("The number of glyphs and glyph offsets must be the same.");
if (path == null)
throw new ArgumentNullException (nameof (path));
if (glyphs.Length == 0)
return new SKPath ();
using var glyphPathCache = new GlyphPathCache (this);
using var pathMeasure = new SKPathMeasure (path);
var contourLength = pathMeasure.Length;
var textLength = glyphPositions[glyphs.Length - 1].X + glyphWidths[glyphs.Length - 1];
var alignment = (int)textAlign * 0.5f;
var startOffset = glyphPositions[0].X + (contourLength - textLength) * alignment;
var textPath = new SKPath ();
// TODO: deal with multiple contours?
for (var index = 0; index < glyphPositions.Length; index++) {
var glyphOffset = glyphPositions[index];
var gw = glyphWidths[index];
var x0 = startOffset + glyphOffset.X;
var x1 = x0 + gw;
if (x1 >= 0 && x0 <= contourLength) {
var glyphId = glyphs[index];
var glyphPath = glyphPathCache.GetPath (glyphId);
if (glyphPath != null) {
var transformation = SKMatrix.CreateTranslation (x0, glyphOffset.Y);
MorphPath (textPath, glyphPath, pathMeasure, transformation);
}
}
}
return textPath;
static void MorphPath (SKPath dst, SKPath src, SKPathMeasure meas, in SKMatrix matrix)
{
// TODO:
// Need differentially more subdivisions when the follow-path is curvy. Not sure how to determine
// that, but we need it. I guess a cheap answer is let the caller tell us, but that seems like a
// cop-out. Another answer is to get Skia's Rob Johnson to figure it out.
using var it = src.CreateIterator (false);
SKPathVerb verb;
Span<SKPoint> srcP = stackalloc SKPoint[4];
Span<SKPoint> dstP = stackalloc SKPoint[4];
while ((verb = it.Next (srcP)) != SKPathVerb.Done) {
switch (verb) {
case SKPathVerb.Move:
MorphPoints (dstP, srcP, 1, meas, matrix);
dst.MoveTo (dstP[0]);
break;
case SKPathVerb.Line:
// turn lines into quads to look bendy
srcP[0].X = (srcP[0].X + srcP[1].X) * 0.5f;
srcP[0].Y = (srcP[0].Y + srcP[1].Y) * 0.5f;
MorphPoints (dstP, srcP, 2, meas, matrix);
dst.QuadTo (dstP[0], dstP[1]);
break;
case SKPathVerb.Quad:
MorphPoints (dstP, srcP.Slice (1, 2), 2, meas, matrix);
dst.QuadTo (dstP[0], dstP[1]);
break;
case SKPathVerb.Conic:
MorphPoints (dstP, srcP.Slice (1, 2), 2, meas, matrix);
dst.ConicTo (dstP[0], dstP[1], it.ConicWeight ());
break;
case SKPathVerb.Cubic:
MorphPoints (dstP, srcP.Slice (1, 3), 3, meas, matrix);
dst.CubicTo (dstP[0], dstP[1], dstP[2]);
break;
case SKPathVerb.Close:
dst.Close ();
break;
default:
Debug.Fail ("Unknown verb when iterating points in glyph path.");
break;
}
}
}
static void MorphPoints (Span<SKPoint> dst, Span<SKPoint> src, int count, SKPathMeasure meas, in SKMatrix matrix)
{
for (int i = 0; i < count; i++) {
SKPoint s = matrix.MapPoint (src[i].X, src[i].Y);
if (!meas.GetPositionAndTangent (s.X, out var p, out var t)) {
// set to 0 if the measure failed, so that we just set dst == pos
t = SKPoint.Empty;
}
// y-offset the point in the direction of the normal vector on the path.
dst[i] = new SKPoint (p.X - t.Y * s.Y, p.Y + t.X * s.Y);
}
}
}