Skip to content

Repository files navigation

GotenbergKit

CI

A modern Swift SDK for Gotenberg that provides a type-safe, async/await interface for PDF generation and document conversion. Transform HTML, Markdown, URLs, and office documents into PDFs with intelligent retry logic and comprehensive error handling.

Features

  • 🚀 Async/await support with modern Swift concurrency
  • 🛡️ Type-safe APIs with comprehensive error handling
  • 🔄 Intelligent retry logic for transient failures
  • 📄 Multiple input formats (HTML, Markdown, URLs, Office docs)
  • 🖼️ Screenshot capture from web pages
  • 📋 PDF manipulation (merge, split, metadata)
  • 🔐 PDF encryption (during conversion + dedicated endpoint)
  • 🔑 Authentication support (Basic Auth + custom headers)
  • 📱 Cross-platform (iOS, macOS, Linux, Windows)

Quick Start

Installation

Add GotenbergKit to your Swift package:

dependencies: [
    .package(url: "https://github.com/thoven87/gotenberg-kit.git", from: "0.1.0")
]

Setup Gotenberg Server

Run Gotenberg using Docker:

docker run --rm -p 3000:3000 gotenberg/gotenberg:8

Basic Usage

import GotenbergKit

let client = GotenbergClient(baseURL: URL(string: "http://localhost:3000")!)

// Convert HTML to PDF
let pdfData = try await client.convertHTMLToPDF(
    htmlContent: "<h1>Hello World</h1>",
    options: ConversionOptions()
)

// Convert URL to PDF
let urlResponse = try await client.convertURLToPDF(
    url: URL(string: "https://apple.com")!,
    options: ConversionOptions()
)

// Save the PDF
try await client.writeToFile(urlResponse, at: "/path/to/output.pdf")

Documentation

HTML to PDF Conversion

Convert HTML content with optional CSS and JavaScript:

let options = ConversionOptions(
    paperWidth: 8.27,
    paperHeight: 11.7,
    marginTop: 1.0,
    marginBottom: 1.0,
    marginLeft: 1.0,
    marginRight: 1.0,
    printBackground: true,
    landscape: false,
    scale: 1.0
)

let response = try await client.convertHTMLToPDF(
    htmlContent: """
    <!DOCTYPE html>
    <html>
    <head>
        <style>body { font-family: Arial, sans-serif; }</style>
    </head>
    <body>
        <h1>Professional Document</h1>
        <p>Generated with GotenbergKit</p>
    </body>
    </html>
    """,
    options: options
)

URL to PDF Conversion

Convert web pages to PDF with customizable options:

let response = try await client.convertURLToPDF(
    url: URL(string: "https://swift.org")!,
    options: ConversionOptions(
        waitDelay: 2000, // Wait 2 seconds for page load
        printBackground: true,
        scale: 0.8
    )
)

Screenshot Capture

Capture screenshots of web pages:

let screenshotOptions = ScreenshotOptions(
    width: 1920,
    height: 1080,
    clip: true,
    format: .png,
    quality: 100
)

let screenshot = try await client.captureScreenshot(
    url: URL(string: "https://github.com")!,
    options: screenshotOptions
)

Office Document Conversion

Convert office documents (Word, Excel, PowerPoint) to PDF:

let documents = [
    "document.docx": try Data(contentsOf: docxURL),
    "spreadsheet.xlsx": try Data(contentsOf: xlsxURL)
]

let response = try await client.convertWithLibreOffice(
    documents: documents,
    options: LibreOfficeConversionOptions(merge: true)
)

PDF Manipulation

Merge PDFs

let response = try await client.mergeWithPDFEngines(
    documents: [
        "file1.pdf": pdfData1,
        "file2.pdf": pdfData2
    ],
    options: PDFEngineOptions()
)

Split PDFs

let response = try await client.splitPDF(
    documents: ["document.pdf": pdfData],
    options: SplitPDFOptions(
        splitMode: .pages,
        splitSpan: "1-5",
        splitUnify: false
    )
)

PDF Metadata

Read and write PDF metadata:

// Read metadata
let metadata = try await client.readPDFMetadata(
    documents: ["document.pdf": pdfData]
)

// Write metadata
let response = try await client.writePDFMetadata(
    documents: ["document.pdf": pdfData],
    metadata: [
        "Title": "My Document",
        "Author": "GotenbergKit",
        "Subject": "PDF Generation",
        "Keywords": ["swift", "pdf", "gotenberg"]
    ]
)

PDF Encryption

GotenbergKit provides two ways to encrypt PDFs with password protection:

1. Encrypt During Conversion

Add password protection while converting documents to PDF:

// Encrypt HTML to PDF with both user and owner passwords
let options = ConversionOptions(
    userPassword: "user123",     // Password for opening/viewing the PDF
    ownerPassword: "owner456"    // Password for full access/editing
)

let response = try await client.convertHTMLToPDF(
    htmlContent: "<h1>Confidential Document</h1>",
    options: options
)

// Encrypt office documents
let libreOptions = LibreOfficeConversionOptions(
    password: "source_password",      // Password for opening source file (if encrypted)
    userPassword: "view_password",    // Password for viewing output PDF
    ownerPassword: "edit_password"    // Password for editing output PDF
)

let response = try await client.convertWithLibreOffice(
    documents: ["confidential.docx": docData],
    options: libreOptions
)

// Encrypt when processing existing PDFs
let pdfOptions = PDFEngineOptions(
    userPassword: "reader_access",
    ownerPassword: "full_access"
)

let encryptedPDF = try await client.mergeWithPDFEngines(
    documents: ["file1.pdf": data1, "file2.pdf": data2],
    options: pdfOptions
)

2. Encrypt Existing PDFs

Use the dedicated encryption endpoint to add password protection to existing PDF files with full metadata override support:

// Basic encryption with passwords only
let encryptedResponse = try await client.encryptPDFs(
    documents: [
        "document1.pdf": try Data(contentsOf: pdf1URL),
        "document2.pdf": try Data(contentsOf: pdf2URL)
    ],
    options: PDFEngineOptions(
        userPassword: "viewer_password",
        ownerPassword: "admin_password"  // Optional
    )
)

// Encrypt with custom metadata override
let customMetadata = Metadata(
    author: "Secure Author",
    copyright: "Company Confidential",
    creator: "GotenbergKit",
    marked: true,
    producer: "Swift PDF Processor",
    subject: "Encrypted Document",
    title: "Confidential Report"
)

let encryptedWithMetadata = try await client.encryptPDFs(
    documents: ["report.pdf": reportData],
    options: PDFEngineOptions(
        metadata: customMetadata,        // Override metadata during encryption
        userPassword: "user123",
        ownerPassword: "owner456",
        flatten: true,                   // Additional PDF processing options
        pdfua: true
    )
)

// Encrypt PDFs from URLs
let pdfURLs = [
    DownloadFrom(url: "https://example.com/file1.pdf"),
    DownloadFrom(url: "https://example.com/file2.pdf")
]

let encryptedFromURLs = try await client.encryptPDFs(
    urls: pdfURLs,
    options: PDFEngineOptions(
        userPassword: "required_password"
    )
)

Key Features:

  • Password Protection: User password (required) and owner password (optional)
  • Metadata Override: Set custom metadata during encryption process
  • PDF Processing: Support for flattening, PDF/UA compliance, and format options
  • Flexible Input: Encrypt from file data or URLs
  • Consistent API: Uses PDFEngineOptions like other PDF operations

Password Types:

  • User Password: Required to open and view the PDF (required for encryption)
  • Owner Password: Required for full access (editing, copying, printing) (optional)

PDF File Embedding

GotenbergKit supports embedding files within PDFs in two ways:

  1. During Conversion: Embed files while generating PDFs from HTML, Markdown, or office documents
  2. Post-Processing: Use the dedicated embed route to add files to existing PDFs

This enables compliance with standards like ZUGFeRD/Factur-X that require XML invoices and other attachments to be embedded in the PDF.

1. Embed Files During Conversion

// Create invoice XML for ZUGFeRD/Factur-X compliance
let invoiceXML = """
<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100">
    <ExchangedDocument>
        <ID>INV-12345</ID>
        <TypeCode>380</TypeCode>
    </ExchangedDocument>
</Invoice>
""".data(using: .utf8)!

let metadataJSON = """
{
    "invoice_number": "INV-12345",
    "amount": 1000.00,
    "currency": "USD"
}
""".data(using: .utf8)!

// Embed files during HTML to PDF conversion
let options = ChromiumOptions(
    embeds: [
        "invoice.xml": invoiceXML,
        "metadata.json": metadataJSON,
        "logo.png": try Data(contentsOf: logoURL)
    ]
)

let response = try await client.convert(
    html: htmlContent.data(using: .utf8)!,
    options: options
)

// Embed files during LibreOffice conversion
let libreOptions = LibreOfficeConversionOptions(
    embeds: [
        "factur-x.xml": facturXData,
        "additional-info.pdf": additionalPdfData
    ]
)

let response = try await client.convertWithLibreOffice(
    documents: ["invoice.docx": docData],
    options: libreOptions
)

2. Embed Files During PDF Processing

// Embed files when encrypting existing PDFs
let pdfOptions = PDFEngineOptions(
    userPassword: "password123",
    embeds: [
        "invoice.xml": invoiceXMLData,
        "receipt.pdf": receiptPdfData
    ]
)

let encryptedResponse = try await client.encryptPDFs(
    documents: ["document.pdf": pdfData],
    options: pdfOptions
)

// Embed files when merging PDFs
let mergeOptions = PDFEngineOptions(
    embeds: [
        "source-data.xml": xmlData,
        "attachments.zip": zipData
    ]
)

let mergedResponse = try await client.mergeWithPDFEngines(
    documents: ["doc1.pdf": pdf1Data, "doc2.pdf": pdf2Data],
    options: mergeOptions
)

3. Dedicated Embed Route

For adding files to existing PDFs, use the dedicated embedFiles method that utilizes Gotenberg's /forms/pdfengines/embed route:

// Create files to embed
let invoiceXML = """
<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100">
    <ExchangedDocument>
        <ID>INV-2025-001</ID>
        <TypeCode>380</TypeCode>
    </ExchangedDocument>
</Invoice>
""".data(using: .utf8)!

let metadataJSON = """
{
    "invoice_id": "INV-2025-001",
    "amount": 1500.00,
    "currency": "USD"
}
""".data(using: .utf8)!

// Embed files into existing PDFs
let embedOptions = PDFEngineOptions(
    metadata: Metadata(
        author: "Invoice System",
        title: "Invoice with Embedded Data",
        subject: "ZUGFeRD Compliant Invoice"
    ),
    embeds: [
        "factur-x.xml": invoiceXML,
        "invoice-metadata.json": metadataJSON
    ]
)

let embeddedResponse = try await client.embedFiles(
    documents: ["invoice.pdf": existingPdfData],
    options: embedOptions
)

// Embed files from URLs
let embeddedFromUrls = try await client.embedFiles(
    urls: [DownloadFrom(url: "https://example.com/document.pdf")],
    options: embedOptions
)

Key Features:

  • Multiple Files: Embed multiple files of different types (XML, JSON, PDF, images, etc.)
  • Standards Compliance: Perfect for ZUGFeRD/Factur-X invoice standards
  • Two Approaches: Embed during conversion or post-process existing PDFs
  • Flexible Input: Support for file data and URLs
  • Metadata Control: Override PDF metadata during embedding
  • Preserved Attachments: Embedded files can be extracted by PDF readers that support attachments

Use Cases:

  • Electronic Invoicing: ZUGFeRD/Factur-X compliant invoices with embedded XML
  • Legal Documents: Contracts with embedded supporting documents
  • Document Archival: Add source files to final PDFs
  • Compliance: Meet regulatory requirements for embedded attachments
  • Post-Processing: Add files to existing PDF workflows

Authentication

Basic Authentication

let client = GotenbergClient(
    baseURL: URL(string: "http://localhost:3000")!,
    username: "gotenberg",
    password: "secret"
)

Custom Headers

let response = try await client.convertHTMLToPDF(
    htmlContent: "<h1>Authenticated Request</h1>",
    options: ConversionOptions(),
    customHeaders: [
        "Authorization": "Bearer \(token)",
        "X-Request-ID": "unique-id"
    ]
)

Error Handling

GotenbergKit provides comprehensive error handling with intelligent retry logic:

do {
    let response = try await client.convertHTMLToPDF(
        htmlContent: html,
        options: options
    )
} catch GotenbergError.apiError(let statusCode, let message) {
    switch statusCode {
    case 400:
        print("Bad request: \(message)")
    case 409:
        print("Resource conflict (won't retry): \(message)")
    case 500:
        print("Server error (may have been retried): \(message)")
    default:
        print("API error \(statusCode): \(message)")
    }
} catch GotenbergError.networkError(let description) {
    print("Network error: \(description)")
} catch {
    print("Unexpected error: \(error)")
}

Configuration Options

Retry Configuration

let client = GotenbergClient(
    baseURL: URL(string: "http://localhost:3000")!,
    maxRetries: 3, // Default: 3 retries
    logger: Logger(label: "gotenberg-client")
)

Timeout Configuration

// Per-request timeout
let response = try await client.convertHTMLToPDF(
    htmlContent: html,
    options: options,
    timeoutSeconds: 30 // 30 seconds timeout
)

Encryption Best Practices

// Use strong, unique passwords for conversion
let options = ConversionOptions(
    userPassword: generateSecurePassword(length: 12),
    ownerPassword: generateSecurePassword(length: 16)
)

// For dedicated encryption endpoint with metadata control
let encryptionOptions = PDFEngineOptions(
    metadata: Metadata(
        author: "Document Owner",
        copyright: "Confidential",
        creator: "Secure App",
        marked: true,
        subject: "Encrypted Content",
        title: "Protected Document"
    ),
    userPassword: generateSecurePassword(length: 12),
    ownerPassword: generateSecurePassword(length: 16),
    flatten: true  // Prevent form modifications
)

// User password only (allows viewing)
let viewOnlyOptions = PDFEngineOptions(
    userPassword: "view_password"
)

// Both passwords for maximum control
let secureOptions = PDFEngineOptions(
    userPassword: "user_access",    // Required to open
    ownerPassword: "admin_access"   // Full permissions
)

// Apply encryption to existing PDFs
let encrypted = try await client.encryptPDFs(
    documents: ["sensitive.pdf": pdfData],
    options: encryptionOptions
)

Advanced Features

Batch Processing

Process multiple documents concurrently:

let urls = [
    URL(string: "https://apple.com")!,
    URL(string: "https://swift.org")!,
    URL(string: "https://github.com")!
]

await withTaskGroup(of: Void.self) { group in
    for url in urls {
        group.addTask {
            do {
                let response = try await client.convertURLToPDF(
                    url: url,
                    options: ConversionOptions()
                )
                let filename = "\(url.host ?? "unknown").pdf"
                try await client.writeToFile(response, at: filename)
                print("✅ Generated: \(filename)")
            } catch {
                print("❌ Failed to convert \(url): \(error)")
            }
        }
    }
}

Custom Paper Sizes

let options = ConversionOptions(
    paperWidth: 21.0,  // A4 width in cm
    paperHeight: 29.7, // A4 height in cm
    marginTop: 2.54,   // 1 inch margins
    marginBottom: 2.54,
    marginLeft: 2.54,
    marginRight: 2.54
)

Requirements

  • Swift 6.1+
  • Gotenberg server instance

Contributing

We welcome contributions! Please see our Contributing Guidelines for details.

License

GotenbergKit is released under the MIT License. See LICENSE for details.

Related Projects

Releases

Packages

Used by

Contributors

Languages