
最近,在一家初创公司中,我解决了生成PDF格式的票证的问题。 当时,一个已经建立了一套成熟技术的网站已经准备就绪,因此我正在寻找一种不需要使用其他工具的方法。 最后,我建议先创建HTML格式的票证,然后使用Chrome浏览器将其转换为PDF。 事实证明,此方法不仅可以生成使用CSS装饰丰富的票证,还可以使用JavaScript使用图表生成各种报告。 在本文中,我将讨论如何出于这些目的启动Chrome,提供一些自定义CSS的技巧,并讨论该解决方案的缺点。
此处不讨论其他选项,因为已经在它们上面写下了足够的文字,它们很容易找到,并且它们是现成的工具,有关信息的信息更容易在源代码中找到-官方网站的文档中。 所提出的方法不是独立的工具,而更像是几种技术发展的副产品。 在Internet的俄语部分中,收集到的信息很少,因此我决定填补这一空白。
为什么选择此选项?
Chrome的最大优势在于,无需扩展技术堆栈即可生成PDF。 前端开发人员使用熟悉的开发工具创建HTML,并立即在浏览器中看到工作的中间结果。 同时,Chrome可能正在测试中,将其传输到后端并不困难。 还应注意,编码器能够访问css属性的整个库,包括Flexbox和Grid。
在本文过程中,我将讨论缺点和解决它们的方法。
我们一站式解决问题
在命令行上,我们以无头模式调用Chrome,并将页面保存为pdf:
chrome --headless --disable-gpu --print-to-pdf https://google.com
Linux用户可能需要运行chrome
chromium-browser
而不是chrome
。
MAC用户可能会发现预先创建别名很有帮助:
alias chrome="/Applications/Google\\ \\Chrome.app/Contents/MacOS/Google\\ \\Chrome"
更新:注释澄清了Windows用户需要显式设置PDF文件的名称--print-to-pdf=output.pdf
如果您已经有HTML文档生成器,而不是https://google.com
指定接收该文档的URL。
在本地目录中打开文件output.pdf
并查看结果。
引起您注意的第一件事是带有打印日期的页眉和带有URL和分页的页脚。 为了删除它们,您需要添加一些CSS规则。 这些规则不太可能添加到google.com
,因此,为了进行进一步的工作,最好创建自己的HTML文档。
添加CSS
CSS有一个特殊的媒体查询@page
,用于打印;我们将在其中设置缩进,以使Header和Footer根本不适合:
@page { size: A4; margin: 0mm; }
此方法仅适用于单页文档,当打印两页或更多页时,带有URL和页码的页脚将保留在底部。 您可以通过设置打印参数displayHeaderFooter = False
来明确要求Chrome关闭页眉和页脚的显示,但此刻它尚未移至命令行界面。 要做到这一点,您将需要一些工具来自动化浏览器的工作:Selenium或puppeteer。 接下来,我将考虑第一个选项,因为我的项目使用的是Python。
通过Selenium启动Chrome
因此,请使用pip install selenium
命令安装Selenium,从http://chromedriver.chromium.org/下载与您的Chrome版本匹配的chrome驱动程序,并使用以下示例中的get_pdf_from_html
函数:
import sys from selenium import webdriver from selenium.webdriver.chrome.options import Options import json, base64 def get_pdf_from_html(path, chromedriver='./chromedriver', print_options = {}):
要获取PDF文件,您可以从命令行运行此示例,方法是指定URL和文件名以保存PDF,或者调用get_pdf_from_html
函数并将三个参数传递给它:
- path-HTML文档的url;
- chromedriver-本地计算机上chrome驱动程序的路径(默认情况下,它必须在本地目录中);
- print_options-其他打印属性。
应该注意的是,Selenium没有用于打印PDF页面的标准接口,只有Chrome可以做到这一点,因此您必须直接调用driver.command_executor._request
。
现在,让我们看看哪些工具可用于控制内容在多页文档中的放置。
CSS排版
进行双面打印时,如果打算将来进行装订,可以分别为左右页面的边缘设置不同的边距:
@page :left { margin-left: 4cm; margin-right: 2cm; } @page :right { margin-left: 4cm; margin-right: 2cm; }
对于第一页,您可以指定自己的设计,例如,从顶部边缘增加缩进量:
@page :first { margin-top: 10cm }
可以在第一级标题之前设置分页符,以便它从奇数页开始:
h1 { page-break-before : right }
使用page-break-after
属性,可以防止在某些元素(例如,第二级标题)之后立即分页:
h2 { page-break-after : avoid }
page-break-inside
属性有助于避免不希望这样做的分页符,例如在表中间
table { page-break-inside : avoid }
orphans
和orphans
将有助于防止在段落的开头和结尾处出现分页符:
@page { orphans:4; widows:2; }
性能如何?
在3600MHz的i5-8600K Core i流中,一次简单的文档转换需要0.6秒。 在我的2013年底便携式打字机上,速度为2.4 GHz-1.5秒。
显然,主要资源都花在了启动浏览器上。 如果您将Chrome作为微服务运行一次并向其发送URL进行转换,则可以减少大量文件的转换时间。 此方法的实现超出了本文的范围。
还有什么问题吗?
我看到两个主要问题:
- 无法简单确定文档中元素的位置。 这使得难以创建具有页码自动指示的目录,尤其是如果内容的大小事先未知时。
- Chrome的转换是Google的产品,它收集有关用户的各种信息。 如果从文档中泄漏数据是不可接受的,则应谨慎考虑建议的解决方案-关闭可访问外部资源的浏览器,甚至寻找其他解决方案。 使用开源Chromium无法解决问题-已在其中找到Google的错误。
结论
我建议就我自己使用这种方法的可取性得出结论。 每个项目都有其独特的方式。 此方法是否适合您的项目取决于您。