spring mvc 集成itext 生成PDF
最近临时做个PDF 生成预览 下载的一些东西,了解了下,发现itext 这个插件包 和spring 是已经集成在一起的,其实itext 已经很强大了,普通的servlet 也够了,这里先大概介绍一下吧!
spring mvc 里面的视图结构很多,这里我转载一下:
可以看到其中有AbstractPdf 的类,这里简单介绍它,如果你要用其他的视图,可以参考该图,是否已经实现。
// 可以看出 是继承的抽象视图,可以参考上面图例,具体内容我们这里不探讨,直接看方法
public abstract class AbstractPdfView extends AbstractView {
// 这里是设置了我们返回类型
public AbstractPdfView() {
setContentType("application/pdf");
}
// 这里默认是下载
@Override
protected boolean generatesDownloadContent() {
return true;
}
@Override
// 这个方法和名字描述一样,就是创建输出模型的,总的来说是创建Document
protected final void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// IE workaround: write into byte array first.
ByteArrayOutputStream baos = createTemporaryOutputStream();
// 这里创建 itext 是识别的Document
// Apply preferences and build metadata.
Document document = newDocument();
PdfWriter writer = newWriter(document, baos);
prepareWriter(model, writer, request);
buildPdfMetadata(model, document, request);
// Build PDF document.
document.open();
// 具体PDF内容让子类自己处理,后面我们会看到。
buildPdfDocument(model, document, writer, request, response);
document.close();
// Flush to HTTP response.返回客户端
writeToResponse(response, baos);
}
// 默认A4 大小
protected Document newDocument() {
return new Document(PageSize.A4);
}
// 创建流对象
protected PdfWriter newWriter(Document document, OutputStream os) throws DocumentException {
return PdfWriter.getInstance(document, os);
}
从上面看,整个过程很简单,抽象了一些行为,但是大部分都要子类进行实现,一些简单的功能 也是调用itext 里面的东西进行实现,大部分情况我们只需要itext 就行了,这里仅仅作为spring 视图特性的一种介绍。
因为spring 3.0 支持itext 2的版本,而 我是itest 5 版本,导入引入路径变了,这里我仿照上面的类,从新写一个。
/**
* 这里就全部复制spring 的,然后引入的东西改成第5版的就行了
* 代码 几乎不变,唯一变的是引用路径~。~ 没贴出来了,自己导入就能看见
* @author Ran
*
*/
public abstract class AbstractIText5PdfView extends AbstractView {
public AbstractIText5PdfView() {
setContentType("application/pdf");
}
@Override
protected boolean generatesDownloadContent() {
return true;
}
@Override
protected final void renderMergedOutputModel(Map<String, Object> model,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
// 获得流
ByteArrayOutputStream baos = createTemporaryOutputStream();
Document document = newDocument();
PdfWriter writer = newWriter(document, baos);
prepareWriter(model, writer, request);
buildPdfMetadata(model, document, request);
document.open();
buildPdfDocument(model, document, writer, request, response);
document.close();
writeToResponse(response, baos);
}
protected Document newDocument() {
return new Document(PageSize.A4);
}
protected PdfWriter newWriter(Document document, OutputStream os)
throws DocumentException {
return PdfWriter.getInstance(document, os);
}
protected void prepareWriter(Map<String, Object> model, PdfWriter writer,
HttpServletRequest request) throws DocumentException {
writer.setViewerPreferences(getViewerPreferences());
}
protected int getViewerPreferences() {
return PdfWriter.ALLOW_PRINTING | PdfWriter.PageLayoutSinglePage;
}
protected void buildPdfMetadata(Map<String, Object> model,
Document document, HttpServletRequest request) {
}
protected abstract void buildPdfDocument(Map<String, Object> model,
Document document, PdfWriter writer, HttpServletRequest request,
HttpServletResponse response) throws Exception;
}
下面看看我的子类,如何构建我们需要的对象
/**
*
* @author 生成我需要的PDF
*
*/
public class BusinessPdfView extends AbstractIText5PdfView{
// 标题
public static final String title_content = "测试数据";
// 敬语
public static final String honorific_content ="亲爱的程序员:";
// 这是主体内容
public static String context = "我的名字叫:{name} 编号:{no}{#}"
+"这是另一行内容:{xxx}";
// 表格标题
public static String[] cellTitles = {"标题1","标题2","标题3"};
// 银行信息
public static String bank_content = "请贵司将货款汇入我公司如下银行帐户:{account}";
public static void test(Map<String,Object> map){
}
// 从写方法
// 这里我要实现一个 大概这样的PDF
// 标题
//抬头:
// 正文(...)
// 表格(...)
@Override
protected void buildPdfDocument(Map<String, Object> map,
Document document, PdfWriter writer, HttpServletRequest request,
HttpServletResponse response) throws Exception {
try{
document.open();
// 标题居中
Paragraph title = PDFUtil.getParagraph(
new Chunk(title_content,new Font(PDFUtil.bfChinese,16,Font.BOLD)));
title.setAlignment(Paragraph.ALIGN_CENTER);
// 中文 黑体
document.add(title);
Paragraph head = PDFUtil.getParagraph(honorific_content);
head.setSpacingBefore(20);
head.setSpacingAfter(10);
document.add(head);
// 正文
List<Paragraph> paragraphs = PDFUtil.createParagraphs(context, map);
for(Paragraph p : paragraphs){
document.add(p);
}
// 表格标题
PdfPTable table = new PdfPTable(cellTitles.length);
table.setSpacingBefore(20);
table.setSpacingAfter(30);
for(String str : cellTitles){
table.addCell(PDFUtil.getParagraph(str));
}
// 表格数据
List<String> cells = (List<String>) map.get("poTableList");
if(cells != null){
for(String cell : cells){
table.addCell(cell);
}
}
document.add(table);
// 关闭
document.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
上面代码很简单,我仅仅是向Document 里面设置各种值,段落等信息就行,其他的spring 就帮我们搞定。其中我用到一个工具类,这里也贴出来。
工具类仅仅是封装了itext 的API,然后对我需要的参数替换 规则,以及换行等内容进行了处理。
还有关键的编码处理,一会介绍。
/**
* 这仅仅作为itext5 提供一些方法,更多的需要自己去看相关API
* @author Ran
*
*/
public class PDFUtil {
// 对参数的封装形式比如{name}
public static final String BEGIN = "{";
public static final String END = "}";
// 换行形式{#}
public static final String NEW_LINE = "#";
// 默认的行间距、首行距离等,自己添加
public static final float DEFAULT_LEADING = 20;
public static final float DEFAULT_LINE_INDENT = 30;
// 基本字体和样式
public static BaseFont bfChinese;
public static Font fontChinese;
public static Font UNDER_LINE = null;
static{
try {
// SIMKAI.TTF 默认系统语言,这里没使用第三方语言包
//bfChinese = BaseFont.createFont(PDFTest.class.getResource("/content/")+"SIMKAI.TTF",BaseFont.IDENTITY_H,BaseFont.NOT_EMBEDDED);
bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
fontChinese = new Font(bfChinese, 14, Font.NORMAL);
UNDER_LINE = new Font(bfChinese, 14,Font.UNDERLINE);
} catch (DocumentException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
// 默认样式
public static Paragraph getParagraph(String context){
return getParagraph(context,fontChinese);
}
public static Paragraph getParagraph(Chunk chunk){
return new Paragraph(chunk);
}
// 指定字体样式
public static Paragraph getParagraph(String context,Font font){
return new Paragraph(context,font);
}
// 获得新行,首行缩进,和行间距
public static Paragraph getNewParagraph(String context,float fixedLeading,float firstLineIndent){
Paragraph p = getParagraph(context);
p.setLeading(fixedLeading);
p.setFirstLineIndent(firstLineIndent);
return p;
}
public static Paragraph getParagraph(String content , Font font , float fixedLeading , int alignment){
Paragraph p = getParagraph(content);
p.setFont(font);
p.setLeading(fixedLeading);
p.setAlignment(alignment);
return p;
}
// 默认段落样式
public static Paragraph getDefaultParagraph(String context){
Paragraph p = getParagraph(context);
// 默认行间距
p.setLeading(DEFAULT_LEADING);
// 默认首行空隙
p.setFirstLineIndent(DEFAULT_LINE_INDENT);
return p;
}
// 将参数和字符串内容组合成集合
public static List<Paragraph> createParagraphs(String context ,Map<String,Object> map){
int index = 0;
List<Paragraph> list = new ArrayList<Paragraph>();
Paragraph p = getDefaultParagraph(null);
while((index = context.indexOf(BEGIN)) > -1){
String text = context.substring(0,index);
context = context.substring(index, context.length());
index = context.indexOf(END);
String param = null;
if(index > 0){
param = context.substring(BEGIN.length(),index);
}
p.add(text);
if(!NEW_LINE.equals(param)){
Object value = map.get(param);
if(value != null){
p.add(new Chunk(value.toString(),UNDER_LINE));
}else{
p.add(new Chunk(""));
}
}else{
list.add(p);
p = getDefaultParagraph(null);
p.setSpacingBefore(0);
}
context = context.substring(index+END.length(),context.length());
}
list.add(p);
list.add(getParagraph(context));
return list;
}
}
当子类完成了之后,回到我们的控制层,看看如何调用。
@RequestMapping("/business/applyFor.pdf")
public ModelAndView text(HttpServletRequest req, HttpServletResponse res){
Map<String,Object> map = new HashMap<String,Object>();
// 参数 设置,这里略,根据自己的东西进行匹配吧。
BusinessPdfView view = new BusinessPdfView();
view.setAttributesMap(map);
// 这里使用了spring mvc 的人,应该不陌生,其他配置就略了。
return new ModelAndView(view);
}
上面就完成了,整个过程,重点还是itext API 那些调用,下面我们用itext 单独生成PDF,这里类可以导入的jar,可以直接使用:语言包请下载我从新打包的
public class PDFTest {
// 基本字体和样式
static BaseFont bfChinese = null;
static Font fontChinese = null;
static String CHANGE_LINE = "\n ";
static Font UNDER_LINE = null;
/**
* 这里
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
Document document = new Document(PageSize.A4, 33, 33, 33, 33);
PdfWriter writer = null;
try {
// 语言 .tif 结尾的,都是Windows下的,我是拷贝进行项目了
//bfChinese = BaseFont.createFont(
PDFTest.class.getResource("/content/")+"SIMKAI.TTF",BaseFont.IDENTITY_H,BaseFont.NOT_EMBEDDED);
// 这里我用的第三方语言包,具体的操作,后面说,有一定更改,这里可以测试
bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H",BaseFont.NOT_EMBEDDED);
fontChinese = new Font(bfChinese, 14, Font.NORMAL);
UNDER_LINE = new Font(bfChinese, 14,Font.UNDERLINE);
writer = PdfWriter.getInstance(document, new FileOutputStream("hellword.pdf"));
// 打开
document.open();
Paragraph title = new Paragraph();
// 设置页面格式
title.setSpacingBefore(8);
title.setSpacingAfter(2);
title.setAlignment(1);
// 设置PDF标题
title.add(new Chunk("付款申请书",new Font(bfChinese, 16,Font.BOLD)));
document.add(title);
// 抬头
Paragraph head = getParagraph("致:程序员们");
head.setSpacingBefore(30);
document.add(head);
// 文字参数
Map<String,String> map = new HashMap<String, String>();
map.put("name", "ranqiqiang");
map.put("sex", "男");
map.put("height", "178");
map.put("time", new Date().toString());
// 文字数据
String str = getString();
List<Paragraph> l = getPars(str, map);
for(Paragraph p : l){
document.add(p);
}
//表格数据
//创建一个有4列的表格
PdfPTable table = new PdfPTable(4);
// 可以对表格进行控制
// 默认会生成普通cell,特殊cell 要通过PdfPCell 进行设置
table.addCell(getParagraph("发票税率"));
table.addCell(getParagraph("发票编号"));
table.addCell(getParagraph("发票类型"));
table.addCell(getParagraph("金额"));
// 剩余的表格数据
List<String> cells = getTableParams();
for(String cell:cells){
table.addCell(getParagraph(cell));
}
document.add(table);
document.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (DocumentException e) {
e.printStackTrace();
}
}
// 给Dcouemnt 添加内容
public Document setDocumentContext(Document document,List<Paragraph> paragraphs){
if(paragraphs != null){
for(Paragraph paragraph : paragraphs){
try {
document.add(paragraph);
} catch (DocumentException e) {
e.printStackTrace();
}
}
}
return document;
}
public static String setParams(String context,List<String> params,String str_replace){
if(context == null || params == null || str_replace == null){
return context;
}
for(String param : params){
Chunk chunk = new Chunk(param, UNDER_LINE);
context = context.replaceFirst(str_replace, chunk.toString());
}
return context;
}
public static void setParams(String param,Paragraph pa){
pa.add( new Chunk(param, UNDER_LINE));
}
public static String setParams(String context,List<String> params){
return setParams(context, params,"\\?");
}
public static List<Chunk> getListChunks(List<String> params){
List<Chunk> chunks = new ArrayList<Chunk>();
for(String param : params){
Chunk e = new Chunk(param, UNDER_LINE);
chunks.add(e);
}
return chunks;
}
public static List<String> getList(){
List<String> params = new ArrayList<String>();
params.add("计划-08-采购-购置-传输08-202-002-006");
params.add("2008503180");
params.add("993290.00");
params.add("到货款");
params.add("20%");
params.add("198658.00");
params.add("壹拾玖万捌仟陆佰伍拾捌圆整");
params.add("1");
params.add("198658.00");
return params;
}
public static final String BEGIN = "{";
public static final String END = "}";
public static final String NEW_LINE = "#";
public static final float DEFAULT_LEADING = 20;
public static final float DEFAULT_LINE_INDENT = 30;
public static String getString2(){
String str = "贵司与我司签订的采购合同,合同编号:"
+"{conNo},采购订单号:{poNo} ,该合同金额为:{conMoney} 。"
+"{#} 发票明细:{pp} {#} 魔法:{xx} {#} OVER!";
return str;
}
public static String getString(){
String str =
"{#}工程师们,大家晚上好,我叫:{name},性别:{sex},身高:{height}cm."
+ "当前时间是:{time}{#}"
+ "这是文字行的测试数据,下面我们尝试表格型的测试数据。"
+ "{#}这里展示我们的订单信息:{#}";
return str;
}
public static List<String> getTableParams(){
// 为了方便就按顺序存放
List<String> l = new ArrayList<String>();
l.add("%12");
l.add("T9090990");
l.add("食品类发票");
l.add("1000.0");
l.add("%15");
l.add("Q12023");
l.add("服务类发票");
l.add("9999.0");
return l;
}
public static List<Paragraph> getPars(String context ,Map<String,String> map){
int index = 0;
List<Paragraph> list = new ArrayList<Paragraph>();
Paragraph p = getDefaultParagraph(null);
while((index = context.indexOf(BEGIN)) > -1){
String text = context.substring(0,index);
context = context.substring(index, context.length());
index = context.indexOf(END);
String param = null;
if(index > 0){
param = context.substring(BEGIN.length(),index);
}
p.add(text);
if(!NEW_LINE.equals(param)){
Object value = map.get(param);
if(value != null){
p.add(new Chunk(value.toString(),UNDER_LINE));
}else{
p.add(new Chunk(param));
}
}else{
list.add(p);
p = getDefaultParagraph(null);
p.setSpacingBefore(0);
}
context = context.substring(index+END.length(),context.length());
}
list.add(p);
list.add(getParagraph(context));
return list;
}
// 默认样式
public static Paragraph getParagraph(String context){
return getParagraph(context,fontChinese);
}
// 指定字体样式
public static Paragraph getParagraph(String context,Font font){
return new Paragraph(context,font);
}
// 获得新行,首行缩进,和行间距
public static Paragraph getNewParagraph(String context,float fixedLeading,float firstLineIndent){
Paragraph p = getParagraph(context);
p.setLeading(fixedLeading);
p.setFirstLineIndent(firstLineIndent);
return p;
}
// 默认段落样式
public static Paragraph getDefaultParagraph(String context){
Paragraph p = getParagraph(context);
// 默认行间距
p.setLeading(DEFAULT_LEADING);
// 默认首行空隙
p.setFirstLineIndent(DEFAULT_LINE_INDENT);
return p;
}
}
上面的类,可能有点乱,也是测试用用。在语言包的选择上,itext 5 的和我下到的iTextAsian.jar 不同,因为引用路径变了,也就是说解压iTextAsian.jar,然后将lowagie 改为itextpdf 就行了,然后从新压缩。
命令:jar cvf iTextAsian.jar com/itextpdf/text/pdf/fonts/*
这里我都做好了,下面两个jar,可以直接使用。
小结:
1.最近比较忙,上面的例子很粗糙,但是能给大家一个思路,基本上照着上面的写都可以完成。
2.对于itext 的使用,我也是第一次,很多地方不完善,API 以及语言 还不深入,暂时能用就行,需要深入的,大家自己研究。
3.spring mvc 提供了很多视图,大家平时操作的时候,可以先了解下,如果提供了,可以减少你的部分代码。
4.如果有误的地方,请指出,非常感谢~。~