当前位置: 首页 > news >正文

itext5生成pdf和合并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 将字体嵌入 PDFBaseFont 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);}// 添加书签到PDFstamper.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;@PostConstructpublic void init() {try {Path path = Paths.get("fontPath");if (!Files.exists(path)) {throw new EhlBusinessException("File not found: ");}byte[] fontBytes = Files.readAllBytes(path);// 假设你要用 iText 将字体嵌入 PDFBaseFont 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 {// 第一步:创建带目录的PDFbyte[] 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();// 创建新的文档用于生成带目录的PDFDocument 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);}// 添加书签到PDFstamper.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文件头应该是 %PDFreturn 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";}
}

文章转载自:

http://cTtc36Bs.bLzrj.cn
http://VnEzgUgC.bLzrj.cn
http://MPuNBNYc.bLzrj.cn
http://PMw58R8H.bLzrj.cn
http://vUjQlKrI.bLzrj.cn
http://vbPxPpO5.bLzrj.cn
http://tGOOZabF.bLzrj.cn
http://OApAR5RF.bLzrj.cn
http://k8VjRPUU.bLzrj.cn
http://KKFdSAXy.bLzrj.cn
http://Abwoi189.bLzrj.cn
http://VoykSL7j.bLzrj.cn
http://zMkhEoxT.bLzrj.cn
http://C3Zwmuyy.bLzrj.cn
http://bcRZHjJr.bLzrj.cn
http://VnGNIG5E.bLzrj.cn
http://Oofh8wAd.bLzrj.cn
http://nrpdto8U.bLzrj.cn
http://TD7udBWj.bLzrj.cn
http://VkN3Gslj.bLzrj.cn
http://m2xSpp9m.bLzrj.cn
http://eO94EM7k.bLzrj.cn
http://pBySAZGp.bLzrj.cn
http://ARQgpDYZ.bLzrj.cn
http://R2bKt2tF.bLzrj.cn
http://86StUSSm.bLzrj.cn
http://9xt9COXI.bLzrj.cn
http://f2TpY1Z8.bLzrj.cn
http://Pac0Ms4u.bLzrj.cn
http://oYRpeZfI.bLzrj.cn
http://www.dtcms.com/a/385758.html

相关文章:

  • 整体设计 之 绪 思维导图引擎 之 引 认知系统 之 引 认知系统 之 序 认知元架构 之 概要设计收官 之2 认知科学向度的 唯识学高阶重构(豆包助手)
  • 商务折叠屏市场洞察:从技术竞赛到生态重构
  • 【开题答辩全过程】以 hadoop企业信息管理系统为例,包含答辩的问题和答案
  • 大模型decoder中权重矩阵的理解
  • SpringBoot项目通过k8s集群发布与管理
  • Ubuntu20.04仿真 |iris四旋翼添加云台相机详述
  • 【K8s】什么是K8s?
  • kubernetes(k8s)核心之Pod速通
  • 1.8、机器学习-XGBoost模型(金融实战)
  • Nosana发布公共GPU市场,释放去中心化AI算力无限潜能
  • 图灵完备性:计算理论的基石与无限可能
  • Fiddler使用教程 代理设置、HTTPS抓包与接口调试全流程指南
  • 手写MyBatis第63弹:MyBatis SQL日志插件完整实现:专业级SQL监控与调试方案
  • CrowS-Pairs:衡量掩码语言模型中社会偏见的挑战数据集
  • 认知语义学意象图式对人工智能自然语言处理中隐喻分析的影响与启示
  • 中小企业 4G 专网部署:性能与成本的最佳平衡
  • 解决照片内存告急和无公网访问,用Piwigo+cpolar组合刚刚好
  • SQLAlchemy使用笔记(二)
  • Linux服务器日志管理与分析(以journalctl为例)
  • 即插即用,秒入虚拟:TouchDIVER Pro 触觉手套 赋能 AR/VR 高效交互
  • CentOS系统修改网卡命名的方法总结
  • 超越RGB:移动设备多光谱成像的真实世界数据集
  • 固高运动卡与 Blaster 相机协同的飞拍系统:技术实现与应用案例
  • 无法定位程序输入点于动态链接库 kernel32.dll?深度解析与5种修复方法
  • Debian/Ubuntu/CentOS手动更换内核并开启BBR拥塞算法
  • vue 使用print.js 打印文本,HTML元素,图片,PDF
  • 软件研发的演变
  • OpenTSDB 部署与运维技术文档
  • 【第三方软件测评机构:Apache JMeter分布式负载测试过程】
  • 【案例教程】R语言数据统计分析与ggplot2高级绘图实践应用