- Notifications
You must be signed in to change notification settings - Fork716
Create and modify PDF documents in any JavaScript environment
License
Hopding/pdf-lib
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Learn more atpdf-lib.js.org
- Features
- Motivation
- Usage Examples
- Deno Usage
- Complete Examples
- Installation
- Documentation
- Fonts and Unicode
- Creating and Filling Forms
- Limitations
- Help and Discussion
- Encryption Handling
- Migrating to v1.0.0
- Contributing
- Maintainership
- Tutorials and Cool Stuff
- Prior Art
- Git History Rewrite
- License
- Create new PDFs
- Modify existing PDFs
- Create forms
- Fill forms
- Flatten forms
- Add Pages
- Insert Pages
- Remove Pages
- Copy pages between PDFs
- Draw Text
- Draw Images
- Draw PDF Pages
- Draw Vector Graphics
- Draw SVG Paths
- Measure width and height of text
- Embed Fonts (supports UTF-8 and UTF-16 character sets)
- Set document metadata
- Read document metadata
- Set viewer preferences
- Read viewer preferences
- Add attachments
pdf-lib
was created to address the JavaScript ecosystem's lack of robust support for PDF manipulation (especially for PDFmodification).
Two ofpdf-lib
's distinguishing features are:
- Supporting modification (editing) of existing documents.
- Working in all JavaScript environments - not just in Node or the Browser.
There areother good open source JavaScript PDF libraries available. However, most of them can onlycreate documents, they cannotmodify existing ones. And many of them only work in particular environments.
This example producesthis PDF.
import{PDFDocument,StandardFonts,rgb}from'pdf-lib'// Create a new PDFDocumentconstpdfDoc=awaitPDFDocument.create()// Embed the Times Roman fontconsttimesRomanFont=awaitpdfDoc.embedFont(StandardFonts.TimesRoman)// Add a blank page to the documentconstpage=pdfDoc.addPage()// Get the width and height of the pageconst{ width, height}=page.getSize()// Draw a string of text toward the top of the pageconstfontSize=30page.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)constpdfBytes=awaitpdfDoc.save()// For example, `pdfBytes` can be:// • Written to a file in Node// • Downloaded from the browser// • Rendered in an <iframe>
This example producesthis PDF (whenthis PDF is used for theexistingPdfBytes
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()constexistingPdfBytes= ...// Load a PDFDocument from the existing PDF bytesconstpdfDoc=awaitPDFDocument.load(existingPdfBytes)// Embed the Helvetica fontconsthelveticaFont=awaitpdfDoc.embedFont(StandardFonts.Helvetica)// Get the first page of the documentconstpages=pdfDoc.getPages()constfirstPage=pages[0]// Get the width and height of the first pageconst{ width, height}=firstPage.getSize()// Draw a string of text diagonally across the first pagefirstPage.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)constpdfBytes=awaitpdfDoc.save()// For example, `pdfBytes` can be:// • Written to a file in Node// • Downloaded from the browser// • Rendered in an <iframe>
This example producesthis PDF.
See alsoCreating and Filling Forms
import{PDFDocument}from'pdf-lib'// Create a new PDFDocumentconstpdfDoc=awaitPDFDocument.create()// Add a blank page to the documentconstpage=pdfDoc.addPage([550,750])// Get the form so we can add fields to itconstform=pdfDoc.getForm()// Add the superhero text field and descriptionpage.drawText('Enter your favorite superhero:',{x:50,y:700,size:20})constsuperheroField=form.createTextField('favorite.superhero')superheroField.setText('One Punch Man')superheroField.addToPage(page,{x:55,y:640})// Add the rocket radio group, labels, and descriptionpage.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})constrocketField=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 descriptionpage.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})constexiaField=form.createCheckBox('gundam.exia')constkyriosField=form.createCheckBox('gundam.kyrios')constvirtueField=form.createCheckBox('gundam.virtue')constdynamesField=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 descriptionpage.drawText('Select your favorite planet*:',{x:50,y:280,size:20})constplanetsField=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 descriptionpage.drawText('Select your favorite person:',{x:50,y:180,size:18})constpersonField=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)constpdfBytes=awaitpdfDoc.save()// For example, `pdfBytes` can be:// • Written to a file in Node// • Downloaded from the browser// • Rendered in an <iframe>
This example producesthis PDF (whenthis PDF is used for theformPdfBytes
variable,this image is used for themarioImageBytes
variable, andthis image is used for theemblemImageBytes
variable).
See alsoCreating 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()constformPdfBytes= ...constmarioImageBytes= ...constemblemImageBytes= ...// Load a PDF with form fieldsconstpdfDoc=awaitPDFDocument.load(formPdfBytes)// Embed the Mario and emblem imagesconstmarioImage=awaitpdfDoc.embedPng(marioImageBytes)constemblemImage=awaitpdfDoc.embedPng(emblemImageBytes)// Get the form containing all the fieldsconstform=pdfDoc.getForm()// Get all fields in the PDF by their namesconstnameField=form.getTextField('CharacterName 2')constageField=form.getTextField('Age')constheightField=form.getTextField('Height')constweightField=form.getTextField('Weight')consteyesField=form.getTextField('Eyes')constskinField=form.getTextField('Skin')consthairField=form.getTextField('Hair')constalliesField=form.getTextField('Allies')constfactionField=form.getTextField('FactionName')constbackstoryField=form.getTextField('Backstory')consttraitsField=form.getTextField('Feat+Traits')consttreasureField=form.getTextField('Treasure')constcharacterImageField=form.getButton('CHARACTER IMAGE')constfactionImageField=form.getTextField('Faction Symbol Image')// Fill in the basic info fieldsnameField.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 imagecharacterImageField.setImage(marioImage)// Fill in the allies fieldalliesField.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 fieldfactionField.setText(`Mario's Emblem`)// Fill the faction image field with our emblem imagefactionImageField.setImage(emblemImage)// Fill in the backstory fieldbackstoryField.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 fieldtraitsField.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 fieldtreasureField.setText(['• Gold coins','• Treasure chests'].join('\n'))// Serialize the PDFDocument to bytes (a Uint8Array)constpdfBytes=awaitpdfDoc.save()// For example, `pdfBytes` can be:// • Written to a file in Node// • Downloaded from the browser// • Rendered in an <iframe>
This example producesthis PDF (whenthis PDF is used for theformPdfBytes
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()constformPdfBytes= ...// Load a PDF with form fieldsconstpdfDoc=awaitPDFDocument.load(formPdfBytes)// Get the form containing all the fieldsconstform=pdfDoc.getForm()// Fill the form's fieldsform.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 fieldsform.flatten();// Serialize the PDFDocument to bytes (a Uint8Array)constpdfBytes=awaitpdfDoc.save()// For example, `pdfBytes` can be:// • Written to a file in Node// • Downloaded from the browser// • Rendered in an <iframe>
This example producesthis PDF (whenthis PDF is used for thefirstDonorPdfBytes
variable andthis PDF is used for thesecondDonorPdfBytes
variable).
import{PDFDocument}from'pdf-lib'// Create a new PDFDocumentconstpdfDoc=awaitPDFDocument.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()constfirstDonorPdfBytes= ...constsecondDonorPdfBytes= ...// Load a PDFDocument from each of the existing PDFsconstfirstDonorPdfDoc=awaitPDFDocument.load(firstDonorPdfBytes)constsecondDonorPdfDoc=awaitPDFDocument.load(secondDonorPdfBytes)// Copy the 1st page from the first donor document, and// the 743rd page from the second donor documentconst[firstDonorPage]=awaitpdfDoc.copyPages(firstDonorPdfDoc,[0])const[secondDonorPage]=awaitpdfDoc.copyPages(secondDonorPdfDoc,[742])// Add the first copied pagepdfDoc.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)constpdfBytes=awaitpdfDoc.save()// For example, `pdfBytes` can be:// • Written to a file in Node// • Downloaded from the browser// • Rendered in an <iframe>
This example producesthis PDF (whenthis image is used for thejpgImageBytes
variable andthis image is used for thepngImageBytes
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()constjpgImageBytes= ...constpngImageBytes= ...// Create a new PDFDocumentconstpdfDoc=awaitPDFDocument.create()// Embed the JPG image bytes and PNG image bytesconstjpgImage=awaitpdfDoc.embedJpg(jpgImageBytes)constpngImage=awaitpdfDoc.embedPng(pngImageBytes)// Get the width/height of the JPG image scaled down to 25% of its original sizeconstjpgDims=jpgImage.scale(0.25)// Get the width/height of the PNG image scaled down to 50% of its original sizeconstpngDims=pngImage.scale(0.5)// Add a blank page to the documentconstpage=pdfDoc.addPage()// Draw the JPG image in the center of the pagepage.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 imagepage.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)constpdfBytes=awaitpdfDoc.save()// For example, `pdfBytes` can be:// • Written to a file in Node// • Downloaded from the browser// • Rendered in an <iframe>
This example producesthis PDF (whenthis PDF is used for theamericanFlagPdfBytes
variable andthis PDF is used for theusConstitutionPdfBytes
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()constamericanFlagPdfBytes= ...constusConstitutionPdfBytes= ...// Create a new PDFDocumentconstpdfDoc=awaitPDFDocument.create()// Embed the American flag PDF bytesconst[americanFlag]=awaitpdfDoc.embedPdf(americanFlagPdfBytes)// Load the U.S. constitution PDF bytesconstusConstitutionPdf=awaitPDFDocument.load(usConstitutionPdfBytes)// Embed the second page of the constitution and clip the preambleconstpreamble=awaitpdfDoc.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 sizeconstamericanFlagDims=americanFlag.scale(0.3)// Get the width/height of the preamble clipping scaled up to 225% of// its original sizeconstpreambleDims=preamble.scale(2.25)// Add a blank page to the documentconstpage=pdfDoc.addPage()// Draw the American flag image in the center top of the pagepage.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 pagepage.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)constpdfBytes=awaitpdfDoc.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 usingpdfDoc.registerFontkit(...)
before embedding custom fonts.
This example producesthis PDF (whenthis font is used for thefontBytes
variable).
import{PDFDocument,rgb}from'pdf-lib'importfontkitfrom'@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()constfontBytes= ...// Create a new PDFDocumentconstpdfDoc=awaitPDFDocument.create()// Register the `fontkit` instancepdfDoc.registerFontkit(fontkit)// Embed our custom font in the documentconstcustomFont=awaitpdfDoc.embedFont(fontBytes)// Add a blank page to the documentconstpage=pdfDoc.addPage()// Create a string of text and measure its width and height in our custom fontconsttext='This is text in an embedded font!'consttextSize=35consttextWidth=customFont.widthOfTextAtSize(text,textSize)consttextHeight=customFont.heightAtSize(textSize)// Draw the string of text on the pagepage.drawText(text,{x:40,y:450,size:textSize,font:customFont,color:rgb(0,0.53,0.71),})// Draw a box around the string of textpage.drawRectangle({x:40,y:450,width:textWidth,height:textHeight,borderColor:rgb(1,0,0),borderWidth:1.5,})// Serialize the PDFDocument to bytes (a Uint8Array)constpdfBytes=awaitpdfDoc.save()// For example, `pdfBytes` can be:// • Written to a file in Node// • Downloaded from the browser// • Rendered in an <iframe>
This example producesthis PDF (whenthis image is used for thejpgAttachmentBytes
variable andthis PDF is used for thepdfAttachmentBytes
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()constjpgAttachmentBytes= ...constpdfAttachmentBytes= ...// Create a new PDFDocumentconstpdfDoc=awaitPDFDocument.create()// Add the JPG attachmentawaitpdfDoc.attach(jpgAttachmentBytes,'cat_riding_unicorn.jpg',{mimeType:'image/jpeg',description:'Cool cat riding a unicorn! 🦄🐈🕶️',creationDate:newDate('2019/12/01'),modificationDate:newDate('2020/04/19'),})// Add the PDF attachmentawaitpdfDoc.attach(pdfAttachmentBytes,'us_constitution.pdf',{mimeType:'application/pdf',description:'Constitution of the United States 🇺🇸🦅',creationDate:newDate('1787/09/17'),modificationDate:newDate('1992/05/07'),})// Add a page with some textconstpage=pdfDoc.addPage();page.drawText('This PDF has two attachments',{x:135,y:415})// Serialize the PDFDocument to bytes (a Uint8Array)constpdfBytes=awaitpdfDoc.save()// For example, `pdfBytes` can be:// • Written to a file in Node// • Downloaded from the browser// • Rendered in an <iframe>
This example producesthis PDF.
import{PDFDocument,StandardFonts}from'pdf-lib'// Create a new PDFDocumentconstpdfDoc=awaitPDFDocument.create()// Embed the Times Roman fontconsttimesRomanFont=awaitpdfDoc.embedFont(StandardFonts.TimesRoman)// Add a page and draw some text on itconstpage=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(newDate('2018-06-24T01:58:37.228Z'))pdfDoc.setModificationDate(newDate('2019-12-21T07:00:11.000Z'))// Serialize the PDFDocument to bytes (a Uint8Array)constpdfBytes=awaitpdfDoc.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()constexistingPdfBytes= ...// Load a PDFDocument without updating its existing metadataconstpdfDoc=awaitPDFDocument.load(existingPdfBytes,{updateMetadata:false})// Print all available metadata fieldsconsole.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 (whenthis PDF is used for theexistingPdfBytes
variable):
Title: Microsoft Word - Basic Curriculum Vitae example.docAuthor: AdministratorSubject: undefinedCreator: PScript5.dll Version 5.2Keywords: undefinedProducer: Acrobat Distiller 8.1.0 (Windows)Creation Date: 2010-07-29T14:26:00.000ZModification Date: 2010-07-29T14:26:00.000Z
import{PDFDocument,StandardFonts,NonFullScreenPageMode,ReadingDirection,PrintScaling,Duplex,PDFName,}from'pdf-lib'// Create a new PDFDocumentconstpdfDoc=awaitPDFDocument.create()// Embed the Times Roman fontconsttimesRomanFont=awaitpdfDoc.embedFont(StandardFonts.TimesRoman)// Add a page and draw some text on itconstpage=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:constviewerPrefs=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 closedviewerPrefs.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 pageviewerPrefs.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)constpdfBytes=awaitpdfDoc.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()constexistingPdfBytes= ...// Load a PDFDocument without updating its existing metadataconstpdfDoc=awaitPDFDocument.load(existingPdfBytes)constviewerPrefs=pdfDoc.catalog.getOrCreateViewerPreferences()// Print all available viewer preference fieldsconsole.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 (whenthis PDF is used for theexistingPdfBytes
variable):
HideToolbar: trueHideMenubar: trueHideWindowUI: falseFitWindow: trueCenterWindow: trueDisplayDocTitle: trueNonFullScreenPageMode: UseNoneReadingDirection: R2LPrintScaling: NoneDuplex: DuplexFlipLongEdgePickTrayByPDFSize: truePrintPageRange: [ { start: 1, end: 1 }, { start: 3, end: 4 } ]NumCopies: 2
This example producesthis PDF.
import{PDFDocument,rgb}from'pdf-lib'// SVG path for a wavy lineconstsvgPath='M 0,20 L 100,160 Q 130,200 150,120 C 190,-40 200,200 300,150 L 400,90'// Create a new PDFDocumentconstpdfDoc=awaitPDFDocument.create()// Add a blank page to the documentconstpage=pdfDoc.addPage()page.moveTo(100,page.getHeight()-5)// Draw the SVG path as a black linepage.moveDown(25)page.drawSvgPath(svgPath)// Draw the SVG path as a thick green linepage.moveDown(200)page.drawSvgPath(svgPath,{borderColor:rgb(0,1,0),borderWidth:5})// Draw the SVG path and fill it with redpage.moveDown(200)page.drawSvgPath(svgPath,{color:rgb(1,0,0)})// Draw the SVG path at 50% of its original sizepage.moveDown(200)page.drawSvgPath(svgPath,{scale:0.5})// Serialize the PDFDocument to bytes (a Uint8Array)constpdfBytes=awaitpdfDoc.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 newDeno runtime! All of theusage examples work in Deno. The only thing you need to do is change the imports forpdf-lib
and@pdf-lib/fontkit
to use theSkypack CDN, because Deno requires all modules to be referenced via URLs.
See alsoHow to Create and Modify PDF Files in Deno With pdf-lib
Below is thecreate document example modified for Deno:
import{PDFDocument,StandardFonts,rgb,}from'https://cdn.skypack.dev/pdf-lib@^1.11.1?dts';constpdfDoc=awaitPDFDocument.create();consttimesRomanFont=awaitpdfDoc.embedFont(StandardFonts.TimesRoman);constpage=pdfDoc.addPage();const{ width, height}=page.getSize();constfontSize=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),});constpdfBytes=awaitpdfDoc.save();awaitDeno.writeFile('out.pdf',pdfBytes);
If you save this script ascreate-document.ts
, you can execute it using Deno with the following command:
deno run --allow-write create-document.ts
The resultingout.pdf
file will look likethis 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';importfontkitfrom'https://cdn.skypack.dev/@pdf-lib/fontkit@^1.0.0?dts';consturl='https://pdf-lib.js.org/assets/ubuntu/Ubuntu-R.ttf';constfontBytes=awaitfetch(url).then((res)=>res.arrayBuffer());constpdfDoc=awaitPDFDocument.create();pdfDoc.registerFontkit(fontkit);constcustomFont=awaitpdfDoc.embedFont(fontBytes);constpage=pdfDoc.addPage();consttext='This is text in an embedded font!';consttextSize=35;consttextWidth=customFont.widthOfTextAtSize(text,textSize);consttextHeight=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,});constpdfBytes=awaitpdfDoc.save();awaitDeno.writeFile('out.pdf',pdfBytes);
If you save this script ascustom-font.ts
, you can execute it with the following command:
deno run --allow-write --allow-net custom-font.ts
The resultingout.pdf
file will look likethis PDF.
Theusage examples provide code that is brief and to the point, demonstrating the different features ofpdf-lib
. You can find complete working examples in theapps/
directory. These apps are used to do manual testing ofpdf-lib
before every release (in addition to theautomated tests).
There are currently four apps:
node
- containstests forpdf-lib
in Node environments. These tests are a handy reference when trying to save/load PDFs, fonts, or images withpdf-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
- containstests forpdf-lib
in browser environments. These tests are a handy reference when trying to save/load PDFs, fonts, or images withpdf-lib
in a browser environment.rn
- containstests forpdf-lib
in React Native environments. These tests are a handy reference when trying to save/load PDFs, fonts, or images withpdf-lib
in a React Native environment.deno
- containstests forpdf-lib
in Deno environments. These tests are a handy reference when trying to save/load PDFs, fonts, or images withpdf-lib
from the filesystem.
To install the latest stable version:
# With npmnpm install --save pdf-lib# With yarnyarn add pdf-lib
This assumes you're usingnpm oryarn as your package manager.
You can also downloadpdf-lib
as a UMD module fromunpkg orjsDelivr. The UMD builds have been compiled to ES5, so they should workin 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:
- 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
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 globalwindow.PDFLib
variable. This variable contains all of the classes and functions exported bypdf-lib
. For example:
// NPM moduleimport{PDFDocument,rgb}from'pdf-lib';// UMD modulevarPDFDocument=PDFLib.PDFDocument;varrgb=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 usingpdfDoc.registerFontkit(...)
before embedding custom fonts (see thefont 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 likepdf-lib
itself,@pdf-lib/fontkit
can be installed withnpm
/yarn
or as a UMD module.
# With npmnpm install --save @pdf-lib/fontkit# With yarnyarn add @pdf-lib/fontkit
To register thefontkit
instance:
import{PDFDocument}from'pdf-lib'importfontkitfrom'@pdf-lib/fontkit'constpdfDoc=awaitPDFDocument.create()pdfDoc.registerFontkit(fontkit)
The following builds are available:
- https://unpkg.com/@pdf-lib/fontkit/dist/fontkit.umd.js
- https://unpkg.com/@pdf-lib/fontkit/dist/fontkit.umd.min.js
- https://cdn.jsdelivr.net/npm/@pdf-lib/fontkit/dist/fontkit.umd.js
- https://cdn.jsdelivr.net/npm/@pdf-lib/fontkit/dist/fontkit.umd.min.js
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 globalwindow.fontkit
variable. To register thefontkit
instance:
varpdfDoc=awaitPDFLib.PDFDocument.create()pdfDoc.registerFontkit(fontkit)
API documentation is available on the project site athttps://pdf-lib.js.org/docs/api/.
The repo for the project site (and generated documentation files) islocated 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:
- There are 14 standard fonts defined in the PDF specification. They are as follows:Times Roman (normal, bold, and italic),Helvetica (normal, bold, and italic),Courier (normal, bold, and italic),ZapfDingbats (normal), andSymbol (normal). These 14 fonts are guaranteed to be available in PDF readers. As such, you do not need to embed any font data if you wish to use one of these fonts. You can use a standard font like so:
import{PDFDocument,StandardFonts}from'pdf-lib'constpdfDoc=awaitPDFDocument.create()constcourierFont=awaitpdfDoc.embedFont(StandardFonts.Courier)constpage=pdfDoc.addPage()page.drawText('Some boring latin text in the Courier font',{font:courierFont,})
- The standard fonts do not support all characters available in Unicode. The Times Roman, Helvetica, and Courier fonts use WinAnsi encoding (akaWindows-1252). The WinAnsi character set only supports 218 characters in the Latin alphabet. For this reason, many users will find the standard fonts insufficient for their use case. This is unfortunate, but there's nothing that PDF libraries can do to change this. This is a result of the PDF specification and its age. Note that theZapfDingbats andSymbol fonts use their own specialized encodings that support 203 and 194 characters, respectively. However, the characters they support are not useful for most use cases. Seehere for an example of all 14 standard fonts.
- You can use characters outside the Latin alphabet by embedding your own fonts. Embedding your own font requires to you load the font data (from a file or via a network request, for example) and pass it to the
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'importfontkitfrom'@pdf-lib/fontkit'consturl='https://pdf-lib.js.org/assets/ubuntu/Ubuntu-R.ttf'constfontBytes=awaitfetch(url).then((res)=>res.arrayBuffer())constpdfDoc=awaitPDFDocument.create()pdfDoc.registerFontkit(fontkit)constubuntuFont=awaitpdfDoc.embedFont(fontBytes)constpage=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 thesubset
option totrue
. For example:
constfont=awaitpdfDoc.embedFont(fontBytes,{subset:true});
Note that subsetting does not work for all fonts. See#207 (comment) for additional details.
pdf-lib
can create, fill, and read PDF form fields. The following field types are supported:
See theform creation andform filling usage examples for code samples. Tests 1, 14, 15, 16, and 17 in thecomplete 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 (seeFonts 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 thePDFDocument
).
You can use an embedded font when filling form fields as follows:
import{PDFDocument}from'pdf-lib';importfontkitfrom'@pdf-lib/fontkit';// Fetch the PDF with form fieldsconstformUrl='https://pdf-lib.js.org/assets/dod_character.pdf';constformBytes=awaitfetch(formUrl).then((res)=>res.arrayBuffer());// Fetch the Ubuntu fontconstfontUrl='https://pdf-lib.js.org/assets/ubuntu/Ubuntu-R.ttf';constfontBytes=awaitfetch(fontUrl).then((res)=>res.arrayBuffer());// Load the PDF with form fieldsconstpdfDoc=awaitPDFDocument.load(formBytes);// Embed the Ubuntu fontpdfDoc.registerFontkit(fontkit);constubuntuFont=awaitpdfDoc.embedFont(fontBytes);// Get two text fields from the formconstform=pdfDoc.getForm();constnameField=form.getTextField('CharacterName 2');constageField=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 fontform.updateFieldAppearances(ubuntuFont);// Save the PDF with filled form fieldsconstpdfBytes=awaitpdfDoc.save();
Existing form fields can be accessed with the following methods ofPDFForm
:
PDFForm.getButton
PDFForm.getCheckBox
PDFForm.getDropdown
PDFForm.getOptionList
PDFForm.getRadioGroup
PDFForm.getTextField
New form fields can be created with the following methods ofPDFForm
:
PDFForm.createButton
PDFForm.createCheckBox
PDFForm.createDropdown
PDFForm.createOptionList
PDFForm.createRadioGroup
PDFForm.createTextField
Below are some of the most commonly used methods for reading and filling the aforementioned subclasses ofPDFField
:
PDFDropdown.select
PDFDropdown.clear
PDFDropdown.getSelected
PDFDropdown.getOptions
PDFDropdown.addOptions
PDFOptionList.select
PDFOptionList.clear
PDFOptionList.getSelected
PDFOptionList.getOptions
PDFOptionList.addOptions
PDFRadioGroup.select
PDFRadioGroup.clear
PDFRadioGroup.getSelected
PDFRadioGroup.getOptions
PDFRadioGroup.addOptionToPage
PDFTextField.setText
PDFTextField.getText
PDFTextField.setMaxLength
PDFTextField.getMaxLength
PDFTextField.removeMaxLength
pdf-lib
can extract the content of text fields (seePDFTextField.getText
), but itcannot 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 topdf-lib
in the future. See#93,#137,#177,#329, and#380.pdf-lib
can remove and edit the content of text fields (seePDFTextField.setText
), but it doesnot 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 ofpdf-lib
and may be added in the future. See#93,#137,#177,#329, and#380.pdf-lib
doesnot 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 usingPuppeteer.
Discussions is the best place to chat with us, ask questions, and learn more about pdf-lib!
See alsoMAINTAINERSHIP.md#communication andMAINTAINERSHIP.md#discord.
pdf-lib
does not currently support encrypted documents. You should not usepdf-lib
with encrypted documents. However, this is a feature that could be added topdf-lib
. Pleasecreate an issue if you would find this feature helpful!
When an encrypted document is passed toPDFDocument.load(...)
, an error will be thrown:
import{PDFDocument,EncryptedPDFError}from'pdf-lib'constencryptedPdfBytes= ...// Assignment fails. Throws an `EncryptedPDFError`.constpdfDoc=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'constencryptedPdfBytes= ...// Assignment succeeds. Does not throw an error.constpdfDoc=PDFDocument.load(encryptedPdfBytes,{ignoreEncryption:true})
Note thatusing this option does not decrypt the document. This means that any modifications you attempt to make on the returnedPDFDocument
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 topdf-lib
, please take a look at theCONTRIBUTING.md file. It contains information to help you getpdf-lib
setup and running on your machine. (We try to make this as simple and fast as possible! 🚀)
Check outMAINTAINERSHIP.md for details on how this repo is maintained and how we useissues,PRs, anddiscussions.
- labelmake - a library for declarative PDF generation created by @hand-dot
- Möbius Printing helper - a tool created by @shreevatsa
- Extract PDF pages - a tool created by @shreevatsa
- Travel certificate generator - a tool that creates travel certificates for French citizens under quarantine due to COVID-19
- How to use pdf-lib in AWS Lambdas - a tutorial written by Crespo Wang
- Working With PDFs in Node.js Using pdf-lib - a tutorial by Valeri Karpov
- Electron app for resizing PDFs - a tool created by @vegarringdal
- PDF Shelter - online PDF manipulation tools by Lucas Morais
pdfkit
is a PDF generation library for Node and the Browser. This library was immensely helpful as a reference and existence proof when creatingpdf-lib
.pdfkit
's code forfont embedding,PNG embedding, andJPG embedding was especially useful.pdf.js
is a PDF rendering library for the Browser. This library was helpful as a reference when writingpdf-lib
's parser. Some of the code for stream decoding wasported directly to TypeScript for use inpdf-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 forpdf-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 aC++ 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 aroundC++ andJava 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 calledpdf_specification.pdf
in the root directory. This was a copy of thePDF 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 tomaster
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 tomaster
😔.
We removed the file and rewrote the repo's history usingBFG Repo-Cleaner as outlinedhere. 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 ofpdf-lib
, you shouldn't care! Just keep on usingpdf-lib
like normal 😃 ✨!
If you are apdf-lib
developer (meaning you've forkedpdf-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'smaster
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'ssource 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 addedpdf_specification.pdf
). What has changed are the SHAs of those commits.
The simplest way to deal with this fact is to:
- Reclone pdf-lib
- Manually copy any changes you've made from your old clone to the new one
- Use your new clone going forward
- Reopen your unmerged PRs using your new clone
See thisStackOverflow answer for a great, in depth explanation of what a git history rewrite entails.
About
Create and modify PDF documents in any JavaScript environment