# linux-html2pdf-demo **Repository Path**: yjihrp/linux-html2pdf-demo ## Basic Information - **Project Name**: linux-html2pdf-demo - **Description**: linux HTML 转 PDF demo - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-01-16 - **Last Updated**: 2024-03-31 ## Categories & Tags **Categories**: Uncategorized **Tags**: Linux, html2pdf, pdf ## README # HTML 转 PDF 可行性调研 | 名称 | 语言 | 许可 | 系统 | 架构 | 主要依赖 | 自主开发 | 中文乱码 | 字体引用 | CSS2 | CSS3 | SVG | CANVAS | 图片越界 | 表单 | | -------------------- | ----------- | -------- | ------ | ----------------------------- | -------- | -------- | -------- | ---- | ---- | ---- | ------ | -------- | -------- | -------- | | princexml | java8、11 | 需要 | win10 centos7 | x86_64 | prince | 不支持 | 无 | 无 | 支持 | 支持 | 支持 | 支持 | 有 | | | | | | | | | | | | | | | | | | | itext5、8 | java8、11 | AGPLV3 | win10 centos7 | x86_64 | html2pdf | 支持 | 无 | 无 | 支持 | 支持 | 支持 | - | 有 | | | openpdf | java8、11 | LGPL2.1 MPL2.0 | win10 centos7 | x86_64 | flying-saucer-pdf-openpdf 版本较新 | 支持 | 无 | ? | 支持 | 支持 | ? | ? | 有 | | | pdfbox | java8、11 | Apache2.0 LGPL2.1 | win10 centos7 | x86_64 | openhtmltopdf-pdfbox2、3 版本升级? | 支持 | 无 | ? | 支持 | 支持 | ? | ? | 有 | | | | | | | | | | | | | | | | | | | selenium | java11+ | Apache 2.0 | win10 | x86_64 | selenium-java 4.16 firefox116 | 不支持 | 无 | 支持 | 支持 | 支持 | 支持 | 支持 | 有 | | | selenium | java11+ | Apache 2.0 | centos7 | x86_64 | selenium-java 4.16 | 不支持 | 无 | 支持 | 支持 | 支持 | 支持 | 支持 | 有 | | | selenium | python3.7.9 | ? | win10 | x86_64 | selenium4.11 firefox116 | 不支持 | 无 | 支持 | 支持 | 支持 | 支持 | 支持 | 有 | | | selenium | python3.8.18 | ? | centos7 | x86_64 | selenium4.16 firefox102 | 不支持 | 无 | 支持 | 支持 | 支持 | 支持 | 支持 | 有 | | | selenium | python3.6+ | ? | centos7 | x86_64 | selenium3.141 firefox102 | - | - | - | - | - | - | - | - | | | | | | | | | | | | | | | | | | | weasyprint | python3.7.9 | BSD | win10 | x86_64 | gtk3+ | 支持 | 无 | 无 | 支持 | 支持 | 支持 | 无 | 有 | | | weasyprint | python3.9.18 | BSD | stream9 | x86_64 | cairo,Pango,gtk2.6+ | 支持 | 无 | 无 | 支持 | 支持 | 支持 | 无 | 有 | | | | | | | | | | | | | | | | | | | xhtml2pdf | python3.8+ | | stream9 | x86_64 | openssl1.1.1+ | ? | 有 | - | - | - | - | - | | | # 总结 | 名称 | prince | itext | selenium | openpdf | pdfbox | weasyprint | | -------- | ------ | ----- | -------- | ------- | ------ | ---------- | | 效果 | 1 | 1 | 1 | 0.5 | 0.5 | 0.5 | | 授权 | 0 | 0 | 1 | 0 | 1 | 1 | | 系统 | 0.5 | 1 | 1 | 1 | 1 | 0.5 | | 架构 | 0.5 | 1 | 1 | 1 | 1 | 1 | | 自主开发 | 0 | 0.5 | 0 | 1 | 1 | 1 | | | | | | | | | | 总分 | 2 | 3.5 | 4 | 3.5 | 4.5 | 4 | ## princexml It’s quick and simple to convert HTML to PDF with Prince. HTML is seamlessly transformed into documents you can print 下载地址 [Prince - Download Prince 15.2 (princexml.com)](https://www.princexml.com/download/) #### 包装库 - java - C# - PHP - Node - Ruby #### 适配情况 | 系统名称 | x86_64 | arm64 | 说明 | | ---------------------------- | ------ | ----- | ---- | | Windows | 有 | - | | | Red Hat Enterprise Linux 7-9 | 有 | - | | | Ubuntu 18-22 | 有 | 有 | | | Debian GNU Linux 9-10 | 有 | - | | | Debian GNU Linux 11-12 | 有 | 有 | | | OpenSUSE | 有 | - | | | MacOS | ? | ? | | | Alpine Linux | 有 | - | | | Generic Linux | 有 | 有 | | | Free BSD | 有 | - | | #### 主要依赖 ```xml com.princexml prince-java-wrapper 1.3.0 ``` #### 测试代码 ```java // 获取 java 版本 String version = System.getProperty("java.specification.version"); // 获取系统类型 String platform = System.getProperty("os.name", ""); platform = platform.toLowerCase().contains("window") ? "win" : "linux"; // 当前程序目录 String current = System.getProperty("user.dir"); System.out.println(String.format("current=%s", current)); // html 文件路径 File index = Paths.get(current, "..", "index.html").toFile(); if (!index.exists()) { System.out.println(String.format("file not exist,file=%s", index.getAbsolutePath())); return; } String command = Paths.get(current, "..", "prince-15.2-win64", "bin", "prince.exe").toString(); if (OSInfo.getOSType() == OSInfo.OSType.LINUX) { command = "prince"; } Prince prince = new Prince(command); // prince.setLog("/path/to/log.txt"); // prince.addStyleSheet("/path/to/stylesheet.css"); // prince.addScript("/path/to/script.js"); prince.setJavaScript(true); try { // 转换 html 文件 File file = Paths.get(current, String.format("java%s_%s.pdf", version, platform)).toFile(); prince.convert(index.getAbsolutePath(), file.getAbsolutePath()); } catch (IOException e) { e.printStackTrace(); } ``` #### 效果预览 [java1.8_win.pdf](prince-demo\java1.8_win.pdf) [java11_linux.pdf](prince-demo\java11_linux.pdf) ## java 库 ### itext ![](D:\work\linux-html2pdf-demo\images\Snipaste_2023-12-18_15-15-39.png) ![](D:\work\linux-html2pdf-demo\images\屏幕截图 2023-12-16 171211.png) #### 主要依赖 ```xml com.itextpdf html2pdf 5.0.2 ``` ![](D:\work\linux-html2pdf-demo\images\屏幕截图 2023-12-16 172507.png) #### 测试代码 ```java // 获取 java 版本 String version = System.getProperty("java.specification.version"); // 获取系统类型 String platform = System.getProperty("os.name", ""); platform = platform.toLowerCase().contains("window") ? "win" : "linux"; // 当前程序目录 String current = System.getProperty("user.dir"); System.out.println(String.format("current=%s", current)); // html 文件路径 File index = Paths.get(current, "..", "index.html").toFile(); if (!index.exists()) { System.out.println(String.format("file not exist,file=%s", index.getAbsolutePath())); return; } try { // 保存 pdf 文件路径 File file = Paths.get(current, String.format("java%s_%s.pdf", version, platform)).toFile(); // 转换设置 ConverterProperties options = new ConverterProperties(); // 设置根目录类型 String baseUri = Paths.get(current, "..").toUri().toString(); options.setBaseUri(baseUri); // 设置字体 FontProvider fontProvider = new FontProvider(); fontProvider.addStandardPdfFonts(); fontProvider.addSystemFonts(); options.setFontProvider(fontProvider); // 转换 html 文件 HtmlConverter.convertToPdf(index, file, options); } catch (IOException e) { throw new RuntimeException(e); } ``` #### 效果预览 [java1.8_win.pdf](itext-demo\java1.8_win.pdf) [java11_linux.pdf](itext-demo\java11_linux.pdf) ### openpdf ![](D:\work\linux-html2pdf-demo\images\Snipaste_2023-12-17_16-15-04.png) #### 主要依赖 ```xml org.jsoup jsoup 1.17.1 org.xhtmlrenderer flying-saucer-pdf-openpdf 9.3.1 ``` #### 测试代码 ```java // 获取 java 版本 String version = System.getProperty("java.specification.version"); // 获取系统类型 String platform = System.getProperty("os.name", ""); platform = platform.toLowerCase().contains("window") ? "win" : "linux"; // 当前程序目录 String current = System.getProperty("user.dir"); System.out.println(String.format("current=%s", current)); // html 文件路径 File index = Paths.get(current, "..", "index.html").toFile(); if (!index.exists()) { System.out.println(String.format("file not exist,file=%s", index.getAbsolutePath())); return; } try { Document document = Jsoup.parse(index, "UTF-8"); // 补全标记 document.outputSettings().syntax(Document.OutputSettings.Syntax.xml); ITextRenderer render = new ITextRenderer(); ITextFontResolver fontResolver = render.getFontResolver(); File[] fonts = Paths.get(current, "..", "fonts").toFile().listFiles(); for (File item : fonts) { // 应该这样添加字体 fontResolver.addFont(item.getAbsolutePath(), BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); } SharedContext sharedContext = render.getSharedContext(); sharedContext.setPrint(true); sharedContext.setInteractive(false); sharedContext.getTextRenderer().setSmoothingThreshold(0); // 自定义图片解析 // sharedContext.setReplacedElementFactory(new ReplacedElementFactoryImpl()); // 指定根目录,这里需要 URL 格式 // file:/D:/work/linux-html2pdf-demo/openpdf-demo/../ String baseUrl = Paths.get(current, "..").toUri().toURL().toString(); render.setDocumentFromString(document.html(), baseUrl); render.layout(); File file = Paths.get(current, String.format("java%s_%s.pdf", version, platform)).toFile(); FileOutputStream stream = new FileOutputStream(file); render.createPDF(stream); stream.close(); System.out.println(String.format("file=%s", file.getAbsolutePath())); } catch (IOException e) { throw new RuntimeException(e); } ``` #### 效果预览 [java1.8_win.pdf](openpdf-demo\java1.8_win.pdf) [java11_linux.pdf](openpdf-demo\java11_linux.pdf) ### pdfbox #### 主要依赖 ```xml org.jsoup jsoup 1.17.1 com.openhtmltopdf openhtmltopdf-pdfbox 1.0.10 ``` #### 测试代码 ```java // 获取 java 版本 String version = System.getProperty("java.specification.version"); // 获取系统类型 String platform = System.getProperty("os.name", ""); platform = platform.toLowerCase().contains("window") ? "win" : "linux"; // 当前程序目录 String current = System.getProperty("user.dir"); System.out.println(String.format("current=%s", current)); // html 文件路径 File index = Paths.get(current, "..", "index.html").toFile(); if (!index.exists()) { System.out.println(String.format("file not exist,file=%s", index.getAbsolutePath())); return; } try { Document doc = Jsoup.parse(index, "UTF-8"); // 补全标记 doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml); File file = Paths.get(current, String.format("java%s_%s.pdf", version, platform)).toFile(); FileOutputStream stream = new FileOutputStream(file); PdfRendererBuilder builder = new PdfRendererBuilder(); // NOTE 字体问题,文档中出现过的字段,需要手动加载字体 builder.useFont(Paths.get(current, "..", "fonts", "simsun.ttc").toFile(), "SimSun"); builder.useFont(Paths.get(current, "..", "fonts", "msyh.ttc").toFile(), "font-test"); builder.useFont(Paths.get(current, "..", "fonts", "msyh.ttc").toFile(), "Microsoft YaHei UI"); // NOTE 设置根目录 String baseUrl = Paths.get(current, "..").toUri().toString(); builder.withHtmlContent(doc.html(), baseUrl); builder.toStream(stream); builder.run(); } catch (IOException e) { throw new RuntimeException(e); } ``` #### 效果预览 [java1.8_win.pdf](pdfbox-demo\java1.8_win.pdf) [java11_linux.pdf](pdfbox-demo\java11_linux.pdf) ### 实用工具 ```shell # 查看 pdf 内部结构 java -jar pdfbox-app debug path-to-pdf/test.pdf java -jar debugger-app path-to-pdf/test.pdf ``` ### selenium Selenium 通过使用 *WebDriver* 支持市场上所有主流浏览器的自动化。 Webdriver 是一个 API 和协议,它定义了一个语言中立的接口,用于控制 web 浏览器的行为。 每个浏览器都有一个特定的 WebDriver 实现,称为驱动程序。 驱动程序是负责委派给浏览器的组件,并处理与 Selenium 和浏览器之间的通信。 这种分离是有意识地努力让浏览器供应商为其浏览器的实现负责的一部分。 Selenium 在可能的情况下使用这些第三方驱动程序, 但是在这些驱动程序不存在的情况下,它也提供了由项目自己维护的驱动程序。 Selenium 框架通过一个面向用户的界面将所有这些部分连接在一起, 该界面允许透明地使用不同的浏览器后端, 从而实现跨浏览器和跨平台自动化。 ```text # selenium 驱动 https://selenium-python.readthedocs.io/installation.html#drivers https://selenium-python.readthedocs.io/api.html -i http://172.16.51.188:8081/repository/pypi/simple --trusted-host 172.16.51.188 ``` #### 主要依赖 ```xml org.seleniumhq.selenium selenium-java 4.16.1 ``` #### 测试代码 ```java // 获取 java 版本 String version = System.getProperty("java.specification.version"); // 获取系统类型 String platform = System.getProperty("os.name", ""); platform = platform.toLowerCase().contains("window") ? "win" : "linux"; // 当前程序目录 String current = System.getProperty("user.dir"); System.out.println("current:" + current); // firefox 运行参数配置 FirefoxOptions options = new FirefoxOptions(); // 无头模式 options.addArguments("--headless"); // 最大化 options.addArguments("--start-maximized"); FirefoxDriver browser = new FirefoxDriver(options); Path url = Paths.get(current, "..", "index.html"); System.out.println("url:" + url.toString()); // NOTE 要使用 file 协议 browser.get(String.format("file://%s", url.toString())); // 打印设置 PrintOptions print = new PrintOptions(); Pdf pdf = browser.print(print); // pdf base64 内容 String content = pdf.getContent(); // 解码内容 Base64.Decoder decoder = Base64.getDecoder(); byte[] buffer = decoder.decode(content); try { // 将 byte 写入文件 Path file = Paths.get(String.format("java%s_%s.pdf", version, platform)); Files.write(file, buffer); } catch (IOException e) { throw new RuntimeException(e); } finally { browser.quit(); } ``` #### 效果预览 [java11_linux.pdf](selenium\java11_linux.pdf) [java11_win.pdf](selenium\java11_win.pdf) ## python 库 ### selenium Selenium 通过使用 WebDriver 支持市场上所有主流浏览器的自动化。 Webdriver 是一个 API 和协议,它定义了一个语言中立的接口,用于控制 web 浏览器的行为。 每个浏览器都有一个特定的 WebDriver 实现,称为驱动程序。 驱动程序是负责委派给浏览器的组件,并处理与 Selenium 和浏览器之间的通信。 #### 主要依赖 ```shell pip install selenium -i http://172.16.51.188:8081/repository/pypi/simple --trusted-host 172.16.51.188 ``` #### 测试代码 ``` import os import sys import base64 from selenium import webdriver platform = 'win' if sys.platform == 'win32' else 'linux' current = os.path.abspath(os.path.dirname(__file__)) options = webdriver.FirefoxOptions() options.add_argument('--headless') options.add_argument("--start-maximized") # options.add_argument('--disable-gpu') browser = webdriver.Firefox(options=options) browser.get( 'file://{}'.format(os.path.join(current, '..', 'index.html')) ) data = browser.print_page() buffer = base64.b64decode(data) version = sys.version_info file = os.path.join( current, 'python{}{}{}_{}.pdf'.format( version.major, version.minor, version.micro, platform ) ) with open(file, 'wb') as fd: fd.write(buffer) browser.quit() ``` #### 效果预览 [python379_win.pdf](selenium\python379_win.pdf) [python3818_linux.pdf](selenium\python3818_linux.pdf) ### weasyprint WeasyPrint 能在 Linux, macOS and Windows 多平台支持,因为WeasyPrint需要依赖cairo, Pango 和 GDK-PixBuf ,所以这些软件需要独立安装 #### 环境要求 - Python ≥ 3.7.0 手动编译 3.8.18 - Pango ≥ 1.44.0 centos7 默认是 1.43 手动编译 1.45.5 版本 安装编译工具 meson ```shell # centos7 默认 0.55.1; yum install meson ``` 编译时依赖检查失败 glib2.6+ 猜测需要 **高版本的系统** - pydyf ≥ 0.6.0 - CFFI ≥ 0.6 - html5lib ≥ 1.1 - tinycss2 ≥ 1.0.0 - cssselect2 ≥ 0.1 - Pyphen ≥ 0.9.1 - Pillow ≥ 9.1.0 - fontTools ≥ 4.0.0 #### stream-9 虚拟机 ```shell # linux 环境依赖 yum install pango gcc python3-devel gcc-c++ zlib-devel libjpeg-devel openjpeg2-devel libffi-devel # windows 环境依赖 需要安装 gtk3 https://github.com/tschoonj/GTK-for-Windows-Runtime-Environment-Installer/releases/tag/2022-01-04 # 使用命令,有乱码问题 weasyprint index.html wessy.pdf 代码测试,见测试代码 ``` ### xhtml2pdf 基于 ReportLab、html5lib、PyPDF2 等 Python 模块构建的 HTML 到 PDF 转换模块。能够很好的支持 HTML5 、CSS2.1 和部分 CSS3 语法。 因为是基于 Report Lab 模块进行的开发,其对中文的支持在某些环境下会有问题。而且由于开发人员的变更,模块的功能出现了一些断层 #### 环境要求 - Python 3.8+ - openssl 1.1.1+ - 172.16.51.88 ```shell # openssl 编译 wget --no-check-certificate https://www.openssl.org/source/openssl-1.1.1w.tar.gz ./config --prefix=/usr/local/openssl-1.1.1w tar -xvf openssl-1.1.1w.tar.gz cd openssl-1.1.1w make -j4 make -j4 install # 重新编译 python 以支持 openssl 1.1.1w ./configure --prefix=/usr/local/python38 --with-openssl=/usr/local/openssl-1.1.1w/ make -j4 make -j4 install # 验证 python 关联 openssl 版本 import ssl print(ssl.OPENSSL_VERSION) # 安装 pip install xhtml2pdf -i http://172.16.51.188:8081/repository/pypi/simple --trusted-host 172.16.51.188 # 测试,有乱码问题,没有代码实现过 xhtml2pdf index.html xhtml.pdf ```