SVGでテキストを表示するには、<text>要素を使う。この要素は単純に見えて、座標の指定方法などに少しクセがある。基本的な使い方をまとめたいと思う。

text要素で使える属性

SVGの<text>要素では、主に以下のような属性が使える(一般的な属性は省略している)。

属性名 概要
x テキストの左下の X X 座標を、長さあるいは%で指定。
デフォルト値は0
y テキストの左下の Y Y 座標を、長さあるいは%で指定。
デフォルト値は0
dx <text>要素内の文字の X X 座標を、直前の文字からの相対位置で指定。指定する値は、カンマ区切りで文字と対応させる。単位は長さあるいは%。デフォルト値はない。
dy <text>要素内の文字の Y Y 座標を、直前の文字からの相対位置で指定。指定する値は、カンマ区切りで文字と対応させる。単位は長さあるいは%。デフォルト値はnone
rotate <text>要素内の文字の角度を、各文字の左下を回転軸にして指定。指定する値は、カンマ区切りで文字と対応させる。
textLength テキストの長さを、長さあるいは%で指定。デフォルト値はnone
lengthAdjust textLengthに応じて、テキストを伸ばすか、余白を入れるか。spacingspacingAndGlyphsで指定。デフォルトはspacing(余白)。
text-anchor <text>座標の基準を、左下角、中央下、右下角のいずれかに設定。指定できる値はstartmiddleendのいずれか。デフォルトはstart(左下角)。

また、以下は各属性値を変更した際に<text>要素がどのように変化するかを試すサンプルである。

dxdyrotateは、カンマ区切りで複数の値を指定できる。しかし、うまくデモにする方法を思いつかなかったので、そのあたりの指定方法は割愛した。単発で指定すると全体、カンマ区切りで指定すると文字ごとに値を適用できる。

html
<!DOCTYPE html><html><head><!-- D3.jsの読み込み --><script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js" integrity="sha512-M7nHCiNUOwFt6Us3r8alutZLm9qMt4s9951uo8jqO4UwJ1hziseL6O3ndFyigx6+LREfZqnhHxYjKRJ8ZQ69DQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script></head>
  <body>
    <div class="slider-container">
      <label>x: <input type="range" id="x" min="-100" max="300" value="0" /></label
      ><br />
      <label>y: <input type="range" id="y" min="-100" max="300" value="13" /></label
      ><br />

      <label
        >rotate: <span></span
        ><input type="range" id="rotate" min="-360" max="360" value="30" /></label
      ><br />
      <label
        >textLength:
        <input type="range" id="textLength" min="0" max="300" value="50"
      /></label>
      <fieldset>
        <legend>lengthAdjust</legend>
        <input
          type="radio"
          id="lengthAdjust1"
          name="lengthAdjust"
          value="spacing"
        />
        <label for="lengthAdjust1">spacing</label>

        <input
          type="radio"
          id="lengthAdjust2"
          name="lengthAdjust"
          value="spacingAndGlyphs"
          checked
        />
        <label for="lengthAdjust2">spacingAndGlyphs</label>
      </fieldset>
      <fieldset>
        <legend>text-anchor</legend>
        <input
          type="radio"
          id="text-anchor1"
          name="text-anchor"
          value="start"
          checked
        />
        <label for="text-anchor1">start</label>

        <input type="radio" id="text-anchor2" name="text-anchor" value="middle" />
        <label for="text-anchor2">middle</label>
        <input type="radio" id="text-anchor3" name="text-anchor" value="end" />
        <label for="text-anchor3">end</label>
      </fieldset>
      <div class="source-viewer-container">
        source: <span id="source-viewer"></span>
      </div>
    </div>
    <svg width="200" height="200" style="background-color:salmon;">
      <text
        x="0"
        y="13"
        lengthAdjust="spacingAndGlyphs"
        textLength="79"
        rotate="30"
      >
        テスト
      </text>
    </svg>
    <script>

      function updateTextAttribute(attribute, value) {
        d3.select("text").attr(attribute, value);
        d3.select("#source-viewer").text(d3.select("text").node().outerHTML);
      }

      d3.select("#x").on("input", function () {
        updateTextAttribute("x", this.value);
      });

      d3.select("#y").on("input", function () {
        updateTextAttribute("y", this.value);
      });

      d3.select("#rotate").on("input", function () {
        updateTextAttribute("rotate", this.value);
      });

      d3.selectAll("input[name='lengthAdjust']").on("change", function () {
        updateTextAttribute("lengthAdjust", this.value);
      });

      d3.select("#textLength").on("input", function () {
        updateTextAttribute("textLength", this.value);
      });

      d3.selectAll("input[name='lengthAdjust']").on("change", function () {
        updateTextAttribute("lengthAdjust", this.value);
      });

      d3.selectAll("input[name='text-anchor']").on("change", function () {
        updateTextAttribute("text-anchor", this.value);
      });
      d3.select("#source-viewer").text(d3.select("text").node().outerHTML);
    </script>
  </body>
</html>

text要素のクセ

<text>要素を使う際に、地味に躓きそうなポイントがいくつかある。

デフォルト値だと表示されない

一般的な図形要素の座標は、左上が基準となる。一方<text>要素の座標は、左下が基準となる。デフォルトの座標が ( 0 , 0 ) (0, 0) であるため、値を省略すると文字の下が0.5px見切れる程度しか表示されない。よく見ないと、文字が表示されていないのかと見紛う。

座標を指定しないケースは稀かと思うが、私は初見で戸惑ったのでメモしておく。

折り返しが指定できない

SVGには、HTMLのように自動折り返しできる機能が備わっていない。SVG 2.0では、inline-sizeという属性値によって指定可能になるらしい。しかし、現在(2024年1月)のところ対応しているブラウザは見当たらない。

SVG 1.1で自動折り返しを実現するには、<text>タグではなく<foreignObject>タグを使う。このタグの中では、SVG以外のタグ名(別のXML名前空間)を含むことができる。

HTMLを使う場合は、そのタグにxmlns="http://www.w3.org/1999/xhtml"を追加する。

例えば以下のように書く。

html
<!DOCTYPE html><html><head><!-- D3.jsの読み込み --><script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js" integrity="sha512-M7nHCiNUOwFt6Us3r8alutZLm9qMt4s9951uo8jqO4UwJ1hziseL6O3ndFyigx6+LREfZqnhHxYjKRJ8ZQ69DQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script></head>
  <body>
    <svg width="200" height="200" style="background-color:salmon;">
      <foreignObject x="0" y="0" width="6em" height="10em">
        <div  xmlns="http://www.w3.org/1999/xhtml">これでSVGタグの中でもテキストの自動折り返しができる</div>
      </foreignObject>
    </svg>
  </body>
</html>

<foreignObject>要素には、xywidthheightの4つの属性を指定できる。

中央寄せ、左寄せがない

<text>要素は、HTMLのtext-alignのように、属性値によって中央寄せや左寄せを指定できない。座標値を直接指定するしかない。

デフォルトでは、テキスト領域の左下端が座標に対応する。ただ、これだと座標の計算が地味に面倒である。text-anchorを使うと、この手間を削減できる。

text-anchor属性にmiddleを指定するとテキスト領域の中央、endを指定すると同右下端に座標が変更される。中央寄せする場合はmiddle、左寄せする場合はendを使うと、細かい計算をしなくて済む。

text要素の中で使える要素

<text>タグには、単純なテキストの他に以下の2つの要素を含められる。

タグ 使いどころ
tspan <text>タグ中の一部のテキストを装飾する。
textPath <textPath>タグの中身を、<path>タグで定義したパスに沿って描画する。

両者は、同じ<text>タグに含めることができる。ただ、<textPath>タグは個別の<text>タグで分けた方がわかり良さそうではある。

<tspan>の属性値は、<text>タグと共通している。<tspan>に値を指定しない場合、<text>の属性値がそのまま適用される。

また、<textPath>タグの属性値は、まとめる気が起きないので割愛する。

html
<!DOCTYPE html><html><head><!-- D3.jsの読み込み --><script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js" integrity="sha512-M7nHCiNUOwFt6Us3r8alutZLm9qMt4s9951uo8jqO4UwJ1hziseL6O3ndFyigx6+LREfZqnhHxYjKRJ8ZQ69DQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script></head>
  <body>
    <svg width="300" height="300" style="background-color:salmon;">
      <path id="my_path" d="M 40,260 C 20,90 80,40 100,90" stroke="gold"  fill="none" />
      <text y="13" rotate="90 0"><tspan>テキストの</tspan><tspan rotate="20 40 60 80 100" fill="steelblue" dy="13 26 39 52 65" font-weight="bold">一部に装飾</tspan>をつける。
      <textPath xlink:href="#my_path">パスに沿ってテキストを表示する</textPath>
      </text>
    </svg>
  </body>
<html>

参考資料