package com.point.strategy.common; import com.drew.imaging.ImageMetadataReader; import com.drew.metadata.Directory; import com.drew.metadata.Metadata; import com.drew.metadata.exif.ExifDirectoryBase; import com.itextpdf.text.*; import com.itextpdf.text.Font; import com.itextpdf.text.Image; import com.itextpdf.text.Rectangle; import com.itextpdf.text.pdf.*; import lombok.extern.slf4j.Slf4j; import org.springframework.util.ResourceUtils; import com.itextpdf.text.pdf.PdfCopy; import com.itextpdf.text.pdf.PdfReader; import javax.imageio.ImageIO; import javax.imageio.ImageReader; import javax.imageio.stream.FileImageInputStream; import javax.swing.*; import java.awt.*; import java.awt.image.BufferedImage; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; import java.util.List; @Slf4j public class PdfFileHelper { /* 水印间隔 * */ private static int interval = -100; /** * 获取字体文件,优先从文件系统读取,然后从classpath读取 * 兼容JAR包环境和Docker环境 * @return FontFile对象,包含字体文件路径和临时文件 */ private static class FontFile { public File file; public boolean isTempFile = false; public FontFile(File file, boolean isTempFile) { this.file = file; this.isTempFile = isTempFile; } } /** * 获取SIMYOU字体文件,兼容JAR包和文件系统 * @return FontFile对象 * @throws IOException 字体文件获取失败 */ private static FontFile getSimYouFontFile() throws IOException { // 1. 优先从Docker环境的标准字体目录查找 String[] dockerFontPaths = { "/usr/share/fonts/SIMYOU.TTF", "/usr/local/share/fonts/SIMYOU.TTF", "/app/fonts/SIMYOU.TTF", "/app/data/fonts/SIMYOU.TTF" }; for (String fontPath : dockerFontPaths) { File fontFile = new File(fontPath); if (fontFile.exists() && fontFile.canRead()) { log.info("从文件系统找到字体文件: {}", fontPath); return new FontFile(fontFile, false); } } // 2. 从classpath中读取字体文件(适用于JAR包环境) try { InputStream fontStream = PdfFileHelper.class.getClassLoader().getResourceAsStream("SIMYOU.TTF"); if (fontStream != null) { // 创建临时文件 File tempFontFile = File.createTempFile("SIMYOU", ".TTF"); tempFontFile.deleteOnExit(); // JVM退出时删除临时文件 // 将资源流写入临时文件 try (InputStream input = fontStream; FileOutputStream output = new FileOutputStream(tempFontFile)) { byte[] buffer = new byte[8192]; int bytesRead; while ((bytesRead = input.read(buffer)) != -1) { output.write(buffer, 0, bytesRead); } } log.info("从classpath创建临时字体文件: {}", tempFontFile.getAbsolutePath()); return new FontFile(tempFontFile, true); } } catch (Exception e) { log.warn("从classpath读取字体文件失败: {}", e.getMessage()); } // 3. 尝试使用ResourceUtils(开发环境) try { File fontFile = ResourceUtils.getFile("classpath:SIMYOU.TTF"); if (fontFile.exists() && fontFile.canRead()) { log.info("使用ResourceUtils读取字体文件: {}", fontFile.getAbsolutePath()); return new FontFile(fontFile, false); } } catch (Exception e) { log.warn("使用ResourceUtils读取字体文件失败: {}", e.getMessage()); } throw new IOException("无法找到SIMYOU.TTF字体文件,请确保字体文件存在于文件系统或classpath中"); } /** * 获取BaseFont对象,自动处理字体文件路径 * @return BaseFont对象 * @throws Exception 字体创建失败 */ private static BaseFont getSimYouBaseFont() throws Exception { FontFile fontFile = getSimYouFontFile(); try { return BaseFont.createFont(fontFile.file.getAbsolutePath(), BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); } finally { // 如果是临时文件,清理资源 if (fontFile.isTempFile && fontFile.file.exists()) { fontFile.file.delete(); log.debug("清理临时字体文件: {}", fontFile.file.getAbsolutePath()); } } } /** * 归档章 单元格宽度 */ public static int cell_width = 45; /** * 归档章 单元格高度 */ public static int cell_height = 24; /** * 归档章 顶部距离(最小值38) */ public static int grid_top = 40; /** * 归档章 字体大小 */ public static int font_size = 9; /** * 页号字体大小 */ public static int pageno_font_size = 35; /** * PDF文件加盖归档章 * * @param srcFile 源文件名 * @param tarFile 目标文件名称 * @param pageIndex 需要盖章的页序号 默认传1 * @param textContent 归档章内容 * @param direction 归档章位置1表示右边, 2表示中间, 3表示左边 * @param scale 归档章放大系数,默认为1 */ public static boolean Seal(String srcFile, String tarFile, int pageIndex, String[][] textContent, String direction, double scale) { //目标文件存在,则先删除 File _tarFile = new File(tarFile); if (_tarFile.exists()) { _tarFile.delete(); } try { PdfReader pdfReader = new PdfReader(srcFile); PdfReader.unethicalreading = true; PdfStamper pdfStamper = new PdfStamper(pdfReader, new FileOutputStream(tarFile)); int numberOfPages = pdfReader.getNumberOfPages(); if (numberOfPages < 1) return false; Rectangle rectangle = pdfReader.getPageSizeWithRotation(pageIndex); PdfContentByte content = pdfStamper.getOverContent(pageIndex); //表格默认宽度 int grid_col_width = (int) Math.ceil(cell_width * scale * 1.0); //表格默认高度 int grid_col_height = (int) Math.ceil(cell_height * scale * 1.0); //表格行合计 int grid_rows = 2; //表格列合计 int grid_cols = 3; //起点位置(居中) float grid_left = (rectangle.getWidth() - grid_col_width * grid_cols) / 2; float text_left = (rectangle.getWidth() - grid_col_width * grid_cols) / 2; if ("1".equals(direction)) { grid_left = rectangle.getWidth() - grid_col_width * grid_cols - 70; text_left = rectangle.getWidth() - grid_col_width * grid_cols - 70; } else if ("3".equals(direction)) { grid_left = 10; text_left = 10; } BaseFont font = getSimYouBaseFont(); //BaseFont font = BaseFont.createFont("C:\\Users\\MI\\Desktop\\13\\SIMYOU.TTF", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); float width = rectangle.getWidth(); float height = rectangle.getHeight(); //全宗号,年度,件号 //机构或者问题,保管期限,总页数 PdfGState gs = new PdfGState(); gs.setFillOpacity(0.6f); content.setGState(gs); content.setGrayFill(0.3f); //画网格 for (int r = 0; r < grid_rows; r++) { for (int j = 0; j < grid_cols; j++) { //画网格矩形 content.setColorStroke(BaseColor.BLACK); float x = grid_left + grid_col_width * j; float y = height - grid_top - grid_col_height * (r + 1); content.rectangle(x, y, grid_col_width, grid_col_height); content.setLineWidth(1f); content.stroke(); //计算文本显示的矩形大小 float xxxx = font.getWidthPointKerned(textContent[r][j], (int) Math.ceil(font_size * scale * 1.0)); float yyyy = font.getAscentPoint(textContent[r][j], (int) Math.ceil(font_size * scale * 1.0)); int x1 = (int) ((grid_col_width - xxxx) / 2); int y1 = (int) ((grid_col_height - yyyy) / 2); //写入文本 content.beginText(); content.setColorFill(BaseColor.BLACK); content.setFontAndSize(font, (int) Math.ceil(font_size * scale * 1.0)); content.setTextMatrix(0, 0); //content.ShowTextAligned(Element.ALIGN_LEFT, textContent[r, j], text_left + grid_col_width*j + // 2, height - grid_top - grid_col_height*r + 5, 0); content.showTextAligned(Element.ALIGN_LEFT, textContent[r][j], x + x1, y + y1, 0); content.endText(); } } pdfStamper.close(); pdfReader.close(); return true; } catch (Exception ex) { System.out.println(ex.getMessage()); return false; } } /** * 当前号码 */ public static int CurrPageNo = 1; /** * 新绘制页码 * * @param srcFile * @param tarFile * @return */ public static boolean pageNo(String srcFile, String tarFile) { int currPageNo2 = 1; //目标文件存在,则先删除 File _tarFile = new File(tarFile); if (_tarFile.exists()) { _tarFile.delete(); } try { PdfReader pdfReader = new PdfReader(srcFile); PdfReader.unethicalreading = true; PdfStamper pdfStamper = new PdfStamper(pdfReader, new FileOutputStream(tarFile)); int numberOfPages = pdfReader.getNumberOfPages(); BaseFont font = getSimYouBaseFont(); // BaseFont font = BaseFont.createFont("C:\\Users\\MI\\Desktop\\13\\SIMYOU.TTF", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); PdfGState gs = new PdfGState(); gs.setFillOpacity(0.6f); for (int i = 1; i <= numberOfPages; i++) { Rectangle rectangle = pdfReader.getPageSizeWithRotation(i); float pageWidth = rectangle.getWidth(); float pageHeight = rectangle.getHeight(); PdfContentByte content = pdfStamper.getOverContent(i); content.setGState(gs); content.setGrayFill(0.6f); content.beginText(); content.setColorFill(BaseColor.BLACK); content.setFontAndSize(font, pageno_font_size); content.setTextMatrix(0, 0); if (currPageNo2 % 2 == 1) { content.showTextAligned(Element.ALIGN_LEFT, (currPageNo2++) + "", pageWidth - 40, pageHeight - 40, 0); } else { content.showTextAligned(Element.ALIGN_LEFT, (currPageNo2++) + "", 40, pageHeight - 40, 0); } content.endText(); } pdfStamper.close(); pdfReader.close(); return true; } catch (Exception ex) { System.out.println(ex.getMessage()); return false; } } /** * 获取pdf文件总页数 * PdfReader * * @param f * @return */ public static int getPdfPageCoun(String f) { try { PdfReader pdfReader = new PdfReader(f); PdfReader.unethicalreading = true; int numberOfPages = pdfReader.getNumberOfPages(); pdfReader.close(); return numberOfPages; } catch (Exception ex) { return 0; } } /** * 获取pdf文件总页数 * PdfReader * * @param f * @return */ public static int getPdfPageCounOrOther(String f) { try { PdfReader pdfReader = new PdfReader(f); PdfReader.unethicalreading = true; int numberOfPages = pdfReader.getNumberOfPages(); pdfReader.close(); return numberOfPages; } catch (Exception ex) { return 1; } } /** * Pdf文件加水印文字--居中 * * @param srcFile * @param markStr * @return */ public static boolean setWaterMark(String srcFile, String tarFile, String markStr, int fontSize, String color, int globalOblique) { //目标文件存在,则先删除 File _tarFile = new File(tarFile); if (_tarFile.exists()) { _tarFile.delete(); } try { PdfReader pdfReader = new PdfReader(srcFile); PdfReader.unethicalreading = true; PdfStamper pdfStamper = new PdfStamper(pdfReader, new FileOutputStream(tarFile)); int numberOfPages = pdfReader.getNumberOfPages(); BaseFont font = getSimYouBaseFont(); // BaseFont font = BaseFont.createFont("C:\\Users\\MI\\Desktop\\13\\SIMYOU.TTF", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); PdfGState gs = new PdfGState(); gs.setFillOpacity(0.6f); for (int page = 1; page <= numberOfPages; page++) { Rectangle rectangle = pdfReader.getPageSizeWithRotation(page); float pageWidth = rectangle.getWidth(); float pageHeight = rectangle.getHeight(); PdfContentByte content = pdfStamper.getOverContent(page); content.setGState(gs); content.setGrayFill(0.6f); //计算该文本某字体的矩形 float xxxx = font.getWidthPointKerned(markStr, fontSize); float yyyy = font.getAscentPoint(markStr, fontSize); content.beginText(); switch (color) { case "WHITE": content.setColorFill(BaseColor.WHITE); break; case "LIGHT_GRAY": content.setColorFill(BaseColor.LIGHT_GRAY); break; case "GRAY": content.setColorFill(BaseColor.GRAY); break; case "DARK_GRAY": content.setColorFill(BaseColor.DARK_GRAY); break; case "BLACK": content.setColorFill(BaseColor.BLACK); break; case "RED": content.setColorFill(BaseColor.RED); break; case "PINK": content.setColorFill(BaseColor.PINK); break; case "ORANGE": content.setColorFill(BaseColor.ORANGE); break; case "YELLOW": content.setColorFill(BaseColor.YELLOW); break; case "GREEN": content.setColorFill(BaseColor.GREEN); break; case "MAGENTA": content.setColorFill(BaseColor.MAGENTA); break; case "CYAN": content.setColorFill(BaseColor.CYAN); break; case "BLUE": content.setColorFill(BaseColor.BLUE); break; default: content.setColorFill(BaseColor.GREEN); } content.setFontAndSize(font, fontSize); content.setTextMatrix(0, 0); //角度 content.showTextAligned(Element.ALIGN_LEFT, markStr, (pageWidth - xxxx) / 2, (pageHeight - yyyy) / 2, globalOblique); content.endText(); } pdfStamper.close(); pdfReader.close(); return true; } catch (Exception ex) { System.out.println("水印添加失败:" + ex.getMessage()); return false; } } /** * 合并Pdf * * @param fileArray pdf图片数组[]{"d:/a/1.pdf","d:/a/2.pdf"} * @return */ public static boolean mergePdf(String[] fileArray, String targetFile) { if (fileArray == null || fileArray.length == 0) { log.warn("输入的PDF文件数组为空,无需合并。"); return true; // 或根据业务需求返回false } File target = new File(targetFile); if (target.exists()) { if (!target.delete()) { log.error("无法删除已存在的目标文件: {}", targetFile); return false; } } // 按文件名中的数字片段正序排序(如 xxx.001.pdf, xxx.002.pdf) try { log.info("mergePdf 接收到 {} 个PDF,按文件名数字片段正序合并。", (fileArray == null ? 0 : fileArray.length)); if (fileArray != null) { Arrays.sort(fileArray, new FileNameComparator()); for (int idx = 0; idx < fileArray.length; idx++) { log.info("排序后合并顺序 {} -> {}", idx + 1, fileArray[idx]); } } } catch (Exception ignore) { } Document document = null; PdfCopy pdfCopy = null; try { document = new Document(); pdfCopy = new PdfCopy(document, new FileOutputStream(targetFile)); document.open(); for (String filePath : fileArray) { if (filePath == null || filePath.trim().isEmpty()) { log.warn("发现一个空的PDF文件路径,已跳过。"); continue; } PdfReader pdfReader = null; try { pdfReader = new PdfReader(filePath); pdfCopy.addDocument(pdfReader); } catch(Exception e) { log.error("读取或合并文件 {} 时出错,已跳过此文件。", filePath, e); } finally { if (pdfReader != null) { pdfReader.close(); } } } return true; } catch (IOException | DocumentException e) { log.error("合并PDF过程中发生严重错误。", e); return false; } finally { // 5. 在最外层的 finally 块中关闭 Document 和 PdfCopy // Document.close() 会自动调用 PdfCopy.close() 和底层的流。 // 检查 document 是否为 null 并且已打开,以防在初始化时就发生异常。 if (document != null && document.isOpen()) { document.close(); } } } /* * @Description 图片转pdf * @Date 13:33 2019/6/10 * @Param [] * @return boolean **/ public static boolean image2Pdf(String source, String target) { try { BufferedImage img = ImageIO.read(new File(source)); PdfReader.unethicalreading = true; Image png1 = Image.getInstance(source); //通过文件路径获取image // float heigth = png1.getHeight(); // float width = png1.getWidth(); // 新增:读取图片的EXIF方向信息 int orientation = getExifOrientation(source); //new一个pdf文档 Document doc = new Document(null, 0, 0, 0, 0); if (img == null) { doc.setPageSize(new Rectangle(png1.getWidth(), png1.getHeight())); } else { doc.setPageSize(new Rectangle(img.getWidth(), img.getHeight())); } PdfWriter.getInstance(doc, new FileOutputStream(target)); //pdf写入 doc.open();//打开文档 // doc.newPage(); //在pdf创建一页 // int percent = getPercent2(heigth, width); // png1.setAlignment(Image.MIDDLE); // png1.scalePercent(percent+3);// 表示是原来图像的比例; // 新增:根据EXIF方向调整图片 adjustImageOrientation(png1, orientation); doc.add(png1); doc.close(); File mOutputPdfFile = new File(target); //输出流 if (!mOutputPdfFile.exists()) { mOutputPdfFile.deleteOnExit(); } mOutputPdfFile.createNewFile(); return true; } catch (Exception ex) { System.out.println("图片转pdf失败:" + ex.getMessage()); return false; } } // 新增:获取图片的EXIF方向信息 private static int getExifOrientation(String imagePath) { try { File imageFile = new File(imagePath); Metadata metadata = ImageMetadataReader.readMetadata(imageFile); // 遍历元数据目录,查找 EXIF 方向标签 for (Directory directory : metadata.getDirectories()) { // 使用 ExifDirectoryBase 的常量 if (directory.containsTag(ExifDirectoryBase.TAG_ORIENTATION)) { return directory.getInt(ExifDirectoryBase.TAG_ORIENTATION); } } } catch (Exception e) { System.err.println("读取EXIF方向失败: " + e.getMessage()); } return 1; // 默认方向:正常 } // 新增:根据EXIF方向调整图片 private static void adjustImageOrientation(Image image, int orientation) throws DocumentException { switch (orientation) { case 1: // 正常 break; case 3: // 旋转180度 image.setRotationDegrees(180); break; case 6: // 顺时针旋转90度 image.setRotationDegrees(90); break; case 8: // 逆时针旋转90度 image.setRotationDegrees(270); break; // 其他情况可以根据需要添加 } } /**** * 给PDF文件某些页增加马赛克 * @param srcFile 源文件 * @param tarFile 目标文件 * @param tarPages 需要增加马赛克的页号数组 * @return */ public static boolean PdfBlock(String srcFile, String tarFile, int[] tarPages) { //目标文件存在,则先删除 File _tarFile = new File(tarFile); if (_tarFile.exists()) { _tarFile.delete(); } try { PdfReader pdfReader = new PdfReader(srcFile); PdfReader.unethicalreading = true; PdfStamper pdfStamper = new PdfStamper(pdfReader, new FileOutputStream(tarFile)); //获取文件总页数 int numberOfPages = pdfReader.getNumberOfPages(); for (int page : tarPages) { if (page > numberOfPages) { break; } Rectangle rectangle = pdfReader.getPageSizeWithRotation(page); float pageWidth = rectangle.getWidth(); float pageHeight = rectangle.getHeight(); PdfContentByte content = pdfStamper.getOverContent(page); //保存状态 content.saveState(); content.setColorFill(BaseColor.BLACK); content.rectangle(0, 0, pageWidth, pageHeight); content.fill(); //还原状态 content.restoreState(); } pdfStamper.close(); pdfReader.close(); return true; } catch (Exception ex) { System.out.println(ex.getMessage()); return false; } } public static int getPercent(float h, float w) { int p = 0; float p2 = 0.0f; if (h > w) { p2 = 297 / h * 100; } else { p2 = 210 / w * 100; } p = Math.round(p2); return p; } public static int getPercent2(float h, float w) { int p = 0; float p2 = 0.0f; p2 = 530 / w * 100; p = Math.round(p2); return p; } /* pdf加水印平铺 * */ public static void waterMark(String inputFile, String outputFile, String waterMarkName) { try { PdfReader reader = new PdfReader(inputFile); PdfStamper stamper = new PdfStamper(reader, new FileOutputStream( outputFile)); BaseFont base = getSimYouBaseFont(); // BaseFont base = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED); Rectangle pageRect = null; PdfGState gs = new PdfGState(); gs.setFillOpacity(0.2f); gs.setStrokeOpacity(0.2f); int total = reader.getNumberOfPages() + 1; JLabel label = new JLabel(); FontMetrics metrics; int textH = 0; int textW = 0; label.setText(waterMarkName); metrics = label.getFontMetrics(label.getFont()); textH = metrics.getHeight(); textW = metrics.stringWidth(label.getText()); PdfContentByte under; for (int i = 1; i < total; i++) { pageRect = reader.getPageSizeWithRotation(i); under = stamper.getOverContent(i); under.saveState(); under.setGState(gs); under.beginText(); under.setFontAndSize(base, 30); // 水印文字成30度角倾斜 //你可以随心所欲的改你自己想要的角度 for (int height = interval + textH; height < pageRect.getHeight(); height = height + textH*3) { for (int width = interval + textW; width < pageRect.getWidth() + textW; width = width + textW*2) { under.showTextAligned(Element.ALIGN_LEFT , waterMarkName, width - textW, height - textH, 30); } } // 添加水印文字 under.endText(); } //说三遍 //一定不要忘记关闭流 //一定不要忘记关闭流 //一定不要忘记关闭流 stamper.close(); reader.close(); } catch (Exception e) { e.printStackTrace(); } } /* pdf加水印平铺 */ public static ByteArrayOutputStream waterMark(InputStream inputStream, String waterMarkName) { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try { PdfReader reader = new PdfReader(inputStream); PdfStamper stamper = new PdfStamper(reader, outputStream); BaseFont base = getSimYouBaseFont(); Rectangle pageRect = null; PdfGState gs = new PdfGState(); gs.setFillOpacity(0.2f); gs.setStrokeOpacity(0.2f); int total = reader.getNumberOfPages() + 1; JLabel label = new JLabel(); FontMetrics metrics; int textH = 0; int textW = 0; label.setText(waterMarkName); metrics = label.getFontMetrics(label.getFont()); textH = metrics.getHeight(); textW = metrics.stringWidth(label.getText()); PdfContentByte under; for (int i = 1; i < total; i++) { pageRect = reader.getPageSizeWithRotation(i); under = stamper.getOverContent(i); under.saveState(); under.setGState(gs); under.beginText(); under.setFontAndSize(base, 30); // 水印文字成30度角倾斜 //你可以随心所欲的改你自己想要的角度 for (int height = interval + textH; height < pageRect.getHeight(); height = height + textH*10) { for (int width = interval + textW; width < pageRect.getWidth() + textW; width = width + textW*9) { under.showTextAligned(Element.ALIGN_LEFT , waterMarkName, width - textW, height - textH, 30); } } // 添加水印文字 under.endText(); } stamper.close(); reader.close(); } catch (Exception e) { e.printStackTrace(); } return outputStream; } public static int getTifPageCount(String filePath) { int pageCount = 0; try { File file = new File(filePath); Iterator readers = ImageIO.getImageReadersByFormatName("TIFF"); ImageReader reader = readers.next(); reader.setInput(new FileImageInputStream(file)); pageCount = reader.getNumImages(true); reader.dispose(); } catch (IOException e) { e.printStackTrace(); } return pageCount; } public static void main1(String[] args) { String source = "C:\\Users\\MI\\Desktop\\13\\pdf\\b.pdf"; String target = "C:\\Users\\MI\\Desktop\\13\\pdf\\b-" + DateUtil.date2String(new Date(), 3) + ".pdf"; // String source = "D:\\test\\222.pdf"; // String target = "D:\\test\\111.pdf"; String[][] aaa = {{"11", "12", "13"}, {"21", "22", "23"}}; Seal(source, target, 1, aaa, "2", 2); // test(source, target); // source = "d:/2.pdf"; // target = "d:/3.pdf"; // int[] pages = {1, 3}; // // boolean r = PdfBlock(source, target, pages); System.out.print("1"); } public static void main2(String[] args) { String source = "C:\\Users\\MI\\Desktop\\13\\img\\2.jpg"; String target = "C:\\Users\\MI\\Desktop\\13\\img\\\\2-110.pdf"; image2Pdf(source, target); } public static void main3(String[] args) { String source = "C:\\Users\\MI\\Desktop\\13\\img\\1.jpg"; String target = "C:\\Users\\MI\\Desktop\\13\\img\\1-" + DateUtil.date2String(new Date(), 3) + ".pdf"; image2Pdf(source, target); } public static void main4(String[] args) { String srcFile = "C:\\Users\\MI\\Desktop\\13\\pdf\\b.pdf"; String tarFile = "C:\\Users\\MI\\Desktop\\13\\pdf\\setWaterMark-" + DateUtil.date2String(new Date(), 3) + ".pdf"; String markStr = "管理员"; int fontSize = 50; String color = "GREEN"; int globalOblique = 0; setWaterMark(srcFile, tarFile, markStr, fontSize, color, globalOblique); } public static void main(String[] args) { String tarFile = "/Users/ab/Desktop/pdf/test.pdf"; String[] fileArray = {"/Users/ab/Desktop/pdf/1.pdf", "/Users/ab/Desktop/pdf/2.pdf"}; mergePdf(fileArray, tarFile); } public static boolean mergeOfdAndPdf(String[] sourceFiles, String targetFile, String tempDir) { if (sourceFiles == null || sourceFiles.length == 0) { log.warn("输入文件数组为空"); return true; } Path tempPath = Paths.get(tempDir); try { if (!Files.exists(tempPath)) { Files.createDirectories(tempPath); } } catch (Exception e) { log.error("创建临时目录失败: {}", tempDir, e); return false; } List pdfFiles = new ArrayList<>(); List tempFilesToDelete = new ArrayList<>(); try { // 不在此处改变顺序,保持调用方传入顺序(调用方如需排序,请在外部完成) try { log.info("mergeOfdAndPdf 接收到 {} 个源文件(保持传入顺序)", (sourceFiles == null ? 0 : sourceFiles.length)); if (sourceFiles != null) { for (int i = 0; i < sourceFiles.length; i++) { log.info("顺序 {} -> {}", i + 1, sourceFiles[i]); } } } catch (Exception ignore) {} for (String filePath : sourceFiles) { if (filePath == null || filePath.trim().isEmpty()) { continue; } String lowerPath = filePath.toLowerCase(); if (lowerPath.endsWith(".pdf")) { pdfFiles.add(filePath); } else if (lowerPath.endsWith(".ofd")) { // 将 OFD 转换后的 PDF 命名为原始文件名.pdf(而非 UUID),保证排序与可读性 String originalName = new File(filePath).getName(); String baseName = originalName.endsWith(".ofd") ? originalName.substring(0, originalName.length() - 4) : originalName; // 简单清洗,防止异常字符 // 仅保留字母、数字、下划线、点和连字符,其他替换为下划线 String sanitized = baseName.replaceAll("[^\\w.-]+", "_"); Path outPath = Paths.get(tempDir, sanitized + ".pdf"); int suffix = 1; while (Files.exists(outPath)) { outPath = Paths.get(tempDir, sanitized + "(" + (suffix++) + ").pdf"); } String tempPdfPath = outPath.toString(); try { OfdToPdfUtil.ofdToPdf(filePath, tempPdfPath); pdfFiles.add(tempPdfPath); tempFilesToDelete.add(tempPdfPath); log.info("OFD转换成功: {} -> {}", filePath, tempPdfPath); } catch (Exception e) { log.error("OFD转换失败: {}", filePath, e); } } else { log.warn("不支持的文件格式: {}", filePath); } } if (pdfFiles.isEmpty()) { log.warn("没有可合并的PDF文件"); return false; } try { log.info("最终参与合并的PDF顺序(已按源文件名排序)共 {} 个:", pdfFiles.size()); for (int i = 0; i < pdfFiles.size(); i++) { log.info("合并顺序 {} -> {}", i + 1, pdfFiles.get(i)); } } catch (Exception ignore) {} boolean mergeSuccess = mergePdfFiles( pdfFiles.toArray(new String[0]), targetFile ); return mergeSuccess; } finally { cleanupTempFiles(tempFilesToDelete); } } /** * 合并PDF文件(改进版) */ private static boolean mergePdfFiles(String[] fileArray, String targetFile) { // 删除已存在的目标文件 File target = new File(targetFile); if (target.exists() && !target.delete()) { log.error("无法删除已存在的目标文件: {}", targetFile); return false; } Document document = null; PdfCopy pdfCopy = null; try { document = new Document(); pdfCopy = new PdfCopy(document, new FileOutputStream(targetFile)); document.open(); int successCount = 0; int totalPages = 0; try { log.info("mergePdfFiles 将合并 {} 个PDF,按如下顺序:", (fileArray == null ? 0 : fileArray.length)); if (fileArray != null) { for (int i = 0; i < fileArray.length; i++) { log.info("合并顺序 {} -> {}", i + 1, fileArray[i]); } } } catch (Exception ignore) {} for (String filePath : fileArray) { PdfReader reader = null; try { reader = new PdfReader(filePath); int pages = reader.getNumberOfPages(); // 逐页添加 for (int i = 1; i <= pages; i++) { pdfCopy.addPage(pdfCopy.getImportedPage(reader, i)); } totalPages += pages; successCount++; log.info("已合并文件: {} ({}页)", filePath, pages); } catch (Exception e) { log.error("处理文件失败: {}", filePath, e); } finally { if (reader != null) { try { reader.close(); } catch (Exception e) { log.warn("关闭reader失败", e); } } } } log.info("合并完成: {}/{} 个文件, 共{}页", successCount, fileArray.length, totalPages); return successCount > 0; } catch (Exception e) { log.error("合并PDF失败", e); return false; } finally { if (document != null && document.isOpen()) { document.close(); } } } /** * 清理临时文件 */ private static void cleanupTempFiles(List tempFiles) { for (String tempFile : tempFiles) { try { Files.deleteIfExists(Paths.get(tempFile)); log.debug("删除临时文件: {}", tempFile); } catch (Exception e) { log.warn("删除临时文件失败: {}", tempFile, e); } } } }