前回の記事で「まあこれくらいで良いかな」と言ったにもかかわらずまたアップデートしました。
Nuget:
https://www.nuget.org/packages/Tategaki/
今回の変更点
今回は目新しい新機能の追加等々はそんなに無いのですが、フォント周りの処理を一新しました。
今まではTypeLoaderというライブラリを使用して読み込んでいたのですが、これを自前のコードで実装しました。TypeLoaderで実装されていない情報を使用したかったのですが、やはり他人の書いたコードに手を入れるのは好きになれず…と言うよりあまり中身を理解しないまま触るのに抵抗感があり、自分での実装に踏み切りました。
それに伴って、以下の機能が実装されています。
- プロポーショナルフォントに対応
- 使用できるフォントが増えた
- 代替描画機能を実装
順に説明していきます。
プロポーショナルフォントへの対応
世の中には二種類のフォントがある。等幅フォントとプロポーショナルフォントだ。
とまあ大げさに言うほどではないのですが、皆さんよくご存じと思います。
等幅フォントはプログラミングの際のテキストエディターなどでもよく使われていて、すべてのフォントの幅が同じものです。それに対してプロポーショナルフォントは、字によって幅が異なるフォントです。状況に応じて使い分けられるものですが、まあ、実装する側としては等幅フォントのほうが扱いやすいのは言うまでもありません。
プロポーショナルフォントには何種類か実装方法があるようですが、最も近代的な方法は、フォントファイルに含まれているVertical Proportional Alternateと呼ばれる情報をもとに文字の位置や幅を調整するものです。Windowsパソコンだと游ゴシックや游明朝などが対応しているようです。
このオプションを実装しました。
↓等幅フォント
オプションで有効/無効を切り替えられるので、好みに合わせて使えば良いでしょう。
ちなみに、Vertical Proportional Alternateが含まれないフォントは、このオプションを有効にしてもプロポーショナルフォントにならないだけです。対応しているフォントはそんなに多くはなさそうでした。
使用できるフォントの増加
縦書きを実現するには、かっこや句読点など横書きと縦書きで異なる字体を取るものを置き換えて表示しなければなりません。
そもそも文字コードから字体を得るには、グリフインデックスと呼ばれるIDに変換したうえで、そのグリフインデックスをもとにフォントファイル内の描画情報を取得せねばなりません。縦書きの字体を得るためには、グリフインデックスを縦書きのグリフインデックスに読み替えたうえで縦書きの字体を得る必要があります。
この縦書きに変換するテーブルは、フォントファイル内のGSUBテーブル(Glyph Substitution Table)と呼ばれるものの中に含まれていて、縦書き以外にも様々な変換がこのテーブルに含まれています。例えばアラビア語は複数の文字がつながって一体になって描画されるため、複数のグリフインデックスを別の一つのグリフインデックスに変換するテーブルなどもあるそうです。そのため、テーブルの実装が8種類くらいあって、すべての機能を使用するならばすべての実装をせねばなりません。
ただ、縦書き変換に使用するのはGSUBの中でも特にSingle Substitutionと呼ばれる種類のテーブルだけですので、もともと使っていたTypeLoaderはこれを含む限定的な種類のテーブルにのみ対応していました。
しかし、実際にはExtension Substitutionと呼ばれるテーブルにも縦書きが格納されることがあるようです。と言うよりも入れ子になっていて、Extension Substitutionの中にSingle Substitutionテーブルが入っているという構造になっています。TypeLoaderはこのExtension Substitutionに対応しておらず、例えばこれを使用するYu Gothic UIなどでは縦書きを表示することができませんでした。
TategakiではこのExtension Substitutionにも対応させましたので、Yu Gothic UIを含むあらゆる縦書き対応フォントで描画することができるようになりました。
代替描画機能の実装
さて、気付いている人もいたかもしれませんが、実は、MS P明朝やMS PゴシックでTategakiTextを使用すると、若干表示が乱れます。
よく見ると、例えば1行目の「太刀の鞘(さや)」の部分を見るだけでも「あっ…」となりますね。
「の」と「鞘」も少しかぶっていますし、「(」は完全に「さ」とかぶっています。
これの原因は正直よくわからないのですが、DrawGlyphRunメソッドを使わずに、グリフをジオメトリに変換してDrawGeometryメソッドで描画することで回避することができるようです。
この機能をなんと名付けようか少し悩んだのですが、結局は「代替描画機能」としました。プロパティ名としてはEnableAlternateRenderingで、これを有効にするとGlyphRunを使わずにジオメトリで描画します。見ての通り、見違えるほどきれいに描画できています。
ただ、描画処理は少し重くて、このサンプルアプリでウィンドウをリサイズしたりフォントサイズなどのスライダーを動かすとカクツキを感じます。あくまでもMS P明朝やMS Pゴシックをきれいに描画するための限定的なものと考えておきたいです。
ちなみにですが、代替描画ではなくても、フォントサイズを大きくすれば MS P明朝やMS Pゴシックでもきれいに描画されるようです。その境目で、フォントがビットマップからベクターに変化したように見えるので、もしかしたらそのあたりの不具合なのかもしれませんね。
フォントファイルの構造のお勉強
さて、最初にも述べた通り、今回のバージョンからフォントファイルを読み込むのに自前のコードを使用しています。
現代のWindowsパソコンなどで使われるフォントはOpenTypeと呼ばれるフォーマットになっていて、 これを読み取る必要があります。このフォーマットを勉強する必要があるのですが、結局は以下の2つのサイトが中心となりました。
- https://learn.microsoft.com/en-us/typography/opentype/spec/
- https://aznote.jakou.com/prog/opentype/index.html
前者はOpenTypeフォーマットの開発者の一人であるMicrosoftの公式ドキュメントで、網羅的に仕様が書いてあります。後者は、その中から特に日本語フォントで必要な内容をピックアップして日本語で解説されているサイトです。どちらも有用で大変お世話になりました。
もう一つ、GlyphLoaderです。
おそらくTypeLoaderと同じ作者が作ったライブラリで、TypeLoaderの後継と思われます。このソースコードが大変参考になりました。
ところで、このようなバイナリーデータを読み込むにはうってつけの機能がSpan<T>で、ファイル内のデータをいったんすべてメモリに読み込みさえすれば、その先は部分部分を切り出して、わかりやすく、かつ高速にデータを切り出せます。さらに、BinaryPrimitivesというクラスがあり、ReadOnlySpan<byte>から任意のサイズ/エンディアンのデータを取り出すことができるので、ushortやintなどへの変換も簡単です。あとはIndex / Rangeさえ使えれば言うことは無かったのですが…この機能は.NET Frameworkでは使えないようですね…。Tategakiを.NET Frameworkで使ってくださっている方もいるようなので、ひたすらSliceしまくりました。
今度こそこんなものですかね。だいたいやりたいことはやり切った気がします。
ここのところ本業から帰宅した後、夜の時間をひたすらこのソフトの開発やフォントファイルのお勉強に使っていたので、寝不足気味なうえ疲れもあまり抜けていませんでした。
もう週末もほぼほぼ終わりになってしまいましたが、アニメでも消化しながらゆっくりするとしますか。