Java——pdf增加水印

news2025/4/18 13:10:59

文章目录

  • 前言
  • 方式一 itextpdf
    • 项目依赖引入
    • 编写PDF添加水印工具类
    • 测试
    • 效果展示
  • 方式二 pdfbox
    • 依赖引入
    • 编写实现类
    • 效果展示
  • 扩展
    • 1、将inputstream流信息添加水印并导出zip
    • 2、部署出现找不到指定字体文件
  • 资料参考

前言

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

方式一 itextpdf

项目依赖引入

<!-- 文件水印添加 -->
<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添加水印工具类

/**
 * 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();
}

测试

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

依赖引入

<dependency>
    <groupId>org.apache.pdfbox</groupId>
    <artifactId>pdfbox</artifactId>
    <version>2.0.4</version>
</dependency>

编写实现类

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更方便处理。如下工具类:

/**
 * 添加水印并打包压缩
 * @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();

}

然后再调用处使用:

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/下放入字体文件。如下
在这里插入图片描述
然后修改字体获取方式,如下:

String ttfPath = Thread.currentThread().getContextClassLoader().getResource("font/simfang.ttf").getPath();

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

资料参考

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2332089.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

leetcode_19. 删除链表的倒数第 N 个结点_java

19. 删除链表的倒数第 N 个结点https://leetcode.cn/problems/remove-nth-node-from-end-of-list/ 1、题目 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#…

41、web前端开发之Vue3保姆教程(五 实战案例)

一、项目简介和需求概述 1、项目目标 1.能够基于Vue3创建项目 2.能够基本Vue3相关的技术栈进行项目开发 3.能够使用Vue的第三方组件进行项目开发 4.能够理解前后端分离的开发模式 2、项目概述 使用Vue3结合ElementPlus,ECharts工具实现后台管理系统页面,包含登录功能,…

zsh: command not found: hdc - 鸿蒙 HarmonyOS Next

终端中执行 hdc 命令抛出如下错误; zsh: command not found: hdc 解决办法 首先,查找到 DevEco-Studio 的 toolchains 目录路径; 其次,按照类似如下的文件夹层级结果推理到 toolchains 子级路径下,其中 sdk 后一级的路径可能会存在差异,以实际本地路径结构为主,直至找到 ope…

蓝桥杯--寻找整数

题解 public static void main(String[] args) {int[] mod {0, 0, 1, 2, 1, 4, 5, 4, 1, 2, 9, 0, 5, 10, 11, 14, 9, 0, 11, 18, 9, 11, 11, 15, 17, 9, 23, 20, 25, 16, 29, 27, 25, 11, 17, 4, 29, 22, 37, 23, 9, 1, 11, 11, 33, 29, 15, 5, 41, 46};long t lcm(2, 3);lo…

自然语言处理入门6——RNN生成文本

一、文本生成 我们在前面的文章中介绍了LSTM&#xff0c;根据输入时序数据可以输出下一个可能性最高的数据&#xff0c;如果应用在文字上&#xff0c;就是根据输入的文字&#xff0c;可以预测下一个可能性最高的文字。利用这个特点&#xff0c;我们可以用LSTM来生成文本。输入…

FPGA_DDR错误总结

1otp 31-67 解决 端口没连接 必须赋值&#xff1b; 2.PLACE 30-58 TERM PLINITCALIBZ这里有问题 在顶层输出但是没有管脚约束报错 3.ERROR: [Place 30-675] 这是时钟不匹配IBUF不在同一个时钟域&#xff0c;时钟不在同一个时钟域里&#xff0c;推荐的不建议修改 问题 原本…

NOIP2011提高组.玛雅游戏

目录 题目算法标签: 模拟, 搜索, d f s dfs dfs, 剪枝优化思路*详细注释版代码精简注释版代码 题目 185. 玛雅游戏 算法标签: 模拟, 搜索, d f s dfs dfs, 剪枝优化 思路 可行性剪枝 如果某个颜色的格子数量少于 3 3 3一定无解因为要求字典序最小, 因此当一个格子左边有…

基于ssm框架的校园代购服务订单管理系统【附源码】

1、系统框架 1.1、项目所用到技术&#xff1a; javaee项目 Spring&#xff0c;springMVC&#xff0c;mybatis&#xff0c;mvc&#xff0c;vue&#xff0c;maven项目。 1.2、项目用到的环境&#xff1a; 数据库 &#xff1a;mysql5.X、mysql8.X都可以jdk1.8tomcat8 及以上开发…

【10】数据结构的矩阵与广义表篇章

目录标题 二维以上矩阵矩阵存储方式行序优先存储列序优先存储 特殊矩阵对称矩阵稀疏矩阵三元组方式存储稀疏矩阵的实现三元组初始化稀疏矩阵的初始化稀疏矩阵的创建展示当前稀疏矩阵稀疏矩阵的转置 三元组稀疏矩阵的调试与总代码十字链表方式存储稀疏矩阵的实现十字链表数据标签…

猜猜乐游戏(python)

import randomprint(**30) print(欢迎进入娱乐城) print(**30)username input(输入用户名&#xff1a;) cs 0answer input( 是否加入"猜猜乐"游戏(yes/no)? )if answer yes:while True:num int(input(%s! 当前你的金币数为%d! 请充值(100&#xffe5;30币&…

spring boot 2.7 集成 Swagger 3.0 API文档工具

背景 Swagger 3.0 是 OpenAPI 规范体系下的重要版本&#xff0c;其前身是 Swagger 2.0。在 Swagger 2.0 之后&#xff0c;该规范正式更名为 OpenAPI 规范&#xff0c;并基于新的版本体系进行迭代&#xff0c;因此 Swagger 3.0 实际对应 OpenAPI 3.0 版本。这一版本着重强化了对…

Dinky 和 Flink CDC 在实时整库同步的探索之路

摘要&#xff1a;本文整理自 Dinky 社区负责人&#xff0c;Apache Flink CDC contributor 亓文凯老师在 Flink Forward Asia 2024 数据集成&#xff08;二&#xff09;专场中的分享。主要讲述 Dinky 的整库同步技术方案演变至 Flink CDC Yaml 作业的探索历程&#xff0c;并深入…

视频融合平台EasyCVR搭建智慧粮仓系统:为粮仓管理赋能新优势

一、项目背景 当前粮仓管理大多仍处于原始人力监管或初步信息化监管阶段。部分地区虽采用了简单的传感监测设备&#xff0c;仍需大量人力的配合&#xff0c;这不仅难以全面监控粮仓复杂的环境&#xff0c;还容易出现管理 “盲区”&#xff0c;无法实现精细化的管理。而一套先进…

3D Gaussian Splatting as MCMC 与gsplat中的应用实现

3D高斯泼溅(3D Gaussian splatting)自2023年提出以后,相关研究paper井喷式增长,尽管出现了许多改进版本,但依旧面临着诸多挑战,例如实现照片级真实感、应对高存储需求,而 “悬浮的高斯核” 问题就是其中之一。浮动高斯核通常由输入图像中的曝光或颜色不一致引发,也可能…

C++初阶-C++的讲解1

目录 1.缺省(sheng)参数 2.函数重载 3.引用 3.1引用的概念和定义 3.2引用的特性 3.3引用的使用 3.4const引用 3.5.指针和引用的关系 4.nullptr 5.总结 1.缺省(sheng)参数 &#xff08;1&#xff09;缺省参数是声明或定义是为函数的参数指定一个缺省值。在调用该函数是…

STM32_USB

概述 本文是使用HAL库的USB驱动 因为官方cubeMX生成的hal库做组合设备时过于繁琐 所以这里使用某大神的插件,可以集成在cubeMX里自动生成组合设备 有小bug会覆盖生成文件里自己写的内容,所以生成一次后注意保存 插件安装 下载地址 https://github.com/alambe94/I-CUBE-USBD-Com…

STM32 的编程方式总结

&#x1f9f1; 按照“是否可独立工作”来分&#xff1a; 库/方式是否可独立使用是否依赖其他库说明寄存器裸写✅ 是❌ 无完全自主控制&#xff0c;无库依赖标准库&#xff08;StdPeriph&#xff09;✅ 是❌ 只依赖 CMSIS自成体系&#xff08;F1专属&#xff09;&#xff0c;只…

MFC工具栏CToolBar从专家到小白

CToolBar m_wndTool; //创建控件 m_wndTool.CreateEx(this, TBSTYLE_FLAT|TBSTYLE_NOPREFIX, WS_CHILD | WS_VISIBLE | CBRS_FLYBY | CBRS_TOP | CBRS_SIZE_DYNAMIC); //加载工具栏资源 m_wndTool.LoadToolBar(IDR_TOOL_LOAD) //在.rc中定义&#xff1a;IDR_TOOL_LOAD BITMAP …

大厂机考——各算法与数据结构详解

目录及其索引 哈希双指针滑动窗口子串普通数组矩阵链表二叉树图论回溯二分查找栈堆贪心算法动态规划多维动态规划学科领域与联系总结​​ 哈希 ​​学科领域​​&#xff1a;计算机科学、密码学、数据结构 ​​定义​​&#xff1a;通过哈希函数将任意长度的输入映射为固定长度…

10:00开始面试,10:08就出来了,问的问题有点变态。。。

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到8月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40%…