JavaScript
A document that builds itself with scripts prints what its scripts made.
With options.javascript: true, the document's scripts run before it prints, over the engine's own document (the one it prints), with its own layout answering what they measure. What they could not do is said in the response.
curl https://api.reflowpdf.com/v1/pdf \
-H "Authorization: Bearer $REFLOWPDF_API_KEY" \
-H "Content-Type: application/json" \
-d @report.json -o report.pdf # {"html": "…<script>…</script>…", "options": {"javascript": true}}const pdf = await reflow.pdf.create({ html, options: { javascript: true } })
console.log(pdf.scripts) // { run, errors, skipped, modules, stopped, console }pdf = client.pdf.create(html=html, options={"javascript": True})
print(pdf.scripts)What runs#
- Classic scripts, in the document's order (HTML §4.12.1): inline, and external by an
https:ordata:address, fetched as the document's images are (public addresses, up to 5 MB, 20 s; none withallow_remote: false), up to 50 a document. A relativesrcresolves from the document's<base href>:options.base_url, or the page's own address when it is printed byurl. - Scripts the scripts insert run too, as in a browser: a created
<script>appended to the document (inline at once, external when fetched), and whatdocument.writewrites while a script runs. Markup set byinnerHTMLdoes not run its scripts, as in a browser. - Then
DOMContentLoadedandload, promise jobs, timers, animation frames, resize observers and idle callbacks — until nothing is left to do; thenbeforeprint, and the queues again. Time is a clock that starts at the moment of the print:new Date()is the print's date, and a timer does not wait for real time. - Not run: modules (
type="module", named inscripts.modules) and data blocks (application/json,application/ld+json,text/template— scripts read them, as they should).
The page the scripts see#
- The window is the page area of the first sheet:
innerWidthis its width in CSS pixels (A4 with 20 mm margins: 643),matchMedia('print')matches andmatchMedia('screen')does not: the same media queries as the document's style sheets. A chart that sizes itself to its container is drawn at the width it prints at. - Layout is the engine's own:
getBoundingClientRect,offsetWidth,getComputedStyleanswer from the layout that prints, with the same fonts, external style sheets and images. Heights are of one continuous page — sheets are cut after the scripts. navigator.languageis the document'slang(en-USwithout one);navigator.onLinesays whether the print may fetch;devicePixelRatiois 1 — a print's canvas is vector (below), so no chart needs more pixels.
What scripts can use#
| The document | The DOM: elements with their own interfaces (HTMLFormElement, SVGTextElement, …), attributes and attributes, text and comments, innerHTML, selectors, events, <template>, SVG and MathML elements (createElementNS), TreeWalker, MutationObserver, DOMParser, XMLSerializer, document.implementation |
| Its styles | getComputedStyle, style sheets and their rules (CSSOM), CSS.supports, @font-face and FontFace by script, matchMedia |
| Its layout | getBoundingClientRect, offsetWidth and the rest, elementsFromPoint, ResizeObserver; in SVG, getBBox, getComputedTextLength and getNumberOfChars, measured with the faces the text prints in, HTML inside <foreignObject> laid out as it prints |
| Canvas | getContext('2d'): paths, arcs, curves, roundRect, fills and strokes with dashes, linear and radial gradients, patterns, clipping, shadows, transforms, Path2D, drawImage, putImageData, text with measureText, printed as vector graphics, the text as outlines of the same faces it is measured with |
| The window | setTimeout, requestAnimationFrame, requestIdleCallback, Promise, fetch and XMLHttpRequest (GET, below), URL, URLSearchParams, TextEncoder/TextDecoder, structuredClone, Blob, FileReader, FormData, URL.createObjectURL (a blob: image prints), crypto.getRandomValues, localStorage (empty, for this print), history.pushState, MessageChannel, AbortController, console (kept: scripts.console) |
| Languages | Intl with the Unicode CLDR data: NumberFormat (currencies, percentages, compact and scientific notation, units of length, mass, time, area and volume), DateTimeFormat with IANA time zones, PluralRules, Collator and localeCompare, ListFormat, RelativeTimeFormat, DisplayNames, Segmenter, Locale; toLocaleString of numbers and dates |
Languages and dates#
- The default locale is the document's
lang(en-USwithout one) and the default time zone is UTC: a document that formats with no locale is formatted in its own language, the same on every print. - Formats come from the Unicode CLDR through ICU4X. They are the CLDR's, so a few differ from a browser's ICU by a detail: a numeric date in some languages keeps its zero padding (
02.11.2026in German,2026/11/02in Japanese). - A unit the data has no names for (bytes, temperatures, speeds, angles) is a
NotSupportedErrornaming it, not a bare number.
Fetching data#
A script may fetch what the document itself could: fetch() and XMLHttpRequest read data: addresses and, unless allow_remote is false, public https addresses, anonymously, with the same guard as the document's images: up to 50 a print, 5 MB each, 25 s in all. Only GET and HEAD: a print sends nothing. A relative address resolves from the document's <base href>. A response that is not a 2xx is not delivered: for the script it is a network error, as is an address that cannot be reached.
Libraries#
Checked by printing their own examples:
| Chart.js · D3 · Highcharts · ECharts (SVG and canvas renderers) · Plotly · ApexCharts · Mermaid (HTML and SVG labels) · JsBarcode · qrcode-generator · KaTeX · highlight.js · marked · Handlebars · Day.js · Vue · Alpine.js |
- What a print has no use for is not there: workers, WebGL, WebSocket, IndexedDB, media playback; dialogs answer as with no one to show them to (
alertreturns,confirmisfalse,promptisnull). - Reading a canvas's pixels (
getImageData,toDataURL) is not available: the canvas is vector, it has no pixels; a script that tries gets aNotSupportedErrornaming it. So does any other ability the engine lacks: it is named inscripts.errors, never answered with a guess. - Limits: about 15 seconds of tight work (400 000 interpreter steps), 30 seconds in all, 256 MB. Past one, the scripts stop where they are (
scripts.stoppedsays which) and the document prints as they left it. - The engine in your own pages (
@reflowpdf/engine) does not run scripts: run them in the page, then print its HTML.
The response#
In the JSON response, scripts; beside the file, ReflowPDF-Scripts, the same object as percent-encoded JSON:
{
"run": 4,
"errors": [
"script #6: notDefined is not defined"
],
"error_count": 1,
"skipped": [],
"modules": [],
"stopped": null,
"console": [
"warn: using fallback data"
]
}An error does not stop the print: each script runs, as in a browser, and the document prints as the scripts left it.