在此之前,先來勾畫一下我心中比較理想的一個解決方案。在企業應用中,碰到的比較多的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文檔。
- 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。這不正是我要的東西麼?
仔細再看裏面的文檔:
完美了。這東西能解析HTML和CSS,而且能輸出成image,PDF等格式。哇!我們來看看sample代碼(代碼醜陋,不過已經能說明問題了):
- /*
- * 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