使用wkhtmltopdf實現HTML轉PDF的解決方案


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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM