A pure-Swift PDF generation library with a declarative DSL. Build pixel-perfect PDFs using a SwiftUI-inspired syntax — from simple one-page documents to multi-page invoices with automatic pagination.
- Declarative DSL — Result-builder syntax for pages, text, tables, columns, and more
- Cross-platform — CoreGraphics on Apple platforms, HTML-to-PDF on Linux; no AppKit or UIKit dependency
- Invoice engine — 5 built-in layouts with automatic multi-page pagination
- Business documents — Quotes, sales orders, delivery notes, and shipment documents
- Themeable — 3 built-in themes + fully customizable colors, fonts, and spacing
- QR codes — SEPA/EPC QR and arbitrary payloads via pure-Swift generation
- SwiftUI previews — Live Xcode canvas previews with
PDFPreviewView
Add SwiftlyPDFKit to your Package.swift:
dependencies: [
.package(url: "https://github.com/Swiftly-Developed/Swiftly-PDFKit.git", from: "0.2.0"),
]Then add the target dependency:
.target(
name: "YourTarget",
dependencies: ["SwiftlyPDFKit"]
),For SwiftUI preview support, also add SwiftlyPDFKitUI:
.target(
name: "YourTarget",
dependencies: ["SwiftlyPDFKit", "SwiftlyPDFKitUI"]
),import SwiftlyPDFKit
let pdf = PDF {
Page(size: .a4) {
Text("Hello, world!")
.font(.helvetica, size: 24)
.bold()
Spacer(height: 20)
Text("Generated with SwiftlyPDFKit")
.fontSize(12)
.foregroundColor(PDFColor.gray)
}
}
let data = try pdf.render()
try pdf.write(to: URL(fileURLWithPath: "/tmp/hello.pdf"))import SwiftlyPDFKit
let invoice = InvoiceDocument(
header: InvoiceHeader(
invoiceNumber: "INV-2026-001",
issueDate: "2026-03-01",
dueDate: "2026-03-15",
currency: "EUR"
),
supplier: InvoiceSupplier(name: "Acme Corp"),
client: InvoiceClient(name: "Client BV"),
lines: [
InvoiceLine(description: "Consulting", quantity: 8, unit: "hrs",
unitPrice: 150, vatRate: 21),
InvoiceLine(description: "Design review", quantity: 3, unit: "hrs",
unitPrice: 120, vatRate: 21, discountPercent: 10),
]
)
let pdf = PDF(layout: .classic, invoice: invoice, theme: .standard)
try pdf.write(to: URL(fileURLWithPath: "/tmp/invoice.pdf"))SwiftlyPDFKit provides 12 building blocks that compose via result builders:
// DSL builder
let pdf = PDF {
Page(size: .a4, margins: 40) {
// ... content ...
}
Page(size: .letter) {
// ... more content ...
}
}
// Render to Data or write to disk
let data = try pdf.render()
try pdf.write(to: url)Page sizes: .a4 (595 x 842), .letter (612 x 792), .legal (612 x 1008), or custom PageSize(width:height:).
Text("Title")
.font(.times, size: 24)
.bold()
.italic()
.foregroundColor(PDFColor.blue)
.alignment(.center)Modifier chain: .font(_:size:), .bold(), .italic(), .fontSize(_:), .foregroundColor(_:), .alignment(_:)
Table(data: rows, style: .default, showHeader: true) {
Column("Description", width: .flex)
Column("Qty", width: .fixed(60), alignment: .trailing)
Column("Price", width: .fixed(80), alignment: .trailing)
}Column widths are .flex (fills remaining space proportionally) or .fixed(points). Each column supports independent text alignment and a separate header alignment.
TableStyle controls header colors, font sizes, row height, alternating row tint, border width, cell padding, and more.
Columns(spacing: 10) {
ColumnItem(width: .flex) {
Text("Left column")
}
ColumnItem(width: .fixed(200)) {
Text("Right column (200pt)")
}
}Each column renders independently with its own cursor. The overall cursor advances by the height of the tallest column.
| Element | Description |
|---|---|
Spacer(height:) |
Vertical whitespace (default 12pt) |
HRule(thickness:color:) |
Horizontal divider line |
FilledBox(color:height:padding:) { ... } |
Colored background behind nested content |
ImageContent(path:maxWidth:maxHeight:alignment:) |
PNG/JPEG with aspect-ratio preservation |
QRCodeContent("payload", size: 80) |
QR code rendered as a crisp vector image |
Footer(height:) { ... } |
Content pinned to the bottom of every page |
// Named colors
PDFColor.black, .white, .gray, .lightGray, .darkGray, .red, .green, .blue
// Custom colors
PDFColor(red: 0.2, green: 0.4, blue: 0.8)
PDFColor(white: 0.9)
// Built-in fonts
PDFFont.helvetica, .helveticaBold, .helveticaOblique, .helveticaBoldOblique
PDFFont.times, .timesBold, .timesItalic
PDFFont.courier, .courierBold
// Custom PostScript font name
PDFFont(name: "Menlo-Regular")The invoice system uses a single data model (InvoiceDocument) that feeds into different visual layouts.
InvoiceDocument
+-- header: InvoiceHeader // number, dates, currency, payment terms, QR, notes
+-- supplier: InvoiceSupplier // name, address, VAT, IBAN, logo path
+-- client: InvoiceClient // name, address, VAT, PO number
+-- lines: [InvoiceLine] // description, qty, unit, price, VAT rate, discount
+-- totals: InvoiceTotals // auto-computed from lines, or override manually
+-- footer: InvoiceFooter? // text lines pinned to page bottom
InvoiceTotals is derived automatically from lines. Override it for edge cases like global discounts or partial payments:
let totals = InvoiceTotals(
subtotalExcl: 1200,
totalVat: 252,
totalIncl: 1452,
amountPaid: 500 // deposit already paid
)
let invoice = InvoiceDocument(header: ..., supplier: ..., client: ...,
lines: lines, totals: totals)| Layout | Description |
|---|---|
.classic |
Two-column header, metadata grid, line-items table, totals, QR + payment banner |
.classicWithSidebar |
Classic layout with a full-height accent-colored bar on the left edge |
.minimal |
Borderless tables, generous whitespace, plain-text totals |
.stacked |
Full-width title banner, vertically stacked supplier and client blocks |
.summaryFirst |
Page 1 shows totals and payment only; line items start on page 2 |
let pdf = PDF(layout: .minimal, invoice: invoice, theme: .corporate, pageSize: .a4)All layouts handle automatic multi-page pagination: line items overflow gracefully across pages, and totals/payment sections are guaranteed their own space (bumping to a new page if needed).
Three built-in presets:
| Theme | Style |
|---|---|
.standard |
Clean black-and-white with subtle gray accents |
.gold |
Warm gold-tinted accent color |
.corporate |
Bold blue headers with light-blue alternating rows |
Customize any theme property:
var theme = InvoiceTheme.standard
theme.accentColor = PDFColor(red: 0.2, green: 0.6, blue: 0.4)
theme.bodyFont = .times
theme.logoPosition = .right
theme.lineItemRowHeight = 24Theme properties: accentColor, tableHeaderBackground, tableHeaderTextColor, tableAlternateRowColor, ruleColor, paymentBannerColor, paymentBannerTextColor, tableBorderColor, bodyFont, titleFont, bodyFontSize, titleFontSize, tableHeaderFontSize, tableCellFontSize, pageMargins, logoPosition (.left / .right / .topCenter), logoMaxWidth, logoMaxHeight, lineItemRowHeight, totalsRowHeight.
Beyond invoices, SwiftlyPDFKit supports four additional document types. All share the InvoiceDocument data model and add a type-specific supplement for extra fields.
let supplement = QuoteSupplement(
expiryDate: "2026-04-01",
acceptanceNote: "Please sign and return to confirm."
)
let pdf = PDF(quoteLayout: .classic, invoice: invoice, supplement: supplement,
theme: .standard, pageSize: .a4)Layouts: .classic, .minimal
let supplement = SalesOrderSupplement(
poConfirmedDate: "2026-03-01",
requestedDeliveryDate: "2026-03-15"
)
let pdf = PDF(salesOrderLayout: .classic, invoice: invoice, supplement: supplement,
theme: .corporate, pageSize: .a4)Layouts: .classic, .stacked
let supplement = DeliverySupplement(
shipToAddress: "Warehouse 3\nIndustrial Park\n2000 Antwerp",
signatureRequired: true,
signatureLabel: "Received in good order by:"
)
let pdf = PDF(deliveryLayout: .standard, invoice: invoice, supplement: supplement,
theme: .standard, pageSize: .a4)Layouts: .standard
let supplement = ShipmentSupplement(
carrier: "DHL Express",
trackingNumber: "JJD000390007843164734",
shipToAddress: "Warehouse 3\nIndustrial Park\n2000 Antwerp",
estimatedDelivery: "2026-03-18",
signatureRequired: true
)
let pdf = PDF(shipmentLayout: .standard, invoice: invoice, supplement: supplement,
theme: .corporate, pageSize: .a4)Layouts: .standard, .compact
Tip: Set
header.documentTitleto customize the title shown on the document (e.g. "Quotation", "Sales Order Confirmation", "Delivery Note", "Packing Slip").
SwiftlyPDFKitUI provides PDFPreviewView for live Xcode canvas previews:
import SwiftUI
import SwiftlyPDFKit
import SwiftlyPDFKitUI
#Preview("Invoice", traits: .fixedLayout(width: 595, height: 842)) {
PDFPreviewView {
Page(size: .a4) {
Text("Preview content").font(.helvetica, size: 16)
}
}
}Or preview a pre-built PDF:
let pdf = PDF(layout: .classic, invoice: invoice, theme: .standard)
#Preview("Classic Invoice", traits: .fixedLayout(width: 595, height: 842)) {
PDFPreviewView(pdf)
}The project includes 15 demo configurations covering every layout, theme, and document type. Each demo has a SwiftUI #Preview for the Xcode canvas and a corresponding CLI generator.
| # | Name | Type | Layout | Theme |
|---|---|---|---|---|
| 01 | Standard | Invoice | classic | standard |
| 02 | Gold | Invoice | classic | gold |
| 03 | Corporate | Invoice | classic | corporate |
| 04 | Purple | Invoice | classic | custom (Times, logo right) |
| 05 | Green | Invoice | classic | custom (logo top-center) |
| 06 | Partial Payment | Invoice | classic | standard |
| 07 | Mono | Invoice | classic | custom (Courier, Letter) |
| 08 | Sidebar | Invoice | classicWithSidebar | corporate |
| 09 | Minimal | Invoice | minimal | standard |
| 10 | Stacked | Invoice | stacked | custom (teal) |
| 11 | Summary First | Invoice | summaryFirst | gold |
| 12 | Quote | Quote | classic | standard |
| 13 | Sales Order | Sales Order | classic | corporate |
| 14 | Delivery | Delivery | standard | standard |
| 15 | Shipment | Shipment | standard | corporate |
swift run GenerateDemosOutputs 15 PDF files to DemoPDFs/ at the package root.
Sources/
SwiftlyPDFKit/
Core/
PDF.swift # Entry point, rendering
Page.swift # Page, PageSize
PDFContent.swift # PDFContent protocol, Text, Spacer, HRule
ContentBuilder.swift # @ContentBuilder result builder
Color.swift # PDFColor
Font.swift # PDFFont
Table.swift # Table, Column, TableStyle
Columns.swift # Columns, ColumnItem
FilledBox.swift # Colored background container
ImageContent.swift # Image rendering
QRCodeContent.swift # QR code generation
Footer.swift # Page footer
HTMLToPDFConverter.swift # Linux HTML-to-PDF via wkhtmltopdf
HTMLUtilities.swift # HTML escaping helpers
Documents/
Invoice/
Invoice.swift # Data model (Supplier, Client, Header, Line, etc.)
InvoiceTheme.swift # Theme presets and customization
InvoiceLayout.swift # 5 layout implementations
Shared/
DocumentSupplement.swift # Quote/SalesOrder/Delivery/Shipment supplements
DocumentLayoutHelpers.swift # Shared drawing helpers
Quote/ # Quote layout + builder
SalesOrder/ # Sales order layout + builder
Delivery/ # Delivery note layout + builder
Shipment/ # Shipment document layout + builder
SwiftlyPDFKitUI/
PDFPreviewView.swift # SwiftUI wrapper (iOS + macOS)
DemoPDFKit/ # 15 demo files + shared fixtures
GenerateDemos/
main.swift # CLI tool for batch PDF generation
| Dependency | Version | Purpose |
|---|---|---|
| swift-qrcode-generator | ~> 1.0 | Pure-Swift QR code encoder (cross-platform) |
Apple platforms: CoreGraphics, CoreText (macOS/iOS), PDFKit (SwiftUI previews only).
Linux: wkhtmltopdf system binary for HTML-to-PDF conversion (install with apt-get install wkhtmltopdf).
- Swift 6.0+
- macOS 12+ or iOS 15+ for Apple platforms
- Linux: Swift 6.0+ toolchain and
wkhtmltopdf(apt-get install wkhtmltopdf)
MIT