最近又重新改造了一遍三年前写的 Web Printer 项目,今天又迭代到 3.0 版本了。
这个项目最早是为了解决一个很具体的业务问题:后台财务报表需要导出 PDF,但是表格内容比较长,直接打印容易出现跨页截断。
当时的实现基本就是围绕业务需求堆代码。虽然解决了表格分页的问题,但是没有真正处理长文本的分页,工程结构也比较混乱。
三年后重新捡起来,一开始的想法其实很简单,就是想把代码重构一下,让它更工程化一点。顺便把之前没有解决的文本分页问题补上,比如段落太长时,怎么按照文字行进行拆分,而不是直接截断。
研究这个问题的时候,我又注意到了小红书那种 Markdown 长文本转图片卡片的开源项目。它们其实也在解决类似的问题:将 Markdown 解析成 AST,根据不同元素制定分页规则,测量内容高度,再将长文本拆分成多张卡片。包括段落拆分、表格跨页、标题保护、模板渲染等,都已经有比较成熟的实现思路。
于是我又产生了一个想法:既然 Markdown 可以通过这套流程实现分页,那么复杂的 Web 页面是不是也可以?毕竟浏览器里的 DOM 本身也是一棵树。如果把元素分类、布局测量和分页算法抽象出来,理论上就可以处理更加通用的页面,而不只是 Markdown。
但继续想下去,又觉得通用复杂 Web 页面分页,可能本身就是一个伪需求。因为真实的 Web 页面是为屏幕和交互设计的,它可能包含动态渲染、复杂嵌套、折叠面板、滚动容器,以及各种 Flex、Grid 布局。但这些交互和布局不一定适合打印。
比如一个包含一万条数据的表格,在网页上可以做成分页,每页显示十条。但如果要打印 PDF,究竟是打印当前十条,还是把一万条数据全部展开?如果只是为了阅读,为什么不直接在线上看?如果是为了存档,那确实需要导出完整数据,但这时候更合理的做法应该是生成一个专门的打印视图,而不是直接打印原来的交互页面,而且像jsPdf之类的库,可以通过json数据写入的方式打印表格,已经可以解决了。
同样,一个有大量滚动容器和折叠内容的网页,直接打印可能丢失信息。即使技术上可以处理,也不代表打印出来的结果有实际价值。
所以我逐渐意识到,真正有打印需求的,往往是报表、凭证、合同、长文档等内容相对静态的页面。 它们可以由动态数据生成,但最终导出的内容和布局应该是确定的。
这也让我重新考虑 Web Printer 的产品定位。
既然 Markdown 卡片分页已经有比较成熟的解决方案,而任意复杂 Web 页面打印又未必是真实需求,那其实没有必要重复造轮子。
所以我用AI调研了下,发现可以这样定位:所见即所得,保留 Web 打印页面的原有视觉布局,实现内容感知分页,并导出 PDF。直接可以对标的就是 html2pdf.js。
html2pdf.js 主要依赖 html2canvas 将页面栅格化,再通过 jsPDF 生成 PDF。这种方式比较容易上手,但是在复杂长页面下,也会面临性能开销、分页截断、渲染位置偏差等问题。而且这个库每周有93.3 万的下载量,473多个open issue,说明需求量大,而且还问题多。
而我的Web Printer 可以把重点放在内容感知分页上:识别不同类型的 DOM 元素,根据内容和页面剩余空间决定分页位置,尽可能避免文字截断、表格行拆分等问题,而且尽可能保留打印视图原有的布局和视觉效果
项目地址:
分页算法和工程实现那部分我另写了一篇:我开源了一个 Web 端 HTML 智能分页引擎,解决 PDF 导出时的内容截断问题。