jspdf、pdf-lib、pdfmake はすべてブラウザ上で PDF を扱うためのライブラリですが、アプローチが異なります。jspdf は座標を指定して从零から描画する伝統的な方式です。pdf-lib は既存の PDF 読み込みと編集に強く、フォーム-fill や結合に適しています。pdfmake は JSON 構造で文書を定義し、レポートや表組みのような複雑なレイアウトを宣言的に作成できます。
ブラウザ上で PDF を生成・編集する必要がある場合、jspdf、pdf-lib、pdfmake の 3 つが主要な選択肢です。これらはすべて JavaScript で動作しますが、設計思想と得意とする領域が明確に異なります。単純な書類生成から複雑なレポート、既存ファイルの編集まで、ユースケースに応じて適切なツールを選ぶ必要があります。
jspdf はキャンバスに描画する感覚で PDF を作ります。
// jspdf: 座標指定でテキスト配置
import jsPDF from 'jspdf';
const doc = new jsPDF();
doc.text('Hello World', 10, 10); // x=10, y=10
doc.save('document.pdf');
pdf-lib も同様に座標ベースですが、既存 PDF の操作に特化しています。
// pdf-lib: ページ取得と描画
import { PDFDocument } from 'pdf-lib';
const pdfDoc = await PDFDocument.create();
const page = pdfDoc.addPage([595, 842]);
page.drawText('Hello World', { x: 10, y: 800 });
const pdfBytes = await pdfDoc.save();
pdfmake は文書構造を JSON で定義します。
// pdfmake: 宣言的な文書定義
import pdfMake from 'pdfmake/build/pdfmake';
const docDefinition = {
content: ['Hello World']
};
pdfMake.createPdf(docDefinition).download('document.pdf');
この機能の有無がライブラリ選定の決定的な要因になります。
jspdf は既存 PDF の読み込みをサポートしていません。
// jspdf: 既存 PDF 編集は非対応
// 背景画像として配置するしかありません
const doc = new jsPDF();
doc.addImage(backgroundImage, 'JPEG', 0, 0, 210, 297);
doc.text('New Content', 10, 10);
pdf-lib は既存 PDF の読み込みと修改が核心機能です。
// pdf-lib: 既存 PDF の読み込みと修改
import { PDFDocument } from 'pdf-lib';
const existingPdfBytes = await fetch('/template.pdf').then(res => res.arrayBuffer());
const pdfDoc = await PDFDocument.load(existingPdfBytes);
const pages = pdfDoc.getPages();
pages[0].drawText('Modified', { x: 50, y: 50 });
pdfmake も既存 PDF の編集には対応していません。
pdf-lib との併用を検討します。// pdfmake: 既存 PDF 編集は非対応
// 常に新しい定義から生成します
const docDefinition = { content: 'New Report' };
pdfMake.createPdf(docDefinition).download();
複雑なレポートを作る場合、レイアウト機能の差が顕著に現れます。
jspdf はすべての位置を手動で管理します。
// jspdf: 表の手動描画
const doc = new jsPDF();
doc.line(10, 10, 100, 10);
doc.line(10, 20, 100, 20);
doc.text('Cell 1', 15, 15);
doc.text('Cell 2', 50, 15);
pdf-lib も基本的には手動配置です。
// pdf-lib: 手動配置
const page = pdfDoc.addPage();
page.drawText('Label', { x: 10, y: 800 });
page.drawText('Value', { x: 100, y: 800 });
pdfmake は表や列レイアウトを内置サポートしています。
// pdfmake: 表と列の自動レイアウト
const docDefinition = {
content: [
{ columns: ['Left', 'Right'] },
{ table: { body: [['Name', 'Age'], ['Alice', 30]] } }
]
};
pdfMake.createPdf(docDefinition).download();
PDF フォーム(AcroForm)を扱う場合、対応状況が異なります。
jspdf はプラグインなしではフォーム生成が困難です。
// jspdf: フォーム支援は限定的
// 標準機能ではフォームフィールド作成は複雑です
const doc = new jsPDF();
// 専用のプラグインが必要になる場合があります
pdf-lib はフォーム操作に最も優れています。
// pdf-lib: フォームフィールドへの入力
const form = pdfDoc.getForm();
const nameField = form.getTextField('Name');
nameField.setText('John Doe');
await form.flatten(); // 編集不可に固定
pdfmake はフォーム生成を主目的としていません。
// pdfmake: フォーム機能はなし
// 静的なレポート生成に使用します
const docDefinition = { content: 'Report Content' };
これら 3 つのライブラリには、共通する大きな利点があります。
// すべてクライアント側で完結
// jspdf, pdf-lib, pdfmake 共通
const doc = createDocument(); // 各ライブラリの生成処理
doc.save(); // ブラウザでダウンロード
// 共通のダウンロードフロー
button.addEventListener('click', () => {
generatePdf().then(blob => {
// ダウンロード処理
});
});
# 共通のインストールコマンド
npm install jspdf
npm install pdf-lib
npm install pdfmake
| 機能 | jspdf | pdf-lib | pdfmake |
|---|---|---|---|
| 生成方式 | 座標指定(手動) | 座標指定(手動) | 宣言的(JSON) |
| 既存編集 | ❌ 非対応 | ✅ 対応 | ❌ 非対応 |
| レイアウト | 手動計算が必要 | 手動計算が必要 | 自動(表・列) |
| フォーム | 限定的 | ✅ 充実 | ❌ 非対応 |
| 用途 | 単純な書類 | 編集・結合 | レポート・表 |
jspdf はシンプルな証明書やラベル印刷 — 座標固定で良い場合 — に最適です。学習コストが低く、すぐに使い始められます。
pdf-lib は既存のテンプレートシステムがある場合に不可欠です。フォーム入力や PDF 結合 — サーバー側処理を減らしたい — 場合に選びます。
pdfmake は請求書や報告書 — 表や項目が多い — 生成に適しています。メンテナンス性を重視するプロジェクトで真価を発揮します。
結論:新規生成でレイアウト重視なら pdfmake、既存ファイル編集なら pdf-lib、単純な描画なら jspdf を選んでください。プロジェクトの要件 — 編集が必要か、レイアウトが複雑か — を最初に確認することが重要です。
既存の PDF テンプレートにデータを埋め込む場合や、複数の PDF を結合する必要がある場合に最適です。フォームフィールドの操作も可能です。修改既存のドキュメントが必要なプロジェクトでは事実上の標準です。
シンプルな請求書や証明書など、座標指定で十分な場合に選びます。既存ファイルの編集はできないため、新規生成専用プロジェクト向けです。API が直感的で、単純なテキストや画像配置ならすぐに実装できます。
複雑な表や列レイアウトを含むレポート生成に適しています。スタイル定義を一元管理でき、メンテナンス性が向上します。コンテンツ駆動で開発したい場合に最適です。
Learn more at pdf-lib.js.org
pdf-lib was created to address the JavaScript ecosystem's lack of robust support for PDF manipulation (especially for PDF modification).
Two of pdf-lib's distinguishing features are:
There are other good open source JavaScript PDF libraries available. However, most of them can only create documents, they cannot modify existing ones. And many of them only work in particular environments.
This example produces this 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>
This example produces this PDF (when this PDF is used for the existingPdfBytes variable).
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>
This example produces this PDF.
See also Creating and Filling Forms
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>
This example produces this PDF (when this PDF is used for the formPdfBytes variable, this image is used for the marioImageBytes variable, and this image is used for the emblemImageBytes variable).
See also Creating and Filling Forms
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>
This example produces this PDF (when this PDF is used for the formPdfBytes variable).
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>
This example produces this PDF (when this PDF is used for the firstDonorPdfBytes variable and this PDF is used for the secondDonorPdfBytes variable).
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>
This example produces this PDF (when this image is used for the jpgImageBytes variable and this image is used for the pngImageBytes variable).
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>
This example produces this PDF (when this PDF is used for the americanFlagPdfBytes variable and this PDF is used for the usConstitutionPdfBytes variable).
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 relies on a sister module to support embedding custom fonts: @pdf-lib/fontkit. You must add the @pdf-lib/fontkit module to your project and register it using pdfDoc.registerFontkit(...) before embedding custom fonts.
This example produces this PDF (when this font is used for the fontBytes variable).
import { PDFDocument, rgb } from 'pdf-lib'
import fontkit from '@pdf-lib/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>
This example produces this PDF (when this image is used for the jpgAttachmentBytes variable and this PDF is used for the pdfAttachmentBytes variable).
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>
This example produces this 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())
This script outputs the following (when this PDF is used for the existingPdfBytes variable):
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())
This script outputs the following (when this PDF is used for the existingPdfBytes variable):
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
This example produces this 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>
pdf-lib fully supports the exciting new Deno runtime! All of the usage examples work in Deno. The only thing you need to do is change the imports for pdf-lib and @pdf-lib/fontkit to use the Skypack CDN, because Deno requires all modules to be referenced via URLs.
See also How to Create and Modify PDF Files in Deno With pdf-lib
Below is the create document example modified for 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);
If you save this script as create-document.ts, you can execute it using Deno with the following command:
deno run --allow-write create-document.ts
The resulting out.pdf file will look like this PDF.
Here's a slightly more complicated example demonstrating how to embed a font and measure text in Deno:
import {
degrees,
PDFDocument,
rgb,
StandardFonts,
} from 'https://cdn.skypack.dev/pdf-lib@^1.11.1?dts';
import fontkit from 'https://cdn.skypack.dev/@pdf-lib/fontkit@^1.0.0?dts';
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);
If you save this script as custom-font.ts, you can execute it with the following command:
deno run --allow-write --allow-net custom-font.ts
The resulting out.pdf file will look like this PDF.
The usage examples provide code that is brief and to the point, demonstrating the different features of pdf-lib. You can find complete working examples in the apps/ directory. These apps are used to do manual testing of pdf-lib before every release (in addition to the automated tests).
There are currently four apps:
node - contains tests for pdf-lib in Node environments. These tests are a handy reference when trying to save/load PDFs, fonts, or images with pdf-lib from the filesystem. They also allow you to quickly open your PDFs in different viewers (Acrobat, Preview, Foxit, Chrome, Firefox, etc...) to ensure compatibility.web - contains tests for pdf-lib in browser environments. These tests are a handy reference when trying to save/load PDFs, fonts, or images with pdf-lib in a browser environment.rn - contains tests for pdf-lib in React Native environments. These tests are a handy reference when trying to save/load PDFs, fonts, or images with pdf-lib in a React Native environment.deno - contains tests for pdf-lib in Deno environments. These tests are a handy reference when trying to save/load PDFs, fonts, or images with pdf-lib from the filesystem.To install the latest stable version:
# With npm
npm install --save pdf-lib
# With yarn
yarn add pdf-lib
This assumes you're using npm or yarn as your package manager.
You can also download pdf-lib as a UMD module from unpkg or jsDelivr. The UMD builds have been compiled to ES5, so they should work in any modern browser. UMD builds are useful if you aren't using a package manager or module bundler. For example, you can use them directly in the <script> tag of an HTML page.
The following builds are available:
NOTE: if you are using the CDN scripts in production, you should include a specific version number in the URL, for example:
When using a UMD build, you will have access to a global window.PDFLib variable. This variable contains all of the classes and functions exported by pdf-lib. For example:
// NPM module
import { PDFDocument, rgb } from 'pdf-lib';
// UMD module
var PDFDocument = PDFLib.PDFDocument;
var rgb = PDFLib.rgb;
pdf-lib relies upon a sister module to support embedding custom fonts: @pdf-lib/fontkit. You must add the @pdf-lib/fontkit module to your project and register it using pdfDoc.registerFontkit(...) before embedding custom fonts (see the font embedding example). This module is not included by default because not all users need it, and it increases bundle size.
Installing this module is easy. Just like pdf-lib itself, @pdf-lib/fontkit can be installed with npm/yarn or as a UMD module.
# With npm
npm install --save @pdf-lib/fontkit
# With yarn
yarn add @pdf-lib/fontkit
To register the fontkit instance:
import { PDFDocument } from 'pdf-lib'
import fontkit from '@pdf-lib/fontkit'
const pdfDoc = await PDFDocument.create()
pdfDoc.registerFontkit(fontkit)
The following builds are available:
NOTE: if you are using the CDN scripts in production, you should include a specific version number in the URL, for example:
When using a UMD build, you will have access to a global window.fontkit variable. To register the fontkit instance:
var pdfDoc = await PDFLib.PDFDocument.create()
pdfDoc.registerFontkit(fontkit)
API documentation is available on the project site at https://pdf-lib.js.org/docs/api/.
The repo for the project site (and generated documentation files) is located here: https://github.com/Hopding/pdf-lib-docs.
When working with PDFs, you will frequently come across the terms "character encoding" and "font". If you have experience in web development, you may wonder why these are so prevalent. Aren't they just annoying details that you shouldn't need to worry about? Shouldn't PDF libraries and readers be able to handle all of this for you like web browsers can? Unfortunately, this is not the case. The nature of the PDF file format makes it very difficult to avoid thinking about character encodings and fonts when working with PDFs.
pdf-lib does its best to simplify things for you. But it can't perform magic. This means you should be aware of the following:
import { PDFDocument, StandardFonts } from 'pdf-lib'
const pdfDoc = await PDFDocument.create()
const courierFont = await pdfDoc.embedFont(StandardFonts.Courier)
const page = pdfDoc.addPage()
page.drawText('Some boring latin text in the Courier font', {
font: courierFont,
})
embedFont method. When you embed your own font, you can use any Unicode characters that it supports. This capability frees you from the limitations imposed by the standard fonts. Most PDF files use embedded fonts. You can embed and use a custom font like so (see also):
import { PDFDocument } from 'pdf-lib'
import fontkit from '@pdf-lib/fontkit'
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 ubuntuFont = await pdfDoc.embedFont(fontBytes)
const page = pdfDoc.addPage()
page.drawText('Some fancy Unicode text in the ŪЬȕǹƚü font', {
font: ubuntuFont,
})
Note that encoding errors will be thrown if you try to use a character with a font that does not support it. For example, Ω is not in the WinAnsi character set. So trying to draw it on a page with the standard Helvetica font will throw the following error:
Error: WinAnsi cannot encode "Ω" (0x03a9)
at Encoding.encodeUnicodeCodePoint
Embedding a font in a PDF document will typically increase the file's size. You can reduce the amount a file's size is increased by subsetting the font so that only the necessary characters are embedded. You can subset a font by setting the subset option to true. For example:
const font = await pdfDoc.embedFont(fontBytes, { subset: true });
Note that subsetting does not work for all fonts. See https://github.com/Hopding/pdf-lib/issues/207#issuecomment-537210471 for additional details.
pdf-lib can create, fill, and read PDF form fields. The following field types are supported:
See the form creation and form filling usage examples for code samples. Tests 1, 14, 15, 16, and 17 in the complete examples contain working example code for form creation and filling in a variety of different JS environments.
IMPORTANT: The default font used to display text in buttons, dropdowns, option lists, and text fields is the standard Helvetica font. This font only supports characters in the latin alphabet (see Fonts and Unicode for details). This means that if any of these field types are created or modified to contain text outside the latin alphabet (as is often the case), you will need to embed and use a custom font to update the field appearances. Otherwise an error will be thrown (likely when you save the PDFDocument).
You can use an embedded font when filling form fields as follows:
import { PDFDocument } from 'pdf-lib';
import fontkit from '@pdf-lib/fontkit';
// Fetch the PDF with form fields
const formUrl = 'https://pdf-lib.js.org/assets/dod_character.pdf';
const formBytes = await fetch(formUrl).then((res) => res.arrayBuffer());
// Fetch the Ubuntu font
const fontUrl = 'https://pdf-lib.js.org/assets/ubuntu/Ubuntu-R.ttf';
const fontBytes = await fetch(fontUrl).then((res) => res.arrayBuffer());
// Load the PDF with form fields
const pdfDoc = await PDFDocument.load(formBytes);
// Embed the Ubuntu font
pdfDoc.registerFontkit(fontkit);
const ubuntuFont = await pdfDoc.embedFont(fontBytes);
// Get two text fields from the form
const form = pdfDoc.getForm();
const nameField = form.getTextField('CharacterName 2');
const ageField = form.getTextField('Age');
// Fill the text fields with some fancy Unicode characters (outside
// the WinAnsi latin character set)
nameField.setText('Ӎӑȑїõ');
ageField.setText('24 ŷȇȁŗš');
// **Key Step:** Update the field appearances with the Ubuntu font
form.updateFieldAppearances(ubuntuFont);
// Save the PDF with filled form fields
const pdfBytes = await pdfDoc.save();
Existing form fields can be accessed with the following methods of PDFForm:
PDFForm.getButtonPDFForm.getCheckBoxPDFForm.getDropdownPDFForm.getOptionListPDFForm.getRadioGroupPDFForm.getTextFieldNew form fields can be created with the following methods of PDFForm:
PDFForm.createButtonPDFForm.createCheckBoxPDFForm.createDropdownPDFForm.createOptionListPDFForm.createRadioGroupPDFForm.createTextFieldBelow are some of the most commonly used methods for reading and filling the aforementioned subclasses of PDFField:
PDFDropdown.selectPDFDropdown.clearPDFDropdown.getSelectedPDFDropdown.getOptionsPDFDropdown.addOptionsPDFOptionList.selectPDFOptionList.clearPDFOptionList.getSelectedPDFOptionList.getOptionsPDFOptionList.addOptionsPDFRadioGroup.selectPDFRadioGroup.clearPDFRadioGroup.getSelectedPDFRadioGroup.getOptionsPDFRadioGroup.addOptionToPagePDFTextField.setTextPDFTextField.getTextPDFTextField.setMaxLengthPDFTextField.getMaxLengthPDFTextField.removeMaxLengthpdf-lib can extract the content of text fields (see PDFTextField.getText), but it cannot extract plain text on a page outside of a form field. This is a difficult feature to implement, but it is within the scope of this library and may be added to pdf-lib in the future. See
#93,
#137,
#177,
#329, and
#380.pdf-lib can remove and edit the content of text fields (see PDFTextField.setText), but it does not provide APIs for removing or editing text on a page outside of a form field. This is also a difficult feature to implement, but is within the scope of pdf-lib and may be added in the future. See
#93,
#137,
#177,
#329, and
#380.pdf-lib does not support the use of HTML or CSS when adding content to a PDF. Similarly, pdf-lib cannot embed HTML/CSS content into PDFs. As convenient as such a feature might be, it would be extremely difficult to implement and is far beyond the scope of this library. If this capability is something you need, consider using Puppeteer.Discussions is the best place to chat with us, ask questions, and learn more about pdf-lib!
See also MAINTAINERSHIP.md#communication and MAINTAINERSHIP.md#discord.
pdf-lib does not currently support encrypted documents. You should not use pdf-lib with encrypted documents. However, this is a feature that could be added to pdf-lib. Please create an issue if you would find this feature helpful!
When an encrypted document is passed to PDFDocument.load(...), an error will be thrown:
import { PDFDocument, EncryptedPDFError } from 'pdf-lib'
const encryptedPdfBytes = ...
// Assignment fails. Throws an `EncryptedPDFError`.
const pdfDoc = PDFDocument.load(encryptedPdfBytes)
This default behavior is usually what you want. It allows you to easily detect if a given document is encrypted, and it prevents you from trying to modify it. However, if you really want to load the document, you can use the { ignoreEncryption: true } option:
import { PDFDocument } from 'pdf-lib'
const encryptedPdfBytes = ...
// Assignment succeeds. Does not throw an error.
const pdfDoc = PDFDocument.load(encryptedPdfBytes, { ignoreEncryption: true })
Note that using this option does not decrypt the document. This means that any modifications you attempt to make on the returned PDFDocument may fail, or have unexpected results.
You should not use this option. It only exists for backwards compatibility reasons.
We welcome contributions from the open source community! If you are interested in contributing to pdf-lib, please take a look at the CONTRIBUTING.md file. It contains information to help you get pdf-lib setup and running on your machine. (We try to make this as simple and fast as possible! :rocket:)
Check out MAINTAINERSHIP.md for details on how this repo is maintained and how we use issues, PRs, and discussions.
pdfkit is a PDF generation library for Node and the Browser. This library was immensely helpful as a reference and existence proof when creating pdf-lib. pdfkit's code for font embedding, PNG embedding, and JPG embedding was especially useful.pdf.js is a PDF rendering library for the Browser. This library was helpful as a reference when writing pdf-lib's parser. Some of the code for stream decoding was ported directly to TypeScript for use in pdf-lib.pdfbox is a PDF generation and modification library written in Java. This library was an invaluable reference when implementing form creation and filling APIs for pdf-lib.jspdf is a PDF generation library for the browser.pdfmake is a PDF generation library for the browser.hummus is a PDF generation and modification library for Node environments. hummus is a Node wrapper around a C++ library, so it doesn't work in many JavaScript environments - like the Browser or React Native.react-native-pdf-lib is a PDF generation and modification library for React Native environments. react-native-pdf-lib is a wrapper around C++ and Java libraries.pdfassembler is a PDF generation and modification library for Node and the browser. It requires some knowledge about the logical structure of PDF documents to use.This repo used to contain a file called pdf_specification.pdf in the root directory. This was a copy of the PDF 1.7 specification, which is made freely available by Adobe. On 8/30/2021, we received a DMCA complaint requiring us to remove the file from this repo. Simply removing the file via a new commit to master was insufficient to satisfy the complaint. The file needed to be completely removed from the repo's git history. Unfortunately, the file was added over two years ago, this meant we had to rewrite the repo's git history and force push to master 😔.
We removed the file and rewrote the repo's history using BFG Repo-Cleaner as outlined here. For full transparency, here are the exact commands we ran:
$ git clone git@github.com:Hopding/pdf-lib.git
$ cd pdf-lib
$ rm pdf_specification.pdf
$ git commit -am 'Remove pdf_specification.pdf'
$ bfg --delete-files pdf_specification.pdf
$ git reflog expire --expire=now --all && git gc --prune=now --aggressive
$ git push --force
If you're a user of pdf-lib, you shouldn't care! Just keep on using pdf-lib like normal 😃 ✨!
If you are a pdf-lib developer (meaning you've forked pdf-lib and/or have an open PR) then this does impact you. If you forked or cloned the repo prior to 8/30/2021 then your fork's git history is out of sync with this repo's master branch. Unfortunately, this will likely be a headache for you to deal with. Sorry! We didn't want to rewrite the history, but there really was no alternative.
It's important to note that pdf-lib's source code has not changed at all. It's exactly the same as it was before the git history rewrite. The repo still has the exact same number of commits (and even the same commit contents, except for the commit that added pdf_specification.pdf). What has changed are the SHAs of those commits.
The simplest way to deal with this fact is to:
See this StackOverflow answer for a great, in depth explanation of what a git history rewrite entails.