Java實現(xiàn)PDF添加水印的完整方案
前言
在企業(yè)級應(yīng)用中,PDF水印是文檔安全管理的必備功能。無論是版權(quán)保護(hù)、版本標(biāo)識,還是敏感信息管控,水印都扮演著重要角色。
本文將提供一個生產(chǎn)級的PDF水印解決方案,基于Apache PDFBox實現(xiàn),具備以下特點(diǎn):
- ? 高度可配置:支持文字、字體、顏色、旋轉(zhuǎn)角度、透明度等全方位參數(shù)配置
- ? 智能頁面控制:支持全部頁面、奇偶頁、指定頁碼等靈活策略
- ? 平鋪模式:支持水印平鋪布局,避免單一水印被截斷
- ? 多水印組合:支持在同一文檔中添加多個不同的水印
- ? 建造者模式:鏈?zhǔn)秸{(diào)用,代碼簡潔優(yōu)雅
一、技術(shù)選型
為什么選擇Apache PDFBox?
| 對比項 | Apache PDFBox | iText | OpenPDF |
|---|---|---|---|
| 開源協(xié)議 | Apache 2.0(免費(fèi)) | AGPL(商業(yè)收費(fèi)) | LGPL(較寬松) |
| 功能完整性 | ????? | ????? | ???? |
| 社區(qū)活躍度 | 高 | 中等 | 中等 |
| 學(xué)習(xí)曲線 | 平緩 | 陡峭 | 較平緩 |
| 推薦指數(shù) | ????? | ??? | ???? |
結(jié)論:對于企業(yè)項目,PDFBox是最優(yōu)選擇——開源免費(fèi)、功能完整、社區(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è)計,支持鏈?zhǔn)秸{(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)建,支持鏈?zhǔn)秸{(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)角度(度),支持正負(fù)值
// ==================== 位置配置 ====================
private WatermarkPosition position; // 水印位置枚舉
private Float customX; // 自定義X坐標(biāo)(position=CUSTOM時使用)
private Float customY; // 自定義Y坐標(biāo)(position=CUSTOM時使用)
// ==================== 平鋪配置 ====================
private boolean tilingEnabled; // 是否啟用平鋪模式
private int tilingDensityX; // X軸平鋪密度(每頁重復(fù)次數(shù))
private int tilingDensityY; // Y軸平鋪密度(每頁重復(fù)次數(shù))
private float tilingOffsetX; // X軸偏移量(百分比 0-1)
private float tilingOffsetY; // Y軸偏移量(百分比 0-1)
// ==================== 透明度配置 ====================
private float opacity; // 透明度 0-1(0完全透明,1完全不透明)
// ==================== 頁面過濾配置 ====================
private List<Integer> targetPageNumbers; // 目標(biāo)頁碼列表(從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 // 自定義坐標(biāo)
}
/**
* 頁面過濾策略
*/
public enum PageFilterStrategy {
ALL_PAGES, // 所有頁面(默認(rèn))
EVEN_PAGES, // 僅偶數(shù)頁
ODD_PAGES, // 僅奇數(shù)頁
SPECIFIC_PAGES // 指定頁碼
}
/**
* 私有構(gòu)造方法,使用Builder構(gòu)建
*/
private WatermarkConfig() {
// 設(shè)置默認(rèn)值
this.font = PDType1Font.HELVETICA_BOLD;
this.fontSize = 36f;
this.color = new Color(200, 200, 200, 128); // 默認(rèn)灰色半透明
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;
/**
* 增強(qiáng)版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 目標(biāo)文件
* @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 {
// 單點(diǎn)模式
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);
}
}
}
/**
* 添加單點(diǎn)水?。ü潭ㄎ恢?- 已修復(fù)真正居中)
*
* @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ù)位置枚舉計算坐標(biāo)
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)文字(核心方法 - 已修復(fù)方法調(diào)用順序)
*
* @param contentStream 內(nèi)容流
* @param config 水印配置
* @param x X坐標(biāo)
* @param y Y坐標(biāo)
* @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("機(jī)密文檔")
.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();
}
}
}
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("機(jī)密文檔")
.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)"機(jī)密文檔"水印,紅色半透明,旋轉(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) // 自定義坐標(biāo)
.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頁的坐標(biāo)(200, 300)處出現(xiàn)藍(lá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è)置響應(yīng)頭
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 | 所有頁面 | 默認(rèn)場景,確保每頁都有水印 |
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種預(yù)設(shè)位置 + 自定義坐標(biāo):
| 位置枚舉 | 說明 |
|---|---|
CENTER | 頁面居中 |
TOP_LEFT | 左上角 |
TOP_RIGHT | 右上角 |
BOTTOM_LEFT | 左下角 |
BOTTOM_RIGHT | 右下角 |
TOP_CENTER | 上中 |
BOTTOM_CENTER | 下中 |
CUSTOM | 自定義坐標(biāo) |
代碼示例:
// 使用預(yù)設(shè)位置 .position(WatermarkConfig.WatermarkPosition.TOP_RIGHT) // 使用自定義坐標(biāo) .customPosition(500f, 200f)
4.3 平鋪模式詳解
平鋪模式通過網(wǎng)格布局,讓水印覆蓋整個頁面,避免單一水印被截斷的問題。
核心參數(shù):
densityX:X軸平鋪密度(每頁重復(fù)次數(shù))densityY:Y軸平鋪密度(每頁重復(fù)次數(shù))offsetX:X軸偏移量(百分比 0-1)offsetY:Y軸偏移量(百分比 0-1)
可視化示意:
3x3平鋪(offsetX=0.25, offsetY=0.25): ┌─────────────────────────────┐ │ ? ? ? │ │ ? ? ? │ │ ? ? ?│ └─────────────────────────────┘ 5x4平鋪(offsetX=0.2, offsetY=0.3): ┌─────────────────────────────┐ │ ? ? ? ? ? │ │ ? ? ? ? ? │ │ ? ? ? ? ? │ │? ? ? ? ? │ └─────────────────────────────┘
代碼示例:
// 3x3平鋪,默認(rèn)偏移 .enableTiling(3, 3) // 5x4平鋪,自定義偏移 .enableTiling(5, 4, 0.2f, 0.3f)
4.4 多水印組合
支持在同一文檔中添加多個不同的水印,滿足復(fù)雜業(yè)務(wù)場景。
典型應(yīng)用場景:
- 主水印 + 副水?。ㄈ?quot;機(jī)密" + “禁止外傳”)
- 不同頁面不同水印(奇偶頁策略)
- 不同位置不同樣式的水印組合
代碼示例:
WatermarkConfig config = new WatermarkConfig.Builder()
.text("主水印")
.addMultiWatermark(watermarkConfig1)
.addMultiWatermark(watermarkConfig2)
.addMultiWatermark(watermarkConfig3)
.build();
五、常見問題與解決方案
Q1:水印顯示亂碼或字體不正確?
原因:PDFBox默認(rèn)字體不支持中文。
解決方案:加載外部中文字體文件。
// 加載中文字體
PDFont chineseFont = PDType0Font.load(document,
new File("C:/Windows/Fonts/simhei.ttf"));
WatermarkConfig config = new WatermarkConfig.Builder()
.font(chineseFont)
.text("機(jī)密文檔")
.build();
Q2:水印在原有內(nèi)容下方,被遮擋了?
原因:PDFBox默認(rèn)將新內(nèi)容添加在頁面內(nèi)容流末尾。
解決方案:調(diào)整內(nèi)容流添加順序(需要修改PDFBox底層處理邏輯,較復(fù)雜)。
替代方案:提高水印透明度,使其即使被遮擋也能隱約可見。
.opacity(0.8f) // 提高不透明度
Q3:水印文字過長,超出頁面邊界?
原因:字體大小或位置設(shè)置不當(dāng)。
解決方案:
- 調(diào)小字體大小
- 使用平鋪模式,讓水印分布在頁面多個位置
- 調(diào)整旋轉(zhuǎn)角度,減少水平空間占用
.fontSize(24f) // 調(diào)小字體 .rotation(60) // 增大旋轉(zhuǎn)角度
Q4:如何添加圖片水?。?/h3>
當(dāng)前方案限制:本文僅提供文字水印實現(xiàn)。
擴(kuò)展方案:使用PDFBox的PDImageXObject加載圖片,通過drawImage方法繪制。
// 示例代碼(需自行擴(kuò)展WatermarkConfig)
PDImageXObject image = PDImageXObject.createFromFile("watermark.png", document);
contentStream.drawImage(image, x, y, width, height);
六、性能優(yōu)化建議
6.1 批量處理優(yōu)化
對于大批量PDF文件處理,建議:
- 使用線程池:并行處理多個PDF文件
- 復(fù)用字體對象:避免重復(fù)加載字體文件
- 流式處理:大文件使用流模式,避免內(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文件(幾百M(fèi)B以上):
- 使用流模式:避免一次性加載整個文件到內(nèi)存
- 分頁處理:逐頁處理,及時釋放內(nèi)存
- 增加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ù)配置
- ? 智能頁面控制:支持全部頁面、奇偶頁、指定頁碼等靈活策略
- ? 平鋪模式:支持水印平鋪布局,避免單一水印被截斷
- ? 多水印組合:支持在同一文檔中添加多個不同的水印
- ? 建造者模式:鏈?zhǔn)秸{(diào)用,代碼簡潔優(yōu)雅
- ? 雙模式支持:既支持文件操作(批量處理),也支持流操作(Web響應(yīng)場景)
適用場景:
- 企業(yè)文檔管理系統(tǒng)
- 合同、報告等敏感文檔處理
- Web下載文件自動添加水印
- 批量PDF文件處理任務(wù)
以上就是Java實現(xiàn)PDF添加水印的完整方案的詳細(xì)內(nèi)容,更多關(guān)于Java PDF添加水印的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java 并發(fā)編程學(xué)習(xí)筆記之Synchronized簡介
雖然多線程編程極大地提高了效率,但是也會帶來一定的隱患。比如說兩個線程同時往一個數(shù)據(jù)庫表中插入不重復(fù)的數(shù)據(jù),就可能會導(dǎo)致數(shù)據(jù)庫中插入了相同的數(shù)據(jù)。今天我們就來一起討論下線程安全問題,以及Java中提供了什么機(jī)制來解決線程安全問題。2016-05-05
Java 把json對象轉(zhuǎn)成map鍵值對的方法
這篇文章主要介紹了java 把json對象中轉(zhuǎn)成map鍵值對的方法,本文的目的是把json串轉(zhuǎn)成map鍵值對存儲,而且只存儲葉節(jié)點(diǎn)的數(shù)據(jù) 。需要的朋友可以參考下2018-04-04
關(guān)于Spring?Data?Jpa?自定義方法實現(xiàn)問題
這篇文章主要介紹了關(guān)于Spring?Data?Jpa?自定義方法實現(xiàn)問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12
java實現(xiàn)table添加右鍵點(diǎn)擊事件監(jiān)聽操作示例
這篇文章主要介紹了java實現(xiàn)table添加右鍵點(diǎn)擊事件監(jiān)聽操作,結(jié)合實例形式分析了Java添加及使用事件監(jiān)聽相關(guān)操作技巧,需要的朋友可以參考下2018-07-07
Java實現(xiàn)經(jīng)典游戲2048的示例代碼
2014年Gabriele Cirulli利用周末的時間寫2048這個游戲的程序。本文將用java語言實現(xiàn)這一經(jīng)典游戲,并采用了swing技術(shù)進(jìn)行了界面化處理,需要的可以參考一下2022-02-02

