1. 调研背景
客户要求大比武系统,比武评比活动结束后,需要给老师发布证书,要根据不同模板设置不同格式证书,并下载为pdf打印,存档。考虑前端页面上使用VUE渲染HTML,点击下载导出PDF方式的实现。
2.调研目的
满足客户需求,完成动态设置证书,下载pdf。
3.实施方案
3.1PDF组件对比
pdfbox组件
功能简介:Apache PDFbox是一个开源的、基于Java的、支持PDF文档生成的工具库,它可以用于创建新的PDF文档,修改现有的PDF文档,还可以从PDF文档中提取所需的内容。Apache PDFBox还包含了数个命令行工具。
使用场景:在读取和解析PDF的时候使用PDFBox,较为简单。PDFBox主要有以下特征:
1.提取文本,包括Unicode字符。
2.集成Lucene搜索引擎:Lucene搜索引擎与PDF索引相集成。
3.加密/解密PDF文档。
4.从PDF和XFDF格式中导入或导出表单数据。
5.向已有PDF文档中追加内容。
6.将一个PDF文档切分为多个文档。
7.覆盖PDF文档。
测试结果
| 测试环境 | 堆内存消耗 | 读取时间 | 写入/转换时间 | 文件大小 |
|---|---|---|---|---|
| Win64 4核8gjdk1.8 | W:70.77MB R:61.86MB | 1561ms | 2560ms | W:836BR:852KB |
itext组件
功能简介:iText是著名的开放项目,是用于生成PDF文档的一个java类库。通过iText不仅可以生成PDF或rtf的文档,而且可以将XML、Html文件转化为PDF文件。
使用场景:修改PDF的时候使用Itext,支持粒度较细,比如插入文字可以自定义字体,使用字库文件(ttf)。
测试结果
| 测试环境 | 堆内存消耗 | 读取时间 | 写入/转换时间 | 文件大小 |
|---|---|---|---|---|
| Win64 4核8gjdk1.8 | W:22.60MBR:53.97MB | 165ms | 54ms | W:836BR:852KB |
jodconverter(包含jodconverter-core、jodconverter-local等)组件
功能简介:Jodconvert , 是一个java的文档转换器, 利用 openoffice 或 libreOffice 来进行文档格式转换。jodconverter 4.1.0版本后,改进了api的结构,同时新增了local以及online的模块,local方式是本地启动libreoffice,然后使用uno进行编程,操作本地的office。
使用场景:JODConverter主要的功能是用来做各种档案的转换,springboot集成了jodconverter 。libreOffice比OpenOffice转换稳定,需安装对应系统的libreOffice/OpenOffice软件。
测试结果
| 测试环境 | 堆内存消耗 | 读取时间 | 写入/转换时间 | 文件大小 |
|---|---|---|---|---|
| Win64 4核8gjdk1.8 | libreOffice:3.31MBOpenOffice:3.07MB | # | libreOffice:18363msOpenOffice:8124ms | libreOffic:807KB->2.09MBOpenOffice:807KB->525KB |
3.2 iText介绍
iText是著名的开放源码的站点sourceforge一个项目,是用于生成PDF文档的一个java类库。通过iText不仅可以生成PDF或rtf的文档,而且可以将XML、Html文件转化为PDF文件。
iText 官网:http://itextpdf.com/
iText 开发文档: http://developers.itextpdf.com/developers-home
4. 开发集成
经过各个组件对比,选用Itext来实现,因为HTML编写对项目成员要求不高,而且无需安装客户端等。
4.1 iText常用类
com.itextpdf.text.Document:这是iText库中最常用的类,它代表了一个pdf实例。如果你需要从零开始生成一个PDF文件,你需要使用这个Document类。首先创建(new)该实例,然后打开(open)它,并添加(add)内容,最后关闭(close)该实例,即可生成一个pdf文件。
com.itextpdf.text.Paragraph:表示一个缩进的文本段落,在段落中,你可以设置对齐方式,缩进,段落前后间隔等。
com.itextpdf.text.Chapter:表示PDF的一个章节,他通过一个Paragraph类型的标题和整形章数创建。
com.itextpdf.text.Font:这个类包含了所有规范好的字体,包括family of font,大小,样式和颜色,所有这些字体都被声明为静态常量。
com.itextpdf.text.List:表示一个列表;
cocom.itextpdf.text.List:表示一个列表;
com.itextpdf.text.Anchor:表示一个锚,类似于HTML页面的链接。
com.itextpdf.text.pdf.PdfWriter:当这个PdfWriter被添加到PdfDocument后,所有添加到Document的内容将会写入到与文件或网络关联的输出流中。
com.itextpdf.text.pdf.PdfReader:用于读取PDF文件。
4.2 iText生成PDF步骤
用iText生成PDF文档需要5个步骤:
①建立Document对象的实例。
Document document = new Document();
②建立一个书写器(Writer)与document对象关联,通过书写器(Writer)可以将文档写入到磁盘中。
PDFWriter.getInstance(document,new FileOutputStream(“D:\Helloworld.PDF”));
③打开文档。
document.open();
④向文档中添加内容。
document.add(new Paragraph(“Hello World”));
⑤关闭文档。
document.close();
通过上面的5个步骤,就能产生一个Helloworld.PDF的文件,文件内容为”Hello World”。
4.3 中文支持
通过下面的代码就可以在文档中使用中文了,字体大小和粗细可以调整Font()方法中的12和Font.NORMAL,
切记:Font不是AWT.Font,是我们jar包中的
BaseFont bfChinese = BaseFont.createFont(“STSong-Light”, “UniGB-UCS2-H”, BaseFont.NOT_EMBEDDED);
Font FontChinese = new Font(bfChinese, 12, Font.NORMAL);
Paragraph pragraph=new Paragraph(“你好”, FontChinese);
4.4 用itext布局加元素生成pdf
Paragraph,排版布局。
Itext的com.itextpdf.text.Paragraph 类是段落的处理。在一个段落中,你可以设置段落的对齐方式,缩进和间距。
PdfWriter.getInstance(document, response.getOutputStream());
document.open();
//设置字体
BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H",BaseFont.NOT_EMBEDDED);
com.itextpdf.text.Font FontChinese20 = new com.itextpdf.text.Font(bfChinese, 20, com.itextpdf.text.Font.BOLD);
com.itextpdf.text.Font FontChinese18 = new com.itextpdf.text.Font(bfChinese, 18, com.itextpdf.text.Font.BOLD);
com.itextpdf.text.Font FontChinese16 = new com.itextpdf.text.Font(bfChinese, 16, com.itextpdf.text.Font.BOLD);
com.itextpdf.text.Font FontChinese12 = new com.itextpdf.text.Font(bfChinese, 12, com.itextpdf.text.Font.NORMAL);
com.itextpdf.text.Font FontChinese11Bold = new com.itextpdf.text.Font(bfChinese, 11, com.itextpdf.text.Font.BOLD);
com.itextpdf.text.Font FontChinese9 = new com.itextpdf.text.Font(bfChinese, 9, com.itextpdf.text.Font.ITALIC);
com.itextpdf.text.Font FontChinese9Normal = new com.itextpdf.text.Font(bfChinese, 9, com.itextpdf.text.Font.NORMAL);
//table1
PdfPTable table1 = new PdfPTable(3);
PdfPCell cell11 = new PdfPCell(new Paragraph("大家好",FontChinese20));
//垂直居中
cell11.setVerticalAlignment(Element.ALIGN_MIDDLE);
//水平居中
cell11.setHorizontalAlignment(Element.ALIGN_CENTER);
//去掉边框
cell11.setBorder(0);
Image image1 = Image.getInstance("d:/xxx1.jpg");
Image image2 = Image.getInstance("d:/xxx2.jpg");
//设置每列宽度比例
int width11[] = {35,40,25};
table1.setWidths(width11);
table1.getDefaultCell().setBorder(0);
table1.addCell(image1);
table1.addCell(cell11);
table1.addCell(image2);
//把table加入document
document.add(table1);
//加入空行
Paragraph blankRow1 = new Paragraph(18f, " ", FontChinese18);
document.add(blankRow1);
//可以继续增加其他table和空行来布局,很好编辑
document.close();
4.5集成依赖jar包
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.10</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>kernel</artifactId>
<version>7.1.2</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>layout</artifactId>
<version>7.1.2</version>
</dependency>
4.6核心代码
* @return
* @throws IOException
*/
private void creatProductCert(CertificateQueryVo certQueryVo,File templateFile,PdfWriter writer,String qrTempUrl){
//证书抬头内容
String headContent = getHeadContent(certQueryVo);
//证书主体内容
String bodyContent = getBodyContent(certQueryVo);
//证书内容最后一句话
String footContent = getFootContent(certQueryVo);
Document document = null;
try (PdfDocument pdfDoc = new PdfDocument(writer)) {
PageSize p = new PageSize(PageSize.A4.getHeight(), PageSize.A4.getWidth());
pdfDoc.setDefaultPageSize(p);
document = new Document(pdfDoc);
File fontFile = getFondTempFile();
PdfFont font = PdfFontFactory.createFont(
fontFile.getPath() + ",1",
BaseFont.IDENTITY_H,
BaseFont.NOT_EMBEDDED);
Image tImgCover = new Image(
ImageDataFactory.create(templateFile.getPath()));
tImgCover.scaleAbsolute(PageSize.A4.getHeight(), PageSize.A4.getWidth());
document.add(tImgCover.setFixedPosition(0, 0));
Paragraph paragraph= new Paragraph(headContent);
paragraph.setFirstLineIndent(0);
paragraph.setFont(font);
paragraph.setFontSize(20);
paragraph.setBold();
paragraph.setFixedPosition(65, 370, 720);
document.add(paragraph);
Paragraph paragraphBody= new Paragraph(bodyContent);
paragraphBody.setFirstLineIndent(45);
paragraphBody.setFont(font);
paragraphBody.setFontSize(20);
paragraphBody.setBold();
paragraphBody.setWidth(720);
paragraphBody.setRelativePosition(30, 190, 0, 0);
document.add(paragraphBody);
Paragraph paragraphFoot= new Paragraph(footContent);
paragraphFoot.setFirstLineIndent(45);
paragraphFoot.setFont(font);
paragraphFoot.setFontSize(20);
paragraphFoot.setBold();
paragraphFoot.setWidth(720);
paragraphFoot.setFixedPosition(65, 210, 720);
document.add(paragraphFoot);
//二维码图片目录
mkTempDir(qrPath);
//二维码中的链接
String qrTempContent = qrContent.concat(certQueryVo.getCertNum());
//生成二维码
CodeUtil.getBarCode(qrTempContent, qrTempUrl);
Image qrcode = new Image(
ImageDataFactory.create(qrTempUrl));
qrcode.scaleAbsolute(80, 80);
document.add(qrcode.setFixedPosition(110, 125));
Paragraph paragraphCert= new Paragraph("证书编号:" + certQueryVo.getCertNum());
paragraphCert.setFirstLineIndent(45);
paragraphCert.setFont(font);
paragraphCert.setFontSize(12);
paragraphCert.setBold();
paragraphCert.setWidth(720);
paragraphCert.setFixedPosition(65, 100, 720);
document.add(paragraphCert);
Paragraph paragraphCertifi= new Paragraph("验证网站:" + certificateWebUrl);
paragraphCertifi.setFirstLineIndent(45);
paragraphCertifi.setFont(font);
paragraphCertifi.setFontSize(12);
paragraphCertifi.setBold();
paragraphCertifi.setWidth(720);
paragraphCertifi.setFixedPosition(65, 80, 720);
document.add(paragraphCertifi);
Image stampImg = new Image(
ImageDataFactory.create(new URL(certQueryVo.getCertStamp())));
stampImg.scaleAbsolute(120, 120);
document.add(stampImg.setFixedPosition(600, 80));
Paragraph paragraphCertQuer= new Paragraph(certQueryVo.getOrgName());
paragraphCertQuer.setFirstLineIndent(45);
paragraphCertQuer.setFont(font);
paragraphCertQuer.setFontSize(16);
paragraphCertQuer.setBold();
paragraphCertQuer.setTextAlignment(TextAlignment.CENTER);
paragraphCertQuer.setFixedPosition(490, 100, 300);
document.add(paragraphCertQuer);
String date = DateUtils.numDateTurnToChinese(certQueryVo.getAwardTime());
Paragraph paragraphDate = new Paragraph(date);
paragraphDate.setFirstLineIndent(45);
paragraphDate.setFont(font);
paragraphDate.setFontSize(16);
paragraphDate.setBold();
paragraphDate.setTextAlignment(TextAlignment.CENTER);
paragraphDate.setFixedPosition(490, 80, 300);
document.add(paragraphDate);
document.close();
} catch (Exception e) {
logger.error("生成证书时异常,{}", e);
} finally {
try {
if (writer != null) {
writer.close();
}
if (document != null) {
document.close();
}
handDeleteFile(qrTempUrl);
} catch (Exception e) {
logger.error("生成证书关闭writer异常,{}", e);
}
}
}