1️⃣ 背景与需求
在企业内部流转或对外发布的 PDF 文档中,往往需要加入"内部仅阅""机密"等标识,以防止泄露或伪造。iText 9 作为业界成熟的 PDF 处理库,提供了灵活的绘图 API,能够在已有 PDF 上批量、透明、倾斜地绘制文字水印。
博客示例基于 iText 9,实现了以下功能:
- 支持中文水印(使用
STSong-Light字体) - 透明度可调(示例中为 0.2)
- 文字倾斜 45°,实现斜纹平铺效果
- 自动遍历所有页并在每页均匀分布水印
2️⃣ 环境准备
xml
<!-- 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 读取/写入流
java
PdfReader reader = new PdfReader(inputStream);
PdfWriter writer = new PdfWriter(outputStream);
PdfDocument pdfDoc = new PdfDocument(reader, writer);
PdfReader读取原始 PDF,PdfWriter将结果写入内存流。PdfDocument同时持有读写能力,适合在原文件上直接添加内容。
3.2 配置中文字体
java
PdfFont font = PdfFontFactory.createFont("STSong-Light", "UniGB-UCS2-H");
STSong-Light是 iText 自带的宋体变体,配合UniGB-UCS2-H编码即可渲染简体中文。
3.3 定义水印样式
java
Paragraph paragraph = new Paragraph(watermarkText)
.setFont(font)
.setFontSize(15)
.setFontColor(new DeviceRgb(200, 200, 200))
.setOpacity(0.2f);
setOpacity控制透明度,0.2 让底层文字仍可辨认。- 颜色使用浅灰,避免遮挡正文。
3.4 遍历页面并绘制水印
java
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 关闭文档并返回流
java
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️⃣ 完整代码
java
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 {
// 创建输出流用于保存添加水印后的PDF
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// 创建PdfReader和PdfWriter
PdfReader 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);
}
}
}