对itext的使用也是从itext 5开始,一点一点的拼凑出来的。做到后面发现业务上需要对图片进行处理,一直没有找到比较好的解决办法,最后入了itext 7的坑。
先说一说 itext 7的优点吧。代码量比较少,很简洁。对于图片的处理也比较优雅。哈哈哈哈哈哈哈哈说的太闷骚了

废话不多说,开干吧。刚吧嘚
首先pom导入itext 7大礼包:
<dependencies>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext7-core</artifactId>
<version>7.1.8</version>
<type>pom</type>
</dependency>
</dependencies>
然后是PDF的主方法,参数是模板,response(返回文件给前端要用到),和String字符串格式的数据。
/**
* @param MultipartFile 设置好文本域的模板文件
* @param str 填充的数据
* @throws IOException
*/
@PostMapping("/printPDF")
public void interviewReportPDF(@RequestParam MultipartFile templateFile, HttpServletResponse response,@RequestParam String str) {
Map<String, Object> entity = new HashMap<>();
File file = null;
String newPDFPath = null;
File template = null;
try {
entity = objectToMap(str);//把对象转成map
newPDFPath = "C:\\Users\\Administrator\\Desktop"+System.currentTimeMillis()+".pdf";//在本地生成文件,后续可以做到删除这些缓存文件
template =MultipartFileToFile.multipartFileToFile(templateFile);
file = replaceImageFieldPdf(template, newPDFPath, entity);
} catch (Exception e) {
e.printStackTrace();
}
//返回给前端
response.reset();
response.setHeader("ETag", String.valueOf(System.currentTimeMillis()));
response.setDateHeader("Last-Modified", System.currentTimeMillis());
response.setHeader("Access-Control-Allow-Origin", "*");
FileInputStream in = null;
OutputStream out = null;
try {
in = new FileInputStream(file);
out = response.getOutputStream();
long fileLen = in.available();
response.setHeader("Content-Length", String.valueOf(fileLen));
int len;
byte[] bs = new byte[1024];
while ((len = in.read(bs)) != -1) {
out.write(bs, 0, len);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
}
}
}
}
主方法里面第一个调用的就是objectToMap(str);//把对象转成map,这里面主要是将Object转成Map,然后根据业务对数据做处理。可以根据自己的需求定制。
/**
* 字段处理
* 1.将Object对象里面的属性和值转化成Map对象
* 2.时间转换,
* 3.签名列表转换
* 4.当事人信息转换
*
* @param obj
* @return
* @throws IllegalAccessException
*/
public Map<String, Object> objectToMap(String str) throws IllegalAccessException {
Map<String, Object> map = new HashMap<String, Object>();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
map=(Map<String, Object>)JSONObject.parse(str);
for(Map.Entry<String,Object> m:map.entrySet()){
boolean time = m.getKey().startsWith("time");
if(time){
Long ll = Long.parseLong(m.getValue().toString());
String s = DateUtils.timestarmpToStrSDF(ll, DateUtils.yyyy_MM_dd_wzsj);
map.put(m.getKey(),s);
}
}
//对签名列表做处理
if (map.get("signList") != null) {
List<Map<String,Object>> signList = (List<Map<String,Object>>) map.get("signList");
for (int i = 0; i < signList.size(); i++) {
Map<String, Object> stringObjectMap = signList.get(i);
if(stringObjectMap.get("roleType")!=null){
map.put(stringObjectMap.get("roleType") + "Content", stringObjectMap.get("content"));
map.put("img_" + stringObjectMap.get("roleType") + "CasePeopleUrl",stringObjectMap.get("casePeopleUrl"));
if (stringObjectMap.get("timeSign") != null) {
Long ll = Long.parseLong(stringObjectMap.get("timeSign").toString());
String s = DateUtils.timestarmpToStrSDF(ll, DateUtils.yyyy_MM_dd_wzsj);
map.put(stringObjectMap.get("roleType") + "TimeSign",s);
}
}
}
}
//对当事人信息做处理
if (map.get("caseEnterprise") != null) {
Map<String,Object> caseEnterprise= (Map<String,Object>)map.get("caseEnterprise");
for(Map.Entry<String,Object> m:caseEnterprise.entrySet()){
map.put("caseEnterprise" +m.getKey().substring(0, 1).toUpperCase()+m.getKey().substring(1), m.getValue());
}
}
return map;
}
主方法里面接着是对前段传过来的文件做处理,我直接扒的一个工具类,将MultipartFile 转 File,以及删除本地生成的临时文件。
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class MultipartFileToFile {
/**
* MultipartFile 转 File
*
* @param file
* @throws Exception
*/
public static File multipartFileToFile(MultipartFile file) throws Exception {
File toFile = null;
if (file.equals("") || file.getSize() <= 0) {
file = null;
} else {
InputStream ins = null;
ins = file.getInputStream();
toFile = new File(file.getOriginalFilename());
inputStreamToFile(ins, toFile);
ins.close();
}
return toFile;
}
//获取流文件
private static void inputStreamToFile(InputStream ins, File file) {
try {
OutputStream os = new FileOutputStream(file);
int bytesRead = 0;
byte[] buffer = new byte[8192];
while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.close();
ins.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 删除本地临时文件
* @param file
*/
public static void delteTempFile(File file) {
if (file != null) {
File del = new File(file.toURI());
del.delete();
}
}
}
接下来的才是PDF填充文本域和图片等处理。
/**
* 替换PDF图片表单域(文本)变量,1、获取表单域的大小;2、根据表单域的位置,确定图片的位置;3、如果图片的宽或者高大于表单域,则等比压缩图片。
*
* @param templatePdfPath 要替换的pdf全路径
* @param params 替换参数
* @param destPdfPath 替换后保存的PDF全路径
* @throws FileNotFoundException
* @throws IOException
*/
public File replaceImageFieldPdf(File templatePdf, String destPdfPath, Map<String, Object> params) throws FileNotFoundException, IOException {
PdfDocument pdf = new PdfDocument(new PdfReader(templatePdf), new PdfWriter(destPdfPath));
if (params != null && !params.isEmpty()) {// 有参数才替换
PdfAcroForm form = PdfAcroForm.getAcroForm(pdf, true);
Map<String, PdfFormField> fields = form.getFormFields(); // 获取所有的表单域
for (String param : params.keySet()) {
PdfFormField formField = fields.get(param);
if (formField != null && params.get(param) != null) {
System.out.println(formField);
String[] s = param.split("_");
if (s[0].equals("img")) {
replaceFieldImage(params, pdf, param, formField); // 替换图片
} else {
formField.setValue(params.get(param).toString(), true); // 替换值,generateAppearance为true是设置单选框复选框的值
}
}
}
form.flattenFields();// 锁定表单,不让修改
}
pdf.close();
return new File(destPdfPath);
}
/**
* 替换域中的图片
*
* @param params
* @param pdf
* @param param
* @param formField
* @throws MalformedURLException
*/
private void replaceFieldImage(Map<String, Object> params, PdfDocument pdf, String param, PdfFormField formField) throws MalformedURLException {
String value = params.get(param).toString();
byte[] imgDate = fileFacade.getFileDate(value);
Rectangle rectangle = formField.getWidgets().get(0).getRectangle().toRectangle(); // 获取表单域的xy坐标
PdfCanvas canvas = new PdfCanvas(pdf.getPage(1));
ImageData image = ImageDataFactory.create(imgDate);
float imageWidth = image.getWidth();
float imageHeight = image.getHeight();
float rectangleWidth = rectangle.getWidth();
float rectangleHeight = rectangle.getHeight();
float tempWidth = 0;
float tempHeight = 0;
int result = 1; // 压缩宽度
if (imageWidth > rectangleWidth) {
tempHeight = imageHeight * rectangleWidth / imageWidth;
if (tempHeight > rectangleHeight) {
tempHeight = rectangleHeight;
result = 2; // 压缩高度
} else {
tempWidth = rectangleWidth;
tempHeight = imageHeight * rectangleWidth / imageWidth;
}
} else {
if (imageHeight > rectangleHeight) {
tempHeight = rectangleHeight;
result = 2;
} else {
result = 3;
}
}
float y = 0;
if (result == 1) { // 压缩宽度
y = rectangleHeight - tempHeight;
} else if (result == 3) { // 不压缩
y = rectangleHeight - imageHeight;
}
// y/=2; // 如果想要图片在表单域的上下对齐,这个值除以2就行。同理可以计算x的偏移
if (result == 1) {
canvas.addImage(image, rectangle.getX(), rectangle.getY() + y, tempWidth, false);
} else if (result == 2) {
canvas.addImage(image, rectangle.getX(), rectangle.getY(), tempHeight, false, false);
} else if (result == 3) {
canvas.addImage(image, rectangle.getX(), rectangle.getY() + y, false);
}
}
作为大自然的搬运工,要感谢各位大神引路。这个方法可以生成本地文件同时返回给前端,不管是测试模板还是用于打印填充PDF都比较方便。希望各位多多指教!
本文介绍使用iText7处理PDF文件,重点讲解如何优雅地处理图片和文本填充,包括MultipartFile转File、PDF表单域处理、图片等比压缩及位置调整等关键技术点。
1832





