2024年5月3日金曜日

WPF用縦書きテキストブロック Tategaki ver.3.2.1

前回の記事で「今度こそこんなものですかね」と言ったにもかかわらずまたアップデートしました。まあ前回よりインターバル長いしセーフ。

Github:

Nuget:
https://www.nuget.org/packages/Tategaki/

今回の変更点

今回は下一桁のバージョンアップで、マイナーチェンジです。新たな機能の追加は無しです。Readmeにも書いていますが以下の点をアップデートしました。

  • 縦書きフォントの検索パフォーマンスを改善
  • 下線/中線/上線の配置を改善
  • 特定のフォントファイルを読み込むと落ちる不具合を修正

一つひとつ説明していきます。

縦書きフォントの検索パフォーマンスを改善

TategakiText.AvailableFontsプロパティを呼び出すとき、全フォントを読み込んで、縦書きに対応しているか、すなわちGSUBテーブルのvertタグを持つデータがあるか確認します。しかし、ここに意外と処理に時間がかかります。特に久々にノートパソコンを出してきてこのプロパティにアクセスしてみたところ、制御が返ってくるまでに大体4秒くらいかかることがわかりました。これじゃあさすがに遅すぎて困りますね。

いろいろと原因を調査してみたところ、改善の余地として以下の3点が見つかりました。

  • GSUBテーブル以外のテーブルのテーブルも解析している
  • フォントファイルのバイナリを最初にいったんすべてメモリに読み込んでいる
  • 並列処理をしていない

一つ目は、GSUBテーブル以外のテーブルも解析しているという点です。実際に縦書き描画をする上ではそれ以外の情報も多数必要になるので最終的には読み込むことになるのですが、縦書きに対応しているフォントかどうかを調べるだけなら全体を解析する必要はありません。というわけで、縦書きに対応しているかどうかを調べるだけの専用パーサーを別途実装しました。

二つ目は、フォントファイルはいったんすべてFile.ReadAllBytesメソッドでメモリに読み込んだうえで処理をかけている点です。この読み込むだけの操作が意外と重いようで、処理時間の半分くらいを占めていたようです。読み込んだデータの大部分を解析するならメモリに置いても損は無いとは思いますが、GSUBテーブルがvertタグを持っているかどうかを調べるだけなら全体をわざわざ読み込む必要はありません。というわけで、一つ目の点と併せて専用パーサーでは必要な部分だけをFileStreamでSeekしながら読み込むようにしました。

三つ目は単純です。それぞれのフォントファイルの読み込みを並列に処理するようにしました。環境にもよりますが、これで2割くらい処理速度の向上がされるようです。

これら3つの改善をすることで、読み込みが0.8秒くらいまで縮まりました。5倍くらいの改善ですね。実用的なレベルまで圧縮することができました。

実は、まだ現状はGSUBテーブルすべてを解析しているのですが、ここをFeatureTagの解析だけにするともう少し改善が見込めるかもしれません。ただ、めんどくさいので似たような実装を増やすのもあまりよくないので、それはまた必要になったときにでも。

下線/中線/上線の配置を改善

実は今までのTategakiは下線/中線/上線の場所があまりイケていませんでした。


こちらが今回の更新前(ver.3.2.0)で下線を引いたときの画像です。下線は引けているのですが、左隣の行のほうが近いですね。イケてないです。

この下線の位置はGlyphTypeface.UnderlinePositionプロパティからもらっていたのですが、まあ、標準ライブラリにあることからもわかる通りこれは横書き用の位置なんですね。それをそのまま縦書きで使っちゃそりゃイケてませんわ。

ということで、ちゃんとフォントデータのBASEテーブルを読み込むようにしました。ここでは縦書き用のベースラインなどの寸法情報が格納されていて、それをもとに下線/中線/上線などを描画することで、イケている位置に線を引けるようになります。

良い感じですね。離れすぎず、重なるわけでもなく、良い位置に線を引けるようになりました。

ちなみに、TategakiTextでは「Underline(下線)」と言うと文字列の左側、「Overline(上線)」と言うと右側に描画するのですが、これは縦書きは横書きを90度時計回りに回して描画しているという考えに基づくものです。ですが、Microsoft Wordでは縦書きで下線を設定すると右側に線が引かれるのですね。あまり深く意識したことが無かったですが、昔、国語のテストとかで線が引かれていたのも右側でしたっけ。まあ、TategakiTextで右側に線が欲しい人はTextDecorationsでOverlineを指定してください。

Wordで下線を指定したもの

特定のフォントファイルを読み込むと落ちる不具合を修正

行儀の悪いフォントファイルがあったときに落ちるバグみたいなのは…なかなか対処が難しいですね。これも私のノートパソコンでTategakiTextを使用したときに発覚した不具合でした。

OpenTypeフォントは、Headテーブルにて作成日時/更新日時情報を持っています(ファイルシステムのタイムスタンプとは別です)。これは1904年1月1日午前0時0分からの経過秒数を64bit型で記録されているのですが、この数値が大きすぎたとき、.NETのTimeSpan構造体に入りきらず例外を吐くようです。まあ実際にそんな日時は遠い未来ですのであり得ないのですが、お行儀が悪いフォントファイルだとこういう例外の原因になってしまうようでした。

この不具合は、境界チェックを入れるを入れることで回避するよう修正しました。

余談:WPFのContent

余談ですが、WPFにはContentという仕組みがあり、コントロールの内部に別のコントロールを入れ込むことができます。もちろんTategakiTextもWPFのコントロールですので、別のコントロールに内包することができます。

<Button>
    <tg:TategakiText Text="縦書きのボタン" />
</Button>

素晴らしいですね。WPFに縦書きコントロールがあるだけで、いとも簡単にこういったものも作れてしまう表現力がWPFのすごいところです。

今回のアップデートは、WPFでこういうことができることに気が付いて実際にコーディングしてみたところ、左右の余白の大きさが違って非常に不格好になってしまっているのに気が付いたところから始まりました。

その原因には実は心当たりがあって、Underlineを描画する際に、その座標を前述のとおりGlyphTypeface.UnderlinePositionから貰ったところ、値がマイナスにまで触りきってしまっていたので描画領域をはみ出て見切れるという事象があったのです。ですので、Underlineをの分を見越して左側に大きめの余白を用意していました。ですが、それでバランスが崩れるのはやはりイケていないです。そもそも上で述べた通り、その座標が左に寄りすぎていました。なぜだ?と調べていったところ、たどり着いたのが、その下線の座標は横書きを前提にしたものということでした。

コンピューターの文字の描画の原点は英語、すなわちアルファベットですからね。ご存じのとおり、アルファベットをきれいに書く練習を人間がするときは、4本の横線の中に字を書く練習をします。そして、下から2番目の線が基準線となっているわけです。ですので、文字と被ることがない下線を引こうと思ったら、マイナスの座標にまで入ることになるのです。

日本語ではそうではありません。日本語は、手書きの練習をするときは田の字のマスに字を書く練習をします。Underlineと言えば迷わず字の外殻に沿うように線を引けばいいだけなんですね。

そんな字体の違いから始まる文字描画の違い、それがこのTategakiを開発するうえでプログラミングの枠を超えて面白いところですね。

0 件のコメント:

コメントを投稿