用自己的排版引擎写博客
这是 zball.io 的第一篇文章,也是一次自举:你正在读的每一行,都由我们自研的 TeX 级排版引擎排出。构建期它生成语义 HTML——编号、引用、代码高亮全部就位;浏览器端再渐进升级为逐字形定位的排版渲染,Knuth–Plass 断行、中西文混排 k-rule、标点挤压一并生效。
它能做什么
行内公式随文断行,如 $e^(i pi) + 1 = 0$ 或 $sum_(k=1)^n k = (n(n+1))/2$;显示公式带编号并可引用:
$ int_(-oo)^oo e^(-x^2) d x = sqrt(pi) $
如式 (1)所示,公式编号与图表编号同源。代码由构建期的 tree-sitter 文法高亮——正经 GLR,不是正则:
// 静态页与排版页共用同一套 token
const memo = new Map();
function fib(n) {
if (n <= 1) return n;
if (!memo.has(n)) memo.set(n, fib(n - 1) + fib(n - 2));
return memo.get(n);
}
接下来
插图与环绕、三盒代码块、分页打印这些能力会在后面的文章里逐一展示;引擎本身的设计记录在 §1 提到的仓库文档里。
Blogging on a Homegrown Typesetting Engine
This is the first post on zball.io, and it is also a bootstrap: every line you are reading was laid out by our own TeX-grade typesetting engine. At build time it produces semantic HTML — numbering, cross-references and code highlighting all in place; in the browser the page then upgrades progressively to a glyph-positioned rendering, with Knuth–Plass line breaking, the mixed CJK–Latin k-rule and punctuation compression all in effect.
What it can do
Inline formulas break across lines with the text, like $e^(i pi) + 1 = 0$ or $sum_(k=1)^n k = (n(n+1))/2$; display formulas are numbered and referenceable:
$ int_(-oo)^oo e^(-x^2) d x = sqrt(pi) $
As 式 (1) shows, formula numbers share one counter family with figures and tables. Code is highlighted by build-time tree-sitter grammars — a real GLR parser, not regexes:
// the static page and the typeset page share one token stream
const memo = new Map();
function fib(n) {
if (n <= 1) return n;
if (!memo.has(n)) memo.set(n, fib(n - 1) + fib(n - 2));
return memo.get(n);
}
What comes next
Figures with float wrap, the three-box code block and paged printing will each get their own post; the engine's design notes live in the repository mentioned in §1.