驾驶员: 罐头
@cantoo/pdf-lib 是一个积极维护的分叉 Hopding/pdf-lib。我们保留原有的API和Docs,并将其特征运至上游没有提供(或不再提供)的下方。 安装方式 :
npm install @cantoo/pdf-lib
# or
yarn add @cantoo/pdf-lib
我们维持这个项目是为了满足我们自己的产品需要,不能保证支持路线图以外的每个问题,但欢迎提供适合图书馆的捐款.
福利对原有 pdf-lib
绘图内容( C)
- 全 SVG 绘图 (单位:千美元)
page.drawSvg编号 :embedSvg)——不仅仅是drawSvgPath:形状、文本、变换、视图框裁剪、混合模式、圆矩形、CSS颜色通过culori PDFPage.extractContents()——从页面内容流中拖出打出的资产(文本,图像,大致矢量图形为SVG)characterSpacing打开drawText
文档保存( S)
- 递增更新 (单位:千美元)
forIncrementalUpdate, (中文(简体) ),save({ useObjectStreams… }), (中文(简体) ),commit()) — 附加更改而无需重写整个文件( 签名/ 多增量工作流程) - PDF 头保留 保存( 写作者不再强制)
%PDF-1.7(中文(简体) ) - 回收已截断的 PDF 而不是在损坏的拖车物体上失败
- 更强的解析( hex-string whitespace, 旧 PDF 上的对象流, HTML 关闭标记跨行, 大小写不敏感 PDF 名称,...)
窗体脚本( C)
- XFA 帮助者 —— 说
preserveXFA,读取/修改 XFA JavaScript,签名-场发现(静态/政府式 XFA) - 提取/ 与 文档级别 JavaScript
PDFForm.flatten()并平整未列出的已忽略的部件注释AcroForm.Fields- 附录 添加、 提取( 包括未保存) 并拆分嵌入文件; 名称树按 PDF 词典顺序保存
标准和电子发票
convertToPDFA()— PDF/A-1/2/3 结构部件(输出输入+sRGB ICC,/ID, XMP 与 Info 同步)embedFacturX()- Factur-X/ZUGFERD PDF/A-3混合发票
安全加密( C)
PDFDocument.encrypt()- AES-256 默认修订 6;AES-128 / RC4通过algorithm(RC4需求)allowWeakCryptography(中文(简体) )- 解密密码过滤器为 间接对象
- 没有
crypto-js: 在树上 AES/RC4/MD5/SHA-256 + Web CryptogetRandomValues
字体和图层
- 头等舱
@cantoo/fontkit支持( 类型、 默认导出、 UMD); 仍然与上游兼容fontkit编号 :@pdf-lib/fontkit postscriptName来取一张脸.ttc编号 :.dfont收藏- 可选内容 (PDF 层) —— 说
getOptionalContentGroups()编号 :setOptionalContentGroupVisibility()
包装破解
- 供应商
@cantoo/upng财务报告和审定财务报表@cantoo/standard-fonts(亦为@cantoo/pdf-lib/upng财务报告和审定财务报表@cantoo/pdf-lib/standard-fonts(中文(简体) ) - 通过压缩
fflate改为pako(模式 TS UPNG 有相片平面)quantize编号 :framize/损失cnum(中文(简体) ) - 浅色解析
culori· ;html-entities作为对等点( 由 HTML/ SVG 解析器驱动)
见 变动情况 逐个版本的细节.
目录
- 福利对原有
pdf-lib - 特征
- 动机
- 用法实例
- 代诺用法
- 完整示例
- 安装
- 文档
- 字体和 Unicode 代码
- 创建和填充表格
- 限制
- 帮助和讨论
- 加密处理
- 移动到 v1. 0.0
- 捐款
- 维护
- 教学和酷东西
- 上一个艺术
- Git 历史重写
- 更改日志
- 许可证
特征
- 创建新的 PDF
- 修改已存在的 PDF
- 递增文档更新( 仅附录保存 / 多承诺)
- 加密/解密 PDF (默认情况下的 AES-256)
- 创建窗体
- 填写表格
- 平面表单
- 保存 XFA 表格
- 提取 XFA JavaScript
- 修改 XFA JavaScript
- 提取文档级别 JavaScript
- 添加页面
- 插入页面
- 删除页面
- 复制 PDF 之间的页面
- 绘制文字( 包括字符间隔)
- 绘制图像
- 绘制 PDF 页面
- 绘制矢量图形
- 绘制 SVG 路径
- 绘制完整的 SVG 文档 (
drawSvg编号 :embedSvg(中文(简体) ) - 提取页面内容( 文本、 图像、 向量图形)
- 可选内容/ PDF 层可见度
- 测量文本的宽度和高度
- 嵌入字体( 支持 UTF-8 和 UTF-16 字符集; TTC/ DFont 面部选择)
- 设定文档元数据
- 读取文档元数据
- 设置查看器首选项
- 阅读查看器偏好
- 添加附件
- 提取/ 拆分附件
- 创建 PDF/A 文档( 第 1-3 部分)
- 嵌入 Factur-X / ZUGFERD 电子发票 (PDF/A-3)
动机
pdf-lib 用于解决 JavaScript 生态系统缺乏对 PDF 操作的有力支持(特别是 PDF) 修改) (中文(简体) ).
两位 pdf-lib其显著特征是:
- 支持修改(编辑)现有文件.
- 在所有 JavaScript 环境中工作 - 不仅仅是在节点或浏览器中工作 .
还有 其他项目 良好的开源 JavaScript PDF 库可用. 但是,他们大多只能 创建 文档,它们不能 修改 现有。 他们中许多人只在特定环境中工作.
用法实例
创建文档
这个例子产生 这个 PDF。 。 。 .
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib'
// Create a new PDFDocument
const pdfDoc = await PDFDocument.create()
// Embed the Times Roman font
const timesRomanFont = await pdfDoc.embedFont(StandardFonts.TimesRoman)
// Add a blank page to the document
const page = pdfDoc.addPage()
// Get the width and height of the page
const { width, height } = page.getSize()
// Draw a string of text toward the top of the page
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),
})
// Serialize the PDFDocument to bytes (a Uint8Array)
const pdfBytes = await pdfDoc.save()
// For example, `pdfBytes` can be:
// • Written to a file in Node
// • Downloaded from the browser
// • Rendered in an <iframe>
修改文档
这个例子产生 这个 PDF (当 这个 PDF 用于 existingPdfBytes 变量).
import { degrees, PDFDocument, rgb, StandardFonts } from 'pdf-lib';
// This should be a Uint8Array or ArrayBuffer
// This data can be obtained in a number of different ways
// If your running in a Node environment, you could use fs.readFile()
// In the browser, you could make a fetch() call and use res.arrayBuffer()
const existingPdfBytes = ...
// Load a PDFDocument from the existing PDF bytes
const pdfDoc = await PDFDocument.load(existingPdfBytes)
// Embed the Helvetica font
const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica)
// Get the first page of the document
const pages = pdfDoc.getPages()
const firstPage = pages[0]
// Get the width and height of the first page
const { width, height } = firstPage.getSize()
// Draw a string of text diagonally across the first page
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),
})
// Serialize the PDFDocument to bytes (a Uint8Array)
const pdfBytes = await pdfDoc.save()
// For example, `pdfBytes` can be:
// • Written to a file in Node
// • Downloaded from the browser
// • Rendered in an <iframe>
递增文件修改
您可以加载一个 PDF 以进行递增更新,生成原始文档加增量, 保存时。 您也可以手动处理递增更新 . 增量修改保存被设计用于pdf签名. 签名被添加到已存在的页面中,然后只生成"递增"的PDF并被压缩到初始版本.
这个例子产生 这个 PDF (当 这个 PDF 用于 existingPdfBytes 变量).
import { PDFDocument, StandardFonts } from 'pdf-lib';
// This should be a Uint8Array or ArrayBuffer
// This data can be obtained in a number of different ways
// If your running in a Node environment, you could use fs.readFile()
// In the browser, you could make a fetch() call and use res.arrayBuffer()
const existingPdfBytes = ...
// Load a PDFDocument from the existing PDF bytes
const pdfDoc = await PDFDocument.load(existingPdfBytes)
// Take a snapshot of the document
const snapshot = pdfDoc.takeSnapshot();
// Get the first page of the document
const pages = pdfDoc.getPages()
const firstPage = pages[0]
// Mark the page as modified
snapshot.markRefForSave(firstPage.ref)
// Draw a string of text diagonally across the first page
firstPage.drawText('Incremental saving is also awesome!', {
x: 50,
y: 4 * fontSize,
size: fontSize
})
// Serialize the PDFDocument to bytes (a Uint8Array)
const pdfIncrementalBytes = await pdfDoc.saveIncremental(snapshot)
const pdfBytes = Buffer.concatenate([ existingPdfBytes, pdfIncrementalBytes ])
// For example, `pdfBytes` can be:
// • Written to a file in Node
// • Downloaded from the browser
// • Rendered in an <iframe>
装入一个用于递增更新的 PDF , 使事情更容易 :
import { PDFDocument, StandardFonts } from 'pdf-lib';
// This should be a Uint8Array or ArrayBuffer
const existingPdfBytes = ...
// Load a PDFDocument from the existing PDF bytes, for incremental update
const pdfDoc = await PDFDocument.load(existingPdfBytes,{forIncrementalUpdate:true})
// Get the first page of the document
const pages = pdfDoc.getPages()
const firstPage = pages[0]
// Draw a string of text diagonally across the first page
firstPage.drawText('Incremental saving is also awesome!', {
x: 50,
y: 4 * fontSize,
size: fontSize
})
// Serialize the PDFDocument to bytes (a Uint8Array), using incremental updates
const pdfBytes = await pdfDoc.save()
// For example, `pdfBytes` can be:
// • Written to a file in Node
// • Downloaded from the browser
// • Rendered in an <iframe>
您可以强制重写一个 PDF , 该 PDF 在保存时使用正确的参数进行递增更新 :
import { PDFDocument } from 'pdf-lib';
// This should be a Uint8Array or ArrayBuffer
const existingPdfBytes = ...
// Load a PDFDocument from the existing PDF bytes, for incremental update
const pdfDoc = await PDFDocument.load(existingPdfBytes,{forIncrementalUpdate:true})
// Do something..
// Serialize the PDFDocument to bytes (a Uint8Array), NOT using incremental updates
const pdfBytes = await pdfDoc.save({rewrite: true})
使用pdf-lib生成电子签名的占位符
@ signpdf包含一个pdf-lib-placeholders组件,但它基于pdf-lib软件包,没有递增更新功能. 此代码取自该库并被修改为使用递增的更新来不使先前的文件签名无效. 举例来说,在集成测试#20中可以看到,一些"signpdf"常数被修改为任意值,用于示例工作"出框".
import { PDFDocument, StandardFonts, rgb, PDFArray, PDFNumber, PDFName, PDFHexString, PDFString, PDFInvalidObject } from 'pdf-lib';
const pdfBytes = ...
const pdfDoc = await PDFDocument.load(pdfBytes, {forIncrementalUpdate: true});
// visual representation of the signature
const page = pdfDoc.addPage([500, 200]);
const font = pdfDoc.embedStandardFont(StandardFonts.Helvetica);
page.drawRectangle({
x: 10,
y: 30,
width: 280,
height: 150,
borderWidth: 2,
borderColor: rgb(0.45, 0.45, 0.45),
});
page.drawText(`Electronic Signature Example\nSigned on ${(new Date()).toIsoString()}\nThis is not the real signature!!`, {
x: 20,
y: 200,
size: 14,
font,
});
// Add an AcroForm or update the existing one
let acroForm = pdfDoc.catalog.getOrCreateAcroForm();
// Create a placeholder where the the last 3 parameters of the
// actual range will be replaced when signing is done.
const byteRange = PDFArray.withContext(pdfDoc.context);
byteRange.push(PDFNumber.of(0));
byteRange.push(PDFName.of('*********'));
byteRange.push(PDFName.of('*********'));
byteRange.push(PDFName.of('*********'));
// Fill the contents of the placeholder with 00s.
const placeholder = PDFHexString.of(String.fromCharCode(0).repeat(8096));
// Create a signature dictionary to be referenced in the signature widget.
const appBuild = { App: { Name: 'Signature Example pdf-lib' } };
const signatureDict = pdfDoc.context.obj({
Type: 'Sig',
Filter: 'Adobe.PPKLite',
SubFilter: 'adbe.pkcs7.detached',
ByteRange: byteRange,
Contents: placeholder,
Reason: PDFString.of('Example Signature'),
M: PDFString.fromDate(new Date()),
ContactInfo: PDFString.of('[email protected]'),
Name: PDFString.of('Example Signer'),
Location: PDFString.of('In a far away galaxy..'),
Prop_Build: {
Filter: { Name: 'Adobe.PPKLite' },
...appBuild,
},
});
// Register signatureDict as a PDFInvalidObject to prevent PDFLib from serializing it
// in an object stream.
const signatureBuffer = new Uint8Array(signatureDict.sizeInBytes());
signatureDict.copyBytesInto(signatureBuffer, 0);
const signatureObj = PDFInvalidObject.of(signatureBuffer);
const signatureDictRef = pdfDoc.context.register(signatureObj);
// Create the signature widget
const widgetRect = [0, 0, 0, 0];
const rect = PDFArray.withContext(pdfDoc.context);
widgetRect.forEach((c) => rect.push(PDFNumber.of(c)));
const apStream = pdfDoc.context.formXObject([], {
BBox: widgetRect,
Resources: {}, // Necessary to avoid Acrobat bug (see https://stackoverflow.com/a/73011571)
});
const widgetDict = pdfDoc.context.obj({
Type: 'Annot',
Subtype: 'Widget',
FT: 'Sig',
Rect: rect,
V: signatureDictRef,
T: PDFString.of('TestSig'),
TU: PDFString.of('Electronic Signature Example'),
F: 2,
P: page.ref,
AP: { N: pdfDoc.context.register(apStream) }, // Required for PDF/A compliance
});
const widgetDictRef = pdfDoc.context.register(widgetDict);
// Annotate the widget on the given page
let annotations = page.node.lookupMaybe(PDFName.of('Annots'), PDFArray);
if (typeof annotations === 'undefined') {
annotations = pdfDoc.context.obj([]);
}
annotations.push(widgetDictRef);
page.node.set(PDFName.of('Annots'), annotations);
let sigFlags: PDFNumber;
if (acroForm.dict.has(PDFName.of('SigFlags'))) {
// Already has some flags, will merge
sigFlags = acroForm.dict.get(PDFName.of('SigFlags')) as PDFNumber;
} else {
// Create blank flags
sigFlags = PDFNumber.of(0);
}
const updatedFlags = PDFNumber.of(sigFlags!.asNumber() | 1 | 2);
acroForm.dict.set(PDFName.of('SigFlags'), updatedFlags);
let fields = acroForm.dict.get(PDFName.of('Fields'));
if (!(fields instanceof PDFArray)) {
fields = pdfDoc.context.obj([]);
acroForm.dict.set(PDFName.of('Fields'), fields);
}
(fields as PDFArray).push(widgetDictRef);
// Serialize the PDFDocument to bytes (a Uint8Array), using incremental updates
const pdfBytes = await pdfDoc.save()
// `pdfBytes` should be handled to the signing library to calculate the
// file hash and fill in the generated placeholder for the signature
连续递增更新
您可以加载一个 PDF 以进行递增更新,然后在原始文档上用承诺()方法生成多个增量.
这种方法简化了序列:
import { PDFDocument, StandardFonts } from 'pdf-lib';
// This should be a Uint8Array or ArrayBuffer
const existingPdfBytes = ...
// Load a PDFDocument from the existing PDF bytes, for incremental update
const pdfDoc = await PDFDocument.load(existingPdfBytes,{forIncrementalUpdate:true})
// modify pdf
...
// Serialize the PDFDocument to bytes (a Uint8Array), using incremental updates
const firstUpdatedDoc = await pdfDoc.save()
const pdfDoc2 = await PDFDocument.load(firstUpdatedDoc,{forIncrementalUpdate:true})
// modify pdf
...
// Serialize the PDFDocument to bytes (a Uint8Array), using incremental updates
const secondUpdateDoc = await pdfDoc2.save()
// etc, etc
允许这样:
import { PDFDocument, StandardFonts } from 'pdf-lib';
// This should be a Uint8Array or ArrayBuffer
const existingPdfBytes = ...
// Load a PDFDocument from the existing PDF bytes, for incremental update
const pdfDoc = await PDFDocument.load(existingPdfBytes,{forIncrementalUpdate:true})
// modify pdf
...
// Serialize the PDFDocument to bytes (a Uint8Array), using incremental updates
const firstUpdatedDoc = await pdfDoc.commit();
// modify pdf
...
const secondUpdateDoc = await pdfDoc.commit();
// etc, etc
这个 承诺 方法的参数与 保存 方法。 如果未装入文档 递增更新,则提出例外。 打电话后 承诺 在原始文档中添加更新部分,以此取代原始文档,并取出新的快照.
创建窗体
这个例子产生 这个 PDF。 。 。 .
另见 创建和填充表格
import { PDFDocument } from 'pdf-lib'
// Create a new PDFDocument
const pdfDoc = await PDFDocument.create()
// Add a blank page to the document
const page = pdfDoc.addPage([550, 750])
// Get the form so we can add fields to it
const form = pdfDoc.getForm()
// Add the superhero text field and description
page.drawText('Enter your favorite superhero:', { x: 50, y: 700, size: 20 })
const superheroField = form.createTextField('favorite.superhero')
superheroField.setText('One Punch Man')
superheroField.addToPage(page, { x: 55, y: 640 })
// Add the rocket radio group, labels, and description
page.drawText('Select your favorite rocket:', { x: 50, y: 600, size: 20 })
page.drawText('Falcon Heavy', { x: 120, y: 560, size: 18 })
page.drawText('Saturn IV', { x: 120, y: 500, size: 18 })
page.drawText('Delta IV Heavy', { x: 340, y: 560, size: 18 })
page.drawText('Space Launch System', { x: 340, y: 500, size: 18 })
const rocketField = form.createRadioGroup('favorite.rocket')
rocketField.addOptionToPage('Falcon Heavy', page, { x: 55, y: 540 })
rocketField.addOptionToPage('Saturn IV', page, { x: 55, y: 480 })
rocketField.addOptionToPage('Delta IV Heavy', page, { x: 275, y: 540 })
rocketField.addOptionToPage('Space Launch System', page, { x: 275, y: 480 })
rocketField.select('Saturn IV')
// Add the gundam check boxes, labels, and description
page.drawText('Select your favorite gundams:', { x: 50, y: 440, size: 20 })
page.drawText('Exia', { x: 120, y: 400, size: 18 })
page.drawText('Kyrios', { x: 120, y: 340, size: 18 })
page.drawText('Virtue', { x: 340, y: 400, size: 18 })
page.drawText('Dynames', { x: 340, y: 340, size: 18 })
const exiaField = form.createCheckBox('gundam.exia')
const kyriosField = form.createCheckBox('gundam.kyrios')
const virtueField = form.createCheckBox('gundam.virtue')
const dynamesField = form.createCheckBox('gundam.dynames')
exiaField.addToPage(page, { x: 55, y: 380 })
kyriosField.addToPage(page, { x: 55, y: 320 })
virtueField.addToPage(page, { x: 275, y: 380 })
dynamesField.addToPage(page, { x: 275, y: 320 })
exiaField.check()
dynamesField.check()
// Add the planet dropdown and description
page.drawText('Select your favorite planet*:', { x: 50, y: 280, size: 20 })
const planetsField = form.createDropdown('favorite.planet')
planetsField.addOptions(['Venus', 'Earth', 'Mars', 'Pluto'])
planetsField.select('Pluto')
planetsField.addToPage(page, { x: 55, y: 220 })
// Add the person option list and description
page.drawText('Select your favorite person:', { x: 50, y: 180, size: 18 })
const personField = form.createOptionList('favorite.person')
personField.addOptions([
'Julius Caesar',
'Ada Lovelace',
'Cleopatra',
'Aaron Burr',
'Mark Antony',
])
personField.select('Ada Lovelace')
personField.addToPage(page, { x: 55, y: 70 })
// Just saying...
page.drawText(`* Pluto should be a planet too!`, { x: 15, y: 15, size: 15 })
// Serialize the PDFDocument to bytes (a Uint8Array)
const pdfBytes = await pdfDoc.save()
// For example, `pdfBytes` can be:
// • Written to a file in Node
// • Downloaded from the browser
// • Rendered in an <iframe>
填充表单
这个例子产生 这个 PDF (当 这个 PDF 用于 formPdfBytes 变量, 此图像 用于 marioImageBytes 变量,以及 此图像 用于 emblemImageBytes 变量).
另见 创建和填充表格
import { PDFDocument } from 'pdf-lib'
// These should be Uint8Arrays or ArrayBuffers
// This data can be obtained in a number of different ways
// If your running in a Node environment, you could use fs.readFile()
// In the browser, you could make a fetch() call and use res.arrayBuffer()
const formPdfBytes = ...
const marioImageBytes = ...
const emblemImageBytes = ...
// Load a PDF with form fields
const pdfDoc = await PDFDocument.load(formPdfBytes)
// Embed the Mario and emblem images
const marioImage = await pdfDoc.embedPng(marioImageBytes)
const emblemImage = await pdfDoc.embedPng(emblemImageBytes)
// Get the form containing all the fields
const form = pdfDoc.getForm()
// Get all fields in the PDF by their names
const nameField = form.getTextField('CharacterName 2')
const ageField = form.getTextField('Age')
const heightField = form.getTextField('Height')
const weightField = form.getTextField('Weight')
const eyesField = form.getTextField('Eyes')
const skinField = form.getTextField('Skin')
const hairField = form.getTextField('Hair')
const alliesField = form.getTextField('Allies')
const factionField = form.getTextField('FactionName')
const backstoryField = form.getTextField('Backstory')
const traitsField = form.getTextField('Feat+Traits')
const treasureField = form.getTextField('Treasure')
const characterImageField = form.getButton('CHARACTER IMAGE')
const factionImageField = form.getTextField('Faction Symbol Image')
// Fill in the basic info fields
nameField.setText('Mario')
ageField.setText('24 years')
heightField.setText(`5' 1"`)
weightField.setText('196 lbs')
eyesField.setText('blue')
skinField.setText('white')
hairField.setText('brown')
// Fill the character image field with our Mario image
characterImageField.setImage(marioImage)
// Fill in the allies field
alliesField.setText(
[
`Allies:`,
` • Princess Daisy`,
` • Princess Peach`,
` • Rosalina`,
` • Geno`,
` • Luigi`,
` • Donkey Kong`,
` • Yoshi`,
` • Diddy Kong`,
``,
`Organizations:`,
` • Italian Plumbers Association`,
].join('\n'),
)
// Fill in the faction name field
factionField.setText(`Mario's Emblem`)
// Fill the faction image field with our emblem image
factionImageField.setImage(emblemImage)
// Fill in the backstory field
backstoryField.setText(
`Mario is a fictional character in the Mario video game franchise, owned by Nintendo and created by Japanese video game designer Shigeru Miyamoto. Serving as the company's mascot and the eponymous protagonist of the series, Mario has appeared in over 200 video games since his creation. Depicted as a short, pudgy, Italian plumber who resides in the Mushroom Kingdom, his adventures generally center upon rescuing Princess Peach from the Koopa villain Bowser. His younger brother and sidekick is Luigi.`,
)
// Fill in the traits field
traitsField.setText(
[
`Mario can use three basic three power-ups:`,
` • the Super Mushroom, which causes Mario to grow larger`,
` • the Fire Flower, which allows Mario to throw fireballs`,
` • the Starman, which gives Mario temporary invincibility`,
].join('\n'),
)
// Fill in the treasure field
treasureField.setText(['• Gold coins', '• Treasure chests'].join('\n'))
// Serialize the PDFDocument to bytes (a Uint8Array)
const pdfBytes = await pdfDoc.save()
// For example, `pdfBytes` can be:
// • Written to a file in Node
// • Downloaded from the browser
// • Rendered in an <iframe>
平整窗体
这个例子产生 这个 PDF (当 这个 PDF 用于 formPdfBytes 变量).
import { PDFDocument } from 'pdf-lib'
// This should be a Uint8Array or ArrayBuffer
// This data can be obtained in a number of different ways
// If your running in a Node environment, you could use fs.readFile()
// In the browser, you could make a fetch() call and use res.arrayBuffer()
const formPdfBytes = ...
// Load a PDF with form fields
const pdfDoc = await PDFDocument.load(formPdfBytes)
// Get the form containing all the fields
const form = pdfDoc.getForm()
// Fill the form's fields
form.getTextField('Text1').setText('Some Text');
form.getRadioGroup('Group2').select('Choice1');
form.getRadioGroup('Group3').select('Choice3');
form.getRadioGroup('Group4').select('Choice1');
form.getCheckBox('Check Box3').check();
form.getCheckBox('Check Box4').uncheck();
form.getDropdown('Dropdown7').select('Infinity');
form.getOptionList('List Box6').select('Honda');
// Flatten the form's fields
form.flatten();
// Serialize the PDFDocument to bytes (a Uint8Array)
const pdfBytes = await pdfDoc.save()
// For example, `pdfBytes` can be:
// • Written to a file in Node
// • Downloaded from the browser
// • Rendered in an <iframe>
使用 XFA 表格工作
XFA (XML Forms Architecture) 表格是政府表格,税务文件和企业应用中常用的复杂,动态的PDF表格. 与标准AcroForms不同的是,XFA形式将其结构和JavaScript嵌入了XML格式.
重要内容: 要在加载和保存 PDF 时保存 XFA 表格,请使用 preserveXFA 选项 :
import { PDFDocument } from 'pdf-lib'
const xfaPdfBytes = ... // Load your XFA PDF
// Load with XFA preservation
const pdfDoc = await PDFDocument.load(xfaPdfBytes, {
preserveXFA: true
})
// Make modifications...
// Save the document
const pdfBytes = await pdfDoc.save()
说明: 这个 preserveXFA 选项必须设置为 true 当装入以保存 XFA 数据时。 XFA 保存期间如果保存在加载期间,则自动发生.
范围和限制(v1): XFA在pdf-lib的支持有意缩小,并针对共同的政府/税"静态"XFA布局:
- 只有数组形式
/XFA(更名/流对)支持;单流包装不读取或写出. - 只有
template检查包件——动态包件和其他包件(如.datasets, (中文(简体) ),config, (中文(简体) ),xdp包件)没有生成(再生成). 编辑 JavaScript 不重递或重新包装窗体. - XFA 签名检测读取
<signature>编号 :<manifest>从模板中输入;它不验证或创建加密签名. getForm()除非文档被装入,否则删除 XFA 数据preserveXFA: true,所以请联系 XFA 帮助者getForm()(或装入preserveXFA: true) (中文(简体) ).- Template XML被解码为UTF-8并带有latin1回落;异地编码可能不会干净地进行往返.
提取 XFA JavaScript
XFA表格中经常包含JavaScript用于验证,计算,以及数据导入/导出. 您可以从 XFA 表格中提取全部 JavaScript :
import { PDFDocument } from 'pdf-lib'
const xfaPdfBytes = ... // Load your XFA PDF
// Load the PDF with XFA preservation
const pdfDoc = await PDFDocument.load(xfaPdfBytes, {
preserveXFA: true
})
// Extract all XFA JavaScript
const scripts = pdfDoc.getXFAJavaScripts()
// Each script contains:
// - field: The field name (e.g., 'Button1', 'TextField2')
// - event: The event name (e.g., 'event__click', 'event__change')
// - script: The JavaScript code
console.log(`Found ${scripts.length} scripts`)
scripts.forEach((script) => {
console.log(`Field: ${script.field}`)
console.log(`Event: ${script.event}`)
console.log(`Code: ${script.script}`)
})
// Find specific scripts
const clickHandlers = scripts.filter(s =>
s.event.includes('click')
)
const validationScripts = scripts.filter(s =>
s.script.includes('validate') || s.script.includes('Validate')
)
修改 XFA JavaScript
您可以修改 XFA 表格中的 JavaScript , 以自定义行为, 添加日志, 或固定问题 :
import { PDFDocument } from 'pdf-lib'
const xfaPdfBytes = ... // Load your XFA PDF
// Load the PDF with XFA preservation
const pdfDoc = await PDFDocument.load(xfaPdfBytes, {
preserveXFA: true
})
// Extract scripts to find what you want to modify
const scripts = pdfDoc.getXFAJavaScripts()
const importButton = scripts.find(s => s.field === 'ImportButton')
if (importButton) {
// Modify the import button's click handler
const newScript = `
// Custom import handler
try {
console.println("Starting import...");
${importButton.script}
console.println("Import completed!");
} catch(e) {
xfa.host.messageBox("Error: " + e.message);
}
`
try {
pdfDoc.setXFAJavaScript(
'ImportButton', // field name
importButton.event, // event name (e.g., 'event__click')
newScript // new JavaScript code
)
console.log('Successfully modified XFA JavaScript')
} catch (error) {
console.error('Failed to modify script:', error.message)
}
}
// Save the document
const pdfBytes = await pdfDoc.save()
// The modified PDF will have the updated JavaScript
使用大小写 :
- 在现有脚本中添加错误处理
- 修改验证规则
- 添加调试日志
- 自定义进出口行为
- 解决兼容性问题
提取文档 JavaScript
PDF文档可以包含在文档打开时执行的文档级别JavaScript. 您可以提取这些脚本 :
import { PDFDocument } from 'pdf-lib'
const pdfBytes = ... // Load your PDF
const pdfDoc = await PDFDocument.load(pdfBytes)
// Extract all document-level JavaScript
const scripts = pdfDoc.getDocumentJavaScripts()
// Each script contains:
// - name: The script name
// - script: The JavaScript code
console.log(`Found ${scripts.length} document scripts`)
scripts.forEach((script) => {
console.log(`Script name: ${script.name}`)
console.log(`Code: ${script.script}`)
})
// Find specific scripts
const initScripts = scripts.filter(s =>
s.name.toLowerCase().includes('init')
)
说明: 文档级别JavaScript与XFA JavaScript不同. 文档级别的脚本被存储在文档的名称词典中,并在打开PDF时执行. XFA JavaScript被嵌入到XFA形式模板中.
复制页面
这个例子产生 这个 PDF (当 这个 PDF 用于 firstDonorPdfBytes 变量和 这个 PDF 用于 secondDonorPdfBytes 变量).
import { PDFDocument } from 'pdf-lib'
// Create a new PDFDocument
const pdfDoc = await PDFDocument.create()
// These should be Uint8Arrays or ArrayBuffers
// This data can be obtained in a number of different ways
// If your running in a Node environment, you could use fs.readFile()
// In the browser, you could make a fetch() call and use res.arrayBuffer()
const firstDonorPdfBytes = ...
const secondDonorPdfBytes = ...
// Load a PDFDocument from each of the existing PDFs
const firstDonorPdfDoc = await PDFDocument.load(firstDonorPdfBytes)
const secondDonorPdfDoc = await PDFDocument.load(secondDonorPdfBytes)
// Copy the 1st page from the first donor document, and
// the 743rd page from the second donor document
const [firstDonorPage] = await pdfDoc.copyPages(firstDonorPdfDoc, [0])
const [secondDonorPage] = await pdfDoc.copyPages(secondDonorPdfDoc, [742])
// Add the first copied page
pdfDoc.addPage(firstDonorPage)
// Insert the second copied page to index 0, so it will be the
// first page in `pdfDoc`
pdfDoc.insertPage(0, secondDonorPage)
// Serialize the PDFDocument to bytes (a Uint8Array)
const pdfBytes = await pdfDoc.save()
// For example, `pdfBytes` can be:
// • Written to a file in Node
// • Downloaded from the browser
// • Rendered in an <iframe>
嵌入 PNG 和 JPEG 图像
这个例子产生 这个 PDF (当 此图像 用于 jpgImageBytes 变量和 此图像 用于 pngImageBytes 变量).
import { PDFDocument } from 'pdf-lib'
// These should be Uint8Arrays or ArrayBuffers
// This data can be obtained in a number of different ways
// If your running in a Node environment, you could use fs.readFile()
// In the browser, you could make a fetch() call and use res.arrayBuffer()
const jpgImageBytes = ...
const pngImageBytes = ...
// Create a new PDFDocument
const pdfDoc = await PDFDocument.create()
// Embed the JPG image bytes and PNG image bytes
const jpgImage = await pdfDoc.embedJpg(jpgImageBytes)
const pngImage = await pdfDoc.embedPng(pngImageBytes)
// Get the width/height of the JPG image scaled down to 25% of its original size
const jpgDims = jpgImage.scale(0.25)
// Get the width/height of the PNG image scaled down to 50% of its original size
const pngDims = pngImage.scale(0.5)
// Add a blank page to the document
const page = pdfDoc.addPage()
// Draw the JPG image in the center of the page
page.drawImage(jpgImage, {
x: page.getWidth() / 2 - jpgDims.width / 2,
y: page.getHeight() / 2 - jpgDims.height / 2,
width: jpgDims.width,
height: jpgDims.height,
})
// Draw the PNG image near the lower right corner of the JPG image
page.drawImage(pngImage, {
x: page.getWidth() / 2 - pngDims.width / 2 + 75,
y: page.getHeight() / 2 - pngDims.height,
width: pngDims.width,
height: pngDims.height,
})
// Serialize the PDFDocument to bytes (a Uint8Array)
const pdfBytes = await pdfDoc.save()
// For example, `pdfBytes` can be:
// • Written to a file in Node
// • Downloaded from the browser
// • Rendered in an <iframe>
嵌入 PDF 页面
这个例子产生 这个 PDF (当 这个 PDF 用于 americanFlagPdfBytes 变量和 这个 PDF 用于 usConstitutionPdfBytes 变量).
import { PDFDocument } from 'pdf-lib'
// These should be Uint8Arrays or ArrayBuffers
// This data can be obtained in a number of different ways
// If your running in a Node environment, you could use fs.readFile()
// In the browser, you could make a fetch() call and use res.arrayBuffer()
const americanFlagPdfBytes = ...
const usConstitutionPdfBytes = ...
// Create a new PDFDocument
const pdfDoc = await PDFDocument.create()
// Embed the American flag PDF bytes
const [americanFlag] = await pdfDoc.embedPdf(americanFlagPdfBytes)
// Load the U.S. constitution PDF bytes
const usConstitutionPdf = await PDFDocument.load(usConstitutionPdfBytes)
// Embed the second page of the constitution and clip the preamble
const preamble = await pdfDoc.embedPage(usConstitutionPdf.getPages()[1], {
left: 55,
bottom: 485,
right: 300,
top: 575,
})
// Get the width/height of the American flag PDF scaled down to 30% of
// its original size
const americanFlagDims = americanFlag.scale(0.3)
// Get the width/height of the preamble clipping scaled up to 225% of
// its original size
const preambleDims = preamble.scale(2.25)
// Add a blank page to the document
const page = pdfDoc.addPage()
// Draw the American flag image in the center top of the page
page.drawPage(americanFlag, {
...americanFlagDims,
x: page.getWidth() / 2 - americanFlagDims.width / 2,
y: page.getHeight() - americanFlagDims.height - 150,
})
// Draw the preamble clipping in the center bottom of the page
page.drawPage(preamble, {
...preambleDims,
x: page.getWidth() / 2 - preambleDims.width / 2,
y: page.getHeight() / 2 - preambleDims.height / 2 - 50,
})
// Serialize the PDFDocument to bytes (a Uint8Array)
const pdfBytes = await pdfDoc.save()
// For example, `pdfBytes` can be:
// • Written to a file in Node
// • Downloaded from the browser
// • Rendered in an <iframe>
嵌入字体和测量文字
pdf-lib 依赖 @cantoo/fontkit 支持嵌入自定义字体。 您必须添加 @cantoo/fontkit 模块并使用 pdfDoc.registerFontkit(...) 在嵌入自定义字体之前。 上游 fontkit 和老年人 @pdf-lib/fontkit 软件包仍然工作,如果您已经依赖它们.
这个例子产生 这个 PDF (当 此字体 用于 fontBytes 变量).
import { PDFDocument, rgb } from 'pdf-lib'
import fontkit from '@cantoo/fontkit'
// This should be a Uint8Array or ArrayBuffer
// This data can be obtained in a number of different ways
// If you're running in a Node environment, you could use fs.readFile()
// In the browser, you could make a fetch() call and use res.arrayBuffer()
const fontBytes = ...
// Create a new PDFDocument
const pdfDoc = await PDFDocument.create()
// Register the `fontkit` instance
pdfDoc.registerFontkit(fontkit)
// Embed our custom font in the document
const customFont = await pdfDoc.embedFont(fontBytes)
// Add a blank page to the document
const page = pdfDoc.addPage()
// Create a string of text and measure its width and height in our custom font
const text = 'This is text in an embedded font!'
const textSize = 35
const textWidth = customFont.widthOfTextAtSize(text, textSize)
const textHeight = customFont.heightAtSize(textSize)
// Draw the string of text on the page
page.drawText(text, {
x: 40,
y: 450,
size: textSize,
font: customFont,
color: rgb(0, 0.53, 0.71),
})
// Draw a box around the string of text
page.drawRectangle({
x: 40,
y: 450,
width: textWidth,
height: textHeight,
borderColor: rgb(1, 0, 0),
borderWidth: 1.5,
})
// Serialize the PDFDocument to bytes (a Uint8Array)
const pdfBytes = await pdfDoc.save()
// For example, `pdfBytes` can be:
// • Written to a file in Node
// • Downloaded from the browser
// • Rendered in an <iframe>
添加附件
这个例子产生 这个 PDF (当 此图像 用于 jpgAttachmentBytes 变量和 这个 PDF 用于 pdfAttachmentBytes 变量).
import { PDFDocument } from 'pdf-lib'
// These should be Uint8Arrays or ArrayBuffers
// This data can be obtained in a number of different ways
// If your running in a Node environment, you could use fs.readFile()
// In the browser, you could make a fetch() call and use res.arrayBuffer()
const jpgAttachmentBytes = ...
const pdfAttachmentBytes = ...
// Create a new PDFDocument
const pdfDoc = await PDFDocument.create()
// Add the JPG attachment
await pdfDoc.attach(jpgAttachmentBytes, 'cat_riding_unicorn.jpg', {
mimeType: 'image/jpeg',
description: 'Cool cat riding a unicorn! 🦄🐈🕶️',
creationDate: new Date('2019/12/01'),
modificationDate: new Date('2020/04/19'),
})
// Add the PDF attachment
await pdfDoc.attach(pdfAttachmentBytes, 'us_constitution.pdf', {
mimeType: 'application/pdf',
description: 'Constitution of the United States 🇺🇸🦅',
creationDate: new Date('1787/09/17'),
modificationDate: new Date('1992/05/07'),
})
// Add a page with some text
const page = pdfDoc.addPage();
page.drawText('This PDF has two attachments', { x: 135, y: 415 })
// Serialize the PDFDocument to bytes (a Uint8Array)
const pdfBytes = await pdfDoc.save()
// For example, `pdfBytes` can be:
// • Written to a file in Node
// • Downloaded from the browser
// • Rendered in an <iframe>
摘录附件
如果您加载有 PDF 的 PDF cars.csv 作为附件,你可以使用
以下取出附件:
const pdfDoc = await PDFDocument.load(...)
const attachments = pdfDoc.getAttachments()
const csv = attachments.find(({ name }) => name === 'cars.csv')
fs.writeFileSync(csv.name, csv.data)
注:该方法还发现在最后一次呼叫之后添加的附件
save()。 。 。 .
创建 PDF/A 文档
通过调用将文档转换为 PDF/A convertToPDFA()中添加
需要PDF/A(文档) /ID,带有嵌入式输出意向
sRGB ICC 简介, 未压缩的 XMP 元数据 pdfaid,以及适当的
PDF头版. 没错 没有 重写任意内容使之成功
特别是,起草的案文必须使用 内嵌 字体( 14)
标准字体不符合PDF/A。 用如下工具验证结果:
维拉PDF。 。 。 .
import { PDFDocument } from '@cantoo/pdf-lib'
import fontkit from '@cantoo/fontkit'
const fontBytes = ... // e.g. fs.readFileSync('Roboto-Regular.ttf')
const pdfDoc = await PDFDocument.create()
pdfDoc.registerFontkit(fontkit)
const font = await pdfDoc.embedFont(fontBytes)
const page = pdfDoc.addPage()
page.drawText('Hello PDF/A', { x: 50, y: 700, size: 24, font })
pdfDoc.setTitle('Archival document')
pdfDoc.setAuthor('ACME GmbH')
// '1B' | '2B' | '2U' | '3B' | '3U' — defaults to '3B'
pdfDoc.convertToPDFA({ conformance: '3B' })
const pdfBytes = await pdfDoc.save()
转换后,pdf-lib将Info字典和XMP元数据同步于
save(),同时保留任何 完全为外国 XMP 电源 rdf:Description 块
(例如Factur-X计划通过) extensions) (中文(简体) ). 混合的描述
拥有的命名空间( n)dc 编号 : xmp 编号 : pdf 编号 : pdfaid有习惯的不是
保留——通过 extensions 或者说 embedFacturX()。正在装入一个
已有的 PDF/A 文件本身无法同步: 调用 convertToPDFA()
(或 embedFacturX())如果您更改了 Info 字段并需要它们镜像的话.
嵌入Factur-X / ZUGFERD 发票
embedFacturX() 包装一个人可读 PDF 和一个机器可读 Factur-X /
ZUGFERD XML进入PDF/A-3混合发票:确保PDF/A-3(转换为3B)
如果需要,或保留现有的3U/3B级),写出所需的 fx: XMP 电源
属性(加上PDF/A扩展计划),并将 XML 附加一个
关联文件关系.
没错 没有 生成或验证跨行业发票XML——通过 a
完整 factur-x.xml 从你的发票堆.
import { PDFDocument, embedFacturX } from '@cantoo/pdf-lib'
import fontkit from '@cantoo/fontkit'
const fontBytes = ...
const invoiceXmlBytes = ... // Factur-X / ZUGFeRD XML (Uint8Array)
const pdfDoc = await PDFDocument.create()
pdfDoc.registerFontkit(fontkit)
const font = await pdfDoc.embedFont(fontBytes)
const page = pdfDoc.addPage()
page.drawText('Invoice 2026-0001', { x: 50, y: 700, size: 18, font })
// ... draw the rest of the human-readable invoice with the embedded font ...
pdfDoc.setTitle('Invoice 2026-0001')
pdfDoc.setAuthor('ACME GmbH')
await embedFacturX(pdfDoc, invoiceXmlBytes, {
// MINIMUM | BASIC WL | BASIC | EN 16931 | EXTENDED | XRECHNUNG
conformanceLevel: 'EN 16931',
})
const pdfBytes = await pdfDoc.save()
设置文档元数据
这个例子产生 这个 PDF。 。 。 .
import { PDFDocument, StandardFonts } from 'pdf-lib'
// Create a new PDFDocument
const pdfDoc = await PDFDocument.create()
// Embed the Times Roman font
const timesRomanFont = await pdfDoc.embedFont(StandardFonts.TimesRoman)
// Add a page and draw some text on it
const page = pdfDoc.addPage([500, 600])
page.setFont(timesRomanFont)
page.drawText('The Life of an Egg', { x: 60, y: 500, size: 50 })
page.drawText('An Epic Tale of Woe', { x: 125, y: 460, size: 25 })
// Set all available metadata fields on the PDFDocument. Note that these fields
// are visible in the "Document Properties" section of most PDF readers.
pdfDoc.setTitle('🥚 The Life of an Egg 🍳')
pdfDoc.setAuthor('Humpty Dumpty')
pdfDoc.setSubject('📘 An Epic Tale of Woe 📖')
pdfDoc.setKeywords(['eggs', 'wall', 'fall', 'king', 'horses', 'men'])
pdfDoc.setProducer('PDF App 9000 🤖')
pdfDoc.setCreator('pdf-lib (https://github.com/Hopding/pdf-lib)')
pdfDoc.setCreationDate(new Date('2018-06-24T01:58:37.228Z'))
pdfDoc.setModificationDate(new Date('2019-12-21T07:00:11.000Z'))
// Serialize the PDFDocument to bytes (a Uint8Array)
const pdfBytes = await pdfDoc.save()
// For example, `pdfBytes` can be:
// • Written to a file in Node
// • Downloaded from the browser
// • Rendered in an <iframe>
读取文档元数据
import { PDFDocument } from 'pdf-lib'
// This should be a Uint8Array or ArrayBuffer
// This data can be obtained in a number of different ways
// If your running in a Node environment, you could use fs.readFile()
// In the browser, you could make a fetch() call and use res.arrayBuffer()
const existingPdfBytes = ...
// Load a PDFDocument without updating its existing metadata
const pdfDoc = await PDFDocument.load(existingPdfBytes, {
updateMetadata: false
})
// Print all available metadata fields
console.log('Title:', pdfDoc.getTitle())
console.log('Author:', pdfDoc.getAuthor())
console.log('Subject:', pdfDoc.getSubject())
console.log('Creator:', pdfDoc.getCreator())
console.log('Keywords:', pdfDoc.getKeywords())
console.log('Producer:', pdfDoc.getProducer())
console.log('Creation Date:', pdfDoc.getCreationDate())
console.log('Modification Date:', pdfDoc.getModificationDate())
此脚本输出如下( T)何时 这个 PDF 用于 existingPdfBytes 变量:
Title: Microsoft Word - Basic Curriculum Vitae example.doc
Author: Administrator
Subject: undefined
Creator: PScript5.dll Version 5.2
Keywords: undefined
Producer: Acrobat Distiller 8.1.0 (Windows)
Creation Date: 2010-07-29T14:26:00.000Z
Modification Date: 2010-07-29T14:26:00.000Z
设置查看器首选项
import {
PDFDocument,
StandardFonts,
NonFullScreenPageMode,
ReadingDirection,
PrintScaling,
Duplex,
PDFName,
} from 'pdf-lib'
// Create a new PDFDocument
const pdfDoc = await PDFDocument.create()
// Embed the Times Roman font
const timesRomanFont = await pdfDoc.embedFont(StandardFonts.TimesRoman)
// Add a page and draw some text on it
const page = pdfDoc.addPage([500, 600])
page.setFont(timesRomanFont)
page.drawText('The Life of an Egg', { x: 60, y: 500, size: 50 })
page.drawText('An Epic Tale of Woe', { x: 125, y: 460, size: 25 })
// Set all available viewer preferences on the PDFDocument:
const viewerPrefs = pdfDoc.catalog.getOrCreateViewerPreferences()
viewerPrefs.setHideToolbar(true)
viewerPrefs.setHideMenubar(true)
viewerPrefs.setHideWindowUI(true)
viewerPrefs.setFitWindow(true)
viewerPrefs.setCenterWindow(true)
viewerPrefs.setDisplayDocTitle(true)
// Set the PageMode (otherwise setting NonFullScreenPageMode has no meaning)
pdfDoc.catalog.set(PDFName.of('PageMode'), PDFName.of('FullScreen'))
// Set what happens when fullScreen is closed
viewerPrefs.setNonFullScreenPageMode(NonFullScreenPageMode.UseOutlines)
viewerPrefs.setReadingDirection(ReadingDirection.L2R)
viewerPrefs.setPrintScaling(PrintScaling.None)
viewerPrefs.setDuplex(Duplex.DuplexFlipLongEdge)
viewerPrefs.setPickTrayByPDFSize(true)
// We can set the default print range to only the first page
viewerPrefs.setPrintPageRange({ start: 0, end: 0 })
// Or we can supply noncontiguous ranges (e.g. pages 1, 3, and 5-7)
viewerPrefs.setPrintPageRange([
{ start: 0, end: 0 },
{ start: 2, end: 2 },
{ start: 4, end: 6 },
])
viewerPrefs.setNumCopies(2)
// Serialize the PDFDocument to bytes (a Uint8Array)
const pdfBytes = await pdfDoc.save()
// For example, `pdfBytes` can be:
// • Written to a file in Node
// • Downloaded from the browser
// • Rendered in an <iframe>
读取查看器首选项
import { PDFDocument } from 'pdf-lib'
// This should be a Uint8Array or ArrayBuffer
// This data can be obtained in a number of different ways
// If your running in a Node environment, you could use fs.readFile()
// In the browser, you could make a fetch() call and use res.arrayBuffer()
const existingPdfBytes = ...
// Load a PDFDocument without updating its existing metadata
const pdfDoc = await PDFDocument.load(existingPdfBytes)
const viewerPrefs = pdfDoc.catalog.getOrCreateViewerPreferences()
// Print all available viewer preference fields
console.log('HideToolbar:', viewerPrefs.getHideToolbar())
console.log('HideMenubar:', viewerPrefs.getHideMenubar())
console.log('HideWindowUI:', viewerPrefs.getHideWindowUI())
console.log('FitWindow:', viewerPrefs.getFitWindow())
console.log('CenterWindow:', viewerPrefs.getCenterWindow())
console.log('DisplayDocTitle:', viewerPrefs.getDisplayDocTitle())
console.log('NonFullScreenPageMode:', viewerPrefs.getNonFullScreenPageMode())
console.log('ReadingDirection:', viewerPrefs.getReadingDirection())
console.log('PrintScaling:', viewerPrefs.getPrintScaling())
console.log('Duplex:', viewerPrefs.getDuplex())
console.log('PickTrayByPDFSize:', viewerPrefs.getPickTrayByPDFSize())
console.log('PrintPageRange:', viewerPrefs.getPrintPageRange())
console.log('NumCopies:', viewerPrefs.getNumCopies())
此脚本输出如下( T)何时 这个 PDF 用于 existingPdfBytes 变量:
HideToolbar: true
HideMenubar: true
HideWindowUI: false
FitWindow: true
CenterWindow: true
DisplayDocTitle: true
NonFullScreenPageMode: UseNone
ReadingDirection: R2L
PrintScaling: None
Duplex: DuplexFlipLongEdge
PickTrayByPDFSize: true
PrintPageRange: [ { start: 1, end: 1 }, { start: 3, end: 4 } ]
NumCopies: 2
绘制 SVG 路径
这个例子产生 这个 PDF。 。 。 .
import { PDFDocument, rgb } from 'pdf-lib'
// SVG path for a wavy line
const svgPath =
'M 0,20 L 100,160 Q 130,200 150,120 C 190,-40 200,200 300,150 L 400,90'
// Create a new PDFDocument
const pdfDoc = await PDFDocument.create()
// Add a blank page to the document
const page = pdfDoc.addPage()
page.moveTo(100, page.getHeight() - 5)
// Draw the SVG path as a black line
page.moveDown(25)
page.drawSvgPath(svgPath)
// Draw the SVG path as a thick green line
page.moveDown(200)
page.drawSvgPath(svgPath, { borderColor: rgb(0, 1, 0), borderWidth: 5 })
// Draw the SVG path and fill it with red
page.moveDown(200)
page.drawSvgPath(svgPath, { color: rgb(1, 0, 0) })
// Draw the SVG path at 50% of its original size
page.moveDown(200)
page.drawSvgPath(svgPath, { scale: 0.5 })
// Serialize the PDFDocument to bytes (a Uint8Array)
const pdfBytes = await pdfDoc.save()
// For example, `pdfBytes` can be:
// • Written to a file in Node
// • Downloaded from the browser
// • Rendered in an <iframe>
绘制 SVG
import { PDFDocument, rgb } from 'pdf-lib';
// SVG of a square inside a square
const svg = `<svg width="100" height="100">
<rect y="0" x="0" width="100" height="100" fill="none" stroke="black"/>
<rect y="25" x="25" width="50" height="50" fill="black"/>
</svg>`;
const svg2 =
'<svg><image href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQYV2NgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII="/></svg>';
// Create a new PDFDocument
const pdfDoc = await PDFDocument.create();
// Add a blank page to the document
const page = pdfDoc.addPage();
// drawSvg can accept the svg as a string, as long as there are no images in it
page.moveTo(100, 10);
page.drawSvg(svg);
// If the svg has images, or if you don't know if it does, you should call embedSVG first
page.moveTo(200, 10);
const pdfSvg = await pdfDoc.embedSvg(svg2);
page.drawSvg(pdfSvg);
// Serialize the PDFDocument to bytes (a Uint8Array)
const pdfBytes = await pdfDoc.save();
代诺用法
pdf-lib 完全支持令人兴奋的新 德诺 运行时间! 全部都给我 使用示例 在德诺工作 你只需要改变进口 pdf-lib 财务报告和审定财务报表 fontkit 以使用 CDN URL,因为 Deno 要求通过 URL 引用所有模块.
用 Deno 创建文档
下面是 创建文档 为 Deno 修改的示例 :
import {
PDFDocument,
StandardFonts,
rgb,
} from 'https://cdn.skypack.dev/pdf-lib@^1.11.1?dts';
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();
await Deno.writeFile('out.pdf', pdfBytes);
如果您将此脚本保存为 create-document.ts,可以使用Deno执行,其命令如下:
deno run --allow-write create-document.ts
结果 out.pdf 文件看起来像 这个 PDF。 。 。 .
用 Deno 嵌入字体
以下是一个比较复杂的例子,说明如何将字体嵌入德诺并测量文字:
import {
degrees,
PDFDocument,
rgb,
StandardFonts,
} from 'https://cdn.skypack.dev/pdf-lib@^1.11.1?dts';
import fontkit from 'https://esm.sh/@cantoo/[email protected]';
const url = 'https://pdf-lib.js.org/assets/ubuntu/Ubuntu-R.ttf';
const fontBytes = await fetch(url).then((res) => res.arrayBuffer());
const pdfDoc = await PDFDocument.create();
pdfDoc.registerFontkit(fontkit);
const customFont = await pdfDoc.embedFont(fontBytes);
const page = pdfDoc.addPage();
const text = 'This is text in an embedded font!';
const textSize = 35;
const textWidth = customFont.widthOfTextAtSize(text, textSize);
const textHeight = customFont.heightAtSize(textSize);
page.drawText(text, {
x: 40,
y: 450,
size: textSize,
font: customFont,
color: rgb(0, 0.53, 0.71),
});
page.drawRectangle({
x: 40,
y: 450,
width: textWidth,
height: textHeight,
borderColor: rgb(1, 0, 0),
borderWidth: 1.5,
});
const pdfBytes = await pdfDoc.save();
await Deno.writeFile('out.pdf', pdfBytes);
如果您将此脚本保存为 custom-font.ts,可以使用以下命令执行:
deno run --allow-write --allow-net custom-font.ts
结果 out.pdf 文件看起来像 这个 PDF。 。 。 .
完整示例
这个 使用示例 提供简短的代码,以显示其不同特征 pdf-lib。 您可以在 apps/ 目录。 这些应用程序用来进行人工测试 pdf-lib 在每次释放之前(除 自动测试) (中文(简体) ).
目前有四个应用程序:
node- 包含 测试 页:1pdf-lib在节点环境中。 这些测试在试图保存/装入 PDF 、 字体或图像时是一个方便的参考pdf-lib从文件系统。 它们也允许您在不同的取景者(Acrobat, Preview, Foxit, Chrome, Firefox等)中快速打开您的PDF,以确保兼容性.web- 包含 测试 页:1pdf-lib在浏览器环境中。 这些测试在试图保存/装入 PDF 、 字体或图像时是一个方便的参考pdf-lib在浏览器环境中.rn- 包含 测试 页:1pdf-lib(原始内容存档于2018-10-21). React Environment. 这些测试在试图保存/装入 PDF 、 字体或图像时是一个方便的参考pdf-lib(原始内容存档于2019-10-21). React Entertainment.deno- 包含 测试 页:1pdf-lib在德诺环境。 这些测试在试图保存/装入 PDF 、 字体或图像时是一个方便的参考pdf-lib从文件系统.
安装
国家预防机制模块
要安装最新稳定的 Cantoo 叉 :
# With npm
npm install --save @cantoo/pdf-lib
# With yarn
yarn add @cantoo/pdf-lib
(单位:千美元)pdf-lib 没有瞄准镜仍然是npm上的原Hopding包. )
销售 PNG 标准字体( C)
@cantoo/pdf-lib 供应商 @cantoo/upng 财务报告和审定财务报表 @cantoo/standard-fonts 下级 src/packages/ (模式与 @cantoo/fontkit) (中文(简体) ). 压缩用途 fflate (无) pako) (中文(简体) ).
必要时单独安装子包 :
npm install @cantoo/upng @cantoo/standard-fonts
# or via pdf-lib subpath exports:
# import UPNG from '@cantoo/pdf-lib/upng'
# import { FontNames } from '@cantoo/pdf-lib/standard-fonts'
UMD 模块
您也可以下载 pdf-lib 作为UMD模块来自 (单位:公斤) 或者说 日记. UMD的建筑已经编译为ES5, 因此它们应该工作 在任何现代浏览器中。UMD 构建是有用的,如果你没有使用软件包管理器或模块捆绑器。 例如,您可以在 <script> 标记 HTML 页面.
以下建筑可供使用:
- https://unpkg.com/pdf-lib/dist/pdf-lib.js (中文(简体) )
- https://unpkg.com/pdf-lib/dist/pdf-lib.min.js (中文(简体) )
- https://cdn.jsdelivr.net/npm/pdf-lib/dist/pdf-lib.js (中文(简体) )
- https://cdn.jsdelivr.net/npm/pdf-lib/dist/pdf-lib.min.js (中文(简体) )
注: 如果您在生产中使用了 CDN 脚本, 您应该在 URL 中包含特定的版本编号, 例如 :
- https://unpkg.com/[email protected]/dist/pdf-lib.min.js (中文(简体) )
- https://cdn.jsdelivr.net/npm/[email protected]/dist/pdf-lib.min.js (中文(简体) )
当您使用 UMD 架构时, 您将可访问全球 window.PDFLib 变量。 此变量包含导出的所有类和函数 pdf-lib。例如:
// NPM module
import { PDFDocument, rgb } from 'pdf-lib';
// UMD module
var PDFDocument = PDFLib.PDFDocument;
var rgb = PDFLib.rgb;
字体安装
pdf-lib 依赖 @cantoo/fontkit 支持嵌入自定义字体。 您必须在您的工程中添加一个字体匹配的模块并使用 pdfDoc.registerFontkit(...) 在嵌入自定义字体之前(见 字体嵌入示例) (中文(简体) ). 这个模块不默认包含在内,因为并非所有用户都需要它,它会增加捆绑大小.
更喜欢Cantoo的维护 @cantoo/fontkit 叉 (基于字体kit v2). 它搭载了TypeScript类型和UMD构建(包括一个精密文件). 上游 fontkit 和老年人 @pdf-lib/fontkit 软件包仍然支持兼容性.
Fontkit 国家预防机制模块
# With npm
npm install --save @cantoo/fontkit
# With yarn
yarn add @cantoo/fontkit
登记 fontkit 实例后再嵌入自定义字体。 与 @cantoo/fontkit ^2.0.9 您可以使用默认导入(建议)。 命名空间导入也可行 。 上游 fontkit 在节点无害环境管理下仍然没有默认出口,因此使用 import * as fontkit from 'fontkit' 如果你依赖于这个包裹.
import { PDFDocument } from 'pdf-lib'
import fontkit from '@cantoo/fontkit'
const pdfDoc = await PDFDocument.create()
pdfDoc.registerFontkit(fontkit)
Fontkit UMD 模块
您也可以下载 @cantoo/fontkit 作为UMD模块来自 (单位:公斤) 或者说 日记数字 :
- https://unpkg.com/@cantoo/fontkit/dist/fontkit.umd.min.js (中文(简体) )
- https://unpkg.com/@cantoo/fontkit/dist/fontkit.umd.js (中文(简体) )
- https://cdn.jsdelivr.net/npm/@cantoo/fontkit/dist/fontkit.umd.min.js (中文(简体) )
- https://cdn.jsdelivr.net/npm/ (中文(简体) )