
Type-safe DSL for building invoices with sealed IR, themes, branding, status displays, taxes/discounts, and multiple renderers: PDF, print-quality HTML, email-safe HTML, and Compose UI.
Kotlin Multiplatform invoicing library with a sealed IR, type-safe DSL builder, and four renderers (Compose, PDF, HTML, Email HTML).
Full documentation — installation, section guides, theming, branding, recipes, and examples.
dependencies {
// Core IR + DSL (all KMP targets)
implementation("com.chrisjenx.kinvoicing:core:1.0.0")
// Renderers — pick the ones you need:
// Compose UI (JVM, Android, iOS, wasmJs)
implementation("com.chrisjenx.kinvoicing:render-compose:1.0.0")
// Email-safe HTML — inline styles, table layout (all KMP targets)
implementation("com.chrisjenx.kinvoicing:render-html-email:1.0.0")
// JVM only:
implementation("com.chrisjenx.kinvoicing:render-pdf:1.0.0") // PDF via compose2pdf
implementation("com.chrisjenx.kinvoicing:render-html:1.0.0") // Print-quality HTML via compose2pdf
}val doc = invoice {
header {
branding {
primary {
name("Acme Corp")
address("123 Main St", "Springfield, IL 62701")
email("billing@acme.com")
}
}
invoiceNumber("INV-2026-0001")
issueDate(LocalDate(2026, 3, 1))
dueDate(LocalDate(2026, 3, 31))
}
billFrom {
name("Acme Corp")
address("123 Main St", "Springfield, IL 62701")
email("billing@acme.com")
phone("+1 (555) 100-0001")
}
billTo {
name("Jane Smith")
address("456 Oak Ave", "Boulder, CO 80301")
email("jane@example.com")
phone("+1 (555) 200-0002")
}
lineItems {
columns("Description", "Qty", "Rate", "Amount")
item("Web Development", qty = 40, unitPrice = 150.0)
item("Design Services", qty = 10, unitPrice = 100.0)
item("Hosting (Monthly)", qty = 1, unitPrice = 49.99)
}
summary {
currency("USD")
}
footer {
notes("Thank you for your business!")
terms("Net 30")
}
}// PDF — write to file
val pdf: ByteArray = doc.toPdf()
File("invoice.pdf").writeBytes(pdf)
// PDF — US Letter page size
val letterPdf = doc.toPdf(PdfRenderConfig.Letter)
// Email-safe HTML (inline styles, table layout — works in Gmail, Outlook, etc.)
val emailHtml: String = doc.toHtml()
// Compose UI — drop into any Compose screen
@Composable
fun InvoiceScreen() {
InvoiceView(doc)
}
// Print-quality HTML (SVG-based, embedded fonts, print styles)
val printHtml: String = renderToHtml { InvoiceContent(doc) }Kinvoicing ships with 8 built-in themes:
| Theme | Character |
|---|---|
Classic |
Default — clean blue on white |
Corporate |
Navy with accent stripe, professional |
Modern |
Indigo tones, airy, minimal decoration |
Bold |
Strong blue, grid lines, stacked header |
Warm |
Amber earth tones, Georgia font, friendly |
Minimal |
Near-monochrome, ultra-clean |
Elegant |
Dark stone + gold, Georgia font, refined |
Fresh |
Green/teal, clean, optimistic |
Apply a theme:
val doc = invoice {
style {
theme(InvoiceThemes.Modern)
}
// ... sections
}Override individual properties after applying a theme:
style {
theme(InvoiceThemes.Elegant)
accentBorder = true
showGridLines = true
primaryColor = 0xFF1D4ED8
fontFamily = "Helvetica"
}| Property | Type | Default | Description |
|---|---|---|---|
primaryColor |
Long (ARGB) |
Blue | Headings, totals, accent elements |
secondaryColor |
Long (ARGB) |
Slate | Labels, secondary text |
textColor |
Long (ARGB) |
Dark | Body text |
backgroundColor |
Long (ARGB) |
White | Page background |
fontFamily |
String |
"Inter" |
Font family name |
logoPlacement |
LogoPlacement |
LEFT |
LEFT, CENTER, or RIGHT
|
headerLayout |
HeaderLayout |
HORIZONTAL |
HORIZONTAL or STACKED
|
showGridLines |
Boolean |
false |
Draw borders around line-item rows |
accentBorder |
Boolean |
false |
Colored top/left border accent |
negativeColor |
Long (ARGB) |
Red | Discounts, credits, negative amounts |
borderColor |
Long (ARGB) |
Light gray | Table borders, grid lines |
dividerColor |
Long (ARGB) |
Lighter gray | Subtle row dividers |
mutedBackgroundColor |
Long (ARGB) |
Near-white | Alternating rows, meta blocks |
surfaceColor |
Long (ARGB) |
White | Card-like section backgrounds |
In a Compose app, derive invoice styles from your Material theme:
@Composable
fun ThemedInvoice(doc: InvoiceDocument) {
val style = InvoiceStyle.fromMaterialTheme {
copy(accentBorder = true) // optional overrides
}
InvoiceView(doc.copy(style = style))
}Provide a logo as a ByteArray with its MIME type:
header {
branding {
primary {
name("Acme Corp")
logo(logoBytes, "image/png") // PNG, JPEG, etc.
}
}
}Show a secondary brand (e.g., a payment processor or platform):
header {
branding {
layout = BrandLayout.DUAL_HEADER // Both side-by-side
primary {
name("My Company")
logo(myLogoBytes, "image/png")
}
poweredBy {
name("Powered by Platform")
logo(platformLogoBytes, "image/png")
}
}
}Layout options:
POWERED_BY_FOOTER (default) — primary in header, powered-by in footerPOWERED_BY_HEADER — primary in header, powered-by in header subtitleDUAL_HEADER — both brands side-by-side in header// Default: A4, vector rendering, auto-pagination
doc.toPdf()
// US Letter
doc.toPdf(PdfRenderConfig.Letter)
// Custom config
doc.toPdf(PdfRenderConfig(
pageConfig = PdfPageConfig.A4,
renderMode = RenderMode.VECTOR,
pagination = PdfPagination.AUTO,
density = Density(2f),
))Long invoices automatically paginate across multiple pages.
// Default: full document with embedded images
doc.toHtml()
// Fragment for embedding in an existing email template
doc.toHtml(HtmlRenderConfig(
embedImages = true,
includeDoctype = false,
wrapInBody = false,
))Output uses inline styles and table layout for maximum email client compatibility (Gmail, Outlook, Apple Mail, etc.).
Show the invoice's state visually with 7 predefined statuses and 5 display modes:
// Simple — badge pill in header
val doc = invoice {
status(InvoiceStatus.Paid)
// ... sections
}
// With display mode — diagonal watermark
val doc = invoice {
status {
voided()
watermark()
}
// ... sections
}
// Custom status
val doc = invoice {
status {
custom("PENDING APPROVAL", 0xFFF59E0B)
banner()
}
// ... sections
}Predefined statuses: Draft (gray), Sent (blue), Paid (green), Overdue (red), Void (gray), Uncollectable (stone), Refunded (violet), Custom(label, color).
Display modes: Badge (default pill), Banner (full-width bar), Watermark (diagonal text), Stamp (rotated seal), None (data only).
All display modes work across Compose, PDF, HTML, and email HTML renderers.
Apply tax, discounts, fees, and credits at line-item or summary level:
lineItems {
item("Consulting", qty = 10, unitPrice = 200.0)
item("Rush Fee", amount = 50.0)
}
summary {
currency("USD")
tax("Sales Tax", percent = 8.0) // 8% tax
discount("Early Payment", percent = 10.0) // 10% discount
fee("Processing Fee", fixed = 25.0) // Flat fee
credit("Previous Credit", 100.0) // Credit applied
}Nest detail rows under a parent line item:
lineItems {
item("Project Alpha") {
sub("Design Phase", qty = 20, unitPrice = 150.0)
sub("Development Phase", qty = 40, unitPrice = 175.0)
sub("Testing", qty = 10, unitPrice = 125.0)
}
}paymentInfo {
bankName("First National Bank")
accountNumber("1234567890")
routingNumber("021000021")
paymentLink("https://pay.example.com/inv-001")
notes("Wire transfer preferred for amounts over $1,000")
}Add key-value metadata (PO numbers, project names, etc.):
metaBlock {
entry("PO Number", "PO-2026-0042")
entry("Project", "Website Redesign")
entry("Department", "Engineering")
}Build freeform sections from primitives:
custom("terms-detail") {
text("Extended Payment Terms", styleRef = "bold")
divider()
text("This invoice is subject to the terms outlined in Contract #C-2026-001.")
spacer()
}| Module | Artifact | Description | Platforms |
|---|---|---|---|
:core |
core |
Invoice IR (sealed classes), DSL builder, currency formatting | JVM, Android, iOS, wasmJs, macOS, Linux, Windows |
:render-compose |
render-compose |
Compose Multiplatform UI renderer | JVM, Android, iOS, wasmJs |
:render-html-email |
render-html-email |
Email-safe HTML renderer via kotlinx.html | JVM, Android, iOS, wasmJs, macOS, Linux, Windows |
:render-pdf |
render-pdf |
PDF renderer via compose2pdf | JVM only |
:render-html |
render-html |
Compose → HTML renderer via compose2pdf | JVM only |
The core module defines a sealed InvoiceDocument IR with 9 section variants (Header, BillFrom, BillTo, LineItems, Summary, PaymentInfo, Footer, Custom, MetaBlock). Renderers use exhaustive when blocks over the sealed hierarchy, so adding a new section type is a compile-time-checked change.
The Compose and PDF renderers share a single InvoiceContent composable — PDF matches Compose by construction via compose2pdf. The HTML renderer also uses compose2pdf to produce SVG-based print-quality output. The email HTML renderer is standalone, using kotlinx.html with inline styles and table layout for maximum email client compatibility.
core (sealed IR + DSL)
├── render-compose (Compose UI)
│ ├── render-pdf (PDF via compose2pdf)
│ └── render-html (HTML via compose2pdf)
└── render-html-email (standalone, kotlinx.html)
Cross-renderer fidelity is verified by automated tests that compare Compose, PDF, HTML, and email HTML output using RMSE and SSIM metrics. See the docs for details.
Copyright 2026 Christopher Jenkins
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Kotlin Multiplatform invoicing library with a sealed IR, type-safe DSL builder, and four renderers (Compose, PDF, HTML, Email HTML).
Full documentation — installation, section guides, theming, branding, recipes, and examples.
dependencies {
// Core IR + DSL (all KMP targets)
implementation("com.chrisjenx.kinvoicing:core:1.0.0")
// Renderers — pick the ones you need:
// Compose UI (JVM, Android, iOS, wasmJs)
implementation("com.chrisjenx.kinvoicing:render-compose:1.0.0")
// Email-safe HTML — inline styles, table layout (all KMP targets)
implementation("com.chrisjenx.kinvoicing:render-html-email:1.0.0")
// JVM only:
implementation("com.chrisjenx.kinvoicing:render-pdf:1.0.0") // PDF via compose2pdf
implementation("com.chrisjenx.kinvoicing:render-html:1.0.0") // Print-quality HTML via compose2pdf
}val doc = invoice {
header {
branding {
primary {
name("Acme Corp")
address("123 Main St", "Springfield, IL 62701")
email("billing@acme.com")
}
}
invoiceNumber("INV-2026-0001")
issueDate(LocalDate(2026, 3, 1))
dueDate(LocalDate(2026, 3, 31))
}
billFrom {
name("Acme Corp")
address("123 Main St", "Springfield, IL 62701")
email("billing@acme.com")
phone("+1 (555) 100-0001")
}
billTo {
name("Jane Smith")
address("456 Oak Ave", "Boulder, CO 80301")
email("jane@example.com")
phone("+1 (555) 200-0002")
}
lineItems {
columns("Description", "Qty", "Rate", "Amount")
item("Web Development", qty = 40, unitPrice = 150.0)
item("Design Services", qty = 10, unitPrice = 100.0)
item("Hosting (Monthly)", qty = 1, unitPrice = 49.99)
}
summary {
currency("USD")
}
footer {
notes("Thank you for your business!")
terms("Net 30")
}
}// PDF — write to file
val pdf: ByteArray = doc.toPdf()
File("invoice.pdf").writeBytes(pdf)
// PDF — US Letter page size
val letterPdf = doc.toPdf(PdfRenderConfig.Letter)
// Email-safe HTML (inline styles, table layout — works in Gmail, Outlook, etc.)
val emailHtml: String = doc.toHtml()
// Compose UI — drop into any Compose screen
@Composable
fun InvoiceScreen() {
InvoiceView(doc)
}
// Print-quality HTML (SVG-based, embedded fonts, print styles)
val printHtml: String = renderToHtml { InvoiceContent(doc) }Kinvoicing ships with 8 built-in themes:
| Theme | Character |
|---|---|
Classic |
Default — clean blue on white |
Corporate |
Navy with accent stripe, professional |
Modern |
Indigo tones, airy, minimal decoration |
Bold |
Strong blue, grid lines, stacked header |
Warm |
Amber earth tones, Georgia font, friendly |
Minimal |
Near-monochrome, ultra-clean |
Elegant |
Dark stone + gold, Georgia font, refined |
Fresh |
Green/teal, clean, optimistic |
Apply a theme:
val doc = invoice {
style {
theme(InvoiceThemes.Modern)
}
// ... sections
}Override individual properties after applying a theme:
style {
theme(InvoiceThemes.Elegant)
accentBorder = true
showGridLines = true
primaryColor = 0xFF1D4ED8
fontFamily = "Helvetica"
}| Property | Type | Default | Description |
|---|---|---|---|
primaryColor |
Long (ARGB) |
Blue | Headings, totals, accent elements |
secondaryColor |
Long (ARGB) |
Slate | Labels, secondary text |
textColor |
Long (ARGB) |
Dark | Body text |
backgroundColor |
Long (ARGB) |
White | Page background |
fontFamily |
String |
"Inter" |
Font family name |
logoPlacement |
LogoPlacement |
LEFT |
LEFT, CENTER, or RIGHT
|
headerLayout |
HeaderLayout |
HORIZONTAL |
HORIZONTAL or STACKED
|
showGridLines |
Boolean |
false |
Draw borders around line-item rows |
accentBorder |
Boolean |
false |
Colored top/left border accent |
negativeColor |
Long (ARGB) |
Red | Discounts, credits, negative amounts |
borderColor |
Long (ARGB) |
Light gray | Table borders, grid lines |
dividerColor |
Long (ARGB) |
Lighter gray | Subtle row dividers |
mutedBackgroundColor |
Long (ARGB) |
Near-white | Alternating rows, meta blocks |
surfaceColor |
Long (ARGB) |
White | Card-like section backgrounds |
In a Compose app, derive invoice styles from your Material theme:
@Composable
fun ThemedInvoice(doc: InvoiceDocument) {
val style = InvoiceStyle.fromMaterialTheme {
copy(accentBorder = true) // optional overrides
}
InvoiceView(doc.copy(style = style))
}Provide a logo as a ByteArray with its MIME type:
header {
branding {
primary {
name("Acme Corp")
logo(logoBytes, "image/png") // PNG, JPEG, etc.
}
}
}Show a secondary brand (e.g., a payment processor or platform):
header {
branding {
layout = BrandLayout.DUAL_HEADER // Both side-by-side
primary {
name("My Company")
logo(myLogoBytes, "image/png")
}
poweredBy {
name("Powered by Platform")
logo(platformLogoBytes, "image/png")
}
}
}Layout options:
POWERED_BY_FOOTER (default) — primary in header, powered-by in footerPOWERED_BY_HEADER — primary in header, powered-by in header subtitleDUAL_HEADER — both brands side-by-side in header// Default: A4, vector rendering, auto-pagination
doc.toPdf()
// US Letter
doc.toPdf(PdfRenderConfig.Letter)
// Custom config
doc.toPdf(PdfRenderConfig(
pageConfig = PdfPageConfig.A4,
renderMode = RenderMode.VECTOR,
pagination = PdfPagination.AUTO,
density = Density(2f),
))Long invoices automatically paginate across multiple pages.
// Default: full document with embedded images
doc.toHtml()
// Fragment for embedding in an existing email template
doc.toHtml(HtmlRenderConfig(
embedImages = true,
includeDoctype = false,
wrapInBody = false,
))Output uses inline styles and table layout for maximum email client compatibility (Gmail, Outlook, Apple Mail, etc.).
Show the invoice's state visually with 7 predefined statuses and 5 display modes:
// Simple — badge pill in header
val doc = invoice {
status(InvoiceStatus.Paid)
// ... sections
}
// With display mode — diagonal watermark
val doc = invoice {
status {
voided()
watermark()
}
// ... sections
}
// Custom status
val doc = invoice {
status {
custom("PENDING APPROVAL", 0xFFF59E0B)
banner()
}
// ... sections
}Predefined statuses: Draft (gray), Sent (blue), Paid (green), Overdue (red), Void (gray), Uncollectable (stone), Refunded (violet), Custom(label, color).
Display modes: Badge (default pill), Banner (full-width bar), Watermark (diagonal text), Stamp (rotated seal), None (data only).
All display modes work across Compose, PDF, HTML, and email HTML renderers.
Apply tax, discounts, fees, and credits at line-item or summary level:
lineItems {
item("Consulting", qty = 10, unitPrice = 200.0)
item("Rush Fee", amount = 50.0)
}
summary {
currency("USD")
tax("Sales Tax", percent = 8.0) // 8% tax
discount("Early Payment", percent = 10.0) // 10% discount
fee("Processing Fee", fixed = 25.0) // Flat fee
credit("Previous Credit", 100.0) // Credit applied
}Nest detail rows under a parent line item:
lineItems {
item("Project Alpha") {
sub("Design Phase", qty = 20, unitPrice = 150.0)
sub("Development Phase", qty = 40, unitPrice = 175.0)
sub("Testing", qty = 10, unitPrice = 125.0)
}
}paymentInfo {
bankName("First National Bank")
accountNumber("1234567890")
routingNumber("021000021")
paymentLink("https://pay.example.com/inv-001")
notes("Wire transfer preferred for amounts over $1,000")
}Add key-value metadata (PO numbers, project names, etc.):
metaBlock {
entry("PO Number", "PO-2026-0042")
entry("Project", "Website Redesign")
entry("Department", "Engineering")
}Build freeform sections from primitives:
custom("terms-detail") {
text("Extended Payment Terms", styleRef = "bold")
divider()
text("This invoice is subject to the terms outlined in Contract #C-2026-001.")
spacer()
}| Module | Artifact | Description | Platforms |
|---|---|---|---|
:core |
core |
Invoice IR (sealed classes), DSL builder, currency formatting | JVM, Android, iOS, wasmJs, macOS, Linux, Windows |
:render-compose |
render-compose |
Compose Multiplatform UI renderer | JVM, Android, iOS, wasmJs |
:render-html-email |
render-html-email |
Email-safe HTML renderer via kotlinx.html | JVM, Android, iOS, wasmJs, macOS, Linux, Windows |
:render-pdf |
render-pdf |
PDF renderer via compose2pdf | JVM only |
:render-html |
render-html |
Compose → HTML renderer via compose2pdf | JVM only |
The core module defines a sealed InvoiceDocument IR with 9 section variants (Header, BillFrom, BillTo, LineItems, Summary, PaymentInfo, Footer, Custom, MetaBlock). Renderers use exhaustive when blocks over the sealed hierarchy, so adding a new section type is a compile-time-checked change.
The Compose and PDF renderers share a single InvoiceContent composable — PDF matches Compose by construction via compose2pdf. The HTML renderer also uses compose2pdf to produce SVG-based print-quality output. The email HTML renderer is standalone, using kotlinx.html with inline styles and table layout for maximum email client compatibility.
core (sealed IR + DSL)
├── render-compose (Compose UI)
│ ├── render-pdf (PDF via compose2pdf)
│ └── render-html (HTML via compose2pdf)
└── render-html-email (standalone, kotlinx.html)
Cross-renderer fidelity is verified by automated tests that compare Compose, PDF, HTML, and email HTML output using RMSE and SSIM metrics. See the docs for details.
Copyright 2026 Christopher Jenkins
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.