Itext生成pdf文件,itext+Freemarker生成pdf,(中文空白解決)


來源:https://my.oschina.net/lujianing/blog/894365

 

1.背景

在某些業務場景中,需要提供相關的電子憑證,比如網銀/支付寶中轉賬的電子回單,簽約的電子合同等。方便用戶查看,下載,打印。目前常用的解決方案是,把相關數據信息,生成對應的pdf文件返回給用戶。

本文源碼:http://git.oschina.net/lujianing/java_pdf_demo

2.iText

iText是著名的開放源碼的站點sourceforge一個項目,是用於生成PDF文檔的一個java類庫。通過iText不僅可以生成PDF或rtf的文檔,而且可以將XML、Html文件轉化為PDF文件。 

iText 官網:http://itextpdf.com/

iText 開發文檔: http://developers.itextpdf.com/developers-home

iText目前有兩套版本iText5和iText7。iText5應該是網上用的比較多的一個版本。iText5因為是很多開發者參與貢獻代碼,因此在一些規范和設計上存在不合理的地方。iText7是后來官方針對iText5的重構,兩個版本差別還是挺大的。不過在實際使用中,一般用到的都比較簡單,所以不用特別拘泥於使用哪個版本。比如我們在http://mvnrepository.com/中搜索iText,出來的都是iText5的依賴。

來個最簡單的例子:

添加依賴:

<!-- https://mvnrepository.com/artifact/com.itextpdf/itextpdf -->
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itextpdf</artifactId>
    <version>5.5.11</version>
</dependency>

  

 

測試代碼:JavaToPdf

package com.lujianing.test;
 
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.pdf.PdfWriter;
 
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
 
/**
 * Created by lujianing on 2017/5/7.
 */
public class JavaToPdf {
 
    private static final String DEST = "target/HelloWorld.pdf";
 
 
    public static void main(String[] args) throws FileNotFoundException, DocumentException {
        Document document = new Document();
        PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(DEST));
        document.open();
        document.add(new Paragraph("hello world"));
        document.close();
        writer.close();
    }
}

  

 

運行結果:

 

 

3.iText-中文支持

iText默認是不支持中文的,因此需要添加對應的中文字體,比如黑體simhei.ttf

可參考文檔:http://developers.itextpdf.com/examples/font-examples/using-fonts#1227-tengwarquenya1.java

測試代碼:JavaToPdfCN

package com.lujianing.test;
 
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Font;
import com.itextpdf.text.FontFactory;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfWriter;
 
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
 
/**
 * Created by lujianing on 2017/5/7.
 */
public class JavaToPdfCN {
 
    private static final String DEST = "target/HelloWorld_CN.pdf";
    private static final String FONT = "simhei.ttf";
 
 
    public static void main(String[] args) throws FileNotFoundException, DocumentException {
        Document document = new Document();
        PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(DEST));
        document.open();
        Font f1 = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
        document.add(new Paragraph("hello world,我是魯家寧", f1));
        document.close();
        writer.close();
    }
}

  

輸出結果:

 

 

 

4.iText-Html渲染

在一些比較復雜的pdf布局中,我們可以通過html去生成pdf

可參考文檔:http://developers.itextpdf.com/examples/xml-worker-itext5/xml-worker-examples

添加依賴:

 

<!-- https://mvnrepository.com/artifact/com.itextpdf.tool/xmlworker -->
<dependency>
    <groupId>com.itextpdf.tool</groupId>
    <artifactId>xmlworker</artifactId>
    <version>5.5.11</version>
</dependency> 

  

添加模板:template.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>Title</title>
    <style>
        body{
            font-family:SimHei;
        }
        .red{
            color: red;
        }
    </style>
</head>
<body>
<div class="red">
    你好,魯家寧
</div>
</body>
</html>

  

測試代碼:JavaToPdfHtml

package com.lujianing.test;
 
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.tool.xml.XMLWorkerFontProvider;
import com.itextpdf.tool.xml.XMLWorkerHelper;
import com.lujianing.test.util.PathUtil;
 
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
 
/**
 * Created by lujianing on 2017/5/7.
 */
public class JavaToPdfHtml {
 
    private static final String DEST = "target/HelloWorld_CN_HTML.pdf";
    private static final String HTML = PathUtil.getCurrentPath()+"/template.html";
    private static final String FONT = "simhei.ttf";
 
 
    public static void main(String[] args) throws IOException, DocumentException {
        // step 1
        Document document = new Document();
        // step 2
        PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(DEST));
        // step 3
        document.open();
        // step 4
        XMLWorkerFontProvider fontImp = new XMLWorkerFontProvider(XMLWorkerFontProvider.DONTLOOKFORFONTS);
        fontImp.register(FONT);
        XMLWorkerHelper.getInstance().parseXHtml(writer, document,
                new FileInputStream(HTML), null, Charset.forName("UTF-8"), fontImp);
        // step 5
        document.close();
    }
}

  

輸出結果:

 

需要注意:

1.html中必須使用標准的語法,標簽一定需要閉合

2.html中如果有中文,需要在樣式中添加對應字體的樣式

 

5.iText-Html-Freemarker渲染

在實際使用中,html內容都是動態渲染的,因此我們需要加入模板引擎支持,可以使用FreeMarker/Velocity,這里使用FreeMarker舉例

添加FreeMarke依賴:

<!-- https://mvnrepository.com/artifact/org.freemarker/freemarker -->
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.19</version>
</dependency>

  

添加模板:template_freemarker.html

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>Title</title>
    <style>
        body{
            font-family:SimHei;
        }
        .blue{
            color: blue;
        }
    </style>
</head>
<body>
<div class="blue">
    你好,${name}
</div>
</body>
</html>

  

測試代碼:JavaToPdfHtmlFreeMarker

注意:setDirectoryForTemplateLoading(new File(PathUtil.getCurrentPath())) 指定的是目錄,所以如果你的服務器是linux/centos的話,需要指定為,例如:/root/pdfmodel/freemarker.html這樣指定。

如果你的項目生成的是jar包,就不能這樣指定,應該改為:setClassForTemplateLoading(當前文件.class, "/pdfmodel"); 其中pdfmodel已經被編譯成jar里面的東西了

 

package com.lujianing.test;
 
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.tool.xml.XMLWorkerFontProvider;
import com.itextpdf.tool.xml.XMLWorkerHelper;
import com.lujianing.test.util.PathUtil;
 
import freemarker.template.Configuration;
import freemarker.template.Template;
 
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
 
/**
 * Created by lujianing on 2017/5/7.
 */
public class JavaToPdfHtmlFreeMarker {
 
    private static final String DEST = "target/HelloWorld_CN_HTML_FREEMARKER.pdf";
    private static final String HTML = "template_freemarker.html";
    private static final String FONT = "simhei.ttf";
 
    private static Configuration freemarkerCfg = null;
 
    static {
        freemarkerCfg =new Configuration();
        //freemarker的模板目錄
        try {
            freemarkerCfg.setDirectoryForTemplateLoading(new File(PathUtil.getCurrentPath()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
 
    public static void main(String[] args) throws IOException, DocumentException {
        Map<String,Object> data = new HashMap();
        data.put("name","魯家寧");
        String content = JavaToPdfHtmlFreeMarker.freeMarkerRender(data,HTML);
        JavaToPdfHtmlFreeMarker.createPdf(content,DEST);
    }
 
 
    public static void createPdf(String content,String dest) throws IOException, DocumentException {
        // step 1
        Document document = new Document();
        // step 2
        PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(dest));
        // step 3
        document.open();
        // step 4
        XMLWorkerFontProvider fontImp = new XMLWorkerFontProvider(XMLWorkerFontProvider.DONTLOOKFORFONTS);
        fontImp.register(FONT);
        XMLWorkerHelper.getInstance().parseXHtml(writer, document,
                new ByteArrayInputStream(content.getBytes()), null, Charset.forName("UTF-8"), fontImp);
        // step 5
        document.close();
 
    }
 
    /**
     * freemarker渲染html
     */
    public static String freeMarkerRender(Map<String, Object> data, String htmlTmp) {
        Writer out = new StringWriter();
        try {
            // 獲取模板,並設置編碼方式
            Template template = freemarkerCfg.getTemplate(htmlTmp);
            template.setEncoding("UTF-8");
            // 合並數據模型與模板
            template.process(data, out); //將合並后的數據和模板寫入到流中,這里使用的字符流
            out.flush();
            return out.toString();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        return null;
    }
}

  

輸出結果:

目前為止,我們已經實現了iText通過Html模板生成Pdf的功能,但是實際應用中,我們發現iText並不能對高級的CSS樣式進行解析,比如CSS中的position屬性等,因此我們要引入新的組件

6.Flying Saucer-CSS高級特性支持

Flying Saucer is a pure-Java library for rendering arbitrary well-formed XML (or XHTML) using CSS 2.1 for layout and formatting, output to Swing panels, PDF, and images.

Flying Saucer是基於iText的,支持對CSS高級特性的解析。

添加依賴:

<!-- https://mvnrepository.com/artifact/org.xhtmlrenderer/flying-saucer-pdf -->
<dependency>
    <groupId>org.xhtmlrenderer</groupId>
    <artifactId>flying-saucer-pdf</artifactId>
    <version>9.1.5</version>
</dependency>
 
<!-- https://mvnrepository.com/artifact/org.xhtmlrenderer/flying-saucer-pdf-itext5 -->
<dependency>
    <groupId>org.xhtmlrenderer</groupId>
    <artifactId>flying-saucer-pdf-itext5</artifactId>
    <version>9.1.5</version>
</dependency>

  

 

添加模板:template_freemarker_fs.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>Title</title>
    <style>
        body{
            font-family:SimHei;
        }
        .color{
            color: green;
        }
        .pos{
            position:absolute;
            left:200px;
            top:5px;
            width: 200px;
            font-size: 10px;
        }
    </style>
</head>
<body>
<img src="logo.png" width="600px"/>
<div class="color pos">
    你好,${name}
</div>
</body>
</html>

  

測試代碼:JavaToPdfHtmlFreeMarker

package com.lujianing.test.flyingsaucer;
 
 
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.BaseFont;
import com.lujianing.test.util.PathUtil;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
 
/**
 * Created by lujianing on 2017/5/7.
 */
public class JavaToPdfHtmlFreeMarker {
 
    private static final String DEST = "target/HelloWorld_CN_HTML_FREEMARKER_FS.pdf";
    private static final String HTML = "template_freemarker_fs.html";
    private static final String FONT = "simhei.ttf";
    private static final String LOGO_PATH = "file://"+PathUtil.getCurrentPath()+"/logo.png";
 
    private static Configuration freemarkerCfg = null;
 
    static {
        freemarkerCfg =new Configuration();
        //freemarker的模板目錄
        try {
            freemarkerCfg.setDirectoryForTemplateLoading(new File(PathUtil.getCurrentPath()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    public static void main(String[] args) throws IOException, DocumentException, com.lowagie.text.DocumentException {
        Map<String,Object> data = new HashMap();
        data.put("name","魯家寧");
        String content = JavaToPdfHtmlFreeMarker.freeMarkerRender(data,HTML);
        JavaToPdfHtmlFreeMarker.createPdf(content,DEST);
    }
 
    /**
     * freemarker渲染html
     */
    public static String freeMarkerRender(Map<String, Object> data, String htmlTmp) {
        Writer out = new StringWriter();
        try {
            // 獲取模板,並設置編碼方式
            Template template = freemarkerCfg.getTemplate(htmlTmp);
            template.setEncoding("UTF-8");
            // 合並數據模型與模板
            template.process(data, out); //將合並后的數據和模板寫入到流中,這里使用的字符流
            out.flush();
            return out.toString();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        return null;
    }
 
    public static void createPdf(String content,String dest) throws IOException, DocumentException, com.lowagie.text.DocumentException {
        ITextRenderer render = new ITextRenderer();
        ITextFontResolver fontResolver = render.getFontResolver();
        fontResolver.addFont(FONT, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
        // 解析html生成pdf
        render.setDocumentFromString(content);
        //解決圖片相對路徑的問題
        render.getSharedContext().setBaseURL(LOGO_PATH);
        render.layout();
        render.createPDF(new FileOutputStream(dest));
    }
}

  

輸出結果:

在某些場景下,html中的靜態資源是在本地,我們可以使用render.getSharedContext().setBaseURL()加載文件資源,注意資源URL需要使用文件協議 "file://"。

對於生成的pdf頁面大小,可以用css的@page屬性設置。

 

7.PDF轉圖片

在某些場景中,我們可能只需要返回圖片格式的電子憑證,我們可以使用Jpedal組件,把pdf轉成圖片

添加依賴:

<!-- https://mvnrepository.com/artifact/org.jpedal/jpedal-lgpl -->
<dependency>
    <groupId>org.jpedal</groupId>
    <artifactId>jpedal-lgpl</artifactId>
    <version>4.74b27</version>
</dependency>

  

測試代碼:JavaToPdfImgHtmlFreeMarker

package com.lujianing.test.flyingsaucer;
 
 
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.BaseFont;
import com.lujianing.test.util.PathUtil;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.jpedal.PdfDecoder;
import org.jpedal.exception.PdfException;
import org.jpedal.fonts.FontMappings;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
 
import javax.imageio.ImageIO;
 
/**
 * Created by lujianing on 2017/5/7.
 */
public class JavaToPdfImgHtmlFreeMarker {
 
    private static final String DEST = "target/HelloWorld_CN_HTML_FREEMARKER_FS_IMG.png";
    private static final String HTML = "template_freemarker_fs.html";
    private static final String FONT = "simhei.ttf";
    private static final String LOGO_PATH = "file://"+PathUtil.getCurrentPath()+"/logo.png";
    private static final String IMG_EXT = "png";
 
    private static Configuration freemarkerCfg = null;
 
    static {
        freemarkerCfg =new Configuration();
        //freemarker的模板目錄
        try {
            freemarkerCfg.setDirectoryForTemplateLoading(new File(PathUtil.getCurrentPath()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
 
    public static void main(String[] args) throws IOException, DocumentException, com.lowagie.text.DocumentException {
        Map<String,Object> data = new HashMap();
        data.put("name","魯家寧");
 
        String content = JavaToPdfImgHtmlFreeMarker.freeMarkerRender(data,HTML);
        ByteArrayOutputStream pdfStream = JavaToPdfImgHtmlFreeMarker.createPdf(content);
        ByteArrayOutputStream imgSteam = JavaToPdfImgHtmlFreeMarker.pdfToImg(pdfStream.toByteArray(),2,1,IMG_EXT);
 
        FileOutputStream fileStream = new FileOutputStream(new File(DEST));
        fileStream.write(imgSteam.toByteArray());
        fileStream.close();
 
    }
 
 
    /**
     * freemarker渲染html
     */
    public static String freeMarkerRender(Map<String, Object> data, String htmlTmp) {
        Writer out = new StringWriter();
        try {
            // 獲取模板,並設置編碼方式
            Template template = freemarkerCfg.getTemplate(htmlTmp);
            template.setEncoding("UTF-8");
            // 合並數據模型與模板
            template.process(data, out); //將合並后的數據和模板寫入到流中,這里使用的字符流
            out.flush();
            return out.toString();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        return null;
    }
 
    /**
     * 根據模板生成pdf文件流
     */
    public static ByteArrayOutputStream createPdf(String content) {
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        ITextRenderer render = new ITextRenderer();
        ITextFontResolver fontResolver = render.getFontResolver();
        try {
            fontResolver.addFont(FONT, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
        } catch (com.lowagie.text.DocumentException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 解析html生成pdf
        render.setDocumentFromString(content);
        //解決圖片相對路徑的問題
        render.getSharedContext().setBaseURL(LOGO_PATH);
        render.layout();
        try {
            render.createPDF(outStream);
            return outStream;
        } catch (com.lowagie.text.DocumentException e) {
            e.printStackTrace();
        } finally {
            try {
                outStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
 
    /**
     * 根據pdf二進制文件 生成圖片文件
     *
     * @param bytes   pdf二進制
     * @param scaling 清晰度
     * @param pageNum 頁數
     */
    public static ByteArrayOutputStream pdfToImg(byte[] bytes, float scaling, int pageNum,String formatName) {
        //推薦的方法打開PdfDecoder
        PdfDecoder pdfDecoder = new PdfDecoder(true);
        FontMappings.setFontReplacements();
        //修改圖片的清晰度
        pdfDecoder.scaling = scaling;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try {
            //打開pdf文件,生成PdfDecoder對象
            pdfDecoder.openPdfArray(bytes); //bytes is byte[] array with PDF
            //獲取第pageNum頁的pdf
            BufferedImage img = pdfDecoder.getPageAsImage(pageNum);
 
            ImageIO.write(img, formatName, out);
        } catch (PdfException e) {
            e.printStackTrace();
        } catch (IOException e){
            e.printStackTrace();
        }
 
        return out;
    }
}

  

 

輸出結果:

Jpedal支持將指定頁Pdf生成圖片,pdfDecoder.scaling設置圖片的分辨率(不同分辨率下文件大小不同) ,支持多種圖片格式,具體更多可自行研究

8.總結

對於電子憑證的技術方案,總結如下:

1.html模板+model數據,通過freemarker進行渲染,便於維護和修改

2.渲染后的html流,可通過Flying Saucer組件生成pdf文件流,或者生成pdf后再轉成jpg文件流

3.在Web項目中,對應的文件流,可以通過ContentType設置,在線查看/下載,不需通過附件服務

 

9.純前端解決方案

還有一種解決方案是使用PhantomJS

git地址: https://github.com/ariya/phantomjs 

PhantomJS 是一個基於 WebKit 的服務器端 JavaScript API。它全面支持web而不需瀏覽器支持,其快速,原生支持各種Web標准: DOM 處理, CSS 選擇器, JSON, Canvas, 和 SVG。 PhantomJS 可以用於 頁面自動化 , 網絡監測 , 網頁截屏 ,以及 無界面測試 等。

具體方法可自行查詢。

 

 

 

10.中文支持(中文空白解決)

首先需要添加中文字庫,也就是你的頁面中用到的所有字體:

ITextFontResolver fontResolver = renderer.getFontResolver(); 
        fontResolver.addFont("C:/Windows/Fonts/simsun.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
        fontResolver.addFont("C:/Windows/Fonts/simhei.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
        fontResolver.addFont("C:/Windows/Fonts/simkai.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);

  

注意:頁面中字體不能使用中文,需要使用英文名稱,而且是大小寫敏感的!例如宋體的英文名稱是 SimSun(注意不是simsun!,首字母都是大寫的)

      錯誤寫法:font-family:宋體 或者  font-family:simsun

      正確寫法:font-family:SimSun 或者 font-family:SimHei

如果生成的pdf中文不顯示或者亂碼,請確認如下信息:

  • 確保頁面中所有內容都指定了字體,最好能指定 body {font-family:....},以防止漏網之魚。

  • 確保上述所有字體均通過addFont加入,字體名稱錯誤或者字體不存在會拋出異常,很方便,但是沒導入的字體不會有任何提示。

  • 確保字體名稱正確,不使用中文,大小寫正確。

  • 確保html標簽都正確,簡單的方法是所有內容都去掉,隨便寫幾個中文看看能否正常生成,如果可以,在認真檢查html標簽,否則再次檢查上述幾條。

還有就是中文換行的問題了,帶有中文而且文字較多存在換行情況時,需要給table加入樣式:

table-layout:fixed,然后表格中的td使用%還指定td的寬度。

 https://www.cnblogs.com/reese-blogs/p/5546806.html


免責聲明!

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



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