最近,項目需要將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類用於清空輸入流緩存。
