选择哪个版本?
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";
}
}
iText5生成与合并PDF方法
6265

被折叠的 条评论
为什么被折叠?



