itext5生成pdf和合并pdf

iText5生成与合并PDF方法

选择哪个版本?
‌iText 5开源免费,‌iText 7部分功能收费;本次使用iText5版本,使用方式如下:
目标:使用itext5生成pdf,生成内容包括标题,段落,多个合并单元格的表格,复选框。
1 引入依赖


        <!-- 核心库 -->
            <dependency>
                <groupId>com.itextpdf</groupId>
                <artifactId>itextpdf</artifactId>
                <version>5.5.13.3</version>
            </dependency>
            <!-- 中文字体支持 -->
            <dependency>
                <groupId>com.itextpdf</groupId>
                <artifactId>itext-asian</artifactId>
                <version>5.2.0</version>
            </dependency>

2 生成pdf代码部分

    private static Font titleFont;
    private static Font tableHeaderFont;
    private static Font tableContentFont;

	//设置字体
    static {
        try {
            BaseFont baseFont = BaseFont.createFont(
                    "STSong-Light",
                    "UniGB-UCS2-H",
                    BaseFont.NOT_EMBEDDED);
            titleFont = new Font(baseFont, 18, Font.BOLD);
            tableHeaderFont = new Font(baseFont, 12, Font.BOLD, BaseColor.BLACK);
            tableContentFont = new Font(baseFont, 14, Font.NORMAL);
        } catch (Exception e) {
            e.printStackTrace();
        }
     }

        // 1. 初始化文档
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        Document document = new Document(PageSize.A4, 50, 50, 30, 30);
        PdfWriter writer = PdfWriter.getInstance(document, outputStream);
        document.open();
        //2设置标题
         Paragraph title = new Paragraph(desc, titleFont);
        title.setAlignment(Element.ALIGN_CENTER);
        title.setSpacingAfter(20f);
        document.add(title);
        //设置表格样式
        PdfPTable table = new PdfPTable(2);//2列
        table.setWidths(new float[]{3, 7});//2列宽度占比
        table.setWidthPercentage(100);
		//填充表格
		List<List<String>> data = Arrays.asList("列1行1","列1行2");
		for(List<String> list:data){
			for(String str:list){
			 PdfPCell pdfCell = new PdfPCell(new Phrase(desc, tableContentFont));//设置字体样式
             pdfCell.setPadding(5);//设置字体和表格空隙
        	 pdfCell.setHorizontalAlignment(Element.ALIGN_CENTER);//设置文本水平居中
             pdfCell.setVerticalAlignment(Element.ALIGN_MIDDLE);//设置文本垂直居中
				table.addCell(pdfCell);//加到文档中
			}
		}
		//表格中绑定复选框
            RadioCheckField fistApply = PdfUtils.createRadio(writer, "firstApply", "isFirst", true);

	//复选框
    public static RadioCheckField createRadio(PdfWriter writer, String group, String value, boolean checked) throws DocumentException, IOException {
        RadioCheckField radio = new RadioCheckField(writer,
                new Rectangle(0, 0, 8, 8), // 按钮尺寸
                group, value);
        radio.setCheckType(RadioCheckField.TYPE_CHECK); // 方形外观
        radio.setChecked(checked);//是否选中,对号
        radio.setBorderStyle(PdfBorderDictionary.STYLE_BEVELED);
        radio.setBorderWidth(0.5f);//复选框边框
        radio.setBorderColor(BaseColor.BLACK);//复选框边框颜色
        return radio;
    }

3 合并pdf时怎么给pdf添加新标题?

private void mergePdfFile(PdfCopy copy, String path, String title, String subTitle) throws IOException, com.itextpdf.text.DocumentException {
        PdfReader reader = new PdfReader(path);

        // 为第一页添加标题
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        PdfStamper stamper = new PdfStamper(reader, baos);

        try {
            // 获取第一页
            PdfContentByte canvas = stamper.getOverContent(1);

            // 创建支持中文的字体
            BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);

            // 获取页面尺寸(推荐使用此方法)
            Rectangle pageSize = reader.getPageSizeWithRotation(1);

            // 获取页面宽度和高度
            float pageWidth = pageSize.getWidth();
            float pageHeight = pageSize.getHeight();
			//一级标题
            if (StringUtils.isNotEmpty(title)) {
                ColumnText.showTextAligned(
                        canvas,
                        Element.ALIGN_LEFT,
                        new Phrase(title, new Font(bfChinese, 16, Font.BOLD)),
                        36, // 固定左边距
                        pageHeight - 20, // 距离顶部20个单位
                        0
                );
            }
			//二级标题
            if (StringUtils.isNotEmpty(subTitle)) {
                ColumnText.showTextAligned(
                        canvas,
                        Element.ALIGN_LEFT,
                        new Phrase(subTitle, new Font(bfChinese, 14, Font.NORMAL)),
                        36, // 固定左边距
                        pageHeight - 45, // 距离顶部45个单位
                        0
                );
            }

            stamper.close();

            // 将修改后的PDF添加页码并添加到主文档
            PdfReader modifiedReader = new PdfReader(baos.toByteArray());
            // 获取当前页码作为起始页码
            int startPage = copy.getPageNumber();
            byte[] pdfWithPageNumbers = addPageNumbersToPdf(modifiedReader, startPage);

            PdfReader finalReader = new PdfReader(pdfWithPageNumbers);
            for (int i = 1; i <= finalReader.getNumberOfPages(); i++) {
                copy.addPage(copy.getImportedPage(finalReader, i));
            }
            finalReader.close();

        } catch (Exception e) {
            System.err.println("添加页码时出错: " + e.getMessage());
            throw new DocumentException("添加页码失败", e);
        } finally {
            reader.close();
            baos.close();
        }

        System.out.println("成功合并PDF文件: " + path + " (" + reader.getNumberOfPages() + " 页)");
    }

4 怎么切换字体?
1 下载自定义字体包simsun.ttc;
2 读取字体文件;

            Path path = Paths.get("字体文件路径");
            if (!Files.exists(path)) {
                throw new EhlBusinessException("File not found: "); 
            }
            byte[] fontBytes = Files.readAllBytes(path);
            // 假设你要用 iText 将字体嵌入 PDF
            BaseFont thirdBaseFont = BaseFont.createFont("simsun.ttc,0", BaseFont.IDENTITY_H, BaseFont.EMBEDDED, false, fontBytes, null);



5 怎么生成目录?
使用表格方式,可以保证生成的目录左右都是对齐的,标题和点(根据页宽计算点数)作为第一列,页数作为第二列;

private byte[] createTocPage() throws Exception {
        ByteArrayOutputStream tocStream = new ByteArrayOutputStream();
        Document tocDocument = new Document();

        try {
            PdfWriter tocWriter = PdfWriter.getInstance(tocDocument, tocStream);
            tocDocument.open();
            // 添加目录标题
            Paragraph tocTitle = new Paragraph("目录", TITLE_FONT);
            tocTitle.setAlignment(Element.ALIGN_CENTER);
            tocTitle.setSpacingAfter(20);
            tocDocument.add(tocTitle);

            // 添加目录项(使用点线连接)
            for (TocItem item : tocItems) {
                PdfPTable tocTable = new PdfPTable(2);
                tocTable.setWidthPercentage(100);
                tocTable.setWidths(new float[]{9f,1f});

                // 创建单元格
                PdfPCell cell = new PdfPCell();
                cell.setBorder(Rectangle.NO_BORDER);
                cell.setNoWrap(true);

                // 创建一个段落,手动处理点线连接
                Paragraph tocEntry = new Paragraph();
                tocEntry.setSpacingAfter(5);

                // 添加标题
                Chunk titleChunk = new Chunk(item.title, Level_2_TITLE_FONT);
                tocEntry.add(titleChunk);

                // 计算需要添加的点数(根据页面宽度和内容长度)
                float titleWidth = titleChunk.getWidthPoint();

                // 计算页码的宽度
                String pageNumberStr = String.valueOf(item.pageNumber);
                float pageNumberWidth = new Chunk(pageNumberStr, POINT_FONT).getWidthPoint();

                // 页面总宽度减去边距
                float pageWidth = tocDocument.getPageSize().getWidth() -
                        tocDocument.leftMargin() - tocDocument.rightMargin();

                // 计算可用于点线的空间(总宽度 - 标题宽度 - 页码宽度 - 一些间距)
                float dotsWidth = pageWidth - titleWidth - pageNumberWidth - 10; // 减去额外间距

                // 添加点
                if (dotsWidth > 0) {
                    BaseFont baseFont = POINT_FONT.getBaseFont();
                    float dotWidth = baseFont.getWidthPoint(".", 12);
                    int dotCount = (int) (dotsWidth / dotWidth);

                    StringBuilder dots = new StringBuilder();
                    for (int i = 0; i < dotCount; i++) {
                        dots.append(".");
                    }
                    tocEntry.add(new Chunk(dots.toString(), POINT_FONT));
                }
                cell.addElement(tocEntry);

                Paragraph para = new Paragraph(String.valueOf(item.pageNumber), Level_2_TITLE_FONT);
                para.setAlignment(Element.ALIGN_RIGHT);
                PdfPCell cell2 = new PdfPCell(para);
                cell2.setHorizontalAlignment(Element.ALIGN_RIGHT);
                cell2.setVerticalAlignment(Element.ALIGN_MIDDLE);
                cell2.setBorder(Rectangle.NO_BORDER);
                cell2.setNoWrap(true);

                tocTable.addCell(cell);
                tocTable.addCell(cell2);
                tocDocument.add(tocTable);
            }

            tocDocument.close();
            tocWriter.close();

            return tocStream.toByteArray();

        } finally {
            tocStream.close();
        }
    }

6 怎么生成书签?

private void addBookmarksToPdf(String inputPath, String outputPath) throws Exception {
        PdfReader reader = new PdfReader(inputPath);

        try {
            FileOutputStream fos = new FileOutputStream(outputPath);
            PdfStamper stamper = new PdfStamper(reader, fos);

            try {
                // 创建书签
                List<HashMap<String, Object>> bookmarks = new ArrayList<>();
                for (int i = 0; i < tocItems.size(); i++) {
                    TocItem item = tocItems.get(i);
                    HashMap<String, Object> bookmark = new HashMap<>();
                    bookmark.put("Title", item.title);
                    // 目标页码(考虑目录页偏移,页码从1开始,生成pdf后验证调整)
                    bookmark.put("Page", String.valueOf(item.pageNumber));
                    // 添加目标位置信息,确保可以跳转
                    bookmark.put("Action", "GoTo");
                    bookmarks.add(bookmark);
                }
                // 添加书签到PDF
                stamper.setOutlines(bookmarks);

            } finally {
                stamper.close();
                fos.close();
            }
        } finally {
            reader.close();
        }
    }

整体代码:

public class PdfMergeAndUploadDemo {

    // 在类中统一定义字体常量
    private static  Font TITLE_FONT;
    private static  Font Level_2_TITLE_FONT;
    private static  Font CONTET_FONT;
    private static  Font POINT_FONT;

    private static  Font TABLE_HEADER_FONT;

    private static  Font TABLE_CONTENT_FONT;

    @PostConstruct
    public void init() {
        try {
            Path path = Paths.get("fontPath");
            if (!Files.exists(path)) {
                throw new EhlBusinessException("File not found: ");
            }
            byte[] fontBytes = Files.readAllBytes(path);
            // 假设你要用 iText 将字体嵌入 PDF
            BaseFont bfChinese = BaseFont.createFont("simsun.ttc,0", BaseFont.IDENTITY_H, BaseFont.EMBEDDED, false, fontBytes, null);
            TITLE_FONT = new Font(bfChinese, 16, Font.BOLD);
            Level_2_TITLE_FONT = new Font(bfChinese, 14, Font.BOLD);
            CONTET_FONT = new Font(bfChinese, 14, Font.NORMAL);
            POINT_FONT = new Font(bfChinese, 12, Font.NORMAL);
            TABLE_HEADER_FONT = new Font(bfChinese, 12, Font.BOLD);
            TABLE_CONTENT_FONT = new Font(bfChinese, 10, Font.NORMAL);
        } catch (Exception e) {
            log.error("加载字体异常:{}",e.getMessage(),e);
            throw new EhlBusinessException("Error loading font: " + e.getMessage());
        }
    }

    // 添加目录项类
    private static class TocItem {
        String title;
        int pageNumber;
        String destination;

        public TocItem(String title, int pageNumber, String destination) {
            this.title = title;
            this.pageNumber = pageNumber;
            this.destination = destination;
        }
    }

    // 存储目录项
    private List<TocItem> tocItems = new ArrayList<>();

    /**
     * 使用 iText 5 合并多个 PDF 文件并上传到 MinIO
     * @param catalogDataList 输入的文件路径列表(支持PDF和图片等)
     * @param objectName MinIO 对象名称
     * @throws Exception 操作异常
     */
    public String mergePdfsAndUpload(List<CatalogData> catalogDataList, String title) throws Exception {
        // 创建临时文件
        File tempMergedFile = File.createTempFile("merged_", ".pdf");
        File tempWithBookmarks = File.createTempFile("bookmarks_", ".pdf");
        File tempFinalFile = File.createTempFile("final_", ".pdf");

        try {
            // 第一步:合并所有文件
            mergeAllFiles(catalogDataList, tempMergedFile.getAbsolutePath());

            // 第二步:添加目录并生成最终文件
            addTocToPdf(tempMergedFile.getAbsolutePath(), tempWithBookmarks.getAbsolutePath(),title);

            // 第三步:添加书签
            addBookmarksToPdf(tempWithBookmarks.getAbsolutePath(), tempFinalFile.getAbsolutePath());
            // 保存到本地目录
            String localFilePath = savePdfToLocal(tempFinalFile, title);
            System.out.println("PDF 已保存到本地: " + localFilePath);
            return localFilePath;

        } finally {
            // 清理临时文件
            deleteFileIfExists(tempMergedFile);
            deleteFileIfExists(tempWithBookmarks);
            deleteFileIfExists(tempFinalFile);
            // 清理目录项列表
            tocItems.clear();
        }
    }


    /**
     * 将PDF保存到本地目录
     * @param pdfFile PDF文件
     * @param fileName 文件名(不包含扩展名)
     * @return 保存的文件路径
     */
    private String savePdfToLocal(File pdfFile, String fileName) {
        try {
            // 创建本地保存目录
            String localDirPath = "D:\\project\\v2xserver\\spring-boot-v2x\\src\\main\\test";
            File localDir = new File(localDirPath);
            if (!localDir.exists()) {
                localDir.mkdirs();
            }

            // 生成文件名(带时间戳)
            String timestamp = new java.text.SimpleDateFormat("yyyyMMdd_HHmmss").format(new java.util.Date());
            String localFileName = fileName + "_" + timestamp + ".pdf";
            String localFilePath = localDirPath + localFileName;

            // 复制文件到本地目录
            try (FileInputStream fis = new FileInputStream(pdfFile);
                 FileOutputStream fos = new FileOutputStream(localFilePath)) {

                byte[] buffer = new byte[8192];
                int bytesRead;
                while ((bytesRead = fis.read(buffer)) != -1) {
                    fos.write(buffer, 0, bytesRead);
                }

                fos.flush();
            }

            return localFilePath;
        } catch (Exception e) {
            System.err.println("保存PDF到本地时出错: " + e.getMessage());
            return null;
        }
    }
    private void deleteFileIfExists(File file) {
        if (file != null && file.exists()) {
            file.delete();
        }
    }

    /**
     * 合并所有文件
     * CatalogData 目录数据
     */
    private void mergeAllFiles(List<CatalogData> catalogDataList, String outputPath) throws Exception {
        Document document = new Document();

        try (FileOutputStream fos = new FileOutputStream(outputPath)) {
            PdfCopy copy = new PdfCopy(document, fos);
            document.open();

            // 遍历每个输入文件并合并
            for (int i = 0; i < catalogDataList.size(); i++) {
                CatalogData catalogData = catalogDataList.get(i);
                if (catalogData.getType() == null) {
                    //添加目录,自定义一个表格
                    tocItems.add(new TocItem(catalogData.getTitle(), copy.getPageNumber(), ""));
                    addFixedTable(copy, catalogData.getTitle(), catalogDataList);
                } else if (catalogData.getType().equals(AttachmentConfigTypeEnum.FILE.getCode())) {
                    //处理附件
                    if (CollectionUtils.isNotEmpty(catalogData.getPath())) {
                        for (int j = 0; j < catalogData.getPath().size(); j++) {
                            String title = j == 0 ? catalogData.getTitle() : "";
                            mergeFile(copy, catalogData.getPath().get(j), title, "");
                        }
                    } else {
                        // 当path为空时,添加标题和默认说明文字
                        tocItems.add(new TocItem(catalogData.getTitle(), copy.getPageNumber(), ""));
                        addEmptyPathNotice(copy, catalogData.getTitle());
                    }

                } else if (catalogData.getType().equals(AttachmentConfigTypeEnum.DIRECTORY.getCode())) {
                    //处理子类,第一次循环时添加一级目录,之后只需要加二级目录
                    int first = 0;
                    for (CatalogData child : catalogData.getChildren()) {
                        if (first == 0) {
                            if (CollectionUtils.isNotEmpty(child.getPath())) {
                                for (int j = 0; j < child.getPath().size(); j++) {
                                    String title = j == 0 ? catalogData.getTitle() : "";
                                    String subTitle = j == 0 ? child.getTitle() : "";
                                    mergeFile(copy, child.getPath().get(j), title, subTitle);
                                }
                            } else {
                                // 当path为空时,添加标题和说明文字
                                tocItems.add(new TocItem(child.getTitle(), copy.getPageNumber(), ""));
                                addEmptyPathNotice(copy, child.getTitle());
                            }

                        } else {
                            if (CollectionUtils.isNotEmpty(child.getPath())) {
                                for (int j = 0; j < child.getPath().size(); j++) {
                                    String subTitle = j == 0 ? child.getTitle() : "";
                                    mergeFile(copy, child.getPath().get(j), "", subTitle);
                                }
                            } else {
                                // 当path为空时,添加标题和说明文字
                                tocItems.add(new TocItem(child.getTitle(), copy.getPageNumber(), ""));
                                addEmptyPathNotice(copy, child.getTitle());
                            }
                        }
                        first++;
                    }
                }
            }

        } finally {
            document.close();
        }
    }

    /**
     * 添加固定表格到PDF
     */
    private void addFixedTable(PdfCopy copy, String title,List<CatalogData> catalogDataList) throws Exception {
        // 创建临时PDF用于表格
        ByteArrayOutputStream tableStream = new ByteArrayOutputStream();
        Document tableDocument = new Document();

        try {
            PdfWriter tableWriter = PdfWriter.getInstance(tableDocument, tableStream);
            tableDocument.open();

            Paragraph paragraph = new Paragraph(title, TITLE_FONT);
            paragraph.setAlignment(Element.ALIGN_LEFT);
            paragraph.setSpacingAfter(12f);
            tableDocument.add(paragraph);

            // 创建表格 (3列示例)
            PdfPTable table = new PdfPTable(4);
            table.setWidthPercentage(100);
            table.setSpacingBefore(10f);
            table.setSpacingAfter(10f);

            // 设置列宽
            float[] columnWidths = {1f, 3f, 4f,2f};
            table.setWidths(columnWidths);

            // 添加表头
            PdfPCell cell1 = new PdfPCell(new Paragraph("序号", TABLE_HEADER_FONT));
            cell1.setHorizontalAlignment(Element.ALIGN_CENTER);
            table.addCell(cell1);

            PdfPCell cell2 = new PdfPCell(new Paragraph("类别", TABLE_HEADER_FONT));
            cell2.setHorizontalAlignment(Element.ALIGN_CENTER);
            table.addCell(cell2);

            PdfPCell cell3 = new PdfPCell(new Paragraph("材料名称", TABLE_HEADER_FONT));
            cell3.setHorizontalAlignment(Element.ALIGN_CENTER);
            table.addCell(cell3);

            PdfPCell cell4 = new PdfPCell(new Paragraph("是否提交", TABLE_HEADER_FONT));
            cell4.setHorizontalAlignment(Element.ALIGN_CENTER);
            table.addCell(cell4);

            List<List<String>> dataList = new ArrayList<>();
            int count = 1;
            for (int i = 0; i < 5; i++) {
                CatalogData catalogData = catalogDataList.get(i);
                if (CollectionUtils.isNotEmpty(catalogData.getChildren())){
                    for (CatalogData child : catalogData.getChildren()){
                        List<String> data = new ArrayList<>();
                        data.add(String.valueOf(count));
                        data.add(catalogData.getTitle());
                        data.add(child.getTitle());
                        data.add(CollectionUtils.isNotEmpty(child.getPath()) ? "是" : "否");
                        dataList.add(data);
                        count++;
                    }
                }
            }
            for (List<String> data : dataList){
                for (String cell : data){
                    PdfPCell pdfPCell = new PdfPCell(new Paragraph(cell, TABLE_CONTENT_FONT));
                    pdfPCell.setHorizontalAlignment(Element.ALIGN_CENTER);
                    pdfPCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
                    table.addCell(pdfPCell);
                }
            }
            tableDocument.add(table);

            // 添加页脚显示"第一页"
            Phrase footer = new Phrase("第1页", TABLE_CONTENT_FONT);

            // 计算页脚位置(页面底部居中)
            PdfContentByte canvas = tableWriter.getDirectContent();
            Rectangle pageSize = tableDocument.getPageSize();
            float x = (pageSize.getLeft() + pageSize.getRight()) / 2;
            float y = pageSize.getBottom() + 20;

            ColumnText.showTextAligned(canvas, Element.ALIGN_CENTER, footer, x, y, 0);

            tableDocument.close();
            tableWriter.close();

            // 将表格页添加到主文档
            PdfReader tableReader = new PdfReader(tableStream.toByteArray());
            for (int i = 1; i <= tableReader.getNumberOfPages(); i++) {
                copy.addPage(copy.getImportedPage(tableReader, i));
            }
            tableReader.close();

        } finally {
            tableStream.close();
        }
    }

    /**
     * 添加空路径提示页面
     */
    private void addEmptyPathNotice(PdfCopy copy, String title) throws Exception {
        // 创建临时PDF用于提示信息
        ByteArrayOutputStream noticeStream = new ByteArrayOutputStream();
        Document noticeDocument = new Document();

        try {
            PdfWriter noticeWriter = PdfWriter.getInstance(noticeDocument, noticeStream);
            noticeDocument.open();



            if (StringUtils.isNotEmpty(title)) {
                // 添加标题
                Paragraph titleParagraph = new Paragraph(title, TITLE_FONT);
                titleParagraph.setAlignment(Element.ALIGN_LEFT);
                titleParagraph.setSpacingAfter(20f);
                noticeDocument.add(titleParagraph);
            }

            // 添加说明文字
            Paragraph noticeParagraph = new Paragraph("暂未提供", CONTET_FONT);
            noticeParagraph.setAlignment(Element.ALIGN_LEFT);
            noticeParagraph.setSpacingAfter(10f);
            noticeDocument.add(noticeParagraph);

            noticeDocument.close();
            noticeWriter.close();

            // 将提示页添加到主文档
            PdfReader noticeReader = new PdfReader(noticeStream.toByteArray());
            for (int i = 1; i <= noticeReader.getNumberOfPages(); i++) {
                copy.addPage(copy.getImportedPage(noticeReader, i));
            }
            noticeReader.close();

        } finally {
            noticeStream.close();
        }
    }
    private void mergeFile(PdfCopy copy,String path,String title,String subTitle) throws Exception{

        try {
            // 记录当前页码(目录项的起始页)
            int startPage = copy.getPageNumber();

            // 判断文件类型并处理,添加到目录项
            if (isPdfFile(path)) {
                // 如果是PDF文件,直接合并
                mergePdfFile(copy, path, title,subTitle);
            } else {
                // 如果不是PDF文件,先转换为PDF再合并
                mergeNonPdfFileWithTitle(copy, path, title,subTitle);
            }

            // 添加到目录项
            if (StringUtils.isNotEmpty( title)){
                tocItems.add(new TocItem(title, startPage, "dest_"));
            }
            // 添加到目录项
            if (StringUtils.isNotEmpty( subTitle)){
                tocItems.add(new TocItem(subTitle, startPage, "dest_"));
            }


        } catch (InvalidPdfException e) {
            // 如果PDF验证失败,尝试作为非PDF文件处理
            System.out.println("文件不是有效PDF,尝试转换: " + path);
            int startPage = copy.getPageNumber();
            mergeNonPdfFileWithTitle(copy, path, title ,subTitle);
            // 添加到目录项
            if (StringUtils.isNotEmpty( title)){
                tocItems.add(new TocItem(title, startPage, "dest_"));
            }
            // 添加到目录项
            if (StringUtils.isNotEmpty( subTitle)){
                tocItems.add(new TocItem(subTitle, startPage, "dest_"));
            }
        } catch (Exception e) {
            System.err.println("处理文件时出错: " + path + ", 错误: " + e.getMessage());
            throw new Exception("处理文件失败: " + path, e);
        }
    }

    /**
     * 添加大标题页面
     */
    private void addMainTitlePage(Document document, PdfCopy copy,String objectName) throws Exception {
        // 创建临时PDF用于大标题页
        ByteArrayOutputStream titleStream = new ByteArrayOutputStream();
        Document titleDocument = new Document();

        try {
            PdfWriter titleWriter = PdfWriter.getInstance(titleDocument, titleStream);
            titleDocument.open();

            // 添加大标题
            Paragraph mainTitle = PdfUtils.createPdfTitle(objectName);
            titleDocument.add(mainTitle);

            // 添加日期
            String dateStr = new java.text.SimpleDateFormat("yyyy年MM月dd日").format(new java.util.Date());

            Paragraph date = new Paragraph(dateStr, Level_2_TITLE_FONT);
            date.setAlignment(Element.ALIGN_CENTER);
            titleDocument.add(date);

            titleDocument.close();
            titleWriter.close();

            // 将标题页添加到主文档
            PdfReader titleReader = new PdfReader(titleStream.toByteArray());
            for (int i = 1; i <= titleReader.getNumberOfPages(); i++) {
                copy.addPage(copy.getImportedPage(titleReader, i));
            }
            titleReader.close();

        } finally {
            titleStream.close();
        }
    }

    /**
     * 添加目录到PDF(改进版)
     */
    private void addTocToPdf(String sourcePath, String outputPath,String objectName) throws Exception {
        // 第一步:创建带目录的PDF
        byte[] finalPdf = createPdfWithToc(sourcePath,objectName);

        // 写入最终文件
        try (FileOutputStream fos = new FileOutputStream(outputPath);
             ByteArrayInputStream bis = new ByteArrayInputStream(finalPdf)) {
            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = bis.read(buffer)) != -1) {
                fos.write(buffer, 0, bytesRead);
            }
        }
    }

    /**
     * 创建带目录的PDF
     */
    private byte[] createPdfWithToc(String sourcePath,String objectName) throws Exception {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        // 创建新的文档用于生成带目录的PDF
        Document document = new Document();

        try {
            PdfCopy copy = new PdfCopy(document, outputStream);
            document.open();
            // 1添加大标题页面  pdf名称和时间
            addMainTitlePage(document, copy,objectName);
            // 2首先创建目录页
            byte[] tocContent = createTocPage();
            PdfReader tocReader = new PdfReader(tocContent);
            for (int i = 1; i <= tocReader.getNumberOfPages(); i++) {
                copy.addPage(copy.getImportedPage(tocReader, i));
            }
            tocReader.close();

            // 然后添加原始内容
            PdfReader reader = new PdfReader(sourcePath);
            for (int i = 1; i <= reader.getNumberOfPages(); i++) {
                copy.addPage(copy.getImportedPage(reader, i));
            }
            reader.close();

        } finally {
            document.close();
        }

        return outputStream.toByteArray();
    }
    /**
     * 使用 PdfStamper 添加书签到已合并的PDF
     */
    private void addBookmarksToPdf(String inputPath, String outputPath) throws Exception {
        PdfReader reader = new PdfReader(inputPath);

        try {
            FileOutputStream fos = new FileOutputStream(outputPath);
            PdfStamper stamper = new PdfStamper(reader, fos);

            try {
                // 创建书签
                List<HashMap<String, Object>> bookmarks = new ArrayList<>();

                for (int i = 0; i < tocItems.size(); i++) {
                    TocItem item = tocItems.get(i);

                    HashMap<String, Object> bookmark = new HashMap<>();
                    bookmark.put("Title", item.title);
                    // 目标页码(考虑目录页偏移,页码从1开始)
                    bookmark.put("Page", String.valueOf(item.pageNumber+2));
                    // 添加目标位置信息,确保可以跳转
                    bookmark.put("Action", "GoTo");
                    bookmarks.add(bookmark);
                }

                // 添加书签到PDF
                stamper.setOutlines(bookmarks);

            } finally {
                stamper.close();
                fos.close();
            }
        } finally {
            reader.close();
        }
    }
    /**
     * 创建目录页并返回字节数组
     */
    private byte[] createTocPage() throws Exception {
        ByteArrayOutputStream tocStream = new ByteArrayOutputStream();
        Document tocDocument = new Document();

        try {
            PdfWriter tocWriter = PdfWriter.getInstance(tocDocument, tocStream);
            tocDocument.open();
            // 添加目录标题
            Paragraph tocTitle = new Paragraph("目录页", TITLE_FONT);
            tocTitle.setAlignment(Element.ALIGN_CENTER);
            tocTitle.setSpacingAfter(20);
            tocDocument.add(tocTitle);

            // 添加目录项(使用点线连接)
            for (TocItem item : tocItems) {
                PdfPTable tocTable = new PdfPTable(2);
                tocTable.setWidthPercentage(100);
                // 设置列宽为100%
                tocTable.setWidths(new float[]{8f,2f});

                // 创建单元格
                PdfPCell cell = new PdfPCell();
                cell.setBorder(Rectangle.NO_BORDER);
                cell.setNoWrap(true);

                // 创建一个段落,手动处理点线连接
                Paragraph tocEntry = new Paragraph();
                tocEntry.setSpacingAfter(5);

                // 添加标题
                Chunk titleChunk = new Chunk(item.title, Level_2_TITLE_FONT);
                tocEntry.add(titleChunk);

                // 计算需要添加的点数(根据页面宽度和内容长度)
                float titleWidth = titleChunk.getWidthPoint();

                // 计算页码的宽度
                String pageNumberStr = String.valueOf(item.pageNumber);
                float pageNumberWidth = new Chunk(pageNumberStr, POINT_FONT).getWidthPoint();

                // 页面总宽度减去边距
                float pageWidth = tocDocument.getPageSize().getWidth() -
                        tocDocument.leftMargin() - tocDocument.rightMargin();

                // 计算可用于点线的空间(总宽度 - 标题宽度 - 页码宽度 - 一些间距)
                float dotsWidth = pageWidth - titleWidth - pageNumberWidth - 10; // 减去额外间距

                // 添加点
                if (dotsWidth > 0) {
                    BaseFont baseFont = POINT_FONT.getBaseFont();
                    float dotWidth = baseFont.getWidthPoint(".", 12);
                    int dotCount = (int) (dotsWidth / dotWidth);

                    StringBuilder dots = new StringBuilder();
                    for (int i = 0; i < dotCount; i++) {
                        dots.append(".");
                    }
                    tocEntry.add(new Chunk(dots.toString(), POINT_FONT));
                }
                cell.addElement(tocEntry);

                Paragraph para = new Paragraph(String.valueOf(item.pageNumber), Level_2_TITLE_FONT);
                para.setAlignment(Element.ALIGN_RIGHT);
                PdfPCell cell2 = new PdfPCell(para);
                cell2.setHorizontalAlignment(Element.ALIGN_RIGHT);
                cell2.setVerticalAlignment(Element.ALIGN_MIDDLE);
                cell2.setBorder(Rectangle.NO_BORDER);
                cell2.setNoWrap(true);

                tocTable.addCell(cell);
                tocTable.addCell(cell2);
                tocDocument.add(tocTable);
            }

            tocDocument.close();
            tocWriter.close();

            return tocStream.toByteArray();

        } finally {
            tocStream.close();
        }
    }


    /**
     * 判断文件是否为PDF格式
     * @param path 文件路径
     * @return 是否为PDF文件
     */
    private boolean isPdfFile(String path) {
        try {
            byte[] header = new byte[4];

            if (path.startsWith("http://") || path.startsWith("https://")) {
                // 网络文件
                URL url = new URL(path);
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                connection.setRequestMethod("GET");
                try (InputStream inputStream = connection.getInputStream()) {
                    inputStream.read(header);
                }
            } else {
                // 本地文件
                try (InputStream inputStream = new FileInputStream(path)) {
                    inputStream.read(header);
                }
            }

            // PDF文件头应该是 %PDF
            return header[0] == 0x25 && header[1] == 0x50 && header[2] == 0x44 && header[3] == 0x46;
        } catch (Exception e) {
            System.err.println("检查文件类型时出错: " + path + ", 错误: " + e.getMessage());
            return false;
        }
    }


    /**
     * 合并PDF文件,在同一页添加标题和内容
     * @param copy PdfCopy对象
     * @param path PDF文件路径
     * @param title 标题
     * @throws IOException IO异常
     * @throws DocumentException 文档异常
     */
    private void mergePdfFile(PdfCopy copy, String path, String title, String subTitle) throws IOException, DocumentException {
        PdfReader reader = new PdfReader(path);

        // 为第一页添加标题
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        PdfStamper stamper = new PdfStamper(reader, baos);

        try {
            // 获取第一页
            PdfContentByte canvas = stamper.getOverContent(1);

            // 获取页面尺寸(推荐使用此方法)
            Rectangle pageSize = reader.getPageSizeWithRotation(1);

            // 获取页面宽度和高度
            float pageWidth = pageSize.getWidth();
            float pageHeight = pageSize.getHeight();

            if (StringUtils.isNotEmpty(title)) {
                ColumnText.showTextAligned(
                        canvas,
                        Element.ALIGN_LEFT,
                        new Phrase(title, TITLE_FONT),
                        36, // 固定左边距
                        pageHeight - 30, // 距离顶部20个单位
                        0
                );
            }

            if (StringUtils.isNotEmpty(subTitle)) {
                ColumnText.showTextAligned(
                        canvas,
                        Element.ALIGN_LEFT,
                        new Phrase(subTitle, Level_2_TITLE_FONT),
                        36, // 固定左边距
                        pageHeight - 50, // 距离顶部45个单位
                        0
                );
            }

            stamper.close();

            // 将修改后的PDF添加页码并添加到主文档
            PdfReader modifiedReader = new PdfReader(baos.toByteArray());
            // 获取当前页码作为起始页码
            int startPage = copy.getPageNumber();
            byte[] pdfWithPageNumbers = addPageNumbersToPdf(modifiedReader, startPage);

            PdfReader finalReader = new PdfReader(pdfWithPageNumbers);
            for (int i = 1; i <= finalReader.getNumberOfPages(); i++) {
                copy.addPage(copy.getImportedPage(finalReader, i));
            }
            finalReader.close();

        } catch (Exception e) {
            System.err.println("添加页码时出错: " + e.getMessage());
            throw new DocumentException("添加页码失败", e);
        } finally {
            reader.close();
            baos.close();
        }

        System.out.println("成功合并PDF文件: " + path + " (" + reader.getNumberOfPages() + " 页)");
    }


    /**
     * 为PDF文档添加页码
     * @param reader PDF读取器
     * @param startPage 起始页码
     * @return 添加页码后的PDF字节数组
     * @throws Exception 异常
     */
    private byte[] addPageNumbersToPdf(PdfReader reader, int startPage) throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        try {
            PdfStamper stamper = new PdfStamper(reader, baos);

            // 为每一页添加页码
            int n = reader.getNumberOfPages();
            for (int i = 1; i <= n; i++) {
                PdfContentByte canvas = stamper.getOverContent(i);
                Phrase footer = new Phrase("第 " + (startPage + i - 1) + " 页", TABLE_CONTENT_FONT);

                // 获取页面尺寸
                Rectangle pageSize = reader.getPageSizeWithRotation(i);
                // 计算页码位置(页面底部居中)
                float x = (pageSize.getLeft() + pageSize.getRight()) / 2;
                float y = pageSize.getBottom() + 20;

                ColumnText.showTextAligned(canvas, Element.ALIGN_CENTER, footer, x, y, 0);
            }

            stamper.close();
            return baos.toByteArray();
        } finally {
            baos.close();
        }
    }

    /**
     * 合并非PDF文件并添加标题
     * @param copy PdfCopy对象
     * @param path 非PDF文件路径
     * @param title 标题
     * @throws IOException IO异常
     * @throws DocumentException 文档异常
     */
    private void mergeNonPdfFileWithTitle(PdfCopy copy, String path, String title,String subTitle) throws IOException, DocumentException {
        // 创建临时PDF文档
        ByteArrayOutputStream tempPdfStream = new ByteArrayOutputStream();
        Document tempDocument = new Document();

        try {
            PdfWriter writer = PdfWriter.getInstance(tempDocument, tempPdfStream);
            tempDocument.open();

            if (StringUtils.isNotEmpty(title)){
                // 添加文件标题
                Paragraph titleParagraph = new Paragraph(title, TITLE_FONT);
                titleParagraph.setSpacingAfter(20);
                tempDocument.add(titleParagraph);
            }
            if (StringUtils.isNotEmpty(subTitle)){
                // 添加文件标题
                Paragraph subtitleParagraph = new Paragraph(subTitle, Level_2_TITLE_FONT);
                subtitleParagraph.setSpacingAfter(20);
                tempDocument.add(subtitleParagraph);
            }

            // 根据文件扩展名处理不同类型的文件
            String lowerPath = path.toLowerCase();
            if (lowerPath.endsWith(".jpg") || lowerPath.endsWith(".jpeg") ||
                    lowerPath.endsWith(".png") || lowerPath.endsWith(".gif") ||
                    lowerPath.endsWith(".bmp")) {
                // 处理图片文件
                addImageToDocument(tempDocument, path);
            } else {
                // 处理其他类型文件,添加文件名和路径信息
                addFileInfoToDocument(tempDocument, path, TITLE_FONT);
            }

            tempDocument.close();
            writer.close();

            // 将生成的PDF添加页码并合并到主文档
            PdfReader reader = new PdfReader(tempPdfStream.toByteArray());
            // 获取当前页码作为起始页码
            int startPage = copy.getPageNumber();
            // 调用addPageNumbersToPdf方法为PDF添加页码
            byte[] pdfWithPageNumbers = addPageNumbersToPdf(reader, startPage);

            PdfReader finalReader = new PdfReader(pdfWithPageNumbers);
            for (int i = 1; i <= finalReader.getNumberOfPages(); i++) {
                copy.addPage(copy.getImportedPage(finalReader, i));
            }
            finalReader.close();
            reader.close();

            System.out.println("成功转换并合并非PDF文件: " + path);
        } catch (Exception e) {
            System.err.println("添加页码时出错: " + e.getMessage());
            throw new DocumentException("添加页码失败", e);
        } finally {
            tempPdfStream.close();
        }
    }

    /**
     * 将图片添加到文档中
     * @param document Document对象
     * @param imagePath 图片路径
     * @throws IOException IO异常
     * @throws DocumentException 文档异常
     */
    private void addImageToDocument(Document document, String imagePath) throws IOException, DocumentException {
        try {
            Image img;
            if (imagePath.startsWith("http://") || imagePath.startsWith("https://")) {
                // 网络图片
                img = Image.getInstance(new URL(imagePath));
            } else {
                // 本地图片
                img = Image.getInstance(imagePath);
            }

            // 调整图片大小以适应页面
            img.scaleToFit(document.getPageSize().getWidth() - 50, document.getPageSize().getHeight() - 50);
            img.setAlignment(Image.ALIGN_CENTER);

            document.add(img);
        } catch (Exception e) {
            // 如果图片处理失败,添加错误信息
            document.add(new Paragraph("无法处理图片文件: " + imagePath));
            document.add(new Paragraph("错误信息: " + e.getMessage()));
        }
    }

    /**
     * 将文件信息添加到文档中
     * @param document Document对象
     * @param filePath 文件路径
     * @throws DocumentException 文档异常
     */
    private void addFileInfoToDocument(Document document, String filePath,Font contentFont) throws DocumentException {
        document.add(new Paragraph("原始文件信息"));
        document.add(new Paragraph("文件路径: " + filePath,contentFont));
        document.add(new Paragraph("文件类型: " + getFileExtension(filePath)));
        document.add(new Paragraph("注意: 该文件不是PDF格式,系统已自动转换。"));

        // 尝试获取文件大小
        try {
            long fileSize = 0;
            if (filePath.startsWith("http://") || filePath.startsWith("https://")) {
                URL url = new URL(filePath);
                fileSize = url.openConnection().getContentLength();
            } else {
                File file = new File(filePath);
                fileSize = file.length();
            }
            document.add(new Paragraph("文件大小: " + fileSize + " 字节"));
        } catch (Exception e) {
            document.add(new Paragraph("无法获取文件大小信息"));
        }
    }

    /**
     * 获取文件扩展名
     * @param filePath 文件路径
     * @return 文件扩展名
     */
    private String getFileExtension(String filePath) {
        int lastDotIndex = filePath.lastIndexOf('.');
        if (lastDotIndex > 0 && lastDotIndex < filePath.length() - 1) {
            return filePath.substring(lastDotIndex + 1);
        }
        return "unknown";
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值