2026-09-23·by Sijie Wang#standmeet#rendering#tikz

tikzjax

TikZ(精确数学图形)— 已上线

上级:rendering-engines

补上 mermaid 补不了的缺口:精确的数学图形——几何作图、交换图、带坐标/标签的方框、函数曲线。一个 ```tikz 代码块渲成 SVG,reader 上已经在跑。

选 TikZ 的决定性理由是引擎对称。 node-tikzjax 跟 Obsidian 的 TikZJax 插件是同一个引擎,于是 owner 在 Obsidian 里画,StandMeet 渲出一模一样的图 —— 跟 katex / mermaid 是同一个论证。(权衡后否掉的:quiver、Mafs、function-plot、JSXGraph、Penrose —— 需求是静态的精确图形。)

它在服务端渲染,不在浏览器

最初的设想是浏览器端 WASM,载荷懒加载、或者放在某个设置项后面。落地的不是那样,而这次反转本身就是设计。 TeX 引擎跑在服务端(/render-tikz,Node runtime 的路由,用例在 lib/render/tikz);客户端的 TikZBlock 把源码 POST 上去,收回 SVG。重 WASM 一个字节都不到访客那儿。

真正的代价在别处:serverExternalPackages + outputFileTracingIncludes,因为 TeX 的运行时资产(core.dump.gztex.wasm.gztex_files.tar.gz)是从文件系统读的,不是 import 进来的,Next 的依赖追踪带不进 standalone 构建 —— 少了它们,那条路由 ENOENT 然后挂住。

引擎不可重入 —— 这条是承重的

node-tikzjax 驱动的是一个进程内单例 WASM TeX 引擎。两次渲染重叠就会互相踩,双方一起TeX engine render failed。而 reader 页面上有几张图就同时发几个 POST,于是一篇有四张图的笔记会随机丢掉其中几张 —— 读者在本该是图的地方看到一整段 LaTeX,而且哪几张失败每次刷新都不一样

线上量到的:同一份源码单发返 200,并发两个时两个都 422,0.6 秒就回 —— 离 25 秒的超时差得远,所以它从来不是"慢"的问题。

因此渲染排队:同一时刻只有一个进引擎。计时从真正开跑那一刻起,不含排队等待,否则一页图一多,排在后面的还没轮到就被判超时。队列是每进程一条;多副本各串各的,够用 —— 单例本来就是进程内的。

字体必须自托管

SVG 里的文字是 <text> + font-family: cmr10 / cmsy10 / …,而那些字符是 TeX 字体的槽位,不是 Unicode$\to$ 在 cmsy10 里落在 0x21,也就是 !。字体不加载就退回系统字体,箭头当场变成惊叹号,字距也按错的度量排,词会被拆开(stochasticsto chastic)。

包里 embedFontCss 默认 false,默认的 fontCssUrl 还指着 jsDelivr。对一个自托管产品这两条都是错的:离线的实例取不到,而且每张图都会替 owner 的读者向第三方发一次请求。字体由本实例发(scripts/copy-tikz-fonts.mjs 跟着 build 把它们搬进 public/)。

失败不是读者的问题

渲染失败以前会把 LaTeX 源码印进页面。mermaid 早就定过这个问题 —— owner 看得见诊断,访客什么都看不见,而失败照样记日志 —— TikZ 这边当初就是没跟上。loading 现在也是占位而不是源码,这在排队之后更要紧:多图页面上最后一张要等几秒,而那几秒不能是一整墙 LaTeX。

about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →