HTML 转 PDF

几种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 <[email protected]>
* @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"));
}

项目示例地址:https://gitee.com/dimples9527/html2pdf

你可能感兴趣的:(HTML 转 PDF)