使用 iText 9 为 PDF 添加文字水印的完整实战

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 添加中文文字水印的实现细节。核心要点包括:

  1. 正确加载中文字体
  2. 使用 Paragraph 配合透明度、颜色定义水印样式
  3. 通过 PdfCanvas + Canvas 在每页创建 平铺、倾斜 的文字网格
  4. 关闭文档确保 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);
        }
    }
}
相关推荐
怪兽20144 小时前
缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题
java·缓存·面试
皮皮林5514 小时前
Java 25 正式发布:更简洁、更高效、更现代!
java
源码_V_saaskw4 小时前
JAVA国际版二手交易系统手机回收好物回收发布闲置商品系统源码支持APP+H5
java·开发语言·微信·智能手机·微信小程序·小程序
جيون داد ناالام ميづ4 小时前
Spring AOP核心原理分析
java·数据库·spring
霸道流氓气质4 小时前
SpringBoot+MybatisPlus+自定义注解+切面实现水平数据隔离功能(附代码下载)
java·spring boot·后端
海边夕阳20065 小时前
深入解析volatile关键字:多线程环境下的内存可见性与指令重排序防护
java·开发语言·jvm·架构
ZeroKoop5 小时前
JDK版本管理工具JVMS
java·开发语言
rengang665 小时前
101-Spring AI Alibaba RAG 示例
java·人工智能·spring·rag·spring ai·ai应用编程
乾坤瞬间5 小时前
【Java后端进行ai coding实践系列二】记住规范,记住内容,如何使用iflow进行上下文管理
java·开发语言·ai编程