天天看點

介紹一個PDF的生成方案(轉)

在Java世界,要想生成PDF,方案不少。最近一直在和這個東西打交道,是以簡單做一個小結吧。

在此之前,先來勾畫一下我心中比較 理想的一個解決方案。在企業應用中,碰到的比較多的PDF的需求,可能是針對某個比較典型的具備文檔特性的内 容,導出成為PDF進行存檔。由于我們現在往往使用一些開源架構,諸如ssh來建構我們的應用,是以我們相對熟悉的方案是針對具體的業務邏輯設計實體,使 用開源架構來實作我們的業務邏輯。而PDF的導出,最好不要破壞現有的程式架構,甚至能複用我們業務邏輯層的代碼。 因 為如果把PDF作為一種特殊的表現形式的話,實際上它有點類似模闆。最佳的情況, 是我們能夠通過編寫某種模闆,把PDF的大概樣子确定下來,然後把資料和模闆做一次整合,得到最後的結果

帶着這個目标,開始在網上搜尋解決方案。也找到了一些方案,下面簡單小結一下:

Jasper Report

看到的市面上采用的最多的方案,是Jasper Report。相關的文檔也很多,不過很雜,需要完全掌握,我認為還是有些坡度和時間的。這個時間和坡度我認為主要來自于對iReport這個IDE的反 複嘗試,對裡面的每個屬性的摸索。

Jasper Report的設計思路,本身是不違反我上面所說的初衷的。因為我們的努力方向是先生成模闆,然後得到資料,最後将兩者整合得到結果。但是Jasper Report的問題在于,其生成模闆的方式過于複雜,即使有IDE的幫助,我們還是需要對其中的衆多規則有所了解才行,否則就會給調試帶來極大的麻煩。

是以,我認為Jasper Report是一個半調子方案,這種強依賴于IDE進行可視化編輯的方式令我很不爽。同時,由此帶來的諸多的限制,相信也讓很多使用者頗為頭疼。在經曆了 一番痛苦的掙紮後,決定放棄使用這種方案。

iText

其 實Jasper Report是基于iText的。于是有的人會說,那麼直接使用iText不是一種倒退麼?的确,直接使用iText似乎就需要直接使用原生的API進行 程式設計了。不過幸好iText其實提供了一些友善的API,通過使用這些API,我們可以直接将HTML代碼轉化成iText可識别的Document對 象,進而導出PDF文檔。

Java 代碼

  1. import  java.io.FileOutputStream;  
  2. import  java.io.FileReader;  
  3. import  java.util.ArrayList;  
  4. import  com.lowagie.text.Document;  
  5. import  com.lowagie.text.Element;  
  6. import  com.lowagie.text.html.simpleparser.HTMLWorker;  
  7. import  com.lowagie.text.html.simpleparser.StyleSheet;  
  8. import  com.lowagie.text.pdf.PdfWriter;  
  9. public   class  MainClass {  
  10.   public   static   void  main(String[] args)  throws  Exception {  
  11.     Document document = new  Document();  
  12.     StyleSheet st = new  StyleSheet();  
  13.     st.loadTagStyle("body" ,  "leading" ,  "16,0" );  
  14.     PdfWriter.getInstance(document, new  FileOutputStream( "html2.pdf" ));  
  15.     document.open();  
  16.     ArrayList p = HTMLWorker.parseToList(new  FileReader( "example.html" ), st);  
  17.     for  ( int  k =  0 ; k < p.size(); ++k)  
  18.       document.add((Element) p.get(k));  
  19.     document.close();  
  20.   }  
  21. }  
import java.io.FileOutputStream; import java.io.FileReader; import java.util.ArrayList; import com.lowagie.text.Document; import com.lowagie.text.Element; import com.lowagie.text.html.simpleparser.HTMLWorker; import com.lowagie.text.html.simpleparser.StyleSheet; import com.lowagie.text.pdf.PdfWriter; public class MainClass { public static void main(String[] args) throws Exception { Document document = new Document(); StyleSheet st = new StyleSheet(); st.loadTagStyle("body", "leading", "16,0"); PdfWriter.getInstance(document, new FileOutputStream("html2.pdf")); document.open(); ArrayList p = HTMLWorker.parseToList(new FileReader("example.html"), st); for (int k = 0; k < p.size(); ++k) document.add((Element) p.get(k)); document.close(); } }       

這是從網上找到的一個例子。從代碼中,我們可以看到,iText本身提供了一個簡單的HTML的解析器,它可以把HTML轉化成我們需要的PDF 的document。

有 了這個東西,基本上我的目标就能達成一大半了。接下來我的任務就是根據實際情況去編寫HTML代碼,然後扔進這個方法,就OK了。而真正的 HTML代碼,我們則可以在這裡使用真正的模闆技術,Freemarker或者Velocity去生成我們所需要的内容。當然,這已經是我們熟門熟路的東 西了。

正當我覺得這個方案基本能符合我的要求的時候,我也同樣找到了它的很多弱項:

1. 無法識别很多HTML的tag和attribute(應該是iText的HTMLParser不夠強大)

2. 無法識别CSS

如果說第一點我還可以勉強接受的話,那麼第二點我就完全不能接受了。無法識别簡單的CSS,就意味着HTML失去了最基本的活力,也無法根據實際 要求調整樣式。

是以這種方案也必然無法成為我的方案。

flying sauser

在這種情況下,我幾乎已經燃起了自己編寫一個支援CSS解析的HTML Parser的想法。幸好,在一個非常偶然的情況下,我在google中搜到了這樣一個開源項目,它能夠滿足我的一切需求。這就是flying sauser,項目首頁是:https://xhtmlrenderer.dev.java.net/

項目的首頁非常吸引人:An XML/XHTML/CSS 2.1 Renderer 。這不正是我要的東西麼?

仔細再看裡面的文檔:

引用 Flying Saucer is an XML/CSS renderer, which means it takes XML files as input, applies formatting and styling using CSS, and generates a rendered representation of that XML as output. The output may go to the screen (in a GUI), to an image, or to a PDF file. Because we believe most people will be interested in re-using their knowledge of web layout, our main target for content is XHTML 1.0 (strict), an XML document format that standardizes HTML.

完美了。這東西能解析HTML和CSS,而且能輸出成image,PDF等格式。哇!我們來看看sample代碼(代碼醜陋,不過已經能說明問題 了):

Java 代碼

  1. package  itext;  
  2. import  java.io.File;  
  3. import  java.io.FileOutputStream;  
  4. import  java.io.OutputStream;  
  5. import  org.xhtmlrenderer.pdf.ITextFontResolver;  
  6. import  org.xhtmlrenderer.pdf.ITextRenderer;  
  7. import  com.lowagie.text.pdf.BaseFont;  
  8. public   class  ITextRendererTest {  
  9.     public   static   void  main(String[] args)  throws  Exception {  
  10.         String inputFile = "conf/template/test.html" ;  
  11.         String url = new  File(inputFile).toURI().toURL().toString();  
  12.         String outputFile = "firstdoc.pdf" ;  
  13.         OutputStream os = new  FileOutputStream(outputFile);  
  14.         ITextRenderer renderer = new  ITextRenderer();  
  15.         renderer.setDocument(url);  
  16.         // 解決中文支援問題   
  17.         ITextFontResolver fontResolver = renderer.getFontResolver();  
  18.         fontResolver.addFont("C:/Windows/Fonts/arialuni.ttf" , BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);  
  19.         // 解決圖檔的相對路徑問題   
  20.         renderer.getSharedContext().setBaseURL("file:/D:/Work/Demo2do/Yoda/branch/Yoda%20-%20All/conf/template/" );  
  21.         renderer.layout();  
  22.         renderer.createPDF(os);  
  23.         os.close();  
  24.     }  
  25. }  
/* * ITextRendererTest.java * * Copyright 2009 Shanghai TuDou. * All rights reserved. */ package itext; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; import org.xhtmlrenderer.pdf.ITextFontResolver; import org.xhtmlrenderer.pdf.ITextRenderer; import com.lowagie.text.pdf.BaseFont; /** * TODO class description * * * @author pcwang * * @version 1.0, 上午11:03:26 create $Id$ */ public class ITextRendererTest { public static void main(String[] args) throws Exception { String inputFile = "conf/template/test.html"; String url = new File(inputFile).toURI().toURL().toString(); String outputFile = "firstdoc.pdf"; OutputStream os = new FileOutputStream(outputFile); ITextRenderer renderer = new ITextRenderer(); renderer.setDocument(url); // 解決中文支援問題 ITextFontResolver fontResolver = renderer.getFontResolver(); fontResolver.addFont("C:/Windows/Fonts/arialuni.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); // 解決圖檔的相對路徑問題 renderer.getSharedContext().setBaseURL("file:/D:/Work/Demo2do/Yoda/branch/Yoda%20-%20All/conf/template/"); renderer.layout(); renderer.createPDF(os); os.close(); } }       

運作,成功!實在太簡單了!API幫你完成了一切!

有了這個東西,我們就可以将PDF的生成流程變成這樣:

1) 編寫Freemarker或者Velocity模闆,打造HTML,勾畫PDF的樣式(請任意使用CSS)

2) 在你的業務邏輯層引入Freemarker的引擎或者Velocity的引擎,并将業務邏輯層中可以擷取的資料和模闆,使用引擎生成最終的内容

3) 将我上面的sample代碼做簡單封裝後,調用,生成PDF

這樣,我想作為一個web程式員來說,上面的3點,都不會成為你的絆腳石。你可以輕松駕馭PDF了。

在Flying Saucer的官方文檔中,有一些Q&A,可以解決讀者們大部分的問題。包括PDF的字型、PDF的格式、Image如何處理等等。大家可以嘗試 着去閱讀。

還有一篇文章,好像是作者寫的,非常不錯:http://today.java.net/pub/a/today/2007/06/26/generating-pdfs-with-flying-saucer-and-itext.html