后端笔记 - iText5处理pdf
1.引入依赖
<!-- 生成PDF的工具包 --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itextpdf</artifactId> <version>5.5.12</version> </dependency> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itext-asian</artifactId> <version>5.2.0</version> </dependency>
2.生成pdf
public static void createBillPdf(BillInfo billInfo, BankAccount bankAccount, String filePath, Customer customer, User saler) { df.setRoundingMode(RoundingMode.DOWN);// 舍去分的后一位 Document document = new Document(PageSize.A4, 36, 36, 72, 36); File file = null; try { BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED); // 设置字体样式 Font textFont = new Font(bfChinese, 10, Font.NORMAL);// 正常 Font textBoldFont = new Font(bfChinese, 10, Font.BOLD);// 加粗 Font boldFont = new Font(bfChinese, 18, Font.BOLD); // 加粗 file = File.createTempFile("temp", ".pdf"); if (!file.exists()) { if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } } PdfWriter.getInstance(document, new FileOutputStream(file)); document.open(); Paragraph p0 = new Paragraph(); Paragraph p1 = new Paragraph("", textFont); p1.setLeading(40); p0.add(p1); // 创建table对象 PdfPTable table = new PdfPTable(4); table.setSpacingBefore(10); table.setHorizontalAlignment(Element.ALIGN_CENTER); table.setTotalWidth(new float[] { 110, 110, 110, 110 }); // 设置列宽 table.setLockedWidth(true); // 锁定列宽 PdfPCell cell = new PdfPCell(); // 添加表格内容 cell = PDFUtil.mergeCol(Constants.EXCEL_BILL_TITLE, boldFont, 4); cell.setFixedHeight(60); cell.setVerticalAlignment(Element.ALIGN_CENTER); cell.setHorizontalAlignment(Element.ALIGN_CENTER); cell.setPaddingTop(25); table.addCell(cell); table.addCell(PDFUtil.mergeCol("客户:" + billInfo.getCompanyName(), textBoldFont, 4)); // 客户联系人 // 联系人 table.addCell(PDFUtil.mergeCol("客户联系人:" + (StringUtils.isBlank(billInfo.getContactsName()) ? "" : billInfo.getContactsName()), textBoldFont, 4)); // 联系人部门 table.addCell(PDFUtil.getPDFCell("部门", textBoldFont)); if (StringUtil.isNotBlank(customer.getContactDept())) { table.addCell(PDFUtil.getPDFCell(customer.getContactDept(), textBoldFont)); } else { table.addCell(PDFUtil.getPDFCell("", textBoldFont)); } // 联系人职务 table.addCell(PDFUtil.getPDFCell("联系人职务", textBoldFont)); if (StringUtil.isNotBlank(customer.getContactPosition())) { table.addCell(PDFUtil.getPDFCell(customer.getContactPosition(), textBoldFont)); } else { table.addCell(PDFUtil.getPDFCell("", textBoldFont)); } // 联系人座机 table.addCell(PDFUtil.getPDFCell("座机", textBoldFont)); if (StringUtil.isNotBlank(customer.getContactTelephone())) { table.addCell(PDFUtil.getPDFCell(customer.getContactTelephone(), textBoldFont)); } else { table.addCell(PDFUtil.getPDFCell("", textBoldFont)); } // 联系人手机 table.addCell(PDFUtil.getPDFCell("联系人手机", textBoldFont)); if (StringUtil.isNotBlank(billInfo.getPhone())) { table.addCell(PDFUtil.getPDFCell(billInfo.getPhone(), textBoldFont)); } else { table.addCell(PDFUtil.getPDFCell("", textBoldFont)); } table.addCell(PDFUtil.mergeCol("账单编号:" + billInfo.getBillNumber(), textBoldFont, 4)); String billStartTime = sdf.format(billInfo.getBillDate()); String billEndTime = sdf.format(DateUtil.getThisMonthFinal(billInfo.getBillDate())); table.addCell(PDFUtil.mergeCol("计费周期:" + billStartTime + "至" + billEndTime, textBoldFont, 4)); table.addCell(PDFUtil.getPDFCell("账号", textBoldFont)); table.addCell(PDFUtil.getPDFCell("单价(元/条)", textBoldFont)); table.addCell(PDFUtil.getPDFCell("计费条数(条)", textBoldFont)); table.addCell(PDFUtil.getPDFCell("金额(元)", textBoldFont)); if (!CollectionUtils.isEmpty(billInfo.getAccountInfos())) { for (DetailInfo detail : billInfo.getAccountInfos()) { table.addCell(PDFUtil.getPDFCell(detail.getAccountName(), textFont)); table.addCell(PDFUtil.getPDFCellRight(dff.format(detail.getUnitPrice()), textFont)); table.addCell(PDFUtil.getPDFCellRight(dft.format(detail.getFeeCount()), textFont)); table.addCell(PDFUtil.getPDFCellRight("¥" + df.format(detail.getFee()), textFont)); } } table.addCell(PDFUtil.getPDFCell("实际计费", textBoldFont)); table.addCell(PDFUtil.getPDFCellRight(dff.format(billInfo.getRealFeeInfo().getUnitPrice()), textFont)); table.addCell(PDFUtil.getPDFCellRight(dft.format(billInfo.getRealFeeInfo().getFeeCount()), textFont)); table.addCell(PDFUtil.getPDFCellRight("¥" + df.format(billInfo.getRealFeeInfo().getFee().setScale(2, BigDecimal.ROUND_UP)), textFont)); table.addCell(PDFUtil.getPDFCell("本期应付(大写金额)", textBoldFont)); table.addCell(PDFUtil.mergeCol(Money2ChineseUtil.convert(billInfo.getRealFeeInfo().getFee()), textBoldFont, 2)); table.addCell(PDFUtil.getPDFCellRight("¥" + df.format(billInfo.getRealFeeInfo().getFee().setScale(2, BigDecimal.ROUND_UP)), textFont)); table.addCell(PDFUtil.mergeCol( "出账日期:" + sdf.format(billInfo.getCreateDate()) + " 最后付款日期:" + sdf.format(billInfo.getFinalPayDate()), textBoldFont, 4)); table.addCell(PDFUtil.mergeCol("收款银行账号信息", textBoldFont, 4)); table.addCell(PDFUtil.mergeCol(bankAccount.getAccountName(), textFont, 4)); table.addCell(PDFUtil.mergeCol(bankAccount.getAccountBank(), textFont, 4)); table.addCell(PDFUtil.mergeCol(bankAccount.getBankAccount(), textFont, 4)); table.addCell(PDFUtil.mergeCol(bankAccount.getCompanyAddress(), textFont, 4)); table.addCell(PDFUtil.mergeCol("销售经理:" + billInfo.getSaleName(), textBoldFont, 4)); table.addCell( PDFUtil.mergeCol("经理座机:" + (saler == null || StringUtils.isBlank(saler.getContactPhone()) ? "" : saler.getContactPhone()), textBoldFont, 4)); table.addCell(PDFUtil.mergeCol("经理手机:" + billInfo.getSalePhone(), textBoldFont, 4)); table.addCell(PDFUtil.mergeCol("温馨提示:本单据为您电子对账单,请妥善保管。", textBoldFont, 4, 0)); p0.add(table); document.add(p0); // 关闭文档 document.close(); // 文件转写 int billTableH = (int) table.getTotalHeight(); File signFile = file; if (BillFlow.BILL_SIGNATURE_OPT_NEED) { int baseAbsY = (billTableH % 710 == 0) ? 0 : (710 - billTableH % 710); int signAbsX = 320, signAbsY = baseAbsY + 5 * 25; int pageNumber = (billTableH % 710 == 0) ? billTableH / 710 : billTableH / 710 + 1; if (signAbsY > 710) { pageNumber --; signAbsY -= 710; } signAbsY = IText5PdfUtil.calAndMovedY(signAbsY, 710, 50); URL signImg = loadMetaData(IText5Pdf4MergeUtil.class.getClassLoader(), Bill.BILL_SIGNATURE_OPT_IMG); signFile = IText5PdfUtil.addPdfImgSignature(file, null, signImg, signAbsX, signAbsY, new int[]{pageNumber}, 0.5f); } // 获取水印文件路径 URL logoImageURL = loadMetaData(IText5PdfUtil.class.getClassLoader(), "static/common/imgs/watermark.png"); addPdfImgMark(signFile, filePath, logoImageURL); } catch (Exception e) { logger.error("账单生成异常:", e); } finally { if (file != null) { file.delete(); } } }
3.写入印章
public static URL loadMetaData(ClassLoader classLoader, String path) { try { Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path) : ClassLoader.getSystemResources(path); UrlResource resource = new UrlResource(urls.nextElement()); return resource.getURL(); } catch (Exception e) { logger.error("", e); } return null; } /** * 方法描述:为PDF文档增加图片签章(这种写法图片只能位于文字之下,并且不能跨页) * @param doc 文档源,原文件即需要增加水印的文件 * @param imgPath 签章图片源路径 * @param coordinate 需要加图片的坐标 * @param scale 缩放百分比数值(如:35.5%, scale=35.5f) * @date 2023-09-20 15:50:39 */ public static void addPdfImgSignature(Document doc, String imgPath, int[] coordinate, float scale) { try { URL signatureURL = loadMetaData(IText5Pdf4MergeUtil.class.getClassLoader(), imgPath); Assert.notNull(signatureURL, "签章文件[" + imgPath + "]空"); Image img = Image.getInstance(signatureURL); img.scalePercent(scale); img.setAbsolutePosition(coordinate[0], coordinate[1]); doc.add(img); } catch (Exception e) { logger.error("签章:{} 添加失败. ", imgPath, e); } } /** * 方法描述:为PDF文档增加图片签章 * @param inFile 待处理文件 * @param tagSignFile 价签之后的目标文件 * @param imgUrl URL * @param x 签章x坐标 (坐标最下开始,图标也是以坐标从下往上渲染) * @param y 签章x坐标 * @param pageNum 目标页码 * @param scale 签章图缩放比例 * @return <File> | 输出加签之后的 */ public static File addPdfImgSignature(File inFile, File tagSignFile, URL imgUrl, int x, int y, int[] pageNum, float scale) { File signFile = tagSignFile; try { if (Objects.isNull(tagSignFile)) { signFile = File.createTempFile("tmp", ".pdf"); if (!signFile.exists()) { if (!signFile.getParentFile().exists()) { signFile.getParentFile().mkdirs(); } } } PdfReader reader = new PdfReader(inFile.getPath(), "PDF".getBytes()); PdfStamper stamp = new PdfStamper(reader, new FileOutputStream(signFile)); stamp.setRotateContents(false); Image img = Image.getInstance(imgUrl); img.setAbsolutePosition(x, y); img.scaleAbsoluteWidth(img.getWidth() * scale); img.scaleAbsoluteHeight(img.getHeight() * scale); for (int pn : pageNum) { PdfContentByte over = stamp.getOverContent(pn); over.addImage(img); } stamp.close(); reader.close(); } catch (Exception e) { logger.error("PDF签名失败", e); } return Objects.isNull(signFile) ? inFile : signFile; }
/**
* 方法描述: 计算渲染位置需要平移后的位置
* <p>
* 目标点在最后一页中的位置高度H:
* <br/> 1.当 pageH - imgW > signAbsY > imgW, 则无需移动;
* <br/> 2.当 signAbsY < imgW, 则向上移,至大于 imgW;
* <br/> 3.当 signAbsY <= pageH & signAbsY > pageH - imgW, 向下移动,至小于 pageH - imgW;
* <br/> 4.当 signAbsY > pageH, 对 signAbsY % pageH 进行递归判断, 整数页的高度 + calAndMovedY(signAbsY % pageH);
* </p>
* @param signAbsY 目标渲染点的高度位置
* @param pageH 每个页面内容的总高度
* @param imgW 图片宽度
* @return {@link int} 目标渲染位置需要平移后的位置
* @date 2023-09-21 14:31:57
*/
public static int calAndMovedY(int signAbsY, int pageH, int imgW) {
int minTop = pageH - imgW;
if (signAbsY > imgW && signAbsY < minTop) {
return signAbsY;
}
if (signAbsY < imgW) {
// 向上移,至大于 imgW
signAbsY = imgW + 5;
}
if (signAbsY > pageH - imgW) {
// 向下移动,至小于 pageH - imgW
signAbsY = pageH - imgW;
}
/*
// 不在最后1页时会渲染不上,这种情况暂时不处理
if (signAbsY <= pageH && signAbsY > pageH - imgW) {
// 向下移动,至小于 pageH - imgW
signAbsY = pageH - imgW;
}
if (signAbsY > pageH) {
// 对 signAbsY % pageH 进行递归判断, 整数页的高度 + calAndMovedY(signAbsY % pageH)
int subY = signAbsY % pageH;
signAbsY -= subY;
signAbsY += calAndMovedY(subY, pageH, imgW);
}
*/
return signAbsY;
}
4.写入水印
/** * 方法描述:为PDF文档增加水印, 不改变原文件情况下增加水印 * 如果原文件与输出文件地址相同,则原文件将增加水印 * @param inFile 文档源,原文件即需要增加水印的文件 * @param outPdfFile 带水印的文档保存路径 * @return 是否成功 * @date 2022-04-02 15:50:39 */ protected static boolean addPdfImgMark(File inFile, String outPdfFile, URL logoImageURL) { boolean result = false; PdfStamper stamp = null; PdfReader reader = null; File outFile = null; try { //若为同一文件 if (Objects.equals(inFile.getPath(), outPdfFile)) { outFile = inFile; inFile = new File(outFile.getParentFile(), System.currentTimeMillis() + ".pdf"); FileUtils.copyFile(outFile, inFile); } else { outFile = new File(outPdfFile); } if (!outFile.exists()) { if (!outFile.getParentFile().exists()) { outFile.getParentFile().mkdirs(); } } //读取流,会与下面写入流相冲突 reader = new PdfReader(inFile.getPath(), "PDF".getBytes()); stamp = new PdfStamper(reader, new FileOutputStream(outFile)); // // 加密码,设置权限,仅允许复制、打印 // String pwd = UUID.randomUUID().toString().replace("-", ""); // int permissions = PdfWriter.ALLOW_COPY | PdfWriter.ALLOW_PRINTING; // stamp.setEncryption(null, pwd.getBytes(), permissions, false); PdfGState gs1 = new PdfGState(); gs1.setFillOpacity(0.2f);// 透明度设置 int imgWidth = 20; int imgHeight = 480; Image img = Image.getInstance(logoImageURL);// 插入图片水印 img.setAbsolutePosition(imgWidth, imgHeight); // 坐标 img.setRotation(-20);// 旋转 弧度 img.setRotationDegrees(-10);// 旋转 角度 // img.scaleAbsolute(200,100);//自定义大小 img.scalePercent(21);// 依照比例缩放 int pageSize = reader.getNumberOfPages();// 原pdf文件的总页数 PdfContentByte under; for (int i = 1; i <= pageSize; i++) { under = stamp.getUnderContent(i);// 水印在之前文本下 under.setGState(gs1);// 图片水印 透明度 under.addImage(img);// 图片水印 } int imgWidth2 = 320; int imgHeight2 = 300; Image img2 = Image.getInstance(logoImageURL);// 插入图片水印 img2.setAbsolutePosition(imgWidth2, imgHeight2); // 坐标 img2.setRotation(-20);// 旋转 弧度 img2.setRotationDegrees(-10);// 旋转 角度 img2.scalePercent(21);// 依照比例缩放 for (int i = 1; i <= pageSize; i++) { under = stamp.getUnderContent(i);// 水印在之前文本下 under.setGState(gs1);// 图片水印 透明度 under.addImage(img2);// 图片水印 } result = true; } catch (Exception e) { logger.error("", e); } finally { try { inFile.deleteOnExit(); if (stamp != null) { stamp.close(); } if (reader != null) { reader.close(); } } catch (DocumentException | IOException e) { e.printStackTrace(); } } return result; }