JavaScript Rendering
SPAs, interactive charts, and API-hydrated dashboards render in EuroPDF the same way they render in a browser. The pipeline has two stages:
- Chromium preprocessing — a full headless Chrome instance executes your JavaScript. Frameworks hydrate, charts render,
fetch()calls resolve. When the page signals it is ready, the final DOM is captured. - PrinceXML — the captured DOM is passed to Prince, which handles paged-media layout: page breaks, running headers and footers, named pages, footnotes, PDF/UA tagging, font embedding.
The point of running both is that Chrome-based PDF services give you JavaScript but no real paged-media layout, while Prince on its own has paged-media layout but only an ES5 scripted runtime with no network, no timers, and no event loop — enough for TOC generation and DOM transforms, not enough to hydrate a React or Vue app. EuroPDF runs the hydration in Chrome and the layout in Prince.
A complete example
<script src="https://cdn.jsdelivr.net/npm/highcharts@12.5.0/highcharts.min.js"></script>
...
<div id="revenue-chart" style="height: 260px;"></div>
<script>
var isExport = !!window.EuroPDF;
Highcharts.chart('revenue-chart', {
chart: { type: 'area' },
title: { text: null },
xAxis: { categories: ['Aug','Sep','Oct','Nov','Dec','Jan'] },
plotOptions: { series: { animation: !isExport } },
series: [
{ name: 'Recurring', data: [156000, 169000, 182000, 195000, 208000, 227000] },
{ name: 'One-time', data: [36000, 33000, 44000, 41000, 48000, 55000] }
]
});
</script>The Playground example renders three Highcharts charts (area, pie, column) client-side during chromium preprocessing, then captures the resulting SVG and hands it to Prince. window.EuroPDF is set during preprocessing — the snippet uses it to disable chart animations during PDF generation so the capture happens on a finished frame, not mid-animation.
Try it now
Test it instantly in the Playground — no sign-up required.
JavaScript is enabled on this example.
Configuring JavaScript
EuroPDF exposes two separate JavaScript layers via the API:
javascript: true— enables the chromium preprocessing layer. This is the one you want for SPA rendering, Chart.js, and dynamic content. It runs full modern JavaScript (whatever current Chrome supports).prince_options[javascript]: true— enables Prince’s native ES5 runtime for PDF-layout-time scripting (access to PDF objects, late-binding document metadata, TOC generation from the final layout).
For almost all modern use cases, you want the first one. See the JavaScript preprocessing docs for the full API reference and options.
More examples
Delayed rendering via EuroPDF.readyToRender()
<script>
let ready = false;
EuroPDF.readyToRender = () => ready;
setTimeout(() => {
document.querySelector('.stat').textContent = "Loaded 1,234 records";
ready = true;
}, 2000);
</script>Override EuroPDF.readyToRender() to delay the DOM capture until your async content has settled.
Dynamic tables from JSON
<table><thead><tr><th>Region</th><th>Revenue</th></tr></thead><tbody></tbody></table>
<script>
const rows = [{ region: "Germany", revenue: 480000 }, /* ... */];
const tbody = document.querySelector("tbody");
rows.forEach(r => {
const tr = document.createElement("tr");
tr.innerHTML = "<td>" + r.region + "</td><td>" + r.revenue + "</td>";
tbody.appendChild(tr);
});
</script>Generate tables, lists, or entire sections from data at render time. The same pattern works with any framework that outputs to the DOM.
For the complete JavaScript preprocessing API reference, see JavaScript preprocessing in the developer docs.