Export Formats
Templates can be exported to PDF, SVG, and PNG.
PDF export
Section titled “PDF export”The PDF export supports all standards that Typst has to offer. The default in Oicana is PDF/A-3b (PDF 1.7).
You can configure the default PDF standards to use when exporting a given template in the template’s manifest file.
[tool.oicana.export.pdf]standards = ["ua-1"]The manifest above would configure this template to produce PDF files for Universal Access.
Tagged PDFs
Section titled “Tagged PDFs”By default, Oicana produces tagged (accessible) PDFs. You can turn tagging off in the template’s manifest:
[tool.oicana.export.pdf]tagged = falseCombining standards
Section titled “Combining standards”The standards array accepts three kinds of entries (see the Typst docs for a complete list of supported standards):
- Versions:
1.4,1.5,1.6,1.7,2.0 - PDF/A profiles: every
a-*entry (e.g.a-2b,a-3b,a-4) - PDF/UA:
ua-1
You can list at most one of each kind, and they all have to share a compatible base PDF version. In particular, a PDF/A profile and PDF/UA-1 combine into an accessible, archivable document when both build on the same PDF version. For example, PDF/A-3b and PDF/UA-1 (both based on PDF 1.7):
[tool.oicana.export.pdf]standards = ["ua-1", "a-3b"]This is exactly the combination our invoice example template exports, and what an accessible e-invoice needs.
Incompatible combinations are rejected. For example, ["a-4", "ua-1"] fails because PDF/A-4 builds on PDF 2.0 while PDF/UA-1 requires PDF 1.7 or earlier. Listing two PDF/A profiles or two versions is rejected as well. oicana validate catches invalid combinations in your manifest, and oicana test catches documents that do not meet the standards they declare.
Embedded files and e-invoices
Section titled “Embedded files and e-invoices”Oicana’s PDF export can attach files to the document and write custom XMP metadata. Together with the default PDF/A-3b standard, this is what e-invoice formats such as ZUGFeRD and Factur-X require: a human-readable PDF/A-3b document with the structured invoice XML embedded as an associated file and the matching metadata declared in the document’s XMP.
PNG export
Section titled “PNG export”In many scenarios, PNG export is an easy option for previews.
Oicana integrations allow configuring the pixels per point in a PNG export. A smaller ratio leads to faster file generation and smaller files, but lower resolution. The default is 1px/pt. The parameter is called pixelsPerPt (pixels_per_pt in Python and Rust) and comes before the page range on every PNG export method, as in the examples below.
To keep memory usage in check, a PNG export is limited to 256 million pixels by default, which is about 1 GB of memory. The budget covers the per-page buffers as well as the merged canvas they are combined into, so an export costs roughly twice its output pixels. That corresponds to roughly 14 A4 pages at 300 DPI, or a single A4 page at about 1150 DPI. A large document combined with a high pixels-per-point ratio can hit this limit. In that case you can still export pages individually.
SVG export
Section titled “SVG export”Like PNG, a multi-page export produces a single file with the pages stacked vertically, separated by the same gap. A two-page A4 document comes out as one SVG with a viewBox covering both pages. Export pages individually (see below) if you need one file per page.
SVG has no resolution setting, since it is vector output, and none of the PNG pixel budget applies to it.
Exporting specific pages
Section titled “Exporting specific pages”Every export method accepts an optional page range, so you can export just a part of a document instead of the whole thing. Page indices are 0-based and inclusive: the range 0–2 exports the first three pages. Both bounds are optional. Omitting the start exports from the first page, omitting the end exports through the last page.
Each integration exposes this as a pages argument on the export methods, together with a PageRange helper to construct the range. Method names follow each language’s conventions, for example exportPdf in Node.js, export_pdf in Python and Rust, and ExportPdf in C#.
import { CompilationMode, PageRange, Template } from '@oicana/browser';
// The first three pagesconst excerpt = template.exportPdf( jsonInputs, blobInputs, CompilationMode.Production, PageRange.of(0, 2),);
// The third page through the end of the documentconst rest = template.exportPdf( jsonInputs, blobInputs, CompilationMode.Production, PageRange.of(2),);
// The first page as a PNG at twice the default resolutionconst preview = template.exportPng( jsonInputs, blobInputs, CompilationMode.Production, 2.0, PageRange.single(0),);using Oicana.Config;
var options = new CompilationOptions(CompilationMode.Production);
// The first three pagesvar excerpt = template.ExportPdf(jsonInputs, blobInputs, options, PageRange.Of(0, 2));
// The third page through the end of the documentvar rest = template.ExportPdf(jsonInputs, blobInputs, options, PageRange.Of(2));
// The first page as a PNG at twice the default resolutionvar preview = template.ExportPng(jsonInputs, blobInputs, options, 2.0f, PageRange.Single(0));import com.oicana.CompilationMode;import com.oicana.PageRange;
// The first three pagesbyte[] excerpt = template.exportPdf( jsonInputs, blobInputs, CompilationMode.PRODUCTION, PageRange.of(0, 2));
// The third page through the end of the documentbyte[] rest = template.exportPdf( jsonInputs, blobInputs, CompilationMode.PRODUCTION, PageRange.of(2, null));
// The first page as a PNG at twice the default resolutionbyte[] preview = template.exportPng( jsonInputs, blobInputs, CompilationMode.PRODUCTION, 2.0f, PageRange.single(0));import { CompilationMode, PageRange, Template } from '@oicana/node';
// The first three pagesconst excerpt = template.exportPdf( jsonInputs, blobInputs, CompilationMode.Production, PageRange.of(0, 2),);
// The third page through the end of the documentconst rest = template.exportPdf( jsonInputs, blobInputs, CompilationMode.Production, PageRange.of(2),);
// The first page as a PNG at twice the default resolutionconst preview = template.exportPng( jsonInputs, blobInputs, CompilationMode.Production, 2.0, PageRange.single(0),);use Oicana\PageRange;
// The first three pages$excerpt = $template->exportPdf( jsonInputs: $jsonInputs, pages: PageRange::of(0, 2),);
// The third page through the end of the document$rest = $template->exportPdf( jsonInputs: $jsonInputs, pages: PageRange::of(2),);
// The first page as a PNG at twice the default resolution$preview = $template->exportPng( jsonInputs: $jsonInputs, pixelsPerPt: 2.0, pages: PageRange::single(0),);from oicana import PageRange
# The first three pagesexcerpt = template.export_pdf(json_inputs=json_inputs, pages=PageRange.of(0, 2))
# The third page through the end of the documentrest = template.export_pdf(json_inputs=json_inputs, pages=PageRange.of(2))
# The first page as a PNG at twice the default resolutionpreview = template.export_png( json_inputs=json_inputs, pixels_per_pt=2.0, pages=PageRange.single(0),)use oicana::export::pages::PageRange;use oicana::export::pdf::export_pdf;use oicana::export::png::export_png;
let result = template.compile(inputs)?;
// The first three pageslet excerpt = export_pdf( &result.document, &template, template.manifest().pdf_standards(), template.manifest().pdf_tagged(), Some(&PageRange { start: Some(0), end: Some(2) }),)?;
// The third page through the end of the documentlet rest = export_pdf( &result.document, &template, template.manifest().pdf_standards(), template.manifest().pdf_tagged(), Some(&PageRange::from(2)),)?;
// The first page as a PNG at twice the default resolutionlet preview = export_png(&result.document, 2.0, Some(&PageRange::single(0)))?;Rust builds a range with the PageRange struct or its single, from and to constructors, and passes None for the whole document.
PNG and SVG export sit behind Cargo features:
oicana = { version = "0.8.0", features = ["png", "svg"] }Reusing a compilation
Section titled “Reusing a compilation”The export methods above compile the template and export it in a single call. When you need several exports of the same inputs, for example a full PDF plus a per-page PNG preview, compiling once and exporting repeatedly avoids the redundant compilation work.
Call compile instead of an export method to get a compiled document handle. It exposes the same export methods and reports its page count, so you can export individual pages or page ranges without recompiling. Release the handle when you are done to free memory.
import { PageRange, Template } from '@oicana/browser';
const document = template.compile(jsonInputs, blobInputs);try { const pdf = document.exportPdf(); const thumbnails = Array.from({ length: document.pageCount }, (_, page) => document.exportPng(2.0, PageRange.single(page)), );} finally { document.dispose();}CompiledDocument implements Symbol.dispose, so using document = template.compile(...) releases it for you. TypeScript needs ESNext.Disposable in the lib of your tsconfig.json for that; the Vite react-ts scaffold ships "lib": ["ES2023", "DOM"] and reports Cannot find global type 'Disposable' until you add it.
using Oicana.Config;
using var document = template.Compile( jsonInputs, blobInputs, new CompilationOptions(CompilationMode.Production));
var pdf = document.ExportPdf();var thumbnails = Enumerable .Range(0, document.PageCount) .Select(page => document.ExportPng(2.0f, PageRange.Single(page))) .ToList();import com.oicana.CompiledDocument;import com.oicana.PageRange;
try (CompiledDocument document = template.compile(jsonInputs, blobInputs)) { byte[] pdf = document.exportPdf();
for (int page = 0; page < document.pageCount(); page++) { byte[] thumbnail = document.exportPng(2.0f, PageRange.single(page)); }}import { PageRange, Template } from '@oicana/node';
using document = await template.compileAsync(jsonInputs, blobInputs);
const pdf = document.exportPdf();const thumbnails = Array.from({ length: document.pageCount }, (_, page) => document.exportPng(2.0, PageRange.single(page)),);using frees the document at the end of the scope. It is TypeScript syntax here: tsc compiles it down for you, but plain JavaScript needs Node 24, so call document.dispose() in a finally block if you are not compiling through TypeScript. See the Node.js async guide for when to prefer the Async variants.
use Oicana\PageRange;
$document = $template->compile(jsonInputs: $jsonInputs);try { $pdf = $document->exportPdf();
for ($page = 0; $page < $document->pageCount(); $page++) { $thumbnail = $document->exportPng(2.0, PageRange::single($page)); }} finally { $document->close();}from oicana import PageRange
with template.compile(json_inputs=json_inputs) as document: pdf = document.export_pdf() thumbnails = [ document.export_png(2.0, pages=PageRange.single(page)) for page in range(len(document.pages)) ]use oicana::export::pages::PageRange;use oicana::export::pdf::export_pdf;use oicana::export::png::export_png;
let result = template.compile(inputs)?;
let pdf = export_pdf( &result.document, &template, template.manifest().pdf_standards(), template.manifest().pdf_tagged(), None,)?;
let cover = export_png(&result.document, 2.0, Some(&PageRange::single(0)))?;