This document describes PagePreview, the canvas library's API for turning a
spread — or a single page inside a spread — into an image. It is intended for
developers who embed Typograph and need thumbnails, contact sheets, a page
picker, or an image to upload to a backend.
PagePreview lives in the canvas library, not the editor, so it is
available to any host: editor, viewer, or a headless integration.
The editor ships as a single bundle (editor.js), so there is no module to
import from — the library's internals have no import path of their own once
bundled. PagePreview is reached through the global typograph object, the same
place as setup() and get_typograph_document():
<script type="text/javascript" src="https://cdn.typograph.nl/editor/latest/scripts/jsColorEngineWeb.js"></script>
<script type="module" src="https://cdn.typograph.nl/editor/latest/editor.js"></script>
const { PagePreview } = window.typograph;
window.typograph is created once the colour engine has initialised, so read it
inside a lifecycle listener rather than at the top level of your script:
document.addEventListener('typograph_editor_ready', () => {
const { PagePreview } = window.typograph;
const spread = window.typograph.get_typograph_document().get_active_page();
const url = PagePreview.data_url(spread, { width: 200 });
});
typograph_editor_ready is fired by the editor. In a non-editor host use
typograph_init_ready, which the canvas library dispatches the moment
window.typograph exists.
Building against the canvas library from source instead of the bundle? Then the ordinary module import works and you do not need the global:
import { PagePreview } from "typograph-canvas/scripts/pagepreview";
// a 512px PNG data URL of the whole spread
const url = PagePreview.data_url(spread);
// 200px wide JPEG
const url = PagePreview.data_url(spread, { width: 200, format: 'jpeg' });
// just the right-hand page of a spread, 300px tall
const url = PagePreview.data_url(spread, { page: 1, height: 300 });
// a Blob for upload
const blob = await PagePreview.blob(spread, { format: 'webp' });
// a drawn <canvas> ready to put on the page
container.appendChild(PagePreview.canvas(spread, { width: 256 }));
A spread is a page container from the document:
const doc = window.typograph.get_typograph_document();
const spread = doc.get_active_page(); // the current one
const spread = doc.pages[0]; // by index
const all = doc.get_pages(); // every spread
width and height are a bounding box, not the output size. The preview is
scaled to fit inside the box and always keeps the spread's width/height
ratio, so normally only one of the two is actually reached.
For an A4 page (210 × 297 mm):
| Options | Output |
|---|---|
| (none) | 362 × 512 — fits the default 512 × 512 box |
{width: 200} |
200 × 283 — height follows from the ratio |
{height: 300} |
212 × 300 — width follows from the ratio |
{width: 400, height: 400} |
283 × 400 — height is the binding constraint |
{width: 100, height: 900} |
100 × 141 — width is the binding constraint |
Give only one axis when you want that axis to be exact and do not care about the other. Give both when the preview has to fit a fixed slot.
There is no upscale limit — ask for {width: 2000} and you get a 2000px-wide
render, drawn at that resolution rather than scaled up from a small one.
PagePreview.size() returns the exact pixel size the image will have without
building or drawing anything. Use it to lay out around a preview instead of
re-deriving the scale yourself:
const { width, height } = PagePreview.size(spread, { width: 400, height: 400 });
slot.style.width = `${width}px`;
slot.style.height = `${height}px`;
A spread contains one or more page records. Pass page with a 0-based index
to render just that page; omit it for the whole spread.
PagePreview.data_url(spread, { page: 0 }); // left-hand page
PagePreview.data_url(spread, { page: 1 }); // right-hand page
To find out how many pages a spread has:
spread.sync_page_records();
const count = spread.page_records.length; // 1 for a single page, 2+ for a spread
An out-of-range index throws:
PagePreview: page 5 does not exist — the spread has 2
Note the sizes differ: a 2-page A4 spread in a 400px box renders as 400 × 283 (landscape), while either of its pages renders as 283 × 400 (portrait).
| Option | Type | Default | Description |
|---|---|---|---|
width |
number |
512 |
Maximum output width in pixels |
height |
number |
512 |
Maximum output height in pixels |
page |
number |
(whole spread) | 0-based index of a single page within the spread |
format |
'png' | 'jpeg' | 'webp' |
'png' |
Encoding |
quality |
number |
0.92 |
0..1. Applies to 'jpeg' and 'webp' only; ignored for 'png' |
Every option is optional. PagePreview.data_url(spread) is valid.
You do not need a background colour. A spread always paints its own opaque
background — a missing or unresolvable fill swatch falls back to Paper — so the
output never contains transparent pixels and 'jpeg', which has no alpha
channel, is safe to use directly.
These build, draw and discard a preview in a single call. Use them unless you need the same preview more than once.
PagePreview.data_url(spread, options?): string
PagePreview.blob(spread, options?): Promise<Blob>
PagePreview.file(spread, name?, options?): Promise<File>
PagePreview.canvas(spread, options?): HTMLCanvasElement
PagePreview.size(spread, options?): { width: number, height: number }
file() names the result after the spread when name is omitted, and picks the
extension from the format ('jpeg' → .jpg):
const file = await PagePreview.file(spread, 'cover', { format: 'jpeg' });
// File { name: "cover.jpg", type: "image/jpeg" }
Pass an explicit name whenever the filename matters. A spread's page_name
often carries a generated suffix — an untitled page yields something like
Untitled_3db29f4b-d155-4884-84b7-370a17f4260c.jpg.
Construct once and repaint when the document changes — the canvas is created a single time.
new PagePreview(spread, options?)
.size // { width, height } — exact output size
.canvas // the backing HTMLCanvasElement
.redraw() // repaint after the spread changed
.to_canvas(): HTMLCanvasElement // redraw, then return the canvas
.to_data_url(format?, quality?): string
.to_blob(format?, quality?): Promise<Blob>
.to_file(name?, format?, quality?): Promise<File>
format and quality given to a method override those from the constructor, so
one instance can emit several encodings:
const preview = new PagePreview(spread, { width: 256 });
const thumb = preview.to_data_url('webp', 0.8);
const upload = await preview.to_blob('jpeg', 0.95);
const { PagePreview } = window.typograph;
window.typograph.get_typograph_document().get_pages().forEach((spread, i) => {
const img = new Image();
img.src = PagePreview.data_url(spread, { width: 120 });
img.title = `Page ${i + 1}`;
strip.appendChild(img);
});
for (const spread of window.typograph.get_typograph_document().get_pages()) {
spread.sync_page_records();
for (let p = 0; p < spread.page_records.length; p++) {
const url = PagePreview.data_url(spread, { page: p, height: 200 });
// …
}
}
const file = await PagePreview.file(spread, spread.page_name, { format: 'jpeg' });
const form = new FormData();
form.append('images[]', file);
await fetch('/your/upload/endpoint', { method: 'POST', body: form });
const preview = new PagePreview(spread, { width: 160 });
container.appendChild(preview.to_canvas());
// call redraw() whenever your application changes the document
preview.redraw();
typograph_edit_done (on document) is fired when a rich-text edit
finishes. It is not a general "document changed" signal, so do not rely on it
alone to keep a thumbnail current.
PagePreview draws the spread as it currently stands. Fonts and images that
have not finished loading are simply missing from the render — no error, just an
incomplete image. This is the most common cause of a blank or half-empty preview.
After opening a document, wait for the load events before taking previews:
// images — on document, like every other Typograph event
document.addEventListener('typograph_page_images_loaded', () => {
const doc = window.typograph.get_typograph_document();
const url = window.typograph.PagePreview.data_url(doc.get_active_page());
});
// fonts — on document
document.addEventListener('typograph_fonts_loaded', () => { /* … */ });
It fires once when the last pending image has painted, and once at open for a document that contains no images — so a single listener covers both cases.
For a freshly opened document you generally want both events. Previews taken during normal editing, after the document has settled, need no special handling.
If you already listen on
window, you do not have to change anything yet. Earlier builds emitted this signal in two other shapes:'typograph_page_images_loaded'onwindow(when the last image finished) and'images_loaded'ondocument(when a document had none to load). Neither could catch both cases, which is why it was unified.Both older shapes are still emitted for backwards compatibility, and now fire in both cases rather than one — so existing code keeps working and gets more coverage than before. They are deprecated and will be removed once no integration depends on them; move to the
documentlistener above when convenient.
canvas() / to_canvas() is detached — you choose where it goes.devicePixelRatio. Ask
for a larger width when you want a sharper image, for example 2 * css_width
for a retina thumbnail.data_url() is synchronous; blob() and file() are async because the
browser encodes off the main thread.