Skip to content

thoven87/gotenberg-kit

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