It seems it is 27 years already since Delphi Pascal was created. I can say that I have used it from the start, even before that, when I was at University we learned programming with Turbo Pascal. Today, if I want to flesh out some idea’s I have, Delphi is still the tool I grab first.
One idea I had recently, is how I could recreate the effects in SVG of another tool that has been around for many years, namely “WordArt”. With this you can apply all kinds of effects on pieces of text. One of them is “warping” the text, so that is what I want to try in this post.
The source code can be found here. You will need the (demo) SVG control package for compilation. You could also use this code with some alterations in Delphi FMX (Firemonkey), because basically it works on a TPathData object.
With “Warping” I mean, distorting a 2d rectangular space into another shape using a warp-function. The picture below shows the warping functions that you could select in WordArt97.

For example, to warp a rectangle to the shape shown in the left bottom, this would involve a tapering from left to right.
I find it easier to first “normalize” the rectangle that you start out with, so after normalization the left top is point (0,0) and the right bottom is (1,1).
Nx = (P.X – Rect.Left) / Rect.Width
Ny = (P.Y – Rect.Top) / Rect.Height
Now we can use Nx and Ny as interpolation parameters.
The tapering is an interpolation function of Nx, so if we start out with a height of 100% and want to end with a height of 30%, we interpolate from 1.0 to 0.3:
Scale = 1.0 + (0.3 – 1.0) * N.x
So the warp function becomes
Wx = Nx
Wy = Ny * Scale + (1.0 – Scale) / 2
Finally we de-normalize so we scale back to the dimensions of the original rectangle:
X = Rect.Left + Rect.Width * Wx
Y = Rect.Top + Rect.Height * Wy

On the other hand, the left bottom shape could also be a perspective projection. This can be written, for example, as follows (maybe experiment a bit with the constants to get the right fit):
Scale = 0.5 * (1.5 + N.X)
Wx = 0.5 + (N.X – 0.5) / Scale
Wy = 0.5 + (N.Y – 0.5) / Scale

For the image at the start of the post I use a slightly more complex function. The points are defined by an arc in the x direction that has a large radius at the top and a smaller one at the bottom. Also the image is tapered to the top. You can find this function in the source code.

So now for the rest of the code.
Text to path
I start of with the following SVG. So I have downloaded the font “Ranchers” for the text. I have placed this font in the same directory as this SVG so the renderer can find it. The font is included in the SVG by the CSS in the style element and then applied by the “font-family” attribute on the text element.
<?xml version="1.0" standalone="no"?>
<svg width="10cm" height="10cm" viewBox="0 0 1000 1000"
xmlns="http://www.w3.org/2000/svg" version="1.1">
<style type="text/css"><![CDATA[
@font-face {
font-family: "Ranchers";
src: url("Ranchers-Regular.ttf") format("ttf");
}
]]></style>
<g id="warp">
<text font-family="Ranchers">
<tspan font-size="260" text-anchor="middle" x="500" y="400">27</tspan>
<tspan font-size="120" text-anchor="middle" x="500" y="510">years of</tspan>
<tspan font-size="260" text-anchor="middle" x="500" y="770">Delphi</tspan>
</text>
<path d="M100,100 L900,100 L900,900 L100,900 Z" fill="none" stroke="green"/>
</g>
<rect x="0" y="0" width="1000" height="1000" fill="none" stroke="black" />
</svg>
The next thing that has to be done is converting the text elements to path elements. In the SVG control package you can do that by adding [sroTextToPath] to the “RenderOptions” property of, for example, the TSVG2Image. On rendering the SVG, any text element will be converted to paths, a path for every glyph. The SVG will be transformed to something like below (this is not the complete SVG):
<?xml version="1.0" standalone="no"?>
<svg width="10cm" height="10cm" viewBox="0 0 1000 1000" xmlns="http://www.w3.org/2000/svg" version="1.1">
<style type="text/css">
<![CDATA[
@font-face {
font-family: "Ranchers";
src: url("Ranchers-Regular.ttf") format("ttf");
}
]]>
</style>
<g id="warp">
<g id="" font-family="Ranchers">
<g id="" font-size="260" text-anchor="middle">
<path d="M121.420,-159.900 ... 121.420,-159.900Z" transform="matrix(1,0,0,1,372.896003186703,400)" />
<path d="M9.100,0.000L79.300 ... 66.820,0.000Z" transform="matrix(1,0,0,1,498.996001660824,400)" />
</g>
<path d="" transform="matrix(1,0,0,1,625.615996778011,400)" />
...
In Delphi FMX you could use the ConvertToPath method of TTextlayout to convert a piece of text to a TPathData object.
Iterating over path sections
Next we need to iterate over the sections of every path. For this we must convert de “d” attribute of the path, which contains the path commands as text, to a path point list. In Delphi FMX you can do that by setting the “Data” property of the TPathData object. In the SVG package this works basically the same (see source code).
The end result is a list of path points that define the segments of the path. There are basically only two sort of segments, straight lines or cubic beziers.
Interpolating path segments
For a smooth result, it might not be enough to just transform the points in the path point list using the warp function. So what we can do is split the segments until they fit into the warped space within a certain accuracy.
For a straight line, this can be done like this:
- Calculate the middle point of the line P0, P1: M
- Transform this point using the warp function: Mw
- Warp the start and end point of the line: P0w, P1w
- Calculate the midpoint of the line P0w, P1w: M2
- If the distance between Mw and M2 is greater than some error, we split the original line P0, P1 into two new lines: P0, M and M, P1 and we repeat the process for each of these two lines
- Otherwise we have reached the accuracy we need and we can add the warped line to the result path point list.
The same can be done with the cubic bezier.
Finally we can convert the path point list back to a path command string and assign it to the “d” attribute of the path.
So that is basically it. You can find the source code for warping paths on the delphi-svg-control-examples git hub page.