R4PDF
A free, open-source .NET library that converts JSON templates into fully customizable PDFs. No paid licenses — define your layout in JSON and generate professional documents.
Installation#
Install R4PDF from NuGet using your preferred method:
dotnet add package R4PDFsudo apt install fonts-liberation. Windows and macOS use bundled fonts automatically.Quick Start#
Create a PdfGenerator instance, define a JSON template, and call Generate(). The generator is safe to instantiate once and reuse across requests.
using R4PDF;
var generator = new PdfGenerator();
var template = """
{
"pages": [{
"body": {
"elements": [
{ "type": "text", "text": "Hello, World!", "fontSize": 24, "fontWeight": "bold" },
{ "type": "paragraph", "content": "Generated from a JSON template." }
]
}
}]
}
""";
byte[] pdf = generator.Generate(template);
generator.GenerateToFile(template, "output.pdf");// ...).Data Binding#
Use ${path.to.value} placeholders in your template and pass a JSON data object as a second argument. Nested dot-notation paths are resolved automatically.
var template = """
{
"pages": [{
"body": {
"elements": [
{ "type": "text", "text": "Hello, ${customer.name}!" },
{ "type": "text", "text": "Order #${order.id}" },
{ "type": "text", "text": "Total: ${order.total}" }
]
}
}]
}
""";
var data = """
{
"customer": { "name": "John Doe" },
"order": { "id": "12345", "total": "$99.95" }
}
""";
byte[] pdf = generator.Generate(template, data);Resolution Rules
| Property | Type | Description |
|---|---|---|
| String | string | Inserted as-is |
| Number | number | Converted to text representation |
| Boolean | boolean | Becomes "true" or "false" |
| Null | null | Replaced with empty string |
| Object | object | Serialized as raw JSON text |
| Unresolved | — | Placeholder remains unchanged |
Template Schema#
Root Structure
A template has four top-level keys. Only pages is required.
| Property | Type | Description |
|---|---|---|
| metadata | object? | PDF document info (title, author, subject, keywords) |
| settings | object? | Default page settings (size, orientation, margins) |
| styles | object? | Dictionary of reusable named styles |
| pages | array | Array of page definitions (required, min 1) |
{
"metadata": {
"title": "My Report",
"author": "Jane Doe",
"subject": "Quarterly Summary",
"keywords": "report finance"
},
"settings": {
"pageSize": "A4",
"orientation": "Portrait",
"margins": { "top": "20mm", "bottom": "20mm", "left": "15mm", "right": "15mm" }
},
"styles": {
"heading": { "fontSize": 20, "fontWeight": "bold", "color": "#003366" }
},
"pages": [ ... ]
}Page Structure
Each page can override settings and define header, body (required), and footer sections.
| Property | Type | Description |
|---|---|---|
| settings | object? | Per-page overrides for pageSize, orientation, margins |
| header | section? | Header section with height and elements |
| body | section | Body section with elements (required) |
| footer | section? | Footer section fixed to page bottom |
Section Properties
| Property | Type | Description |
|---|---|---|
| height | string? | Section height (e.g., "40mm"). Auto-calculated for body. |
| background | string? | Background color for the section (e.g., "#F5F5F5") |
| elements | array | Array of element objects to render |
{
"header": {
"height": "30mm",
"background": "#F8F9FA",
"elements": [
{ "type": "text", "text": "Company Report", "fontSize": 12, "color": "#666" }
]
},
"body": {
"elements": [{ "type": "text", "text": "Main content", "fontSize": 14 }]
},
"footer": {
"height": "20mm",
"elements": [
{ "type": "text", "text": "Page {pageNumber} of {pageCount}", "alignment": "center", "fontSize": 9 }
]
}
}Element Types#
R4PDF supports 6 element types. All share a base set of properties — the type field determines which renderer is used.
Base Element Properties
| Property | Type | Description |
|---|---|---|
| type | string | "text" | "paragraph" | "table" | "image" | "line" | "rectangle" |
| style | string? | Name of a reusable style from the styles dictionary |
| inlineStyle | object? | Inline PdfStyle object for element-level overrides |
| x | string? | Absolute X position (e.g., "10mm") |
| y | string? | Absolute Y position (e.g., "20mm") |
| width | string? | Element width (e.g., "100mm", "50%") |
| height | string? | Element height (e.g., "30mm") |
text
Single-line text with alignment and font properties.
| Property | Type | Description |
|---|---|---|
| text | string | The text content to display |
| fontSize | number? | Font size in points (default: 12) |
| fontWeight | string? | "normal" or "bold" |
| fontFamily | string? | Font name (default: "Liberation Sans") |
| color | string? | Text color (default: "#000000") |
| alignment | string? | "left", "center", or "right" |
{ "type": "text", "text": "Hello World", "fontSize": 14, "fontWeight": "bold", "color": "#003366" }paragraph
Multi-line text with automatic word wrapping, line height, and spacing control.
| Property | Type | Description |
|---|---|---|
| content | string | The paragraph text (wraps automatically) |
| fontSize | number? | Font size in points (default: 12) |
| fontWeight | string? | "normal" or "bold" |
| color | string? | Text color |
| alignment | string? | "left", "center", "right", or "justify" |
| lineHeight | number? | Line spacing multiplier (default: 1.2) |
| spacing.before | string? | Space before (e.g., "6pt") |
| spacing.after | string? | Space after (e.g., "6pt") |
{
"type": "paragraph",
"content": "Long text that will wrap automatically across multiple lines...",
"fontSize": 11,
"lineHeight": 1.5,
"alignment": "justify",
"spacing": { "before": "6pt", "after": "6pt" }
}table
Data tables with configurable columns, header styling, alternate row colors, per-row overrides, and borders.
| Property | Type | Description |
|---|---|---|
| columns | array | Column definitions with name, width, alignment |
| rows | array | Row data: { cells, backgroundColor?, textColor? } |
| headerStyle | object? | PdfStyle for the header row |
| showHeader | boolean? | Show/hide header row (default: true) |
| alternateRowColors | boolean? | Enable alternate row coloring |
| alternateColor | string? | Alternate row color (e.g., "#F5F5F5") |
| borders | object? | Border config: { width, color } |
Column widths support three modes:
| Property | Type | Description |
|---|---|---|
| Percentage | "50%" | Percentage of available table width |
| Fixed | "100mm" | Fixed width in any unit |
| Auto | (omitted) | Equally divided remaining space |
{
"type": "table",
"columns": [
{ "name": "Item", "width": "50%" },
{ "name": "Price", "width": "25%", "alignment": "right" },
{ "name": "Qty", "width": "25%", "alignment": "center" }
],
"rows": [
{ "cells": ["Widget A", "$10.00", "3"] },
{ "cells": ["Widget B", "$5.50", "7"], "backgroundColor": "#FFF3CD", "textColor": "#856404" }
],
"headerStyle": { "backgroundColor": "#003366", "color": "#FFFFFF", "fontWeight": "bold" },
"alternateRowColors": true,
"alternateColor": "#F5F5F5",
"borders": { "width": "0.5pt", "color": "#CCCCCC" }
}image
Embed images via base64 data URIs or file paths. Scales to fit width while preserving aspect ratio.
| Property | Type | Description |
|---|---|---|
| source | string | File path or data:image/png;base64,... URI |
| width | string? | Image width (e.g., "50mm") |
| height | string? | Image height (auto if aspect ratio preserved) |
| alignment | string? | "left", "center", or "right" |
| maintainAspectRatio | boolean? | Preserve aspect ratio (default: true) |
{ "type": "image", "source": "data:image/png;base64,iVBOR...", "width": "60mm", "alignment": "center" }line
Horizontal rule or absolutely positioned line with optional dash patterns.
| Property | Type | Description |
|---|---|---|
| color | string? | Line color (default: black) |
| strokeWidth | string? | Thickness (e.g., "1pt") |
| dashPattern | string? | Dash values (e.g., "4,2") |
| x1, y1 | string? | Start point (absolute) |
| x2, y2 | string? | End point (absolute) |
// Horizontal rule
{ "type": "line", "color": "#CCCCCC", "strokeWidth": "1pt" }
// Dashed
{ "type": "line", "color": "#003366", "strokeWidth": "2pt", "dashPattern": "6,3" }
// Absolute diagonal
{ "type": "line", "x1": "10mm", "y1": "10mm", "x2": "100mm", "y2": "50mm", "color": "#FF0000" }rectangle
Filled or stroked rectangle with optional rounded corners.
| Property | Type | Description |
|---|---|---|
| width | string? | Width (default: available width) |
| height | string? | Height (default: 20pt) |
| fillColor | string? | Interior fill color |
| strokeColor | string? | Border color |
| strokeWidth | string? | Border thickness (e.g., "1pt") |
| cornerRadius | string? | Rounded corners (e.g., "3mm") |
{ "type": "rectangle", "width": "100mm", "height": "50mm", "fillColor": "#EEE", "strokeColor": "#003366", "cornerRadius": "3mm" }Style System#
R4PDF uses a three-level style resolution. Define reusable styles in a root dictionary, apply them via style, and override with direct properties.
Priority (lowest → highest)
Style Properties
| Property | Type | Description |
|---|---|---|
| fontFamily | string? | Font name (e.g., "Helvetica") |
| fontSize | number? | Font size in points |
| fontWeight | string? | "normal" or "bold" |
| fontStyle | string? | "normal" or "italic" |
| color | string? | Text/foreground color |
| backgroundColor | string? | Background fill color |
| alignment | string? | "left", "center", "right", "justify" |
| lineHeight | number? | Line spacing multiplier |
| padding | string? | Universal padding (e.g., "5mm") |
| border | object? | { width, color, type } |
{
"styles": {
"heading": { "fontSize": 20, "fontWeight": "bold", "color": "#003366" },
"body": { "fontSize": 11, "color": "#333", "lineHeight": 1.5 },
"caption": { "fontSize": 9, "color": "#888", "fontStyle": "italic" }
},
"pages": [{
"body": {
"elements": [
{ "type": "text", "text": "Title", "style": "heading" },
{ "type": "paragraph", "content": "Body text...", "style": "body" },
// Direct property overrides a named style:
{ "type": "text", "text": "Red Title", "style": "heading", "color": "#CC0000" }
]
}
}]
}Color Formats#
All color properties accept multiple formats:
| Property | Type | Description |
|---|---|---|
| #RRGGBB | string | Standard hex — "#003366" |
| #RGB | string | Shorthand — "#F00" → #FF0000 |
| #RRGGBBAA | string | With alpha — "#003366FF" |
| Named | string | CSS-style: red, blue, transparent, etc. |
Supported Named Colors
Fonts#
R4PDF uses Liberation Sans as the default and maps common aliases automatically. All four variants supported: Regular, Bold, Italic, Bold Italic.
Font Aliases
These names all resolve to Liberation Sans:
// All produce the same result:
{ "type": "text", "text": "Hello", "fontFamily": "Helvetica" }
{ "type": "text", "text": "Hello", "fontFamily": "Arial" }
// Weight & style:
{ "type": "text", "text": "Bold", "fontWeight": "bold" }
{ "type": "text", "text": "Italic", "fontStyle": "italic" }Measurement Units#
All size values accept a number + unit suffix. Bare numbers default to points.
| Property | Type | Description |
|---|---|---|
| mm | Millimeters | 1mm = 2.835pt |
| cm | Centimeters | 1cm = 28.346pt |
| in | Inches | 1in = 72pt |
| pt | Points | Baseline unit |
| px | Pixels (96 DPI) | 1px = 0.75pt |
"-5mm", "2.5cm".Page Sizes & Orientation#
Page Sizes
Orientation
Default Margins
| Property | Type | Description |
|---|---|---|
| top | "20mm" | Top margin |
| bottom | "20mm" | Bottom margin |
| left | "15mm" | Left margin |
| right | "15mm" | Right margin |
{
"settings": { "pageSize": "A4", "orientation": "Portrait" },
"pages": [
{ "body": { "elements": [{ "type": "text", "text": "Portrait A4" }] } },
{
"settings": { "pageSize": "A3", "orientation": "Landscape" },
"body": { "elements": [{ "type": "text", "text": "Landscape A3" }] }
}
]
}Page Number Placeholders#
Use these tokens in text elements — they are replaced automatically during rendering:
| Property | Type | Description |
|---|---|---|
| {pageNumber} | token | Current page number (1-based) |
| {pageCount} | token | Total number of pages |
"footer": {
"height": "15mm",
"elements": [
{ "type": "line", "color": "#CCC", "strokeWidth": "0.5pt" },
{ "type": "text", "text": "Page {pageNumber} of {pageCount}", "alignment": "center", "fontSize": 9 }
]
}API Reference#
All methods live on PdfGenerator. The pipeline: Data Binding → Parsing → Rendering → Output.
using R4PDF;
var generator = new PdfGenerator();
// ── byte[] ───────────────────────────────────────────────
byte[] pdf = generator.Generate(template);
byte[] pdf = generator.Generate(template, data);
// ── Stream ───────────────────────────────────────────────
generator.GenerateToStream(template, stream);
generator.GenerateToStream(template, data, stream);
// ── File ─────────────────────────────────────────────────
generator.GenerateToFile(template, "output.pdf");
generator.GenerateToFile(template, data, "output.pdf");byte[]In-memory PDF. Ideal for API responses.
voidWrite to an existing Stream.
voidWrite to disk. Simplest for batch jobs.
Full Example: Invoice#
A complete invoice demonstrating styles, headers/footers, data binding, tables, and page numbers:
{
"metadata": { "title": "Invoice #2024-001", "author": "Acme Corp" },
"settings": {
"pageSize": "A4",
"margins": { "top": "20mm", "bottom": "20mm", "left": "15mm", "right": "15mm" }
},
"styles": {
"title": { "fontSize": 24, "fontWeight": "bold", "color": "#003366" },
"subtitle": { "fontSize": 14, "color": "#666" },
"label": { "fontSize": 10, "fontWeight": "bold", "color": "#333" },
"value": { "fontSize": 10, "color": "#000" },
"footer": { "fontSize": 9, "color": "#999", "alignment": "center" }
},
"pages": [{
"header": {
"height": "40mm",
"elements": [
{ "type": "text", "text": "ACME CORPORATION", "style": "title" },
{ "type": "text", "text": "123 Business Ave, New York, NY", "style": "subtitle" },
{ "type": "line", "color": "#003366", "strokeWidth": "2pt" }
]
},
"body": {
"elements": [
{ "type": "text", "text": "INVOICE", "fontSize": 20, "fontWeight": "bold", "color": "#003366" },
{ "type": "text", "text": "Invoice #: ${invoice.number}", "style": "value" },
{ "type": "text", "text": "Date: ${invoice.date}", "style": "value" },
{ "type": "text", "text": "" },
{ "type": "text", "text": "Bill To:", "style": "label" },
{ "type": "text", "text": "${customer.name}", "style": "value" },
{ "type": "text", "text": "" },
{
"type": "table",
"columns": [
{ "name": "Description", "width": "45%" },
{ "name": "Qty", "width": "15%", "alignment": "center" },
{ "name": "Price","width": "20%", "alignment": "right" },
{ "name": "Total","width": "20%", "alignment": "right" }
],
"rows": [
{ "cells": ["Web Development", "40", "$150", "$6,000"] },
{ "cells": ["UI/UX Design", "20", "$120", "$2,400"] },
{ "cells": ["Project Mgmt", "10", "$100", "$1,000"] }
],
"headerStyle": { "backgroundColor": "#003366", "color": "#FFF", "fontWeight": "bold" },
"alternateRowColors": true,
"alternateColor": "#F0F4F8"
},
{ "type": "line", "color": "#CCC", "strokeWidth": "0.5pt" },
{ "type": "text", "text": "Total: $9,400.00", "fontSize": 14, "fontWeight": "bold", "alignment": "right", "color": "#003366" }
]
},
"footer": {
"height": "20mm",
"elements": [
{ "type": "line", "color": "#CCC", "strokeWidth": "0.5pt" },
{ "type": "text", "text": "Thank you for your business!", "style": "footer" },
{ "type": "text", "text": "Page {pageNumber} of {pageCount}", "style": "footer" }
]
}
}]
}using R4PDF;
var generator = new PdfGenerator();
var template = File.ReadAllText("invoice-template.json");
var data = """
{
"invoice": { "number": "2024-001", "date": "2024-12-01" },
"customer": { "name": "John Doe" }
}
""";
generator.GenerateToFile(template, data, "invoice.pdf");Error Handling#
R4PDF wraps internal errors in PdfGenerationException:
| Property | Type | Description |
|---|---|---|
| Invalid JSON | JsonException | Template fails JSON validation |
| No pages | JsonException | Template has zero pages |
| Missing font | FileNotFoundException | Liberation Sans not installed |
| Missing image | FileNotFoundException | Image path doesn't exist |
| Invalid unit | FormatException | Unrecognized format (e.g., "20xyz") |
| Invalid color | FormatException | Unrecognized color (e.g., "#ZZZ") |
using R4PDF;
using R4PDF.Exceptions;
try
{
var generator = new PdfGenerator();
byte[] pdf = generator.Generate(template, data);
}
catch (PdfGenerationException ex)
{
Console.WriteLine($"PDF generation failed: {ex.Message}");
// ex.InnerException contains the original error
}