前言
如果你有 Markdown 或者 HTML 转到 PDF 的需求, 有非常大的可能你选了一圈方案, 最后找到了大名鼎鼎的wkhtmltopdf. 但找到它之后并没有解决所有问题, 相反, 它会带来更多的问题.
首先说一下它的优点:
由于是基于 WebKit, 所以渲染结果和 Chrome( 包括其他基于 Chromium的浏览器几乎完全一致)
可以和前端公用一套 CSS 样式, 结果还是体验的一致性
支持大量的定制, 包括页头页脚, 页码, 目录等等, 后面会详细说
再来说一下缺点:
对特别长的表格支持有问题. 其实原因还是来自 WebKit 引擎, 因为WebKit本身是用来渲染网页的, 而网页是不需要上下分页的, 所以引擎本身并不支持表格跨页显示, 需要做一些特殊处理, 划重点了.
对于特别宽的表格, 如果不做处理, 它会被横向截断,同时会在表格下方出现一个 scrollbar, 但尴尬的是这个 scrollbar 不能拖动, 因此也就无法解决完整显示的问题.
所以, 优点很明显, 缺点也很明显. 而且通常如果你有需求生成 PDF, 那多数情况下是有表格的, 不解决这个问题, 这个方案就无法实施。
实践
下面让我们通过实验一步步解决上面的两个大问题.
长表格
这里我简单写了一长段表格, 然后用 MacDown 编辑器把它导出为 html, 这里只是用来说明问题, 所以就没有用我实际在生产环境使用的 Parsedown.写着写着觉得还是用代码能说的更清晰一些,
原始 markdown 文件和 html 文件就不贴在这里了, 可以查看phpwkhtmltopdf.
在 article 目录里执行
wkhtmltopdf longtable.html longtable-01.pdf
生成的 PDF 在分页处如下的效果
表头重复, 字体重叠
很明显可以看到表头重复出现了. 其实实际的问题不止如此, 如果单元格很宽导致换行, 很大概率会和重复显示的表头重叠.
我翻看了不下100个网页, 花费了大量时间最终找到了解决方案
thead {
display: table-row-group;
}
解决了表头重复和文字重叠的问题.
表头不重复, 字体不重叠, 但一行被分割
有所改善, 但还是明显的发现表格的一行被切成了两部分.
下面这个就是如果你搜索这个问题最经常看到的答案了
tr {
page-break-before: always;
page-break-after: always;
page-break-inside: avoid;
}
于是, 变成了这样子
长表格问题解决
长表格的问题到此解决.
宽表格
同样我将 markdown 文件和 HTML 文件放在了 github 上.
不加特殊处理, 得到的表格将是这样的
表格右侧被截断
这是因为默认的样式中并没有
table {
word-wrap: break-word;
}
的设置. 我这里的例子举的有些不合理, 因为不会全是一样长的单元格宽度, 实际情况总会有长有短, 当同一列中存在一个很长的单词(或者根本不是单词, 而仅仅是很长的连起来的无意义的字符串, 像本例中那样), 该列单元格的最短列宽就会以它为准. 这样导致表格的总宽度很容易超出页面限制.
因为word-wrap 控制的是有多个单词的情况, 如果只有一个单词, 它是不做处理的. 所以需要另外加一个样式
table td {
word-break: break-all;
}
这样的结果就是所有表格都完整的展示出来了.
其他选项
提到 wkhtmltopdf 的选项也真是让人头疼, 我本来是用了snappy和它推荐的二进制包, 所有东西都准备好了, 一个问题一直解决不了: 我已经安装了宋体, PDF 的正文中的宋体也可以正常显示了, 但唯独 header/footer 里面的中文字体死活都出不来. 因为我的需求是要有页眉和页脚, 当然也要有相应的 header-line和footer-line, 如果用HTML 来填充 header, 那么 header-line 就会消失, 这时如果用 html 中的
来代替, 又会看起来很奇怪, 后来发现直接调用二进制文件加上相应的选项在相同的机器上可以得出我需要的结果, 那么问题就很清楚了, 问题出在这个 snappy 上, 于是我一气之下直接用 PHP 的shell_exec简单封装了一下 wkhtmltopdf, 简直不能更好用, 所有问题迎刃而解.
总结
宽度的问题其实在网页设计里面更常见, 我不是专业的前端, 所以对相应的样式比较陌生, 导致花了一些时间. 但分页的问题真是困扰了每个用过 wkhtmltopdf 的人, 随便一搜, 几乎全是关于这个问题, 但没有一个人真正的给出普适的真正可用的解决方案. 本文给出了我自己的一些经验, 供大家参考.