風柳メモ

ソフトウェア・プログラミング関連の覚書が中心

SVGでテキストの縦位置(baseline)を調整する方法を調べてみた

SVGで、テキストの縦位置(baseline)を揃える処理を書こうとしてはまったので、覚え書き。

経緯

コピィ・ライターを作成していてふと、
「あれ? 基本的にテキストを配置してバナーを作るものだし、SVGと相性良いんじゃ? 設定ファイルを JSON で書き出しているところをちょっといじれば、SVG で書き出すこともできるようになるかも?」
と思い立ったので、処理を追加してみたところ、テキストの baseline を揃えるところで苦労したというもの。
そもそも、SVGをいじるのはこれが初めてなので、誤解をしているところはあるかも……変なところがあればご指摘願いたい。

どこではまったか? 「え……またIEかよっ!」

HTML5 Canvas の場合、textBaseline プロパティを指定すればテキストの baseline が指定できていた。
同じようなものが SVG でも無いか探してみると、text 要素等で指定できる dominant-baseline プロパティがこれに相当するようだ。

HTML5 Canvas と SVG のテキストの baseline の対応(今回対象としたもののみ)
baseline(基底線) Canvas
(textBaselineプロパティ)
SVG
(dominant-baselineプロパティ)
The top of the em square top text-before-edge
The middle of the em square middle central
The bottom of the em square bottom text-after-edge

em square=文字の構造的な大きさを表す四角形、EM box・em quad 等とも。

■参考

textBaseline プロパティ - Canvasリファレンス - HTML5.JP
svg要素の基本的な使い方まとめ


それで、実際やってみたところ……

のように、IE では効いていないように思われた。


それで調べてみると、確かにサポートされていないようだ。

IE9 Mode, IE10 Mode, IE11 Mode, and EdgeHTML Mode (All
Versions)

The dominant-baseline attribute is not supported.

[MS-SVG]: [SVG11] Section 10.9.2, Baseline alignment properties

ではどうするか?

とりあえず、IE でなんとかする方法を探してみたところ、

The specification defines central like that:


central


This identifies a computed baseline
that is at the center of the EM box.

We can take an EM box of known font size and measure its bounding box to compute the center.

internet explorer 9 - How to center SVG text vertically in IE9 - Stack Overflow

とあり、具体的なソースコードが書かれている。


どうやら、SVG中の script 要素にて、EM box の座標と大きさを元に central 相当の位置を決めて、transform 属性にて位置を調整する、ということらしい。

  • SVG 中の text 要素に指定した座標を (Tx, Ty)
  • 実際に描画された当該 text 要素の EM box(外枠) の左上の座標を (Rx, Ry)、高さを Rh


とすると、

text-before-edge 相当位置 Ry + (Ty - Ry)
central 相当位置 Ry + ( (Ty - Ry) - (Rh / 2) )
text-after-edge 相当位置 Ry + ( (Ty - Ry) - Rh )

になると思われる。


transform 属性には、

translate(<tx> [<ty>])
tx, ty による 並進 を指定する。
<ty> が与えられていない場合、 0 とみなされる。

座標系, 変換, 単位 – SVG 1.1 (第2版)

のような変換定義を指定してやればテキストの移動が可能なので、例えば central の場合には
tx = 0, ty = (Ty - Ry) - (Rh / 2)
を指定してやればよい。
tx, tyは、EM box左上端からの相対位置なので注意。


コピィ・ライターでは、具体的には、以下のようなスクリプトを SVG 内に挿入した。

var useragent = window.navigator.userAgent.toLowerCase();
if (useragent.indexOf('msie') < 0 && useragent.indexOf('trident') < 0) {
    return; // IE 以外は何もしない
}
window.onload = function() {
    var elm_text_list = document.getElementsByTagName('text');
    for (var ci=0, len = elm_text_list.length; ci < len; ci++) {
        var elm_text = elm_text_list[ci]
        ,   text_y = parseInt(elm_text.getAttribute('y'))
        ,   rect = elm_text.getBBox()
        ,   rect_y = rect.y
        ,   rect_height = rect.height
        ,   offset_y = (text_y - rect_y) - (rect_height / 2); // central の場合
        
        elm_text.setAttribute('transform', 'translate(0, ' + offset_y + ')');
    }
};


なお、IE 以外の場合には、別途 SVG の text 要素に対して dominant-baseline プロパティを設定してあるため、スクリプトでは処理を行わない。

結果

実際に上記手法を適用して作成したSVG画像が、Chrome と IE でどう見えるかを示す。
赤線は基線(SVGのテキスト要素で指定したY座標)、テキストの周囲の四角はEM box(スクリプトで描写)

Google Chrome (バージョン 43.0.2357.132 m)


Windows 7 上で確認した限りでは、Firefox や Opera の最新版でもほぼ同様に見えた。

IE11

注意点

  • SVG ファイルを HTML中に貼り付ける際、IMG要素 を使用すると、SVG内のスクリプトも実行されないため、上記の小細工が適用されない。
    これはCSSのbackground-image等で指定する場合でも同様。
  • OBJECT要素や IFRAME要素で指定してやれば、意図通りに表示される。
    ただし、IFRAMEだと扱いが比較的面倒であるため(CSSで拡大縮小しづらい等)、OBJECT要素を使用するのがベター。