Comprehensive guide to configuring HtmlToPdf for your specific needs.
HtmlToPdf uses a flexible configuration system that follows progressive disclosure - start simple with sensible defaults, then customize as needed.
Works out of the box with optimal settings:
@Dependency(\.pdf) var pdf
try await pdf.render(html: html, to: fileURL)
// Uses: A4 paper, standard margins, continuous mode, automatic concurrencyQuick configuration for common scenarios:
// Platform-optimized settings
$0.pdf.render.configuration = .platformOptimized
// US Letter paper
$0.pdf.render.configuration = .letter
// Print-ready multi-page documents
$0.pdf.render.configuration = .multiPage
// Fast continuous mode
$0.pdf.render.configuration = .continuous
// Smart auto-detection
$0.pdf.render.configuration = .smart
// Large batch processing
$0.pdf.render.configuration = .largeBatchFine-tune every aspect:
try await withDependencies {
$0.pdf.render.configuration = PDF.Configuration(
paperSize: .letter,
margins: .wide,
baseURL: URL(string: "https://example.com"),
paginationMode: .paginated,
concurrency: 16,
documentTimeout: .seconds(30),
batchTimeout: .seconds(3600),
webViewAcquisitionTimeout: .seconds(300),
createDirectories: true,
namingStrategy: .sequential
)
} operation: {
@Dependency(\.pdf) var pdf
try await pdf.render(html: html, to: fileURL)
}Standard paper sizes are provided as static properties:
// ISO 216 sizes
$0.pdf.render.configuration.paperSize = .a3 // 297 × 420 mm
$0.pdf.render.configuration.paperSize = .a4 // 210 × 297 mm (default)
$0.pdf.render.configuration.paperSize = .a5 // 148 × 210 mm
// US sizes
$0.pdf.render.configuration.paperSize = .letter // 8.5 × 11 inches
$0.pdf.render.configuration.paperSize = .legal // 8.5 × 14 inches
$0.pdf.render.configuration.paperSize = .tabloid // 11 × 17 inches
// Landscape orientation
$0.pdf.render.configuration.paperSize = .a4.landscape
$0.pdf.render.configuration.paperSize = .letter.landscape
// Custom size (in points: 1 point = 1/72 inch)
$0.pdf.render.configuration.paperSize = CGSize(width: 600, height: 800)Predefined margin presets:
$0.pdf.render.configuration.margins = .none // 0 inches
$0.pdf.render.configuration.margins = .minimal // 0.25 inch (18pt)
$0.pdf.render.configuration.margins = .standard // 0.5 inch (36pt) - default
$0.pdf.render.configuration.margins = .comfortable // 0.75 inch (54pt)
$0.pdf.render.configuration.margins = .wide // 1 inch (72pt)
// Custom margins (in points)
$0.pdf.render.configuration.margins = EdgeInsets(
top: 50,
left: 40,
bottom: 50,
right: 40
)
// Symmetric margins
$0.pdf.render.configuration.margins = EdgeInsets(all: 72)
// Horizontal and vertical
$0.pdf.render.configuration.margins = EdgeInsets(
horizontal: 40,
vertical: 50
)Note: Negative margin values are automatically clamped to zero.
Resolve relative URLs in HTML:
$0.pdf.render.configuration.baseURL = URL(string: "https://example.com")This allows relative paths in your HTML:
<img src="/images/logo.png"> <!-- Resolves to https://example.com/images/logo.png -->
<link rel="stylesheet" href="/css/styles.css">Choose how content flows into pages:
// Single tall page (fast, 1,939 PDFs/sec)
$0.pdf.render.configuration.paginationMode = .continuous
// Multiple pages (print-ready, 677 PDFs/sec)
$0.pdf.render.configuration.paginationMode = .paginated
// Automatic selection based on content
$0.pdf.render.configuration.paginationMode = .automatic()
$0.pdf.render.configuration.paginationMode = .automatic(heuristic: .contentLength(threshold: 1.5))
$0.pdf.render.configuration.paginationMode = .automatic(heuristic: .htmlStructure)
$0.pdf.render.configuration.paginationMode = .automatic(heuristic: .preferSpeed)
$0.pdf.render.configuration.paginationMode = .automatic(heuristic: .preferPrintReady)See doc:PerformanceGuide for detailed mode comparison.
Control how many PDFs render simultaneously:
// Automatic (recommended) - 1x CPU count on macOS
$0.pdf.render.configuration.concurrency = .automatic
// Fixed value
$0.pdf.render.configuration.concurrency = .fixed(16)
// Integer literal (syntactic sugar for .fixed)
$0.pdf.render.configuration.concurrency = 8Performance characteristics:
- 1-4 WebViews: Conservative, minimal memory
- 4-8 WebViews: Balanced (typical iOS constraint)
- 8 WebViews: High throughput (macOS optimal on 8-core)
Prevent hanging on problematic documents:
// Per-document timeout (nil = no timeout)
$0.pdf.render.configuration.documentTimeout = .seconds(30)
// Entire batch timeout (nil = no timeout)
$0.pdf.render.configuration.batchTimeout = .seconds(3600)
// WebView acquisition timeout (must have a value)
$0.pdf.render.configuration.webViewAcquisitionTimeout = .seconds(300)Guidelines:
- documentTimeout: Set based on your most complex document (typically 10-30s)
- batchTimeout: Set based on expected batch size and throughput
- webViewAcquisitionTimeout: Keep at 300s unless under extreme load
Automatically create output directories:
// Automatically create directories (default: true)
$0.pdf.render.configuration.createDirectories = true
// Require directories to exist
$0.pdf.render.configuration.createDirectories = falseControl how files are named in batch operations:
// Sequential numbering: "1.pdf", "2.pdf", ... (default)
$0.pdf.render.configuration.namingStrategy = .sequential
// UUID-based names
$0.pdf.render.configuration.namingStrategy = .uuid
// Custom naming function
$0.pdf.render.configuration.namingStrategy = PDF.NamingStrategy { index in
"document-\(String(format: "%05d", index + 1))"
}
// Results in: "document-00001.pdf", "document-00002.pdf", ...Optimize for throughput:
$0.pdf.render.configuration = PDF.Configuration(
paperSize: .a4,
margins: .standard,
paginationMode: .continuous, // Fast mode
concurrency: .automatic, // Maximum throughput
batchTimeout: .seconds(86400), // 24 hours
createDirectories: true
)Optimize for quality:
$0.pdf.render.configuration = PDF.Configuration(
paperSize: .letter,
margins: .wide,
paginationMode: .paginated, // Proper page breaks
concurrency: 8, // Balanced
documentTimeout: .seconds(60), // Complex documents
createDirectories: true
)Minimize resource usage:
$0.pdf.render.configuration = PDF.Configuration(
paperSize: .a4,
margins: .standard,
paginationMode: .continuous,
concurrency: 2, // Minimal concurrency
webViewAcquisitionTimeout: .seconds(60),
createDirectories: true
)Fast iteration with debugging:
$0.pdf.render.configuration = PDF.Configuration(
paperSize: .a4,
margins: .minimal,
paginationMode: .continuous, // Fastest
concurrency: 4, // Moderate
documentTimeout: .seconds(10), // Fail fast
createDirectories: true,
namingStrategy: .sequential // Easy to identify
)Set once for entire application:
// In your app setup
withDependencies {
$0.pdf.render.configuration = .platformOptimized
} operation: {
// All PDF operations use this configuration
}Override for specific operations:
// Default configuration for most operations
@Dependency(\.pdf) var pdf
// Override for specific operation
try await withDependencies {
$0.pdf.render.configuration.paginationMode = .paginated
} operation: {
try await pdf.render(html: invoice, to: fileURL)
}Test different configurations:
@Test(.dependency(\.pdf.render.configuration.paperSize, .letter))
func testLetterSize() async throws {
// Test runs with Letter paper size
}
@Test(.dependency(\.pdf.render.configuration.margins, .wide))
func testWideMargins() async throws {
// Test runs with wide margins
}PDF/ConfigurationPDF/PaginationModePDF/ConcurrencyStrategyPDF/NamingStrategyEdgeInsets