Node.js 快速分割和合并 PDF 文档

851 阅读3分钟

PDF文件分割一些软件开始收费,本着能写代码不下软件的,编程启动!

〇、代码环境

  • pnpm -v 9.14.2
  • node -v v22.0.0
  • pdf-lib ^1.17.1

使用Node.js的包管理pnpm工具安装第三方模块pdf-lib。若没有pnpm 可以额外执行安装:

npm i -g pnpm  //已安装pnpm可忽略
pnpm install pdf-lib  

一、分割PDF文件

图片.png

1.0 起始代码

//1处理PDF.js
const fsPro = require('fs/promises');
const { PDFDocument } = require('pdf-lib');

//单页调用
处理PDF('A.pdf', '第1页.pdf', [1]);
处理PDF('A.pdf', '第2页.pdf', [2]);
处理PDF('A.pdf', '第3页.pdf', [3]);
处理PDF('A.pdf', '第1-3页.pdf', [1, 2, 3]);

//循环调用
for (let i = 1; i <= 10; i++) {
    处理PDF('A.pdf', `第${i}页.pdf`, [i]);
}

async function 处理PDF(输入路径, 输出路径, 页码列表) {
    try {
        const 文件内容 = await fsPro.readFile(输入路径);
        const 输入PDF = await PDFDocument.load(文件内容);
        constPDF = await PDFDocument.create();

        for (let 页码 of 页码列表) {
            if (!Number.isInteger(页码) || 页码 <= 0) {
                throw new Error(`无效的页面索引: ${页码}。页面索引必须是正整数。`);
            }
            const [复制页] = awaitPDF.copyPages(输入PDF, [页码 - 1]);
            新PDF.addPage(复制页);
        }

        const 新文件内容 = awaitPDF.save();
        await fsPro.writeFile(输出路径, 新文件内容);
        console.log(` ${输出路径} 文件提取成功!`);
    } catch (错误) {
        if (错误.errno === -4056) {
            console.error(`检查这个 ${输入路径} 文件不存在`);
        } else {
            console.error(`${输出路径} 文件发生错误:`, 错误);
        }
    }
}

1.1 自定义截取函数

问题2:要生成第4、5、6...99、100页,我该怎么传入参数

答案: [4,5,6,...,99,100]

传参不太方便,需要穿一个完整的数组 自定义一个页码截取函数,可以设置2000页使用slice截取,注意此处页码要从1开始。

图片.png

// 2截取.js
console.log( 截取(4,100)  ) //第4页,到第100页
console.log( 截取(4,10)  )

//待完成:输出 从第1页 截取到学号后2位的页码

function 截取(起,终){
    let arr = []
    for(let i= 0;i<=2000;i++){
        arr.push(i)
    }
    return arr.slice(起,终+1)

}//截取(起,终)

将以上两部分代码截图起来:

图片.png 现在执行代码语句只用传入起始页和结束页,以下两种写法是等价的。

提取页面('A.pdf', '第1-8页.pdf', [1,2,3,4,5,6,7,8] );
提取页面('A.pdf', '第1-8页.pdf', 截取(1,8) );

1.2 代码执行效果演示

PDF分割.gif

1.3 完整代码

//1处理PDF.js
const fsPro = require('fs/promises');
const { PDFDocument } = require('pdf-lib');

处理PDF('A.pdf', '模块一样卷5.pdf', 截取(3,11) )
处理PDF('A.pdf', '模块二样卷5.pdf', 截取(11,23) )
处理PDF('A.pdf', '模块三样卷5.pdf', 截取(24,29) )

async function 处理PDF(输入路径, 输出路径, 页码列表) {
    try {
        const 文件内容 = await fsPro.readFile(输入路径);
        const 输入PDF = await PDFDocument.load(文件内容);
        constPDF = await PDFDocument.create();

        for (let 页码 of 页码列表) {
            if (!Number.isInteger(页码) || 页码 <= 0) {
                throw new Error(`无效的页面索引: ${页码}。页面索引必须是正整数。`);
            }
            const [复制页] = awaitPDF.copyPages(输入PDF, [页码 - 1]);
            新PDF.addPage(复制页);
        }

        const 新文件内容 = awaitPDF.save();
        await fsPro.writeFile(输出路径, 新文件内容);
        console.log(` ${输出路径} 文件提取成功!`);
    } catch (错误) {
        if (错误.errno === -4056) {
            console.error(`检查这个 ${输入路径} 文件不存在`);
        } else {
            console.error(`${输出路径} 文件发生错误:`, 错误);
        }
    }
}


function 截取(起,终){
    let arr = []
    for(let i= 0;i<=2000;i++){
        arr.push(i)
    }
    return arr.slice(起,终+1)

}//截取(起,终)

二、合并PDF文件

图片.png

2.0 完整代码

const fsPro = require('fs/promises')
const { PDFDocument } = require('pdf-lib')

//步骤2.1 传入参数格式和调用
const 文件数组 = ['第1页.pdf', '第2页.pdf', '第3页.pdf']
const 输出路径 = '第1-3页.pdf'

合并PDF(文件数组, 输出路径)


async function 合并PDF(文件数组, 输出路径) {
    try {
        //关键1. 创建一个新的PDF空文档
        const PDF空文档 = await PDFDocument.create()

        for (const 文件 of 文件数组) {
            
            const 文件内容 = await fsPro.readFile(文件)
            const 读取PDF = await PDFDocument.load(文件内容)
            const 总页数 = 读取PDF.getPageCount() 

            for (let 页码 = 0; 页码 < 总页数; 页码++) {
                const [复制页] = await PDF空文档.copyPages(读取PDF, [页码])
                PDF空文档.addPage(复制页)
            }

        }

        const 新文件内容 = await PDF空文档.save()
        await fsPro.writeFile(输出路径, 新文件内容)
        console.log(`${输出路径} 文件合并成功!`)
    } catch (错误) {
        console.error(`合并文件发生错误:`, 错误)
    }
}

2.1 传入参数格式和调用

const 文件数组 = ['第1页.pdf', '第2页.pdf', '第3页.pdf']
const 输出路径 = '第1-3页.pdf'
合并PDF(文件数组, 输出路径)

2.2 创建一个新的PDF空文档

const PDF空文档 = await PDFDocument.create()

2.3 遍历文件数组

使用 readFile 读取PDF文件内容
使用 PDFDocument.load() 加载读取到的PDF文件内容
使用getPageCount()获取加载PDF总页数

for (const 文件 of 文件数组) {
    const 文件内容 = await fsPro.readFile(文件)
    const 读取PDF = await PDFDocument.load(文件内容)
    const 总页数 = 读取PDF.getPageCount() 
     //...
}

2.4 复制每一页到新的PDF空文档

使用 copyPages(PDF对象,页码数组) 读取PDF页面,
使用 PDFDocument.addPage() 添加复制的页面

for (const 文件 of 文件数组) {
     //...
     for (let 页码 = 0; 页码 < 总页数; 页码++) {
      const [复制页] = await PDF空文档.copyPages(读取PDF, [页码])
      PDF空文档.addPage(复制页)
     }
     
}

2.5 保存新的PDF文档

新PDF文档.save()pdf-lib 库提供的方法,用于将当前的PDF文档状态保存为二进制数据。返回的结果是一个Buffer对象,包含PDF文件的二进制数据。

const 新文件内容 = await PDF空文档.save()
await fsPro.writeFile(输出路径, 新文件内容)

三、pnpm常见错误

1、提示:network err...seconds 等

通常是网络问题,确定网络状况良好。可以重复尝试。 下载模块太慢 配置淘宝镜像源

pnpm config set registry https://registry.npmmirror.com/

2、提示:禁止在系统上运行脚本

这里有多种方法都是目的就是授权命令,

2.1使用VScode中的终端

使用管理员权限打开VScode,在任意位置的命令行终端执行:
set-ExecutionPolicy RemoteSigned即可立即生效。

图片.png
2.2使用系统自带的终端

Win+R键输入cmd,回车输入 set-ExecutionPolicy RemoteSigned输入Y确认即可。 解决办法:在电脑中 使用管理员身份运行 Windows PowerShell ! 图片.png

3、写入文件路径错误

发现是有的同学将代码放在了U盘里!神操作啊。经常修改的代码不要放在U盘里修改或者运行, 会极大地减少U盘寿命,并且代码写入的权限容易不稳定!

四、附录

1.pdf-lib库及作者信息(更新于3年前)

图片.png

www.npmjs.com/package/pdf…