Java——pdf增加水印

文章目录

前言

近期为了知识库文件导出,文件数据安全处理,增加水印处理。

方式一 itextpdf

项目依赖引入

xml 复制代码
<!-- 文件水印添加 -->
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itextpdf</artifactId>
    <version>5.5.13</version>
</dependency>
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itext-asian</artifactId>
    <version>5.2.0</version>
</dependency>

编写PDF添加水印工具类

java 复制代码
/**
 * pdf文件添加文字水印
 *
 * @param srcPath  输入的文件路径
 * @param destPath 输出的文件路径
 * @param word     水印文字
 * @throws Exception
 */
public static void addPDFWaterMark(String srcPath, String destPath, String word)
        throws Exception {

    PdfReader reader = new PdfReader(srcPath);
    PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(destPath));

    //使用系统字体
    String prefixFont = null;
    String os = System.getProperties().getProperty("os.name");
    if (os.startsWith("win") || os.startsWith("Win")) {
        // 某些机型可能没有,需要调整
        prefixFont = "C:\\Windows\\Fonts\\SIMSUN.TTC,1";
    } else {
        prefixFont = "/usr/share/fonts/chinese/TrueType/uming.ttf";
    }

    //创建字体,第一个参数是字体路径
    BaseFont base = BaseFont.createFont(prefixFont, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
    //BaseFont base = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED);

    PdfGState gs = new PdfGState();
    gs.setFillOpacity(0.1f);//图片水印透明度(数值越大,水印越深)
    //gs.setStrokeOpacity(0.4f);//设置笔触字体不透明度
    PdfContentByte content = null;

    int total = reader.getNumberOfPages();//pdf文件页数
    for (int i = 0; i < total; i++) {
        float x = reader.getPageSize(i + 1).getWidth();//页宽度
        float y = reader.getPageSize(i + 1).getHeight();//页高度
        content = stamper.getOverContent(i + 1);
        content.setGState(gs);
        content.beginText();//开始写入
        content.setFontAndSize(base, 10);//字体大小(数值越大,字体越大)
        //每页7行,一行3个
        for (int j = 0; j < 3; j++) {
            for (int k = 0; k < 7; k++) {
                //showTextAligned 方法的参数(文字对齐方式,位置内容,输出水印X轴位置,Y轴位置,旋转角度)
                content.showTextAligned(Element.ALIGN_CENTER, word, x / 3 * j + 90, y / 7 * k, 25);
            }
        }
        content.endText();//结束写入
    }
    //关闭流
    stamper.close();
    reader.close();
}

测试

java 复制代码
public static void main(String[] args) {
    // 获取指定路径的pdf
    File file = new File("F:\\springboot-stydy\\springboot-pdf-waterMark\\pdf\\22.pdf");
    try {
        System.out.println(file.getName());
        PDFUtil.addPDFWaterMark("F:\\springboot-stydy\\springboot-pdf-waterMark\\pdf\\22.pdf",
                "F:\\springboot-stydy\\springboot-pdf-waterMark\\pdf\\water\\22.pdf" , "香蕉不拿拿(xjbnn)@xiangjiao 2025-04-08");
    } catch (Exception e) {
        e.printStackTrace();
    }
}

效果展示

方式二 pdfbox

依赖引入

xml 复制代码
<dependency>
    <groupId>org.apache.pdfbox</groupId>
    <artifactId>pdfbox</artifactId>
    <version>2.0.4</version>
</dependency>

编写实现类

java 复制代码
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
import org.apache.pdfbox.util.Matrix;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class PdBoxTest {
    public static void main(String[] args) throws IOException {
        // 水印文字
        String waterMark = "香蕉不拿拿@(xjbnn) "+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));

        // 加载源文件
        File file = new File("F:\\springboot-stydy\\springboot-pdf-waterMark\\pdf\\22.pdf");
        // 如果是 inputstream 类的流信息,也可以直接使用 PDDocument.load(inputStream)
        PDDocument document = PDDocument.load(file);
        document.setAllSecurityToBeRemoved(true);

        //使用系统字体
        String prefixFont = null;
        String os = System.getProperties().getProperty("os.name");
        if (os.startsWith("win") || os.startsWith("Win")) {
            // 仿宋 简体 常规 (其他类型会报错)
            prefixFont = "C:\\Windows\\Fonts\\simfang.ttf";
        } else {
            prefixFont = "/usr/share/fonts/chinese/TrueType/uming.ttf";
        }
        PDFont font = PDType0Font.load(document, new FileInputStream(prefixFont), true);

        // 读取pdf文件,将每一页中都增加水印
        for (PDPage page : document.getPages()) {
            PDPageContentStream stream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true, true);
            PDExtendedGraphicsState r = new PDExtendedGraphicsState();

            // 设置透明度
            r.setNonStrokingAlphaConstant(0.1f);
            r.setAlphaSourceFlag(true);
            stream.setGraphicsStateParameters(r);

            //开始写入
            stream.beginText();
            stream.setFont(font, 10);
            stream.newLineAtOffset(0, -15);

            // 获取PDF页面大小
            float pageHeight = page.getMediaBox().getHeight();
            float pageWidth = page.getMediaBox().getWidth();

            // 根据纸张大小添加水印,30度倾斜
            for (int h = 10; h < pageHeight; h = h + 150) {
                for (int w = - 10; w < pageWidth; w = w + 150) {
                    stream.setTextMatrix(Matrix.getRotateInstance(0.3, w, h));
                    stream.showText(waterMark);
                }
            }

            // 结束渲染,关闭流
            stream.endText();
            stream.restoreGraphicsState();
            stream.close();
        }

        // 将加工后的文件写入到新的文件中
        File outFile = new File("F:\\springboot-stydy\\springboot-pdf-waterMark\\pdf\\water\\pdbox.pdf");
        document.save(outFile);
        document.close();
    }
}

效果展示

扩展

1、将inputstream流信息添加水印并导出zip

使用pdfbox更方便处理。如下工具类:

java 复制代码
/**
 * 添加水印并打包压缩
 * @param inputStream 文件输入流信息
 * @param path 文件路径
 * @param zipOutputStream zip压缩包
 * @param waterMark 水印
 * @param rowSpace 行间距,大中小分别对应150/100/50
 * @param colSpace 列间距,大中小分别对应150/100/50
 * @throws Exception
 */
private void addPDFWaterMarkAndZip(InputStream inputStream,String path, ZipOutputStream zipOutputStream, String waterMark,int rowSpace,int colSpace)
        throws Exception {

    PDDocument document = PDDocument.load(inputStream);
    document.setAllSecurityToBeRemoved(true);

    //使用系统字体
    String prefixFont = null;
    String os = System.getProperties().getProperty("os.name");
    if (os.startsWith("win") || os.startsWith("Win")) {
        prefixFont = "C:\\Windows\\Fonts\\simfang.ttf";
    } else {
        prefixFont = "/usr/share/fonts/chinese/TrueType/uming.ttf";
    }
    PDFont font = PDType0Font.load(document, new FileInputStream(prefixFont), true);

    for (PDPage page : document.getPages()) {
        PDPageContentStream stream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true, true);
        PDExtendedGraphicsState r = new PDExtendedGraphicsState();

        // 设置透明度
        r.setNonStrokingAlphaConstant(0.1f);
        r.setAlphaSourceFlag(true);
        stream.setGraphicsStateParameters(r);

        //开始写入
        stream.beginText();
        stream.setFont(font, 10);
        stream.newLineAtOffset(0, -15);

        // 获取PDF页面大小
        float pageHeight = page.getMediaBox().getHeight();
        float pageWidth = page.getMediaBox().getWidth();

        // 根据纸张大小添加水印,30度倾斜
        for (int h = 10; h < pageHeight; h = h + rowSpace) {
            for (int w = - 10; w < pageWidth; w = w + colSpace) {
                stream.setTextMatrix(Matrix.getRotateInstance(0.3, w, h));
                stream.showText(waterMark);
            }
        }

        // 结束渲染,关闭流
        stream.endText();
        stream.restoreGraphicsState();
        stream.close();
    }

    // 创建临时的文件
    String snowflakeNextIdStr = IdUtil.getSnowflakeNextIdStr();
    File tempFile = File.createTempFile(snowflakeNextIdStr, ".pdf");

    // 加了水印的文件暂存临时文件
    document.save(tempFile);
    document.close();

    // 获取临时文件的数据流
    InputStream fileInputStream = new FileInputStream(tempFile);

    // 加入 zip
    ZipEntry ze = new ZipEntry(path);
    zipOutputStream.putNextEntry(ze);
    byte[] buffer = new byte[1024];
    int len;
    while ((len = fileInputStream.read(buffer)) > 0) {
        zipOutputStream.write(buffer, 0, len);
    }

    fileInputStream.close();
    // 删除临时文件
    tempFile.delete();

}

然后再调用处使用:

java 复制代码
response.setContentType("application/force-download");
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8));
ZipOutputStream zipOutputStream = new ZipOutputStream(response.getOutputStream());

2、部署出现找不到指定字体文件

可以在项目中的src/main/resource/下放入字体文件。如下

然后修改字体获取方式,如下:

java 复制代码
String ttfPath = Thread.currentThread().getContextClassLoader().getResource("font/simfang.ttf").getPath();

PDFont font = PDType0Font.load(document, new FileInputStream(ttfPath));

资料参考

V少年如他-Java实现PDF文件添加水印
Shacoray-Java针对不同文件加水印
PDF格式怎么加水印?

相关推荐
王磊鑫37 分钟前
重返JAVA之路-初识JAVA
java·开发语言
半兽先生1 小时前
WebRtc 视频流卡顿黑屏解决方案
java·前端·webrtc
南星沐2 小时前
Spring Boot 常用依赖介绍
java·前端·spring boot
代码不停3 小时前
Java中的异常
java·开发语言
何似在人间5753 小时前
多级缓存模型设计
java·jvm·redis·缓存
多云的夏天3 小时前
ubuntu24.04-MyEclipse的项目导入到 IDEA中
java·intellij-idea·myeclipse
Fanxt_Ja4 小时前
【数据结构】红黑树超详解 ---一篇通关红黑树原理(含源码解析+动态构建红黑树)
java·数据结构·算法·红黑树
Aphelios3804 小时前
TaskFlow开发日记 #1 - 原生JS实现智能Todo组件
java·开发语言·前端·javascript·ecmascript·todo
weixin_448771724 小时前
使用xml模板导出excel
xml·java·excel
烁3475 小时前
每日一题(小白)模拟娱乐篇27
java·数据结构·算法·娱乐