I recently encountered this SVG:
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<!-- to hide the path, it is usually wrapped in a <defs> element -->
<!-- <defs> -->
<path
id="MyPath"
fill="none"
stroke="red"
d="M10,90 Q90,90 90,45 Q90,10 50,10 Q10,10 10,40 Q10,70 45,70 Q70,70 75,50" />
<!-- </defs> -->
<text>
<textPath href="#MyPath">Quick brown fox jumps over the lazy dog.</textPath>
</text>
</svg>
Which renders in browsers like that:
Now in Direct2D SVG this isn't supported. Even if it was, what 's the math algorithm behind this? I want to implement it myself in C++ but I can't find any resource that describes a logic for it. I am already using a custom Direct2D renderer but , given a path, I'm not sure how to proceed.
Essentialy, what I need is, when having a series of ID2D1Geometry's of Glyphs, to translate/rotate them properly to match the path in another ID2D1Geometry.
Edit: The best I 've got so far is this:
float StartPoint = 0;
// j.sPaths contains ID2D1Geometry for all letters, as rendered by GetGlyphRunOutline.
for (size_t iip = 0; iip < j.sPaths.size(); iip++)
{
if (g1) // The geometry in which to adjust the letters
{
nop();
D2D1::Matrix3x2F m1 = D2D1::Matrix3x2F::Identity();
D2F bnds = {};
j.sPaths[iip].pPath->GetBounds(m1, &bnds);
if (bnds.left > bnds.right)
continue;
// make sure this is at 0,0
float ExtraX = -bnds.left;
float ExtraY = -bnds.top;
auto wi2 = bnds.Width();
auto he2 = bnds.Height();
auto wi = wi2 / (float)vepXX.SizeCreated.cx; // vepXX.SizeCreated = rendering area, Normalize everything to [0,1] because the geometry g1 is within [0,1].
auto he = he2 / (float)vepXX.SizeCreated.cy;
D2D1_POINT_2F StartPointOnThePath = {}, p0tv = {};
D2D1_POINT_2F EndpointOnThePath = {}, p1tv = {};
g1->ComputePointAtLength(StartPoint, m1, &StartPointOnThePath, &p0tv);
float Endpoint = StartPoint + wi;
g1->ComputePointAtLength(Endpoint, m1, &EndpointOnThePath,&p1tv);
float MidpointOnThePath = (EndpointOnThePath.x - StartPointOnThePath.x)/2.0f + StartPointOnThePath.x;
StartPoint = Endpoint;
if (oo) StartPoint += oo->AddX; // Extra X option
float angle = 0;
angle = (float)atan2(p1tv.y, p1tv.x);
float angled = angle * 180.0f / 3.1415f;
float xx = (MidpointOnThePath - wi/2)* vepXX.SizeCreated.cx; // convert to view
float yy = (EndpointOnThePath.y - he)* vepXX.SizeCreated.cy;
D2D1_POINT_2F pr = {wi2/2.0f,he2/2.0f};
auto xform = D2D1::IdentityMatrix();
// Translate the glyph to 0,0
xform = xform * D2D1::Matrix3x2F::Translation(ExtraX, ExtraY);
// Rotate to put
xform = xform * D2D1::Matrix3x2F::Rotation(angled,{wi2/2.0f,he2/2.0f});
// Translate to position
xform = xform * D2D1::Matrix3x2F::Translation(xx,yy);
faa->CreateTransformedGeometry(j.sPaths[iip].pPath, &xform, &j.sPaths[iip].pPathX);
// Draw the new geometry later.
}



Here is a fully working example, using ID2D1PathGeometry1::ComputePointAndSegmentAtLength that allows you to get points coordinates of a path from a
lengthparameter.lengthrepresents the point on the path which is "at length distance" along this path from its start.The sample code uses a custom IDWriteTextRenderer implementation which is used to render each glyph along the path. This sample implementation mostly implements
IDWriteTextRenderer::DrawGlyphRunfor the whole string, which in turns re-uses ID2D1RenderTarget::DrawGlyphRun for each glyph after appropriate translation and rotation.And here is the result:
With 250px start offset and 10px baseline offset: