Chapter 4. Multiline SVG Text

A single SVG <text> element creates a single line of text. It does not—in SVG 1.1, anyway—have any way of wrapping text to a new line. For this reason, when text consists of more than independent short labels, individual <text> elements positioned at explicit points on the page are usually insufficient.

For longer text, you need to break the text into smaller chunks to position them separately. However, you often still want to coordinate the position of different words to reflect that they are part of a continuous whole. This is true not only for normal paragraph-like text wrapping, but also for an area in which SVG excels: complex text layouts used in posters, advertisements, and poetry.

Individual spans of SVG text can be shifted from their natural position, or repositioned completely. This chapter discusses the basic attributes to position spans of text, showing how you can move the virtual typewriter to a new point on the page. However, many style options affect the final position of the characters, and the following chapters will introduce these complexities.

Stepping Up

The <tspan> element can be used to identify segments of text for positioning as well as for styling. By default, each <tspan> is aligned next to the previous character in the text string, but attributes can reset or adjust that position.

Tip

Although a <tspan> element can be positioned independently, it cannot be used on its own: it must be inside a <text> element, which declares a block of SVG text.

There are four positioning attributes for SVG text: the x and y that we have already seen, and also dx and dy. While the first two declare absolute positions within the coordinate system, the latter two declare differences (or deltas) in position, which should be added to the position that otherwise would apply.

Any or all of these attributes can be applied to both <text> and <tspan> elements. Which to use depends on whether later parts of text should adjust if you change previous parts. If a <tspan> element has both x and y attributes, it is positioned independently of the previous content in the <text> element—and of the x and y attributes on the <text> itself.

Tip

Using a single <text> element provides a logical grouping—combining different pieces of text into a continuous whole—even if each <tspan> is positioned independently. This can affect copy-and-paste operations, screen readers, and search engine optimization.

Because x and y are independent attributes, you can control the position along one axis while letting the other axis be calculated automatically based on the text flow. The default x/y behavior for <tspan> is automatic positioning, in contrast to <text> elements, where these attributes default to 0.

Example 4-1 uses the y attribute to offset the vertical position of words within a text string. It uses the same text content and styles as Example 3-5. However, classes are used to set the styles so they don’t distract from the geometric attributes in the markup. Figure 4-1 shows the result.

Example 4-1. Resetting the position of text using absolute attributes on tspan elements
<svg xmlns="http://www.w3.org/2000/svg"
     xml:lang="en" width="10cm" height="2.5cm">
    <title>Positioning tspan</title>
    <style type="text/css">
        svg {
            font-family: serif;
            font-size: 12mm;
            fill: navy;
        }
        .em {
            fill: royalBlue;
        }
        .strong {
            stroke: navy;
            font-style: italic;
        }
    </style>
    <rect fill="#CEE" width="100%" height="100%" />
    <text x="5mm" y="2.1cm" >One,
        <tspan class="em" y="1.6cm">Two,</tspan>
        <tspan class="strong em" y="1.1cm">Three!</tspan>
    </text>
</svg>
svtl 0401
Figure 4-1. SVG text using automatic horizontal positions with absolute vertical positions

The exact same result can also be achieved using the relative positioning attributes, as follows:

<text x="5mm" y="2.1cm" >One,
    <tspan class="em" dy="-0.5cm">Two,</tspan>
    <tspan class="strong em" dy="-0.5cm">Three!</tspan>
</text>

The first dy shifts the current text position from 2.1cm to 1.6cm; the second span then starts from that position and the dy value shifts it up another half centimeter.

The <text> element as a whole is still positioned absolutely, but the <tspan> elements are positioned using dy to specify vertical offsets, instead of y to specify the final destination. The main benefit of using dy and dx is that you can move the entire element as a whole by changing the initial position value. All the pieces maintain their relative position.

Using dy and dx also allows you to specify the position of your text as a mix of length and percentage units. When an element (<text> or <tspan>) has both absolute and relative position attributes for a given direction (horizontal or vertical), the delta adjustments are applied after moving to the absolute position. You can therefore set the base position (x or y) using a percentage, then offset it by a fixed amount using dx or dy.

To ensure that the text from Example 4-1 always remains vertically centered within the SVG—even after changing the SVG height—you could position the original <text> element as an offset from 50%:

<text x="5mm" y="50%" dy="0.85cm">One,
    <tspan class="em" dy="-0.5cm">Two,</tspan>
    <tspan class="strong em" dy="-0.5cm">Three!</tspan>
</text>

In contrast, if you wanted the text to maintain its position relative to the bottom of the SVG (100% height), you would use the following:

<text x="5mm" y="100%" dy="-0.4cm">One,
    <tspan class="em" dy="-0.5cm">Two,</tspan>
    <tspan class="strong em" dy="-0.5cm">Three!</tspan>
</text>

Figure 4-2 shows the difference by embedding both versions of the SVG in an HTML page with <object> elements. Width and height set on the <object> with CSS are propagated to the SVG, overriding the dimensions set in the SVG file:

object {
    display: table-cell;
    width: 9.5cm;
    height: 2.5cm;
    margin: 0.25cm;
}
.stretch object {
    height: 7.5cm;
}
.squish object {
    height: 1.5cm;
}
svtl 0402
Figure 4-2. Percentage-positioned SVG text with relative offsets, in SVG objects of differing heights: positioned relative to 50% (left), positioned relative to 100% (right)

Because the SVG files (which are identical to Example 4-1 except for the text positioning attributes) do not have a viewBox attribute, the content does not scale. The percentages are calculated relative to the applied height and width.

In both cases, the adjustments on the <tspan> elements do not change. The effect of a dy attribute is always calculated from the final position of the previous text, regardless of whether that position was set with y, dy, or a combination of the two.

Tip

SVG 2 is expected to adopt the CSS calc() function, which will make percentage-plus-offset adjustments possible within any length attribute.

The preceding examples have all used the natural flow of the text to control horizontal position. However, for many uses of SVG text, you’ll need to reset the horizontal position with x and dx attributes. A dx value is also calculated from the final net position of previous text, including (for horizontal text) the offsets caused by the letters themselves. An x attribute establishes a new horizontal starting point, regardless of the previous text position.

Waxing Poetic

Using an absolute x attribute and a relative dy, you can create a line break. The x value is usually set to the same value for each line; it resets the horizontal flow of the text, like a carriage return on an old typewriter. The dy value is equivalent to the desired line height; it shifts the text down like a typewriter’s line feed motion.

Example 4-2 uses x and dy to position the lines of a poem (Alice’s muddled morality lesson, “How doth the little crocodile,” from Lewis Carroll’s Alice in Wonderland). Each verse is a separate <text> element containing four <tspan> elements for each line. Every second line is inset and styled differently. Figure 4-3 displays the typeset result.

svtl 0403
Figure 4-3. “How doth the little crocodile,” set in SVG
Example 4-2. Typesetting poetry using x, dy, and dx on SVG tspan elements
<svg xmlns="http://www.w3.org/2000/svg"
     xml:lang="en-GB" width="4.3in" height="3in">
    <title>How Doth the Little Crocodile - Lewis Carroll</title>
    <desc>From Alice in Wonderland</desc>
    <style>
@import
  url(http://fonts.googleapis.com/css?family=Miltonian+Tattoo); 1

        svg {
            font-family: "Miltonian Tattoo", serif;
            font-size: 18pt;
        }
        .verse {
            fill: darkGreen;
            stroke: #031;
            word-spacing: 2px;                             2
        }
        .verse > tspan:nth-child(2n) {                     3
            fill: navy;
            stroke: #013;
        }
    </style>
    <rect fill="#CEE" width="100%" height="100%" />
    <text class="verse">                                   4
        <tspan dy="1.2em" x="10"
               >How doth the little crocodile</tspan>      5
        <tspan dy="1.2em" x="10" dx="1em"
               >Improve his shining tail,</tspan>          6
        <tspan dy="1.2em" x="10"
               >And pour the waters of the Nile</tspan>
        <tspan dy="1.2em" x="10" dx="1em"
               >On every golden scale!</tspan>
      </text>
    <text class="verse" y="50%">                           7
        <tspan dy="1.2em" x="10"
               >How cheerfully he seems to grin,</tspan>
        <tspan dy="1.2em" x="10" dx="1em"
               >How neatly spreads his claws,</tspan>
        <tspan dy="1.2em" x="10"
               >And welcomes little fishes in</tspan>
        <tspan dy="1.2em" x="10" dx="1em"
               >With gently smiling jaws!</tspan>
    </text>
</svg>
1

The SVG uses a decorative web font, Miltonian Tattoo by Pablo Impallari, accessed from Google Font’s repository of free typefaces. We’ll discuss more about web fonts in Chapter 10.

2

The letters are stroked, which can make them seem overly close together. A small amount of extra word-spacing helps maintain legibility.

3

An nth-child(2n) selector styles every other line differently, in blue instead of green.

4

There are no positioning attributes on the first <text> element: both x and y default to 0.

5

Each line (<tspan>) resets the x position to a left margin of 10px. Each line also shifts the vertical position (dy) by 1.2em. For the first line, this offset is measured from the y=0 position set by default on the <text>.

6

Every other line has a dx attribute, which adds an inset from the margin created by the x="10" attribute. Although you could combine the x and dx values, keeping them separate helps clarify the differing purposes, and also allows you to use different units for each.

7

The second verse follows the same structure, except that it starts from a y position of 50%, halfway down the graphic.

All these attributes might seem a little excessive to create simple line breaks. In a way, it is. SVG was not designed to set paragraphs of text. However, it can be used to set complicated text layouts where the exact position of each text element is important.

Example 4-3 sets another poem from Alice in Wonderland: “The Mouse’s Tale.” In the book, Alice mishears the title as “The Mouse’s Tail,” and therefore imagines the words arranged in the shape of a long, curvy appendage that gets narrower toward the tip. This is known as a concrete poem, where the artistry of the text has as much to do with the way it is printed as the words themselves. Figure 4-4 shows the final typeset text.

svtl 0404
Figure 4-4. “The Mouse’s Tale,” set in SVG
Example 4-3. Typesetting concrete poetry
<svg xmlns="http://www.w3.org/2000/svg"
     xml:lang="en-GB" width="100%" height="47.5em">
    <title>The Mouse's Tale - Lewis Carroll</title>
    <desc>From Alice in Wonderland</desc>
    <style>
        svg {
            font-family: serif;
            font-size: medium;
        }
        text {
            font-size: 150%;           1
        }
        .em {
            font-style: italic;        2
        }
        .smaller {
            font-size: 85%;            3
        }
    </style>
    <text>                             4
      <tspan dy="1em" x="50%" dx="-2.68em">Fury said to</tspan> 5
      <tspan dy="1em" x="50%" dx="-1.65em">a mouse, That</tspan>
      <tspan dy="1em" x="50%" dx="-1.03em">he met in the</tspan>
      <tspan dy="1em" x="50%" dx="-0.62em">house, “Let</tspan>  6
      <tspan dy="1em" x="50%" dx="-1.03em">us both go</tspan>
      <tspan dy="1em" x="50%" dx="-1.44em">to law:
                <tspan class="em">I</tspan></tspan>             7
      <tspan dy="1em" x="50%" dx="-2.06em">will prose-</tspan>
      <tspan dy="1em" x="50%" dx="-2.06em">cute
                <tspan class="em">you.</tspan></tspan>
      <tspan class="smaller">                                   8
        <tspan dy="1em" x="50%" dx="-2.19em">Come, I’ll</tspan>
        <tspan dy="1em" x="50%" dx="-1.7em">take no de-</tspan>
        <tspan dy="1em" x="50%" dx="-0.73em">nial; We </tspan>
        <tspan dy="1em" x="50%" dx="-0.24em">must have</tspan>
        <tspan dy="1em" x="50%" dx="-0em">the trial:</tspan>
        <tspan dy="1em" x="50%" dx="-0.49em">For really</tspan>
        <tspan class="smaller">                                 9
          <tspan dy="1em" x="50%" dx="-1.14em">this morn-</tspan>
          <tspan dy="1em" x="50%" dx="-1.43em">ing I’ve</tspan>
          <tspan dy="1em" x="50%" dx="-2em">nothing</tspan>
          <tspan dy="1em" x="50%" dx="-2.57em">to do.”</tspan>
          <tspan dy="1em" x="50%" dx="-3.14em">Said the</tspan>
          <tspan dy="1em" x="50%" dx="-3.71em">mouse to</tspan>
          <tspan dy="1em" x="50%" dx="-4em">the cur,</tspan>
          <tspan class="smaller">
            <tspan dy="1em" x="50%" dx="-5.04em">“Such a</tspan>
            <tspan dy="1em" x="50%" dx="-4.7em">trial, dear</tspan>
            <tspan dy="1em" x="50%" dx="-4.03em">Sir, With</tspan>
            <tspan dy="1em" x="50%" dx="-3.36em">no jury</tspan>
            <tspan dy="1em" x="50%" dx="-2.69em">or judge</tspan>
            <tspan dy="1em" x="50%" dx="-2.02em">would</tspan>
            <tspan dy="1em" x="50%" dx="-1.34em">be wast-</tspan>
            <tspan dy="1em" x="50%" dx="-1.01em">ing our</tspan>
            <tspan class="smaller">
              <tspan dy="1em" x="50%" dx="-0.4em">breath.”</tspan>
              <tspan dy="1em" x="50%" dx="-0em">“I’ll be</tspan>
              <tspan dy="1em" x="50%" dx="0.79em">judge,</tspan>
              <tspan dy="1em" x="50%" dx="0.79em">I’ll be</tspan>
              <tspan dy="1em" x="50%" dx="0.4em">jury,”</tspan>
              <tspan dy="1em" x="50%" dx="-0em">said</tspan>
              <tspan dy="1em" x="50%" dx="-0.79em">cun-</tspan>
              <tspan dy="1em" x="50%" dx="-1.19em">ning</tspan>
              <tspan class="smaller">
                <tspan dy="1em" x="50%" dx="-1.86em">old</tspan>
                <tspan dy="1em" x="50%" dx="-2.79em">Fury:</tspan>
                <tspan dy="1em" x="50%" dx="-3.26em">“I’ll</tspan>
                <tspan dy="1em" x="50%" dx="-3.72em">try</tspan>
                <tspan dy="1em" x="50%" dx="-4.19em">the</tspan>
                <tspan dy="1em" x="50%" dx="-4.19em">whole</tspan>
                <tspan dy="1em" x="50%" dx="-3.72em">cause</tspan>
                <tspan class="smaller">
                  <tspan dy="1em" x="50%" dx="-3.29em">and</tspan>
                  <tspan dy="1em" x="50%" dx="-2.19em"
                                    >condemn</tspan>            10
                  <tspan dy="-0.7em">you to</tspan>
                  <tspan dy="-0.7em">death!”</tspan>
                </tspan>
              </tspan>
            </tspan>
          </tspan>
        </tspan>                                 11
      </tspan>
    </text>
</svg>
1

The text will start out at a font size that is half-again as large as the user’s normal text. The medium font size is explicitly set on the <svg> itself to circumvent the font-size error in embedded SVG images in WebKit/Blink browsers.

2

Because SVG does not have an <em> element that would by default italicize text, emphasized words within the poem are styled using a special em class.

3

As the poem progresses, the font will get smaller and smaller, each time set to 85% of the previous value.

4

Again, the main <text> element uses the default position values; the horizontal position will be reset for each line, while the vertical position of the first line is calculated as a one-line offset from the top of the graphic.

5

Each line is shifted down with dy="1em" and is reset horizontally with x="50%". The actual shape of the text is controlled by the dx offsets, which were generated by hand—viewing the file and then adjusting the attributes—to create the desired shape.

6

Unicode characters are used for typographically correct quotation marks and dashes; alternatively, numeric entities could have been used. Since this is an SVG file, HTML named entities such as &rdquo; or &mdash; are not available.

7

The italicized spans are simply more <tspan> elements, but without any positioning attributes.

8

The font-size change is also created by a <tspan> without positioning attributes, but in this case it contains all the remaining lines of the poem.

9

Subsequent smaller sections are nested inside the previous, so that the percentage reductions in font size accumulate.

10

The final curl of the tail is created by not resetting the horizontal position between spans, and by reversing the direction of the vertical dy adjustment.

11

At the end of it all, be sure to close up all your <tspan> elements, otherwise all you’ll see are XML validation errors!

The use of both x and dx on each line of text may seem excessive, but it allows the entire SVG to adjust to fit any width page and remain centered. Similarly, the use of em units for the height ensures that it will adjust vertically even if the user needs to increase the base font size in order to read the tiny text at the end.

Get SVG Text Layout now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.