Java实现实现自动化pdf打水印小项目 使用技术pdfbox、Documents4j

文章目录

  • 前言
  • 源码获取
  • 一、需求说明
  • 二、 调研
    • pdf处理工具
    • word处理工具
  • 三、技术栈选择
  • 四、功能实现
    • 实现效果
    • 详细功能介绍
    • 详细代码实现
      • 项目目录
      • WordUtils
      • Main类实现部分:第一部分
      • Main类实现部分:第二部分
      • Main类实现部分:第三部分
  • 资料获取

前言

博主介绍:✌目前全网粉丝2W+,csdn博客专家、Java领域优质创作者,博客之星、阿里云平台优质作者、专注于Java后端技术领域。

涵盖技术内容:Java后端、算法、分布式微服务、中间件、前端、运维、ROS等。

博主所有博客文件目录索引:博客目录索引(持续更新)

视频平台:b站-Coder长路


源码获取

项目源码:Gitee、Github

本篇文档的视频系列讲解:Java实现自动化pdf打水印工具 开源PDF工具PDFBoxWord、Word转PDF开源工具Documents4j


一、需求说明

背景:做默默学代理需要去给每日日日练打上水印,对于重复性操作pdf编辑统一通过程序来批量处理。

目标:实现一个自定义的pdf水印工具。

第一部分:word转pdf

第二部分:pdf编辑

页眉:咨询专转本默默学课程联系官方报名处QQ:3503851091,更多资料可加群828303961
页脚:江苏专转本公众号:专转本智慧树
水印:江苏专转本网课报名vx:mmxchanglu属性:旋转45°,不透明度30%
右上角图片:专转本咨询群二维码.jpg属性:缩小比例30%

二、 调研

pdf处理工具

开源项目-pdfbox:

  • 官方网站:https://pdfbox.apache.org/

x-easypdf:国内开发,基于pdfbox。

Stirling-PDF:一款优秀的开源PDF处理工具:Stirling-PDF(Github)

  • 引用开源工具:org.apache.pdfbox

ttf文件:

  • 阿里巴巴普惠体:https://www.iconfont.cn/fonts/

word处理工具

word相关开源工具如下:

  1. Apache POI:
    • Apache POI 是一个 Java 库,可以用于处理 Microsoft Office 文档,包括 Word 文档。您可以使用 Apache POI 来读取 Word 文档并将其转换为 PDF。需要注意的是,Apache POI 主要用于读取和编辑 Word 文档,转换为 PDF 需要额外的步骤。
  2. iText:
    • iText 是一个流行的 Java 库,用于处理 PDF 文档。它可以用于将 Word 文档转换为 PDF。请注意,iText 并不是完全开源的,它有一些限制,具体取决于您的使用情况。
  3. LibreOffice:
    • LibreOffice 是一个开源办公套件,包括一个强大的文档编辑器。您可以使用 LibreOffice 的命令行接口(soffice)来将 Word 文档转换为 PDF。LibreOffice 在许多平台上都可用,并且支持多种文档格式的转换。
  4. Pandoc:
    • Pandoc 是一个通用文档转换工具,支持多种输入和输出格式。它可以将 Word 文档转换为 PDF,同时还支持许多其他文档格式。Pandoc 使用命令行进行操作。

实际选择:Documents4j

	Documents4j 是一个开源的 Java 库,用于在 Java 应用程序中进行 Microsoft Office 文档(如 Word、Excel、PowerPoint 等)的转换。它利用 Microsoft Office 的本机 API,通过启动 Microsoft Office 进程来执行文档转换。Documents4j 提供了一种简单的方式来将 Office 文档转换为其他格式,例如将 Word 文档转换为 PDF、将 Excel 表格转换为 CSV 等。Documents4j 的工作原理是通过将 Microsoft Office 作为外部进程启动,并与其进行通信来执行文档转换任务。这种方法使得可以利用 Microsoft Office 的强大功能来执行文档转换,同时又能够在 Java 环境中方便地进行集成和控制。Documents4j 提供了一个简单的 API,使得在 Java 应用程序中执行文档转换变得简单。它支持多线程操作,并且具有一定的性能优化,使得可以高效地处理大量文档转换任务。

相关实际案例:

  1. 不要在去充VIP啦 ,Java 实现 PDF、Word 互转:公众号 WeDayDayUp ,发送 案例 即可,gitee地址,https://gitee.com/HelloWangXianLin/xiaoxiao-demo

三、技术栈选择

语言选择:Java

pdf水印、编辑开源工具:pdfbox

word转pdf:Documents4j


四、功能实现

实现效果

实现功能效果:最终得到的是已经打上广告以及水印的pdf文件

image-20240215121714140

代码运行效果:

image-20240215121533848


详细功能介绍

实现思路如下

1、给定一个目录,检索该目录下所有文件,筛选出所有的docx以及PDF文件。

2、将目录下docx文件转为pdf文件,并将docx文件统一剪切到tmp目录。

3、对所有的pdf文件来进行最定义【水印+广告】处理,将所有水印处理过的pdf文件统一输出到pdf目录。【其中文件名包含有如:高数,最终输出的名字转为 2024.2.15高数日日练及答案】

页眉:咨询专转本默默学课程联系官方报名处QQ:3503851091,更多资料可加群828303961
页脚:江苏专转本公众号:专转本智慧树
水印:江苏专转本网课报名vx:mmxchanglu属性:旋转45°,不透明度30%
右上角图片:专转本咨询群二维码.jpg属性:缩小比例30%

pdf中水印增加的效果:主要就是这四个部分

image-20240215121938960


详细代码实现

项目目录

image-20240215120327526

WordUtils:word转pdf工具类
Main:任务类
images/:水印素材图片
ttfs/:ttf字体

WordUtils

package com.changlu.utils;import com.documents4j.api.DocumentType;
import com.documents4j.api.IConverter;
import com.documents4j.job.LocalConverter;
import java.io.*;public class WordUtils {/*** Word转PDF* @param filePath 源docx文件目录及名称  示例:C:\Users\93997\Desktop\watermark tools\watermarkTools\src\main\resources\2024-2-8计算机.docx* @param outFilePath 输出文件目录及名称 示例:C:\Users\93997\Desktop\watermark tools\watermarkTools\src\main\resources\2024-2-8.pdf*/public static void wordToPdf(String filePath, String outFilePath) {//源文件地址File inputWord = new File(filePath);//导出文件地址File outputFile = new File(outFilePath);InputStream doc = null;OutputStream outputStream = null;try {doc = new FileInputStream(inputWord);outputStream = new FileOutputStream(outputFile);IConverter converter = LocalConverter.builder().build();//转换docx=>pdfboolean flag = converter.convert(doc).as(DocumentType.DOC).to(outputStream).as(DocumentType.PDF).execute();if (flag) {converter.shutDown();}doc.close();outputStream.close();System.out.println("文件名:" + outFilePath + " 转换成功!");} catch (Exception e) {e.printStackTrace();}}public static void main(String[] args) {String filePath = "C:\\Users\\93997\\Desktop\\watermark tools\\watermarkTools\\src\\main\\resources\\2024-2-8计算机.docx";String outFilePath = "C:\\Users\\93997\\Desktop\\watermark tools\\watermarkTools\\src\\main\\resources\\2024-2-8.pdf";//word转pdfWordUtils.wordToPdf(filePath, outFilePath);}}

Main类实现部分:第一部分

功能描述:给定一个目录,检索该目录下所有文件,筛选出所有的docx以及PDF文件。

package com.changlu;import com.changlu.utils.WordUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLDecoder;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** @Description:* @Author: changlu* @Date: 11:19 AM*/
public class Main {//处理文件目录private static String directoryPath = "C:\\Users\\93997\\Desktop\\日日练";public static void main(String[] args) {//任务1:给定一个目录,检索该目录下所有文件,筛选出所有的docx以及PDF文件。File directory = new File(directoryPath);//搜集所有的docx文档List<String> waitWorkDocs = new ArrayList<>();//搜集所有的pdf名称List<String> waitWorkPDFs = new ArrayList<>();//遍历目录下所有的文件for (File file : directory.listFiles()) {//筛选不是目录if (!file.isDirectory()) {if (file.getName().endsWith(".docx")) {//添加docx的文件名waitWorkDocs.add(file.getName());}else if (file.getName().endsWith(".pdf")) {waitWorkPDFs.add(file.getName());}}}   }
}

Main类实现部分:第二部分

功能描述:将目录下docx文件转为pdf文件,并将docx文件统一剪切到tmp目录。

/*** @Description:* @Author: changlu* @Date: 11:19 AM*/
public class Main {//处理文件目录private static String directoryPath = "C:\\Users\\93997\\Desktop\\日日练";private static String docxDirectoryPath = "tmp";//docs临时存放目录public static void main(String[] args) {//任务1:xxx//任务2:将目录下docx文件转为pdf文件,并将docx文件统一剪切到tmp目录。System.out.println("开始执行word转pdf任务...");//临时存放docx文件的目录 tmp,若是不存在创建String targetTmpDirectory = directoryPath + File.separator + docxDirectoryPath;if (!Files.exists(Paths.get(targetTmpDirectory))) {new File(targetTmpDirectory).mkdirs();}//将所有的docx进行转换,word转pdffor (String docxName : waitWorkDocs) {String originPath = directoryPath + File.separator + docxName;String targetPath = originPath.replace(".docx", ".pdf");//docx转为pdf文件WordUtils.wordToPdf(originPath, targetPath);//将docx剪切到tmp文件String targetTmpPath = targetTmpDirectory + File.separator + docxName;try {Files.move(Paths.get(originPath), Paths.get(targetTmpPath));} catch (IOException e) {e.printStackTrace();}//添加pdf名称到pdf集合中String pdfName = targetPath.substring(targetPath.lastIndexOf(File.separator) + 1);waitWorkPDFs.add(pdfName);}}
}

Main类实现部分:第三部分

/*** @Description:* @Author: changlu* @Date: 11:19 AM*/
public class Main {//处理文件目录private static String directoryPath = "C:\\Users\\93997\\Desktop\\日日练";private static String docxDirectoryPath = "tmp";//docs临时存放目录public static void main(String[] args) {//任务1:xxx//任务2:xxx//任务3:对所有的pdf文件来进行最定义【水印+广告】处理,将所有水印处理过的pdf文件统一输出到pdf目录。System.out.println();System.out.println("开始处理pdf打水印、广告任务....");//临时存放docx文件的目录 tmp,若是不存在创建String targetPDFDirectory = directoryPath + File.separator + pdfDirectoryPath;if (!Files.exists(Paths.get(targetPDFDirectory))) {new File(targetPDFDirectory).mkdirs();}//遍历所有的pdf文件来去添加水印及其他任务并输出pdffor (String waitWorkPDFName : waitWorkPDFs) {String originPDFPath = directoryPath + File.separator + waitWorkPDFName;String targetPDFPath = targetPDFDirectory + File.separator + fileNameTransfer(waitWorkPDFName);//执行真正的水印+广告任务try {work(originPDFPath, targetPDFPath);} catch (Exception e) {e.printStackTrace();}}}//根据当前文件名称来匹配相应的文件名称public static String fileNameTransfer(String originName) {for (Map.Entry<String, String> entry : subjectNamesMap.entrySet()) {String name = entry.getKey();//若是当前名称中包含有科目名称if (originName.contains(name)) {return entry.getValue();}}return "未知" + System.currentTimeMillis() + ".pdf";}/*** 实际水印处理任务*/public static void work(String originPDFPath, String targetPDFPath) throws Exception{//读取resources目录下input.pdf文件
//        InputStream is = Main.class.getClassLoader().getResourceAsStream("input.pdf");InputStream is = new FileInputStream(originPDFPath);PDDocument pdDocument = PDDocument.load(is);//自定义字体 C:\Users\93997\Desktop\watermark tools\watermarkTools\target\classes\ttfs//URLDecoder.decode() 方法来解码 URL 编码的路径,将 %20 转换回空格
//        PDType0Font font = PDType0Font.load(pdDocument, new File("C:\\Users\\93997\\Desktop\\watermark tools\\watermarkTools\\src\\main\\resources\\ttfs\\Alibaba_PuHuiTi_2.0_65_Medium_65_Medium.ttf"));String fontFile = URLDecoder.decode(Main.class.getClassLoader().getResource(File.separator + "ttfs" + File.separator + "Alibaba_PuHuiTi_2.0_65_Medium_65_Medium.ttf").getFile(), "UTF-8");PDType0Font font = PDType0Font.load(pdDocument, new File(fontFile));float fontSize = 10; // 设置字体大小为12// 设置透明度状态对象PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();graphicsState.setNonStrokingAlphaConstant(0.2f);graphicsState.setAlphaSourceFlag(true);graphicsState.setStrokingAlphaConstant(0.2f);//设置新的页眉String headerText = "咨询专转本默默学课程联系官方报名处QQ:3503851091,更多资料可加群828303961";String footerText = "江苏专转本公众号:专转本智慧树";String waterText = "江苏专转本网课报名vx:mmxchanglu";//遍历原先的pdf文档for (PDPage page : pdDocument.getPages()) {float pageWidth = page.getMediaBox().getWidth();float pageHeight = page.getMediaBox().getHeight();//计算页眉的居中位置float headerTextWidth = font.getStringWidth(headerText) / 1000 * fontSize;float headerCenteredX = (pageWidth - headerTextWidth) / 2; // 计算水平居中位置//计算页脚的居中位置float footerTextWidth = font.getStringWidth(footerText) / 1000 * fontSize;float footerCenteredX = (pageWidth - footerTextWidth) / 2; // 计算水平居中位置// 创建用于页眉的内容流PDPageContentStream headerContentStream = new PDPageContentStream(pdDocument, page, PDPageContentStream.AppendMode.APPEND, true, true);headerContentStream.beginText(); // 开始文本操作headerContentStream.setFont(font, fontSize); // 设置字体和字号headerContentStream.newLineAtOffset(headerCenteredX, page.getMediaBox().getHeight() - 30); // 设置文本起始位置headerContentStream.showText(headerText); // 绘制页眉内容headerContentStream.endText(); // 结束文本操作headerContentStream.close(); // 关闭内容流// 添加页脚PDPageContentStream footerContentStream = new PDPageContentStream(pdDocument, page, PDPageContentStream.AppendMode.APPEND, true, true);footerContentStream.beginText(); // 开始文本操作footerContentStream.setFont(font, fontSize); // 设置字体和字号footerContentStream.newLineAtOffset(footerCenteredX, 30); // 设置文本起始位置footerContentStream.showText(footerText); // 绘制页脚内容footerContentStream.endText(); // 结束文本操作footerContentStream.close(); // 关闭内容流//添加水印   要求:旋转45°,不透明度30%float waterTextWidth = font.getStringWidth(waterText) / 1000 * 30;float waterCenteredX = (pageWidth - waterTextWidth) / 2;float waterCenteredY = pageHeight / 2;//创建一个水印内容流PDPageContentStream waterContentStream = new PDPageContentStream(pdDocument, page, PDPageContentStream.AppendMode.APPEND, true, true);waterContentStream.beginText();waterContentStream.setFont(font, 30);// 设置不透明度waterContentStream.setNonStrokingColor(0, 0, 0); // black colorwaterContentStream.setStrokingColor(0, 0, 0); // black colorwaterContentStream.setGraphicsStateParameters(graphicsState);//设置透明度//设置旋转文本 45° 对于tx、ty是以左下角为偏移位置中心来进行旋转角度waterContentStream.setTextRotation(Math.toRadians(45), 400, -50);//设置文本waterContentStream.newLineAtOffset(waterCenteredX, waterCenteredY);waterContentStream.showText(waterText);waterContentStream.endText();waterContentStream.close();//添加图片水印//创建一个水印内容流PDPageContentStream imageContentStream = new PDPageContentStream(pdDocument, page, PDPageContentStream.AppendMode.APPEND, true, true);// 创建图像对象
//            PDImageXObject image = PDImageXObject.createFromFile("C:\\Users\\93997\\Desktop\\watermark tools\\watermarkTools\\src\\main\\resources\\images\\ConsultationGroupQRCode.jpg", pdDocument);String pictureFile = URLDecoder.decode(Main.class.getClassLoader().getResource(File.separator + "images" + File.separator + "ConsultationGroupQRCode.jpg").getFile(), "UTF-8");PDImageXObject image = PDImageXObject.createFromFile(pictureFile, pdDocument);// 计算图像的宽度和高度(缩小比例为0.3)float imageWidth = (float) (image.getWidth() * 0.25);float imageHeight = (float) (image.getHeight() * 0.25);//具体图片位置float imageX = pageWidth - imageWidth - 10;float imageY = pageHeight - imageHeight - 10;// 在指定位置绘制图像imageContentStream.drawImage(image, imageX, imageY, imageWidth, imageHeight);imageContentStream.close();}//目标目录
//        File outputFile = new File("C:\\Users\\93997\\Desktop\\watermark tools\\watermarkTools\\src\\main\\resources\\output.pdf");File outputFile = new File(targetPDFPath);// 若是文件存在先进行删除Files.deleteIfExists(Paths.get(outputFile.toURI()));// 保存修改后的文档pdDocument.save(outputFile);System.out.println("转换任务:" + originPDFPath + "=>" + targetPDFPath + " 成功!");// 关闭文档pdDocument.close(); // 关闭文档}
}

资料获取

大家点赞、收藏、关注、评论啦~

精彩专栏推荐订阅:在下方专栏👇🏻

  • 长路-文章目录汇总(算法、后端Java、前端、运维技术导航):博主所有博客导航索引汇总
  • 开源项目Studio-Vue—校园工作室管理系统(含前后台,SpringBoot+Vue):博主个人独立项目,包含详细部署上线视频,已开源
  • 学习与生活-专栏:可以了解博主的学习历程
  • 算法专栏:算法收录

更多博客与资料可查看👇🏻获取联系方式👇🏻,🍅文末获取开发资源及更多资源博客获取🍅


整理者:长路 时间:2024.2.16

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

win7自带截图工具保存失效解决办法

今日发现一台远航技术的win7中自带的截图工具使用时正常&#xff0c;保存图片时没有弹出保存位置的对话窗口&#xff0c;无法正常保存图片。解决方案如下&#xff1a; 1、进入注册表编辑器。开始-搜索程序和文件-输入 regedit 按下回车键&#xff0c;打开注册表&#xff1b; 2、…

DS Wannabe之5-AM Project: DS 30day int prep day18

Q1. What is Levenshtein Algorithm? Levenshtein算法&#xff0c;也称为编辑距离算法&#xff0c;是一种量化两个字符串之间差异的方法。它通过计算将一个字符串转换成另一个字符串所需的最少单字符编辑操作次数来实现。这些编辑操作包括插入、删除和替换字符。Levenshtein距…

【知识图谱--第三讲知识图谱的存储与查询】

知识图谱的存储与查询 基于关系型数据库的知识图谱存储基于原生图数据库的知识图谱存储原生图数据库实现原理浅析 基于关系型数据库的知识图谱存储 基于原生图数据库的知识图谱存储 原生图数据库实现原理浅析

黑群晖一键修复:root、AME、DTS、转码、CPU型号等

食用方法&#xff1a;SSH连接群晖使用临时root权限执行 AME3.x激活补丁 只适用于x86_64的&#xff1a;DSM7.x Advanced Media Extensions (AME)版本3.0.1-2004、3.1.0-3005 激活过程需要下载官方的解码包&#xff0c;过程较慢&#xff0c;耐心等待。。。 DSM7.1和7.2的AME版…

CMake进行C/C++与汇编混合编程

1. 前提 这篇文章记录一下怎么用CMake进行项目管理, 并用C/C和汇编进行混合编程, 为了使用这项技术, 必须在VS的环境中安装好cmake组件 由于大部分人不会使用C/C与汇编进行混合编程的情况。所以这篇文章并不适用于绝大部分人不会对其中具体细节进行过多叙述。只是做一些简单的…

静态时序分析:SDC约束命令set_clock_transition详解

相关阅读 静态时序分析https://blog.csdn.net/weixin_45791458/category_12567571.html?spm1001.2014.3001.5482 在静态时序分析&#xff1a;SDC约束命令create_clock详解一文的最后&#xff0c;我们谈到了针对理想(ideal)时钟&#xff0c;可以使用set_clock_transition命令直…

数据发送程序

#include<reg51.h> //包含单片机寄存器的头文件 unsigned char code Tab[ ]{0xFE,0xFD,0xFB,0xF7,0xEF,0xDF,0xBF,0x7F}; //流水灯控制码&#xff0c;该数组被定义为全局变量 /***************************************************** 函数功能&#xff1a;向PC发…

wordpress外贸成品网站模板

首页大图slider轮播&#xff0c;橙色风格的wordpress外贸网站模板 https://www.zhanyes.com/waimao/6250.html 蓝色经典风格的wordpress外贸建站模板 https://www.zhanyes.com/waimao/6263.html

【论文精读】DALL·E

摘要 本文利用从互联网上收集的2.5亿个图像/文本对数据&#xff0c;训练了一个120亿参数的自回归transformer&#xff0c;进而得到一个可以通过自然语言/图像控制生成的高保真图像生成模型。在大多数数据集上的表现超越以往的方法。 框架 本文的目标为通过训练一个自回归trans…

牛客小白月赛87

说明 年后第一次写题&#xff0c;已经麻了&#xff0c;这次的题很简单但居然只写了两道题。有种本该发挥80分的水平&#xff0c;但是只做出了20分的水平的感觉。不过剩下几个题&#xff08;除了G题&#xff09;&#xff0c;比完赛一小时内就AC了。欢迎大家交流学习。&#xff0…

java8-用optional取代nu11

本章内容口nu11引用引发的问题&#xff0c;以及为什么要避免nu11引用从nu11到optiona1:以nu11安全的方式重写你的域模型让optiona1发光发热:去除代码中对nu11的检查 读取optiona1中可能值的几种方法口对可能缺失值的再思考 如果你作为Java程序员曾经遭遇过Nu11PointerException…

【动态规划初识】不同的二叉搜索树

每日一道算法题之不同二叉搜索树个数 一、题目描述二、思路三、C++代码一、题目描述 题目来源:LeetCode 给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。 C++程序要求输入输出格式如下: 示例1:…