typograph-editor-documentation
    Preparing search index...

    Typograph — Validating a document

    This document describes typograph.validate(): one call that returns everything the library can see wrong with the open document, so a host can show it to a user before they order.

    It is a canvas method on window.typograph, so it works in the viewer bundle as well as the editor — which is where a pre-order check usually lives.

    Source of truth: validate() in the canvas package at src/canvas.ts; the finding shape is document_finding in src/typograph_document.ts.


    const findings = typograph.validate();

    const blocking = findings.filter(f => f.severity === 'error');
    if (blocking.length) showProblems(blocking);

    Ask one question only:

    typograph.validate({ checks: ['text_overflow'] });
    
    {
    severity: 'error', // 'error' | 'warning'
    check: 'text_overflow',
    message: 'Text does not fit in "Body copy"',
    container_index: 5, // which spread
    record_index: 1, // which page of it
    element_uuid: 'a3f1…', // the element at fault
    detail: { … } // check-specific, see below
    }

    container_index / record_index address a page the same way PageSelection.md describes, so a finding can be turned into "go to this page" without searching. Both are absent on a document-wide finding (font_missing), and element_uuid is absent where no single element is at fault (swatch_missing).

    Findings come back in document order — spread by spread, elements as they sit — then the document-wide ones.


    check Severity Reports
    text_overflow error the last frame of a text chain does not fit its text
    image_no_link error an image frame that names no image at all
    image_not_in_manifest warning the link is not in manifest.images; detail.link
    image_load_failed error the browser could not fetch the image; detail.url
    image_is_placeholder warning the image resolved — to a placeholder; detail.placeholder_in
    image_low_resolution warning or error the image prints below 300 dpi; error below 150; detail.dpi
    swatch_missing error a colour used on a spread is not in the document; detail.swatch_uuid
    font_missing warning a font had to be substituted; detail.kind / family / variant
    element_off_page warning an element entirely outside the paper and its bleed
    empty_text_frame warning a text frame holding no text
    autofit_exhausted warning every allowed adjustment was applied and the text still does not fit; detail.spent
    autofit_not_applied warning the frame carries Fit text rules the canvas will not apply; detail.reason
    role_missing warning the element is bound to a permission role this document does not have — its own permissions apply; detail.role_uuid. See EditPermissions.md

    The library reports facts and proposes a severity; what blocks an order is the host's decision. Overflowing text may be fatal for one product and acceptable for another; a substituted font may be worth a warning or nothing at all. This is the same split as typograph_document_changed / is_changed() for saving: the library says what happened, the host owns the policy.

    text_overflow is asked of the last frame of each chain, once per story. Linked frames re-derive their text from the chain root, so the last one is where text runs out of room; asking every frame would report one story several times.

    image_not_in_manifest is a warning, not an error. A link that is not in the manifest can still resolve — the canvas builds a URL by convention, and a link that is itself a URL resolves directly. It is reported because manifest.images is what the PDF publisher looks an image up in, so print may find nothing where the screen looks fine.

    An image frame with no link is reported once. It is not also reported as missing from the manifest: two findings about one absent thing is one too many.

    image_is_placeholder is the one to watch before an order. A placeholder passes every other image check — it has a link, the link is in the document's image list, and it loads, because it is a real picture from a real service. So without this check a document reads as clean while every picture on the page is a grey box with a filename across it.

    Placeholders arrive from three places: a conversion whose image link was broken before the package was made, a conversion run without images, and a POD document, which carries image keys and never URLs — so every POD photo block starts as a placeholder. It is a warning rather than an error because that is the intended state of a template nobody has filled in yet; whether an unfilled slot blocks an order is your decision.

    detail.placeholder_in lists where it was found — element, element_print, manifest_editor, manifest_print, manifest_thumbnail. The manifest's print URL is the one that reaches paper, so a placeholder there is the most serious: the screen can show artwork while print shows the grey box, and vice versa.

    image_low_resolution is the only check whose severity is not fixed. It is one question — is this sharp enough to print — with two degrees, and 149 dpi is a different conversation from 299. The figure is effective resolution: the artwork's own pixels spread over the space it is printed in.

    ⚠️ It is measured against the picture's rect, not the frame's. The frame crops. A 300 dpi photograph cropped to a stamp is still 300 dpi, and judging it by the frame would call it several thousand and wave through artwork that is genuinely soft.

    ⚠️ The pixel count comes from manifest.images[].size — the original's own pixels — never from the copy the editor loaded, which is a web-sized version. detail carries dpi, dpi_x, dpi_y, pixels, placed_mm, and the two thresholds so a host need not hard-code them.

    Three kinds of image are not reported at all, and each for its own reason:

    • a vector, which has no resolution and needs none — reporting one would fail every logo in every document;
    • a placeholder, whose resolution is nobody's intention: both converters size one from the frame it fills, and a POD document is placeholders throughout, so this would error on every POD template while saying nothing about artwork. image_is_placeholder is the finding that belongs there;
    • an image whose pixel count is unknown — "unknown" and "too low" are different answers, and the missing manifest entry behind it is already image_not_in_manifest.

    The same figure is shown in the editor's parameter bar beside the frame's name, red below 150, using the same thresholds — so a panel and an order cannot disagree about where the line is.

    The two autofit_* checks exist because a fit is derived. Nothing about it is stored, so nothing about it can be read off a saved document. A host asking "is this fit to print?" has no other way to learn that the type it is about to buy is 62% of the size the designer set.

    Fit text is a per-frame rule — an ordered list of adjustments the designer allows (break words, smaller type, tighter lines, tighter letters, tighter spaces) which the canvas applies, in order, only as far as it must. The result is recomputed whenever the text or the box changes and is never written into the document, so the authored size stays the authored size. See TextFit.md.

    autofit_exhausted deliberately doubles up with text_overflow, which is an error on the same element. That is the point rather than a duplicate: overflow says the text does not fit, and this says every allowed adjustment was applied first — so raise the allowance and the box is too small are different fixes, made by different people. detail.spent names only the levers that actually moved (point_size_factor, effective_point_size, leading_factor, letter_spacing_em, word_spacing_em, hyphenation), and detail.steps echoes the authored rule.

    autofit_not_applied is the one that is otherwise invisible. A frame linked into a text chain and a frame set to grow to fit its text are both left unfitted by design, so a designer who set a rule on one sees no fit, no explanation and no complaint — the rule silently does nothing. Everything else validate() reports can be seen on the page if you look; this cannot. detail.reason is text_chain or auto_size.

    ⚠️ Neither reports a fit that simply worked. A template designed to be fitted would otherwise report on every frame every time, and a check that fires on correct documents teaches a host to ignore it — which would cost the two states above their audience.

    element_off_page reports only elements entirely outside the paper and bleed. An element overhanging the edge is normal and usually required — that is what bleed is for — so overhang is not reported. Per-side bleed is honoured.

    empty_text_frame treats whitespace as empty, because a placeholder frame carrying a stray space is exactly the case worth reporting: it looks filled in the document JSON and prints nothing. A frame holding a table is not empty — the table is content nobody has typed in yet.

    swatch_missing is reported per spread, with no element_uuid. A colour reference can live in a character run inside a story that spans several frames, so the story — not any one frame — owns it; the spread is the honest unit. detail.swatch_uuid names the missing colour.


    Nothing is composed. A frame's overflow depends on its laid-out text, so answering it outside the canvas would mean laying the text out — which is exactly the cost the library avoids elsewhere. The open document is already composed: every frame on every spread has been through the composer in order to be drawn. validate() reads that and walks elements.

    So it is safe to call on a change, on a button press, or before a save. It is a read: calling it twice returns the same thing and changes nothing.

    ⚠️ The one consequence of reading composed state: an element added programmatically and not yet drawn has no layout, and a text frame in that state will answer that its text fits. In an interactive editor a draw has always happened; a host building a document by API should let a redraw happen before validating.

    This holds for the new checks too. autofit_exhausted reads the overflow the frame already has — the fit was computed when the text or the box last changed, not here — and image_low_resolution is arithmetic on two numbers the document already carries.


    It does not replay get_load_errors(). That list records what went wrong while opening the document, is append-only and is never cleared by editing — so replaying it would keep reporting a font or an image that has since been replaced. Every finding from validate() is re-derived from the document as it stands now.

    get_load_errors() is still there and still useful for "what happened when this file was opened". The two answer different questions.


    function describe(f) {
    const where = f.container_index === undefined
    ? ''
    : ` (spread ${f.container_index + 1}, page ${f.record_index + 1})`;
    return `${f.message}${where}`;
    }

    document.getElementById('order').addEventListener('click', (e) => {
    const findings = typograph.validate();
    const errors = findings.filter(f => f.severity === 'error');
    const warnings = findings.filter(f => f.severity === 'warning');

    // This host's policy: text that does not fit and missing images stop an order;
    // a substituted font, an empty frame or an unfilled image slot is worth showing, not blocking.
    // A shop selling personalised products might well reverse that last one — a placeholder that
    // reaches print is a complaint — which is why the library proposes and you decide.
    //
    // Resolution arrives already graded: below 150 dpi is proposed as an error and below 300 as a
    // warning, so a host that prints large format might raise both and one printing business cards
    // might drop the warning entirely. `f.detail.warn_below` / `error_below` say what was applied.
    if (errors.length) {
    e.preventDefault();
    showBlockingDialog(errors.map(describe));
    return;
    }
    if (warnings.length) showAdvisoryList(warnings.map(describe));
    });
    // A product where overflowing text is the only thing that matters.
    const overflow = typograph.validate({ checks: ['text_overflow'] });
    overflow.forEach(f => markPageProblem(f.container_index, f.record_index));

    typograph.validate() every check, document order
    typograph.validate({checks:[…]}) only those checks
    Returns document_finding[] — severity, check, message, container_index?, record_index?, element_uuid?, detail?
    Checks 12: four about text, five about images, plus colours, fonts and geometry
    Cost a walk over elements; composes nothing
    Decides policy no — the host does