Java实现PDF添加水印的完整方案

 更新时间:2026年02月04日 08:50:29   作者:J_liaty  
在企业级应用中,PDF水印是文档安全管理的必备功能,无论是版权保护、版本标识,还是敏感信息管控,水印都扮演着重要角色,本文将提供一个生产级的PDF水印解决方案,需要的朋友可以参考下

前言

在企业级应用中,PDF水印是文档安全管理的必备功能。无论是版权保护、版本标识,还是敏感信息管控,水印都扮演着重要角色。

本文将提供一个生产级的PDF水印解决方案,基于Apache PDFBox实现,具备以下特点:

  • 高度可配置:支持文字、字体、颜色、旋转角度、透明度等全方位参数配置
  • 智能页面控制:支持全部页面、奇偶页、指定页码等灵活策略
  • 平铺模式:支持水印平铺布局,避免单一水印被截断
  • 多水印组合:支持在同一文档中添加多个不同的水印
  • 建造者模式:链式调用,代码简洁优雅

一、技术选型

为什么选择Apache PDFBox?

对比项Apache PDFBoxiTextOpenPDF
开源协议Apache 2.0(免费)AGPL(商业收费)LGPL(较宽松)
功能完整性⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
社区活跃度中等中等
学习曲线平缓陡峭较平缓
推荐指数⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

结论:对于企业项目,PDFBox是最优选择——开源免费、功能完整、社区活跃。

Maven依赖

		<!-- Apache PDFBox for PDF处理 -->
		<dependency>
			<groupId>org.apache.pdfbox</groupId>
			<artifactId>pdfbox</artifactId>
			<version>2.0.29</version>
		</dependency>

二、核心代码实现

2.1 水印配置类(WatermarkConfig)

采用建造者模式设计,支持链式调用,所有参数都可灵活配置。

import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.font.PDFont;

import java.awt.Color;
import java.util.ArrayList;
import java.util.List;

/**
 * PDF水印配置类
 * 使用建造者模式构建,支持链式调用
 */
public class WatermarkConfig {
    
    // ==================== 基础配置 ====================
    private String watermarkText;           // 水印文字(必填)
    private PDFont font;                     // 字体
    private float fontSize;                  // 字体大小
    private Color color;                     // 颜色(支持透明度)
    
    // ==================== 旋转配置 ====================
    private int rotationAngle;               // 旋转角度(度),支持正负值
    
    // ==================== 位置配置 ====================
    private WatermarkPosition position;      // 水印位置枚举
    private Float customX;                   // 自定义X坐标(position=CUSTOM时使用)
    private Float customY;                   // 自定义Y坐标(position=CUSTOM时使用)
    
    // ==================== 平铺配置 ====================
    private boolean tilingEnabled;           // 是否启用平铺模式
    private int tilingDensityX;              // X轴平铺密度(每页重复次数)
    private int tilingDensityY;              // Y轴平铺密度(每页重复次数)
    private float tilingOffsetX;             // X轴偏移量(百分比 0-1)
    private float tilingOffsetY;             // Y轴偏移量(百分比 0-1)
    
    // ==================== 透明度配置 ====================
    private float opacity;                   // 透明度 0-1(0完全透明,1完全不透明)
    
    // ==================== 页面过滤配置 ====================
    private List<Integer> targetPageNumbers; // 目标页码列表(从0开始)
    private PageFilterStrategy pageFilterStrategy; // 页面过滤策略
    
    // ==================== 多水印配置 ====================
    private List<WatermarkConfig> multiWatermarks; // 多水印配置列表

    /**
     * 水印位置枚举
     */
    public enum WatermarkPosition {
        CENTER,           // 居中
        TOP_LEFT,         // 左上角
        TOP_RIGHT,        // 右上角
        BOTTOM_LEFT,      // 左下角
        BOTTOM_RIGHT,     // 右下角
        TOP_CENTER,       // 上中
        BOTTOM_CENTER,    // 下中
        CUSTOM            // 自定义坐标
    }

    /**
     * 页面过滤策略
     */
    public enum PageFilterStrategy {
        ALL_PAGES,        // 所有页面(默认)
        EVEN_PAGES,       // 仅偶数页
        ODD_PAGES,        // 仅奇数页
        SPECIFIC_PAGES    // 指定页码
    }

    /**
     * 私有构造方法,使用Builder构建
     */
    private WatermarkConfig() {
        // 设置默认值
        this.font = PDType1Font.HELVETICA_BOLD;
        this.fontSize = 36f;
        this.color = new Color(200, 200, 200, 128); // 默认灰色半透明
        this.rotationAngle = 45;
        this.position = WatermarkPosition.CENTER;
        this.tilingEnabled = false;
        this.tilingDensityX = 3;
        this.tilingDensityY = 3;
        this.tilingOffsetX = 0.25f;
        this.tilingOffsetY = 0.25f;
        this.opacity = 0.5f;
        this.pageFilterStrategy = PageFilterStrategy.ALL_PAGES;
        this.targetPageNumbers = new ArrayList<>();
    }

    /**
     * Builder建造者类
     */
    public static class Builder {
        private WatermarkConfig config;

        public Builder() {
            this.config = new WatermarkConfig();
        }

        public Builder text(String text) {
            config.watermarkText = text;
            return this;
        }

        public Builder font(PDFont font) {
            config.font = font;
            return this;
        }

        public Builder fontSize(float fontSize) {
            config.fontSize = fontSize;
            return this;
        }

        public Builder color(Color color) {
            config.color = color;
            return this;
        }

        public Builder rotation(int angle) {
            config.rotationAngle = angle;
            return this;
        }

        public Builder position(WatermarkPosition position) {
            config.position = position;
            return this;
        }

        public Builder customPosition(float x, float y) {
            config.position = WatermarkPosition.CUSTOM;
            config.customX = x;
            config.customY = y;
            return this;
        }

        public Builder enableTiling(int densityX, int densityY) {
            config.tilingEnabled = true;
            config.tilingDensityX = densityX;
            config.tilingDensityY = densityY;
            return this;
        }

        public Builder enableTiling(int densityX, int densityY, float offsetX, float offsetY) {
            config.tilingEnabled = true;
            config.tilingDensityX = densityX;
            config.tilingDensityY = densityY;
            config.tilingOffsetX = offsetX;
            config.tilingOffsetY = offsetY;
            return this;
        }

        public Builder opacity(float opacity) {
            config.opacity = opacity;
            return this;
        }

        public Builder targetPages(PageFilterStrategy strategy) {
            config.pageFilterStrategy = strategy;
            return this;
        }

        public Builder targetPages(Integer... pageNumbers) {
            config.pageFilterStrategy = PageFilterStrategy.SPECIFIC_PAGES;
            for (Integer num : pageNumbers) {
                config.targetPageNumbers.add(num);
            }
            return this;
        }

        public Builder addMultiWatermark(WatermarkConfig watermarkConfig) {
            if (config.multiWatermarks == null) {
                config.multiWatermarks = new ArrayList<>();
            }
            config.multiWatermarks.add(watermarkConfig);
            return this;
        }

        public WatermarkConfig build() {
            if (config.watermarkText == null || config.watermarkText.trim().isEmpty()) {
                throw new IllegalArgumentException("水印文字不能为空");
            }
            return config;
        }
    }

    // ==================== Getter方法 ====================
    public String getWatermarkText() { return watermarkText; }
    public PDFont getFont() { return font; }
    public float getFontSize() { return fontSize; }
    public Color getColor() { return color; }
    public int getRotationAngle() { return rotationAngle; }
    public WatermarkPosition getPosition() { return position; }
    public Float getCustomX() { return customX; }
    public Float getCustomY() { return customY; }
    public boolean isTilingEnabled() { return tilingEnabled; }
    public int getTilingDensityX() { return tilingDensityX; }
    public int getTilingDensityY() { return tilingDensityY; }
    public float getTilingOffsetX() { return tilingOffsetX; }
    public float getTilingOffsetY() { return tilingOffsetY; }
    public float getOpacity() { return opacity; }
    public List<Integer> getTargetPageNumbers() { return targetPageNumbers; }
    public PageFilterStrategy getPageFilterStrategy() { return pageFilterStrategy; }
    public List<WatermarkConfig> getMultiWatermarks() { return multiWatermarks; }
}

2.2 水印执行工具类(EnhancedPdfWatermarkUtil)

核心执行逻辑,支持流式和文件两种模式。

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.util.Matrix;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * 增强版PDF水印工具
 * 支持灵活配置、页面过滤、平铺模式、多水印组合
 */
public class EnhancedPdfWatermarkUtil {

    /**
     * 添加水印(流模式)- 适合Web场景
     *
     * @param inputStream 输入PDF流
     * @param outputStream 输出PDF流
     * @param config 水印配置
     * @throws IOException IO异常
     */
    public static void addWatermark(InputStream inputStream, OutputStream outputStream,
                                    WatermarkConfig config) throws IOException {
        try (PDDocument document = PDDocument.load(inputStream)) {
            addWatermarkToDocument(document, config);
            document.save(outputStream);
        }
    }

    /**
     * 添加水印(文件模式)- 适合批量处理
     *
     * @param sourceFile 源文件
     * @param targetFile 目标文件
     * @param config 水印配置
     * @throws IOException IO异常
     */
    public static void addWatermark(File sourceFile, File targetFile,
                                    WatermarkConfig config) throws IOException {
        try (PDDocument document = PDDocument.load(sourceFile)) {
            addWatermarkToDocument(document, config);
            document.save(targetFile);
        }
    }

    /**
     * 向文档添加水印(支持多水印配置)
     *
     * @param document PDF文档对象
     * @param config 水印配置
     * @throws IOException IO异常
     */
    private static void addWatermarkToDocument(PDDocument document, WatermarkConfig config) throws IOException {
        int pageCount = document.getNumberOfPages();

        // 处理主水印
        for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) {
            if (shouldAddWatermarkToPage(pageIndex, config)) {
                PDPage page = document.getPage(pageIndex);
                addWatermarkToPage(document, page, config);
            }
        }

        // 处理多水印配置(递归处理子水印)
        if (config.getMultiWatermarks() != null) {
            for (WatermarkConfig subConfig : config.getMultiWatermarks()) {
                for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) {
                    if (shouldAddWatermarkToPage(pageIndex, subConfig)) {
                        PDPage page = document.getPage(pageIndex);
                        addWatermarkToPage(document, page, subConfig);
                    }
                }
            }
        }
    }

    /**
     * 判断页面是否需要添加水印
     *
     * @param pageIndex 页码索引(从0开始)
     * @param config 水印配置
     * @return true-需要添加,false-跳过
     */
    private static boolean shouldAddWatermarkToPage(int pageIndex, WatermarkConfig config) {
        switch (config.getPageFilterStrategy()) {
            case ALL_PAGES:
                return true;
            case EVEN_PAGES:
                return (pageIndex + 1) % 2 == 0;
            case ODD_PAGES:
                return (pageIndex + 1) % 2 != 0;
            case SPECIFIC_PAGES:
                return config.getTargetPageNumbers().contains(pageIndex);
            default:
                return true;
        }
    }

    /**
     * 向单页添加水印
     *
     * @param document PDF文档对象(必须传入)
     * @param page PDF页面对象
     * @param config 水印配置
     * @throws IOException IO异常
     */
    private static void addWatermarkToPage(PDDocument document, PDPage page, WatermarkConfig config) throws IOException {
        PDRectangle pageSize = page.getMediaBox();
        float pageWidth = pageSize.getWidth();
        float pageHeight = pageSize.getHeight();

        try (PDPageContentStream contentStream = new PDPageContentStream(
                document,
                page,
                PDPageContentStream.AppendMode.APPEND,
                true,
                true)) {

            // 设置字体和颜色
            contentStream.setFont(config.getFont(), config.getFontSize());
            contentStream.setNonStrokingColor(config.getColor());

            // 根据配置选择绘制模式
            if (config.isTilingEnabled()) {
                // 平铺模式
                addTiledWatermark(contentStream, config, pageWidth, pageHeight);
            } else {
                // 单点模式
                addSingleWatermark(contentStream, config, pageWidth, pageHeight);
            }
        }
    }

    /**
     * 添加平铺水印(网格布局)
     *
     * @param contentStream 内容流
     * @param config 水印配置
     * @param pageWidth 页面宽度
     * @param pageHeight 页面高度
     * @throws IOException IO异常
     */
    private static void addTiledWatermark(PDPageContentStream contentStream,
                                          WatermarkConfig config,
                                          float pageWidth, float pageHeight) throws IOException {
        // 计算旋转角度(弧度)
        float angle = (float) Math.toRadians(config.getRotationAngle());

        // 计算网格间距
        float stepX = pageWidth / config.getTilingDensityX();
        float stepY = pageHeight / config.getTilingDensityY();

        // 遍历网格绘制水印
        for (int i = 0; i < config.getTilingDensityX(); i++) {
            for (int j = 0; j < config.getTilingDensityY(); j++) {
                float x = stepX * i + stepX * config.getTilingOffsetX();
                float y = stepY * j + stepY * config.getTilingOffsetY();

                drawRotatedText(contentStream, config, x, y, angle);
            }
        }
    }

    /**
     * 添加单点水印(固定位置 - 已修复真正居中)
     *
     * @param contentStream 内容流
     * @param config 水印配置
     * @param pageWidth 页面宽度
     * @param pageHeight 页面高度
     * @throws IOException IO异常
     */
    private static void addSingleWatermark(PDPageContentStream contentStream,
                                           WatermarkConfig config,
                                           float pageWidth, float pageHeight) throws IOException {
        float x, y;
        float angle = (float) Math.toRadians(config.getRotationAngle());

        // ✅ 计算文字宽度,实现真正的左右居中
        float textWidth = config.getFont().getStringWidth(config.getWatermarkText())
                / 1000 * config.getFontSize();

        // 根据位置枚举计算坐标
        switch (config.getPosition()) {
            case CENTER:
                // ✅ 页面中心 - 文字宽度的一半 = 文字居中
                x = pageWidth / 2 - textWidth / 2;
                y = pageHeight / 2;
                break;
            case TOP_LEFT:
                x = 100;
                y = pageHeight - 100;
                break;
            case TOP_RIGHT:
                // ✅ 右边缘 - 文字宽度 - 边距
                x = pageWidth - textWidth - 100;
                y = pageHeight - 100;
                break;
            case BOTTOM_LEFT:
                x = 100;
                y = 100;
                break;
            case BOTTOM_RIGHT:
                // ✅ 右边缘 - 文字宽度 - 边距
                x = pageWidth - textWidth - 100;
                y = 100;
                break;
            case TOP_CENTER:
                // ✅ 页面水平中心 - 文字宽度的一半
                x = pageWidth / 2 - textWidth / 2;
                y = pageHeight - 100;
                break;
            case BOTTOM_CENTER:
                // ✅ 页面水平中心 - 文字宽度的一半
                x = pageWidth / 2 - textWidth / 2;
                y = 100;
                break;
            case CUSTOM:
                x = config.getCustomX();
                y = config.getCustomY();
                break;
            default:
                x = pageWidth / 2 - textWidth / 2;
                y = pageHeight / 2;
        }

        drawRotatedText(contentStream, config, x, y, angle);
    }

    /**
     * 绘制旋转文字(核心方法 - 已修复方法调用顺序)
     *
     * @param contentStream 内容流
     * @param config 水印配置
     * @param x X坐标
     * @param y Y坐标
     * @param angle 旋转角度(弧度)
     * @throws IOException IO异常
     */
    private static void drawRotatedText(PDPageContentStream contentStream,
                                        WatermarkConfig config,
                                        float x, float y, float angle) throws IOException {
        contentStream.saveGraphicsState();

        // ✅ 正确顺序:先 beginText,再 setTextMatrix
        contentStream.beginText();
        contentStream.setTextMatrix(Matrix.getRotateInstance(angle, x, y));
        contentStream.newLineAtOffset(0, 0);
        contentStream.showText(config.getWatermarkText());
        contentStream.endText();

        contentStream.restoreGraphicsState();
    }
}

三、使用示例

示例1:所有页面居中水印(最简单场景)

import java.io.File;
import java.awt.Color;

public class Example1_SimpleWatermark {
    public static void main(String[] args) {
        try {
            File sourceFile = new File("原始.pdf");
            File targetFile = new File("带水印.pdf");
            
            // 构建配置
            WatermarkConfig config = new WatermarkConfig.Builder()
                .text("机密文档")
                .fontSize(48f)
                .color(new Color(200, 0, 0, 128)) // 半透明红色
                .rotation(30)
                .position(WatermarkConfig.WatermarkPosition.CENTER)
                .opacity(0.6f)
                .build();
            
            // 执行添加水印
            EnhancedPdfWatermarkUtil.addWatermark(sourceFile, targetFile, config);
            
            System.out.println("水印添加成功!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

效果:所有页面中央出现"机密文档"水印,红色半透明,旋转30度。

示例2:奇偶页不同水印 + 平铺效果

import java.io.File;
import java.awt.Color;

public class Example2_OddEvenWatermark {
    public static void main(String[] args) {
        try {
            File sourceFile = new File("原始.pdf");
            File targetFile = new File("奇偶水印.pdf");
            
            // 奇数页配置:平铺灰色水印
            WatermarkConfig oddPageConfig = new WatermarkConfig.Builder()
                .text("内部专用")
                .fontSize(32f)
                .color(Color.GRAY)
                .rotation(45)
                .enableTiling(3, 3) // 3x3平铺
                .targetPages(WatermarkConfig.PageFilterStrategy.ODD_PAGES)
                .build();
            
            // 偶数页配置:居中红色大水印
            WatermarkConfig evenPageConfig = new WatermarkConfig.Builder()
                .text("CONFIDENTIAL")
                .fontSize(56f)
                .color(new Color(255, 0, 0, 150))
                .rotation(-30)
                .position(WatermarkConfig.WatermarkPosition.CENTER)
                .targetPages(WatermarkConfig.PageFilterStrategy.EVEN_PAGES)
                .build();
            
            // 组合多水印配置
            WatermarkConfig combinedConfig = new WatermarkConfig.Builder()
                .text("主水印")
                .addMultiWatermark(oddPageConfig)
                .addMultiWatermark(evenPageConfig)
                .build();
            
            EnhancedPdfWatermarkUtil.addWatermark(sourceFile, targetFile, combinedConfig);
            
            System.out.println("奇偶页水印添加成功!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

效果

  • 奇数页:灰色"内部专用"水印,3x3平铺
  • 偶数页:红色"CONFIDENTIAL"水印,居中大字号

示例3:指定页码 + 自定义位置

import java.io.File;
import java.awt.Color;

public class Example3_SpecificPages {
    public static void main(String[] args) {
        try {
            File sourceFile = new File("原始.pdf");
            File targetFile = new File("指定页水印.pdf");
            
            // 配置:只在第1、3、5页添加水印
            WatermarkConfig config = new WatermarkConfig.Builder()
                .text("草稿版本")
                .fontSize(24f)
                .color(Color.BLUE)
                .rotation(0) // 不旋转
                .customPosition(200f, 300f) // 自定义坐标
                .targetPages(0, 2, 4) // 页码从0开始,即第1、3、5页
                .build();
            
            EnhancedPdfWatermarkUtil.addWatermark(sourceFile, targetFile, config);
            
            System.out.println("指定页水印添加成功!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

效果:第1、3、5页的坐标(200, 300)处出现蓝色"草稿版本"水印,不旋转。

示例4:Web流式输出 + 高密度平铺

import org.apache.pdfbox.pdmodel.font.PDType1Font;
import java.awt.Color;
import java.io.InputStream;
import java.io.OutputStream;

public class Example4_WebStream {
    public void downloadWatermarkedPdf(InputStream pdfInput, 
                                        HttpServletResponse response) throws Exception {
        // 配置:高密度平铺水印
        WatermarkConfig config = new WatermarkConfig.Builder()
            .text("禁止外传")
            .font(PDType1Font.HELVETICA_BOLD)
            .fontSize(28f)
            .color(new Color(150, 150, 150, 100))
            .rotation(45)
            .enableTiling(5, 4, 0.2f, 0.3f) // 5x4平铺,带偏移
            .opacity(0.4f)
            .targetPages(WatermarkConfig.PageFilterStrategy.ALL_PAGES)
            .build();
        
        // 设置响应头
        response.setContentType("application/pdf");
        response.setHeader("Content-Disposition", "attachment; filename=watermarked.pdf");
        
        // 流式输出
        try (OutputStream output = response.getOutputStream()) {
            EnhancedPdfWatermarkUtil.addWatermark(pdfInput, output, config);
        }
    }
}

效果:用户下载的PDF文件中,所有页面都布满5x4网格的"禁止外传"水印。

四、核心功能详解

4.1 页面过滤策略

策略说明使用场景
ALL_PAGES所有页面默认场景,确保每页都有水印
EVEN_PAGES仅偶数页奇偶页不同水印的需求
ODD_PAGES仅奇数页奇偶页不同水印的需求
SPECIFIC_PAGES指定页码仅对特定页面(如封面、结尾页)加水印

代码示例

// 偶数页加水印
.targetPages(WatermarkConfig.PageFilterStrategy.EVEN_PAGES)

// 指定第1、3、5页加水印(页码从0开始)
.targetPages(0, 2, 4)

4.2 水印位置配置

支持8种预设位置 + 自定义坐标:

位置枚举说明
CENTER页面居中
TOP_LEFT左上角
TOP_RIGHT右上角
BOTTOM_LEFT左下角
BOTTOM_RIGHT右下角
TOP_CENTER上中
BOTTOM_CENTER下中
CUSTOM自定义坐标

代码示例

// 使用预设位置
.position(WatermarkConfig.WatermarkPosition.TOP_RIGHT)

// 使用自定义坐标
.customPosition(500f, 200f)

4.3 平铺模式详解

平铺模式通过网格布局,让水印覆盖整个页面,避免单一水印被截断的问题。

核心参数

  • densityX:X轴平铺密度(每页重复次数)
  • densityY:Y轴平铺密度(每页重复次数)
  • offsetX:X轴偏移量(百分比 0-1)
  • offsetY:Y轴偏移量(百分比 0-1)

可视化示意

3x3平铺(offsetX=0.25, offsetY=0.25):
┌─────────────────────────────┐
│  ✓        ✓        ✓      │
│     ✓        ✓        ✓    │
│        ✓        ✓        ✓│
└─────────────────────────────┘

5x4平铺(offsetX=0.2, offsetY=0.3):
┌─────────────────────────────┐
│   ✓   ✓   ✓   ✓   ✓       │
│  ✓   ✓   ✓   ✓   ✓        │
│ ✓   ✓   ✓   ✓   ✓         │
│✓   ✓   ✓   ✓   ✓          │
└─────────────────────────────┘

代码示例

// 3x3平铺,默认偏移
.enableTiling(3, 3)

// 5x4平铺,自定义偏移
.enableTiling(5, 4, 0.2f, 0.3f)

4.4 多水印组合

支持在同一文档中添加多个不同的水印,满足复杂业务场景。

典型应用场景

  • 主水印 + 副水印(如"机密" + “禁止外传”)
  • 不同页面不同水印(奇偶页策略)
  • 不同位置不同样式的水印组合

代码示例

WatermarkConfig config = new WatermarkConfig.Builder()
    .text("主水印")
    .addMultiWatermark(watermarkConfig1)
    .addMultiWatermark(watermarkConfig2)
    .addMultiWatermark(watermarkConfig3)
    .build();

五、常见问题与解决方案

Q1:水印显示乱码或字体不正确?

原因:PDFBox默认字体不支持中文。

解决方案:加载外部中文字体文件。

// 加载中文字体
PDFont chineseFont = PDType0Font.load(document, 
    new File("C:/Windows/Fonts/simhei.ttf"));

WatermarkConfig config = new WatermarkConfig.Builder()
    .font(chineseFont)
    .text("机密文档")
    .build();

Q2:水印在原有内容下方,被遮挡了?

原因:PDFBox默认将新内容添加在页面内容流末尾。

解决方案:调整内容流添加顺序(需要修改PDFBox底层处理逻辑,较复杂)。

替代方案:提高水印透明度,使其即使被遮挡也能隐约可见。

.opacity(0.8f) // 提高不透明度

Q3:水印文字过长,超出页面边界?

原因:字体大小或位置设置不当。

解决方案

  • 调小字体大小
  • 使用平铺模式,让水印分布在页面多个位置
  • 调整旋转角度,减少水平空间占用
.fontSize(24f) // 调小字体
.rotation(60) // 增大旋转角度

Q4:如何添加图片水印?

当前方案限制:本文仅提供文字水印实现。

扩展方案:使用PDFBox的PDImageXObject加载图片,通过drawImage方法绘制。

// 示例代码(需自行扩展WatermarkConfig)
PDImageXObject image = PDImageXObject.createFromFile("watermark.png", document);
contentStream.drawImage(image, x, y, width, height);

六、性能优化建议

6.1 批量处理优化

对于大批量PDF文件处理,建议:

  1. 使用线程池:并行处理多个PDF文件
  2. 复用字体对象:避免重复加载字体文件
  3. 流式处理:大文件使用流模式,避免内存溢出

示例代码

// 使用线程池批量处理
ExecutorService executor = Executors.newFixedThreadPool(4);

List<File> files = Arrays.asList(new File("dir").listFiles());
for (File file : files) {
    executor.submit(() -> {
        // 处理逻辑
        EnhancedPdfWatermarkUtil.addWatermark(source, target, config);
    });
}

executor.shutdown();

6.2 内存优化

对于超大PDF文件(几百MB以上):

  1. 使用流模式:避免一次性加载整个文件到内存
  2. 分页处理:逐页处理,及时释放内存
  3. 增加JVM堆内存-Xmx2g 或更大

示例代码

// 流模式处理大文件
try (InputStream input = new FileInputStream("large.pdf");
     OutputStream output = new FileOutputStream("output.pdf")) {
    EnhancedPdfWatermarkUtil.addWatermark(input, output, config);
}

七、总结

本文提供了一个生产级的PDF水印解决方案,具备以下核心优势:

  • 高度可配置:支持文字、字体、颜色、旋转角度、透明度等全方位参数配置
  • 智能页面控制:支持全部页面、奇偶页、指定页码等灵活策略
  • 平铺模式:支持水印平铺布局,避免单一水印被截断
  • 多水印组合:支持在同一文档中添加多个不同的水印
  • 建造者模式:链式调用,代码简洁优雅
  • 双模式支持:既支持文件操作(批量处理),也支持流操作(Web响应场景)

适用场景

  • 企业文档管理系统
  • 合同、报告等敏感文档处理
  • Web下载文件自动添加水印
  • 批量PDF文件处理任务

以上就是Java实现PDF添加水印的完整方案的详细内容,更多关于Java PDF添加水印的资料请关注脚本之家其它相关文章!

相关文章

  • Java 并发编程学习笔记之Synchronized简介

    Java 并发编程学习笔记之Synchronized简介

    虽然多线程编程极大地提高了效率,但是也会带来一定的隐患。比如说两个线程同时往一个数据库表中插入不重复的数据,就可能会导致数据库中插入了相同的数据。今天我们就来一起讨论下线程安全问题,以及Java中提供了什么机制来解决线程安全问题。
    2016-05-05
  • Java 把json对象转成map键值对的方法

    Java 把json对象转成map键值对的方法

    这篇文章主要介绍了java 把json对象中转成map键值对的方法,本文的目的是把json串转成map键值对存储,而且只存储叶节点的数据 。需要的朋友可以参考下
    2018-04-04
  • 关于Spring Data Jpa 自定义方法实现问题

    关于Spring Data Jpa 自定义方法实现问题

    这篇文章主要介绍了关于Spring Data Jpa 自定义方法实现问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • IDEA中打jar包的2种方式(Maven打jar包)

    IDEA中打jar包的2种方式(Maven打jar包)

    这篇文章主要给大家介绍了关于IDEA中打jar包的2种方式,分别是不使用Maven直接打Jar包与使用Maven打jar包的两种方法,需要的朋友可以参考下
    2021-05-05
  • java实现table添加右键点击事件监听操作示例

    java实现table添加右键点击事件监听操作示例

    这篇文章主要介绍了java实现table添加右键点击事件监听操作,结合实例形式分析了Java添加及使用事件监听相关操作技巧,需要的朋友可以参考下
    2018-07-07
  • Java实现读取csv文件的两种方式

    Java实现读取csv文件的两种方式

    这篇文章主要为大家详细介绍了如何利用Java读取csv文件的两种方式,文中的示例代码讲解详细,对大家的学习或工作有一定的帮助,感兴趣的小伙伴可以了解一下
    2023-12-12
  • java实现Redisson的基本使用

    java实现Redisson的基本使用

    Redisson是一个在Redis的基础上实现的Java驻内存数据网格客户端,本文主要介绍了java实现Redisson的基本使用,具有一定的参考价值,感兴趣的可以了解一下
    2023-12-12
  • Java实现经典游戏2048的示例代码

    Java实现经典游戏2048的示例代码

    2014年Gabriele Cirulli利用周末的时间写2048这个游戏的程序。本文将用java语言实现这一经典游戏,并采用了swing技术进行了界面化处理,需要的可以参考一下
    2022-02-02
  • Java实现单链表基础操作

    Java实现单链表基础操作

    大家好,本篇文章主要讲的是Java实现单链表基础操作,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下
    2022-02-02
  • maven下载jar包改用阿里云maven库的方法

    maven下载jar包改用阿里云maven库的方法

    这篇文章主要介绍了maven下载jar包改用阿里云maven库的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-01-01

最新评论