pdf-lib (Cantoo Fork)

repository·master·Indexed 43 days ago

https://github.com/cantoo-scribe/pdf-lib

A JavaScript library for creating and modifying PDF files in Node, Browser, Deno, and React Native. This Cantoo fork of version 2.11.0 adds advanced features including full SVG support, incremental updates, XFA support, PDF/A-1/2/3 conversion, Factur-X/ZUGFeRD e-invoicing standards, and AES-256 encryption.

Tokens
35.8K
Snippets
120
Records
161
Agent score
56%

What's inside pdf-lib

  1. Overview of @cantoo/upng

    master

    @cantoo/upng is a PNG and APNG encoder/decoder. It is a modular TypeScript port of the photopea/UPNG.js library, originally used by @pdf-lib/upng.

    Key improvements over the original include:

    • Performance: Uses fflate for compression instead of pako.
    • Modularity: Provided as modular TypeScript modules (decode, encode, quantize, etc.) rather than a monolithic file.
    • Packaging: Can be used as a standalone package or accessed via @cantoo/pdf-lib/upng if you are using the @cantoo/pdf-lib package.
  2. Key features and benefits of @cantoo/pdf-lib

    master

    Compared to the original pdf-lib, @cantoo/pdf-lib provides several advanced capabilities:

    Drawing & Content

    • Full SVG drawing: Use page.drawSvg or embedSvg for shapes, text, transforms, and CSS colors (via culori).
    • Content Extraction: Use PDFPage.extractContents() to pull typed assets (text, images, SVG) from page streams.
    • Text Styling: Support for characterSpacing in drawText.

    Document Manipulation & Saving

    • Incremental Updates: Use forIncrementalUpdate, save({ useObjectStreams... }), and commit() to append changes without rewriting the entire file.
    • Robustness: Preserves PDF headers on save and can recover truncated PDFs with broken trailer objects.

    Forms, Scripts & Standards

    • XFA Support: Helpers for preserveXFA, reading/modifying XFA JavaScript, and signature-field discovery.
    • JavaScript: Extract and work with document-level JavaScript.
    • Flattening: PDFForm.flatten() also flattens orphaned widget annotations.
    • Attachments: Add, extract, and detach embedded files.
    • Standards: convertToPDFA() for PDF/A-1/2/3 and embedFacturX() for Factur-X / ZUGFeRD hybrid invoices.

    Security & Fonts

    • Encryption: PDFDocument.encrypt() supports AES-256 (default), AES-128, and RC4 (requires allowWeakCryptography).
    • Fonts: First-class @cantoo/fontkit support and postscriptName for selecting faces from .ttc or .dfont collections.
    • Layers: Manage Optional Content Groups via getOptionalContentGroups() and setOptionalContentGroupVisibility().
  3. How incremental document modification works

    master

    Incremental modification allows you to update a PDF without rewriting the entire file structure. This is critical for tasks like digital signatures, where modifying the original byte structure would invalidate existing signatures.

    There are two ways to handle this:

    1. Automatic Incremental Update: Load the document with { forIncrementalUpdate: true }. When you call save(), pdf-lib automatically generates an incremental update.
    2. Manual Incremental Update: Use takeSnapshot() to capture the current state, use snapshot.markRefForSave(page.ref) to specify which objects changed, and then use saveIncremental(snapshot) to generate only the bytes for the changes. You can then concatenate these bytes to the original file.

    To force a full rewrite of a document that was loaded for incremental updates, use save({ rewrite: true }).

  4. Understand pdf-lib limitations

    master

    Before using pdf-lib, be aware of the following functional limitations:

    • Text Extraction/Editing: You can extract or edit text within form fields (using PDFTextField.getText or PDFTextField.setText), but you cannot extract or edit plain text that exists on a page outside of a form field.
    • HTML/CSS Support: pdf-lib does not support HTML or CSS for adding or embedding content. For HTML-to-PDF workflows, consider using Puppeteer.
    • PDF/A and Factur-X Compliance: Methods like convertToPDFA() and embedFacturX() add the necessary structural pieces (XMP/attachments), but they do not rewrite page content (e.g., fonts, transparency, or JavaScript) to ensure full compliance. Always validate the output with dedicated tools like veraPDF or Mustang.
  5. Use the simplified drawing API (v1.0.0+)

    master

    In older versions, drawing required manually creating and registering content streams. In v1.0.0 and later, you can call drawing methods directly on a PDFPage instance, such as drawText, drawImage, drawRectangle, or drawSvgPath.

    page.drawText('Creating PDFs in JavaScript is awesome!', {
      x: 50,
      y: 450,
      size: 15,
      font: timesRomanFont,
      color: rgb(0, 0.53, 0.71),
    });
  6. Migrate from v0.x.x to v1.0.0+

    master

    Version v1.0.0 introduced several breaking changes, primarily making many API methods asynchronous. Key changes include:

    • PDFDocumentFactory is renamed to PDFDocument.
    • PDFDocument.create and PDFDocument.load are now asynchronous and must be awaited.
    • pdfDoc.createPage is replaced by pdfDoc.addPage.
    • PDFDocumentWriter.saveToBytes(pdfDoc) is replaced by pdfDoc.save().
    • pdfDoc.embedJPG and pdfDoc.embedPNG are now case-sensitive: embedJpg and embedPng.
    • getMaybe is replaced by get. Note that get no longer automatically converts strings to PDFName objects; you must wrap name strings in PDFName.of(...).
  7. Migrate SVG drawing from v1.x.x to v2.0.0+

    master

    In v2.0.0 and above, the drawSvg method on a PDFPage became asynchronous. If your SVG does not contain images, you must now await the call. If your SVG contains images, you must first embed the SVG using pdfDoc.embedSvg(svg) before drawing it.

    ### If your svg doesn't contain images
    ```js
    const svg = `<svg width="100" height="100"><rect width="100" height="100"/></svg>`;
    await pdfPage.drawSvg(svg);

    If your svg might contain an image

    const svg = '<svg><image href="data:image/png;base64,..."/></svg>';
    const pdfSvg = await pdfDoc.embedSvg(svg);
    pdfPage.drawSvg(pdfSvg);
  8. Set up the local development environment

    master

    To develop with pdf-lib, ensure you have the following prerequisites installed:

    • Node.js: version v9.0.0 or greater.
    • Yarn: version v1.12.0 or greater.
    • Git: version 2.17.2 or greater.

    Follow these steps to clone and prepare the project:

    1. Clone the repository:
      git clone https://github.com/Hopding/pdf-lib.git
      cd pdf-lib
    2. Install dependencies using Yarn:
      yarn install
    git clone https://github.com/Hopding/pdf-lib.git
    cd pdf-lib
    yarn install
  9. Embed custom fonts and measure text

    master

    To use custom fonts, you must register a fontkit implementation (such as @cantoo/fontkit) using pdfDoc.registerFontkit(fontkit). Once registered, you can use pdfDoc.embedFont(fontBytes) to embed the font.

    After embedding, you can use the font object to measure text dimensions:

    • font.widthOfTextAtSize(text, size): Returns the width of the text.
    • font.heightAtSize(size): Returns the height of the text at the given size.

    This is useful for calculating bounding boxes for drawing rectangles around text.

    import { PDFDocument, rgb } from 'pdf-lib'
    import fontkit from '@cantoo/fontkit'
    
    const fontBytes = ...
    
    const pdfDoc = await PDFDocument.create()
    
    // Register fontkit
    pdfDoc.registerFontkit(fontkit)
    
    // Embed custom font
    const customFont = await pdfDoc.embedFont(fontBytes)
    
    const page = pdfDoc.addPage()
    const text = 'This is text in an embedded font!'
    const textSize = 35
    
    // Measure text
    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),
    })
    
    // Draw a box around the text using measured dimensions
    page.drawRectangle({
      x: 40,
      y: 450,
      width: textWidth,
      height: textHeight,
      borderColor: rgb(1, 0, 0),
      borderWidth: 1.5,
    })
    
    const pdfBytes = await pdfDoc.save()