やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

SVGスプライトからフォントを作成できなかったログ

 図形を使い回せるSVGスプライトからフォント作成できたら嬉しい。と思って試したがダメだった記録。

前回まで

 SVG画像からWOFF2を生成できた。

 <svg>をそのまま<glyph>に含めてfontconvに渡せばWOFF2を生成できた。

今回

 以下SVGスプライトをSVGフォントにしてWOFF2を作成しようと試みた。

結果

font.svg

 入力

<svg xmlns="http://www.w3.org/2000/svg">
  <defs>
    <symbol id="arrow-top" viewBox="0 0 256 256"><path style="opacity:1;fill:none;fill-opacity:0;stroke:currentColor;stroke-width:16;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="M128 7.781 7.781 120.22H64V248h128V120.219h56.219L128 7.78z"/></symbol>
    <font name="my-arrows-font" horiz-adv-x="256" vert-adv-y="256" >
      <font-face font-family="my-arrows-font" font-weight="400"
        font-stretch="normal"
        units-per-em="256"
        ascent="240"
        descent="-16"/>
      <missing-glyph glyph-name=".notdef" horiz-adv-x="256" vert-adv-y="256" d=""/>
      <glyph glyph-name="arrow-top" unicode="&#xe902;"><svg><use href="#arrow-top"></use></svg></glyph>
      <glyph glyph-name="arrow-bottom" unicode="&#xe903;"><svg><use href="#arrow-top" transform="rotate(180, 128, 128)"></use></svg></glyph>
      <glyph glyph-name="arrow-left" unicode="&#xe904;"><svg><use href="#arrow-top" transform="rotate(270, 128, 128)"></use></svg></glyph>
      <glyph glyph-name="arrow-right" unicode="&#xe905;"><svg><use href="#arrow-top" transform="rotate(90, 128, 128)"></use></svg></glyph>
      <glyph glyph-name="arrow-top-left" unicode="&#xe906;"><svg><use href="#arrow-top" transform="rotate(315, 128, 128)"></use></svg></glyph>
      <glyph glyph-name="arrow-top-right" unicode="&#xe907;"><svg><use href="#arrow-top" transform="rotate(45, 128, 128)"></use></svg></glyph>
      <glyph glyph-name="arrow-bottom-left" unicode="&#xe908;"><svg><use href="#arrow-top" transform="rotate(225, 128, 128)"></use></svg></glyph>
      <glyph glyph-name="arrow-bottom-right" unicode="&#xe909;"><svg><use href="#arrow-top" transform="rotate(135, 128, 128)"></use></svg></glyph>
      <glyph glyph-name="arrow-top-bottom" unicode="&#xe910;"><svg>
        <use href="#arrow-top"></use>
        <use href="#arrow-bottom"></use>
      </svg></glyph>
      <glyph glyph-name="arrow-pair-top-bottom" unicode="&#xe911;"><svg>
        <use href="#arrow-top" transform="scale(0.5, 1)"></use>
        <use href="#arrow-bottom" transform="scale(0.5, 1) translate(256, 0)"></use>
      </svg></glyph>
      <glyph glyph-name="arrow-pair-left-right" unicode="&#xe912;"><svg>
        <use href="#arrow-left" transform="scale(1, 0.5)"></use>
        <use href="#arrow-right" transform="scale(1, 0.5) translate(0, 256)"></use>
      </svg></glyph>
      <glyph glyph-name="arrow-quad-outside" unicode="&#xe913;"><svg>
        <use href="#arrow-top-left" transform="scale(0.5, 0.5)"></use>
        <use href="#arrow-top-right" transform="scale(0.5, 0.5) translate(256, 0)"></use>
        <use href="#arrow-bottom-left" transform="scale(0.5, 0.5) translate(0, 256)"></use>
        <use href="#arrow-bottom-right" transform="scale(0.5, 0.5) translate(256, 256)"></use>
      </svg></glyph>
      <glyph glyph-name="arrow-quad-inside" unicode="&#xe914;"><svg>
        <use href="#arrow-bottom-right" transform="scale(0.5, 0.5)"></use>
        <use href="#arrow-top-right" transform="scale(0.5, 0.5) translate(0, 256)"></use>
        <use href="#arrow-bottom-left" transform="scale(0.5, 0.5) translate(256, 0)"></use>
        <use href="#arrow-top-left" transform="scale(0.5, 0.5) translate(256, 256)"></use>
      </svg></glyph>
    </font>
  </defs>
</svg>
  1. 最初に<symbol>で図形を定義しておく
  2. あとは<glyph>内で<svg><use>し変形する

 これならパスデータを短縮できるし、最初の一個以外は作図ツールでなくテキストエディタtransform属性値をイジれば済む。めちゃ楽だと思って試した。

コマンド

fontconv font.svg font.woff2

結果

 結果、以下エラーログを吐いて失敗した。

/usr/local/lib/node_modules/fontconv/node_modules/svg2ttf/lib/svg.js:18
    if (pathElem.hasAttribute('d')) {
                 ^

TypeError: Cannot read properties of undefined (reading 'hasAttribute')
    at getGlyph (/usr/local/lib/node_modules/fontconv/node_modules/svg2ttf/lib/svg.js:18:18)
    at /usr/local/lib/node_modules/fontconv/node_modules/svg2ttf/lib/svg.js:160:17
    at /usr/local/lib/node_modules/fontconv/node_modules/lodash/lodash.js:4943:15
    at Function.forEach (/usr/local/lib/node_modules/fontconv/node_modules/lodash/lodash.js:9410:14)
    at Object.load (/usr/local/lib/node_modules/fontconv/node_modules/svg2ttf/lib/svg.js:159:5)
    at svg2ttf (/usr/local/lib/node_modules/fontconv/node_modules/svg2ttf/index.js:22:21)
    at getFont (file:///usr/local/lib/node_modules/fontconv/esm/mod.js:62:21)
    at convert (file:///usr/local/lib/node_modules/fontconv/esm/mod.js:227:24)
    at main (file:///usr/local/lib/node_modules/fontconv/esm/cli.js:33:27)
    at file:///usr/local/lib/node_modules/fontconv/esm/cli.js:41:1

Node.js v18.10.0

 ログから察するに、グリフを取得するためのSVG要素を取得するのに失敗したのだろう。nullが返されて、そのnullhasAttributeメソッドが未定義だからエラーになったのではないかと予想する。

 そもそもSVGフォントの仕様的に、<symbol><use>が使えるとはどこにも書いていないと思う。最初に書いたSVGコードが仕様的にダメなのか、それともfontconvの機能不足なのか。少なくともその判断ができない私の知識不足があるのは間違いない。

 改めて調べてみた。

 SVG 1.1 仕様書 glyphには以下のような一文があった。

glyph 要素が子要素を持つ場合、それら子要素は use 要素から参照されたシンボルの描画と同様な仕方で描画される。

 また、フォント定義ではuse要素とWEBフォントの比較の項目があった。

 どちらも<glyph>内で<use>を使うことは想定していないような書き方に見える。私的には不自然に思えるのだが。そういう仕様なのか?

 SVG 1.1 仕様書 glyphを見てみると、内容モデル:の項目に構造要素がある。リンク先を見るとそれはdefs, g, svg, symbol, useの要素を指していた。つまり<glyph>の子要素には<use>を含めることができるはずだ。

 なのにエラーになったのはfontconv<use>に非対応だからだと思われる。

ソースコードを追う

function getGlyph(glyphElem, fontInfo) {
  var glyph = {};

  if (glyphElem.hasAttribute('d')) {
    glyph.d = glyphElem.getAttribute('d').trim();
  } else {
    var pathElem = glyphElem.getElementsByTagName('path')[0];

    if (pathElem.hasAttribute('d')) {
      ...
    } else {
      throw new Error("Can't find 'd' attribute of <glyph> tag.");
    }
}

 上記はSVGフォントの<glyph>から図形データを取得するコードである。この時、<glyph>d属性値または<glyph>の最初の子要素<path>d属性値からしか取得していない。

 仕様書によれば<use>も使えると書いてあった。なのに<use>から取得するコードがないように見える。つまり実装漏れか、未対応/非対応ではないか。

 もし実装するなら<use>transformなどの影響を受けた結果のd属性値をどうにかして算出し、それを<glyph>d属性値にセットする必要がありそうだ。

結論

 fontconvでは<use>が使えない。

 svg2ttfライブラリが<use>から図形データを読み取ってくれないから、同ライブラリを使用したfontconvでも<use>が使えずエラーになる。

成功するパターン

 <symbol><use>を使わず、<glyph>の中にSVG画像を<svg><path>丸ごと含めてfontconvに渡す。これは正常終了した。

<svg xmlns="http://www.w3.org/2000/svg">
  <defs>
    <font name="my-arrows-font" horiz-adv-x="256" vert-adv-y="256" >
      <font-face font-family="my-arrows-font" font-weight="400"
        font-stretch="normal"
        units-per-em="256"
        ascent="240"
        descent="-16"/>
      <missing-glyph glyph-name=".notdef" horiz-adv-x="256" vert-adv-y="256" d=""/>
      <glyph glyph-name="arrow-top" unicode="&#xe900;"><svg><path style="opacity:1;fill:none;fill-opacity:0;stroke:currentColor;stroke-width:16;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="M128 7.781 7.781 120.22H64V248h128V120.219h56.219L128 7.78z"/></glyph>
    </font>
  </defs>
</svg>

 でもこれだと<symbol>, <use>の恩恵が受けられず、一個ずつ個別に図形データをInkscape等で作成する必要がある。面倒すぎて嫌だ。誰か何とかしてくれ。

 というか、なぜ成功するか謎。今回のSVG<glyph>直下に<svg>があり、その下に<path>だ。先述で追ったJSコードでは解析できずエラーになるのでは? throw new Error("Can't find 'd' attribute of <glyph> tag.");のルートに入ると思うのだが? なのに正常終了したんだが? 意味分からん。バージョン差異によりコードが違うとか? 謎。