オープンソースの組版エンジン · v1.16.3

ウェブで動く、プログラムできる組版システム

Postextは、意味構造のあるMarkdownに、何世紀も受け継がれてきた組版の規則を当てはめます。Knuth-Plassの両端そろえ、高さのそろった段組み、収まるべき場所を心得たフロート。そうして、HTML、Canvas、入稿できるPDFで出版品質のページを生成します。

すべてのページは、あなたのブラウザーの中で計算されます

Postext

かんたんな説明

Postextは、本や雑誌、報告書のページをレイアウトする、無料のオープンソースソフトウェアです。いくつかの簡単な記号を付けて文章を書くと、Postextがそれを、印刷された本のような完成したページにします。印刷の世界で何世紀も使われてきた規則に従うので、文章が読みやすくなります。ページはウェブサイトやアプリに載せることも、そのまま印刷できるPDFにすることもできます。書き手、出版社、デザイナー、開発者のためのソフトウェアです。

文字起こし

ナレーションの全文と、画面に映るものの説明。

  1. 0:00

    画面暗いカードに、細い金色の罫線の上で「MAINZ · 1455」の文字。見出し「5世紀をかけて、印刷はページの組み方を身につけた。」の背後で、巨大な袋文字の年号が1455から2026まで数え上がり、そのまわりに印刷用語が次々に降りてきます。両端そろえ、カーニング、ウィドウ、オーファン、段末そろえ、行送り、ベースライングリッド、hy·phen·a·tion(ハイフネーション)、行長、フロート、柱、リバー。カメラが最後の句点に飛び込むと、画面は金色に満たされます。

    ナレーション5世紀をかけて、印刷はページの組み方を身につけてきました。

  2. 0:05

    画面見出し「Webはインターフェースのレイアウトを覚えた。ページを組むことは覚えなかった。」example.com/longform/articleを開いたブラウザーのウィンドウに、3段組みで両端そろえの記事「なぜWebはページを組めないのか」が表示されています。赤いタグが、白いすき間の流れ(リバー)、ウィドウ、オーファン、本文に重なった画像、そろっていない最後の段を指します。その横にコードのチップ「column-count: 3;」、続いて「…ほかにはほとんど何もない。」と、同じ5つの欠陥を取り消し線で消したリストが並びます。ウィンドウが乱れ、崩れ落ちます。

    ナレーションWebが覚えたのはインターフェースのレイアウトで、ページではありません。だから長い文章は今も崩れます。リバー、ウィドウ、オーファン、衝突。

  3. 0:18

    画面白い閃光と、青・金・赤の縞が横切ったあと、Postextのロゴとワードマークがせり上がり、キャッチフレーズ「プログラムできるWebの組版エンジン」が添えられます。その下に「オープンソース · MITライセンス · v1.7.0 · ブラウザーで動作」。

    ナレーションPostextです。プログラムできる、Webのための組版エンジンです。

  4. 0:24

    画面全7セクションの01「コンテンツの入力」:「Markdownは、それが何であるかを書く。」Markdownファイル03-why-postext.mdが打ち出されていきます。partディレクティブ、リード文付きの見出し「Why Postext」、:refで図fig-flowを参照する段落、::resourceの行、:::calloutのブロック。隣のconfig.tsは、2段組み、両端そろえの本文、en-usのハイフネーションを指定してbuildDocumentを呼び出しています。その上に「見た目は設定が決める。レイアウトに関することは本文に一切書かない。」の一文。

    ナレーション書くのは意味を表すMarkdownで、それぞれが何であるかを示します。見た目は設定が決めます。

  5. 0:32

    画面セクション02「Pretextが測る」:「DOMに触れずに計測する。」見出し、段落、囲みが1つずつスキャンされ、寸法線がそれぞれの高さをポイントで示します。コードパネルでは、prepare(text, '25px Lora')とlayout(prepared, width, 38)が高さと行数を返します。2本のバーが1万段落を競い合います。「ブラウザーのリフロー · DOM」はほとんど進まず、「Pretext · canvasの計測値と算術」は一瞬で満ちます。カウンターが600×まで上がり、その上に「DOMより速いテキスト計測。入力するそばから本全体が組み直される」と「Powered by @chenglou/pretext」の文字。

    ナレーションどの段落もDOMに触れずに計測します。最大で600倍の速さです。それを可能にしたのが、ライブラリーのPretextです。

  6. 0:42

    画面セクション03「Postextが決める」:「1ピクセルを描く前に、すべての行の位置を決める。」7つの区分からなる輪が順に点灯します。構造、計測、配置、フロート、調整、段末そろえ、リズム。それぞれに1行の説明が付きます。その横でPostextガイドの2段組みのページが組み上がっていきます。ラベル付きのブロックが段に飛び込み、注記「:ref{id="fig-flow"} → 段の下端」の下に図が滑り込み、章扉の帯が降り、行が現れ、段の高さがそろい、ベースライングリッドが上から下へ走ります。輪は反復1から3まで回り、チェックマークとともに「3回の反復で確定」と表示されます。囲みには1行のバウンディングボックスとベースラインが示されます。

    ナレーションそしてPostextが決めます。7つの工程ですべての行を配置し、すべての図をフロートさせ、すべての段をそろえます。何も動かなくなるまで繰り返します。

  7. 0:54

    画面セクション04「仮想文書」:「1語を変えれば、変わったところだけが組み直される。」注記は「Reactの仮想DOMと同じ発想を、ページに。VDT、仮想文書ツリー。」p. 7からp. 13までの7ページが横に並びます。p. 11の編集箇所にキャレットが立ち、その段の残りがDIRTYとして金色に変わり、一部がp. 12にあふれます。p. 12にはチェックマークとSETTLED、前のページにはSKIPPED、後のページにはUNTOUCHEDの表示。3つの箇条書きが規則を述べ、renderToHtmlIndexed()と題したパネルが、文書ツリーの変更されたノードにPATCHEDの印を付けます。

    ナレーションすべては仮想文書の中にあります。Reactの仮想DOMと同じです。1語を変えれば、変わったところだけが組み直されます。

  8. 1:03

    画面「長い文書?描画するのは画面に映る部分だけ。」本のページの列が、VIEWPORTと記された金色の枠の中をスクロールしていきます。枠の上下には200ピクセルの余白があります。枠内のページは完全に描かれ、枠外のページは破線の輪郭だけです。大きなカウンターが、48ページのうちその瞬間に描画されているページ数を示し(IntersectionObserver · +200 px)、隣に「ジオメトリーは全ページ分。ピクセルは見える分だけ。」の文字。

    ナレーション長い本でも、描画するのは画面に映っているページだけです。

  9. 1:10

    画面セクション05「Knuth–Plassの両端そろえ」:「段落のあらゆる改行のしかたを、一度に。」左では、改行についての段落がブラウザーと同じ先頭詰め(ファーストフィット)で組まれ、もっともゆるい行が赤くなります。その下で、すべての単語が1本の線上の点になります。赤い弧は貪欲法の改行位置、淡い青の弧は可能な改行位置をすべて試し、金色の経路が最良の組み合わせを選びます。右のKnuth–Plass版は間隔が均等で、各行のバッドネス、式「(1 + badness + penalty)²」、より小さいデメリットの合計が示されます。

    ナレーションブラウザーは貪欲に、1行ずつ改行します。PostextはTeXのアルゴリズムであるKnuth–Plassを使います。段落全体のあらゆる改行のしかたを比べ、もっとも均等なものを残します。

  10. 1:24

    画面セクション06「1つのレイアウト、3つのレンダラー」:「見えているものが、そのまま印刷に回る。」コードの行const vdt = buildDocument(content, config);の下で、1つのページが3つに分かれます。Canvas(renderToCanvas()、ピクセル単位で正確なビットマップ)にはピクセルの格子を拡大するルーペが重なります。HTML(renderToHtml()、選択可能、リサイズに追従)では、すべてのブロックが枠で囲まれ、<h1>、<p>、<figure>などのタグが付きます。PDF(renderToPdf()、印刷用、PDF/UA、CMYK)では4色の版が滑り込んで見当が合い、トンボとカラーバーが現れます。

    ナレーション1つのレイアウトに、3つのレンダラー。canvas、HTML、そして印刷用のPDFです。見えているものが、そのまま印刷に回ります。

  11. 1:35

    画面セクション07「なぜオープンな標準なのか」:「印刷の水準を決めたのはLaTeXだった。今、出版はあらゆる画面の上にもある。」2枚のカード。1984年のLaTeXには、「印刷ページのために作られた」「ビルドが1台のマシンのフォントとパッケージに縛られる」「コンパイルして、待って、繰り返す」「レスポンシブなWebでは編集デザインのレイアウトができない」に×印。2026年のPostextには、「Web、canvas、印刷用PDFを1つのレイアウトから」「持ち運べる1つの.postextファイルにすべてを収める」「入力するそばから組み直される」「どのブラウザーでも動作 · サーバー不要 · MIT」にチェックマーク。Postextのカードが明るくなります。

    ナレーションLaTeXは印刷の標準を作りました。しかし今、出版はあらゆる画面の上にあります。

  12. 1:41

    画面キッカーは「1つのファイルに、本のすべてを」、見出しは「LaTeXの届かないところまで広がる、出版のためのオープンな標準。」.postextファイルのアイコンが5つの部分にはじけます。preset.json(設定とデザイン)、chapters/*.md(章ごとに1つのMarkdownファイル)、fonts/*.woff2(書体そのもの)、images/ · SVG(すべての画像を埋め込み)、resources(表、図、キャプション)。キャプションは「ただのZIP。オープンで、仕様が公開され、MITライセンス。どんなツールでも読み書きできる。」

    ナレーションPostextはオープンな標準です。持ち運べる1つのファイルで、仕様が公開され、MITライセンスなので、どんなツールでも読み書きできます。

  13. 1:51

    画面「ショーケース · 本物のエンジンで組んだもの」:本のページの壁が遠近法で奥へ遠ざかり、カウンターが48ページに達します。文字は「12章、3部、表紙を、約0.5秒でレイアウト。Postextガイドの全ページを、この映像のためにpostext v1.7.0がブラウザーで計算。手作業で置いたものは何もない。」続いてタグが並びます。教科書、雑誌、文芸書、カタログ。

    ナレーションだから、数百ページの本でも10分の数秒でレイアウトできるのです。

  14. 1:58

    画面エンドカード:Postextのロゴとキャッチフレーズ「プログラムできるWebの組版エンジン」、アドレスpostext.dev、「$ pnpm add postext」と打ち込まれるターミナルの行、フッター「オープンソース · MIT · github.com/drnachio/postext」。

    ナレーションPostext。オープンソースです。postext.devでどうぞ。

章 1 · なぜPostextか

書籍の組版を、 ウェブのために計算する

印刷の組版は500年をかけてページの組み方を身につけ、ブラウザーは画面のレイアウトの仕方を身につけました。Postextは前者を後者に持ち込みます。

CSSには、flexbox、grid、columnsといったレスポンシブなレイアウトの仕組みがあります。しかし、段の高さをそろえること、見出しをそれが導く段落と同じ段に保つこと、ウィドウとオーファンを防ぐこと、図を次の段の頭へフロートさせることはできません。これらは、何世紀もの印刷で磨かれてきた編集上の判断です。

Postextはこの溝を埋めます。意味構造のあるMarkdownと設定オブジェクトを渡すと、@chenglou/pretext (新しいタブで開きます)を使ってDOMより300–600倍速くテキストを計測し、すべての行、フロート、キャプションを含むページの幾何情報を残らず計算して、HTML、Canvas、入稿できるPDFのバックエンドで描画します。

入力:Markdown

:::part{number="I" title="基礎"
  palette="band=#2b4acb"}
:::

# なぜPostextか {lead="印刷の組版は
五百年をかけて……"}

Postextは**オープンソースの
組版エンジン**です。
:ref{id="fig-flow"}のように、
印刷組版の技をウェブに
もたらします。

:::callout{type="try"}
一語を変えれば、ページが
組み直されます。
:::

出力:ページ

図 1.1 エンジンを通す前と後の同じMarkdown。章扉、高さのそろった2つの段、段の頭へフロートした図。

数字で見るPostext

DOMと比べたテキスト計測の速さ。入力するそばから本全体が組み直されます
300–600×DOMと比べたテキスト計測の速さ。入力するそばから本全体が組み直されます
1つのレイアウトから得られる出力。HTML、Canvas、入稿できるPDF、EPUB 3
41つのレイアウトから得られる出力。HTML、Canvas、入稿できるPDF、EPUB 3
ブックマークとCMYKカラーを備えた、タグ付きのアクセシブルなPDF出力
PDF/UAブックマークとCMYKカラーを備えた、タグ付きのアクセシブルなPDF出力
必要なサーバーの数。エンジンはすべてブラウザーの中で動き、MITライセンスで公開されています
0必要なサーバーの数。エンジンはすべてブラウザーの中で動き、MITライセンスで公開されています

章 2 · 機能

印刷の組版者が昔から知っていたこと

ページに必要でありながら、CSSが決めるようには設計されていない6つのこと。エンジンがそれを計算し、毎回同じ結果を出します。

  1. 2.1

    Knuth-Plassの両端そろえ

    TeXと同じ最適な行分割アルゴリズムを、TeXのパターンによるハイフネーションとオーファン制御とともに使います。白い川(リバー)のない均一な版面を、CSSでは表現できない水準で組みます。

    ドキュメントで読む
  2. 2.2

    リソースと相互参照

    図、SVGの図表、表を、印刷のフロートと同じように段やページの天または地へフロートとして配置します。番号は種類ごとに振られ、:refによる相互参照は常に番号と一致します。

    ドキュメントで読む
  3. 2.3

    表とキャプション

    本文と見出し行に別々の文字組みを設定できる表。ページをまたいで分割し、見出し行を繰り返します。キャプションは設定に従って番号が振られ、説明する対象のリソースのそばに置かれます。

    ドキュメントで読む
  4. 2.4

    単色インクの図

    SVGの図を輝度に応じて1色のインクの濃淡に置き換え、すべての図版をデザインに合わせます。特色印刷にもそのまま使えます。

    ドキュメントで読む
  5. 2.5

    LaTeXの数式

    LaTeXで書いたインライン数式と別行立て数式を、エンジンが計測して組み、本文とともにベースライングリッドに乗せます。

    ドキュメントで読む
  6. 2.6

    ウェブ、入稿用PDF、EPUB

    1つの組版エンジンから4つの出力。計算した同じページを、HTML、Canvas、入稿できるPDF(ブックマーク、CMYKカラー、タグ付きのアクセシブルなPDF/UA出力に対応)に描画し、ページそのままの形またはリフロー型のEPUB 3電子書籍としても書き出せます。

    ドキュメントで読む
Postextガイドは、表紙も目次も部扉も含めて全ページを、読んでいるそのときに、あなたのブラウザーの中でPostextが組んでいます。
— 付属ガイドの奥付

作例

本物の本を、ブラウザーで組む

教科書、雑誌、注釈付きの叙事詩、展覧会の図録、全120回の中国古典小説、アラビア語の『千夜一夜物語』、縦組みの日本の小説。どれでもSandboxで開いて規則を一つ変えると、すべてのページが組み直されます。

章 3 · ワークフロー

出版品質のレイアウトまで3ステップ

内容はMarkdownで、デザインは設定オブジェクトで書き、ページは1回の関数呼び出しで得られます。

  1. 3.1

    意味構造のあるMarkdownを書く

    見出し、リスト、表、数式、囲み、図への:ref相互参照を使い、拡張Markdownで内容を書きます。レイアウトを気にする必要はなく、書くのは内容だけです。

  2. 3.2

    レイアウトの規則を設定する

    ページサイズ、段組み、文字組み、見出しのデザイン、柱、フロートの配置をPostextConfigオブジェクトで定義します。Sandboxで目で見ながらデザインすることもできます。

  3. 3.3

    HTML、Canvas、PDFに描画する

    buildDocumentを呼び出すと、レイアウトの幾何情報がすべて返ります。それをHTML、Canvas、入稿できるPDFのバックエンドで描画します。

API

1つの関数で、すべてを制御

コンテンツと設定オブジェクトを渡してbuildDocumentを呼び出すと、ページの幾何情報がすべて返ります。renderPageで任意のページを画像として描くことも、renderToPdfで本全体を入稿できるPDFとして組むこともできます。

book.ts
import { buildDocument, renderPage } from "postext";
import { renderToPdf } from "postext-pdf";

const doc = buildDocument(
  { markdown },
  {
    page: { sizePreset: "21x28" },
    layout: { layoutType: "double" },
    bodyText: {
      fontSize: { value: 12, unit: "pt" },
      textAlign: "justify",
      hyphenation: { enabled: true, locale: "en-us" },
    },
  }
);

// すべてのページを画像に…
const cover = renderPage(doc.pages[0], doc);
cover.toBlob((png) => save(png, "cover.png"));

// …または本全体を入稿できるPDFに
// fontProvider:ファミリーとウェイトごとのTTFバイト列(ドキュメントを参照)
const pdf = await renderToPdf(doc, { fontProvider });

章 4 · オープンな形式

1つのファイルに、本1冊

1つの.postextファイルに、どのマシンでも文書を組み直すために必要なものがすべて入っています。誰でも採用できる、オープンで編集可能な形式です。

多くの出版用の形式では、プロジェクトがばらばらに散らばります。LaTeXのビルドは特定のマシンにインストールされたフォントとパッケージに依存し、主要なレイアウトソフトのネイティブファイルは、外部に置かれた画像や書体にリンクしています。プロジェクトを移すと、何かが欠けます。私たち自身、LaTeXでこの限界に突き当たりました。

.postextは、誰でも開いて読み、編集できる通常のZIPアーカイブです。中身はJSONのマニフェスト、章ごとに1つのMarkdownファイル、そしてフォントファイルと画像そのものです。Sandboxが書き出し、postext-portエージェントスキルが書き、postext/bundleがどのプログラムからでも開いて同じページを組み直します。形式は文書化され、MITライセンスで公開されており、どんなツールからも読み書きできます。

postext-guide.postextの中身
  1. 4.1preset.json

    設定. ページ、グリッド、書体、見出しと段落のスタイル、色、柱を、読みやすいJSONで。

  2. 4.2chapters/<lang>/*.md

    章. 章ごとに1つのMarkdownファイル。ここでは2言語で14章。

  3. 4.3fonts/*.woff2

    書体. 名前だけでなく、フォントファイルそのもの。Lora、Fraunces、Geist、Bricolage Grotesqueの18ファイル。

  4. 4.4preset.json › resources

    リソース. 本文が参照する24点の図と表の、キャプション、番号、配置、表の内容。

  5. 4.5resources/*.svg

    画像. 図版そのもの(SVGまたはビットマップ)と、言語ごとの版。34ファイル。

図 4.1 ガイド自身の.postext(524 KB)。本に必要なものはすべて入っていて、外から取ってくるものはありません。

試してみる

ガイドを開いて、ページを得る

Sandboxの最初の見本であるPostextガイドは、1つの.postextファイルです。ブラウザーで開いて、すべてのページを画像にします。書体も図も表も、ファイル自体から読み込まれます。

ガイドをダウンロードpostext-guide.postext · 524 KB
open-guide.js
import { openBundle, loadBundleFonts, registerBundleImages,
  buildBundle, renderPageToCanvas } from "postext";

// Sandboxのガイド:設定、章、フォント、画像
const file = await fetch("postext-guide.postext").then((r) => r.blob());
const book = await openBundle(file, { locale: "en" });

await loadBundleFonts(book);      // ガイド自身の書体
await registerBundleImages(book); // ガイド自身の図

// すべての章の全ページを画像に
for (const doc of buildBundle(book))
  for (const page of doc.pages) {
    const canvas = document.createElement("canvas");
    renderPageToCanvas(page, doc, canvas, { scale: 0.5 });
    document.body.append(canvas);
  }

インストール

1分以内に始める

パッケージは1つだけ。サーバーもネイティブの依存関係も要りません。印刷が必要になったらpostext-pdfを追加します。

quick-start.ts
// クイックスタート
import { buildDocument, renderPage } from "postext";
import { renderToPdf } from "postext-pdf";

const doc = buildDocument({
  markdown: "# Hello World\n\nYour content here.",
});

// 最初のページをcanvasに描く
document.body.append(renderPage(doc.pages[0], doc));

// 文書全体をPDFに
const pdf = await renderToPdf(doc, { fontProvider });