PdfReader 讀取 PDF 後會鎖定文件,**無法直接寫回原文件**。必須「讀取副本 → 寫入原路徑」或「寫入新路徑 → 替換」。
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;
import java.io.*;
import java.nio.channels.FileChannel;
public class PdfAppender {
public static void appendContent(String pdfPath) throws Exception {
File src = new File(pdfPath);
File tmp = new File(pdfPath + ".tmp");
// 1. 高效拷貝:NIO 管道流,零用戶態拷貝
fileChannelCopy(src, tmp);
// 2. 讀取副本,寫回原文件
PdfReader reader = new PdfReader(tmp.getAbsolutePath());
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(src));
// 3. 獲取第 1 頁的「上層」繪圖畫布 (疊加在現有內容之上)
PdfContentByte over = stamper.getOverContent(1);
// --- 追加文字 ---
BaseFont font = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
over.beginText();
over.setFontAndSize(font, 12);
over.setTextMatrix(200, 700); // 底部座標 (左下角原點)
over.showText("追加的水印文字");
over.endText();
// --- 追加圖片 ---
Image img = Image.getInstance("logo.png");
img.setAbsolutePosition(100, 600);
over.addImage(img);
// --- 繪製紅色圓圈 ---
over.setRGBColorStroke(0xFF, 0x00, 0x00);
over.setLineWidth(2f);
over.ellipse(250, 450, 350, 550); // 外接矩形
over.stroke();
stamper.close();
reader.close();
// 4. 清理臨時文件
tmp.delete();
}
// NIO 管道拷貝:極快、內存友好
public static void fileChannelCopy(File src, File dest) throws IOException {
try (FileInputStream in = new FileInputStream(src);
FileOutputStream out = new FileOutputStream(dest);
FileChannel inCh = in.getChannel();
FileChannel outCh = out.getChannel()) {
inCh.transferTo(0, inCh.size(), outCh);
}
}
}
| 類/方法 | 用途 |
|---|---|
PdfReader | 解析現有 PDF 結構 |
PdfStamper(reader, os) | 基於 reader 向輸出流寫入修訂 |
getOverContent(pageNum) | 獲取頁面**上層**畫布 (疊加模式) |
getUnderContent(pageNum) | 獲取頁面**下層**畫布 (背景水印模式) |
BaseFont.createFont | 註冊中文字體 (需 iTextAsian.jar) |
PdfContentByte 繪圖 | 文字/圖片/幾何圖形/路徑構建 |
STSong-Light + UniGB-UCS2-H 且引入 itext-asian 依賴,否則顯示方塊。finally 塊確保 .tmp 刪除,防止磁盤洩漏。PdfReader.unethicalreading = true 或分頁增量處理。iText 5.x 核心思想:**增量更新**。原 PDF 結構不變,僅在末尾追加修訂區 (xref 表)。理解這一點,就明白為什麼必須「讀副本寫原文」——避免同一文件同時被 Reader 和 OutputStream 佔用導致損壞。