原文地址:http://jeemiss.iteye.com/blog/1112765
参考资料:http://today.java.net/pub/a/today/2007/06/26/generating-pdfs-with-flying-saucer-and-itext.html
Freemarker、Flying sauser 、Itext ,这三个框架的作用就不详细介绍了,google一下就知道了。
Itext提供了很多底层的API,让我们可以用java代码画一个pdf出来,但是很不灵活,布局渲染代码都hard code 进java类里面了。
当需求发生改变时,哪怕只需要更改一个属性名,我们都要重新修改那段代码,很不符合开放关闭的原则。想到用模版来做渲染,但自己实现起来比较繁琐,然后google了下,找到了用freemarker做模版,Flying sauser 照着模版做渲染,让Itext做输出生成PDF的方案。
freemarker和itext都比较熟悉了,Flying sauser 第一次听说,看完官方的user guide(http://flyingsaucerproject.github.com/flyingsaucer/r8/guide/users-guide-R8.html)后,自己着手做了个demo实践:
需要的包:core-renderer.jar 和 iText-2.0.8.jar
测试数据模型:
package com.jeemiss.pdfsimple.entity; public class User { private String name; private int age; private int sex; /** * Constructor with all fields * * @param name * @param age * @param sex */ public User(String name, int age, int sex) { super(); this.name = name; this.age = age; this.sex = sex; } /////////////////////// getter and setter /////////////////////////////////////////// public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getSex() { return sex; } public void setSex(int sex) { this.sex = sex; } }
package com.jeemiss.pdfsimple.freemarker; import freemarker.template.Configuration; public class FreemarkerConfiguration { private static Configuration config = null; /** * Static initialization. * * Initialize the configuration of Freemarker. */ static{ config = new Configuration(); config.setClassForTemplateLoading(FreemarkerConfiguration.class, "template"); } public static Configuration getConfiguation(){ return config; } }
HTML生成器:
package com.jeemiss.pdfsimple.generator; import java.io.BufferedWriter; import java.io.StringWriter; import java.util.Map; import com.jeemiss.pdfsimple.freemarker.FreemarkerConfiguration; import freemarker.template.Configuration; import freemarker.template.Template; public class HtmlGenerator { /** * Generate html string. * * @param template the name of freemarker teamlate. * @param variables the data of teamlate. * @return htmlStr * @throws Exception */ public static String generate(String template, Map<String,Object> variables) throws Exception{ Configuration config = FreemarkerConfiguration.getConfiguation(); Template tp = config.getTemplate(template); StringWriter stringWriter = new StringWriter(); BufferedWriter writer = new BufferedWriter(stringWriter); tp.setEncoding("UTF-8"); tp.process(variables, writer); String htmlStr = stringWriter.toString(); writer.flush(); writer.close(); return htmlStr; } }
PDF 生成器:
package com.jeemiss.pdfsimple.generator; import java.io.ByteArrayInputStream; import java.io.OutputStream; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; import org.xhtmlrenderer.pdf.ITextRenderer; public class PdfGenerator { /** * Output a pdf to the specified outputstream * * @param htmlStr the htmlstr * @param out the specified outputstream * @throws Exception */ public static void generate(String htmlStr, OutputStream out) throws Exception { DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); Document doc = builder.parse(new ByteArrayInputStream(htmlStr.getBytes())); ITextRenderer renderer = new ITextRenderer(); renderer.setDocument(doc, null); renderer.layout(); renderer.createPDF(out); out.close(); } }
用来做测试的ftl模版,用到部分css3.0的属性来控制pdf强制分页和输出分页信息
<html> <head> <title>${title}</title> <style> table { width:100%;border:green dotted ;border-width:2 0 0 2 } td { border:green dotted;border-width:0 2 2 0 } @page { size: 8.5in 11in; @bottom-center { content: "page " counter(page) " of " counter(pages); } } </style> </head> <body> <h1>Just a blank page.</h1> <div style="page-break-before:always;"> <div align="center"> <h1>${title}</h1> </div> <table> <tr> <td><b>Name</b></td> <td><b>Age</b></td> <td><b>Sex</b></td> </tr> <#list userList as user> <tr> <td>${user.name}</td> <td>${user.age}</td> <td> <#if user.sex = 1> male <#else> female </#if> </td> </tr> </#list> </table> </div> </body> </html>
最后写个测试用例看看:
package com.jeemiss.pdfsimple.test; import java.io.FileOutputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.junit.Test; import com.jeemiss.pdfsimple.entity.User; import com.jeemiss.pdfsimple.generator.HtmlGenerator; import com.jeemiss.pdfsimple.generator.PdfGenerator; public class TestCase { @Test public void generatePDF() { try{ String outputFile = "C:\\sample.pdf"; Map<String,Object> variables = new HashMap<String,Object>(); List<User> userList = new ArrayList<User>(); User tom = new User("Tom",19,1); User amy = new User("Amy",28,0); User leo = new User("Leo",23,1); userList.add(tom); userList.add(amy); userList.add(leo); variables.put("title", "User List"); variables.put("userList", userList); String htmlStr = HtmlGenerator.generate("sample.ftl", variables); OutputStream out = new FileOutputStream(outputFile); PdfGenerator.generate(htmlStr, out); }catch(Exception ex){ ex.printStackTrace(); } } }
若要在Web中实现点击某个下载PDF按钮后,浏览器出现下载框的话,只要将 输出流做如下处理:
response.setHeader("Content-disposition", "attachment; filename=" + new String("YourPdfName.pdf".getBytes("gb2312"), "iso8859-1")); response.setContentType("application/pdf"); OutputStream out = response.getOutputStream(); //将PDF写入此输出流 最后记得将流关闭 out.flush(); out.close();不过,如上方法并不支持中文,如要支持中文,可以在创建PDF的方法内加入如下方法(未验证是否可行)
...... ...... renderer.setDocument(doc, null); // 解决中文支持问题 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(out); ...... ......