项目中有个需求,对于已有的数据生成对应的发票pdf或者合同pdf,这些pdf具有一些特性,就是pdf有固定的格式,类似于表格,我们只要往表格里面填充数据即可。当然,也会涉及到签章,二维码等需求。
总体思路:
1.得到需要生成的pdf的初始模板,包含格式,只是不含数据
2.使用工具Adobe Acrobat,编辑pdf模板,在对应区域生成文本域,每个域都有自己的name
3.java中使用itextpdf对pdf进行操作,将对应的数据填写入pdf
(1)首先假设我们有一个发票的pdf模板(局部截图)
(2)下载Adobe Acrobat工具并编辑pdf
我这边使用的是Adobe Acrobat Pro DC,打开pdf后在工具栏中选择准备表单
我们可以清除默认生成的文本域,按自己的需求进行右键生成文本域大小,这里我生成了一个gfmc的文本域,在这个文本域属性中在外观中可以设置字体的大小,字体的类型;在选项中可以设置文字左对齐,剧中,右对齐,自动换行等。
当我们对pdf设置完需要的文本域时候,就要对pdf进行一个数据的填充工作。这里展示的是主要的方法代码,并不是整个流程的逻辑代码。
1.获取PdfReader对象
PdfReader reader = new PdfReader(template);//template是模板pdf文件相对路径
2.获取PdfStamper对象,目的是读取模板pdf,写入新的目标文件
File deskFile = new File(filePath);//filePath是将生成的文件
PdfStamper stamp = new PdfStamper(reader, new FileOutputStream(deskFile));
3.获取模板文件中的文本域,并写入数据到新文件中
AcroFields form = stamp.getAcroFields();
form.setField("gfmc", "这是一个购方名称");
4.自定义字体(按需)
(1)加载字体
其中TARGET_COUR_FONT_PATH为字体文件路径
BaseFont courBf = BaseFont.createFont(TARGET_COUR_FONT_PATH, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
(2)文本域赋值并设置字体
其中chunkStr表示文本内容,size1字体大小,size2行距,property为文本域名称
public static void setFieldAndFont(BaseFont bf, PdfStamper stamp, AcroFields form, String chunkStr, Integer size1, Integer size2, String property) {
try {
Font font = new Font(bf, size1,-1,new BaseColor(0,0,0));
List list = form.getFieldPositions(property);
int page = list.get(0).page;
PdfContentByte pdfContentByte = stamp.getOverContent(page);
ColumnText columnText = new ColumnText(pdfContentByte);
Rectangle rectangle = list.get(0).position;
columnText.setSimpleColumn(rectangle);
Chunk chunk = null;
chunk = new Chunk(chunkStr);
Paragraph paragraph = new Paragraph(size2, chunk);
columnText.addText(paragraph);
paragraph.setFont(font);
columnText.addElement(paragraph);
columnText.go();
} catch (DocumentException e) {
e.printStackTrace();
}
}
5.添加二维码
这里用到的是com.google.zxing
com.google.zxing
core
3.3.3
com.google.zxing
javase
3.3.3
public static void invoiceEwm(PdfStamper stamp, String content) throws Exception {
if(content == null || "".equals(content)){
return;
}
BufferedImage image = createQrCodeBufferdImage(content, 70, 70);
//PdfGState gs = new PdfGState();
PdfContentByte waterMar = stamp.getOverContent(1);// 在内容上方加水印
//waterMar = stamp.getUnderContent(1);//在内容下方加水印
// 设置图片透明度为0.2f
//gs.setFillOpacity(0.2f);
// 设置笔触字体不透明度为0.4f
//gs.setStrokeOpacity(0.4f);
// 开始水印处理
waterMar.beginText();
// 设置透明度
//waterMar.setGState(gs);
// 设置水印字体参数及大小
//waterMar.setFontAndSize(BaseFont.createFont(BaseFont.HELVETICA, BaseFont.WINANSI, BaseFont.NOT_EMBEDDED), 60);
// 设置水印对齐方式 水印内容 X坐标 Y坐标 旋转角度
//waterMar.showTextAligned(Element.ALIGN_CENTER, "公司内部文件,请注意保密!", 500, 430, 45);
// 设置水印颜色
waterMar.setColorFill(BaseColor.GRAY);
// 创建水印图片
//com.itextpdf.text.Image itextimage = getImage(image, 99);
com.itextpdf.text.Image itextimage = com.itextpdf.text.Image.getInstance(image, new Color(128, 128, 128));
// 水印图片位置
itextimage.setAbsolutePosition(150, 380);
// 边框固定3
//itextimage.scaleToFit(200, 200);
// 设置旋转弧度
//image.setRotation(30);// 旋转 弧度
// 设置旋转角度
//image.setRotationDegrees(45);// 旋转 角度
// 设置等比缩放
itextimage.scalePercent(85);
// 自定义大小
//itextimage.scaleAbsolute(100, 100);
// 附件加上水印图片
waterMar.addImage(itextimage);
// 完成水印添加
waterMar.endText();
// stroke
waterMar.stroke();
}
/**
* 生成二维码
* @author swj
* @date 2019年6月11日下午4:39:16
* @param contents 二维码的内容
* @param width 二维码图片宽度
* @param height 二维码图片高度
*/
public static BufferedImage createQrCodeBufferdImage(String contents, int width, int height){
Hashtable hints= new Hashtable();
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
hints.put(EncodeHintType.MARGIN, 0);
BufferedImage image = null;
try {
BitMatrix bitMatrix = new MultiFormatWriter().encode(
contents, BarcodeFormat.QR_CODE, width, height, hints);
image = toBufferedImage(bitMatrix);
} catch (WriterException e) {
e.printStackTrace();
}
return image;
}
6.添加签章(盖章)
在指定位置添加图片
private static void invoiceDefaultSign(PdfStamper stamper, String filePath, int x, int y) throws Exception {
Resource resource = new ClassPathResource(filePath);
byte[] bytes = null;
bytes = FileUtils.is2ByeteArray(resource.getInputStream());
// 读图片
Image image = Image.getInstance(bytes);
// 获取操作的页面
PdfContentByte under = stamper.getOverContent(1);
// 根据域的大小缩放图片
image.scaleToFit(MAIN_SIGN_WIDTH, MAIN_SIGN_HEIGHT);
// 添加图片
image.setAbsolutePosition(x, y);
under.addImage(image);
}
/**
* 通过InputStream 转Byte数组
*
* @param is
* @return
* @throws IOException
*/
public static byte[] is2ByeteArray(InputStream is) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buff = new byte[100];
int rc = 0;
while((rc=is.read(buff, 0, 100))>0) {
baos.write(buff, 0, rc);
}
return baos.toByteArray();
}
7.关闭流
操作完成后,记得在最后关闭流,很简单,但是很重要
finally {
if (stamp != null) {
stamp.close();
}
if (reader != null) {
reader.close();
}
}
如果对pdf的文本域位置要求比较高的,需要逐步的进行微调,还是比较考验耐心的。涉及到数据的安全,一些效果图我就不上传了。如果生成了多个pdf,需要对pdf进行一个合并的操作,最后可以根据需求,上传至公司fastdfs文件服务器等操作。