Javascript で canvasへ文字を描く

先の技術解説は、技術と言っても「ボットよけの技術」の解説を中心とした。

今回作成のプログラムで使われた技術についても、少し書いておこう。


今回の肝は、canvas だった。

HTML5 で追加された新要素だが、あまりにも野心的過ぎて策定に時間がかかったので、完全な説明を行っているサイトが少ない。

解説サイトでは「文字は描画できない」という記述が多いが、現在では描画できる。でも、文字を描画しようとすると、機能が足りなくてなかなか困る。

そのうち機能が充実して、このページに書いてある苦労話なんて、機能が低かった時代の笑い話になればよいと思う。


後日追記。
2012.12.17 にHTML5の仕様がやっと固まった。まだドラフト案だけど、必要な機能がちゃんと盛り込まれ、このページに書いてあることは「過去の笑い話」になった。
もっとも、仕様が固まったからと言って、すぐにブラウザが実装できるわけでもないし、過去にインストールされたブラウザがすべてアップデートされるわけでもない。
もうしばらくは、ここに書かれた「面倒くさいテクニック」は有効であり続けるだろう。


文字を描画したい、と思ったら、font のデザインについて知らなくてはならない。

英語のフォントは、「ベースライン」の上に描かれる。ただし、一部の小文字( g j p q y )はベースラインの下にはみ出る。まぁ、この程度なら多くの人が理解しているだろう。


小文字が下にはみ出るときの、最下限位置が「ディセンダライン」だ。デザインを統一するため、下にはみ出るときはみな、この線まではみ出る。でも、それより下には行かない。

同じように、ベースラインより上にも、多くの文字が従う3つの線がある。多くの小文字の上限、全ての大文字の上限、一部の小文字( b d f h k l )の上限だ。

ここで重要なのは、一部の小文字の上限だ。これらの文字は、大文字よりも上に突き出るように描かれる。この上限の線のことを、「アセンダライン」と呼ぶ。


つまり、全ての文字は、ディセンダラインからアセンダラインの間に描かれる。その間には、全ての文字を描く基準となるベースラインがある。あと2つの線があるが、今はこれは気にしないでよい。


さて、フォントの「サイズ」の話をしよう。

通常、フォントのサイズとは「大文字 M の縦の大きさ」だと考えてよい。この文字が一番正方形に近い見た目をもち、フォントのサイズを測るさいの目安にされるためだ。css などで、一文字を意味する em という単位があるが、これは M に由来している。

つまり、フォントのサイズ=大文字の縦の大きさ、ということになる。

しかし、PC のフォントでは、「だいたい」フォントサイズ=ディセンダラインからアセンダラインの間、である。つまり、描画領域が「だいたい」フォントのサイズだ。


「だいたい」と繰り返すにはわけがある。フォントのサイズと、描画領域の大きさはイコールではないのだ。

その昔、「テキストモード」と呼ばれる画面モードがあった時代がある。この時代、文字は 8×8dot なり、8×16dot なりでデザインされていた。なので、16dot フォントであれば、大文字も小文字も 8×16dot の中に描かなくてはならなかった。

しかし、テキストモードは文字と文字の間に隙間が無かった。文字デザインは、最初から「周囲の文字とくっつかないように」1dot の隙間を作る必要があった。別の言い方をすれば、アセンダラインからディセンダラインまで、15dot で描かなくては、16dot のフォントとして使用できなかった。

結果的に、PC では、15dot で描かれたフォントを 16dot フォントと呼ぶ習慣ができた。

同様に、8dot フォントは 7dot で描かれるし、24dot フォントは 23dot で描かれる。


1984 年に発売された Macintosh では、プロポーショナルなフォントを扱えるようになった。といっても、最初はまだビットマップフォントで、標準の 12dot フォントは、やはり縦が 11dot だった。

Mac OS 7 が完成したとき、TrueType が搭載された。それまでは、フォントは「升目を塗りつぶして表現したもの」だったが、TrueType では、文字は「線の集合」として作られたので、もはや升目は問題ではなくなった。はみ出すことも可能なのだ。


あいかわらず、「フォントサイズ」の基準は、ディセンダラインからアセンダラインである。しかし、フォント作者は、ディセンダラインより下や、アセンダラインより上に線がはみ出るフォントを設計できる。

つまり、16dot フォントの描画領域の高さは、16dot とは決まらなくなった。15dot かも知れないし、17dot かもしれない。もはや、フォントのサイズは「作者が主張するサイズ」というだけで、あまり意味を持たない。


さて、フォントのサイズが意味を持たないと言っても、それでは描画ができない。

OS では、フォントに関する情報を問い合わせるための API が備えられている。これを使えば、実際のベースラインがどこか、ディセンダライン・アセンダラインがどこにあるか、さらに、それをはみ出して描画するか、その場合はどこまではみ出るか…などを細かく調べることができる。なので、アプリケーション作者は、変なフォントを使っても正しく画面描画ができる。万々歳である。



ところが、だ。ここからが本題だ。html5 の canvas は、文字を描画できるにもかかわらず、文字フォントに対する情報を問い合わせる方法が無い。

一応、measureText というメソッドはある。canvas (の 2d コンテキスト)にフォントやそのサイズを設定し、mesureText で描画したい文字列を設定すると、描画に必要な情報を教えてくれる。

情報はオブジェクトになっていて、各種情報をプロパティとして取り出せる。…が、現在のところ、文字列描画に必要な「幅」しか教えてくれない。

つまり、どこをベースラインとして描画すればよいかもわからず、縦の大きさもわからない。縦の大きさはフォントサイズと決め打ちしてもよいが、先に書いたとおりにそれでは正しい描画ができない可能性が高い。


技術話を書くために、何が苦労なのかを解説するだけでずいぶんかかってしまった。

つまり、文字の描画をしたいのに描画領域が確定しない、というのが問題なのである。


解決策

ではどうやって解決したか?

canvas は、基本的に「描画領域」である。描画はできるが、特定ドットが何色になっているか、などは調べられない。

なので、文字を描いてみて、どこにドットがあるかを調べてみる、と言う手法は使えない。



2013.3.25追記
このプログラムを作った段階では不勉強で知らなかったのですが、getImageData すると得られるオブジェクトの内部構造は、ECMAScript の仕様で厳格に決められていました。
(プログラム作成時には、資料が見つからず、実装依存だと思っていた)
そのため、getImageData してからオブジェクトを参照することで、ドット単位で色などを調べられます。

ただ、データを取り出す方法が無いわけではない。toDataURL を使うと、画像を png 化し、base64 encode して、さらに data: 形式の URL 化した形で返してくれる。

もちろん、画像サイズが変わればデータも変わる。なので、画像サイズを変えながらひたすら文字を描いてみて、うまく描けたかどうか調べてみる、と言う方法も使えない。


でも、まだ手はある。画像サイズが変わらないまま、「仮想的に」画像にサイズがあるように制限すればよいのだ。canvas の 2d コンテキストには、 clip という、クリッピング領域を作り出す命令がある。これを使おう。


結果、処理は次のようなものに落ち着いた。

  1. 十分なサイズの canvas を用意する。この canvas の、上から8割程度のところを「ベースライン」と仮定する。
  2. ベースラインを中心として、上下に「少し」のところを、矩形にクリッピングする。
  3. ベースライン上に文字を描いて見る。
  4. toDataURL して、以前描いたデータと比べる。
    データが以前と違うなら処理を続ける。
    同じなら、直前に広げたクリッピング範囲は必要以上に大きい、ということだ。
    前のクリッピング範囲が「適正範囲」だったとわかったので、処理終了。
  5. クリッピング範囲を広げながら、2 に戻って繰り返す。

簡単に書いてあるけど、クリッピング領域を広げるのは、上方向と下方向で別々にやらないといけないし、ちゃんと「直前に書いた」文字を消してから描かないとおかしなことになる。

(同じ文字だから大丈夫、と思っていると、アンチエイリアス処理に足元をすくわれる)


これでフォントに対して、本当のベースライン・ディセンダライン・アセンダラインがわかったら、あとは適切なサイズに canvas をリサイズして、改めて描画すれば目的は完了である。



ちなみに、font の描画領域を知る方法が無いのと同じように、Javascript から、OS で使用可能なフォント一覧を得ることもできない。

今回のプログラムでは、OSの標準フォントや、よく使われるフォントから「入っていると期待」するフォントのリストを作っているが、フォントが存在しないときはリストから消去するようにしている。

どうやってフォントが存在しないことを調べるかと言えば、これも toDataURL で生成されるデータを比較している。存在しないフォントを指定すると、デフォルトフォントで描画されるため、最初にデフォルトフォントで描画しておけば、「同じデータ」を検出できるのだ。

(ページ作成 2011-09-14)
(最終更新 2013-03-25)

前記事:スパムメールが来ないようにする     戻る     次記事:Javascriptプログラムの短縮
トップページへ

-- share --

0000

-- follow --




- Reverse Link -