Java实现pdf文件压缩(aspose-pdf实现压缩、itextpdf去除aspose-pdf版权水印)

embedded/2025/3/4 3:02:46/

pdf_0">Java实现pdf文件压缩

时间换空间,实现pdf文件无损压缩。

1、依赖准备

市面上操作pdf文件的组件有spire.pdf.free、itextpdf、openpdfpdfbox等,它们各有千秋。我们主要完成的场景为压缩,减少文件大小去优化存储、传输等。

在这里选取的组件为aspose-pdfitextpdf,原因是spire.pdf.free压缩代码比较直观和简单但是只能免费压缩前10页,itextpdf压缩代码较为复杂开发难度大适合去水印,而openpdfpdfbox也有开发难度较大的问题。

1、aspose-pdf依赖

可能比较冷门,阿里云maven仓库等没有对应的依赖,无法通过gav坐标添加!因此我们需要到中央仓库下载jar包!

地址为https://mvnrepository.com/artifact/com.aspose/aspose-pdf

建议选择低版本,高版本难以去除版权水印,如这里选择21.11版本的

在这里插入图片描述
将jar引入工程

这里可以参考这篇文章

https://blog.csdn.net/m0_46357847/article/details/140749772

如果是gradle工程,可参考下图

在这里插入图片描述

2、itextpdf依赖

这里主要用于去除aspose-pdf的版权水印,直接添加即可。

<!-- https://mvnrepository.com/artifact/com.itextpdf/itextpdf -->
<dependency><groupId>com.itextpdf</groupId><artifactId>itextpdf</artifactId><version>5.5.13</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.itextpdf/itext-asian -->
<dependency><groupId>com.itextpdf</groupId><artifactId>itext-asian</artifactId><version>5.2.0</version>
</dependency>

2、压缩代码实现

PdfCompression.java压缩逻辑与去除水印的逻辑都在这个类上。

java">/*** TODO** @Description* @Author laizhenghua* @Date 2025/2/25 11:28**/
public class PdfCompression {private final static Logger log = LoggerFactory.getLogger(PdfCompression.class);/*** 水印字体常量*/private static final String WATERMARK_TEXT = "Evaluation Only. Created with Aspose.PDF. Copyright 2002-2021 Aspose Pty Ltd.";/*** 压缩比0-100可选 越低压缩比越大*/private int imageQuality = 40;public PdfCompression() {}public PdfCompression(int imageQuality) {this.imageQuality = imageQuality;}public void start(String fileName, String src, String dest) {InputStream inputStream = null;OutputStream outputStream = null;try {File srcFile = new File(src);inputStream = new FileInputStream(srcFile);File destFile = new File(dest);if (!destFile.exists()) {destFile.createNewFile();}outputStream = new FileOutputStream(destFile);start(fileName, inputStream, outputStream);} catch (IOException ex) {log.error(ex.getMessage());ex.printStackTrace();} finally {IoUtil.close(inputStream);IoUtil.close(outputStream);}}public void start(String fileName, InputStream inputStream, OutputStream outputStream) {long startTime = System.currentTimeMillis();int sourceSize = 0;long compressionSize = 0;OutputStream tempOutputStream = null;InputStream tempInputStream = null;try {// 创建临时文件// File tempFile = PathUtil.getDistTempFile(fileName);// 使用 hutool 工具类创建临时文件File tempFile = FileUtil.createTempFile("temp", ".pdf", new File("src/main/resources/static/"), true);tempOutputStream = new FileOutputStream(tempFile);Locale locale = new Locale("zh", "cn");Locale.setDefault(locale);// 记录原始大小单位为MBsourceSize = inputStream.available() / (1024 * 1024);// 读取pdf文档Document document = new Document(inputStream);// 设置压缩属性OptimizationOptions options = new OptimizationOptions();// 删除PDF不必要的对象options.setRemoveUnusedObjects(true);// 链接重复流options.setLinkDuplcateStreams(false);// 删除未使用的流options.setRemoveUnusedStreams(false);// 删除不必要的字体options.setUnembedFonts(true);// 压缩PDF中的图片options.getImageCompressionOptions().setCompressImages(true);// 图片压缩比 0-100可选 越低压缩比越大options.getImageCompressionOptions().setImageQuality(imageQuality);document.optimizeResources(options);// 优化web的PDF文档document.optimize();// 先输出到临时文件方便后续去除水印document.save(tempOutputStream);// 关闭文档-此时 aspose-pdf 使命已达document.close();// tempOutputStream.flush();// 重新记录压缩后的大小compressionSize = tempFile.length() / (1024 * 1024);// 使用 itext-pdf 去除水印// ================== 去除水印 ==================List<MatchItem> matchItemList = new ArrayList<>();// itext-pdf readertempInputStream = new FileInputStream(tempFile);PdfReader reader = new PdfReader(tempInputStream);PdfReaderContentParser parser = new PdfReaderContentParser(reader);// pdf页数int pageSize = reader.getNumberOfPages();for (int pageNum = 1; pageNum <= pageSize; pageNum++) {Rectangle rectangle = reader.getPageSize(pageNum);// 匹配监听KeyWordPositionListener listener = new KeyWordPositionListener();listener.setKeyword(WATERMARK_TEXT);listener.setPageNumber(pageNum);listener.setCurPageSize(rectangle);parser.processContent(pageNum, listener);// 先判断本页中是否存在关键词List<MatchItem> allItems = listener.getAllItems();StringBuilder sbTemp = new StringBuilder();// 将一页中所有的块内容连接起来组成一个字符串for (MatchItem item : allItems) {sbTemp.append(item.getContent());}List<MatchItem> matches = listener.getMatches();// 第一种情况:关键词与块内容完全匹配的项直接返回if (!sbTemp.toString().contains(WATERMARK_TEXT) || matches.size() > 0) {matchItemList.addAll(matches);continue;}// 第二种情况:多个块内容拼成一个关键词,则一个一个来匹配,组装成一个关键词sbTemp = new StringBuilder();List<MatchItem> tempItems = new ArrayList<>();for (MatchItem item : allItems) {if (WATERMARK_TEXT.contains(item.getContent())) {tempItems.add(item);sbTemp.append(item.getContent());// 如果暂存的字符串和关键词 不再匹配时if (!WATERMARK_TEXT.contains(sbTemp.toString())) {sbTemp = new StringBuilder(item.getContent());tempItems.clear();tempItems.add(item);}// 暂存的字符串正好匹配到关键词时if (sbTemp.toString().equalsIgnoreCase(WATERMARK_TEXT)) {// 得到匹配的项matches.add(tempItems.get(0));// 清空暂存的字符串sbTemp = new StringBuilder();// 清空暂存的LISTtempItems.clear();// 继续查找}} else {// 如果找不到则清空sbTemp = new StringBuilder();tempItems.clear();}}matchItemList.addAll(matches);}PdfStamper stamper = new PdfStamper(reader, outputStream);PdfContentByte canvas = null;Map<Integer, List<MatchItem>> mapItem = new HashMap<>();List<MatchItem> itemList = null;for (MatchItem item : matchItemList) {Integer pageNum = item.getPageNum();if (mapItem.containsKey(pageNum)) {itemList = mapItem.get(pageNum);itemList.add(item);} else {itemList = new ArrayList<>();itemList.add(item);mapItem.put(pageNum, itemList);}}// 遍历每一页去修改for (Integer page : mapItem.keySet()) {List<MatchItem> items = mapItem.get(page);// 遍历每一页中的匹配项for (MatchItem item : items) {canvas = stamper.getOverContent(page);float x = item.getX();float y = item.getY();float fontWidth = item.getFontWidth();canvas.saveState();canvas.setColorFill(BaseColor.WHITE);canvas.rectangle(x, y, fontWidth * WATERMARK_TEXT.length(), fontWidth + 2);canvas.fill();canvas.restoreState();// 开始写入文本canvas.beginText();BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED);Font font = new Font(bf, fontWidth, Font.BOLD);// 设置字体和大小canvas.setFontAndSize(font.getBaseFont(), fontWidth);// 设置字体的输出位置canvas.setTextMatrix(x, y + fontWidth / 10 + 0.5f);// 要输出的textcanvas.showText("");canvas.endText();}}stamper.close();reader.close();// 使用 hutool 工具类删除临时文件FileUtil.del(tempFile);} catch (Exception ex) {ex.printStackTrace();} finally {IoUtil.close(tempOutputStream);IoUtil.close(tempInputStream);}long endTime = System.currentTimeMillis();long duration = endTime - startTime;log.info("[{}] 压缩成功:[{}MB -> {}MB] 耗时为 {}s", fileName, sourceSize, compressionSize, duration / 1000);}
}

其他新增的辅助类:

MatchItem.java

java">/*** TODO** @Description* @Author laizhenghua* @Date 2025/2/25 09:48**/
public class MatchItem {// 页数private Integer pageNum;// x坐标private Float x;// y坐标private Float y;// 页宽private Float pageWidth;// 页高private Float pageHeight;// 匹配字符private String content;// 字体宽private float fontWidth;// 字体高private float fontHeight = 12;public Integer getPageNum() {return pageNum;}public void setPageNum(Integer pageNum) {this.pageNum = pageNum;}public Float getX() {return x;}public void setX(Float x) {this.x = x;}public Float getY() {return y;}public void setY(Float y) {this.y = y;}public Float getPageWidth() {return pageWidth;}public void setPageWidth(Float pageWidth) {this.pageWidth = pageWidth;}public Float getPageHeight() {return pageHeight;}public void setPageHeight(Float pageHeight) {this.pageHeight = pageHeight;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}public float getFontWidth() {return fontWidth;}public void setFontWidth(float fontWidth) {this.fontWidth = fontWidth;}public float getFontHeight() {return fontHeight;}public void setFontHeight(float fontHeight) {this.fontHeight = fontHeight;}
}

KeyWordPositionListener.java

java">/*** TODO** @Description 用来匹配pdf的关键词-监听类* @Author laizhenghua* @Date 2025/2/25 10:01**/
public class KeyWordPositionListener implements RenderListener {// 存放匹配上的字符信息private final List<MatchItem> matches = new ArrayList<>();// 存放所有的字符信息private List<MatchItem> allItems = new ArrayList<>();private Rectangle curPageSize;/*** 匹配的关键字*/private String keyword;/*** 匹配的当前页*/private Integer pageNumber;@Overridepublic void beginTextBlock() {// do nothing}@Overridepublic void renderText(TextRenderInfo renderInfo) {// 获取字符String content = renderInfo.getText();Rectangle2D.Float textRectangle = renderInfo.getDescentLine().getBoundingRectange();MatchItem item = new MatchItem();item.setContent(content);item.setPageNum(pageNumber);item.setFontHeight(textRectangle.height == 0 ? 12 : textRectangle.height); // 默认12item.setFontWidth(textRectangle.width);item.setPageHeight(curPageSize.getHeight());item.setPageWidth(curPageSize.getWidth());item.setX((float) textRectangle.getX());item.setY((float) textRectangle.getY());// 若keyword是单个字符,匹配上的情况if (content.equalsIgnoreCase(keyword)) {matches.add(item);}// 保存所有的项allItems.add(item);}@Overridepublic void endTextBlock() {// do nothing}@Overridepublic void renderImage(ImageRenderInfo renderInfo) {//do nothing}/*** 设置需要匹配的当前页** @param pageNumber*/public void setPageNumber(Integer pageNumber) {this.pageNumber = pageNumber;}/*** 设置需要匹配的关键字,忽略大小写** @param keyword*/public void setKeyword(String keyword) {this.keyword = keyword;}/*** 返回匹配的结果列表** @return*/public List<MatchItem> getMatches() {return matches;}public  void setCurPageSize(Rectangle rect) {this.curPageSize = rect;}public List<MatchItem> getAllItems() {return allItems;}public void setAllItems(List<MatchItem> allItems) {this.allItems = allItems;}
}

3、测试

详见以下代码

java">@Test
public void test4() {ClassPathResource resource = new ClassPathResource("/static/dist.pdf");InputStream inputStream = null;OutputStream outputStream = null;try {File file = new File("src/main/resources/static/output.pdf");if (!file.exists()) {file.createNewFile();}inputStream = resource.getInputStream();outputStream = new FileOutputStream(file);// 创建压缩类PdfCompression pdfCompression = new PdfCompression();// 调用start()方法开始压缩pdfCompression.start(resource.getFilename(), inputStream, outputStream);} catch (IOException ex) {ex.printStackTrace();} finally {IoUtil.close(inputStream);IoUtil.close(outputStream);}
}

执行代码后输出日志

在这里插入图片描述

再来看压缩效果

在这里插入图片描述


http://www.ppmy.cn/embedded/169784.html

相关文章

SpringBoot Maven快速上手

文章目录 一、Maven 1.1 Maven 简介&#xff1a;1.2 Maven 的核心功能&#xff1a; 1.2.1 项目构建&#xff1a;1.2.2 依赖管理&#xff1a; 1.3 Maven 仓库&#xff1a; 1.3.1 本地仓库&#xff1a;1.3.2 中央仓库&#xff1a;1.3.3 私服&#xff1a; 二、第一个 SpringBoot…

信刻光盘安全隔离与信息交换系统让“数据摆渡”安全高效

随着数据传输、存储及信息技术的飞速发展&#xff0c;信息安全保护已成为重中之重。各安全领域对跨网数据交互的需求日益迫切&#xff0c;数据传输的安全可靠性成为不可忽视的关键。为满足业务需求并遵守保密规范&#xff0c;针对于涉及重要秘密信息&#xff0c;需做到安全的物…

实践教程:使用DeepSeek实现PDF转Word的高效方案

&#x1f388;Deepseek推荐工具 PDF文件因其跨平台、格式稳定的特性被广泛使用&#xff0c;但在内容编辑场景中&#xff0c;用户常需将PDF转换为可编辑的Word文档。传统的付费工具&#xff08;如Adobe Acrobat&#xff09;或在线转换平台存在成本高、隐私风险等问题。本文将使…

蓝桥杯自我复习打卡

总复习&#xff0c;打卡1. 一。排序 1。选段排序 太可恶了&#xff0c;直接全排输出&#xff0c;一个测试点都没过。 AC 首先&#xff0c;这个【l,r】区间一定要包含p,或者q&#xff0c;pq一个都不包含的&#xff0c;[l,r]区间无论怎么变&#xff0c;都对ans没有影响。 其次&…

Ubuntu24.04设置静态IP地址

1. 定位配置文件 ls /etc/netplan/*.yaml # 通常为 00-installer-config.yaml2.编辑配置文件&#xff0c;注意空格 # This is the network config written by subiquity network:ethernets:ens5f0:dhcp4: falseaddresses: [ 192.168.0.251/24 ]gateway4: 192.168.0.1nameser…

[ISP] AE 自动曝光

相机通过不同曝光参数&#xff08;档位快门时间 x 感光度 x 光圈大小&#xff09;控制进光量来完成恰当的曝光。 自动曝光流程大概分为三部分&#xff1a; 1. 测光&#xff1a;点测光、中心测光、全局测光等&#xff1b;通过调整曝光档位使sensor曝光在合理的阈值内&#xff0…

蓝桥杯单片机组第十二届省赛第二批次

前言 第十二届省赛涉及知识点&#xff1a;NE555频率数据读取&#xff0c;NE555频率转换周期&#xff0c;PCF8591同时测量光敏电阻和电位器的电压、按键长短按判断。 本试题涉及模块较少&#xff0c;题目不难&#xff0c;基本上准备充分的都能完整的实现每一个功能&#xff0c;并…

【愚公系列】《Python网络爬虫从入门到精通》039-MySQL数据库

标题详情作者简介愚公搬代码头衔华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,CSDN商业化专家,阿里云专家博主,阿里云签约作者,腾讯云优秀博主,腾讯云内容共创官,掘金优秀博主,亚马逊技领云博主,51CTO博客专家等。近期荣誉2022年度…