后端笔记 - iText5处理pdf

发布时间 2023-09-27 14:26:38作者: yangdq

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/>&nbsp; 1.当 pageH - imgW > signAbsY > imgW, 则无需移动;
* <br/>&nbsp; 2.当 signAbsY < imgW, 则向上移,至大于 imgW;
* <br/>&nbsp; 3.当 signAbsY <= pageH & signAbsY > pageH - imgW, 向下移动,至小于 pageH - imgW;
* <br/>&nbsp; 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;
    }