iText生成pdf文书

     我们项目需要生成文书,文书内容比较麻烦,需要动态插入表格、图片、修改字体颜色等。

首先理所当然的使用了word方式,在浏览器中使用NTKO生成word,动态替换标签文字,插入表格,虽然实现了功能,但是有很多缺点。

     1.后台只能直接生成简单的word文档,动态插入表格图片不行,只能在浏览器中生成。

     2.不稳定,时间长:动态插入表格,200行,每行6列,一个单元格大约10个字,插入一次大概1分钟,一般是动态连续生成很多个文档,一个失败了就走不下去了。生成10个文档就需要好几分钟,还容易失败,浏览器经常不响应,测试也麻烦,失败还导致业务编号跳号。

     3.后台不能实现快速批量生成word文书,无法满足业务需要。

word也有很多好处的,好处就是编辑方便,展示、编辑、盖章浏览器控件齐全,制作新文书方便等。 

     所以在2.0版本中,更换了pdf方式来生成文书。

     好处是可以后台自动生成文书和盖章,速度飞快,稳定性高,很少有失败情况,制作文书也简单方便,文档格式保持的很好,内容展示很完美。

     缺点是不支持编辑,生成后无法编辑。我们业务中文书一般是生成后一直再上面盖章、逐级法院盖章,不涉及到修改,所以可以满足需求。相当于pdf只能看,word可以改。

 

     下面我大概总结了一下pdf生成方法

     生成pdf方法很多,组件也好多种,我们用的是iText,这个比较流行,而且开源,还有更好的,商用的等等很多,各有好坏,此处不讨论。版本选择,iText版本升级到2.1.7后直接跳到了5.0.0,包名等完全变了,我们无耻的选用了最新的5.4.1。下面说说iText生成pdf的几个方法,其实非常简单,大部分调用就行。

1. 内容

     首先我们研究了直接生成pdf的方式,非常简单,用文字创建段落等即可,设置好字体、间距、对齐方式等等即可,弄个Hello World 的例子。

package com.thunisoft.demo;
 
import java.io.FileOutputStream;
 
import com.itextpdf.text.Document;
import com.itextpdf.text.Font;
import com.itextpdf.text.FontFactoryImp;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.pdf.BaseFont;
importcom.itextpdf.text.pdf.PdfPHeaderCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;
 
/**
 * 创建一个简单pdf文档,文字、表格
 *
 * @author zhangwd
 * @version 1.0
 *
 */
public class PdfDemo_1 {
 
   private static void create() throws Exception {
        // 创建一个文档(默认大小A4,边距36, 36, 36, 36)
        Document document = new Document();
        // 设置文档大小
        document.setPageSize(PageSize.A4);
        // 设置边距,单位都是像素,换算大约1厘米=28.33像素
        document.setMargins(50, 50, 50, 50);
 
        // 创建writer,通过writer将文档写入磁盘
        PdfWriter writer = PdfWriter.getInstance(document,newFileOutputStream("c:/demo.1.pdf"));
 
        // demo
        String title = "凉州词";
        String content = "黄河远上白云间,一片孤城万仞山。羌笛何须怨杨柳,春风不度玉门关。";
 
        // 定义字体
        FontFactoryImp ffi = new FontFactoryImp();
        // 注册全部默认字体目录,windows会自动找fonts文件夹的,返回值为注册到了多少字体
        ffi.registerDirectories();
        // 获取字体,其实不用这么麻烦,后面有简单方法
        Font font = ffi.getFont("宋体",BaseFont.IDENTITY_H,BaseFont.EMBEDDED, 12, Font.UNDEFINED, null);
 
        // 打开文档,只有打开后才能往里面加东西
        document.open();
 
        // 设置作者
        document.addAuthor("王之涣");
        // 设置创建者
        document.addCreator("王之涣");
        // 设置主题
        document.addSubject("测试");
        // 设置标题
        document.addTitle("凉州词");
 
        // 增加一个段落
        document.add(new Paragraph(title, font));
        document.add(new Paragraph(content, font));
        document.add(new Paragraph("\n\r", font));
 
        // 创建表格,5列的表格
        PdfPTable table = new PdfPTable(4);
        table.setTotalWidth(PageSize.A4.getWidth()- 100);
        table.setLockedWidth(true);
        // 创建头
        PdfPHeaderCell header = new PdfPHeaderCell();
        header.addElement(new Paragraph(title, font));
        header.setColspan(4);
        table.addCell(header);
        // 添加内容
        table.addCell(new Paragraph("黄河远上白云间",font));
        table.addCell(new Paragraph("一片孤城万仞山",font));
        table.addCell(new Paragraph("羌笛何须怨杨柳",font));
        table.addCell(new Paragraph("春风不度玉门关",font));
 
        document.add(table);
        // 关闭文档,才能输出
        document.close();
        writer.close();
   }
 
   public static void main(String[] args) throws Exception {
        create();
   }
}

     结果:



     结果看来还可以,内容有了,格式自己调一调就可以了,满足了普通需求。

2.字体

     我们项目文书字体比较特殊,比如用到了宋体(99%都这个吧)、华文仿宋(安装office后自带)、仿宋_GB2312等,于是就研究了一下pdf字体,网上有很多方法使用中文字体,其实5.0版以后的iText加入字体还是很方便的。

     还是HelloWorld例子:

package com.thunisoft.demo;
 
import java.io.FileOutputStream;
 
import com.itextpdf.text.Document;
import com.itextpdf.text.Font;
import com.itextpdf.text.FontFactoryImp;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.tool.xml.XMLWorkerFontProvider;
 
/**
 * 字体
 *
 * @author zhangwd
 * @version 1.0
 *
 */
public class PdfDemo_2 {
 
    public static void create() throws Exception {
        Document document = new Document();
        PdfWriter writer = PdfWriter.getInstance(document,new FileOutputStream("c:/demo.2.pdf"));
        String title = "凉州词";
        String content = "黄河远上白云间,一片孤城万仞山。羌笛何须怨杨柳,春风不度玉门关。";
        document.open();
        document.add(newParagraph(title, getFont("方正兰亭黑简体")));
        document.add(newParagraph(content, getFont("迷你简娃娃篆")));
        document.close();
        writer.close();
    }
 
    private static Font getFont(String fontName) {
        // xmlworker主要功能是html转pdf用的,非常好用,也是itext官方的
 
        // 这个是xmlworker提供的获取字体方法,很方便,对中文支持很好
        FontFactoryImp fp = newXMLWorkerFontProvider();
        // 注册指定的字体目录,默认构造方法中会注册全部目录,我这里注册了src/font目录
        fp.registerDirectory(PdfDemo_2.class.getClassLoader().getResource("font").getFile(), true);
 
        // 最好的地方是直接支持获取中文的名字
        return fp.getFont(fontName);
 
        // 当然,最好的方法是自己继承XMLWorkerFontProvider,提供一些常用字体,简单方便
    }
 
    public static void main(String[]args) throws Exception {
        create();
    }
}

     结果:


     xmlworker的XMLWorkerFontProvider提供了很方便的获取字体方法:

     1.注册一个文件夹,里面有哪些字体都可以,比如我demo中的字体

     2.使用getFont(字体名)即可获得,不过字体名从哪来的呢


     支持中文,非常方便,结果也还不错,字体也有了,都可以满足了,但是还一个问题,页眉页脚。 

3.页眉页脚

     iText5中并没有之前版本HeaderFooter对象设置页眉和页脚,可以利用PdfPageEvent来完成页眉页脚的设置工作。

     PdfPageEvent提供了几个pdf在创建时的事件,页眉页脚就是在每页加载完写入的。

每一页加个页码还是很简单的,但是总页码就麻烦了,iText是流模式的写入内容,只有写到最后,才能知道有多少页,那么显示总页数就麻烦了,不过麻烦不代表不可能。

     其实iText仅在调用释放模板方法后才将PdfTemplate写入到OutputStream中,否则对象将一直保存在内存中,直到关闭文档。

     所以我们可以在最后关闭文档前,使用PdfTemplate写入总页码。可以理解成先写个占位符,然后统一替换。

     还是HelloWorld例子:

package com.thunisoft.demo;
 
import java.io.FileOutputStream;
 
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Document;
import com.itextpdf.text.Element;
import com.itextpdf.text.ExceptionConverter;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.ColumnText;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfPageEventHelper;
import com.itextpdf.text.pdf.PdfTemplate;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.tool.xml.XMLWorkerFontProvider;
 
/**
 * 页眉、页脚
 *
 * @author zhangwd
 * @version 1.0
 *
 */
public class PdfDemo_3 {
 
    public static void create() throws Exception {
        Document document = newDocument(PageSize.A4, 50, 50, 50, 50);
        PdfWriter writer = PdfWriter.getInstance(document,new FileOutputStream("c:/demo.3.pdf"));
 
        // 增加页眉页脚
        writer.setPageEvent(newMyHeaderFooter());
 
        String title = "凉州词";
        String content = "黄河远上白云间,一片孤城万仞山。羌笛何须怨杨柳,春风不度玉门关。";
        document.open();
 
        Font font = newXMLWorkerFontProvider().getFont("宋体");
        for (int i = 0; i <100; i++) {
            document.add(newParagraph(title, font));
            document.add(new Paragraph(content,font));
            document.add(new Paragraph("\n"));
        }
        document.close();
        writer.close();
    }
 
    public static void main(String[]args) throws Exception {
        create();
    }
}
 
/**
 * iText5中并没有之前版本HeaderFooter对象设置页眉和页脚
* 不过,可以利用PdfPageEventHelper来完成页眉页脚的设置工作。
* 就是在页面完成但写入内容之前触发事件,插入页眉、页脚、水印等。
* * @author zhangwd * @version 1.0 * */ class MyHeaderFooter extends PdfPageEventHelper { Font font = new XMLWorkerFontProvider().getFont("宋体", 12, BaseColor.RED); // 总页数 PdfTemplate totalPage; // 打开文档时,创建一个总页数的模版 public void onOpenDocument(PdfWriter writer,Document document) { PdfContentByte cb =writer.getDirectContent(); totalPage = cb.createTemplate(30, 16); } // 一页加载完成触发,写入页眉和页脚 public void onEndPage(PdfWriter writer, Documentdocument) { PdfPTable table = new PdfPTable(3); try { table.setTotalWidth(PageSize.A4.getWidth() - 100); table.setWidths(new int[] { 24, 24, 3}); table.setLockedWidth(true); table.getDefaultCell().setFixedHeight(-10); table.getDefaultCell().setBorder(Rectangle.BOTTOM); table.addCell(new Paragraph("我是文字", font));// 可以直接使用addCell(str),不过不能指定字体,中文无法显示 table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_RIGHT); table.addCell(new Paragraph("第" + writer.getPageNumber() + "页/", font)); // 总页数 PdfPCell cell = new PdfPCell(Image.getInstance(totalPage)); cell.setBorder(Rectangle.BOTTOM); table.addCell(cell); // 将页眉写到document中,位置可以指定,指定到下面就是页脚 table.writeSelectedRows(0, -1, 50,PageSize.A4.getHeight() - 20, writer.getDirectContent()); } catch (Exception de) { throw new ExceptionConverter(de); } } // 全部完成后,将总页数的pdf模版写到指定位置 public void onCloseDocument(PdfWriter writer,Document document) { String text = "总" + (writer.getPageNumber() - 1) + "页"; ColumnText.showTextAligned(totalPage, Element.ALIGN_LEFT, new Paragraph(text,font), 2, 2, 0); } }

     结果:


     这样,文档也有了,字体也有了,页眉页脚也有了。

4.html转pdf

     结果还不错,虽然可以满足我们的要求,但是比较复杂,动态创建一个个的表格和内容过于繁琐,方法太粗暴了,用户的文档内容或格式变化,就要修改程序了。

     于是,我们讨论了一个比较好的方法,感谢谢国庆提供的帮助。

     先创建html,然后转换成pdf,demo如下: 

package com.thunisoft.demo;
 
import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
 
import com.itextpdf.text.Document;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.tool.xml.XMLWorkerHelper;
 
/**
 * html转pdf
 *
 * @author zhangwd
 * @version 1.0
 *
 */
public class PdfDemo_4 {
 
    public static void create() throws Exception {
 
        // html中字体非常郁闷
        // 1. html中不指定字体,则默认使用英文字体,中文会不显示。
        // 2. html中指定的字体必须是英文名称,如宋体:font-family:SimSun;
        // 3. html中不能指定自定义字体,必须指定itext支持的字体,还好itext支持字体比较多,常见操作系统带的都支持
        // 4. 暂没有找到如何html中支持自定义字体方法,网上都是修改源码实现默认字体中文,也很重要
 
        StringBuilder html = newStringBuilder();
        html.append("");
        html.append("");
        html.append("");
        html.append("");
        html.append("");
        html.append("");
        html.append("");
        html.append("");
        html.append("");
        html.append("");
        html.append("
凉州词
黄河远上白云间,一片孤城万仞山。羌笛何须怨杨柳,春风不度玉门关。
"); html.append(""); html.append(""); InputStream is = newByteArrayInputStream(html.toString().getBytes()); OutputStream os = newFileOutputStream("c:/demo.4.pdf"); Document document = new Document(); PdfWriter writer = PdfWriter.getInstance(document,os); document.open(); // 将html转pdf XMLWorkerHelper.getInstance().parseXHtml(writer,document, is); document.close(); } public static void main(String[]args) throws Exception { create(); } }


结果:

 

     此处使用了XmlWorker,XmlWorker也是iText官方的,目前和iText版本一起更新,可以讲XHTML转换成pdf,支持大部分样式和标签,是大部分哦,不是全部。

     目前我们就用的这个方式,写好html文档,使用时动态替换html中的标记位,然后生成pdf。

 

     使用XHTML转pdf要注意的地方:

     1. html中不指定字体,则默认使用英文字体,中文会不显示;网上很多人通过修改源码来实现默认字体中文,也是一种方式,不过感觉比较麻烦。

     2. html中指定的字体必须是英文名称;

     如宋体:font-family:SimSun;正确

     font-family:宋体;则错误,竟然unicode也不行。

     3. html中不能指定自定义字体(比如上文中的方正兰亭黑),必须指定iText支持的字体,还好iText支持字体比较多,常见操作系统带的都支持

     4. 暂没有找到如何html中支持自定义字体方法。

     5. pdf中添加图片也非常简单,例如:,就可以了。

     6. XHTML不是HTML,所以任何标签都要完整结束,比如
错误,必须
才行。

 

5.其他

     我们项目的文书目前20多种,以后还会大量增加,不过写一个html模版很简单,需要对html和css熟练,调生成的样式部分比较麻烦(比如文字多了会切掉,不切会影响整体样式,表格线有粗有细,xmlworker不支持全部css等),一般A4纸都是厘米单位的,html中最好也使用厘米,处理简单点。

     比如我们的部分模版


     结果

6.小结

     这里只是简单的说说iText怎么创建pdf,其实生成复杂的pdf还有很多方法,例如生成报表用iReport等等,当然,咱们公司也是有很多导出固定pdf的方法的,iText只是写写简单或特殊需求的pdf。

 

     其实iText远比想想的功能强大,常用的pdf操作都可以完成,如拼接,截取等等。

     如果你经常用kindle或者ipad看电子书,有些pdf的尺寸或其他电子书是否排版很不好呢,可以自己写一个程序,来处理一下,或者按自己的喜好转换一下,然后就可以舒舒服服的看了。

你可能感兴趣的:(技术记录)