PDF是便携式文档格式的缩写。PDF是由Adobe在90年代为Windows设计的。它们是独立的文件,支持几乎所有主要的操作系统。
但有时你需要修改PDF以满足你的需要,而不仅仅是查看它。不幸的是,用于PDF的现有软件往往不能满足你的专门要求。
但你是一个程序员,对吗?为什么不做一些软件来帮助PDF按照你的要求来工作呢?嗯,这就是本文的灵感所在。
在这篇文章中,我们将探讨所有流行的PDF相关的JavaScript库。为什么是JavaScript?因为它有一些相当不错的PDF包可用,而且人们喜欢它。尤其是我自己。
我们将在本教程中做什么?
- 首先,我们将探索一些流行的PDF包,用于JavaScript中的PDF相关工作。然后,我们将对它们进行比较,找到适合我们要求的最佳软件包。
- 接下来,我们将加载一个现有的PDF,并从其中提取一些页面。提取的页面将组成一个新的PDF文档。
- 然后,我们将在浏览器中渲染这个新的PDF(我们在第二步中制作的)。
- 最后,我们将下载新的PDF供以后使用。
所以这些就是我们在这里要经历的所有步骤。我希望你能兴奋地看到结果。让我们深入了解一下。
用于JavaScript的PDF库
我发现JavaScript中的PDF库主要有两种类型。一种是用于PDF渲染,另一种是用于PDF操作(或修改)。我在搜索了一个多小时后发现了一堆PDF库,这些是我最好的选择。
这里列出的所有软件包都是免费的、开源的软件包。你可以在npm注册表中找到所有这些包。
pdfjs
这个包是由火狐网络浏览器背后的公司Mozilla制作的。pdfjs是一个基于网络标准的平台,用于解析和渲染PDF。
当你在Firefox中查看PDF时,PDF查看器就是用这个pdfjs包制作的。
这个包的核心优势是在网页上进行PDF渲染。其他的PDF修改功能在这个包里是非常有限的。如果你想为你的网站制作一个自定义的PDF浏览器,这可能是你正在寻找的软件包。
pdfjs有一个非常简单的API。他们有很多教程可以让你开始使用这个库。如果你还不相信,那就用这个库玩上一段时间,你一定会爱上它的。
pdf-lib
与之前的pdfjs包不同,pdf-lib主要用于PDF的创建和操作。你可以根据你的需要用这个包动态地生成一个新的PDF文档。
这个包对修改现有文档有强大的支持。你可以用这个库做很多PDF的修改。例如,你可以做PDF的分割和合并,你可以提取一个页面,注释一个PDF文档,添加一个大纲,以及更多你能想象的事情。
它只有JavaScript作为一个依赖项。因此,它可以运行在任何具有JavaScript运行时间的设备上。浏览器、Nodejs、Deno和React Native都得到良好的支持。如果你能设法在设备上安装JavaScript,那么这个库就肯定能工作。
pdf-lib的主要缺点是,它没有强大的渲染支持。如果你想用这个库做一个漂亮的PDF浏览用户界面,那么pdf-lib不是你的正确选择。在这种情况下,你应该使用pdfjs来代替。
pdfjs#2
如果你认为我在重复自己的话,那么我没有。这是一个用于创建PDF文档的JavaScript库。它有一个非常简单的API可以使用。
我们讨论过的前一个pdfjs库在用户界面上有非常强大的渲染支持,但它缺乏PDF创建和修改功能。
但这个库是以创建PDF为目的而建立的。它有一个非常简单的API,对初学者很友好。你可以将它与pdf-lib包进行比较。
这个pdfjs库的主要缺点是,对现有文档的修改支持仍处于测试阶段。它并不是一直在工作,而且仍在进行中。
如果你的主要关注点是PDF修改(例如,页面提取、合并、拆分、注释等),那么这个库可能不适合你。
如果贡献者能够使修改功能发挥作用,那么这可能是最好的JavaScript的PDF包。
js-pdf
与上面列出的所有PDF包不同,这个库是一个完整的野兽。你可以用这个库做任何与PDF有关的工作。这就像一个万能的库。如果你想要一些复杂的PDF相关的东西,那么这个库可以做到。
但在JavaScript中还有更好的包,对个别任务非常好。例如,pdfjs是一个比js-pdf更好的PDF渲染器,而pdf-lib比js-pdf有更好的修改支持。
这里我说的不是实际性能或其他类型的指标,我说的是开发者的体验。我发现它的API不是很直观。对于一个初学者来说,第一眼就会感到不知所措。不过,这是我的看法,也是我使用它时的体验。
PDF生成是这个库的主要优势。你可以用你的任何设计来生成任何类型的PDF。这个软件包将为你完成所有繁重的工作。如果你有经验,那么这可能是你的最佳选择。
react-pdf
顾名思义,这个库是专门用于React生态系统的。它的用法很像React。你可以用它类似JSX的语法轻松创建一个文档。
你可以用简单的React组件创建和显示一个PDF文档。但功能非常有限。这个库主要用于生成PDF。
如果你的目标是向用户显示一个PDF,那么你可以使用这个包。作为一个React爱好者,你会喜欢这个库。看看他们的游乐场,花些时间来使用这个包。这样你就会知道你是否需要这个库。
为什么我们要在本教程中使用pdf-lib?
在上面提到的所有这些PDF库中,我将在本文中使用pdf-lib。因为我们要分割和合并PDF页面,并在浏览器中渲染它们,pdf-lib似乎是这种情况下的最佳选择。
而且,pdf-lib有相当简单的API可以使用,所有这些API都有很好的文档。如果你使用TypeScript,你还可以获得类型推理,这非常有帮助。
最后但同样重要的是,他们的例子非常好。你可以在几分钟内启动并运行。所以我喜欢这个库,适合我的使用情况。
如何用JavaScript读取本地PDF文件
在对我们的PDF文件做任何操作之前,我们必须从用户那里获得该文件。在浏览器中读取任何文件都可以通过FileReader web API来处理。
首先,我们要做和文件输入按钮,然后用FileReader web API处理上传的文件。
<input type="file" id="file-selector" accept=".pdf" onChange={onFileSelected} />
由于Filereader API使用回调工作,我发现async/await要干净得多,也更容易操作。所以让我们做一个辅助函数,把Filereader的回调修改成async/await。
function readFileAsync(file) {
return new Promise((resolve, reject) => {
let reader = new FileReader();
reader.onload = () => {
resolve(reader.result);
};
reader.onerror = reject;
reader.readAsArrayBuffer(file);
});
}
现在,当用户使用之前的文件输入上传文件时,我们监听文件输入事件,然后使用这个readFileAsync 函数读取文件。
这个逻辑的实现在代码中看起来像这样:
const onFileSelected = async (e) => {
const fileList = e.target.files;
if (fileList?.length > 0) {
const pdfArrayBuffer = await readFileAsync(fileList[0]);
}
};
如何提取PDF页面
到此为止,我们的PDF被上传并转换为JavaScriptArrayBuffer 。由于我们要从PDF中提取一系列的页面,我们希望有一个包含PDF的这些页码的数组。
在JavaScript中生成一个自然数的数组并不难。所以我们做了一个名为range() 的函数来生成我们想要的所有索引。
我们必须提供开始页数和结束页数,然后这个range() 函数可以生成一个带有适当页数的数组:
function range(start, end) {
let length = end - start + 1;
return Array.from({ length }, (_, i) => start + i - 1);
}
这里我们在最后加上-1。你知道其中的原因吗?是的--在编程中,索引是从0开始的,而不是1。所以我们必须从每一个页码中扣除-1,以获得我们想要的行为。
现在让我们开始本文的主要部分:提取。在做任何工作之前,先导入pdf-lib库:
import { PDFDocument } from "pdf-lib";
首先,我们加载我们从之前的onFileSelected 函数中得到的PDFArrayBuffer 。然后我们将ArrayBuffer 加载到PDFDocument.load(arraybuffer) 函数中。这就是我们的用户提供的PDF。为了方便起见,我们把它叫做pdfSrcDoc 。
现在我们将创建一个新的PDF。所有从用户提供的文件中提取的PDF页面都被合并到新文件中。我们使用PDFDocument.create() 函数来做这件事。为了便于使用,我们称它为pdfNewDoc 。
之后,我们通过使用copyPages() 函数将我们想要的页面从pdfSrcDoc 复制到pdfNewDoc 。然后,我们将复制的页面添加到pdfNewDoc 。
为了保存这些变化,运行pdfNewDoc.save() 。让我们创建一个名为extractPdfPage() 的函数来重复使用这个逻辑。该函数内部的代码将看起来像这样:
async function extractPdfPage(arrayBuff) {
const pdfSrcDoc = await PDFDocument.load(arrayBuff);
const pdfNewDoc = await PDFDocument.create();
const pages = await pdfNewDoc.copyPages(pdfSrcDoc,range(2,3));
pages.forEach(page=>pdfNewDoc.addPage(page));
const newpdf= await pdfNewDoc.save();
return newpdf;
}
我们将从extractPdfPage() 函数中返回一个Uint8Array 。
如何在浏览器中渲染PDF
到现在为止,我们有一个修改过的PDF的Uint8Array 。为了在浏览器中呈现它,我们必须将其转换为Blob。
然后我们把它做成一个URL,在一个iframe中呈现。
你也可以使用我上面提到的pdfjs库制作你的自定义PDF浏览器。但如果你不需要这样的品牌和定制,浏览器的默认PDF浏览器就可以达到这个目的:
function renderPdf(uint8array) {
const tempblob = new Blob([uint8array], {
type: "application/pdf",
});
const docUrl = URL.createObjectURL(tempblob);
setPdfFileData(docUrl);
}
iframe现在你可以很容易地在一个renderPdf() 函数中渲染这个docUrl返回。
完整的代码示例
我在本教程中使用Next.js。如果你使用的是其他框架或vanilla JavaScript,结果也会类似。下面是这个项目的所有代码:
import { useState } from "react";
import { PDFDocument } from "pdf-lib";
export default function Home() {
const [pdfFileData, setPdfFileData] = useState();
function readFileAsync(file) {
return new Promise((resolve, reject) => {
let reader = new FileReader();
reader.onload = () => {
resolve(reader.result);
};
reader.onerror = reject;
reader.readAsArrayBuffer(file);
});
}
function renderPdf(uint8array) {
const tempblob = new Blob([uint8array], {
type: "application/pdf",
});
const docUrl = URL.createObjectURL(tempblob);
setPdfFileData(docUrl);
}
function range(start, end) {
let length = end - start + 1;
return Array.from({ length }, (_, i) => start + i - 1);
}
async function extractPdfPage(arrayBuff) {
const pdfSrcDoc = await PDFDocument.load(arrayBuff);
const pdfNewDoc = await PDFDocument.create();
const pages = await pdfNewDoc.copyPages(pdfSrcDoc, range(2, 3));
pages.forEach((page) => pdfNewDoc.addPage(page));
const newpdf = await pdfNewDoc.save();
return newpdf;
}
// Execute when user select a file
const onFileSelected = async (e) => {
const fileList = e.target.files;
if (fileList?.length > 0) {
const pdfArrayBuffer = await readFileAsync(fileList[0]);
const newPdfDoc = await extractPdfPage(pdfArrayBuffer);
renderPdf(newPdfDoc);
}
};
return (
<>
<h1>Hello world</h1>
<input
type="file"
id="file-selector"
accept=".pdf"
onChange={onFileSelected}
/>
<iframe
style={{ display: "block", width: "100vw", height: "90vh" }}
title="PdfFrame"
src={pdfFileData}
frameborder="0"
type="application/pdf"
></iframe>
</>
);
}
现在你可以使用PDF浏览器上的下载按钮保存生成的PDF。
今后的发展方向
在这篇文章中,我只是触及了冰山一角。如果你想处理PDF,并想从中获得一些东西,那么pdf-lib是一个非常强大的库,可以达到这个目的。
你可以将两个PDF合并成一个,你可以旋转页面,或者从一个PDF中删除一些页面。这些只是一些例子,可能性是无穷的。
如果你想将你的Next.js应用程序部署到Cloudflare页面,这是你应该查看的文章。
做点什么出来。做一些创造性的东西,并在Twitter上向我展示。
结论
如果你读到现在,我非常感激。感觉我做的内容,世界上另一个地方的人都会读到。请与你的编码朋友分享。
你想在你的PDF文档中添加一个大纲吗?我知道这是一个非常难实现的任务。我已经经历了很多痛苦,用JavaScript在PDF文档中添加这个功能。你有兴趣吗? 那是未来的一个故事。
祝你有个愉快的一天。