Java iText+FreeMarker生成PDF(HTML轉PDF)

1.背景

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

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

2.iText

iText是著名的開放源碼的站點sourceforge一個項目,是用於生成PDF文檔的一個java類庫。經過iText不只能夠生成PDF或rtf的文檔,並且能夠將XML、Html文件轉化爲PDF文件。 前端

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

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

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

來個最簡單的例子:web

添加依賴:apache

<!-- 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

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屬性等,所以咱們要引入新的組件

若中文變量仍是不顯示,則在pom.xml裏添加以下:

<build>
  <resources>
    <resource>
      <directory>src/main/resources</directory>
      <filtering>true</filtering>
    </resource>
  </resources>
  <!--增長的配置,過濾ttf文件的匹配-->
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-resources-plugin</artifactId>
      <version>2.7</version>
      <configuration>
      <encoding>UTF-8</encoding>
        <nonFilteredFileExtensions>
        <nonFilteredFileExtension>ttf</nonFilteredFileExtension>
        </nonFilteredFileExtensions>
      </configuration>
    </plugin>
  </plugins>
</build>

備註:工具類的方法 PathUtil.getCurrentPath() = "src/main/resources/",(這個對新手有用)

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 能夠用於 頁面自動化 , 網絡監測 , 網頁截屏 ,以及 無界面測試 等。

具體方法可自行查詢。

 

以上內容轉載自:https://my.oschina.net/lujianing/blog/894365,感謝蛙牛的分享,紅色部分是本身作的時候踩到的坑,因此補上

相關文章
相關標籤/搜索