printpdf

repository·master·Indexed 22 days ago

https://github.com/fschutt/printpdf

A Rust library for creating, reading, writing, and rendering PDF documents. It supports advanced typography, graphics, layers, bookmarks, and embedded fonts. The library includes an experimental HTML-to-PDF rendering engine for layouts like reports and books with automatic page-breaking, as well as a WebAssembly (WASM) module for JavaScript integration.

Tokens
27.1K
Snippets
77
Records
88
Agent score
77%

What's inside printpdf

  1. Overview of printpdf

    master
    printpdf is a Rust library designed for creating, reading, writing, and rendering PDF documents. It supports advanced features like layers, bookmarks, link annotations, and embedded fonts. It also provides an experimental HTML-to-PDF rendering engine for basic layouts like reports and books with automatic page-breaking.
  2. Understand PdfResources and shared assets

    master

    The PdfResources structure contains assets that are shared across multiple pages of a document to optimize file size and consistency.

    Serialization Note:

    • Fonts: Parsed fonts are serialized as base64-encoded data URL strings, mapped by a FontId (UUID).
    • XObjects: Images and other XObjects are also serialized as base64-encoded data URL strings, mapped by an XObjectId (UUID).
    interface PdfResources {
        fonts: { [uuid: string]: string };
        xobjects: { [uuid: string]: XObject };
        extgstates: { [uuid: string]: ExtendedGraphicsState };
        layers: { [uuid: string]: Layer };
    }
  3. Understand the PdfDocument structure

    master

    The PdfDocument interface is the primary data structure representing a parsed PDF document in the printpdf.js API. It aggregates metadata, shared resources, bookmarks, and the individual pages that make up the document.

    interface PdfDocument {
        metadata: PdfMetadata;
        resources: PdfResources;
        bookmarks: { [uuid: string]: PageAnnotation };
        pages: PdfPage[];
    }
  4. Understand the printpdf XML structure

    master

    The printpdf XML syntax uses a structure similar to HTML to define PDF documents. The <html title="..."> node is the root, containing a <head> for metadata, styles, and recurring elements, and a <body> for the main content.

    Key Structural Elements

    • <head>: Contains the <style> block, <header>, and <footer>.
    • <header>: Defines content to be rendered at the top of pages. Use the exclude-pages attribute to skip rendering on specific pages (e.g., <header exclude-pages="1"> skips the first page).
    • <footer>: Defines content to be rendered at the bottom of pages.
    • <style>: Used for global CSS rules. Note: Styles defined here are applied after inline styles to allow overriding preconfigured components.
    • <body>: Contains the document content. The <body> node automatically handles page breaks based on content size.

    Layout and Sizing

    • Page Dimensions: 100% width or height corresponds to the actual page width or height.
    • Image Scaling: In the PDF output, 1px is equivalent to 1mm.
    • Flexbox Layout: The layout system behaves similarly to CSS Flexbox. Elements are first expanded to their minimum size, then space is distributed to elements with flex-grow > 0 up to their max-width or max-height limits.
    <html title="Document title">
        <head>
            <header exclude-pages="1">
                <p style="color:red">Header content</p>
            </header>
            <footer style="color:black">
                <p>Footer content</p>
            </footer>
            <style>
                * { color: red; }
            </style>
        </head>
        <body style="padding:10mm">
            <div style="width:100%; height:100%;">
                <h1>Title Page</h1>
            </div>
            <div style="display:flex; flex-grow:1;">
                <!-- Flexbox content -->
            </div>
        </body>
    </html>
  5. Understand XObject types and structures

    master

    In printpdf.js, an XObject is an external object referenced outside the main PDF content stream. It is a tagged enum that can take one of three forms:

    1. image: Contains Base64-encoded image data.
    2. form: A reusable content stream (Note: this is a PDF Form XObject, not a PDF interactive form).
    3. external: An external stream of graphics operations.

    Use XObject when you need to define reusable graphics or embedded images that are called via the /Do operator.

    type XObject =
        | { type: "image"; data: string }
        | { type: "form"; data: FormXObject }
        | { type: "external"; data: ExternalXObject };
  6. Understand PdfMetadata and PdfDocumentInfo

    master

    Metadata in printpdf.js is managed via PdfMetadata, which synchronizes standard document information (PdfDocumentInfo) and XMP metadata.

    Note on Data Types: While the underlying Rust implementation uses specific unit types, the JSON API serializes dates as ISO format strings and the PDF conformance as a kebab-case string.

    interface PdfMetadata {
        info: PdfDocumentInfo;
        xmp: XmpMetadata | null;
    }
    
    interface PdfDocumentInfo {
        trapped: boolean;
        version: number;
        creationDate: string;
        modificationDate: string;
        metadataDate: string;
        conformance: string;
        documentTitle: string;
        author: string;
        creator: string;
        producer: string;
        keywords: string[];
        subject: string;
        identifier: string;
    }
  7. Understand the PdfPage structure

    master

    A PdfPage represents a single page within a PDF. It defines the physical and clipping boundaries of the page using boxes (mediaBox, trimBox, cropBox) measured in points (pt), and contains a list of operations (ops) used to render the page content.

    interface PdfPage {
        mediaBox: Rect;
        trimBox: Rect;
        cropBox: Rect;
        ops: Op[];
    }
  8. Understand the printpdf.js WASM API response format

    master

    All functions in the WASM API take a stringified JSON object as input and return a stringified JSON object as output. When parsed, the output follows this structure:

    {
      status: number,   // 0 = success, non-zero = error
      data: T | string  // The actual data (of type T) or an error string
    }

    Serialization Rules:

    • Enums: Rust enums are converted to kebab-case. Tagged enums use type for the variant and data for the payload (e.g., MyEnum::VariantType { data } becomes { type: "variant-type", data: ... }).
    • Structs: Rust struct fields are converted to camelCase (e.g., page_height becomes pageHeight).
  9. When to choose printpdf

    master

    Select printpdf if your project requires:

    • Both reading and writing capabilities in a single library.
    • Strong graphics and SVG support.
    • Font embedding with Unicode support.
    • Direct control over PDF structures combined with convenience functions.
    • HTML conversion capabilities (currently experimental).
  10. Understand PDF page operations (Op type)

    master

    In printpdf.js, a PDF page is defined by a stream of operations of type Op. This is a tagged enum where each variant represents a specific PDF command (e.g., drawing, text, color settings, or state management). Developers interacting with the low-level data structures must use these operation types to construct or manipulate page content.

    Common operation categories include:

    • Graphics State: save-graphics-state, restore-graphics-state, load-graphics-state.
    • Text Operations: start-text-section, end-text-section, set-font, show-text, set-text-cursor.
    • Drawing: draw-line, draw-rectangle, draw-polygon.
    • Styling: set-fill-color, set-outline-color, set-line-dash-pattern, set-text-rendering-mode.
    • Layers & Content: begin-layer, end-layer, begin-marked-content.
    type Op =
        | { type: "marker"; data: Marker }
        | { type: "set-color-space-stroke"; data: SetColorSpace }
        | { type: "set-color-space-fill"; data: SetColorSpace }
        | { type: "begin-layer"; data: BeginLayer }
        | { type: "end-layer"; data: EndLayer }
        | { type: "save-graphics-state" }
        | { type: "restore-graphics-state" }
        | { type: "load-graphics-state"; data: LoadGraphicsState }
        | { type: "start-text-section" }
        | { type: "end-text-section" }
        | { type: "set-font"; data: SetFont }
        | { type: "show-text"; data: ShowText }
        | { type: "add-line-break" }
        | { type: "set-line-height"; data: SetLineHeight }
        | { type: "set-word-spacing"; data: SetWordSpacing }
        | { type: "set-text-cursor"; data: SetTextCursor }
        | { type: "set-fill-color"; data: SetFillColor }
        | { type: "set-outline-color"; data: SetOutlineColor }
        | { type: "set-outline-thickness"; data: SetOutlineThickness }
        | { type: "set-line-dash-pattern"; data: SetLineDashPattern }
        | { type: "set-line-join-style"; data: SetLineJoinStyle }
        | { type: "set-line-cap-style"; data: SetLineCapStyle }
        | { type: "set-miter-limit"; data: SetMiterLimit }
        | { type: "set-text-rendering-mode"; data: SetTextRenderingMode }
        | { type: "set-character-spacing"; data: SetCharacterSpacing }
        | { type: "set-line-offset"; data: SetLineOffset }
        | { type: "draw-line"; data: DrawLine }
        | { type: "draw-rectangle"; data: DrawRectangle }
        | { type: "draw-polygon"; data: DrawPolygon }
        | { type: "set-transformation-matrix"; data: SetTransformationMatrix }
        | { type: "set-text-matrix"; data: SetTextMatrix }
        | { type: "link-annotation"; data: LinkAnnotationOp }
        | { type: "use-xobject"; data: UseXobject }
        | { type: "move-text-cursor-and-set-leading"; data: MoveTextCursorAndSetLeading }
        | { type: "set-rendering-intent"; data: SetRenderingIntent }
        | { type: "set-horizontal-scaling"; data: SetHorizontalScaling }
        | { type: "begin-inline-image" }
        | { type: "begin-inline-image-data" }
        | { type: "end-inline-image" }
        | { type: "begin-marked-content"; data: BeginMarkedContent }
        | { type: "begin-marked-content-with-properties"; data: BeginMarkedContentWithProperties }
        | { type: "begin-optional-content"; data: BeginOptionalContent }
        | { type: "define-marked-content-point"; data: DefineMarkedContentPoint }
        | { type: "end-marked-content" }
        | { type: "end-marked-content-with-properties" }
        | { type: "end-optional-content" }
        | { type: "begin-compatibility-section" }
        | { type: "end-compatibility-section" }
        | { type: "move-to-next-line-show-text"; data: MoveToNextLineShowText }
        | { type: "set-spacing-move-and-show-text"; data: SetSpacingMoveAndShowText }
        | { type: "paint-shading"; data: PaintShading }
        | { type: "unknown"; data: Unknown };
  11. Initialize the printpdf.js WASM module

    master

    Before calling any PDF functions, you must initialize the WebAssembly module using the init function. Ensure that printpdf_bg.wasm is located in the same directory as printpdf.js.

    import init, {
      Pdf_HtmlToDocument,
      Pdf_BytesToDocument,
      Pdf_ResourcesForPage,
      Pdf_PageToSvg,
      Pdf_DocumentToBytes,
    } from './pkg/printpdf.js';
    
    async function main() {
      // Initialize the WASM
      await init();
    
      // Now we can safely call our PDF functions
      // ...
    }
    
    main().catch(console.error);
  12. Create a basic PDF document

    master

    To create a minimal PDF, initialize a PdfDocument, define a PdfPage with specific dimensions (using units like Mm), and provide a list of Op (operations). Finally, call .save() with PdfSaveOptions to generate the PDF bytes.

    use printpdf::*;
    
    fn main() {
        let mut doc = PdfDocument::new("My first PDF");
        let page1_contents = vec![Op::Marker { id: "debugging-marker".to_string() }];
        let page1 = PdfPage::new(Mm(210.0), Mm(297.0), page1_contents);
        let mut warnings = Vec::new();
        let pdf_bytes: Vec<u8> = doc
            .with_pages(vec![page1])
            .save(&PdfSaveOptions::default(), &mut warnings);
    }