几种HTML转PDF工具的对比

工具

特点

html2image

简单html转化,对CSS的支持不好

itextpdf

需要自己写模板,可以动态填充

wkhtmltopdf

转化速度快,效果好

所以此处我们重点将wkhtmltopdf的使用做一个示例,完整的项目地址在末尾的链接处

使用

springboot是现在开发的主流框架,所以此处主要是示例在springboot项目中如何集成,其他项目请自行参考使用

准备

需要准备三个基础的文件,分别如下:

simsun.ttc:字体文件

wkhtmltopdf.exe:转换工具,window系统下使用,适用于64为系统,32位系统自行去官网下载对应版本

wkhtmltox:转换工具,Linux系统下使用,同样适用于64位系统

将以上三个文件拷贝到springboot的resources根目录,具体的文件可到文章末尾的项目地址链接中获取,如下图:

image

pom依赖

org.projectlombok

lombok

1.18.12

provided

junit

junit

4.12

test

commons-fileupload

commons-fileupload

1.3.1

net.java.dev.jna

jna

5.4.0

cn.hutool

hutool-all

5.4.0

新建工具类

import com.sun.jna.Platform;

import org.apache.commons.fileupload.FileItem;

import org.apache.commons.fileupload.FileItemHeaders;

import org.apache.commons.fileupload.util.FileItemHeadersImpl;

import java.io.ByteArrayInputStream;

import java.io.ByteArrayOutputStream;

import java.io.File;

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.InputStream;

import java.io.InputStreamReader;

import java.io.LineNumberReader;

import java.io.OutputStream;

import java.io.UnsupportedEncodingException;

import java.nio.charset.StandardCharsets;

import java.nio.file.Files;

import java.nio.file.StandardCopyOption;

import java.nio.file.StandardOpenOption;

import java.util.Arrays;

import cn.hutool.core.io.FileUtil;

import lombok.ToString;

import lombok.extern.slf4j.Slf4j;

/**

* Html转PDF的工具类

* @author zhongyj <1126834403@qq.com>

* @date 2020/8/29

*/

@Slf4j

public class Html2PdfUtils {

private static final File WK_HOME_DIR = FileUtil.file(FileUtil.getUserHomePath()+"/wkHome");

private static final File WK_TMP_DIR = FileUtil.file(FileUtil.getTmpDirPath()+"/wkTemp");

private static final File SIM_SUN_FONT_DIR = Platform.isLinux() ? FileUtil.file("/usr/share/fonts/chinese/TrueType")

: FileUtil.file("C:\\Windows\\Fonts");

private static File wkTool;

private static File simSunFont;

private static boolean canUse = true;

private static boolean able = true;

static {

log.info("Tools are only available for Windows 64 and Linux 64 platforms !!!");

boolean init = forceInit();

log.info("Tools init result: {}", init);

}

/**

* 初始化

* @return 是否初始化成功

*/

public static boolean forceInit() {

long initc = 0L;

if (!WK_HOME_DIR.exists()) {

able = WK_HOME_DIR.mkdirs();

}

log.info("{},check wkHomeDir ,result:{}", ++initc, able);

if (!WK_TMP_DIR.exists()) {

able = WK_TMP_DIR.mkdirs();

}

log.info("{},check wkTmpDir ,result:{}", ++initc, able);

if (!SIM_SUN_FONT_DIR.exists()) {

able = SIM_SUN_FONT_DIR.mkdirs();

}

log.info("{},check simsunFontDir ,result:{}", ++initc, able);

InputStream wkHtmlToxAsStream = null;

InputStream simSunAsStream = null;

if (able) {

wkHtmlToxAsStream = Platform.isLinux() ? Html2PdfUtils.class.getResourceAsStream("/wkhtmltox") : Html2PdfUtils.class.getResourceAsStream("/wkhtmltopdf.exe");

simSunAsStream = Html2PdfUtils.class.getResourceAsStream("/simsun.ttc");

}

if (null == wkHtmlToxAsStream || simSunAsStream == null) {

log.error("{},load wkHtmlToxAsStream :{},load simSunAsStream:{}", ++initc, null == wkHtmlToxAsStream, simSunAsStream == null);

able = false;

}

log.info("{},load wktool and font source ,result:{}", ++initc, able);

if (able) {

File font = new File(SIM_SUN_FONT_DIR, "simsun.ttc");

File wk = new File(WK_HOME_DIR, Platform.isLinux() ? "wkhtmltox" : "wkhtmltopdf.exe");

try {

if (!font.exists()) {

assert simSunAsStream != null;

able = 1 < Files.copy(simSunAsStream, font.toPath(), StandardCopyOption.REPLACE_EXISTING);

}

log.info("{},copy font source to {},result:{}", ++initc, font.toPath(), able);

if (!wk.exists()) {

assert wkHtmlToxAsStream != null;

able = 1 < Files.copy(wkHtmlToxAsStream, wk.toPath(), StandardCopyOption.REPLACE_EXISTING);

}

log.info("{},copy wktools source to {},result:{}", ++initc, font.toPath(), able);

if (able) {

wkTool = wk;

simSunFont = font;

}

} catch (IOException e) {

e.printStackTrace();

able = false;

log.error("{}, error when copy source : {} ", ++initc, e.getMessage());

}

}

if (able) {

if (Platform.isLinux()) {

boolean canExe = exePermissionCheck();

log.info("{},check run permission,result: {} ", ++initc, canExe ? "has permission" : "no permission");

if (!canExe) {

simpleExecCommand("chmod +x " + wkTool.getPath());

if (!exePermissionCheck()) {

log.error("{},add permission failed", ++initc);

able = false;

}

}

}

}

if (able) {

able = cleanTempDir();

}

if (able) {

log.info("{},init success!", ++initc);

} else {

log.info("{},init failed!", ++initc);

canUse = false;

}

return able;

}

public String getSimsunPath() {

log.info("world path:" + simSunFont.getPath());

return simSunFont.getPath();

}

public static Html2PdfUtils build() {

return new Html2PdfUtils();

}

private static boolean exePermissionCheck() {

String permissionLog = simpleExecCommand("ls -l " + wkTool.getPath());

return null != permissionLog && permissionLog.length() >= 10 && 120 == permissionLog.charAt(9);

}

private static boolean cleanTempDir() {

if (WK_TMP_DIR.exists()) {

canUse = deleteFiles(WK_TMP_DIR) ? WK_TMP_DIR.mkdirs() : canUse;

log.info("cleanTempDir,result:{} ", canUse);

} else {

canUse = WK_TMP_DIR.mkdirs();

}

return canUse;

}

public synchronized FileItem convertPdfFromText(String text, String fileName) {

cleanTempDir();

if (!canUse) {

log.info("tools crash,can invoke forceInit() method see reason !!!");

return null;

}

File html = new File(WK_TMP_DIR, fileName + ".html");

File pdf = new File(WK_TMP_DIR, fileName + ".pdf");

// 将html字符串写入到临时的html文件

FileUtil.writeUtf8String(text, html);

if (html.exists() && html.isFile()) {

log.info("exec html to pdf ,wktoolPath=>{}", wkTool.getPath());

simpleExecCommand(wkTool.getPath() + " " + html.getPath() + " " + pdf.getPath());

}

if (pdf.exists() && pdf.isFile()) {

try (FileInputStream fileInputStream = new FileInputStream(pdf); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {

byte[] buffer = new byte[2014];

while (fileInputStream.read(buffer) != -1) {

byteArrayOutputStream.write(buffer);

}

byteArrayOutputStream.flush();

byte[] data = byteArrayOutputStream.toByteArray();

if (data.length > 0) {

log.info("html to pdf success ");

SimplePdfFileItem file = new SimplePdfFileItem(pdf, data, Files.probeContentType(pdf.toPath()), "file");

log.info("pdf size :{}", file.getSize());

return file;

}

} catch (IOException e) {

log.error("html to pdf failed");

e.printStackTrace();

return null;

}

}

log.info("html to pdf failed,no data");

return null;

}

private static boolean deleteFiles(File file) {

if (!file.exists()) {

log.info("del the file:{},is not exists", file.getPath());

return false;

}

if (file.isFile()) {

return file.delete();

}

File[] subFiles = file.listFiles();

if (null != subFiles && subFiles.length > 0) {

Arrays.asList(subFiles).forEach(Html2PdfUtils::deleteFiles);

}

return file.delete();

}

private static String simpleExecCommand(String cmd) {

try {

String[] linux = {"/bin/sh", "-c", cmd};

String[] windows = {"cmd", "/c", cmd};

String[] cmdA = Platform.isLinux() ? linux : windows;

Process process = Runtime.getRuntime().exec(cmdA);

LineNumberReader br = new LineNumberReader(new InputStreamReader(process.getInputStream()));

StringBuilder sb = new StringBuilder();

String line;

while ((line = br.readLine()) != null) {

log.info(line);

sb.append(line).append("\n");

}

return sb.toString();

} catch (Exception e) {

e.printStackTrace();

return null;

}

}

@ToString

static class SimplePdfFileItem implements FileItem {

private static final long serialVersionUID = 2237570099615271025L;

public static final String DEFAULT_CHARSET = "ISO-8859-1";

private String fieldName;

private final String fileName;

private boolean isFormField;

private final byte[] cachedContent;

private final String contentType;

private final File dFosFile;

private FileItemHeaders headers;

public SimplePdfFileItem(File dFosFile, byte[] cachedContent, String contentType, String fieldName) {

this.fieldName = fieldName;

this.fileName = dFosFile.getName();

this.isFormField = false;

this.cachedContent = null == cachedContent || cachedContent.length < 1 ? new byte[0] : cachedContent;

this.contentType = contentType;

this.dFosFile = dFosFile;

this.headers = new FileItemHeadersImpl();

}

public SimplePdfFileItem(String fieldName, String fileName, boolean isFormField, byte[] cachedContent

, String contentType, File dFosFile, FileItemHeaders headers) {

this.fieldName = fieldName;

this.fileName = fileName;

this.isFormField = isFormField;

this.cachedContent = cachedContent;

this.contentType = contentType;

this.dFosFile = dFosFile;

this.headers = headers;

}

@Override

public InputStream getInputStream() throws IOException {

if (null == this.dFosFile) {

return new ByteArrayInputStream(this.cachedContent);

}

return new FileInputStream(dFosFile);

}

@Override

public String getContentType() {

return this.contentType;

}

@Override

public String getName() {

return this.fileName;

}

@Override

public boolean isInMemory() {

return this.cachedContent.length > 0;

}

@Override

public long getSize() {

return this.cachedContent.length;

}

@Override

public byte[] get() {

return this.cachedContent;

}

@Override

public String getString(String s) throws UnsupportedEncodingException {

return getString();

}

@Override

public String getString() {

return new String(cachedContent, StandardCharsets.UTF_8);

}

@Override

public void write(File file) throws Exception {

Files.write(file.toPath(), cachedContent, StandardOpenOption.CREATE);

}

@Override

public void delete() {

boolean delete = dFosFile.delete();

}

@Override

public String getFieldName() {

return this.fieldName;

}

@Override

public void setFieldName(String s) {

this.fieldName = s;

}

@Override

public boolean isFormField() {

return this.isFormField;

}

@Override

public void setFormField(boolean b) {

this.isFormField = b;

}

@Override

public OutputStream getOutputStream() throws IOException {

if (null == this.dFosFile) {

return new ByteArrayOutputStream(1024);

}

return new FileOutputStream(this.dFosFile);

}

@Override

public FileItemHeaders getHeaders() {

return this.headers;

}

@Override

public void setHeaders(FileItemHeaders fileItemHeaders) {

this.headers = fileItemHeaders;

}

}

}

转换

@Test

public void down() throws UnsupportedEncodingException {

String html = FileUtil.readUtf8String("E:\\入院记录.html");

FileItem sx = Html2PdfUtils.build().convertPdfFromText(html, "sx");

log.info(sx.toString());

byte[] bytes = sx.get();

FileUtil.writeBytes(bytes,new File("E:\\入院记录-1.pdf"));

}

c html 转 pdf,HTML 转 PDF相关推荐

  1. 如何编辑PDF文件,PDF编辑器如何使用

    如何编辑PDF呢?其实大多数人都不知道该如何下手,部分人会选择将PDF文件转换成Word然后进行编辑,其实这种方法比较麻烦,大大拉低了我们的工作效率.如果想要提高工作效率更加快速的编辑PDF文件,就可 ...

  2. python pdf报告_Python实现html转换为pdf报告(生成pdf报告)功能示例

    本文实例讲述了Python实现html转换为pdf报告(生成pdf报告)功能.分享给大家供大家参考,具体如下: 1.先说下html转换为pdf:其实支持直接生成,有三个函数pdfkit.f 安装pyt ...

  3. java pdf 书签_Java PDF书签——添加、编辑、删除、读取书签

    本文介绍通过Java程序来操作PDF书签,根据对书签的不同操作要求,分以下情况来介绍: 1. 添加书签(包括添加一级书签.多级子书签) 3. 删除书签(包括删除所有书签.删除子书签等) 4. 读取书签 ...

  4. java 其他文件转pdf_java 其他文件转成pdf java生成pdf

    java生成pdf需要用到的包pd4ml.jar 下载地址:http://download.csdn.net/detail/yanning1314/7124741 package com.cular. ...

  5. pdf各种处理 PDF 的实用代码:PyPDF2、PDFMiner、pdfplumber

    你不懂得安排自己的人生,会有很多人帮你安排,他们需要你做的事. PDF文件我们经常用,尤其是这两个场景: 下载参考资料,如各类报告.文档 分享只读资料,方便传播同时保留源文件 场景和模块 所以,对于P ...

  6. linux 分割pdf,PDFBox分割PDF文档

    在前一章中,我们已经看到了如何将JavaScript添加到PDF文档. 现在来学习如何将给定的PDF文档分成多个文档. 分割PDF文档中的页面 可以使用Splitter类将给定的PDF文档分割为多个P ...

  7. bmp转换tiff c++代码_如何用Java语言将图像转换为PDF?Spire.PDF for Java轻松搞定

    对于开发人员,在日常工作中经常也会处理许多文档格式,将图像转换为PDF也是常有的事.那么,在Java语言开发中,如何将图像转换为PDF呢? Spire.PDF for Java支持将多种图像格式(例如 ...

  8. python数据生成pdf,Python生成pdf文件的方法

    摘要:这篇Python开发技术栏目下的"Python生成pdf文件的方法",介绍的技术点是"python生成pdf文件.python生成pdf.生成pdf文件.Pytho ...

  9. PDF Expert使用教程:如何在Mac上使用PDF Expert编辑PDF

    PDF Expert 是专为 Mac 而设计的 PDF 编辑应用,易用,强大.其界面简洁.人性,恰似它的 iOS 版本.PDF 即按即开,无论是小得像邮件附件,还是多得像 2000 多页的报告. 视频 ...

  10. PHP利用FPDI 制作PDF 档案 (php合并pdf, php签名pdf)

    昨天研究如何在既有的PDF 档案上放入中文字,虽然找到支援中文的FPDF ,但是有些Unicode 字集我实在试不出如何显示(如:堃) . 我的同事建议我用图形来解决看看,以下就是我的实验过程(我用的 ...

最新文章

  1. 关于全国大学生智能汽车竞赛有关问题的建议
  2. 多个旅游网站被挂马 五一假期外出旅游应小心
  3. 前端学习(2755):配置tabber其他属性
  4. c++基础学习(08)--(继承、重载、多态、虚函数)
  5. Linux查看网卡带宽
  6. linux ssh非交互脚本,sshpass-Linux命令之非交互SSH
  7. Android底部菜单栏的两种实现方式 附完整源码
  8. 如何把图纸转换为t3格式_怎么把图纸转换成t3格式
  9. ①ESP8266-wifi模块使用方法
  10. Windows查找文件内容
  11. Aho-Corasick 算法
  12. 阿里巴巴校招实习面试
  13. 对闰年和平年计算均值
  14. VmWare16 安装图解
  15. Chino with Triangle ( 西工大程序设计创新实践基地春季选拔赛)树形dp
  16. 不看绝对血亏!mysql下载安装教程win10
  17. 华为云与阿里云那个好
  18. 为什么CAD输入文字时文字特别大但输入后就消失了?
  19. DDC EDID 介绍
  20. redis数据结构--hyperloglog

热门文章

  1. [CF592D]Super M
  2. (转载)Xcode 4.1/4.2/4.3 免证书(iDP)开发+真机调试+生成IPA全攻略
  3. 比特率 波特率 带宽与容量
  4. Java多线程实现-线程池
  5. 练习题|python常用模块
  6. C和指针 第十六章 标准函数库 本地跳转setjmp.h
  7. 关于代码整理重构小记
  8. 人人都能学会的python编程教程4:关系运算符与循环
  9. JavaWeb01-HTML篇笔记(一)
  10. 《树莓派Python编程入门与实战(第2版)》——2.2 使用Raspbian命令行