最近,项目需要将HTML页面转换为PDF文件,所以就研究了下HTML转PDF的解决方案,发现网上比较流行的解决方案有3种:
(1)iText
(2)Flying Saucer
(3)wkhtmltopdf
还有一些收费的,我就没测试过了,前两种对HTML的要求过于严格,而且即使你写标准的HTML(当然这都是理想情况下),他也未必可以完美解析,所以我就选择了(3),wkhtmltopdf基于WebKit渲染引擎将HTML内容转换为HTML页面,之后再转换成PDF,所以其转换后的PDF文件的显示效果可以和HTML页面基本保持一致,是一个相当完美的解决方案,美中不足的是他需要你安装插件,并不能像前两种解决方案那样以jar包的形式嵌入到项目中。
因为在使用的过程中,也发现了一些问题,所以就把自己的解决方案写出来,供需要的朋友参考。
CustomWKHtmlToPdfUtil.java是自定义的一个操作wkhtmltopdf的工具类:
package us.kagome.wkhtmltopdf; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; import java.util.UUID; /** * wkhtmltopdf工具类 * * 约定: * 1. 插件安装位置,在Windows系统中将插件安装在D盘根目录下(D:/), 在Linux系统中将插件安装在opt目录下(/opt) * * 注意: * 1. wkhtmltopdf的Linux版本中,解压后,默认的文件名为"wkhtmltox",为了统一起见,一律将解压后的文件名,重命名为"wkhtmltopdf"(命令:mv wkhtmltox wkhtmltopdf) * * Created by kagome on 2016/7/26. */ public class CustomWKHtmlToPdfUtil { // 临时目录的路径 public static final String TEMP_DIR_PATH = CustomWKHtmlToPdfUtil.class.getResource("/").getPath().substring(1) + "temp/"; static { // 生成临时目录 new File(TEMP_DIR_PATH).mkdirs(); } public static void main(String[] args) throws Exception { String htmlStr = "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"></meta><title>HTML转PDF</title></head><body><h1>Hello 世界!</h1></body></html>"; htmlToPdf(strToHtmlFile(htmlStr), TEMP_DIR_PATH + UUID.randomUUID().toString() + ".pdf"); } /** * 将HTML文件内容输出为PDF文件 * * @param htmlFilePath HTML文件路径 * @param pdfFilePath PDF文件路径 */ public static void htmlToPdf(String htmlFilePath, String pdfFilePath) { try { Process process = Runtime.getRuntime().exec(getCommand(htmlFilePath, pdfFilePath)); new Thread(new ClearBufferThread(process.getInputStream())).start(); new Thread(new ClearBufferThread(process.getErrorStream())).start(); process.waitFor(); } catch (Exception e) { throw new RuntimeException(e); } } /** * 将HTML字符串转换为HTML文件 * * @param htmlStr HTML字符串 * @return HTML文件的绝对路径 */ public static String strToHtmlFile(String htmlStr) { OutputStream outputStream = null; try { String htmlFilePath = TEMP_DIR_PATH + UUID.randomUUID().toString() + ".html"; outputStream = new FileOutputStream(htmlFilePath); outputStream.write(htmlStr.getBytes("UTF-8")); return htmlFilePath; } catch (Exception e) { throw new RuntimeException(e); } finally { try { if (outputStream != null) { outputStream.close(); outputStream = null; } } catch (Exception e) { throw new RuntimeException(e); } } } /** * 获得HTML转PDF的命令语句 * * @param htmlFilePath HTML文件路径 * @param pdfFilePath PDF文件路径 * @return HTML转PDF的命令语句 */ private static String getCommand(String htmlFilePath, String pdfFilePath) { String osName = System.getProperty("os.name"); // Windows if (osName.startsWith("Windows")) { return String.format("D:/wkhtmltopdf/bin/wkhtmltopdf.exe %s %s", htmlFilePath, pdfFilePath); } // Linux else { return String.format("/opt/wkhtmltopdf/bin/wkhtmltopdf %s %s", htmlFilePath, pdfFilePath); } } }
ClearBufferThread.java用于清空Process的输入流的缓存的线程类:
package us.kagome.wkhtmltopdf; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; /** * 清理输入流缓存的线程 * Created by kagome on 2016/8/9. */ public class ClearBufferThread implements Runnable { private InputStream inputStream; public ClearBufferThread(InputStream inputStream){ this.inputStream = inputStream; } public void run() { try{ BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)); while(br.readLine() != null); } catch(Exception e){ throw new RuntimeException(e); } } }
以上是解决方案的完整代码,接下来说下自己遇到的问题吧!
(1)在jdk1.6环境下,下面代码会造成阻塞:
process.waitFor();
导致程序不能正常执行,jdk1.7就没有这个问题了,去网上找了好久,发现是Process的输入流和错误流缓存不足导致的,所以就增加了ClearBufferThread类用于清空输入流缓存。