第 9 章 文本

SVG 中有好几个用于图像中加入文本的元素,<text> 元素是主要方法

文本的相关术语

  • 字符:在 XML 文档中,字符是指带有一个数字值的一个或多个字节,数字值与 Unicode 标准对应
  • 符号:符号(glyph)是指字符的视觉呈现。每个字符都可以用很多不同的符号来呈现(简单理解为不同字体)
  • 字体:代表某个字符集合的一组符号。一套字体中的所有符号都有以下特性
    • 基线、上坡度、下坡度 字体中的所有符号都以基线对齐。基线到字体中最高字符顶部的距离称为上坡度(ascent),到最深字符底部的距离称为下坡度(descent)。字符的总高度为上坡度和下坡度之和,也称为 em 高度。em-box 指宽度为 em 高度的方块
    • 大写字母高度、x 高度 大写字母高度(cap-height)指基线上大写字母的高度,x 高度指基线到小写字母 x 顶部的高度。x 高度通常能比 em 高度更好地衡量一个字体的尺寸和可读性

一个符号可能由多个字符构成,一个字符也可能由多个符号组合而成(打印程序可能组合符号 e 和重音符号 ˊ 来打印 é

<text> 元素的基本属性

文本的默认样式和其他所有对象一样:黑色填充、没有轮廓

最简单样式是只设置基础属性

  • x:元素内容第一个字符的基线 x 坐标
  • y:元素内容第一个字符的基线 y 坐标

很多可以应用到文本上的属性和 CSS 标准中是一样的,适用于大部分 SVG 阅读器

  • font-family
  • font-size:如果有多行文本,font-size 的值为两条基线的距离(在 SVG 中,开发者需要自己定位多行文本)。如果指定了单位,则在渲染前字体大小会被转换为用户坐标,所以它可能会受变换和 viewBox 影响。如果使用相对单位,这些单位会相对于继承的字体大小进行计算
  • font-weight:最常用的值为 boldnormal
  • font-style:最常用的值为 italic(斜体)和 normal
  • text-decoration
  • word-spacing:值为一个长度,可带单位,也可使用用户坐标
  • letter-spacing:值为一个长度,可带单位,也可使用用户坐标

文本对齐

<text> 元素指定了起始点,但是并不能提前知道它的终点,这使得文本居中对齐或者右对齐很困难

可以使用 text-anchor 指定文本坐标生效的位置

  • start
  • middle
  • end

如果文字是从左往右书写,则分别表示左、中、右对齐,如果不是从左往右书写,则会有不同的效果(见本章后文)

<tspan> 元素

无法提前预知文本元素的长度带来的另一个问题是,很难为一个字符串应用不同的文本属性,如一个句子中穿插斜体、正常字体、粗体

<tspan> 解决了这个问题,它与 <span> 元素类似,可以嵌套在 <text> 中,并可以改变其中文本的样式

除了改变样式外,还可以应用一些属性来改变其包裹的字符的位置

  • xy:绝对位置的偏移量
  • dxdy:相对位置的偏移量
  • rotate:旋转字符
  • baseline-shift:设置上下标。可取值为 supersub、一个长度值、百分比(相对字体尺寸计算)

注意,xydxdy<tspan> 标签关闭后仍然有效,</tspan> 之后的文本的偏移量不会重置

可以为 xydxdyrotate 设置一些列的值,这样可以一次修改多个字母的位置

<!-- @format -->

<svg>
    <text x="30" y="30" style="font-size: 14px">
        It's
        <tspan dx="0 4 -3 5 -4 6" dy="0 -3 7 3 -2 -8" rotate="5 10 -5 -20 0 15">
            shaken
        </tspan>
        , not stirred.
    </text>

    <text x="20" y="75" style="font-size: 12pt">
        C
        <tspan baseline-shift="sub">12</tspan>
        H
        <tspan baseline-shift="sub">22</tspan>
        O
        <tspan baseline-shift="sub">11</tspan>
        (sugar)
    </text>
    <text x="20" y="120" style="font-size: 12pt">
        6.02 x 10
        <tspan baseline-shift="super">23</tspan>
        (Avogadro's number)
    </text>
</svg>

设置文本长度

虽然无法提前知道一段文本的结束位置,但是可以使用 textLength 属性显式设置文本的长度。此时 SVG 会将文本调整到指定的长度,根据 lengthAdjust 属性取值,会有不同的调整效果

  • spacing:默认值,仅调整字符间距
  • spacingAndGlyphs:同时调整字符间距和字符大小,即字符可能会变形
<!-- @format -->

<svg height="200px">
    <g style="font-size: 14pt">
        <path d="M 20 10 20 70 M 220 10 220 70" style="stroke: gray" />
        <text x="20" y="30" textLength="200" lengthAdjust="spacing">
            Two words
        </text>
        <text x="20" y="60" textLength="200" lengthAdjust="spacingAndGlyphs">
            Two words
        </text>
        <text x="20" y="90">
            Two words
            <tspan style="font-size: 10pt">(normal length)</tspan>
        </text>

        <path d="M 20 100 20 170 M 100 100 100 170" style="stroke: gray" />
        <text x="20" y="120" textLength="80" lengthAdjust="spacing">
            Two words
        </text>
        <text x="20" y="160" textLength="80" lengthAdjust="spacingAndGlyphs">
            Two words
        </text>
    </g>
</svg>

纵向文本

要实现纵向文本通常有两种做法

  • 使用 transform 将文本旋转 90 度
  • writing-mode 属性值设置为 tb(top to bottom,从上到下)

text-orientation 属性设置纵向排列的文本中的英文是否水平显示。默认值为 mixed,设置为 upright 时即可水平排列

<!-- @format -->

<svg height="200px">
    <text x="10" y="20" transform="rotate(90,10,20)">Rotated 90</text>
    <text x="50" y="20" style="writing-mode: tb">从上到下abc</text>
    <text x="90" y="20" style="writing-mode: tb; text-orientation: upright">
        Vertical zero
    </text>
</svg>

国际化和文本

Unicode 和双向语言

一些语言的书写方向是从右到左的,当这些语言的文本与从左到右书写的语言混合在一起时,这些语言就是双向的(bidirectional),简称 bidi

系统软件知道每个字符的书写方向,并正确地计算出它们的位置

  • direction:用于设置书写方向,可选值为 rtlltr
  • unicode-bidi:设置为 unicode-bidi 可重设底层的 Unicode 双向文本算法(更多取值请看 MDN 文档)
<!-- @format -->

<svg width="100%" height="200px">
    <g style="font-size: 14pt">
        <text x="10" y="30">Greek:</text>
        <text x="100" y="30">αβγδε</text>
        <text x="10" y="50">Russian:</text>
        <text x="100" y="50">абвгд</text>
        <text x="10" y="70">Hebrew:</text>
        <text x="100" y="70">אבגדה (written right to left)</text>
        <text x="10" y="90">Arabic:</text>
        <text x="100" y="90">(written right to left)ا ب ج د</text>
        <text x="10" y="130">
            This is
            <tspan
                style="
                    direction: rtl;
                    unicode-bidi: bidi-override;
                    font-weight: bold;
                "
            >
                right-to-left
            </tspan>
            writing.
        </text>
    </g>
</svg>

<switch> 元素

该元素会搜索所有子节点,直到发现 systemLanguage 属性值与用户正在使用的软件的语言设置相符的节点,该匹配子节点将会显示,其他子节点将会忽略

systemLanguage 属性的值是一个语言名称或使用逗号分隔的语言名称列表,语言名称格式如下

  • 两个字母的语言代码:如 ru 代表俄语
  • 语言代码加上国家代码:如 fr-CA 代表加拿大法语,fr-CH 代表瑞士法语

此处我用 edge 浏览器会显示英文,但是用火狐则会正常显示中文

<!-- @format -->

<svg width="100%">
    <circle cx="40" cy="60" r="20" style="fill: none; stroke: black" />
    <g font-size="12pt">
        <switch>
            <g systemLanguage="en-UK">
                <text x="10" y="30">A circle</text>
                <text x="10" y="100">without colour.</text>
            </g>
            <g systemLanguage="zh">
                <text x="10" y="30">这是一个圆</text>
                <text x="10" y="100">without color.</text>
            </g>
            <g systemLanguage="es">
                <text x="10" y="30">Un circulo</text>
                <text x="10" y="100">sin color.</text>
            </g>
            <g systemLanguage="ru">
                <text x="10" y="30">Kpyr</text>
                <text x="10" y="100">6e3 CBeTa.</text>
            </g>
        </switch>
    </g>
</svg>

使用自定义字体

这一部分的内容,在 MDN 已提示过时,因此不记录

文本路径

文本可沿任何抽象路径排列,只需要将文本放在 <textPath> 元素中,然后使用 xlink:href 属性引用一个之前已经定义好的 <path> 元素,字母会被旋转到与曲线垂直的方向(即基线是曲线的切线。沿光滑连续曲线排列的文本比沿含有锐角或者不连续的路径排列的文本更易读

<!-- @format -->

<svg width="100%">
    <defs>
        <path
            id="curvepath"
            d="M30 40 C 50 10, 70 10, 120 40 S 150 0, 200 40"
            style="stroke: gray; fill: none"
        />
        <path
            id="round-corner"
            d="M250 30 L 300 30 A 30 30 0 0 1 330 60 L 330 110"
            style="stroke: gray; fill: none"
        />
        <path
            id="sharp-corner"
            d="M 30 110 100 110 100 160"
            style="stroke: gray; fill: none"
        />
        <path
            id="discontinuous"
            d="M 150 110 A 40 30 0 1 0 230 110 M 250 110 270 140"
            style="stroke: gray; fill: none"
        />
    </defs>
    <g style="font-family: 'Liberation Sans'; font-size: 10pt">
        <use xlink:href="#curvepath" />
        <text>
            <textPath xlink:href="#curvepath">
                Following a cubic Bézier curve.
            </textPath>
        </text>
        <use xlink:href="#round-corner" />
        <text>
            <textPath xlink:href="#round-corner">
                Going 'round the bend
            </textPath>
        </text>
        <use xlink:href="#sharp-corner" />
        <text>
            <textPath xlink:href="#sharp-corner">Making a quick turn</textPath>
        </text>
        <use xlink:href="#discontinuous" />
        <text>
            <textPath xlink:href="#discontinuous">
                Text along a broken path
            </textPath>
        </text>
    </g>
</svg>

<textPath> 中指定 <path> 并不会自动将路径显示出来,上面示例中的路径是通过 <use> 来显示的

  • 通过设置 startOffset 属性值(百分比或长度)来调整文本在路径上开始的位置
  • 如果希望文本相对路径居中,只需设置 <text> 元素 textanchor="middle",并在 <textPath> 元素上设置 startOffset="50%" 即可
  • 超出路径结尾处的文本会被截断
<!-- @format -->

<svg width="100%">
    <defs>
        <path
            id="short-corner"
            transform="translate(40,40)"
            d="M0 0 L 30 0 A 30 30 0 0 1 60 30 L 60 60"
            style="stroke: gray; fill: none"
        />
        <path
            id="long-corner"
            transform="translate(140,40)"
            d="M0 0 L 50 0 A 30 30 0 0 1 80 30 L 80 80"
            style="stroke: gray; fill: none"
        />
    </defs>
    <g style="font-family: 'Liberation Sans'; font-size: 12pt">
        <use xlink:href="#short-corner" />
        <text>
            <textPath xlink:href="#short-corner">
                This text is too long for the path.
            </textPath>
        </text>
        <use xlink:href="#long-corner" />
        <text style="text-anchor: middle">
            <textPath xlink:href="#long-corner" startOffset="50%">
                centered
            </textPath>
        </text>
    </g>
</svg>

空白和文本

通过改变 xml:space 属性的值来改变 SVG 处理文本中空白字符(空格、制表符、换行符)的方式,取值如下

  • default:默认值,处理规则如下
    • 删除所有换行符
    • 将所有制表符转换为空格
    • 删除首尾空格
    • 将任意数量的连续空格换为一个空格
  • preserve:处理规则如下
    • 将所有换行符和制表符换为空格
    • 保留首尾空格

注意 SVG 的默认处理方式与 HTML 的不同。HTML 默认是将换行符转换为空格

案例学习:为图形添加文本

<!-- @format -->

<svg width="100%" height="300px">
    <defs>
        <path id="upper-curve" d="M -8 154 A 162 130 0 1 1 316 154" />
        <path id="lower-curve" d="M -21 154 A 175 140 0 1 0 329 154" />
    </defs>
    <ellipse cx="154" cy="154" rx="150" ry="120" style="fill: #999999" />
    <ellipse cx="152" cy="152" rx="150" ry="120" style="fill: #cceeff" />
    <!-- 浅红色大半圆填充符号的上半部分,其下方“浸入"符号左下方的浅红色小半圆 -->
    <path
        d="M 302 152 A 150 120, 0, 1, 0, 2 152 
 A 75 60, 0, 1, 0, 152 152"
        style="fill: #ffcccc"
    />
    <!-- 浅蓝色小半圆,填充符号右上方 -->
    <path d="M 152 152 A 75 60, 0, 1, 1, 302 152" style="fill: #cceeff" />
    <text style="font-size: 24px; text-anchor: middle">
        <textPath xlink:href="#upper-curve" startOffset="50%">
            这是在上面的文字
        </textPath>
    </text>
    <text style="font-size: 14px; text-anchor: middle">
        <textPath xlink:href="#lower-curve" startOffset="50%">
            这是在下面的文字
        </textPath>
    </text>
</svg>
Last Updated:
Contributors: af