了解Google Chrome将HTML转换为PDF功能


最近,在一家初创公司中,我解决了生成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 = {}): #  Chrome webdriver_options = Options() webdriver_options.add_argument('--headless') webdriver_options.add_argument('--disable-gpu') driver = webdriver.Chrome(chromedriver, options=webdriver_options) #   url driver.get(path) #    calculated_print_options = { 'landscape': False, 'displayHeaderFooter': False, 'printBackground': True, 'preferCSSPageSize': True, } calculated_print_options.update(print_options) #    pdf  result = send_devtools(driver, "Page.printToPDF", calculated_print_options) driver.quit() #    base64 -  return base64.b64decode(result['data']) def send_devtools(driver, cmd, params={}): resource = "/session/%s/chromium/send_command_and_get_result" % driver.session_id url = driver.command_executor._url + resource body = json.dumps({'cmd': cmd, 'params': params}) response = driver.command_executor._request('POST', url, body) if response['status']: raise Exception(response.get('value')) return response.get('value') if __name__ == "__main__": if len(sys.argv) != 3: print ("usage: converter.py <html_page_sourse> <filename_to_save>") exit() result = get_pdf_from_html(sys.argv[1]) with open(sys.argv[2], 'wb') as file: file.write(result) 

要获取PDF文件,您可以从命令行运行此示例,方法是指定URL和文件名以保存PDF,或者调用get_pdf_from_html函数并将三个参数传递给它:


  1. path-HTML文档的url;
  2. chromedriver-本地计算机上chrome驱动程序的路径(默认情况下,它必须在本地目录中);
  3. 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 /* Top margin on first page 10cm */ } 

可以在第一级标题之前设置分页符,以便它从奇数页开始:


 h1 { page-break-before : right } 

使用page-break-after属性,可以防止在某些元素(例如,第二级标题)之后立即分页:


 h2 { page-break-after : avoid } 

page-break-inside属性有助于避免不希望这样做的分页符,例如在表中间


 table { page-break-inside : avoid } 

orphansorphans将有助于防止在段落的开头和结尾处出现分页符:


 @page { orphans:4; widows:2; } 

性能如何?


在3600MHz的i5-8600K Core i流中,一次简单的文档转换需要0.6秒。 在我的2013年底便携式打字机上,速度为2.4 GHz-1.5秒。
显然,主要资源都花在了启动浏览器上。 如果您将Chrome作为微服务运行一次并向其发送URL进行转换,则可以减少大量文件的转换时间。 此方法的实现超出了本文的范围。


还有什么问题吗?


我看到两个主要问题:


  1. 无法简单确定文档中元素的位置。 这使得难以创建具有页码自动指示的目录,尤其是如果内容的大小事先未知时。
  2. Chrome的转换是Google的产品,它收集有关用户的各种信息。 如果从文档中泄漏数据是不可接受的,则应谨慎考虑建议的解决方案-关闭可访问外部资源的浏览器,甚至寻找其他解决方案。 使用开源Chromium无法解决问题-已在其中找到Google的错误。

结论


我建议就我自己使用这种方法的可取性得出结论。 每个项目都有其独特的方式。 此方法是否适合您的项目取决于您。

Source: https://habr.com/ru/post/zh-CN459112/


All Articles