SVGをdata URI schemeに変換するとき文字化けしないように必要なこと

現在、パソコン、スマートフォンタブレットと様々な画面サイズの情報機器が世の中に溢れています。そうした端末に適切に対応するには画像も色々なサイズのものをいくつも用意する必要があるわけですが、SVGのようなベクター画像であればそうした手間も少なくて済むのがメリットです。
例えば、AppleのサイトではロゴにSVGを使っています。

https://www.apple.com/jp/iphone/



そしてそのロゴを調べてみるとSVGをdata URI schemeで指定しているのが分かりますね。

data URI schemeはHTML文書内に記述できるため、画像ファイルのリクエストを減らして素早く画面表示が行えるという利点がありますから、世界中から多くのアクセスを集めるAppleのサイトで採用されているのでしょう。
こうした使われ方も多いことから、data URI schemeを簡単に作成できるWebサービスもいくつかあります。

DataURL.net
ただし、SVGをdata URI schemeに変換する際は気をつけないと予期せぬトラブルが起きてしまいます。


これはシンプルなSVGファイルですが、

こちらを前述の DataURL.net で変換したデータを使うとChromeではPC/Androidともにこのようになってしまいます。

はてなダイアリーではdata URI schemeは貼れないのでデモページで)
なぜこのように文字化けをしてしまうのか?今回は、data URI schemeの仕様から原因を探り、その解決策も解説していきます。

簡易目次



詳しくは後ほど説明しますが英語圏ではこの問題が顕在化しないのか、解説記事も他に見当たらないんですよね。



data URI schemeとは?そのメリットとデメリット

data URI schemeとは外部ファイルを指定することなくデータを記述できるURIスキームです。SVGに限らず、JPEGやGIF,PNGなどのラスター画像でも使えますし、テキストファイル、CSSJavaScript、(X)HTMLファイルなども仕様上は変換できます。
(※ただしIE9以下は画像ファイルにしか対応していません)
data URI schemeの大きなメリットは外部ファイルを指定せずに使えることでリクエスト数の削減になり、その結果描画速度を向上できます。また個人的にはGreasemokeyのようなユーザースクリプトBookmarklet、ユーザースタイルシートでもよく使いますね、画像ファイルも含めてまとめられるため配布する際にもとても便利な形式です。


ただしデメリットもあります。

デメリット

  • 元のファイルサイズから比較すると3割ほど大きくなる
  • data URI schemeで指定されたファイルそのものはキャッシュされない
  • 携帯機器などでは大きな画像は描画処理に時間がかかる
  • IE7以下は未対応

例えばこちらの画像は96バイトのGIFファイルですが

data URI schemeに変換すると150バイトになってしまいます*1

data:image/gif;base64,R0lGODlhEAAQAJEDAAAAAP/yAGMuB////yH5BAEAAAMALAAAAAAQABAAAAIxnC2ZxycBoWgOghhmSxZnpniRtokLNTwjWklsK5GUiilyKq5g7pYma1O8UohhkXgoAAA7

また、data URI schemeで指定したファイルそのものはキャッシュされないのも特徴ですね。そのため、前述のAppleのサイトのロゴのように複数のページで共通の要素はdata URI schemeを使わず画像としてキャッシュさせた方がトータルでは効率的かもしれません。
そしてdata URI schemeは必然的にデコードが行われます。スマートフォンなど携帯機器で大きな画像のデコードを行う場合は却って描画に時間がかかってしまうこともあります。

あとはIE7以下はdata URI scheme未対応というのもありますね、これはシェアを考えるとそろそろ改善してきていますが。


ですので何でもかんでもdata URI schemeにすれば良い、というわけではなく特徴を理解して利用するのが大切です。



さらに詳しくdata URI schemeの仕様について

data URI schemeの仕様はRFC2397(日本語訳)で規定されています。
ここでは「Hello world!」と書かれた12バイトのテキストファイルをdata URI schemeにして解説していきます。

data:text/plain;charset=US-ASCII;base64,SGVsbG8gd29ybGQh

URIスキーム

data:はURIスキームです。URI(URL)の先頭部分というと分かりやすいでしょうか。
URIスキームは他には例えばメールアドレスを記載する際によく使われる mailto: もその一つですね。ftp: 、http: 、https: もURIスキームの仲間です。
参考記事:URI scheme - Wikipedia

mediatype

mediatypeは対象のデータのContent-typeを記述します。

Content-type

仕様ではこのように規定され

mediatype := [ type "/" subtype ] *( ";" parameter )

typeとsubtypeでファイルの種類を記述します。代表的なContent-typeはこんな感じです。

種類 拡張子 Contents-type
テキストファイル txt text/plain
HTML (Hyper Text Markup Language) htm, html text/html
CSS (Cascading Style Sheets) css text/css
JPEG (Joint Photographic Experts Group) jpg, jpegなど image/jpeg
GIF (Graphics Interchange Format) gif image/gif
PNG (Portable Network Graphics) png image/png
SVG (Scalable Vector Graphics) svg, svgz image/svg+xml
parameter

parameter := attribute "=" value

parameterは仕様ではこうなっていますが、実際には文字コードの指定として使われることが多いでしょう。
このContent-typeとparameterが省略された場合は規定値の

text/plain;charset=US-ASCII

が適用されます。

データ形式

base64形式はアルファベットの小文字a-z、大文字A-Z、数字0-9の計62字とさらに / と + と = の3字を加えた文字を使ってデータを記述する形式です。64文字じゃないですが名称はbase64
ただdata URI schemeには必ずしもbase64形式を使う必要はなく、パーセントエンコード形式を使っても構いません。

データ

ここは変換されたデータ部分です。




つまり、ここまでの仕様をおさらいすると
Hello world!」と書かれたテキストファイルのdata URI schemeは先ほど紹介した通りこのようになりますが

data:text/plain;charset=US-ASCII;base64,SGVsbG8gd29ybGQh



文字コードを省略した場合「charset=US-ASCII」がデフォルトとなるため、こう記述してもいいですし

data:text/plain;base64,SGVsbG8gd29ybGQh



Content-typeは「text/plain」がデフォルトですからさらに省略できますし

data:base64,SGVsbG8gd29ybGQh



base64形式でなくパーセントエンコード形式でもOKなので

data:,Hello%20world%21

と簡略した記述もできるわけです。



data URI scheme文字コードの指定

data URI schemeの仕様を学んだところで、DataURL.netで変換した前述のGIF画像を再度確認してみましょう。

data:image/gif;base64,R0lGODlhEAAQAJEDAAAAAP/yAGMuB////yH5BAEAAAMALAAAAAAQABAAAAIxnC2ZxycBoWgOghhmSxZnpniRtokLNTwjWklsK5GUiilyKq5g7pYma1O8UohhkXgoAAA7

こちらにも文字コードの指定がありませんね。
JPEGやGIF,PNG画像といったバイナリデータならばそれでも問題はありません。むしろ文字コードの記述を削った方がファイルサイズの削減につながります。
さらにIE8-9では画像ファイル以外にdata URI schemeには対応しておらず、他のファイルをdata URI schemeにするということも滅多になかったために長らく文字コードを指定しないで使われることが多くなりました。
そのためかWebの技術を紹介する海外・国内のブログなどでも

data URI schemeには文字コードの指定は必要ない

という誤った認識がどうやら流布していったようなんですよね……。


これは
(※JPEG,GIF,PNG画像ならば)data URI schemeには文字コードの指定は必要ない
というのは確かに事実ではあるのですが、いくつかの技術記事の孫引きを経てその注釈が無くなっていったのではないかなー、と。

SVGで表出する問題と表出しない問題

バイナリデータであるJPEG,GIF,PNG画像とは異なり、SVGは画像ファイルではあるのですが実態はXMLを基盤としたテキストデータですから適切な文字コードの指定を行わないといけません。


例えばAppleのdata URI schemeをもう一度 確認すると。

こちらも文字コードの指定がありませんね。
SVGファイル自体にはXML宣言で文字コードUTF-8と指定されているにもかかわらず、この場合は仕様通り「charset=US-ASCII」として扱われることになります、……が、英語圏では英数字しか使われないためそのままでも問題が起きずに見過ごされています。
data URI schemeに変換する機能を持つライブラリやサービスはいくつかあるのものの、文字コードを適切に処理するタイプは少ないんですよね……。
具体的に言うとSVG関連だと利用者の多い、最適化ツールのSVGOもdata URI schemeを書き出す機能がありますが文字コードの指定はされません。

SVGO - GitHub



ここまでをまとめると

  • 従来data URI scheme文字コードの指定を省く事例が多かった
  • JPEG,GIF,PNG画像のようなバイナリデータならそれでもOKだった
  • やがていつの間にか、文字コードの指定が必要ないという誤解が広まる
  • しかしSVGはテキストデータなので文字コードの指定が必要
    • ……なのだが英数字しか使わない英語圏では省いても問題が起きない
  • data URI schemeの変換サービス、ライブラリが未対応なケースが多い

という感じです。

文字化けを防ぐための対処法

誤った文字コードの指定で起きる文字化けを防ぐため、正しく文字コードを示しましょう。
このSVG画像は

<svg xmlns="http://www.w3.org/2000/svg" width="200" height="25">
	<text x="5" y="20" font-size="20" stroke="none" fill="#000">abc123日本語</text>
</svg>

こうした構造になっており、文字コードUTF-8です*2
ですから、data URI schemeにするときにはこのように

data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMDAiIGhlaWdodD0iMjUiPjx0ZXh0IHg9IjUiIHk9IjIwIiBmb250LXNpemU9IjIwIiBzdHJva2U9Im5vbmUiIGZpbGw9IiMwMDAiPmFiYzEyM+aXpeacrOiqnjwvdGV4dD48L3N2Zz4=

適切に指定しましょう。
今回の例ではtext要素で文字列を扱いましたが、SVGの仕様から制作者が任意の文字列を入れられるのはtext要素、title要素、desc要素、metadata要素、id名、class名、フォント名などと多岐にわたります。
特に制作ソフトが Adobe Illustrator だと、レイヤー名やブラシの名前が意図せずそのままidに使われることもありますから、仕様に詳しくないデザイナーがうっかり制作したSVGファイルが文字化けしたり、表示されない事態を避けるためにも、文字コードの指定を必ず入れるようにするといいのではないでしょうか。

あとちなみにbase64形式ではなく、パーセントエンコード形式で行うならこうなります。

data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22200%22%20height%3D%2225%22%3E%3Ctext%20x%3D%225%22%20y%3D%2220%22%20font-size%3D%2220%22%20stroke%3D%22none%22%20fill%3D%22%23000%22%3Eabc123%E6%97%A5%E6%9C%AC%E8%AA%9E%3C%2Ftext%3E%3C%2Fsvg%3E

パーセントエンコード方式はbase64形式に比べてファイルサイズが増えてしまうのが欠点ですが、可読性の面で利点がありますし*3、また文字列の操作だけで手軽に色やサイズの変更を行いやすいため、JavaScriptCSS拡張メタ言語 SCSS(Sass)やLESSでも扱いやすいというのがメリットでしょうか。



ブラウザの対応状況はどうか?

仕様はさておき、肝心のブラウザ対応状況はどうよ?という点を検証してみました。
実のところ、data URI scheme文字コードの指定を省いていたとしてもブラウザ側でうまいこと処理してくれるケースもあります。
仕様上SVGHTML5で指定できる要素はインラインSVGを除くと概ね以下の通り

  • object要素
  • embed要素
  • iframe要素
  • img要素
  • input要素(type:image イメージボタン)
  • favicon
  • CSSによる指定
    • background-image
    • border-image
    • list-style
    • filter
    • clip-path
    • Web fonts *4

まだ実装が揃っていないものもありますし(faviconなど)、これ以外にもリンクからdata URI schemeを直接表示に表示させる方法もありますね*5。これらに文字コードの指定を省いたdata URI schemeを適用して文字化けをするか検証してみました。

検証用のデモページ
文字コードの指定が無いことで文字化けするものが×、指定の有無に関係なく表示するのは○です。

objectembediframeinputimgCSS background-image直接表示
PC Chrome38××××
PC Opera24××××
PC Firefox32
PC IE11
Android Chrome38××××
iOS7 Safari
iOS8 Safari

環境はWindows7 64bit、AndroidはNexus7(2013)、iOSiPhone5iPad(第3世代)です。
IE11はセキュリティの懸念からSVGに限らずdata URI scheme自体がiframe要素などで使えない(参考記事)ため、※マークです。
文字コード指定の有無が関連するのはPC/AndroidChromeOperaですね。
しかし実際の用途としては、JPEG,GIF,PNG画像を置き換える目的でSVG画像を使うでしょうし、その際は主にimg要素やbackground-imageで使用するために影響は少なさそうです。


ちなみになぜimg要素などは大丈夫な一方で文字化けする要素があるかというと、SVGは使用する要素によって挙動が異なる「参照モード(Referencing modes)」があります。
その区別で言うところのDynamic Interactive Modeで動作するobject,embed,iframe要素及び直接表示ではブラウザの扱いが異なるために文字コードの指定の有無が影響を及ぼすようです。
参考記事:svgの表示方法についての補足 - svg要素の基本的な使い方まとめ



まとめ:実際のところ

長々と書いてきましたが、実状としてはdata URI schemeSVGを指定する場合はおそらく小さいアイコンやロゴを使うことが多いでしょうから、英数字以外の文字列が用いられることもまず無く、現状でもさしてトラブルは起きないでしょう。
ただ誰かがまとめておかないと、このノウハウは共有されないままだな、きっと……と思ったので書きました。
この問題はSVGに限らず(X)HTMLやCSSJavaScriptファイルでも同様なのですが、それらをdata URI schemeにするケースはあまりない、というのも顕在化しない理由なのかもしれませんね。


><

*1:この例では1.5倍くらいになっていますが、「data:image/gif;base64,」が22バイト、データ部分は128バイトと、データに限って言えば3割ほどの増大です

*2:そのためXML宣言は省略されています、詳しくは以前書いた記事で SVGコードゴルフ - 条件を満たせばXML宣言は省略できる

*3:普通の人はSVGファイルのソースを読まない、……というツッコミはさておき

*4:SVG fontsのサポートはChrome38以降はWindows Vista/XPだけになったことを鑑みると、敢えて使う理由は特段なさそうですが。参考記事:Chrome 38 Beta: New primitives for the next-generation web - Chromium Blog

*5:クライアントサイドで動作するサービスで生成したSVG画像をダウンロードさせたい場合などにそうした手法を使います。例えば手動SVG組版機など