最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java實現(xiàn)PDF添加水印的完整方案

 更新時間:2026年02月04日 08:50:29   作者:J_liaty  
在企業(yè)級應用中,PDF水印是文檔安全管理的必備功能,無論是版權(quán)保護、版本標識,還是敏感信息管控,水印都扮演著重要角色,本文將提供一個生產(chǎn)級的PDF水印解決方案,需要的朋友可以參考下

前言

在企業(yè)級應用中,PDF水印是文檔安全管理的必備功能。無論是版權(quán)保護、版本標識,還是敏感信息管控,水印都扮演著重要角色。

本文將提供一個生產(chǎn)級的PDF水印解決方案,基于Apache PDFBox實現(xiàn),具備以下特點:

  • ? 高度可配置:支持文字、字體、顏色、旋轉(zhuǎn)角度、透明度等全方位參數(shù)配置
  • ? 智能頁面控制:支持全部頁面、奇偶頁、指定頁碼等靈活策略
  • ? 平鋪模式:支持水印平鋪布局,避免單一水印被截斷
  • ? 多水印組合:支持在同一文檔中添加多個不同的水印
  • ? 建造者模式:鏈式調(diào)用,代碼簡潔優(yōu)雅

一、技術(shù)選型

為什么選擇Apache PDFBox?

對比項Apache PDFBoxiTextOpenPDF
開源協(xié)議Apache 2.0(免費)AGPL(商業(yè)收費)LGPL(較寬松)
功能完整性??????????????
社區(qū)活躍度中等中等
學習曲線平緩陡峭較平緩
推薦指數(shù)????????????

結(jié)論:對于企業(yè)項目,PDFBox是最優(yōu)選擇——開源免費、功能完整、社區(qū)活躍。

Maven依賴

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

二、核心代碼實現(xiàn)

2.1 水印配置類(WatermarkConfig)

采用建造者模式設(shè)計,支持鏈式調(diào)用,所有參數(shù)都可靈活配置。

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水印配置類
 * 使用建造者模式構(gòu)建,支持鏈式調(diào)用
 */
public class WatermarkConfig {
    
    // ==================== 基礎(chǔ)配置 ====================
    private String watermarkText;           // 水印文字(必填)
    private PDFont font;                     // 字體
    private float fontSize;                  // 字體大小
    private Color color;                     // 顏色(支持透明度)
    
    // ==================== 旋轉(zhuǎn)配置 ====================
    private int rotationAngle;               // 旋轉(zhuǎn)角度(度),支持正負值
    
    // ==================== 位置配置 ====================
    private WatermarkPosition position;      // 水印位置枚舉
    private Float customX;                   // 自定義X坐標(position=CUSTOM時使用)
    private Float customY;                   // 自定義Y坐標(position=CUSTOM時使用)
    
    // ==================== 平鋪配置 ====================
    private boolean tilingEnabled;           // 是否啟用平鋪模式
    private int tilingDensityX;              // X軸平鋪密度(每頁重復次數(shù))
    private int tilingDensityY;              // Y軸平鋪密度(每頁重復次數(shù))
    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,       // 僅偶數(shù)頁
        ODD_PAGES,        // 僅奇數(shù)頁
        SPECIFIC_PAGES    // 指定頁碼
    }

    /**
     * 私有構(gòu)造方法,使用Builder構(gòu)建
     */
    private WatermarkConfig() {
        // 設(shè)置默認值
        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 水印執(zhí)行工具類(EnhancedPdfWatermarkUtil)

核心執(zhí)行邏輯,支持流式和文件兩種模式。

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 {

    /**
     * 添加水?。髂J剑? 適合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);
        }
    }

    /**
     * 添加水?。ㄎ募J剑? 適合批量處理
     *
     * @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)) {

            // 設(shè)置字體和顏色
            contentStream.setFont(config.getFont(), config.getFontSize());
            contentStream.setNonStrokingColor(config.getColor());

            // 根據(jù)配置選擇繪制模式
            if (config.isTilingEnabled()) {
                // 平鋪模式
                addTiledWatermark(contentStream, config, pageWidth, pageHeight);
            } else {
                // 單點模式
                addSingleWatermark(contentStream, config, pageWidth, pageHeight);
            }
        }
    }

    /**
     * 添加平鋪水?。ňW(wǎng)格布局)
     *
     * @param contentStream 內(nèi)容流
     * @param config 水印配置
     * @param pageWidth 頁面寬度
     * @param pageHeight 頁面高度
     * @throws IOException IO異常
     */
    private static void addTiledWatermark(PDPageContentStream contentStream,
                                          WatermarkConfig config,
                                          float pageWidth, float pageHeight) throws IOException {
        // 計算旋轉(zhuǎn)角度(弧度)
        float angle = (float) Math.toRadians(config.getRotationAngle());

        // 計算網(wǎng)格間距
        float stepX = pageWidth / config.getTilingDensityX();
        float stepY = pageHeight / config.getTilingDensityY();

        // 遍歷網(wǎng)格繪制水印
        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 內(nèi)容流
     * @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());

        // ? 計算文字寬度,實現(xiàn)真正的左右居中
        float textWidth = config.getFont().getStringWidth(config.getWatermarkText())
                / 1000 * config.getFontSize();

        // 根據(jù)位置枚舉計算坐標
        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);
    }

    /**
     * 繪制旋轉(zhuǎn)文字(核心方法 - 已修復方法調(diào)用順序)
     *
     * @param contentStream 內(nèi)容流
     * @param config 水印配置
     * @param x X坐標
     * @param y Y坐標
     * @param angle 旋轉(zhuǎn)角度(弧度)
     * @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:所有頁面居中水?。ㄗ詈唵螆鼍埃?/h3>
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");
            
            // 構(gòu)建配置
            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();
            
            // 執(zhí)行添加水印
            EnhancedPdfWatermarkUtil.addWatermark(sourceFile, targetFile, config);
            
            System.out.println("水印添加成功!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

效果:所有頁面中央出現(xiàn)"機密文檔"水印,紅色半透明,旋轉(zhuǎn)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");
            
            // 奇數(shù)頁配置:平鋪灰色水印
            WatermarkConfig oddPageConfig = new WatermarkConfig.Builder()
                .text("內(nèi)部專用")
                .fontSize(32f)
                .color(Color.GRAY)
                .rotation(45)
                .enableTiling(3, 3) // 3x3平鋪
                .targetPages(WatermarkConfig.PageFilterStrategy.ODD_PAGES)
                .build();
            
            // 偶數(shù)頁配置:居中紅色大水印
            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();
        }
    }
}

效果

  • 奇數(shù)頁:灰色"內(nèi)部專用"水印,3x3平鋪
  • 偶數(shù)頁:紅色"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) // 不旋轉(zhuǎn)
                .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)處出現(xiàn)藍色"草稿版本"水印,不旋轉(zhuǎn)。

示例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();
        
        // 設(shè)置響應頭
        response.setContentType("application/pdf");
        response.setHeader("Content-Disposition", "attachment; filename=watermarked.pdf");
        
        // 流式輸出
        try (OutputStream output = response.getOutputStream()) {
            EnhancedPdfWatermarkUtil.addWatermark(pdfInput, output, config);
        }
    }
}

效果:用戶下載的PDF文件中,所有頁面都布滿5x4網(wǎng)格的"禁止外傳"水印。

四、核心功能詳解

4.1 頁面過濾策略

策略說明使用場景
ALL_PAGES所有頁面默認場景,確保每頁都有水印
EVEN_PAGES僅偶數(shù)頁奇偶頁不同水印的需求
ODD_PAGES僅奇數(shù)頁奇偶頁不同水印的需求
SPECIFIC_PAGES指定頁碼僅對特定頁面(如封面、結(jié)尾頁)加水印

代碼示例

// 偶數(shù)頁加水印
.targetPages(WatermarkConfig.PageFilterStrategy.EVEN_PAGES)

// 指定第1、3、5頁加水?。摯a從0開始)
.targetPages(0, 2, 4)

4.2 水印位置配置

支持8種預設(shè)位置 + 自定義坐標:

位置枚舉說明
CENTER頁面居中
TOP_LEFT左上角
TOP_RIGHT右上角
BOTTOM_LEFT左下角
BOTTOM_RIGHT右下角
TOP_CENTER上中
BOTTOM_CENTER下中
CUSTOM自定義坐標

代碼示例

// 使用預設(shè)位置
.position(WatermarkConfig.WatermarkPosition.TOP_RIGHT)

// 使用自定義坐標
.customPosition(500f, 200f)

4.3 平鋪模式詳解

平鋪模式通過網(wǎng)格布局,讓水印覆蓋整個頁面,避免單一水印被截斷的問題。

核心參數(shù)

  • densityX:X軸平鋪密度(每頁重復次數(shù))
  • densityY:Y軸平鋪密度(每頁重復次數(shù))
  • 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 多水印組合

支持在同一文檔中添加多個不同的水印,滿足復雜業(yè)務場景。

典型應用場景

  • 主水印 + 副水?。ㄈ?quot;機密" + “禁止外傳”)
  • 不同頁面不同水印(奇偶頁策略)
  • 不同位置不同樣式的水印組合

代碼示例

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:水印在原有內(nèi)容下方,被遮擋了?

原因:PDFBox默認將新內(nèi)容添加在頁面內(nèi)容流末尾。

解決方案:調(diào)整內(nèi)容流添加順序(需要修改PDFBox底層處理邏輯,較復雜)。

替代方案:提高水印透明度,使其即使被遮擋也能隱約可見。

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

Q3:水印文字過長,超出頁面邊界?

原因:字體大小或位置設(shè)置不當。

解決方案

  • 調(diào)小字體大小
  • 使用平鋪模式,讓水印分布在頁面多個位置
  • 調(diào)整旋轉(zhuǎn)角度,減少水平空間占用
.fontSize(24f) // 調(diào)小字體
.rotation(60) // 增大旋轉(zhuǎn)角度

Q4:如何添加圖片水印?

當前方案限制:本文僅提供文字水印實現(xiàn)。

擴展方案:使用PDFBox的PDImageXObject加載圖片,通過drawImage方法繪制。

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

六、性能優(yōu)化建議

6.1 批量處理優(yōu)化

對于大批量PDF文件處理,建議:

  1. 使用線程池:并行處理多個PDF文件
  2. 復用字體對象:避免重復加載字體文件
  3. 流式處理:大文件使用流模式,避免內(nèi)存溢出

示例代碼

// 使用線程池批量處理
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 內(nèi)存優(yōu)化

對于超大PDF文件(幾百MB以上):

  1. 使用流模式:避免一次性加載整個文件到內(nèi)存
  2. 分頁處理:逐頁處理,及時釋放內(nèi)存
  3. 增加JVM堆內(nèi)存-Xmx2g 或更大

示例代碼

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

七、總結(jié)

本文提供了一個生產(chǎn)級的PDF水印解決方案,具備以下核心優(yōu)勢:

  • ? 高度可配置:支持文字、字體、顏色、旋轉(zhuǎn)角度、透明度等全方位參數(shù)配置
  • ? 智能頁面控制:支持全部頁面、奇偶頁、指定頁碼等靈活策略
  • ? 平鋪模式:支持水印平鋪布局,避免單一水印被截斷
  • ? 多水印組合:支持在同一文檔中添加多個不同的水印
  • ? 建造者模式:鏈式調(diào)用,代碼簡潔優(yōu)雅
  • ? 雙模式支持:既支持文件操作(批量處理),也支持流操作(Web響應場景)

適用場景

  • 企業(yè)文檔管理系統(tǒng)
  • 合同、報告等敏感文檔處理
  • Web下載文件自動添加水印
  • 批量PDF文件處理任務

以上就是Java實現(xiàn)PDF添加水印的完整方案的詳細內(nèi)容,更多關(guān)于Java PDF添加水印的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • spring?mvc?AOP切面方法未執(zhí)行的一種情況的分析和處理過程

    spring?mvc?AOP切面方法未執(zhí)行的一種情況的分析和處理過程

    這篇文章主要介紹了spring?mvc?AOP切面方法未執(zhí)行的一種情況的分析和處理,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • Java數(shù)據(jù)結(jié)構(gòu)之插入排序與希爾排序

    Java數(shù)據(jù)結(jié)構(gòu)之插入排序與希爾排序

    在本篇文章,我們將為小伙伴們進行排序概念的基本講解并具體講解其中的兩種基礎(chǔ)排序:插入排序和希爾排序,希望小伙伴們能夠從中有所收獲
    2023-04-04
  • Java網(wǎng)絡編程實例——簡單模擬在線聊天

    Java網(wǎng)絡編程實例——簡單模擬在線聊天

    學了java網(wǎng)絡,也是該做個小案例來鞏固一下了。本次案例將使用UDP和多線程模擬即時聊天,簡單練練手。
    2021-05-05
  • javaweb中Http協(xié)議詳解

    javaweb中Http協(xié)議詳解

    HTTP是hypertext transfer protocol(超文本傳輸協(xié)議)的簡寫,它是TCP/IP協(xié)議的一個應用層協(xié)議,用于定義WEB瀏覽器與WEB服務器之間交換數(shù)據(jù)的過程。這篇文章主要為大家詳細介紹了javaweb中的Http協(xié)議,感興趣的小伙伴們可以參考一下
    2016-05-05
  • Java集合和IO流實現(xiàn)水果攤項目

    Java集合和IO流實現(xiàn)水果攤項目

    最近閑來無事,使用java基礎(chǔ)知識集合和IO流做了一個簡單的小項目,水果攤項目,用到GUI和Mysql數(shù)據(jù)庫搭建,本文通過實例代碼給大家介紹的非常詳細,需要的朋友參考下吧
    2021-06-06
  • SpringCloud微服務集成Dubbo的詳細過程

    SpringCloud微服務集成Dubbo的詳細過程

    Apache?Dubbo?是一款易用、高性能的?WEB?和?RPC?框架,同時為構(gòu)建企業(yè)級微服務提供服務發(fā)現(xiàn)、流量治理、可觀測、認證鑒權(quán)等能力、工具與最佳實踐,這篇文章主要介紹了SpringCloud微服務集成Dubbo,需要的朋友可以參考下
    2024-03-03
  • 解決FeignClient發(fā)送post請求異常的問題

    解決FeignClient發(fā)送post請求異常的問題

    這篇文章主要介紹了FeignClient發(fā)送post請求異常的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • MyEclipse如何取消默認工作空間方法示例

    MyEclipse如何取消默認工作空間方法示例

    這篇文章主要介紹了MyEclipse如何取消默認工作空間,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-10-10
  • 測試環(huán)境頻繁Full GC問題的解決思路分析

    測試環(huán)境頻繁Full GC問題的解決思路分析

    全文介紹了作者通過與調(diào)用方交互,發(fā)現(xiàn)welink-front服務不可用的問題,通過jmap-heap和jstat-gccause命令,作者找到了問題的原因是元數(shù)據(jù)區(qū)內(nèi)存使用率過高,觸發(fā)了FullGC,作者通過分析GC日志和堆內(nèi)存使用情況,確定了問題的根本原因
    2025-01-01
  • Java的注解原理詳解

    Java的注解原理詳解

    這篇文章主要介紹了Java的注解原理詳解,注解是JDK1.5引入的新特性,包含在java.lang.annotation包中,它是附加在代碼中的一些元信息,將一個類的外部信息與內(nèi)部成員聯(lián)系起來,在編 譯、運行時進行解析和使用,需要的朋友可以參考下
    2023-10-10

最新評論

阿拉尔市| 桂东县| 绵阳市| 舒城县| 佛教| 宜阳县| 珠海市| 马公市| 桐梓县| 深圳市| 丹阳市| 新乐市| 宁南县| 东至县| 五华县| 石家庄市| 盘锦市| 昭通市| 安新县| 阿坝县| 牟定县| 绥芬河市| 始兴县| 汪清县| 河津市| 育儿| 定安县| 陇川县| 新民市| 沧州市| 桂林市| 武宁县| 深水埗区| 逊克县| 永新县| 申扎县| 宁蒗| 禹州市| 沅陵县| 平武县| 广昌县|