simple_pdf_generator 0.2.4
simple_pdf_generator: ^0.2.4 copied to clipboard
A lightweight Flutter package to generate PDFs from structured data.
simple_pdf_generator #
A lightweight Flutter package for generating PDFs from structured data with minimal boilerplate.
Overview #
Building PDFs in Flutter often means a lot of repetitive layout code: tables, alignment, row formatting, headers, and footers. This package focuses on that workflow so you can pass structured data (for example maps or API models) and get a usable PDF with a small, readable API.
What it helps with #
- Less repetitive PDF layout code
- Tables built from lists and optional column mapping
- Consistent header and footer blocks
- Faster reporting, billing, and export features
- Built-in minimal invoice PDFs (
SimplePdf.invoice)
Features #
- Simple
SimplePdf.generateAPI plusSimplePdf.invoicefor structured invoices (reuses the same engine) - Multiple tables in a single PDF (
tables: List<PdfTable>), or mixed layout viasections: List<PdfSection>(PdfTable,PdfPlainTextBlockfor borderless text blocks, andPdfTableRowfor side‑by‑side tables) - Tables from structured data with optional
mapperfor dynamic or nested sources - Per-table summary sections (
summaryHeaders+summaryData) - Per-table styling:
- Table header styling (
PdfTableHeaderStyle) - Table cell styling (
PdfTableCellStyle) - Summary styling (
PdfSummaryStyle)
- Table header styling (
- Multilingual table text support (Unicode) with the package theme font
- Table body cells can mix text and images (
PdfTableCell, or shorthandString/Uint8Listin row maps) - Page orientation support: portrait (default) and landscape
- Configurable header (
title,subtitle,extra) and optional footer (PdfFooter:textplus optionaltrailingLinesfor stacked notes) - Small surface area and a thin dependency on
pdf
Installation #
Add simple_pdf_generator to your pubspec.yaml.
From pub.dev:
dependencies:
simple_pdf_generator: ^0.2.4
Run flutter pub get.
Usage #
Import the library:
import 'package:simple_pdf_generator/simple_pdf_generator.dart';
Basic example #
When your rows are already maps whose keys match the column headers:
final pdf = await SimplePdf.generate(
header: PdfHeader(
title: 'My Organization',
subtitle: 'Summary Report',
extra: '01/01/2026 – 10/01/2026',
),
tables: [
PdfTable(
headers: ['Name', 'Age'],
data: [
{'Name': 'John', 'Age': 28},
{'Name': 'Alice', 'Age': 24},
],
),
],
footer: PdfFooter(text: 'Generated by simple_pdf_generator'),
);
Invoices (SimplePdf.invoice) #
Use SimplePdf.invoice when you want a minimal, predictable invoice without hand-building PdfTable rows. It calls the same pipeline as generate internally: document header, a plain-text “Bill To” block (no table borders), a single line-items table (Description, Quantity, Price, Amount), then a compact footer.
Models: InvoiceData and InvoiceItem (exported from this library).
| Field | Notes |
|---|---|
| Required | companyName, customerName, items (InvoiceItem: name, quantity, price) |
| Optional | invoiceNumber, date |
| Customer (optional) | customerAddress (supports \n for multiple lines), customerPhone |
| Footer (optional) | amountInWords — shown below the numeric total as Amount in words: … |
footerNotes — free text (e.g. office locations, tagline); use \n for multiple lines |
Also in generate: you can use PdfPlainTextBlock in sections anywhere you need paragraph-style copy without table chrome. PdfFooter supports trailingLines for any document, not only invoices.
final pdf = await SimplePdf.invoice(
data: InvoiceData(
companyName: 'Acme Ltd',
customerName: 'Jane Buyer',
customerAddress: '12 High St\nLondon EC1',
customerPhone: '+44 20 0000 0000',
invoiceNumber: 'INV-1042',
date: DateTime.now(),
amountInWords: 'One thousand two hundred only',
footerNotes: 'Acme Ltd — London & Manchester\nwww.example.com',
items: const [
InvoiceItem(name: 'Consulting (hours)', quantity: 8, price: 150),
InvoiceItem(name: 'License', quantity: 1, price: 0),
],
),
);
final bytes = await pdf.save();
invoice accepts the same optional theme, pageLandscape, and pageFormat arguments as generate. Invalid data (empty items, bad quantities/prices, blank required strings) throws ArgumentError.
Coming soon: richer invoice and receipt layouts (tax lines, discounts, logos, stronger styling) and other advanced templates—see Roadmap.
Multilingual text + image cells in tables #
Tables are now designed for mixed content:
String/PdfTableCell.text(...)for normal Unicode text (multilingual)PdfTableCell.image(...)or rawUint8Listfor image cells in any column
Recommended canonical form: use PdfTableCell so each column’s intent is clear.
- Text (Unicode / multilingual):
PdfTableCell.text('...')or a plainString. Text uses the same document theme as today (bundled Noto by default), so multilingual content renders as normal text in tables. - Images:
PdfTableCell.image(uint8List, maxWidth: …, maxHeight: …, fit: …). OmittingmaxWidth/maxHeightapplies defaults of 40×40 PDF points so rows stay compact.fitis a FlutterBoxFitand is mapped to thepdflayout engine (defaultcontain). - Shorthand: a raw
Uint8Listin the row map is treated as image bytes with those same default bounds.
Example with mixed columns (Label, Photo, Amount):
final logoPng = await File('logo.png').readAsBytes();
final pdf = await SimplePdf.generate(
header: PdfHeader(title: 'Report'),
tables: [
PdfTable(
headers: ['Label', 'Photo', 'Amount'],
data: [
{
'Label': 'Line item',
'Photo': PdfTableCell.image(logoPng, maxWidth: 32, maxHeight: 32),
'Amount': r'$10.00',
},
{
'Label': 'Another row',
'Photo': logoPng, // same as image cell with default 40×40 max size
'Amount': '5.00',
},
],
),
],
);
String-only tables are unchanged and still use the existing fast text-table path.
In short: existing string tables keep working without changes, and now the same table can also include image cells when needed.
Using a mapper #
Use mapper when your source data does not match header names (for example API or database records):
final pdf = await SimplePdf.generate(
header: PdfHeader(title: 'Report'),
tables: [
PdfTable(
headers: ['Date', 'Amount'],
data: apiResponse,
mapper: (item) => {
'Date': item['date'].toString(),
'Amount': item['amount'].toString(),
},
),
],
);
Page orientation (portrait / landscape) #
SimplePdf.generate supports both portrait and landscape pages:
- Portrait (default): no extra flag needed
- Landscape: set
pageLandscape: true - Custom page format: pass
pageFormat; when provided, it overridespageLandscape
final pdf = await SimplePdf.generate(
header: PdfHeader(title: 'Landscape report'),
tables: [
PdfTable(
headers: ['Name', 'Address', 'Present', 'Total Days', 'Attd.'],
data: rows,
),
],
pageLandscape: true, // A4 landscape
);
Side-by-side tables (PdfTableRow) #
Use sections when you need a full-width table and then several tables on one horizontal row (equal width per table, with gaps). Each PdfTable still owns its own headerStyle, cellStyle, and summary.
- Pass
sections: [ ... ]where each element is aPdfTableor aPdfTableRow. - If
sectionsis non-null and not empty, it is used andtables/tableare ignored. PdfTableRowoptions:horizontalGap,verticalPaddingBefore,verticalPaddingAfter(defaults: 8 / 12 / 12 pt). Normal vertical spacing between sections still applies (kSimplePdfTableSpacing).- Validation: before layout, each table’s estimated minimum width is compared to its share of the page body width (
pageFormat.availableWidth). If any table does not fit, generation throwsStateErrorwith:
Tables exceed available width in PdfTableRow. Reduce columns or number of tables. - Tables inside a row cannot use
startOnNewPage: true.
final pdf = await SimplePdf.generate(
header: PdfHeader(title: 'Dashboard'),
sections: [
PdfTable(
headers: ['Metric', 'Value'],
data: const [
{'Metric': 'Total', 'Value': '100'},
],
),
PdfTableRow(
tables: [
PdfTable(headers: ['A'], data: const [{'A': '1'}]),
PdfTable(headers: ['B'], data: const [{'B': '2'}]),
],
),
PdfTable(
headers: ['Note'],
data: const [{'Note': 'Full width again'}],
),
],
);
Sample PDF mixing full-width tables with 2-up and 3-up PdfTableRow sections:

Multiple tables + per-table summary #
final pdf = await SimplePdf.generate(
header: PdfHeader(title: 'Daily Report'),
tables: [
PdfTable(
headers: ['Date', 'Qty'],
data: dailyRows,
),
PdfTable(
headers: ['Item', 'Amount'],
data: billingRows,
summaryHeaders: const ['Label', 'Value'],
summaryData: const [
['Subtotal', '4500'],
['Tax', '225'],
['Total', '4725'],
],
),
],
);
Multi-table report example (with per-table summaries) #
final pdf = await SimplePdf.generate(
header: PdfHeader(
title: 'Springfield School',
subtitle: 'Students Report',
extra: 'Term 1 - 2026',
),
tables: [
PdfTable(
headers: ['Student', 'Math', 'Science', 'English', 'Total'],
data: marksRows,
summaryHeaders: const ['Metric', 'Value'],
summaryData: const [
['Students', '2'],
['Avg Math', '86.5'],
['Avg Science', '80.0'],
['Avg English', '83.5'],
['Avg Total / Student', '250.0'],
],
),
PdfTable(
headers: ['Student', 'Present', 'Total Days', 'Attendance %'],
data: attendanceRows,
summaryHeaders: const ['Metric', 'Value'],
summaryData: const [
['Students', '2'],
['Total Present Days', '82'],
['Total School Days', '90'],
['Overall Attendance %', '91.1%'],
],
),
],
footer: PdfFooter(text: 'Generated by simple_pdf_generator'),
);
Styling (header, cells, and summary) #
final pdf = await SimplePdf.generate(
header: PdfHeader(title: 'Styled Report'),
tables: [
PdfTable(
headers: ['A', 'B'],
data: rows,
headerStyle: const PdfTableHeaderStyle(
backgroundColor: SimplePdfColor.blue100,
textColor: SimplePdfColor.blue900,
),
cellStyle: const PdfTableCellStyle(
fontSize: 12,
textColor: SimplePdfColor.grey900,
fontFamily: PdfFontFamily.poppins,
),
summaryHeaders: const ['Label', 'Value'],
summaryData: const [
['Total', '5000'],
['Tax', '250'],
],
summaryStyle: const PdfSummaryStyle(
headerStyle: PdfSummaryHeaderStyle(
backgroundColor: SimplePdfColor.grey100,
textColor: SimplePdfColor.grey700,
),
cellStyle: PdfSummaryCellStyle(
labelColor: SimplePdfColor.grey800,
valueColor: SimplePdfColor.blue900,
),
backgroundColor: SimplePdfColor.grey50,
fontFamily: PdfFontFamily.lato,
),
),
],
);
generate returns a Document from the pdf package. Persist it with save():
import 'dart:io';
// ...
final bytes = await pdf.save();
final file = File('${directory.path}/report.pdf');
await file.writeAsBytes(bytes);
To open the file on device, you can use packages such as path_provider and open_file_safe. See the example/ app in this repository for a full flow.
Example output #
The following sample was generated with the example app: header, tabular data (with a mapper), and footer.

Multi-table example with per-table summaries:

Multilingual table example (Unicode text in table cells):

PdfTableRow demo (full-width tables plus side-by-side tables in one row):

More advanced formats and templates are on the way—see Roadmap.
API summary #
| Type | Role |
|---|---|
SimplePdf |
generate(...) builds general PDFs; invoice(...) builds a minimal invoice from InvoiceData |
InvoiceData / InvoiceItem |
Structured input for SimplePdf.invoice |
PdfHeader |
title (required), optional subtitle, extra |
PdfSection |
Base type: PdfTable, PdfPlainTextBlock, or PdfTableRow for sections |
PdfPlainTextBlock |
Optional title + lines: borderless stacked text (e.g. “Bill To”) |
PdfTable |
headers, data, optional mapper, optional per-table summary + styling |
PdfTableRow |
tables: several PdfTables in one horizontal row (equal width) |
PdfTableCell |
text or image cell for mixed text/PNG (or String / Uint8List in maps) |
PdfFooter |
Optional text; optional trailingLines (extra stacked lines) |
Roadmap #
- Advanced formats (coming soon): richer invoices and receipts (tax, discounts, logos, layout presets), plus more document templates beyond the current minimal invoice
- Sorting and richer formatting
- Multi-language support
- More layout options (alignment, spacing, section layouts)
Contributing #
Issues and pull requests are welcome.
License #
MIT License. See LICENSE.