`
downpour
  • 浏览: 713463 次
  • 性别: Icon_minigender_1
  • 来自: 上海
博客专栏
00a07ae5-264f-3774-8903-8ad88ce08cb0
Struts2技术内幕
浏览量:116729
4d8508f2-c0dd-3df8-9483-04cc612afbbc
SpringMVC深度探险...
浏览量:229878
社区版块
存档分类
最新评论

介绍一个PDF的生成方案

    博客分类:
  • Java
阅读更多
在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文档。

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代码(代码丑陋,不过已经能说明问题了):

/* 
* 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
分享到:
评论
122 楼 A741841403 2018-07-02  
附件怎么看不到了呢?
121 楼 zqb666kkk 2014-02-12  
walle1027 写道
关于中文字体加粗的问题,找到原因了,原始代码如下
BaseFont font = BaseFont.createFont(path, encoding, embedded);
            
            String fontFamilyName = TrueTypeUtil.getFamilyName(font);
            FontFamily fontFamily = getFontFamily(fontFamilyName);
            
            FontDescription descr = new FontDescription(font);
            try {
                TrueTypeUtil.populateDescription(path, font, descr);
            } catch (Exception e) {
                throw new XRRuntimeException(e.getMessage(), e);
            }
            
            fontFamily.addFontDescription(descr);

在增加字体的时候默认只增加字体的normal的字体格式,如果要加粗,斜体等效果,需要将该字体对应的加粗的字体文件也加入进来,要不然在渲染pdf时无法解析。以下是例子:
//微软雅黑字体文件
		fontResolver.addFont("C:/Windows/fonts/msyh.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
		//微软雅黑加粗后额字体文件
		fontResolver.addFont("C:/Windows/fonts/msyhbd.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);

这样以后,就可以正确识别粗体了。

你这个有一个问题 粗体是能识别了 但是中文没法识别
120 楼 walle1027 2013-12-06  
关于中文字体加粗的问题,找到原因了,原始代码如下
BaseFont font = BaseFont.createFont(path, encoding, embedded);
            
            String fontFamilyName = TrueTypeUtil.getFamilyName(font);
            FontFamily fontFamily = getFontFamily(fontFamilyName);
            
            FontDescription descr = new FontDescription(font);
            try {
                TrueTypeUtil.populateDescription(path, font, descr);
            } catch (Exception e) {
                throw new XRRuntimeException(e.getMessage(), e);
            }
            
            fontFamily.addFontDescription(descr);

在增加字体的时候默认只增加字体的normal的字体格式,如果要加粗,斜体等效果,需要将该字体对应的加粗的字体文件也加入进来,要不然在渲染pdf时无法解析。以下是例子:
//微软雅黑字体文件
		fontResolver.addFont("C:/Windows/fonts/msyh.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
		//微软雅黑加粗后额字体文件
		fontResolver.addFont("C:/Windows/fonts/msyhbd.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);

这样以后,就可以正确识别粗体了。
119 楼 walle1027 2013-12-06  
请问各位有没有遇到过中文字体不能加粗的问题?
118 楼 yanbin0830 2012-10-16  
中文解决的方案,太粗糙
117 楼 tcwhvi 2012-06-12  
请问,怎么设置文字加粗?我设置了,不管用
116 楼 wv1124 2012-01-07  
pdf是好,可是碰到中文就麻繁了
115 楼 lingmin.guo 2011-10-26  
This project has moved, and is no longer hosted on java.net.
The new project website is http://code.google.com/p/flying-saucer/.
114 楼 rexhee 2011-08-31  
为什么我用这个东西生成的表格不换行?
每一行头都接到了每一行的行尾了
113 楼 yuhe 2010-10-18  
偶然发现的 利用 flying sauser生成pdf时,
<img src="http://localhost/projectName/1.jpg" />

<img src="c:/tomcat/webapps/projectName/1.jpg" />
效率相差几十倍!
112 楼 yuhe 2010-10-17  

利用flyingsaucer
1、怎么分页,做出来的能不能给个准确解释?
2、多文件很大啊,生成pdf文件好像很耗时,不知道为什么,我这生成20页的pdf文件,耗时接近3分钟。
111 楼 chenjia876 2010-10-05  
在web application中使用flyingsaucer,为什么在jboss下会报下面的异常:
org.xhtmlrenderer.util.XRRuntimeException: Can't load the XML resource (using TRaX transformer). org.w3c.dom.DOMException: NAMESPACE_ERR: An attempt is made to create or change an object in a way which is incorrect with regard to namespaces.

在websphere下或者在java application中使用就不会报这个异常?
110 楼 lpn520 2010-10-01  
楼主,介绍一个PDF的生成方案太多了,网上一搜一大堆,看来楼主也新手了,呵呵
109 楼 rebornsteven 2010-09-24  
我用拉yye_javaeye 的包  可是不能写入中文了  然后又将那个解决中文问题的代码跟换行的代码整合了  但是还是换行不了
108 楼 itlangqun 2010-09-04  
不错!!!顶一个
107 楼 qryl 2010-08-30  
利用flyingsaucer生产PDF时遇到一些问题,比如input 怎么处理,比如<input type='text' value='111'>这样生成不了,必须<input type='text'>111</input>,页面中如果有checkbox的话也显示不出来!
106 楼 kimmking 2010-08-25  
Javakeith 写道
相比搂主,在javaeye里面,导出过pdf博文把! 如果这种需求呢?该如何实现?请指点!

je的文章数据在数据库中。
套用模板生成即可。
105 楼 Javakeith 2010-08-25  
相比搂主,在javaeye里面,导出过pdf博文把! 如果这种需求呢?该如何实现?请指点!
104 楼 xcloudy 2010-08-12  
flyslowfood 写道
中文好像不能bold, 谁有办法解决一下 ? 谢谢

用CSS的font-weight:bold来设置试一下,不要使用<b>标签
103 楼 xcloudy 2010-08-12  
我测试了一下你写的这个CSS没有生效呢?
而且我去Flying Saucer官方网站https://xhtmlrenderer.dev.java.net/,上面写的这是一个
An XML/XHTML/CSS 2.1 Renderer,作者没说它支持CSS3啊?你的CSS3怎么能生效呢?

l116116116 写道


3。还弄过页眉上的图片,利用fs 支持css3,设置page 属性

@top-left {
                content: "sssssss";
                border-bottom: solid 0.01mm #000;
                background:transparent url(images/PartnerMgr.gif) top left no-repeat;    /*add a image at left-top corner*/
              }

相关推荐

Global site tag (gtag.js) - Google Analytics