Using SVG with CSS3 and HTML5 — Supplementary Material

Example code and online extras for the O'Reilly Media book by Amelia Bellamy-Royds, Kurt Cagle, and Dudley Storey.

Perfecting Paths for <textPath>

In SVG 1.1 (and all existing browsers), the “path” used by a <textPath> layout must truly be a <path> element: you can’t yet make text ride a <circle> or <rect>.

This can be somewhat frustrating, as they (along with ellipses) are the most common shapes people want to wrap text around. If you have a basic shape, and want to convert it to a <path> without having to code or draw it one point at a time, there are various tools you can use:

The text will proceed along the path in the direction the path is drawn in the code. If your paths have a clockwise winding order, the text will be on the outside of the shape. If the path is counter-clockwise, the text will be on the inside. In either case, it is possible to end up with upside-down letters. If your path is in the wrong direction, your graphics editor can reverse it for you:

Finally, you may want to adjust the starting point of the text to a position other than the starting point from the path code. Unfortunately, startOffset is not enough here: although it shifts the text around the shape, it doesn’t change the fact that text gets cropped off when it reaches the path start and end points. (SVG 2 proposes to change this, but browsers aren’t yet on board.)

Tools for changing the start point of a path aren’t as common as for reversing it, but in most graphic editors you can force the matter by breaking and then re-joining your path at the chosen point.

Alternatively, you can avoid the cut-off text issue by duplicating the path data string inside the <path> element, so that text will happily wrap around and continue along a second “lap” of the path. Just remember that now your total path is twice as long, so percentage startOffset values need to be divided in half.

Tip

For symmetrical shapes, such as a circle, you can instead use a rotate transformation on the <path> itself (or on the <text> containing the <textPath>) to re-position the start of the text as you wish.

With all those tips in mind, we’re ready to arrange text around a closed path. Example 7-X3 creates a circular crest out of the Three Musketeer’s “All for One & One for All” motto. Figure 7-X3 is the output.

The text 'All for One & One for', arranged in a circle so that the 'All' from the beginning also serves as the final word of the motto.
Figure 7-X3. Circular text using <textPath> and textLength
Example 7-X3. Controlling text length for text on a closed path
<svg xmlns="http://www.w3.org/2000/svg" xml:lang="en"
    xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 500 500">
    <title>Circular Text Path</title>
    <style>
        text {
            font-size: 76px;
            font-family: Copperplate, Copperplate Gothic, serif;
            font-weight: bold;
            font-variant: small-caps;
            font-variant-ligatures: none;
            stroke: black;
            stroke-width: 1.5px;
            fill: white;
        }
    </style>
    <defs>
        <path id="textcircle"
              d="M250,400
                 a150,150 0 0,1 0,-300a150,150 0 0,1 0,300Z"
              transform="rotate(12,250,250)"/>
    </defs>
    <rect width="100%" height="100%" fill="silver" />
    <text>
        <textPath xlink:href="#textcircle"
                  aria-label="All for One &amp; One for All"
                  textLength="942">&#160;One for All
                                         for One &amp;</textPath>
    </text>
</svg>

The circle is created with a <path> element containing two half-circle arcs of 150-unit radius. As we mentioned in Chapter 6, you can’t draw a complete circle with a single arc segment, because it doesn’t have enough positioning information.

The textLength attribute constrains the text to match the circumference of the circle (2π times the radius), and a rotational transform is used to tweak the alignment so that the ampersand (&) is centered at the base of the circle.

Warning

Or at least, that’s the end result. Creating this layout was more difficult than expected, due to issues with textLength on nested elements, textLength in combination with centered text, and inconsistent treatment of whitespace. Code that looked nice in one browser was horribly out of alignment in others. The markup in Example 7-X3 was the result of a lot of tweaking and testing in different browsers, and it still isn’t perfectly aligned everywhere.

As we mentioned in “Beyond Horizontal: Rotated and Vertical Text”, textLength declares the total length you want the text to meet, and browsers adjust the letter spacing to make it fit. It should work on any text element, including <textPath>, but browser implementations are buggy in conflicting ways, and some will ignore it. textLength can only be used reliably for <text> elements without children.

When adjusting the textLength to match the path, you’ll need to know the length of the path you’re using. The browser can calculate it for you with the getTotalLength() method on the <path> element. The value returned by that method can be directly used in the textLength attribute of a <textPath> to make the text exactly fit.

For example, by opening the SVG from Example 7-X3 in a browser, and then opening up the developer’s console, you can find the path length as follows:

document.getElementById("textcircle").getTotalLength()
//returns 942.61083984375

This gives essentially the same result as calculating it from the geometry of the circle:

150 * 2 * Math.PI
//returns 942.4777960769379

It’s not a perfect match, because the browser uses approximations to draw circular paths, and approximations to calculate the length of paths. But it’s close enough for the drawing. And getTotalLength() is invaluable for more complicated paths, whose lengths can’t be converted into a simple formula.

In SVG 2 and the latest versions of Chromium browsers, getTotalLength() can be used on any shape element, not only a <path>. So you can use it in your Chrome console, even if you can’t rely on it in your scripts. But that won’t be useful for text paths until non-path shapes can be referenced by the <textPath> element.

There are a few other things to note in Example 7-X3:

If the path is very sharply curved, text may appear too “tight” on the inside of a curve, or too “loose” on the outside. You can adjust the vertical relationship between text and its path by modifying the value of a dy attribute for the parent <text> element (or a child <tspan> element). A positive value will pull the text baseline down to be more centered over the path, loosening up those tight inside curves and tightening the outside ones.

One day, it may be possible to use the CSS baseline-related properties to adjust the position of text relative to the path, in a more intuitive manner. For now, dy has the better browser support.