简介
当我们把网页应用转化成 PDF 的时候有着各式各样的方法。在下面这篇文章来说,Rachel Andrew 通过她自己使用市面上各种工具的经验来帮助我们找到最合适自己的工具。
许多网页应用有着能让用户转化下载成 PDF 格式的文件的需求。在某些情况下(例如电子商店),需要快速的根据动态的数据生成 PDF。
在这篇文章中,我会带着大家寻找各种各样可以直接把网页应用。虽然这是一个不完全的列表,主要是证明有不同的方法来达到目的。如果你们有什么喜欢的工具或经验,欢迎在评论告诉我们。
从 HTML 和 CSS 开始
我们的网页应用基本都是先把需要在 PDF 中展示的数据加到 HTML 中。在生成发票的例子中,用户可以在线查看各种信息并且可以点击按钮下载对应记录的 PDF。你可能开始准备小纸条;强调下,我们所需要的信息已经隐藏在系统里面了。你可能想通过一个比较好的格式去进行下载和打印。因此,一个简单的入手方式就是看下是否可以通过 HTML 和 CSS 来生成 PDF。
CSS 有其为打印的独特规范,这就是Paged Media module。我已经在我之前的文章Designing For Print With CSS中介绍过,并且许许多多的发行社在他们发行的书中也用到了 CSS。因为 CSS 有打印专用的规范,我们就应该可以直接使用吗?
用户最简单去生成一个 PDF 就是通过他们的浏览器。通过生成 PDF 而不是打印的方式生成了对应的 PDF。事与愿违,这样生成的 PDF 并没办法让我们满意!最显而易见的就是当你在打印某些网页应用的时候,会被自动加上了头部和底部的信息。这个文件还会根据你有自定义的打印格式进行格式化。
我们刚才发现的问题都是因为浏览器对于分块规范支持非常的弱;这就是导致你的文档会在在不正确的地方进行断句。当我在回顾自己的文章Breaking Boxes With CSS Fragmentation时发现分块的支持是零散的。这意味着你并不能在头部被放在页面的最底部时获得比较好的断句等等。
另外,我们没办法控制在有页面空白的盒子中内容,例如在我们选择好的每一个页面新增一个头部或者为一张复杂的发票展示出这是第几页。这些只是 Paged Media 中的一小部分,并且还没被任何浏览器支持。
我的文章A Guide To The State Of Print Stylesheets In 2018已经准确的指出哪些是可以通过使用打印样式在浏览器中直接使用能力。
通过浏览器渲染引擎进行打印
无需通过浏览器的打印菜单,有各种各样通过浏览器渲染引擎进行 PDF 打印,并且可以把对应页面的头部和底部都打印出来。我在我的 tweet 中最为常用的选择就是 wkhtmltopdf(通过无头 Chrome 或者 Puppeteer)。
WKHTMLTOPDF
在 Twitter 上被提到很多次的一个命令行工具wkhtmltopdf。这个工具可以选择一个或多个 HTML 文件,基于它们的样式转化成一个 PDF 文件。这个工具是通过 WebKit 渲染引擎来工作的。
从根本上说,这个工具虽然和浏览器打印做着一样的工作,但是你却发现它没有自动生成头部和底部。利用这一点,我们可以通过这个工具生成一个有着打印样式的内容页面或者简单布局的页面去生成 PDF 文件。
虽然你已经换成渲染引擎的模式,还是会遇到在缺少对 Paged Media specification 和分块功能支持的浏览器上进行同样的问题。这里有一个文章记录了 wkhtmltopdf 和使用 Paged Media specification 时一样的缺失的能力。但是这需要比较好的 HTML 和 CSS 功力来完善。
无头 CHROME
另一种有趣的方式去生成 PDF 文件就是使用无头 Chrom 和 Puppeteer。
但是我又再次发现你会被浏览器所支持的 Paged Media 和分块能力所限制。这里是 page.pdf() 方法中传入的一些参数。和 wkhtmltopdf 一样,这些被加入的函数能力使得来自 CSS 的能力需要被浏览器支持。
你会发现这些方法可以满足你的需求,但是如果你在研究某些能力的时候,你可能会发现你正在疯狂试探着现代浏览器渲染引擎的底线,然后再去寻找其他更好的解决方法。
JAVASCRIPT 中为 PAGED MEDIA 的 POLYFILLS
我们可以通过 JAVASCRIPT 中的 Paged Media Polyfill 重新生成浏览器中的 Paged Media 规范。通过这个操作可以在 Puppeteer 中加入 Paged Media 支持。我们一起来看下 paged.js 和 Vivliostyle。
用打印机 UA
如果你还想使用 HTML 和 CSS 方法,你需要用到专为 HTML 和 CSS 设计的用于生成 PDF 文件的各种 API 的打印 UA。这些 UA 实现了 Paged Media 规范并且对于 CSS 分块能力有着更好的支持;基于这些可以让你对生成有着更好的控制。可以看下下面的各种方案:
打印机 UA 会用 CSS 来格式化文档-就像浏览器一样处理网页。就像查找浏览器中的 CSS 支持一样,你需要去查看这些 UA 的文档确认它们支持哪些属性。例如,Prince(我比较熟悉的)在编写的时候支持 Flexbox 但不支持 CSS Grid Layout。在你把页面发送到你使用的工具时,留意下是否会生成你想要的打印格式。如果是一个普通的打印格式,你在页面上用到的CSS 并不一定会在 PDF 文件上正常展示出来。
为这些工具创建一个样式就和我们创建一个正常的打印样式一样,做出指定模块是否展示或隐藏的选择,可能还会用到不同的字体大小和颜色。后面你可能会利用 Paged Media 规范的有时去加上底部说明和页码等等。
对于在你的网页应用中使用这些工具而言,你需要在你的服务器上安装它们(还需要买上对应工具的证书)。这些工具的最突出的问题是它们很贵。这就是说,你可以轻松的使用它们来打印文档,但同时你也需要为节省时间付出不低的价钱。
使用 Prince 基于它提供的 API,每次使用的时候都是基于一个叫做 DocRaptor 进行每个文档的打印计费。对于许多应用来说对于开发时候切换进程带来的花销最小化并且更加高效化是一个好的开始。
WeasyPrint 是一个相比于之前提到的工具性价比不高但是可以满足你的需求的一个免费的选择。它并没有完全实现 Paged Media 规范,但是它相比于浏览器引擎做的更多。也是一个值得去尝试的选择!
其他工具例如声称支持 Html 和 CSS 转化如 HTML5、CSS3 和 JavaScript 的 PDFCrowd。我并没有发现它在实际上支持了什么,并且任何关于 Paged Media 规范的支持也没有找到。同时我也在 tweet 中发现 mPDF。
从 HTML 和 CSS 中移除
还有着许多其他的解决方法,某些工具就是通过 HTML 和 CSS 中移除并且引用特殊的输出格式。下面有两个相关的工具: