🎉🎉🎉
SuzuriというRustテキストレンダリングクレートを作りました!
バグ直しもろもろあって現在v0.2.1です
みんなつかってね!
自作動画編集ソフトのための自作GUIライブラリのための自作テキストレンダリングライブラリを作りました!
Rustテキスト界隈でよく使われているfontdbと、世界最速らしいfontdueの上に乗っかって作っています
テキストレイアウトロジックとキャッシュ、レンダリングロジックを実装しました
雑に使えるように、全ての機能を FontSystem にまとめて FontSystem のメソッドとしてテキストレイアウトからレンダリングまでできるようにしています
FontSystem は Send + Sync かつすべてのメソッドが &self なので雑にスレッド間共有して色んなスレッドでレンダリングできます
できること!
必要十分な機能だけあります
- フォント管理できます
fontdbのフォント管理法をそのまま受け継いでいます
システムフォント拾えます
ファイルを読ませてフォント増やせます
fontdb形式のフォントクエリを投げたらfontdueのフォント構造体を返します - テキストレイアウトができます
文章をFontSystemに投げるとグリフレイアウトして返します - ビットマップ/テクスチャにレンダリングできます
CPU/GPU両方のレンダリングに対応してます
GPUレンダリングはグラフィックライブラリ非依存に作ってます
OpenGLでも、Vulkanでも、 DirectX3DでもWebGPUでも使えます
ただしGPU操作は自分で実装してね!
キャッシュの管理と描画順、ドローコール順の管理はしてるから! - GPU操作のうちwgpuに対する基本的な実装を用意してます
機能感、レンダリング感はこんな感じ↓
1つのTextData構造体から1発で描画してます

なんで今更テキストレンダリングやってるの?
最初はモーショングラフィックやりたいだけでした
- モーショングラフィックやりたい
- 納得いく動画編集ソフトがなかった
- 動画編集ソフトを作ろう
- 納得いくGUIライブラリがなかった
- GUIライブラリを作ろう
- 納得いくテキストレンダリングライブラリがなかった
- テキストレンダリングライブラリを作ろう
- テキストレンダリングライブラリ作った!← いまここ
既存のテキストレンダリングライブラリの問題点
動画編集ソフトのためのGUIライブラリを自作していることは上記のとおりです
その中で、レイアウトロジックとして2パスレイアウトというものを採用しています

Column、Rowなどのコンテナの実装やテキスト配置を柔軟に実装するためのほぼ必須のロジックです
いろんなGUIライブラリで広く使われています
この、2パスレイアウト中に既存のテキストレンダリングライブラリを利用したときに問題が発生しました
テキストレイアウトに(広義の)再現性が無い
狭義の再現性はもちろんあります。同じテキストに対して同じレイアウト空間を割り当てたらもちろん同じグリフレイアウトを返してくれます
しかし、
- ある幅を渡して、あるテキストレイアウト LayoutA が返ってきたとします
LayoutA の最大幅を調べたところ、wだったとします - 2パスレイアウトの後半で、前半と同じテキストに対してw幅を指定した上でレイアウトさせます
普通に考えればレイアウトに十分な幅が用意されているので、前半と同じテキストレイアウトが返ってくるはずです
しかし既存のテキストレンダリングライブラリでは、より早い段階で改行が入ってしまう、LayoutAとは異なったテキストレイアウトが出力されてしまうのでした
(浮動小数点数の誤差も疑いましたが0.5px程度マージンしても変わりませんでした。僕のユースケースで浮動小数点誤差がここまで大きくなるのはあり得ないのでレイアウトロジックの根本的な問題です。)
つまり、レイアウトされたグリフの右端からレイアウト境界まで十分な余裕があるにも関わらず、レイアウト境界を縮小させて再レイアウトさせると異なる結果を出力してしまうということです
既存テキストレンダリングライブラリ
- cosmic-text
上記の問題 - glyphon
レイアウトにcosmic-textを利用している - fontdue内蔵レイアウト
上記の問題 - その他ラスタライザ
fontdueと比べて遅い
動画編集ソフトのためのテキストレンダリングライブラリということでなるべくGUI側にCPUを使わせたくない
そういうわけで、テキストレンダリングライブラリを作りました
実装!
以下、(チャットアプリを含む)Geminiはすべてgemini-3.0-pro、Claudeはすべてclaude-opus-4.5です
それと、VSCodeではなくAntigravityを使っています
スーパーGemini駆動進捗
1. FontStorage
雑にfontdbとfontdueを一緒にしました
特に深いことはやってません
fontdbのようなクエリで、直接fontdue::Fontを取り出せるようにしました
2. TextData / TextLayout
文章の論理的表現を雑に作りました
グリフレイアウトロジックを自然言語で雑に用意
関数の入力と出力、ループ構造だけスケルトンを適当に書いたのち、丸ごと全部Geminiに投げました
この頃からGeminiにタスクを投げるようになっていきます
すると、僕が自然言語で書いたロジック仕様からそれなりに逸脱した(?)(少なくともぼくの想定したコードではない)コードが出てきました
読めない
まったく意図がわからない
めんどくさかったので5分ぐらいだけ解読しようとしてやめました
まあでもちゃんと動いていればヨシ!!!!!ということで動作を確認します
とりあえず「パフォーマンスを完全に無視し、可能の限り正しく丁寧なコードを書くように、デバッグ用レンダラを作ってください」みたいな感じで頼みます
何度かバグ直し対話を繰り返して、最後に出力のpngを見てみるとなんかちゃんと動いてそうです ヨシ!!!
入出力のインターフェースを揃えてるので後で直せますし、めんどくさいので次に進みます
3. Renderer
I. CPUレンダラ
レンダリングを行う部分は自&明なのでキャッシュ部分を頑張ります
連続メモリ空間を先に用意して、ブロック分けしてLRU管理します
アプリのGeminiと相談しながらLRUキャッシュクレートの選定をしたんですが、LRUの内部状態をみる必要があったので自作します
Geminiに連結リストの高速な実装を聞きます
雑に実装します 実装をGeminiにレビューしてもらいます それだけだと心配なのでテストをたくさん書いてもらいます
テストが大丈夫そうだったのでLRUエントリにキャッシュエントリの管理を紐づけていい感じに完成させます
最後にGeminiにパフォーマンスを詰めてもらいます
II. GPUレンダラ
キャッシュの実装を流用します
テクスチャアトラスの左上から右下までインデックス振るだけなので適当に移植します
ここでGPU特有の事情として、ドローコールの発行はとても時間がかかるのでドローコールはなるべくまとめておかないといけません
なので、一回のドローコールでなるべく多くの文字を描けるようにテクスチャアトラス全体を利用したいです
すると、ドローコールが来るまでに、次のドローコールで描画されるべきキャッシュを上書きされないように保護しないといけません
ここにLRUを自作する必要があったんです
最後に、ドローコールで描画されるべきインスタンスをバッファにプッシュしてしていって、これ以上一回の描画に詰められないなっていうタイミングでドローコールする部分を雑に書いて完成です
最後にGeminiにパフォーマンスを詰めてもらいます
毎ループ生成するVecを、ループ外で作成して毎ループclearする実装に修正されたりしました
たしかにメモリアロケーションコストを節約できそう
III. wgpuレンダラ
ここはほとんど自明な実装しかないし、めんどくさくなったので丸投げしました
90%以上はGeminiが書いてるはずです
ここまでの作業で、以外とLLMに丸投げできるぞって気づいてしまったのも理由です
実装の可読性や関数化、DRYなどレビューして直してもらうなどを何回か繰り返して満足するコードになったので完了
途中テクスチャへのデータアップロードをずっとEncoderではなくQueue経由でやっていて、そのため描画とのテクスチャキャッシュの同期が崩れていた問題に僕が気づいて指摘して直してもらったのが唯一1番引っかかったところでした
スーパーGemini駆動ドキュメント
リリースする前に、Docs.rsに登録するドキュメントを書かないといけません
やりたくないです
やりたくないのでGeminiに全部投げます
確認するのに英語読むのも面倒です
面倒なのでClaudeに全部チェックしてもらいます
Claudeにチェックと修正(あれば)してもらったら、まだ心配なのでもう一度Geminiに最終チェックしてもらいます
良さそうって言ってたので次に進みます
readmeだけ重点的に確認しながら思い通りになるように書かせました
readmeはたぶん両手で数え切れないぐらいには改稿指示したはずです
スーパーGemini駆動命名
名前決めないといけません
自分で頑張って考えました
頑張って考えてCrates.ioを見に行くと全部先客が居ます オヨ~(;o; )
語彙力が尽きたのでGeminiに量優先でたくさん出してもらいました
「Suzuri」テキストレンダリングライブラリに相応しい、渋くてお洒落な名前にしました!
バイブコーディング総括
バイブコーディングの現実
現状のAIに、質の良いコードを書く能力はありませんでした
ポン出し、丸投げで指示して作業を任せると素晴らしいスパゲッティを茹でてくれます(注: ぼくはCodexも使っています その上での感想です)
ただし、人間が適切に舵取りしてあげると爆速でプログラミングが進むアクセラレーターになってくれます
特に、関数やメソッドの入出力を決める、スケルトンを書く、関数化の指示、DRYの指示など、メンテナンス性や可読性に関わる部分をちゃんとアシストしてあげると中身の細々としたコードは意外とちゃんと書いてくれます
つまりAIの書いたMessyなコードをAPIの内側、ロジックの内側に閉じ込めて、アプリ全体に悪影響を与えないようにしてあげれば良いのです
人間に求められる作業
設計原則に気をつけましょう KISSとか、DRYとか
AIが書いたコードの「(良くない)匂い」に気をつけましょう
なんか嫌だなーって思ったらたぶんそれは良くないコードです
パッと見て理解できなかったらそれは高確率で可読性とメンテナンス性に乏しい良くないコードです
修正を依頼するかプロンプトの指示を増やしてやり直しましょう
AIが書いたコードを読むのと、半年/1年前に自分が書いたコードを読むのはほとんど一緒です
読みにくさを感じたらすぐに修正を依頼すべきです
ただ、決めたインターフェースに合っていて、テストしても正常に動いてそうならリファクタを後回しにして先に進んでもいいかも、、、? 進捗状況・時間と相談です
こういう後回しを柔軟にできるようにするためにもアーキテクチャ・APIを先に固めておくのが大事です
Suzuri | 今後の予定
- 合字対応したいです
この過程で、レイアウト基礎バックエンドをrustybuzz化するのと、レイアウト予測性と再現性のためのカスタムロジックを別途自前で用意するというハイブリッド構成にしたいです。(現状のレイアウトエンジンはかなり簡易的で、ユニコードの多様な制御コードに対応できていない、、、) - テキストエディット対応したいです
- レイアウトロジックのリファクタをします
上記のとおり、今のレイアウトエンジンはかなり簡易的な実装でして、これを書き直してユニコード標準に合わせていきたいのです
改行禁則とか、書記素・単語分割とか、、、
あとGemini全投げした影響で少し実装が冗長・メモリの使い方もあんまりよろしくない、、、(入れ子Vecの多用、ヒープ上のメモリコピーを伴う操作の多用など) - キャッシュを並列化したいです。現状はレンダラ丸ごとMutexに包んでいるだけなのでマルチスレッド呼び出しは出来ても並列実行はできません。これどうにかしたい
- fontdbでのフォントロードをもうちょっと高機能にしたいです
- レンダラをもっと使いやすく
現状かなりの量の作業をクロージャにして呼び出し側に実装を強いているので、もっとラッパー・ヘルパーメソッドを書いて、自由度を保ちながら呼び出し側の負担を減らしたい
以上数点直したら、完成と見て以降基本的には大きく手を加えずに枯れさせていこうかなって思ってます
