Exporting to PDF¶
The same bundle that produces your Excel file produces your PDF. No second template, no separate styling pass. Pass format="pdf" and the library renders with ReportLab, starting each sheet on a fresh page and paginating long tables automatically.
PDF always paginates, so there's no export_mode to choose; the renderer streams large tables into bounded chunks on its own. Your job is to set the page geometry and, optionally, the fonts.
Prerequisites¶
- A compiled
ReportBundleor a path to a persisted bundle directory (see Compiling Report Bundles) - Font files on disk if using custom fonts (TrueType
.ttfor OpenType.otf)
Implementation¶
1. Configure PDF Options¶
| Option | Type | Default | Description |
|---|---|---|---|
page_size |
str |
"A4" |
"A4", "LETTER", or "LEGAL" |
orientation |
str |
"portrait" |
"portrait" or "landscape" |
margin |
float |
36 |
Page margin in points (0 or greater), applied to all four sides |
streaming_chunk_rows |
int |
50000 |
Rows read per batch for dataframe-content and repeat sections |
fonts |
dict \| None |
None |
Custom TrueType / OpenType font families. See Register Custom Fonts below |
repeat_dataframe_headers |
bool |
False |
Repeat dataframe header rows across paginated table chunks (opt-in) |
column_width_mode |
str |
schema value | "fixed" or "even" for dataframe-content sheets (no "hug" for columns) |
row_height_mode |
str |
schema value | "fixed", "even", or "hug" (PDF supports "hug" for dataframe-content rows) |
default_column_width |
float |
schema value | Same as XLSX |
default_row_height |
float |
schema value | Same as XLSX |
A note on sizing in PDF
For sheets that render dataframe-content, PDF allows row_height_mode="hug" (it auto-fits each streamed chunk) but not column_width_mode="hug"; measuring column widths would mean buffering every row first, which defeats streaming. See Sizing & Styling.
2. Repeat Headers Across Pages¶
By default a dataframe header is written once. When a long table spills across several PDF pages, you often want the column headers to reappear at the top of each page. Set repeat_dataframe_headers=True; headers repeat across later chunks wherever a matching dataframe-header anchor exists.
3. Register Custom Fonts¶
By default, every cell font maps to ReportLab's built-in Helvetica family. To render with your own fonts, pass a fonts dict. The renderer matches each cell's template font.name (case-sensitive) against your keys; unmatched names fall back to Helvetica.
Shorthand: regular only. When you only have a regular weight, give a single path. Bold and italic fall back to it.
mo_dataport.export(
bundle,
"report.pdf",
format="pdf",
fonts={
"Inter": "/path/to/fonts/Inter-Regular.ttf",
},
)
Full variant map. For distinct weights and styles, give a dict per family:
mo_dataport.export(
bundle,
"report.pdf",
format="pdf",
fonts={
"Inter": {
"regular": "/path/to/fonts/Inter-Regular.ttf",
"bold": "/path/to/fonts/Inter-Bold.ttf",
"italic": "/path/to/fonts/Inter-Italic.ttf",
"bold_italic": "/path/to/fonts/Inter-BoldItalic.ttf",
}
},
)
| Key | Required | Falls back to |
|---|---|---|
regular |
Yes | (none) |
bold |
No | regular |
italic |
No | regular |
bold_italic |
No | bold, then regular |
You can register several families in one call:
Font rules:
- Files must exist on disk when
export()is called; a missing path raisesValueError. - Every family must supply a
regularfile; omitting it raisesValueError. - Fonts are registered with ReportLab once per process; re-registering the same path is a harmless no-op.
4. Understand What Renders¶
PDF reproduces the great majority of Excel styling: fonts, fills (solid), alignment (including justify/distributed), borders (drawn around full merged regions), strikethrough, superscript/subscript, and indents. A couple of things are captured in the schema but not drawn in PDF: text_rotation and diagonal borders. The complete matrix is in Sizing & Styling.
Core Concepts¶
1. Why PDF Has No Export Mode¶
PDF always paginates. There is no fidelity-vs-streaming choice because the renderer has no alternative to chunked output: a table taller than a page must be split across pages regardless. Chunking (streaming_chunk_rows) is always active; it controls how many rows are processed at a time, but it is never optional. You configure the page geometry and fonts; the renderer handles breaking and layout.
2. How Automatic and Manual Page Breaks Interact¶
Two independent mechanisms control where pages break. Automatic height-based breaks happen when a rendered chunk fills the printable page area and the next chunk starts on a new page. Manual row breaks (row_page_breaks, set in the template via Page Layout → Breaks and re-resolved at compile time) force a new page at a specific row regardless of how full the current page is. Both apply simultaneously: a table can hit a manual break mid-chunk and a height-based break at the end of a full chunk, and the renderer handles both correctly.
Troubleshooting¶
ValueErrorabout a font. Either a font file path doesn't exist, or a family is missing its requiredregularentry.- My custom font isn't applied.
Matching is case-sensitive against the template cell's
font.name. Confirm the name in Excel matches yourfontskey exactly. - Headers don't repeat on later pages.
Set
repeat_dataframe_headers=Trueand make sure the sheet uses adataframe-headeranchor (not a baredataframe).