Itext5生成PDF
Itext
Java操作pdf方法
- 通过Adobe Acrobat生成pdf form,通过Java设置form表单中的元素值进行数据填充。
- 读取pdf通过坐标进行填充数据,绘制成结果pdf。
- 通过itext代码绘制pdf。
Acrobat制作模板
先用Excel制作一个Excel模板,另存为PDF文件。
用Adobe Acrobat Pro DC打开PDF模板文件,打开表单功能,在指定地方添加文字域,模板即可制作完成。
Java代码填充AcroField
Java代码对Acrobat制作的PDF中的Form进行填充。
Maven坐标
<properties>
<itextpdf.version>5.5.6</itextpdf.version>
</properties>
<dependencies>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>${itextpdf.version}</version>
</dependency>
<dependency>
<groupId>com.itextpdf.tool</groupId>
<artifactId>xmlworker</artifactId>
<version>${itextpdf.version}</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
</dependencies>
Java代码
//设置字体
//BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
/*模板*/
PdfReader reader = new PdfReader("发票模板.pdf");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
PdfStamper ps = new PdfStamper(reader, bos);
/*使用中文字体 使用 AcroFields填充值的不需要在程序中设置字体,在模板文件中设置字体为中文字体 Adobe 宋体 std L*/
AcroFields s = ps.getAcroFields();
//设置表单的key-value值
//通过s.setFieldProperty("字段名", "textfont", BaseFont , null); 设置字段的字体格式或者在模板中修改样式
//s.setFieldProperty("checkNo","textfont",bfChinese,null); //只有第一个字段是变量,其他的都固定死,这样可以解决Acro默认字段时中文只显示最后一位的问题。
s.setField("checkNo", "20210204");
s.setField("$info$", "测试单位");
s.setField("$projectName_1$", "测试项目");
s.setField("$num_1$", "2");
s.setField("$bw_1$", "1");
s.setField("$amount_1$", "100000000");
s.setField("$total$", "壹佰万整");
s.setField("$bz_1$", "个");
s.setField("$SKR$", "测试人");
s.setField("$SKDW$", "测试单位");
s.setField("$year$", "2021");
s.setField("$month$", "11");
s.setField("$day$", "08");
// 设为true,设置为false后生成的pdf依然可编辑
ps.setFormFlattening(true);
ps.close();
/*输出到指定位置*/
FileOutputStream fos = new FileOutputStream("d:\\发票.pdf");
fos.write(bos.toByteArray());
输出展示
可能遇到的问题
批量设置字体
使用默认表单域中文字段一定要s.setFieldProperty("字段名", "textfont", BaseFont , null)设置中文字体,否则显示可能出问题。
居中显示等样式需要在adobe acrobat中显示。
给每个表单域都添加代码
s.getFields().forEach((k, v) -> {
s.setFieldProperty(k, "textfont", BaseFont, null);
s.setFieldProperty(k, "textsize", Float.valueOf("14"), null);
});
获取微软雅黑字体(非插件内字体)
/**获取微软雅黑字体**/
//获取项目路径
String projectPath = request.getSession().getServletContext().getRealPath("");
//ttf字体路径
String fontPath = projectPath+File.separator+"font"+File.separator+"microsoyh.ttf";
BaseFont bfChinese = BaseFont.createFont(fontPath, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
//批量设置字体
Map<String, AcroFields.Item> fields = s.getFields();
for(String k: fields.keySet()){
s.setFieldProperty(k, "textfont", bfChinese, null);
//s.setFieldProperty(k, "textsize", Float.valueOf("14"), null);
}
文字超出表单域
换行法
换行法就是增加单元格高度,先在adobe acrobat中设置表单文字域属性->选项->勾选多行,然后判断文字宽度是否大于表单域,如果大于,则展示的表单域高度增加。
/**
* 判断字体是否超出文本域长度
* @param filedVal 表单域的值
* @param filedName 表单域的key
* @param form 表单域实例
* @return 文本超过表单域的返回true
* @throws IOException
* @throws DocumentException
*/
public static boolean checkLength(String filedVal, String filedName, AcroFields form) throws IOException, DocumentException {
float fontSize = 12f;
boolean flag = false;
BaseFont baseFont = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
float textWidth = baseFont.getWidthPoint(filedVal, fontSize);
Rectangle position = form.getFieldPositions(filedName).get(0).position;
float textBoxWidth = position.getWidth();
if (textWidth > textBoxWidth) {
flag = true;
}
return flag;
}
/*使用,首先在adobe acrobat中设置表单文字域属性->选项->勾选多行*/
boolean b = checkLength("超出表单域宽度的金额.........","$amount_1$",s);
if (b){ //如果超过了,则修改表单域的大小,使其显示的高度+16
/*获取当前文本框的尺寸,返回的数据依次为左上右下(0,1,2,3)*/
PdfArray rect1 = s.getFieldItem("$amount_1$").getValue(0).getAsArray(PdfName.RECT);
rect1.set(1, new PdfNumber(rect1.getAsNumber(1).intValue() - 16));
}
s.setField("$amount_1$", "超出表单域宽度的金额.........");
缩小字体
单行,用缩小字体方法单行显示字体。
/**
* 判断字体是否超出文本域长度
* @param fieldVal 表单域的值
* @param filedName 表单域的key
* @param form 表单域实例
* @return 字体大小
* @throws IOException
* @throws DocumentException
*/
public static float checkLengthFrontForSingleLine(String fieldVal, String filedName, AcroFields form) throws IOException, DocumentException {
BaseFont baseFont = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
float fontSize = 12f;
Rectangle position = form.getFieldPositions(filedName).get(0).position;
float textBoxWidth = position.getWidth();
/*文本框高度*/
float textBoxHeight = position.getHeight();
/*文本单行行高*/
float ascent = baseFont.getFontDescriptor(baseFont.ASCENT, fontSize);
/*baseFont渲染后的文字宽度*/
float textWidth = baseFont.getWidthPoint(fieldVal, fontSize);
/*文本框高度只够写一行,并且文字宽度大于文本框宽度,则缩小字体*/
if (textBoxHeight < ascent * 1.6) {
while (textWidth > textBoxWidth) {
fontSize--;
textWidth = baseFont.getWidthPoint(fieldVal, fontSize);
}
}
return fontSize;
}
/*使用*/
float fsize= checkLengthFrontForSingleLine("超出表单域宽度的金额.........", "$amount_1$",s); //计算字体大小并返回
s.setFieldProperty("$amount_1$", "textsize", fsize , null); //设置字体大小
s.setField("$amount_1$", "超出表单域宽度的金额.........");
/**
* 判断字体是否超出文本域长度 - 多行
* @param fieldVal 表单域的值
* @param filedName 表单域的key
* @param form 表单域实例
* @return 字体大小
* @throws IOException
* @throws DocumentException
*/
public static float checkLengthFrontForMultiLine(String fieldVal, String filedName, AcroFields form) throws IOException, DocumentException {
BaseFont baseFont = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
float fontSize = 12f;
Rectangle position = form.getFieldPositions(filedName).get(0).position;
/*文本框宽度*/
float textBoxWidth = position.getWidth();
/*文本框高度*/
float textBoxHeight = position.getHeight();
/*文本单行文字行高*/
float ascent = baseFont.getFontDescriptor(baseFont.ASCENT, fontSize);
/*baseFont渲染后的文字宽度*/
float textWidth = baseFont.getWidthPoint(fieldVal, fontSize);
float linecount = textWidth / textBoxWidth; //文字宽度/文本框宽度计算大致需要行数
/*文本框高度只够写一行,并且文字宽度大于文本框宽度,则缩小字体*/
while (textBoxHeight < linecount * ascent * 1.4) { //当行数*文字高度*系数>文本框高度时,文字缩小(系数是因为存在换行和一些差额)
fontSize--; //文字缩小
textWidth = baseFont.getWidthPoint(fieldVal, fontSize); //重新计算文字总宽度
linecount = textWidth / textBoxWidth; //重新计算行数
}
return fontSize;
}
/*使用*/
float fsize= checkLengthFrontForMultiLine("超出表单域宽度的金额.........", "$amount_1$",s); //计算字体大小并返回
s.setFieldProperty("$amount_1$", "textsize", fsize , null); //设置字体大小
s.setField("$amount_1$", "超出表单域宽度的金额.........");
选框样式差号BUG
在制作模板时,放入一组Radio或者CheckBox,无论样式选择成什么,结果都是显示一个x号。
解决办法:把com.itextpdf.itextpdf核心包从5.5.6降到5.2.1/5.3.0/5.5.0/5.5.1/5.5.2/5.5.3/5.5.4,经过多次测试,发现就是不能超过5.5.5,一旦超过5.5.5就会出现差,到5.5.7时Radio会变成圆点样式但是CheckBox依然是差,所以我选择最接近5.5.6的5.5.4。
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.4</version>
</dependency>
<dependency>
<groupId>com.itextpdf.tool</groupId>
<artifactId>xmlworker</artifactId>
<version>5.5.6</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
通过选择框的名称和值来打勾
s.setField("RadioGroup", "1");
s.setField("CheckBox", "1");
最终样式
参考:
如果这篇文章对你有用,可以关注本人微信公众号获取更多ヽ(^ω^)ノ ~