Change path drawing direction
Instead of a transform you should consider reversing the path direction.
This way you can still update attributes/properties such as start-offset without recalculating a transform origin – which becomes way more complicated with asymmetric text path geometries.
svg {
display: inline-block;
width: 20%;
height: auto;
outline: 1px solid #ccc;
overflow: visible;
padding: 5em;
margin-right: 1em;
}
<h3>Symmetric text path</h3>
<svg viewBox="0 0 100 100">
<path id="textPath" d="M 0 50 Q 25 0 50 50 T 100 50" fill="none" stroke="#ccc" />
<circle cx="50" cy="50" r="2" fill="red"/>
<text>
<textPath href="#textPath" >
text
</textPath>
</text>
</svg>
<svg viewBox="0 0 100 100">
<path id="textPath2" d="M 0 50 Q 25 0 50 50 T 100 50" fill="none" stroke="#ccc" />
<circle cx="50" cy="50" r="2" fill="red"/>
<text style="transform: scale(-1, -1); transform-origin: 50px 50px" >
<textPath href="#textPath2" >
text
</textPath>
</text>
</svg>
<h3>Asymmetric text path</h3>
<svg viewBox="0 0 100 100">
<path id="textPath3" d="M 0 50 Q 75 0 50 50 T 100 25" fill="none" stroke="#ccc" />
<circle cx="50" cy="50" r="2" fill="red"/>
<text>
<textPath href="#textPath3" >
text
</textPath>
</text>
</svg>
<svg viewBox="0 0 100 100">
<path id="textPath5" d="M 0 50 Q 75 0 50 50 T 100 25" fill="none" stroke="#ccc" stroke-dasharray="1 1" style="transform: scale(-1, -1); transform-origin: 50px 50px" />
<path id="textPath4" d="M 0 50 Q 75 0 50 50 T 100 25" fill="none" stroke="#ccc" />
<circle cx="50" cy="50" r="2" fill="red"/>
<text style="transform: scale(-1, -1); transform-origin: 50px 50px" >
<textPath href="#textPath4" >
text
</textPath>
</text>
</svg>
You can use a tool like svg-commander to reverse the path data.
<svg style="z-index: 0;" width="800" height="400" viewBox="1400 -1300 700 200">
<g tabindex="0" role="button">
<defs>
<marker
id="red-arrowhead"
viewBox="0 0 15 15"
refX="3"
refY="5"
markerUnits="strokeWidth"
markerWidth="6"
markerHeight="6"
orient="180deg"
>
<path
d="M 0 0.5 L 8 5 L 0 10 Z"
stroke="grey"
stroke-width="1"
fill="grey"
stroke-linecap="round"
stroke-linejoin="round"
></path>
</marker>
</defs>
<path id="textPath"
d="M1459.43-1188.61c271.67 0 271.67-45.92 543.33-45.92"
stroke-width="20"
stroke="grey"
fill="transparent"
marker-start="url(#red-arrowhead)"
style="stroke-width: 6px; pointer-events: none;" />
<text dy="-10px" fill="black" font-size="14">
<textPath
startOffset="50%"
text-anchor="center"
href="#textPath"
>
text
</textPath>
</text>
</g>
</svg>
Benefits from refactoring path
When we're already going to refactor the <textPath> geometry we can also apply some other optimizations such as:
- converting commands to all relative
- reducing the floating point accuracy
By doing so, we can significantly reduce the size of the final SVG markup but most importantly make it slightly more "readable" (admittedly very relative=).
While viewBox offsets or transforms can be helpful – if viewbox offset and transforms are combined, things get quite confusing (where is visual my starting point?)
Foremost, we can simplify multiple "origin-point" references by panning the shape to it's actual starting point coordinates:
Once all commands are relative we can simply move the first M command x/y coordinate pair to the actual positions by subtracting adding the viewBox x/y offsets.
New ViewBox
viewBox="0 0 700 200"
New Textpath
M59.43 112.61 c271.67 0 271.67-45.92 543.33-45.92
svg {
display: block;
outline: 1px solid #ccc;
overflow: visible;
}
<svg style="z-index: 0;" width="800" height="400" viewBox="0 0 700 200">
<g tabindex="0" role="button">
<defs>
<marker
id="red-arrowhead"
viewBox="0 0 15 15"
refX="3"
refY="5"
markerUnits="strokeWidth"
markerWidth="6"
markerHeight="6"
orient="180deg"
>
<path
d="M 0 0.5 L 8 5 L 0 10 Z"
stroke="grey"
stroke-width="1"
fill="grey"
stroke-linecap="round"
stroke-linejoin="round"
></path>
</marker>
</defs>
<path id="textPath"
d="M59.43 112.61 c271.67 0 271.67-45.92 543.33-45.92"
stroke-width="20"
stroke="grey"
fill="transparent"
marker-start="url(#red-arrowhead)"
style="stroke-width: 6px; pointer-events: none;" />
<text dy="-10px" fill="black" font-size="14">
<textPath
startOffset="50%"
text-anchor="center"
href="#textPath"
>
text
</textPath>
</text>
</g>
</svg>
Implications for SVG <marker> elements
Since we're changing the intrinsic drawing direction we also need to adjust the marker orientation. But we can easily do so by tweaking the orient attribute like so:
<marker
id="red-arrowhead"
viewBox="0 0 15 15"
refX="3"
refY="5"
markerUnits="strokeWidth"
markerWidth="6"
markerHeight="6"
orient="180deg"
>
<path
d="M 0 0.5 L 8 5 L 0 10 Z"
stroke="grey"
stroke-width="1"
fill="grey"
stroke-linecap="round"
stroke-linejoin="round"
></path>
</marker>
<textPath> related side attribute
Theoretically, you could also apply the side with values right or left attribute to change alignment of text to the inside or outside – this would be the most straight-forward solution.
Unfortunately, it is currently (2025) only supported by Firefox – so we can't really use it for a robust cross-browser solution. Hopefully, we see side implemented by other vendors in the next years.
See also: "How can I get SVG text to align with the outer arc of a circle segment?"