A Ruby gem · MIT
JSON in.
PDF out.
soli-pdf turns a JSON layout template and your data into a finished PDF — invoices, quotes, statements, reports, Factur-X e-invoices. No headless browser, no HTML-to-PDF guesswork, no drawing DSL to learn: one method call.
gem 'soli-pdf', git: 'https://github.com/solisoft/soli_lang',
glob: 'gem/soli-pdf/*.gemspec'
How you use it
A template, your data, one call.
The template describes the layout once. The data changes on every render. Both are plain JSON, so templates live in your repository, diff cleanly and can be generated or edited by any tool.
1 Describe the layout
{
"fonts": ["titillium"],
"content": [
{ "type": "paragraph",
"value": "Invoice ${invoice.number}",
"options": { "fontSize": 24,
"fontWeight": "bold" } },
{ "type": "table", "data": "items",
"rows": [[
{ "text": "${name}" },
{ "text": "${amount}",
"alignment": "right" }
]] }
]
}
2 Bring the data
def to_pdf_data
{
'invoice' => { 'number' => number },
'items' => lines.map do |line|
{ 'name' => line.label,
'amount' => format('%.2f €', line.total) }
end
}
end
3 Render
require 'soli/pdf'
pdf = Soli::PDF.render(
template: TEMPLATE,
data: invoice.to_pdf_data,
title: "Invoice #{invoice.number}"
)
send_data pdf, type: 'application/pdf',
filename: 'invoice.pdf'
What's in the box
Everything a business document needs.
Tables that paginate
Data-bound rows, headers repeated on every page, carried-forward subtotals, zebra stripes, colspan and rowspan.
Charts from data
Bar, line, pie and donut charts, single or multi-series, stacked or grouped, read straight from your arrays.
QR codes & barcodes
SEPA scan-to-pay GiroCodes, free-text QR codes, Code 128, EAN-13, EAN-8 and Code 39.
Factur-X e-invoices
PDF/A-3b with the EN 16931 CII XML embedded — bring your XML, or let the renderer compute it from a typed invoice.
Data-driven layout
${…} interpolation, repeat blocks, if / unless branches, page numbers and a table of contents that knows its pages.
Rich text & fonts
Mixed weights, colours and links on one line, lists, justification, bundled Titillium Web and JetBrains Mono, your own fonts.
Vector graphics
SVG logos embedded as real vectors, shapes, rules, rounded panels, backgrounds and watermarks.
Security & delivery
AES-128 passwords, letterhead stationery, embedded attachments, document metadata.
Accessible PDFs
Tagged output with headings, figures, lists and tables mapped to a structure tree (PDF/UA).
Rendered live
Real documents, with their source.
Every example comes with its template, its data and the Ruby call. Open one, read how it is built, download the PDF.
VAT-compliant invoice + Factur-X
Every statutory mention of a French VAT invoice, an EPC scan-to-pay QR, and an embedded EN 16931 XML.
Annual report
A five-page annual report: cover page, KPI tiles, bar, line and pie charts, and tables.
Quote by trade section
Line items grouped into numbered sections, each with its own subtotal, and a dashed acceptance box.
Subscription invoice
A billing period, plan fees billed in advance, metered usage billed in arrears, and a donut chart of spend.
Under the hood
A thin gem over a fast native renderer.
The gem is under 300 lines of Ruby with no runtime dependencies. It serialises your template and data, pipes them to render_pdf — a self-contained binary written in Rust — and reads the PDF back from its standard output.
- On first use the gem downloads the binary for your platform from the GitHub release, verifies its SHA-256 and caches it under
~/.cache/soli-pdf/. - Linux and macOS on x86-64 and ARM64, Windows on x86-64. Point
SOLI_PDF_BINat your own build to skip the download. - No browser to boot and no server to run: each render starts the binary, writes the PDF and exits. Fonts ship with it.
Ruby
Hash → JSON
render_pdf
stdin · stdout
binary String
pdf = Soli::PDF.render(template: tpl, data: data)
pdf.encoding # => #<Encoding:BINARY>
pdf[0, 8] # => "%PDF-1.3"
Soli::PDF.last_warnings # => []
Try a template before you install anything.
The playground renders any template and data you paste, shows every page, and gives you the Ruby call to reproduce it.