思路:
需求:要实现一个前端上传文档模板。利用占位符将模板中想要填写的内容放在模板中。
然后直接生成。当模板想要修改时。直接更新模板即可。不需要修改代码。
呕心沥血一个月完成。希望多多指正问题。谢谢。
用什么模板呢
1.上传word文档模板。
(1)思路:使用word模板,使用占位符匹配需要替换的内容。
(2)问题:但是需要线上转pdf.这样在很多个模板同时生成时很浪费时间。
(3)改进:使用html模板。
2.上传html文档模板
(1)思路:使用html模板,使用占位符匹配需要替换的内容。
(2)问题:但是在遇到表格或者复杂的样式的时候,很难操作。并且展示成想要的样式。
(3)改进:使用pdf模板。
3.上传pdf模板
(1)问题:pdf可以读取到所有的文本内容。但是在不保存文件,使用流保存的时候。替换之后的文本无法再转成,将生成的文档保存。
(2)改进:使用pdf表单域。(这样就不需要占位符了。)
模板存放在哪里呢
1.模板内容放在数据库中。(1)问题:当数据多的时候读取会非常慢。
(2)改进:将文件转成流存放到mongo中。数据库中直接存放文件的fileId即可。
代码实现
一.pdf中找到所有的表单域
实现代码: /**
* 获取所有的表单域
* @param inputStream
* @return
*/
private static Set<String> getPdfAllFormFieldsByStream(InputStream inputStream){
try {
/* 打开已经定义好字段以后的pdf模板 */
PdfReader reader = new PdfReader(inputStream);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PdfStamper stamp = new PdfStamper(reader,baos);
/* 取出报表模板中的所有字段 */
AcroFields form = stamp.getAcroFields();
Set<String> fields = form.getFields().keySet();
stamp.close();
reader.close();
baos.close();
return fields;
}catch(Exception e){
e.printStackTrace();
}
return null;
}
二.pdf中插入图片
/**
* 指定位置给pdf插入图片
* @param pdfB
* @param imgB
* @param page
* @param x
* @param y
* @return
* @throws Exception
*/
public static byte[] insertImgToPdf(byte[] pdfB,byte[] imgB,int page,float x,float y) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 读取模板文件
InputStream input = new ByteArrayInputStream(pdfB);
PdfReader reader = new PdfReader(input);
PdfStamper stamper = new PdfStamper(reader,baos);
// 读图片
Image image = Image.getInstance(imgB);
// 获取操作的页面
PdfContentByte under = stamper.getOverContent(page);
// 添加图片
image.setAbsolutePosition(x, y);
under.addImage(image);
stamper.close();
reader.close();
return baos.toByteArray();
}
三..pdf中插入二维码图片
/**
* 指定位置给pdf插入二维码图片
* @param pdfB
* @param page
* @param x
* @param y
* @return
* @throws Exception
*/
public static byte[] insertQRCodeImageToPdf(byte[] pdfB,String content,int page,float x,float y,int width,int height) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 读取模板文件
InputStream input = new ByteArrayInputStream(pdfB);
PdfReader reader = new PdfReader(input);
PdfStamper stamper = new PdfStamper(reader,baos);
// 解决中文解析乱码
Map<EncodeHintType, Object> hints = new HashMap<>();
hints.put(CHARACTER_SET, "UTF-8");
// 读图片
BarcodeQRCode barcodeQRCode = new BarcodeQRCode(content, width, height, hints);
Image image = barcodeQRCode.getImage();
// 获取操作的页面
PdfContentByte under = stamper.getOverContent(page);
// 添加图片
image.setAbsolutePosition(x, y);
under.addImage(image);
stamper.close();
reader.close();
return baos.toByteArray();
}
四..pdf中定位某个表单域的位置。
这个可以用来定位图片。二维码,签章这些的位置。可以非常迅速的实现。需要注意的是:对于itext.x,y是指左下角是原点。y是距离底边距。如果要顶边距需要进行换算
/**
* 获取某个域的坐标
**/
public static Map<String,PdfInfo> getPdfTextPosition(InputStream inputStream, List<String> fields) throws Exception {
Map<String,PdfInfo> positions = new HashMap<>();
//* 打开已经定义好字段以后的pdf模板 */
PdfReader reader = new PdfReader(inputStream);
Rectangle pageSize = reader.getPageSize(1);
AcroFields acroFields = reader.getAcroFields();
for(String field:fields){
List<AcroFields.FieldPosition> pos = acroFields.getFieldPositions(field);
AcroFields.FieldPosition pitem = pos.get(0);
int pageNo = pitem.page;
Rectangle pRectangle = pitem.position;
positions.put(field,new PdfInfo(pageSize.getWidth(),pageSize.getHeight(),
new Position(pageNo,pRectangle.getLeft(),pRectangle.getBottom(),transform(pageSize,pRectangle.getTop()),
pRectangle.getWidth(),pRectangle.getHeight()
)));
}
reader.close();
return positions;
}
/**
* 坐标top需要计算
* @param pagesize
* @param y
* @return
*/
public static float transform(Rectangle pagesize, float y) {
return pagesize.getTop() - y;
}
public class PdfInfo {
/**
* pdf宽
*/
public float width;
/**
* pdf高
*/
public float height;
/**
* 位置信息
*/
public Position position;
}
public class Position {
/**
* 页数
*/
public int pageNo;
/**
* x坐标
*/
public float x;
/**
* y坐标
*/
public float y;
/**
* 距离顶边距(需要进行换算)
*/
public float top;
/**
* 宽(域)
*/
public float width;
/**
* 高(域)
*/
public float height;
}
五。困扰我两天的终极问题——如果要实现生成的文件可编辑。怎么办。而且是填写好的表单域不可编辑。没有填写内容的实现可编辑。
1.使用itext的removefield.填写一个删除一个表单域
bug:表单域填好的内容也给删除了。
2.我使用了一天的时间开始研究一个新的框架:
spire.pdf(一系列我这里不再赘述)
formWidgetCollection.get(f).setReadOnly(true);
最后出现的问题是:可以让表单域不可见。但是他会出现乱码。水印等等问题。
3.最后突然发现itext就可以实现(把我高兴坏了)
form.setFieldProperty(k, “fflags”, PdfFormField.FF_READ_ONLY, null);