使用 iText 9 为 PDF 添加文字水印的完整实战
1️⃣ 背景与需求
在企业内部流转或对外发布的 PDF 文档中,往往需要加入“内部仅阅”“机密”等标识,以防止泄露或伪造。iText 9 作为业界成熟的 PDF 处理库,提供了灵活的绘图 API,能够在已有 PDF 上批量、透明、倾斜地绘制文字水印。
博客示例基于 iText 9,实现了以下功能:
- 支持中文水印(使用
STSong-Light字体) - 透明度可调(示例中为 0.2)
- 文字倾斜 45°,实现斜纹平铺效果
- 自动遍历所有页并在每页均匀分布水印
2️⃣ 环境准备
<!-- https://mvnrepository.com/artifact/com.itextpdf/itext-core -->
<dependency><groupId>com.itextpdf</groupId><artifactId>itext-core</artifactId><version>9.3.0</version><type>pom</type>
</dependency><!-- https://mvnrepository.com/artifact/com.itextpdf/html2pdf -->
<dependency><groupId>com.itextpdf</groupId><artifactId>html2pdf</artifactId><version>6.2.1</version>
</dependency>
3️⃣ 关键代码剖析
3.1 创建 PDF 读取/写入流
PdfReader reader = new PdfReader(inputStream);
PdfWriter writer = new PdfWriter(outputStream);
PdfDocument pdfDoc = new PdfDocument(reader, writer);
PdfReader读取原始 PDF,PdfWriter将结果写入内存流。PdfDocument同时持有读写能力,适合在原文件上直接添加内容。
3.2 配置中文字体
PdfFont font = PdfFontFactory.createFont("STSong-Light", "UniGB-UCS2-H");
STSong-Light是 iText 自带的宋体变体,配合UniGB-UCS2-H编码即可渲染简体中文。
3.3 定义水印样式
Paragraph paragraph = new Paragraph(watermarkText).setFont(font).setFontSize(15).setFontColor(new DeviceRgb(200, 200, 200)).setOpacity(0.2f);
setOpacity控制透明度,0.2 让底层文字仍可辨认。- 颜色使用浅灰,避免遮挡正文。
3.4 遍历页面并绘制水印
int numberOfPages = pdfDoc.getNumberOfPages();
for (int i = 1; i <= numberOfPages; i++) {PdfPage page = pdfDoc.getPage(i);Rectangle pageSize = page.getPageSize();PdfCanvas canvas = new PdfCanvas(page.newContentStreamBefore(),page.getResources(), pdfDoc);Canvas canvas1 = new Canvas(canvas, pageSize);// 平铺参数float xSpacing = 300, ySpacing = 150;float xStart = 100, yStart = pageSize.getHeight() - 100;for (float y = yStart; y > 0; y -= ySpacing) {for (float x = xStart; x < pageSize.getWidth(); x += xSpacing) {canvas1.showTextAligned(paragraph, x, y, i,TextAlignment.CENTER,com.itextpdf.layout.properties.VerticalAlignment.MIDDLE,45); // 45° 斜纹}}canvas1.close();
}
page.newContentStreamBefore()在原内容之前写入,确保水印位于页面底层。- 双层循环实现 水平 300px、垂直 150px 的网格平铺。
showTextAligned支持文字对齐、垂直居中以及旋转角度(45°),是 iText 9 推荐的文字绘制方式。
3.5 关闭文档并返回流
pdfDoc.close();
return new ByteArrayInputStream(outputStream.toByteArray());
- 关闭
PdfDocument会自动写入 EOF 标记,防止文件损坏。
4️⃣ 常见问题与最佳实践
| 场景 | 建议 | 说明 |
|---|---|---|
| 大文件(>100 MB) | 使用 PdfWriter 的 setSmartMode(true),开启智能写入,降低内存占用。 | iText 官方文档推荐的性能优化手段。 |
| 多语言水印 | 通过 PdfFontFactory.createFont(“NotoSansCJKsc-Regular.otf”, PdfEncodings.IDENTITY_H) 加载支持多语言的 Unicode 字体。 | 适用于中英混排。 |
| 水印位置不均 | 调整 xStart、yStart 与间距参数,或使用页面宽高比例计算。 | 不同页面尺寸(A4、Letter)需要自适应。 |
| 需要在已有水印上再添加 | 使用 PdfCanvas 的 newContentStreamAfter() 在最上层绘制,避免被覆盖。 | 参考 iText 9 的层次概念。 |
5️⃣ 小结
本文完整演示了 iText 9 为 PDF 添加中文文字水印的实现细节。核心要点包括:
- 正确加载中文字体
- 使用
Paragraph配合透明度、颜色定义水印样式 - 通过
PdfCanvas+Canvas在每页创建 平铺、倾斜 的文字网格 - 关闭文档确保 PDF 完整性
只要把上述工具类集成到项目中,即可在任何 Java 环境下快速实现批量水印功能,满足企业文档安全的基本需求。祝编码愉快!
6️⃣ 完整代码
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.colors.DeviceRgb;
import com.itextpdf.layout.Canvas;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.properties.TextAlignment;
import com.itextpdf.kernel.geom.Rectangle;
import org.apache.commons.lang3.StringUtils;import java.io.*;/*** @ClassName: FileWatermarkUtils* @Description: 文件水印工具类* @Author: Maxon Phong* @Date: 2025/10/10 9:50**/
public class FileWatermarkUtils {/*** PDF加水印** @param file 文件* @param watermarkText 水印文本* @return 添加水印后的PDF文件*/public static InputStream addWatermark2PDF(File file, String watermarkText) {try {return addWatermark2PDF(new FileInputStream(file), watermarkText);} catch (FileNotFoundException e) {throw new RuntimeException(e);}}/*** PDF加水印** @param inputStream 文件流* @param watermarkText 水印文本* @return 添加水印后的PDF文件流*/public static InputStream addWatermark2PDF(InputStream inputStream, String watermarkText) {if (null == inputStream) {throw new RuntimeException("输入文件不能为空");}if (null == watermarkText) {watermarkText = StringUtils.EMPTY;}try {// 创建输出流用于保存添加水印后的PDFByteArrayOutputStream outputStream = new ByteArrayOutputStream();// 创建PdfReader和PdfWriterPdfReader reader = new PdfReader(inputStream);PdfWriter writer = new PdfWriter(outputStream);// 创建PdfDocument对象PdfDocument pdfDoc = new PdfDocument(reader, writer);// 获取中文字体以支持中文水印PdfFont font = PdfFontFactory.createFont("STSong-Light", "UniGB-UCS2-H");// 定义水印样式Paragraph paragraph = new Paragraph(watermarkText).setFont(font).setFontSize(15) // 设置字体大小为15.setFontColor(new DeviceRgb(200, 200, 200)).setOpacity(0.2f); // 设置透明度为0.2f// 遍历每一页添加水印int numberOfPages = pdfDoc.getNumberOfPages();for (int i = 1; i <= numberOfPages; i++) {PdfPage page = pdfDoc.getPage(i);Rectangle pageSize = page.getPageSize();// 在页面上添加水印PdfCanvas canvas = new PdfCanvas(page.newContentStreamBefore(), page.getResources(), pdfDoc);Canvas canvas1 = new Canvas(canvas, pageSize);// 计算水印的位置,实现平铺效果float xSpacing = 300; // 水平间距float ySpacing = 150; // 垂直间距float xStart = 100; // 起始X位置float yStart = pageSize.getHeight() - 100; // 起始Y位置// 在整个页面上循环添加水印for (float y = yStart; y > 0; y -= ySpacing) {for (float x = xStart; x < pageSize.getWidth(); x += xSpacing) {canvas1.showTextAligned(paragraph, x, y, i, TextAlignment.CENTER, com.itextpdf.layout.properties.VerticalAlignment.MIDDLE, 45);}}canvas1.close();}// 关闭文档pdfDoc.close();// 返回添加水印后的PDF流return new java.io.ByteArrayInputStream(outputStream.toByteArray());} catch (Exception e) {throw new RuntimeException("添加水印失败: " + e.getMessage(), e);}}
}
