Imposia が生まれた理由
ブラウザー出版が繰り返しぶつかる問題と、それに対して Imposia が選んだ設計上の賭けを説明します。
Imposia の出発点は、再現しやすく直しにくい失敗です。コンテンツを編集して印刷するアプリを作ると、「同じはず」の文書が 3 つできあがります。
同じはずの文書が 3 つに分かれる
典型的なブラウザー出版機能は、次の順序で育ちます。
まずエディターがコンテンツを描画し、人が編集できるようにします。次に印刷結果のプレビューが求められ、プレビューが追加されます。多くの場合、別のコンテナーと別の CSS へコンテンツを複製する形です。最後に印刷を実際に動かす必要が出て、印刷経路が現れます。これはコンテンツをもう一度組み立て直すもので、サーバーやヘッドレスブラウザーで動くこともあります。
こうして 3 つの面がそれぞれ自分のツリーを計測するようになります。一致を強制するものは何もなく、ずれていきます。
- プレビューは 12 ページと言い、PDF は 13 ページある。
- 表ヘッダーが画面では正しく繰り返され、紙では繰り返されない。
- 「7 ページを参照」が、ある成果物では 7 ページを、別の成果物では 8 ページを指す。
- あるユーザーでは再現し、別のユーザーでは再現しないバグが生まれる。ずれ方がフォント、タイミング、どの面が先に描画されたかに依存するからです。
これらはレイアウトのバグではありません。レイアウトの決定者が 3 つあり、どれを正とするかの規則がないことの帰結です。
よくある答えと、それぞれの代償
この分野には良いツールがあります。その多くは、1 つの決定者を選び、その代わりに何かを手放すことで衝突を解決します。
| 方式 | すること | 手放すもの |
|---|---|---|
| サーバーレンダラー(ヘッドレスブラウザー、または専用エンジン) | 唯一のレンダラーをクライアントの外に置く | プレビューは別の場所で作られた結果の写しになるため、ユーザーが編集している内容と食い違う余地が残る |
| 独自コンポーネントを持つ別の文書レンダラー | 自分で制御するレイアウトツリーから決定的な出力を得る | 既存の HTML と CSS が使えず、コンテンツを二重に作ることになる |
| 画面のラスタライズ | 画面にあるものがそのまま成果物になる | 本当のページ分割ではなく、テキストがテキストでなくなる |
いずれも合理的なトレードオフで、多くの製品にとって正しい選択です。サーバーで生成した PDF バイトが必要なら、Imposia よりヘッドレスブラウザーの方が適しています。本ドキュメントでもそう案内します。
Imposia の賭け
Imposia は別のトレードオフを選びます。ブラウザーが所有する 1 つの文書を、すべての面が参照するという賭けです。
その帰結として、Imposia は組版エンジンを持ちません。組版はおおよそ 4 つの層に分かれます。
- グリフ整形 — カーニング、リガチャ、文字体系ごとの結合
- 行分割 — 行の終わり、ハイフネーション、CJK の規則
- ブロックレイアウト — マージン、フロート、flex、grid、テーブル
- ページ分割 — 紙面が尽きたとき、内容をどこで切るか
1〜3 層は、ブラウザーが数十年をかけて磨いてきた、際立って得意な領域です。4 層目だけは、紙があるときにしか存在しないため、ブラウザーはほとんど実装していません。
そこで Imposia は 4 層目を実装し、残りをブラウザーに委ねます。コンテンツを実際のブラウザーフレームで描画し、ブラウザーが実際に作り出した結果を計測し、その計測値からページを切り出します。
この方式で得られるもの
日本語や韓国語の行分割規則、アラビア文字の整形、絵文字シーケンス、可変フォント、そしてブラウザーが対応するすべての CSS レイアウト機能がそのまま動きます。ここで実装したからではなく、ブラウザーから取り上げなかったからです。
この方式の代償
この選択には実際の欠点があります。それを伏せれば、この先のドキュメント全体の信頼が下がります。
- 計測は無料ではありません。 切る位置を決めるには、内容を追加し、ブラウザーにジオメトリを問い合わせ、ときには取り消す必要があります。閉じたモデルでレイアウトを計算するより遅くなります。
- エンジンはブラウザーのものです。 行分割は Chromium、Firefox、WebKit で異なるため、ページ数も異なることがあります。構造の基準は Chromium です。他のエンジンでは API、隔離、ライフサイクル、書き出しの動作を検証しており、同一の出力は検証対象ではありません。
- CSS フラグメンテーションは完全には解けていません。 テーブル、flex、grid、段組みは文書化されたサブセットとして対応します。サブセット外の内容は黙って近似される代わりに、分割されずにそのまま残り、型付きの警告を発します。
Imposia がやらないと決めていること
この境界は製品の欠落ではなく、製品の一部です。
- PDF バイトを返しません。
print()は完成したページをブラウザー自身の印刷ダイアログに渡し、読者はそこでプリンターまたはPDF に保存を選びます。PDF レンダラーを追加すれば、他の 3 つと食い違い得る 4 つ目の文書が生まれます。それは、このライブラリが取り除くために存在する問題そのものです。 - Node や CLI のレンダラー、サーバー書き出しを持ちません。 実行環境の境界はブラウザーです。
- エンジン間で同一のページ数を約束しません。 ブラウザーが 1〜3 層を所有する限り守れない約束であり、守れない約束はしません。
Imposia は適した道具か
向いている場合
人がコンテンツを見て編集する React アプリがあり、印刷または PDF の結果が、いま見ているものと一致しなければならない。
向いていない場合
ブラウザーで誰もプレビューしないコンテンツから、サーバー上で PDF バイトを生成する必要がある。
前者が当てはまるなら、次のステップは最初のページを作るです。単一文書の規則が実行時にどう強制されるかを知りたい場合は、出版モデルを読んでください。