Java 实现 PDF 页面删除、排序、旋转和裁剪

在日常开发中,经常需要对 PDF 文档做页面级别的编辑------删除多余页面、调整页面顺序、旋转方向错误的页面,或者裁剪掉不需要的边距。这类需求看起来简单,真动手写的时候,索引变化、坐标系方向这些细节很容易踩坑。本文通过 Java 代码示例,逐一介绍这几种页面操作的具体实现。

环境准备

这里用到的是 Spire.PDF for Java 这个库。如果使用 Maven,在 pom.xml 中添加如下配置:

xml 复制代码
<repositories>
    <repository>
        <id>com.e-iceblue</id>
        <name>e-iceblue</name>
        <url>https://repo.e-iceblue.cn/repository/maven-public/</url>
    </repository>
</repositories>

<dependencies>
    <dependency>
        <groupId>e-iceblue</groupId>
        <artifactId>spire.pdf</artifactId>
        <version>12.7.0</version>
    </dependency>
</dependencies>

不用 Maven 的话,官网也能直接下 jar 包手动导入项目。添加依赖后,所有页面相关的操作基本都是围绕 PdfDocument 这个类和它的 getPages() 页面集合展开的,下面分别看看。

一、删除页面

删除页面是最常见的操作。用 PdfDocument.getPages().removeAt(int index) 方法,通过指定索引(从 0 开始)删除特定页面。

java 复制代码
import com.spire.pdf.PdfDocument;

public class DeletePage {
    public static void main(String[] args) {
        // 创建 PdfDocument 实例
        PdfDocument pdf = new PdfDocument();
        // 加载 PDF 文档
        pdf.loadFromFile("sample.pdf");
        // 删除第二页(索引从 0 开始,1 表示第二页)
        pdf.getPages().removeAt(1);
        // 保存文档
        pdf.saveToFile("output/deletePage.pdf");
        pdf.close();
    }
}

页码从 0 开始计数,容易跟常见的"第一页=1"搞混,操作前最好再确认一遍索引。删除操作不可逆,建议处理前备份原始文件。

如果要一次删多个页面,要注意从后往前删。比如要删第 2、4、6 页,如果正序遍历删除,删完第 2 页后,原来的第 4 页就顶到了第 3 页的位置,接着还按原始索引删就全乱了:

java 复制代码
int[] pagesToDelete = {1, 3, 5}; // 对应第2、4、6页
Arrays.sort(pagesToDelete);
for (int i = pagesToDelete.length - 1; i >= 0; i--) {
    pdf.getPages().removeAt(pagesToDelete[i]);
}

二、调整页面顺序

页面顺序混乱时,用 PdfDocument.getPages().reArrange() 方法重排。该方法接收一个 int[] 数组,数组中每个元素代表原文档的页面索引,按数组顺序组成新的页面排列。

java 复制代码
import com.spire.pdf.PdfDocument;

public class RearrangePages {
    public static void main(String[] args) {
        // 创建 PdfDocument 实例
        PdfDocument doc = new PdfDocument();
        // 加载 PDF 文档
        doc.loadFromFile("input.pdf");
        // 重排页面顺序:原第1页、第3页、第2页、第4页
        doc.getPages().reArrange(new int[]{0, 2, 1, 3});
        // 保存文档
        doc.saveToFile("output/rearranged.pdf");
        doc.close();
    }
}

三、旋转页面

扫描版 PDF 经常会出现方向错误的问题。旋转基于 90 度增量,支持 0°/90°/180°/270° 四个方向,通过 PdfPageBase.setRotation() 方法设置。

java 复制代码
import com.spire.pdf.PdfDocument;
import com.spire.pdf.PdfPageBase;
import com.spire.pdf.PdfPageRotateAngle;

public class RotatePdfPage {
    public static void main(String[] args) {
        // 创建 PdfDocument 实例
        PdfDocument pdf = new PdfDocument();
        // 加载 PDF 文档
        pdf.loadFromFile("sample.pdf");
        // 获取第一页
        PdfPageBase page = pdf.getPages().get(0);
        // 获取当前旋转角度
        int rotation = page.getRotation().getValue();
        // 在原有角度基础上旋转 180 度
        rotation += PdfPageRotateAngle.Rotate_Angle_180.getValue();
        page.setRotation(PdfPageRotateAngle.fromValue(rotation));
        // 保存文档
        pdf.saveToFile("output/rotated.pdf");
        pdf.close();
    }
}

要注意这里是在原有角度基础上做加法,不是直接赋值。扫描件本身可能已经带了旋转属性(手机拍照生成的 PDF 很多默认就是 270 度),如果不先读一下原始角度就直接赋新值,最终显示效果跟预期会完全不一样。

批量旋转所有页面,遍历一下 pdf.getPages() 集合逐个处理即可:

java 复制代码
for (int i = 0; i < pdf.getPages().getCount(); i++) {
    PdfPageBase page = pdf.getPages().get(i);
    int rotation = page.getRotation().getValue();
    rotation += PdfPageRotateAngle.Rotate_Angle_90.getValue();
    page.setRotation(PdfPageRotateAngle.fromValue(rotation));
}

四、裁剪页面

裁剪的本质是修改页面的 CropBox(裁剪框),定义页面内容的可见区域,超出该区域的内容不会显示。用 PdfPageBase.setCropBox(Rectangle2D rect) 方法实现。

java 复制代码
import com.spire.pdf.PdfDocument;
import com.spire.pdf.PdfPageBase;
import java.awt.geom.Rectangle2D;

public class CropPdfPage {
    public static void main(String[] args) {
        // 创建 PdfDocument 实例
        PdfDocument pdf = new PdfDocument();
        // 加载 PDF 文档
        pdf.loadFromFile("example.pdf");
        // 获取第一页
        PdfPageBase page = pdf.getPages().get(0);
        // 定义裁剪区域:参数依次为 x、y、宽度、高度
        Rectangle2D rect = new Rectangle2D.Float(0, 0, 550, 822);
        // 设置裁剪框
        page.setCropBox(rect);
        // 保存裁剪后的文档
        pdf.saveToFile("output/cropped.pdf");
        pdf.close();
    }
}

Rectangle2D.Float 的四个参数单位都是 pt。这里有个注意事项得单独说一下:Spire.PDF 的坐标系统跟原生 PDF 规范是不一样的。PDF 规范里坐标原点定义在页面左下角,y 轴向上;但 Spire.PDF 为了方便程序员"自上而下"地在页面上定位内容,把坐标系统统一成了原点在左上角、y 轴向下。setCropBox 传入的 Rectangle2D 也遵循这套坐标系:x、y 表示裁剪区域左上角相对页面左上角的偏移,width、height 从这个点开始向右、向下延伸。

所以想裁掉页面顶部的一条黑边,直接把 y 设成要裁掉的高度就行,不需要用页面总高度反推。比如页面顶部有 20pt 黑边要裁掉,就写 new Rectangle2D.Float(0, 20, width, height - 20)------从距离顶部 20pt 的地方开始往下延伸到页面底部,正好跳过顶部黑边。之前下意识按"PDF 坐标原点在左下角"这个印象去算,反而是把方向搞反了,裁出来的效果要么黑边还在,要么内容被切掉一大截,后来才意识到 Spire.PDF 这一层是把坐标系统重新定义过的,不能直接套用 PDF 规范里的原始定义去想当然。

另外,setCropBox 改的只是可视区域,原始内容依然完整保留在文件里,只是渲染时超出裁剪框的部分不显示,不算真正意义上的内容删除。涉及敏感信息脱敏的场景,不能靠裁剪来实现。

综合应用示例

实际项目里这几个操作往往是组合使用的。以下示例演示"删除页面 → 重排顺序 → 旋转页面 → 裁剪页面"整合到一起的流程:

java 复制代码
import com.spire.pdf.PdfDocument;
import com.spire.pdf.PdfPageBase;
import com.spire.pdf.PdfPageRotateAngle;
import java.awt.geom.Rectangle2D;

public class PdfPageOperations {
    public static void main(String[] args) {
        PdfDocument pdf = new PdfDocument();
        pdf.loadFromFile("original.pdf");

        // 1. 删除第 3 页(索引为 2)
        pdf.getPages().removeAt(2);

        // 2. 重排剩余页面顺序
        pdf.getPages().reArrange(new int[]{0, 1, 3, 4});

        // 3. 旋转第一页 90 度
        PdfPageBase page = pdf.getPages().get(0);
        int rotation = page.getRotation().getValue();
        rotation += PdfPageRotateAngle.Rotate_Angle_90.getValue();
        page.setRotation(PdfPageRotateAngle.fromValue(rotation));

        // 4. 裁剪第二页
        PdfPageBase page2 = pdf.getPages().get(1);
        Rectangle2D rect = new Rectangle2D.Float(50, 50, 500, 700);
        page2.setCropBox(rect);

        // 保存最终结果
        pdf.saveToFile("output/final.pdf");
        pdf.close();
    }
}

顺序上建议先做删除、排序这类会改变页面索引的操作,再做旋转、裁剪这类针对具体页面对象的操作,这样代码逻辑更清晰,也不容易因为索引变化而误操作到错误的页面。

小结

这篇文章介绍了如何在 Java 中实现 PDF 页面删除、排序、旋转和裁剪。删除页面用 removeAt(),重排页面用 reArrange(),旋转页面用 setRotation(),裁剪页面用 setCropBox(),这几个方法基本覆盖了日常开发中常见的页面编辑需求。有几点需要注意:批量删除要倒序操作;旋转角度是累加而不是覆盖;裁剪的坐标系原点在左下角,跟常见的图形坐标习惯相反。这几个细节提前留意,能省不少调试时间。

相关推荐
人工干智能1 小时前
科普:Pandas 索引器 loc 与 iloc 的编程思维
java·人工智能·pandas
持力行2 小时前
C++与Java变量声明、定义及内存分配的核心区别
java·开发语言·c++
接着奏乐接着舞4 小时前
大模型开发 agent 知识库
java
xuhaoyu_cpp_java4 小时前
SpringBoot学习(四)
java·经验分享·spring boot·笔记·学习
plainGeekDev5 小时前
kapt 替换为 KSP
android·java·kotlin
蜡台5 小时前
通过Gradle脚本声明更改Java变量
android·java·开发语言·python·kotlin·gradle·groovy
程序员天天困5 小时前
后端视角看 EventBus:发布订阅总线的原理、场景与用法
java·后端
要开心吖ZSH6 小时前
AI医疗分诊与健康咨询助手agent开发——(4-2)从零到一:我给AI医疗分诊助手加了个“知识库大脑“,终于搞懂了RAG是什么
java·docker·agent·医疗·rag
AI人工智能+电脑小能手6 小时前
【大白话说Java面试题 第179题】【07_Redis篇】第15题:什么是 Redlock 算法?
java·redis·分布式锁·高可用·redlock