2024年5月11日土曜日

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

Tategakiをアップデートしました。

Github:

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

今回の変更点

今回もマイナーチェンジです。

  • フォント読み込み時のパフォーマンス改善
  • 必要最低限のフォントファイルのみを読み込むように修正

説明していきます。

フォント読み込み時のパフォーマンス改善

前回のソフト更新で、縦書きフォントの検索時のパフォーマンス改善というものがありました。 GSUBテーブルのみを読み込むようにすることでパフォーマンスを飛躍的に向上させたというものでした。しかし、この検索のためにファイルの読み込みルーチンに2種類の実装ができてしまいました。イケてないです。プログラマーというのはコピペコードを嫌がるものです。ですので、実装を統一する作業を実施しました。

これにより、検索時以外にフォントファイルの中身を読み込む際のパフォーマンスも向上しました。1~2割程度の改善だと思います。

古い実装ではいったんすべてFile.ReadAllBytesメソッドでメモリに読み込んだうえで解析していましたが、フォントファイル内の多くのテーブルを読み込む場合でもStreamでファイルの必要な部分のみを都度読み込むほうが速いみたいです。まあ、ファイルのランダムアクセスみたいになってしまいますので、もしかしたらHDDとかなら重くなるのかもしれませんが、最近システムドライブがHDDのパソコンもそうそう無いでしょうからこれで良いでしょう。

必要最低限のフォントファイルのみを読み込むように修正

今回実施したかった改善はまさにこれです。

前回のソフト更新で「特定のフォントファイルを読み込むと落ちる不具合の修正」というものがありました。行儀の悪いフォントファイルがあると落ちるというものです。この時に思ったのですが、潜在的なリスクとして、ほかにも想定外のフォントファイルにより例外が発生するということは充分にあり得ます。私のパソコンでは落ちないけど誰かほかの人のパソコンに入っている特定のフォントなら落ちる…そんなのは検証しきれませんね。

前バージョンまででは、アプリの立ち上げ時に(=staticクラスのコンストラクタで)全フォントを読み込むようにしていました。そのため、行儀が悪いフォントファイルを持っている人の環境では、このライブラリを参照する限りアプリが一切立ち上げられなくなってしまいます。たとえそのフォントを使わなかったとしてもです。それはあまりにもよろしくない挙動で、初めてこのライブラリを使ってくださった方の印象を悪くさせかねません。

そこで、全フォント読み込みをできる限り行わないように修正しました。

具体的には、TategakiText.FontFamilyプロパティに指定されたフォントが縦書きを持っているフォントの場合、全探索は行いません。そのフォントのみを読み込みます。全フォントを読み込むのは以下の2パターンのみです。

  • TategakiText.FontFamilyプロパティに設定されたフォントが存在しない/縦書きに対応しないフォントだった場合
  • TategakiText.AvailableFontsプロパティを読みに行ったとき

後者は単純です。すべての縦書き対応フォントを参照してきたのですから全フォントを読み込むことになります。

前者については、フォールバック処理が入っているためです。使えないフォントだった場合、類似の使えるフォントを探すことになるのですが、そのために全フォントを読み込んだうえで類似フォントを探すような処理にしております。

いずれにせよ、「とりあえずTategakiTextを使ってみよう」レベルでは上記2パターンに抵触することは無いので、仮に行儀の悪いフォントファイルがあったとしてもアプリが立ち上がらない状況までは回避できるようになります。

余談:Tategakiのダウンロード数

NugetでTategakiのダウンロード数が見られるのですが、約1週間ごとにリリースしてきたver.3.0.0以降の各バージョンがだいたい75~85ダウンロードされているみたいです。

まあ、有名ライブラリなどに比べれば全然なのですが、80も使ってくれている人がいるんだ、というのが正直な感想です(ありがとうございます)。使ってくださった方からのフィードバックみたいなのはなかなか無いので、こちらとしてはあまり実感が無いというのが正直なところなのですがね。

引き続きよろしくお願いいたします。何かご意見・ご要望等あればいつでもコメントください。

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を開発するうえでプログラミングの枠を超えて面白いところですね。