最近项目需要用到有关PDF的操作,网上查了很多资料,觉得itext7比较符合我的需求,在此做一下简单地记录。
itext7相对于itext5做了很多改动,所以如果使用itext5的话本文并不适用。不过官网有着非常详细的API文档以及演示,具体可以去官网查看。
官网链接
我是使用maven引入的,并且是全家桶,因为第一次使用,这样比较方便。
<!-- PDF操作,itext7全家桶 --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itext7-core</artifactId> <version>7.1.11</version> <type>pom</type> </dependency>封装的PDF工具类,导包直接选中itext相关的就好,注意IOException不要导itext的(第一次无脑导的后果…)
public class PDFUtils { /** * * @Title: getDefaultFont * @Description: 获取系统自带字体,如未安装可先安装或引入 * @return PdfFont * @author yanghainan * @date 2020年7月8日上午10:46:39 */ public static PdfFont getDefaultFont() { try { // return PdfFontFactory.createFont("C://windows//fonts//simsun.ttc,1", PdfEncodings.IDENTITY_H, false);// 引用系统字体 return PdfFontFactory.createFont("STSong-Light", "UniGB-UCS2-H", false);// 使用itext字体 } catch (IOException e) { // 记录日志 e.printStackTrace(); } return null; } /** * * @Title: getImportFont * @Description: 获取引入的其他字体 (支持Linux系统) * @return PdfFont * @author yanghainan * @date 2020年7月8日上午10:13:01 */ public static PdfFont getImportFont(String fontName) { // 获取resource下文件夹路径 String path = "/template/"; String fontPath = path + fontName; try { // 处理中文乱码(支持Linux系统) FontProgram fontProgram = FontProgramFactory.createFont(fontPath, false); PdfFont font = PdfFontFactory.createFont(fontProgram, PdfEncodings.IDENTITY_H, false); return font; } catch (IOException e) { // 记录日志 e.printStackTrace(); } return null; } /** * 替换PDF文本表单域变量 * * @param templatePdfPath * 要替换的pdf全路径 * @param params * 替换参数 * @param destPdfPath * 替换后保存的PDF全路径 * * @throws FileNotFoundException * @throws IOException */ public static final void replaceTextFieldPdf(String templatePdfPath, String destPdfPath, Map<String, String> params) { PdfDocument pdf = null; try { // 判断文件是否存在 File file = new File(destPdfPath); if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } // 有参数才替换 if (params != null && !params.isEmpty()) { pdf = new PdfDocument(new PdfReader(templatePdfPath), new PdfWriter(destPdfPath)); 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) { formField.setFont(getImportFont("SourceHanSansCN-Regular.ttf")).setValue(params.get(param)); // 替换值 // formField.setFont(getDefaultFont()).setValue(params.get(param)); // 替换值 } } form.flattenFields();// 锁定表单,不让修改 } } catch (IOException e) { e.printStackTrace(); } finally { if (pdf != null) { pdf.close(); } } } /** * 替换PDF图片表单域(文本)变量,1、获取表单域的大小;2、根据表单域的位置,确定图片的位置;3、如果图片的宽或者高大于表单域,则等比压缩图片。 * * @param templatePdfPath * 要替换的pdf全路径 * @param params * 替换参数 * @param destPdfPath * 替换后保存的PDF全路径 * * @throws FileNotFoundException * @throws IOException */ public static final void replaceImageFieldPdf(String templatePdfPath, String destPdfPath, Map<String, String> params) throws FileNotFoundException, IOException { PdfDocument pdf = new PdfDocument(new PdfReader(templatePdfPath), 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) { replaceFieldImage(params, pdf, param, formField); // 替换图片 } } form.flattenFields();// 锁定表单,不让修改 } pdf.close(); } /** * 替换域中的图片 * * @param params * @param pdf * @param param * @param formField * @throws MalformedURLException */ private static final void replaceFieldImage(Map<String, String> params, PdfDocument pdf, String param, PdfFormField formField) throws MalformedURLException { String value = params.get(param); String[] values = value.split("\\|"); Rectangle rectangle = formField.getWidgets().get(0).getRectangle().toRectangle(); // 获取表单域的xy坐标 PdfCanvas canvas = new PdfCanvas(pdf.getPage(Integer.parseInt(values[0]))); ImageData image = ImageDataFactory.create(values[1]); 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); } } /** * * @Title: addWatermark * @Description: 添加文字水印 * @param srcPdfPath 原文件路径 * @param destPdfPath 替换后保存的PDF全路径 * @param watermarkText 水印 * @throws FileNotFoundException * @throws IOException void * @author yanghainan * @date 2020年7月8日上午8:48:15 */ @SuppressWarnings("resource") public static final void addWatermark(String srcPdfPath, String destPdfPath, String watermarkText) throws FileNotFoundException, IOException { // 判断文件是否存在 File f1 = new File(srcPdfPath); if (!f1.exists()) { return; } // 判断文件是否存在 File f2 = new File(destPdfPath); if (!f2.getParentFile().exists()) { f2.getParentFile().mkdirs(); } PdfDocument pdfDoc = new PdfDocument(new PdfReader(srcPdfPath), new PdfWriter(destPdfPath)); pdfDoc.addEventHandler(PdfDocumentEvent.END_PAGE, new IEventHandler() { @Override public void handleEvent(Event event) { PdfDocumentEvent docEvent = (PdfDocumentEvent) event; PdfDocument pdfDoc = docEvent.getDocument(); PdfPage page = docEvent.getPage(); PdfFont font = null; font = getImportFont("STSong.ttf"); // 要显示中文水印的话,需要设置中文字体 PdfCanvas canvas = new PdfCanvas(page); PdfExtGState gs1 = new PdfExtGState(); gs1.setFillOpacity(0.7f); // 水印透明度 canvas.setExtGState(gs1); new Canvas(canvas, pdfDoc, page.getPageSize()).setFontColor(ColorConstants.LIGHT_GRAY) // 颜色 .setFontSize(60) // 字体大小 .setFont(font) // 字体的格式 即导入的字体包 // 水印的内容(中英文都支持) 坐标(例如:298f, 421f) 当前页数 最后的值为倾斜度(170) .showTextAligned(new Paragraph(watermarkText), 298, 421, pdfDoc.getPageNumber(page), TextAlignment.CENTER, VerticalAlignment.MIDDLE, 45); } }); pdfDoc.close(); } /** * * @Title: pdfMerger * @Description: PDF合并 * @param paths 需要合并的所有文件路径(路径+名称) * @param outputPath 合并后的文件路径(路径+名称) * void * @author yanghainan * @date 2020年7月3日下午3:21:00 */ public static boolean pdfMerger(List<String> paths, String outputPath) { if (paths == null || paths.isEmpty() || StringUtils.isBlank(outputPath)) { return false; } // 首页与其他页创建方式不同,所以需要创建两个 PdfDocument firstSourcePdf = null; PdfDocument secondSourcePdf = null; // 合并需要的工具类 PdfMerger merger = null; try { for (int i = 0; i < paths.size(); i++) { if (i == 0) { PdfWriter pdfWriter = new PdfWriter(outputPath); // 启用完全压缩 // pdfWriter.isFullCompression(); firstSourcePdf = new PdfDocument(new PdfReader(paths.get(i)), pdfWriter); merger = new PdfMerger(firstSourcePdf); continue; } secondSourcePdf = new PdfDocument(new PdfReader(paths.get(i))); merger.merge(secondSourcePdf, 1, secondSourcePdf.getNumberOfPages()); secondSourcePdf.close(); } return true; } catch (Exception e) { e.printStackTrace(); } finally { // 关闭流 if (firstSourcePdf != null) { firstSourcePdf.close(); } if (secondSourcePdf != null) { secondSourcePdf.close(); } if (merger != null) { merger.close(); } } return false; } /** * * @Title: downPdf * @Description: 文件下载 * @param response * @param reviewName * @param outputFilePath * @return String * @author yanghainan * @date 2020年7月6日下午4:52:48 */ public static String downPdf(HttpServletResponse response, String reviewName, String outputFilePath) { File file = new File(outputFilePath); if (!file.exists()) { return "下载文件不存在"; } // 设置格式 try { response.reset(); response.setContentType("application/octet-stream"); response.setCharacterEncoding("utf-8"); response.setContentLength((int) file.length()); response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(reviewName, "UTF-8")); } catch (UnsupportedEncodingException e1) { e1.printStackTrace(); } // 文件写出 try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));) { byte[] buff = new byte[1024]; OutputStream os = response.getOutputStream(); int i = 0; while ((i = bis.read(buff)) != -1) { os.write(buff, 0, i); os.flush(); } } catch (Exception e) { return "下载失败"; } finally { // 删除服务器文件 forceDelete(file); } return "下载成功"; } /** * * @Title: forceDelete * @Description: 强制删除文件 * @param f * @return boolean * @author yanghainan * @date 2020年7月7日下午3:05:14 */ public static boolean forceDelete(File f) { boolean result = false; int tryCount = 0; while (!result && tryCount++ < 10) { // System.gc(); result = f.delete(); } return result; } /** * * @Title: forceBatchDelete * @Description: 强制删除文件(批量) * @param paths * @return int * @author yanghainan * @date 2020年7月7日下午3:09:37 */ public static int forceBatchDelete(List<String> paths) { if (paths == null || paths.isEmpty()) { return 0; } int result = 0; for (int i = 0; i < paths.size(); i++) { boolean delete = forceDelete(new File(paths.get(i))); if (delete) { result++; } } return result; } }使用Adobe Acrobat Pro DC 可以很方便的设置PDF模板,这样我们只需要将数据填充进去就可以了。如果没有安装可以去我的云盘下载:
下载链接
访问码:ynj6
如下是项目中用到的一个模板:
光有模板是不够的,还需要对模板进行表单设置:
默认选择就好,成功后会变成这样:
双击字段可以修改属性:
注意这里的名称就是javaBean的属性,需要按照相对应的设置。本文工具类里使用的是Map集合,设置对应的key就好。
运行后:
刚搞好了数据填充生成PDF,又加了个合并PDF的需求,不过好在很简单.
官网Demo import com.itextpdf.kernel.pdf.PdfDocument; import com.itextpdf.kernel.pdf.PdfReader; import com.itextpdf.kernel.pdf.PdfWriter; import com.itextpdf.kernel.utils.PdfMerger; import java.io.IOException; public class PdfMerge { private static final String FILE1 = "/uploads/first.pdf"; private static final String FILE2 = "/uploads/second.pdf"; private static final String OUTPUT_FOLDER = "/myfiles/"; public static void main(String args[]) throws IOException { PdfDocument pdfDocument = new PdfDocument(new PdfReader(FILE1), new PdfWriter(OUTPUT_FOLDER + "merged.pdf")); PdfDocument pdfDocument2 = new PdfDocument(new PdfReader(FILE2)); PdfMerger merger = new PdfMerger(pdfDocument); merger.merge(pdfDocument2, 1, pdfDocument2.getNumberOfPages()); pdfDocument2.close(); pdfDocument.close(); } } 我自己写的小Demo private static final String FILE1 = "F:\\PDFtemplate\\测试1.pdf"; private static final String FILE2 = "F:\\PDFtemplate\\测试2.pdf"; private static final String FILE3 = "F:\\PDFtemplate\\测试3.pdf"; private static final String OUTPUT_FOLDER = "F:\\PDFtemplate\\"; public static void main(String[] args) throws FileNotFoundException, IOException { List<String> paths = new ArrayList<String>(); paths.add(FILE1); paths.add(FILE2); paths.add(FILE3); PDFUtils.pdfMerger(paths, OUTPUT_FOLDER + "测试.pdf"); }具体效果就是合并为一个文件,就不再演示了。 本人也是初学,如有错漏欢迎批评指正。