Java
CSSC#C++Cfreemarker
在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代码 <EMBED type=application/x-shockwave-flash pluginspage=http://www.macromedia.com/go/getflashplayer height=15 width=14 src=http://downpour.iteye.com/javascripts/syntaxhighlighter/clipboard_new.swf allowscriptaccess="always" quality="high" flashvars="clipboard=import%20java.io.FileOutputStream%3B%0Aimport%20java.io.FileReader%3B%0Aimport%20java.util.ArrayList%3B%0A%0Aimport%20com.lowagie.text.Document%3B%0Aimport%20com.lowagie.text.Element%3B%0Aimport%20com.lowagie.text.html.simpleparser.HTMLWorker%3B%0Aimport%20com.lowagie.text.html.simpleparser.StyleSheet%3B%0Aimport%20com.lowagie.text.pdf.PdfWriter%3B%0A%0Apublic%20class%20MainClass%20%7B%0A%20%20public%20static%20void%20main(String%5B%5D%20args)%20throws%20Exception%20%7B%0A%20%20%20%20Document%20document%20%3D%20new%20Document()%3B%0A%20%20%20%20StyleSheet%20st%20%3D%20new%20StyleSheet()%3B%0A%20%20%20%20st.loadTagStyle(%22body%22%2C%20%22leading%22%2C%20%2216%2C0%22)%3B%0A%20%20%20%20PdfWriter.getInstance(document%2C%20new%20FileOutputStream(%22html2.pdf%22))%3B%0A%20%20%20%20document.open()%3B%0A%20%20%20%20ArrayList%20p%20%3D%20HTMLWorker.parseToList(new%20FileReader(%22example.html%22)%2C%20st)%3B%0A%20%20%20%20for%20(int%20k%20%3D%200%3B%20k%20%3C%20p.size()%3B%20%2B%2Bk)%0A%20%20%20%20%20%20document.add((Element)%20p.get(k))%3B%0A%20%20%20%20document.close()%3B%0A%20%20%7D%0A%7D%0A" wmode="transparent">
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代码 <EMBED type=application/x-shockwave-flash pluginspage=http://www.macromedia.com/go/getflashplayer height=15 width=14 src=http://downpour.iteye.com/javascripts/syntaxhighlighter/clipboard_new.swf allowscriptaccess="always" quality="high" flashvars="clipboard=%2F*%20%0A*%20ITextRendererTest.java%20*%20%0A*%20Copyright%202009%20Shanghai%20TuDou.%20%20%0A*%20All%20rights%20reserved.%20%0A*%2F%0A%0Apackage%20itext%3B%0A%0Aimport%20java.io.File%3B%0Aimport%20java.io.FileOutputStream%3B%0Aimport%20java.io.OutputStream%3B%0A%0Aimport%20org.xhtmlrenderer.pdf.ITextFontResolver%3B%0Aimport%20org.xhtmlrenderer.pdf.ITextRenderer%3B%0A%0Aimport%20com.lowagie.text.pdf.BaseFont%3B%0A%0A%2F**%20%0A%20*%20TODO%20class%20description%20*%20%0A%20*%0A%20*%20%40author%20pcwang%0A%20*%0A%20*%20%40version%201.0%2C%20%E4%B8%8A%E5%8D%8811%3A03%3A26%20%20create%20%24Id%24%0A%20*%2F%0Apublic%20class%20ITextRendererTest%20%7B%0A%09public%20static%20void%20main(String%5B%5D%20args)%20throws%20Exception%20%7B%0A%09%09String%20inputFile%20%3D%20%22conf%2Ftemplate%2Ftest.html%22%3B%0A%20%20%20%20%20%20%20%20String%20url%20%3D%20new%20File(inputFile).toURI().toURL().toString()%3B%0A%20%20%20%20%20%20%20%20String%20outputFile%20%3D%20%22firstdoc.pdf%22%3B%0A%20%20%20%20%20%20%20%20OutputStream%20os%20%3D%20new%20FileOutputStream(outputFile)%3B%0A%20%20%20%20%20%20%20%20ITextRenderer%20renderer%20%3D%20new%20ITextRenderer()%3B%0A%20%20%20%20%20%20%20%20renderer.setDocument(url)%3B%0A%0A%20%20%20%20%20%20%20%20%2F%2F%20%E8%A7%A3%E5%86%B3%E4%B8%AD%E6%96%87%E6%94%AF%E6%8C%81%E9%97%AE%E9%A2%98%0A%20%20%20%20%20%20%20%20ITextFontResolver%20fontResolver%20%3D%20renderer.getFontResolver()%3B%0A%20%20%20%20%20%20%20%20fontResolver.addFont(%22C%3A%2FWindows%2FFonts%2Farialuni.ttf%22%2C%20BaseFont.IDENTITY_H%2C%20BaseFont.NOT_EMBEDDED)%3B%0A%0A%20%20%20%20%20%20%20%20%2F%2F%20%E8%A7%A3%E5%86%B3%E5%9B%BE%E7%89%87%E7%9A%84%E7%9B%B8%E5%AF%B9%E8%B7%AF%E5%BE%84%E9%97%AE%E9%A2%98%0A%20%20%20%20%20%20%20%20renderer.getSharedContext().setBaseURL(%22file%3A%2FD%3A%2FWork%2FDemo2do%2FYoda%2Fbranch%2FYoda%2520-%2520All%2Fconf%2Ftemplate%2F%22)%3B%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20renderer.layout()%3B%0A%20%20%20%20%20%20%20%20renderer.createPDF(os)%3B%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20os.close()%3B%0A%09%7D%0A%7D%0A" wmode="transparent">
/*
* 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