ARTICLE · 1226859
前端 PDF 神器!停更 5 年,周下载仍是 4500 万!
大家好,我是 01。
前端和 PDF 的孽缘,比想象中深。
发票、合同、报告、证书,业务方总有各种"导出成 PDF"的需求。
而说到用 JavaScript 生成和修改 PDF,绕不开一个名字:pdf-lib。
它的 npm 周下载量是 4500 万,被 1800 多个包依赖。
但打开它的版本记录,会看到一个尴尬的事实:
最新版 1.17.1,发布于 5 年前。
停更 5 年,下载量不降反升。
今天就来聊聊:它凭什么,以及停更这 5 年发生了什么。
它是什么?
pdf-lib 的定位一句话讲完:
用 JavaScript 创建和修改 PDF 文档,零依赖。
支持的环境列出来很吓人:Node、浏览器、Deno、React Native 全覆盖。
一份代码,前端预览生成、服务端批量出票、边缘函数动态合成,都能跑。
TypeScript 类型内置,MIT 协议。
快速上手
import { PDFDocument, StandardFonts, rgb } from "pdf-lib";async function createPdf() { const pdfDoc = await PDFDocument.create(); const timesRomanFont = await pdfDoc.embedFont(StandardFonts.TimesRoman); const page = pdfDoc.addPage(); const { width, height } = page.getSize(); const fontSize = 30; page.drawText("Creating PDFs in JavaScript is awesome!", { x: 50, y: height - 4 * fontSize, size: fontSize, font: timesRomanFont, color: rgb(0, 0.53, 0.71), }); const pdfBytes = await pdfDoc.save();}除了 drawText,还有 drawRectangle、drawEllipse、drawLine、drawSvgPath,基本图形全覆盖。
embedFont 顺带解决了排版最头疼的问题:widthOfTextAtSize 可以精确测量文字宽度,居中、换行、省略号都能算。

常用高频场景
修改 PDF
import { degrees, PDFDocument, rgb, StandardFonts } from "pdf-lib";async function modifyPdf() { const url = "https://pdf-lib.js.org/assets/with_update_sections.pdf"; const existingPdfBytes = await fetch(url).then((res) => res.arrayBuffer()); const pdfDoc = await PDFDocument.load(existingPdfBytes); const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica); const pages = pdfDoc.getPages(); const firstPage = pages[0]; const { width, height } = firstPage.getSize(); firstPage.drawText("This text was added with JavaScript!", { x: 5, y: height / 2 + 300, size: 50, font: helveticaFont, color: rgb(0.95, 0.1, 0.1), rotate: degrees(-45), }); const pdfBytes = await pdfDoc.save();}修改 PDF 文档,添加文字、图片、表格等。

嵌入图片
import { PDFDocument } from "pdf-lib";async function embedImages() { const jpgUrl = "https://pdf-lib.js.org/assets/cat_riding_unicorn.jpg"; const pngUrl = "https://pdf-lib.js.org/assets/minions_banana_alpha.png"; const jpgImageBytes = await fetch(jpgUrl).then((res) => res.arrayBuffer()); const pngImageBytes = await fetch(pngUrl).then((res) => res.arrayBuffer()); const pdfDoc = await PDFDocument.create(); const jpgImage = await pdfDoc.embedJpg(jpgImageBytes); const pngImage = await pdfDoc.embedPng(pngImageBytes); const jpgDims = jpgImage.scale(0.5); const pngDims = pngImage.scale(0.5); const page = pdfDoc.addPage(); page.drawImage(jpgImage, { x: page.getWidth() / 2 - jpgDims.width / 2, y: page.getHeight() / 2 - jpgDims.height / 2 + 250, width: jpgDims.width, height: jpgDims.height, }); page.drawImage(pngImage, { x: page.getWidth() / 2 - pngDims.width / 2 + 75, y: page.getHeight() / 2 - pngDims.height + 250, width: pngDims.width, height: pngDims.height, }); const pdfBytes = await pdfDoc.save();}嵌入 PNG 和 JPEG 图像到 PDF 文档。

复制页面
import { PDFDocument } from "pdf-lib";async function copyPages() { const url1 = "https://pdf-lib.js.org/assets/with_update_sections.pdf"; const url2 = "https://pdf-lib.js.org/assets/with_large_page_count.pdf"; const firstDonorPdfBytes = await fetch(url1).then((res) => res.arrayBuffer()); const secondDonorPdfBytes = await fetch(url2).then((res) => res.arrayBuffer(), ); const firstDonorPdfDoc = await PDFDocument.load(firstDonorPdfBytes); const secondDonorPdfDoc = await PDFDocument.load(secondDonorPdfBytes); const pdfDoc = await PDFDocument.create(); const [firstDonorPage] = await pdfDoc.copyPages(firstDonorPdfDoc, [0]); const [secondDonorPage] = await pdfDoc.copyPages(secondDonorPdfDoc, [742]); pdfDoc.addPage(firstDonorPage); pdfDoc.insertPage(0, secondDonorPage); const pdfBytes = await pdfDoc.save();}合并多个 PDF 文档,每个文档的第一页作为新文档的第一页,第 743 页作为新文档的第 2 页,以此类推。

中文用户必看
pdf-lib 内置的 StandardFonts(Helvetica 那一族)不包含中文字形。
写中文,必须嵌入自定义字体。
import fontkit from "@pdf-lib/fontkit";pdfDoc.registerFontkit(fontkit);const fontBytes = await fetch("/fonts/SourceHanSansSC.otf").then((r) => r.arrayBuffer(),);const font = await pdfDoc.embedFont(fontBytes, { subset: true });page.drawText("你好,PDF", { font, size: 24 });subset: true 会只嵌入用到的字形,体积从十几 MB 压到几十 KB。
但注意:原版 pdf-lib 的子集化在部分中日韩字体上存在字符损坏的 bug(已知 issue)。
踩到这个坑,有两个出路,往下看。
停更 5 年,谁在接棒
先回答大家最关心的问题:为什么停更?
作者 Hopding 在维护者文档里写得很坦诚:他不拿一分钱维护这个项目,每周只有约 5 小时,还有全职工作。
4500 万周下载的库,一个人用爱发电。
不是跑路,是精力见顶了。
于是社区出现了两个活跃的 fork。
@cantoo/pdf-lib:功能增强派
法国公司 Cantoo 为自家产品维护,保持原版 API 和文档不变,2 天前刚发了新版本。
增强清单相当扎实:
• 完整 SVG 绘制: drawSvg直接嵌整个 SVG,不只是 path• 加密: PDFDocument.encrypt(),AES-256• PDF/A 转换: convertToPDFA(),归档场景刚需• Factur-X / ZUGFeRD:欧洲电子发票标准直接嵌 • 增量更新:电子签名工作流的关键能力 • PDF 图层、附件增删、截断文件恢复……
选它的情况:需要 SVG、加密、PDF/A 这些原版没有的能力。
@pdfme/pdf-lib:修坑实用派
pdfme(PDF 模板引擎)生态的 fork,同样是 9 天前还在发版。
它修掉了原版的 CJK 字体子集化 bug,还顺手加了圆角矩形。
用 pdf-lib 写中文遇到字符损坏,换成它最省事。
怎么选
• 只做基础生成/修改:原版 pdf-lib 照用,停更不等于不能用,PDF 格式本身也很稳定 • 要 SVG、加密、PDF/A、电子签: @cantoo/pdf-lib,API 无缝切换• 中文项目踩到字体 bug: @pdfme/pdf-lib• 要模板化设计 PDF:直接看 pdfme 本体
另外划清边界,pdf-lib 不做这些事:
• 渲染/预览 PDF:那是 pdf.js、react-pdf 的活 • 提取 PDF 文本:看 pdf-parse、unpdf • HTML 转 PDF:无头浏览器方案更合适
写在最后
开源世界的一个经典悖论:最有用的库,往往维护得最少。
pdf-lib 用 5 年没更新的 1.17.1,撑起了每周 4500 万次下载。
而它的接棒者们,正让这个故事继续下去。
下次和 PDF 搏斗之前,先看看这条工具链。
官网:https://pdf-lib.js.org/
原版 GitHub:https://github.com/Hopding/pdf-lib















































































