機能価格ドキュメントブログ
moteを入手
← ブログ
開発·2026年7月2日·読了まで6分

文字を自前で描画せず、プラットフォームに任せた理由

ハングルは問題ないのに、漢字だけどこか見慣れない形に見えたことはありませんか。文字を自分たちで描かず、プラットフォームのテキストエンジンに任せることにすると、私たちの仕事は「何を描くか」ではなく「どのフォントを指定するか」になります。

m
mote
同じ文字列をNoto Sans CJKのKRサブセットとJPサブセットで描画した比較。ハングルは同じですが、뼈 골・곧을 직のような漢字の字形が異なります。
同じ文字列をNoto Sans CJKのKRサブセットとJPサブセットで描画した比較。ハングルは同じですが、뼈 골・곧을 직のような漢字の字形が異なります。

テキストが画面に表示されるまでには、多くの処理が必要です。文字を形に変え、行を分け、ピクセルとして描かなければなりません。ハングルの組み立てから漢字、絵文字、アラビア文字、フォントのフォールバックまで処理する必要があります。

私たちはこの過程を新たに作りませんでした。LinuxのPangoとHarfBuzz、WindowsのDirectWrite、macOSのTextKit 2に任せました。

オペレーティングシステムが長い間磨き上げてきた領域だからです。私たちが独自に作れば、CJK文字やアクセシビリティのような難しい部分で、かえって品質が低下する可能性が高いと考えました。製品の差別化のポイントも、レンダラーそのものではなく、タイポグラフィとプラットフォームらしい使い心地にあると考えました。

その代わり、いつ作るかは私たちが決めました

テキストレイアウトの作成には時間とメモリが必要です。大きな文書には数万もの行があるため、すべてを作ることはできませんでした。

そこで、画面に見えている行とその周辺だけを作ります。画面から遠ざかった行はすぐに破棄します。1MBの文書を長時間スクロールしても、保持されているレイアウトは40個から60個ほどです。

まだ作っていない行については、高さをあらかじめ推定します。スクロールバーが文書全体の長さを把握する必要があるからです。

もちろん、推定が外れることもあります。新しく計算した高さが予想と異なると、画面がずれることがあります。このとき、差分だけスクロール位置を同じフレーム内で補正します。ユーザーの目には、何も起きていないように見えなければなりません。

ハングルを組み立てている行は、装飾なしで描画します。入力中に太字や非表示といった属性が入り込むと、入力メソッドが認識している文字数と画面上の文字数が異なる可能性があるからです。

最初に直面した問題は漢字の顔でした

プラットフォームに任せると、プラットフォームのデフォルト値もついてきます。

UbuntuのデフォルトUIフォントにはハングルがありません。そのため、システムが代わりに選んだフォントはNoto Sans CJK JPでした。ハングルはきれいに表示されましたが、一部の漢字が日本式の字形で表示されました。同じUnicodeでも、韓国と日本で形が異なる文字があるためです。

解決方法は簡単でした。UIフォントのリストでNoto Sans CJK KRを先頭に置きました。

しかし、もっと重要なことは別にありました。私たちが基準を定めなければ、プラットフォームはユーザーの言語ではなく、自身のデフォルト値で答えるという点です。

フォントの順序で言語ごとの形を作ります

フォントスタックは先頭から文字を探します。最初のフォントにない文字だけが、次のフォントへ進みます。

この性質を利用すれば、欧文はセリフ体、ハングルはサンセリフ体で表示できます。設定一行で、言語ごとに適したフォントを組み合わせるのです。

読み取り用フォントを空にすると、本文フォントをそのまま使います。別に指定すると、読み取りモードでのみ変わります。ソースモードではコード用フォントを使います。

文字サイズは、オペレーティングシステムのアクセシビリティ設定に従います。システムで文字を大きくしたユーザーが、アプリごとに設定し直す必要はありません。

設定カードの外観タブ — フォント欄にはカンマで区切られたスタックがそのまま入力されており、読み取り用フォントは空欄のため「本文と同じ」と薄く表示されています。
設定カードの外観タブ — フォント欄にはカンマで区切られたスタックがそのまま入力されており、読み取り用フォントは空欄のため「本文と同じ」と薄く表示されています。

スクリーンリーダーも同じ基盤を活用します。プラットフォームのテキスト階層にはアクセシビリティ機能がすでに接続されているため、私たちは要求された行の内容を正確に渡せばよいのです。

翻訳が抜けるとビルドが失敗します

UIの文は英語で作成し、その文を翻訳表のキーとして使います。翻訳がなければ、画面には英語がそのまま表示されます。

問題は、エラーが発生しないことです。翻訳が抜けていても、何事もなく英語が混ざって表示されます。

そこで、検査スクリプトを作りました。コードで実際に使われている文を集め、12言語の翻訳表と比較します。一つの言語だけに文を追加したり、翻訳を入れ忘れたりすると、ビルドが失敗します。

翻訳表ごとのキーは304個、コードで実際に使われている文は282個です。対応言語はEnglish、한국어、日本語、简体中文、繁體中文、Deutsch、Français、Español、Português (Brasil)、Русский、Italiano、Polskiです。

設定の一般タブ — 一番上の行が言語のドロップダウンで、韓国語が選ばれているため、下の項目名もすべて韓国語です。
設定の一般タブ — 一番上の行が言語のドロップダウンで、韓国語が選ばれているため、下の項目名もすべて韓国語です。

言語を変更すると、同じ文書を新しいウィンドウで開き、既存のウィンドウを閉じます。すでに表示されている数百個のウィジェットを一つずつ翻訳すると、抜けが生じる可能性が高くなります。ほとんど使われない複雑な経路を維持するよりも、言語を変更した瞬間にウィンドウを一度開き直すほうが安全だと判断しました。

プラットフォームに任せると、結果もプラットフォームに似ます

同じ文書でも、オペレーティングシステムによってピクセルが完全に同じになるわけではありません。文字を滑らかにする方法とデフォルトフォントが異なるためです。

私たちは無理に同じにしません。その代わり、文字サイズの比率、行の高さ、余白など、文書の基準を同じに保ちます。

インストールされているフォントによって、フォールバックの結果も変わることがあります。そのため、特定の結果を約束するのではなく、フォントを要求する順序を明確に示します。ユーザーが直接変更することもできます。

自分たちで描かないからといって、責任までなくなるわけではありません。何を任せ、どの順序で要求するかを、より正確に決める必要があります。

← 前の記事1つのRustコアにプラットフォーム別のシェルを載せた理由次の記事 →文書が長くなっても入力が遅くならない理由
RSSで更新を追えます。
書くことだけを残す
日本語
© 2026 mote