有人在 Reddit 论坛贴出一页带着动态花卉边框的文档,问怎么在 LaTeX 里做出这种能自动适应页面留白的花朵。原帖作者宣称那是纯 TikZ 生成的,却始终没有公开源码;评论区只能给出 TikZ 的 L-system 分形库或 pgfornament 等常规替代方案。开发者 Kevin Raymond 看到后没有去猜原版的实现,而是直接开源了 Florilegium(采用 MIT 开源许可证,版权归属显示为 Copyright © 2026 Kevin Raymond):一个完全不需要外部图片或预制路径、只靠 6 个 Lua 脚本实时计算参数化曲线并直接写进 PDF 的 LuaLaTeX 宏包。

这件事最反常的地方,是它用纯代码在枯燥的学术排版引擎里画出了 300 dpi 级别的代夫特蓝(Delft-blue)水彩画,而且每页编译耗时只要约 1 秒(宏包注释中估计为 1 至 2 秒)。对于长期被 TeX 宏展开折磨的学术作者、独立出版者和生成艺术开发者来说,它直接演示了当排版系统接上现代脚本语言,自动化书籍装帧能推到什么边界。

Florilegium 核心工程指标一览 0 外部素材依赖 纯参数化曲线直写 PDF 6 个 核心 Lua 脚本 配合 1 个 .sty 宏包文件 1-2 秒 单页编译耗时 普通模式单次遍历即成 1998 年 SIGGRAPH 算法 最大空心圆贪心空间填充

零外部素材的水彩画与一秒一页的账本

Florilegium 的文件结构非常轻,只包含 1 个宏包文件 florilegium.sty 和 6 个 Lua 脚本(authored.lua、flora.lua、florilegium.lua、grow.lua、initial.lua、ink.lua),底层依赖 TikZ 和 eso-pic 宏包,把文件拷进 Overleaf 并将编译器切到 LuaLaTeX 就能直接跑。

它画出来的不是死板的几何线条,而是带水彩晕染肌理的植物园:先铺一层平涂底色,再叠加深色斑块,边缘做出颜料积水感,中间留出白色飞白条纹和叶脉,几乎没有生硬的轮廓线。从勿忘我聚伞花序、星形百合到穿插其中的蝴蝶、蜜蜂、蜗牛和大约每五页出现一次的小鸟,全部在 OKLCH 色彩空间里由单一主色调(ink)插值算出,保证浅色晕染端不会发灰。

为了照顾打印与复现,项目采用了确定性随机数机制:默认直接拿当前页码作为随机数种子,而不是读取系统时钟;用户也可以在加载宏包前通过 \def\florilegiumseed{7} 固定种子,保证每次编译生成的各页花园分毫不差。作者甚至还顺手把核心绘图与生长循环一对一移植成了纯 PostScript 单文件版本,直接发给支持 PostScript 的打印机就能在解释页面时现场算出一座花园。

把 1998 年的图形学论文塞进排版引擎

Florilegium 提供两种布局:一种是针对 A4 纸张(68 mm 页边距)手工调优控制点的 authored 模式;另一种真正惊艳的,是能自己找地方钻的 grown(生长)模式。

作者在文档里轻描淡写地提了一句借用 1998 年装饰文献的思想。去翻故纸堆就会发现,这对应着华盛顿大学 Michael T. Wong、Douglas E. Zongker 和 David H. Salesin 在 SIGGRAPH 1998 上发表的经典论文《Computer-Generated Floral Ornament》。那篇论文当年提出了一个叫自适应剪贴画(adaptive clip art)的概念,核心是用光栅缓冲区和基于曼哈顿距离的中轴变换(medial axis transform)寻找候选圆心,通过不断膨胀测试圆来测量可用空间,找到最大空心圆(largest free discs),再引导附近的植物藤蔓朝那个空洞生长,并把失败的位置列入排除列表。

LuaLaTeX 空间感知与程序化生长管线 1. 截获页面几何 Shipout 阶段读取 pagetotal 与行内标记 解析字体 OpenType 轮廓 2. 搜索最大空心圆 复刻 1998 SIGGRAPH 基于中轴变换测算留白 先填大花再塞小叶与飞虫 3. 水彩图层计算 OKLCH 色彩空间插值 平涂底色与边缘积水叠色 偏移贝塞尔曲线绘叶脉 4. 原生 PDF 直写 绕过 TeX 浮点算术限制 生成单页 literal 矢量节点 可导出 45 秒生长动画

Kevin Raymond 把这套 28 年前的图形学算法完整搬进了 grow.lua。主茎先顺着页边距往上爬,接着贪心循环接管:每次在主茎够得着的范围内找出最大的空闲圆盘,挑一朵尺寸匹配的花或百合放进去,再从最近的茎节点长出一根分枝连过去,这根分枝随后也变成新的主茎。大窟窿先被大花占领,中等空隙填满枝叶,最后再把小叶、花蕾和昆虫塞进边角。用户跑一行命令,还能在约 45 秒内渲染出带着虚线探测圆的 MP4 或 GIF 生长动画,亲眼看着藤蔓是怎么一步步吞掉留白的。

更绝的是,这个花园能“读懂”正文:

  • 花体首字母穿插.用 \florilegiuminitial[5]{E} 生成 5 行高的首字母时,Lua 脚本会直接去读取默认 Latin Modern Bold 等 OpenType 字体的矢量轮廓,把字母当成花瓣来涂色,并让藤蔓在字母笔画的上下方交替穿插,在字母内部的孔洞里开花;同时底层保留透明的真实字符,你在 PDF 里复制粘贴这段话,拿到的依然是完整的单词。
  • 正文感知与跨页四季.在文档最后一页,如果正文提前结束,算法会在单次编译中通过 \pagetotal 测出下方留白,让枝条横着拱进正文底下的空地;在行内写一个 \florilegiumbloom,页面输出(shipout)阶段就会捕获标记并在旁边开一朵花;开启 aging 选项后,经过两次编译获取总页数,花园还会在整本书里经历从第一页的春芽、仲夏繁花到最后一页枯叶落瓣的季节老化。

为什么绕开纯 TikZ 是一次聪明的降维打击

看懂了上面的机制,就能明白为什么作者一开始就放弃了复刻 Reddit 原帖那个神秘的“纯 TikZ 方案”。

真正的工程克制,是不拿宏语言去硬扛图形学的算力活。

传统的 TeX 宏语言虽然图灵完备,但本质上是一套文本宏展开机制,极不擅长密集的浮点运算与复杂空间数据结构处理。在旧生态里,做页面装饰无非三条路:导入外部画好的 PDF/EPS 矢量图、用 pgfornament 摆放预定义的标准化死板边角,或者用 TikZ 的 L-system 手写简单的分形规则。如果真用纯 TeX/TikZ 去跑中轴变换、做碰撞检测、再叠加几十层水彩半透明贝塞尔路径,轻则编译时间失控,重则直接撞上 TeX 引擎的内存上限。

LaTeX 页面装饰路线对比:传统宏展开 vs Lua 直写架构 传统 TeX / TikZ 装饰方案 • 依赖 pgfornament 预制图案或简单分形 • 装饰层与正文盒子隔离,无法感知留白 • 宏展开算浮点极慢,易触发 TeX 内存溢出 • 难以实现多层水彩晕染与字体轮廓穿插 Florilegium (LuaLaTeX 架构) • 移植 1998 SIGGRAPH 贪心空间填充算法 • Shipout 阶段动态截获页面留白与字体轮廓 • Lua 算几何直写 PDF 节点,每页仅 1-2 秒 • 300 dpi 水彩叠色且保持底层文本可复制

《考工记》有云:“审曲面势,以饬五材,以辨民器。”意思是工匠得先审视材料的弯曲程度和地形势态,再去加工器物。在排版这件事上,页面的正文盒子就是“势”,编程语言就是“材”。

Florilegium 真正捅破的窗户纸,是展示了 LuaTeX 引擎把 Lua 脚本深度嵌入排版流水线后的真实威力。它让 TeX 只管自己最擅长的段落断行与盒子拼装,在页面即将输出的瞬间截获几何坐标,然后把所有肮脏的三角函数、空间搜索、颜色插值统统甩给 Lua,算完直接拼成原生 PDF 路径操作符塞进每页单个 literal 节点里,TeX 全程不做一次算术。这实际上把中世纪修道院抄本里“抄写员先写字、画师再顺着字缝画藤蔓”的手工协作流程,用现代编译器流水线重演了一遍。

  • 结论.Florilegium 的核心价值不在于那套代夫特蓝水彩皮肤,而在于跑通了“读取排版盒子几何信息 + 外部脚本空间填充 + 原生 PDF 直写”的工程范式,这套路子未来完全可以复用到自适应页注排版或动态数据图表生成上。

当然,把话拉回现实的写作场景,这套方案也有还没结算完的工程代价。作者给出的每页 1 到 2 秒耗时,放在几页纸的诗集、证书或邀请函上毫无压力;但如果放进动辄两三百页的学术专著或需要高频实时预览的写作流里,每次重编译都要多等几分钟。更现实的问题在于 PDF 的体积与渲染:为了做出逼真的水彩边缘积水和斑驳感,每一片叶子和花瓣都是由多层半透明贝塞尔路径叠加而成的全矢量图形。当一本书有上百页繁茂的植物时,生成的 PDF 文件体积膨胀,以及读者在平板电脑或低配阅读器上翻页时的光栅化卡顿,都是作者将其推向 CTAN 正式收录和大规模实用化时绕不开的硬约束。

当年那个在 Reddit 上提问怎么画花的读者,原本可能只想找几行现成的 TikZ 代码抄进作业;没想到却炸出了一个把 1998 年图形学论文、OpenType 字体解析和原生 PDF 渲染管线捏在一起的硬核作品。在所有人都忙着用大模型生成千篇一律的位图插画时,看一个程序员用 6 个 Lua 脚本在排版引擎的夹缝里一秒钟种出一页水彩花园,依然是技术世界里最让人愉悦的那种浪漫。