# Isolated document renderer This standalone Java CLI renders user-authored PDF layouts and typed ZPL label scenes. It has no QQQ classes, database access, storage credentials, HTTP service, or script broker. The application authorizes and supplies exact retained asset bytes and prepared JSON context. User templates must run in a separately qualified disposable sandbox; never add this renderer to the application JVM as an in-process fallback. `mvnw.cmd -B verify` builds the CLI, runs Checkstyle and fixture tests, and copies runtime JARs into `target/lib`. `target/qbit-document-renderer-0.1.0-SNAPSHOT.jar` is the entry point. Java compilation targets 21 with selected warnings as errors. The container entry requires Java `21.0.13` and `QQQ_RENDERER_MODE=container`; the environment marker is only an accidental-use guard. It does not establish isolation. No renderer image or runtime is qualified yet; see [qualification.json](qualification.json). ## Source build The PDF output dependency is a separately built, narrowly patched OpenHTML module under `com.kingsrook.thirdparty:openhtmltopdf-pdfbox: (upstream 1.1.105 + QQQ patch p1)`. Build it from the retained source into an isolated Maven repository before a local renderer build; use that same repository with `-Dmaven.repo.local=...`. See [the source build instructions](vendor/openhtml-1.1.105-qqq-p1/README.md). Use `clean verify` when changing this dependency so `target/lib` cannot retain the old output-module JAR. Docker builds the source and runs its fifteen vendor regressions before compiling the renderer. Existing sealed image evidence does not qualify this new dependency; the exact-image background corpus must pass. ## Protocol One UTF-8 JSON object followed by a newline enters stdin, and one JSON response followed by a newline exits stdout. No worker connection is reused. The host must enforce deadline, cgroup limits, input/output bounds, cancellation, container ownership and durable cleanup. The request is limited to 16 MiB before parsing; duplicate keys, trailing JSON, overlong strings and excessive nesting are rejected. ```json { "protocol": "qbit-document-renderer/1", "requestId": "generation-attempt-id", "operation": "RENDER", "format": "PDF", "source": "

${title}

", "stock": { "widthMicrons": 101600, "heightMicrons": 152400, "topMicrons": 5000, "rightMicrons": 5000, "bottomMicrons": 5000, "leftMicrons": 5000, "dpi": 203 }, "context": { "title": "Packing slip" }, "assets": [], "maximumBytes": 20971520 } ``` `operation` is `RENDER`, `VALIDATE`, or `CATALOG`. A catalog request needs only protocol, requestId and operation and returns versioned barcode IDs and typed option metadata. `VALIDATE` compiles PDF template syntax or checks the ZPL scene structure plus asset identity. It does not evaluate arbitrary context, certify future layout fit, or promise that every source option will render with every record. Production publication should also render representative fixtures inside the qualified boundary. A successful render returns protocol, requestId, `outcome: "OK"`, `rendererVersion: "qqq-renderer-0.2.0"`, `catalogVersion: "qqq-barcodes-9"`, mediaType, base64, SHA-256, decoded byte length, pageCount and `diagnostics` (a list, empty except for a version 2 scene). Validation returns no document bytes. Errors return `outcome: "ERROR", code: "RENDER_FAILED"`; no source/context or exception stack is included. A version 2 scene refusal returns its own code and one diagnostic as `detail` (see "ZPL scene 2"). Renderer `0.2.0` adds, all optional: `"targetDpi"` (203, 300 or 600; the resolution a version 2 scene is rendered at, by default the stock's `dpi`; a version 1 scene with a target other than its stock's `dpi` is refused as `TARGET_REQUIRES_SCENE_2`) and `"diagnostics": "COLLECT"` (keep every element's refusal instead of stopping at the first; the outcome is then `REFUSED` when there is at least one, and the answer carries the diagnostics and no document: no `mediaType`, `base64`, `sha256`, `length` or `pageCount`, so a host that looks for `base64` without reading the outcome finds nothing to print). The operations `INSPECT_ASSET` and `UPGRADE_SCENE` are routed and answer `UNSUPPORTED_OPERATION` until their packages exist. A host bridge that accepts only `qqq-renderer-0.1.0` does not accept these answers: upgrade the host library before the image. The host must independently check request identity, response shape, decoded size/hash, format policy, successful process termination and container removal. Worker-reported metadata is untrusted. An external parent must not persist raw stderr containing dependency diagnostics. **Compatibility of version 1 answers.** The ZPL bytes of a version 1 scene are unchanged; the corpus in `src/test/resources/compat-v1` holds them byte for byte. The answer envelope around them changed in two ways in `0.2.0`, for a request a `0.1.0` host already sends. Every `OK` answer gains `diagnostics: []`: six fields for `VALIDATE` and eleven for `RENDER`, where there were five and ten. And a ZPL source that is not readable JSON, has no `schema` or has an unknown `schema` now answers `code: "SCENE_INVALID"` with a `detail` diagnostic (pointer `""`, or `/schema`), where `0.1.0` answered the opaque `RENDER_FAILED`. Every other version 1 refusal keeps its code. Nothing in a released host depended on `RENDER_FAILED` for those three cases: it is not a `GenerationPorts.FailureCode`, so `bridges/renderer` rejects such an answer as `WORKER_ERROR_REJECTED`, exactly as it rejects every `0.2.0` answer today (it accepts only `qqq-renderer-0.1.0` and the exact `0.1.0` field counts). The bridge that accepts `0.2.0` must take the extra `diagnostics` field, `detail` on an error, the `REFUSED` outcome, and `SCENE_INVALID` and the other diagnostic codes as failure codes. ## PDF authoring PDF sources use FreeMarker 2.3.35 with enforced HTML autoescaping. Plain JSON objects, lists and scalars are the root model. Includes have no loader content, locale lookup is disabled, Java bean wrapping/API access and class construction are disabled. These are defense-in-depth settings; FreeMarker is not a hostile-code sandbox. Complex record/API logic belongs in the separate published TypeScript context script. Layouts support the OpenHTMLtoPDF paged CSS model: physical stock sizes, margins, tables, repeating headers/footers, page breaks, absolute positioning, borders, images and retained TrueType fonts. This is a CSS 2.1-oriented print renderer, not a browser; flex/grid, JavaScript, SVG, OpenType/CFF fonts, interactive forms and active links are not supported. Every output page must match the stock's dimensions. Images and fonts are resolved exclusively from this invocation's manifest; unknown resources fail the job. CSS does not gain filesystem or network URL access. ```html

Order ${order.id}

<#list items as item>
${item.name}${item.quantity}
``` Barcode elements must be explicitly closed and empty; self-closing custom HTML tags can absorb following content and are rejected when that happens. Their dimensions are set from bitmap pixels and stock DPI, preserving integer module scale at the selected print resolution. Authors can position them but should not apply CSS transforms that distort bars. Optional `options` is a JSON object encoded as an HTML attribute, for example `options='{"mode":"MICRO"}'` for PDF417. Human-readable captions are ordinary author-controlled text. Assets contain `name`, `sha256`, `mediaType`, and `base64`. Names are local identifiers, not paths. Allowed media are PNG, JPEG and TrueType (`font/ttf`). Fonts also require `fontFamily` and optionally `fontWeight` (100–900) and `fontStyle` (`normal`/`italic`). The host obtains bytes through authorized retained-file APIs, checks the pinned file version/hash, and supplies them offline. Font usage rights remain part of asset intake. Raw SVG, external fonts and image URLs are rejected. ## ZPL authoring ZPL sources are JSON scenes with schema `qqq-zpl-scene/1`, **not raw ZPL templates**. There is no raw-command escape hatch. Coordinates and sizes are integer printer dots, and stock DPI is 203, 300 or 600. ```json { "schema": "qqq-zpl-scene/1", "elements": [ {"type":"text","x":20,"y":20,"width":12,"height":24,"valuePath":"/order/id"}, {"type":"box","x":20,"y":60,"width":500,"height":2,"thickness":2}, {"type":"image","x":20,"y":90,"asset":"logo"}, {"type":"barcode","x":20,"y":150,"symbology":"qr","scale":4,"quiet":4,"value":"SHIPMENT-17"} ] } ``` Text/barcodes require exactly one literal `value` or JSON Pointer `valuePath`; only scalar values are accepted. Barcode `options` is the same typed object as PDF. Boxes provide outlines or lines. Images print at their supplied pixel dimensions; preprocess retained assets to the intended printer resolution. Existing `text` uses the printer's font 0 with explicit width/height. The additive `rasterText` element renders dynamic record text using one exact retained TrueType face, without installing fonts on the printer or host: ```json {"type":"rasterText","x":20,"y":250,"fontAsset":"headingItalic","fontSize":32,"width":500,"height":120,"valuePath":"/record/customerName","align":"left","rotation":0,"threshold":128} ``` The `fontAsset` must name a supplied `font/ttf` asset. Choose the actual regular/bold/italic asset; this path does not synthesize a face or search installed fonts. All size and position units are integer printer dots. `fontSize` is 1–999. `width` and `height` are the unrotated layout box, each 1–4096 and at most 4,000,000 pixels together. `rotation` is clockwise 0, 90, 180 or 270; `x`/`y` locate the rotated box's top-left, and the complete final box must fit the stock. `align` is `left`, `center` or `right`; its default is `left`. Optional `lineHeight` is the baseline interval in dots (1–4096); omission uses the font's rounded-up line metrics. Small explicit intervals can intentionally overlap lines. Text automatically wraps using Java's supported shaping/bidirectional layout and the chosen physical face. Explicit LF starts a new line; other control characters and unpaired surrogates are rejected. There is no universal script/locale guarantee: missing glyphs are refused, and representative language/font fixtures remain required. Text is limited to 1,024 UTF-8 bytes and 128 laid-out lines per element. Empty text yields a white box even at 1×1; spaces and explicit blank lines consume line metrics and must fit. Logical advance, line descent and actual shaped ink/pixel bounds are checked before drawing; overflow is an error, never truncation. Black antialiased glyphs on white are binarized using `threshold` (1–254, default128) before the existing graphic writer; no color commands are added. See [the rasterText schema](schema/raster-text.schema.json). Unknown properties on this element fail closed to catch misspelled layout controls. Before any text font/raster allocation, the scene is bounded to 64 `rasterText` elements and 16,000,000 total unrotated raster pixels. Rendering additionally resolves all dynamic values and checks their combined 16 KiB UTF-8 limit before any such allocation. `VALIDATE` checks static layout/assets and literal text but cannot certify dynamic values, glyph coverage or future fit; render representative record fixtures before publication. Decoded fonts are cached only within one invocation. The new path requires a renderer image containing this implementation; sealed catalog4 image evidence predates it and does not qualify the addition. Only `^XA`, `^XZ`, `^PW`, `^LL`, `^LH`, `^CI28`, `^FO`, `^A0N`, `^FH_`, `^FD`, `^FS`, `^GB` and `^GFA` are generated. Text is fully hex-encoded UTF-8, so `^`, `~` and `_` in record values cannot become new commands. Barcode/image raster fields use fixed uppercase hex and split below the 99,999-byte field limit. Placement uses overflow-safe bounds checks. No stored formats, downloads, network commands, persistent settings, cuts or resets are emitted. The printing boundary still needs independent output grammar enforcement because a compromised worker must not be trusted to report safe printer bytes. ## ZPL scene 2 A source with schema `qqq-zpl-scene/2` is a layout in physical units that renders at 203, 300 or 600 dpi. The worker chooses the code path from `schema` alone: version 1 is unchanged and frozen, and any other source is refused as `SCENE_INVALID`. The schema is `schema/zpl-scene-2.schema.json`; the design is `docs/design/LABEL-LAYOUT-FONTS-PREVIEW.md`. ```json { "schema": "qqq-zpl-scene/2", "design": { "width": "4in", "height": "1in" }, "elements": [ { "id": "frame", "type": "box", "thickness": "0.5pt" }, { "id": "panel", "type": "group", "anchor": "center", "width": "50%", "height": "60%", "children": [ { "id": "mark", "type": "box", "anchor": "bottom-right", "x": "1mm", "y": "1mm", "width": "6mm", "height": "3mm", "fill": true } ] }, { "id": "rule", "type": "box", "anchor": "bottom-center", "y": "2mm", "width": "80%", "height": "0.2mm", "thickness": "0.2mm" } ], "overrides": [ { "when": { "dpi": [203] }, "set": { "frame": { "thickness": "1dot" } } } ] } ``` - **Lengths** are strings of a number (up to three decimals) and a unit: `mm`, `in`, `pt` (1/72 in), `%` (of the container's width for `x` and `width`, of its height for `y` and `height`) and `dot`. The most a length may be is 2 m (`2000mm`, `78.74in`, `5669.292pt`), `32000dot` or `100%`; anything longer, up to the largest number the syntax can write, is `SCENE_INVALID` at its pointer. A physical length becomes whole micrometres, then dots at the render target: a position to the nearest dot, a box width or height rounded down, a thickness to the nearest dot and never less than one. A `dot` length outside an override raises the warning `DEVICE_UNIT_USED`. - **Coordinates** start at the top left of the printable area, the stock less its margins. In dots the printable area is `floor((width - left - right) x dpi)` wide and `floor((height - top - bottom) x dpi)` high, which is how a box size is rounded, and it starts at `ceil(left x dpi)`, `ceil(top x dpi)`, moved back by one dot where it would otherwise pass the page edge. So a box of exactly the printable width and height fits at 203, 300 and 600 dpi alike. No ink is ever a whole dot inside a margin; the right and bottom edges may pass theirs by a fraction of a dot (less than 0.125 mm at 203 dpi). `design` states the stock the layout was drawn for; the request's stock must match it within 0.5 mm on each side (`STOCK_MISMATCH`). - **Every element** has a unique `id` (`[A-Za-z][A-Za-z0-9_-]{0,31}`), a `type`, an `anchor` (one of nine points, default `top-left`: the same point of the box is placed on that point of its container), `x` and `y` (distances inward from the anchored edge; on a centred axis a shift that may be negative), `width` and `height` (omitted: the rest of the container), and `align` and `valign` (where content sits in the box). - **`box`** has `thickness` and optional `fill`; a line is a box whose width or height is its thickness. **`group`** has `children`, placed relative to its box; groups nest four deep. A scene holds at most 1000 elements, and its elements may paint at most sixteen times its printable area in all, an overlap counted each time: a filled box counts its area, an outline its four bars, a group nothing. The element that would pass that is refused as `SCENE_INVALID` at its pointer, with its box and `measured.paintedPercent` against `limit.paintedPercent` of 1600. A real label paints less than twice its area; the limit holds the worst scene to sixteen fills of the canvas where 1000 were possible. - **`text`** draws one value in a face bundled with the renderer: `qqq-sans`, `qqq-sans-bold` (IBM Plex Sans), `qqq-condensed-bold` (IBM Plex Sans Condensed) and `qqq-mono`, `qqq-mono-bold` (JetBrains Mono). There is no printer font in version 2 and no installed font is used; any other `font` is `FONT_UNKNOWN`, and a request that supplies an asset whose name begins `qqq-` is refused as `RESERVED_FONT_NAME`. `font` and `size` may come from the scene's `defaults`. The value is `value` or, read from the record when rendering, `valuePath` (text as it is, `true` or `false`, or a number in plain decimal digits with no exponent and no fraction that is only zeros: `1.0E10` prints `10000000000`, `2.50` prints `2.5`; anything else is `VALUE_MISSING`); at most 1024 characters, each of which the face must draw as one glyph from left to right (no control or format characters, combining marks, shaping or right-to-left scripts: `FONT_MISSING_GLYPH`, with the count). - **Size and measurement.** `size` is the em as a physical length from `1pt` to `1000pt`, not rounded to dots. A text is measured before it is drawn, by integer arithmetic on the face's own numbers, so the measurement is the same on every machine. A line's box is its pen advance widened to hold the ink of every character in it, plus two dots on each inked side for the rasteriser. A text whose ink the rasteriser puts outside that box is not printed in part and does not fail the render: the one element is refused as `TEXT_DOES_NOT_FIT` with `measured.glyphsOutsideMeasure` and `measured.dotsOutsideMeasure`, when the scene is validated if its value is a literal. No bundled face does so at any size from 1 pt to 1000 pt. The block of lines is placed in the element's box by `align` and `valign`, and each line in the block by `align`. `lineHeight` is the distance between baselines as a multiple of the size (default: the face's own). `rotation` turns the text clockwise in a box given as it appears on the label; `reverse` prints white letters on the box filled black. - **Fit.** In this order: lay the value out at `size` within `fit.maxLines` (default 1); if it does not fit, take the largest size, in steps of 0.25 pt down to `fit.shrinkTo`, at which it does (`TEXT_SHRUNK`); if none does, apply `fit.overflow`. `shrinkTo` defaults to 70% of the size and not below 5 pt. `overflow` is `refuse` (the default: `TEXT_DOES_NOT_FIT`, with the measured width, height, lines and size against the box) or `truncate`, which cuts at a character, ends with an ellipsis and warns `TEXT_TRUNCATED`. At each size a line is broken between words while that fits and at any character when it does not, so a value with no space wraps and a larger size is preferred to whole words (`TEXT_WRAPPED`). A member of `fit` that is wrong is `SCENE_INVALID` at that member (`/elements/3/fit/maxLines`). The size chosen is exactly the one a search of every quarter-point step gives, found without making that search, and all the text of one scene is held to a stated amount of work: 64 million steps, where a character visited while fitting is one step, a character to be drawn is 16 and a glyph the scene draws for the first time at a size is 4096. A scene that passes it is refused as `SCENE_INVALID` at the element that did, with `measured.textWorkSteps` against `limit.textWorkSteps`, the same whether it is validated or rendered and on every machine. That is about a second of a worker; a label of a hundred values takes a few percent of it. - **Proving the fit.** `maxLength` is the longest value in characters. When a scene is validated, that many of the face's widest character must fit whole, each taken to reach as far above, below and beside its advance as the furthest-reaching character of the face; otherwise the scene is refused as `TEXT_DOES_NOT_FIT`. The proof never cuts. An element with `overflow: truncate` whose stated length is not proven is still valid, since cutting is what its author chose, but every validation warns `FIT_NOT_PROVEN` with `measured.provenCharacters`, the longest length that is proven to fit whole, against `limit.characters`: longer values may be cut. A scene that passes cannot be refused at that render target because a value of up to that length does not fit, whichever of the face's characters it holds. The proof is therefore stricter than a typical value needs, and a box that is exactly `lines x lineHeight` high is not proven: leave room for an accent above a capital. When printing, a longer value is `VALUE_TOO_LONG` before any layout. A bound element without `maxLength` warns `FIT_NOT_PROVEN` with no number. Validation reads no record. The proof is for the render target of the request; validate at each resolution the document prints at. - **Legibility.** Glyphs are drawn antialiased and a dot is ink when its grey is darker than `threshold` (1 to 254, default 128), so a higher value prints heavier. `TEXT_SMALL_FOR_DPI` warns when the capitals are under 12 dots high at the render target (about 6 pt at 203 dpi), `TEXT_LIGHT_FACE` when a regular weight is used with capitals under 20 dots (about 10 pt at 203 dpi). A literal is judged at the size it is drawn at; a bound value, when validated, at the size its `maxLength` is proven at, which is the smallest any value is printed at (the floor when the proof fails and the element cuts), and at its stated size when it has no `maxLength`. An override is how a scene answers for one resolution: a bold face or a higher `threshold` at 203 dpi. - **`barcode`** draws one value in a fit-qualified symbology (`code128`, `code39`, `code93`, `itf`, `gs1-128`, `ean13`, `ean8`, `upca`, `upce`, `qr`, `datamatrix`, `pdf417`, `aztec`, default encoder options) into the picture of the label, in whole dots. It states `symbology`, or `symbologyPath` with the `allowedSymbologies` the record may name; and `value`, or `valuePath`. A `gs1-128` value is an element string with each application identifier in square brackets (`[01]00012345678905[21]TEST0001`); the encoder adds the leading FNC1 and the separators. - **Module.** The module, the narrowest bar, is the largest whole number of dots at which the symbol fits its box with its quiet zones and its bars are as tall as its symbology asks. It is never under `minModule` and never over `maxModule`; both go up to a whole dot. Without `minModule` the smallest module is what `barcodeProfile` gives the symbology: | `barcodeProfile` | `ean13`, `ean8`, `upca`, `upce` | `gs1-128`, `itf` | `code128`, `code39`, `code93` | `qr`, `datamatrix`, `pdf417`, `aztec` | |---|---|---|---|---| | `none`: the design's own minimum | 0.25 mm | 0.25 mm | 0.25 mm | 0.375 mm | | `gs1-general`: GS1, general retail point of sale | 0.264 mm | 0.25 mm | 0.25 mm | 0.375 mm | | `gs1-logistics`: GS1, general distribution and logistic units | 0.495 mm | 0.495 mm | 0.25 mm | 0.375 mm | | not stated | as `gs1-general` | as `gs1-logistics` | as `none` | as `none` | In dots at 203, 300 and 600 dpi: 0.25 mm is 2, 3 and 6; 0.264 mm is 3, 4 and 7; 0.375 mm is 3, 5 and 9; 0.495 mm is 4, 6 and 12. The GS1 numbers are those of the symbol specification tables of the GS1 General Specifications; 0.25 mm and 0.375 mm are this renderer's own and are to be confirmed by printing and scanning. Example: a 44-character GS1-128 (a GTIN, a date and a serial number) is 319 modules with its quiet zones. With no profile stated it is held to 4 dots at 203 dpi and needs 1276 dots, 160 mm: it fits no label 4 in wide (812 dots) and is refused. Named `gs1-general` or `none`, in a box of `90mm` (719 dots) its module is `719 / 319 = 2` dots (0.25 mm), the symbol is 638 dots, and in the middle of the box it leaves 40 and 41 dots. - **Quiet zones.** Each symbology has its own, in modules before the first bar and after the last: 10 and 10 for `code128`, `gs1-128`, `code39`, `code93` and `itf`; 11 and 7 for `ean13`; 7 and 7 for `ean8`; 9 and 9 for `upca`; 9 and 7 for `upce`; 4 on every side of a two-dimensional symbol. `quiet` states one number for both sides: at least the larger of the two for every symbology the element may draw, at most 64. - **Height.** The bars of a linear symbol take the whole height of the box across them, and that must be what the symbology asks of bars so wide: 15% of the width of the bars, first to last, and never under 5 mm, for `code128`, `gs1-128`, `code39`, `code93` and `itf` (ISO/IEC 15417, Annex G); 69.24 modules for `ean13`, `upca` and `upce` and 55.24 for `ean8` (GS1 General Specifications). A module that would make the symbol too wide for the height of its box is not chosen, and a box too short at the smallest module is `BARCODE_DOES_NOT_FIT`. The 44-character GS1-128 at 2 dots has bars 598 dots wide and needs 90 dots (11.3 mm at 203 dpi). An EAN-13 at 3 dots needs 208 dots (26 mm), more than a label 1 in high has. An author who accepts shorter bars states `minHeight`: the symbol is drawn, and the label carries the warning `BARCODE_HEIGHT_BELOW_RECOMMENDED` every time, with `measured.heightDots` against `limit.heightDots`. - **Place.** The fitted symbol sits in the middle of its box: for a barcode, and only for a barcode, `align` and `valign` default to `center` and `middle`, so what the box has to spare is quiet zone, the same on both sides. A scene may still state them; the odd dot goes to the right and below. The symbol paints its own white ground, quiet zones included, so ink painted before it cannot close a space. `rotation` turns it clockwise in a box given as it appears on the label; a quarter turn fits the symbol along the height of the box, and the widths and heights in its diagnostics are those of the symbol. - **Nothing over it.** An element later in the list whose ink would enter the symbol or its quiet zones is refused as `QUIET_ZONE_INTRUDED`, when the scene is validated as when it is rendered: `element` and `box` are the later element's, `pointer` is the barcode's, `measured` holds the edges of the ink (`leftDots`, `topDots`, `rightDots`, `bottomDots`) and `limit` the edges of what the barcode keeps. Ink is the box of the element, or the four bars of an outline; a group has none of its own. While the scene is only validated and the record gives the value or the symbology, the symbol is not known and the barcode keeps its whole box. Paint grounds and frames before a barcode, or beside it. - **Does not fit.** A value that does not fit at the minimum module is never drawn smaller. It is `BARCODE_DOES_NOT_FIT`, when the scene is validated as when it is rendered, with `measured.modules`, `measured.quietLeftModules` and `measured.quietRightModules`, `measured.widthDots` and `measured.heightDots` (the dots needed at the minimum), `measured.moduleDots` (the largest module that would fit, which may be 0) against `limit.widthDots` and `limit.heightDots` (the box) and `limit.minModuleDots`. A symbol of more modules than the encoder draws carries `measured.characters` against `limit.modules` of 4096. An author who accepts a smaller module states it (`"minModule": "1dot"`, in an override for the resolution it is meant for): the symbol is drawn and the label carries the warning `BARCODE_MODULE_BELOW_RECOMMENDED` every time. A drawn symbol reports `BARCODE_MODULE` with the same numbers and `measured.moduleMicrometres`. - **Values.** A bound value is text, or a number or true/false as JSON writes it; anything else, or nothing, is `VALUE_MISSING` at the pointer of the property that binds it (`valuePath`, `symbologyPath`, `scannerSymbologyPath`, `aimIdentifierPath`), and more than `maxLength` (or 4000) characters is `VALUE_TOO_LONG`. A label is scanned, so a symbol must never read as something other than its value, and four things the encoder would do are refused before it sees the value. (1) No value, literal or from the record, in any symbology, may hold the two characters `\<`: the encoder reads `\` to `\` as function characters, with which a record could make a GS1-128 of a plain Code 128, set the reader-programming flag or change a character. A function character enters a symbol only through its symbology: `gs1-128` adds its own FNC1. (2) `itf` takes an even number of digits; an odd number is never padded with a zero. (3) `ean13`, `ean8`, `upca` and `upce` take the whole number, check digit included (13, 8, 12 and 8 digits). The check digit is verified. A number without its check digit, or of any other length, is refused, never completed or padded. (4) In a `gs1-128` element string the check digit of every key is verified: application identifiers 00, 01, 02, 03, 253, 255, 402, 410 to 417, 8003, 8006, 8017, 8018 and 8026. A value refused by one of these, or that the symbology cannot hold, is `VALUE_NOT_ENCODABLE` (for `gs1-128`, `GS1_INVALID`) at `value` or `valuePath`, with numbers only and never the value: `measured.characters`; for an odd `itf`, `limit.charactersMultipleOf`; for a retail number, `limit.characters` and, when the check digit is wrong, `checkDigitMatches` 0 against 1; for a GS1 key, `measured.applicationIdentifier` and the same. A symbology the scene does not allow, and a scanner symbology and AIM identifier (`scannerSymbologyPath`, `aimIdentifierPath`, the version 1 scanner profile) that do not name the symbology drawn, are `SCENE_INVALID` at that property. - **Proving the fit.** Validation reads no record. A literal value is fitted when the scene is validated, in every symbology the record may name. For a value bound to the record, `maxLength` is the longest value in characters, and validation fits the widest symbol a value of that length can need in a linear symbology (Code 128: 11 modules for each character and 35; Code 39: 13 and 25; Code 93: 18 and 37; `itf`: 9 for each digit and 9; `gs1-128`: 11 for each character of the element string, brackets included, and 24), refusing the scene as `BARCODE_DOES_NOT_FIT` when that does not fit. For Code 128 and Code 93 the widest is a value of characters from space to tilde. The EAN and UPC symbols are one width and need no length. A two-dimensional symbol is fitted for that many bytes, which refuses a box they cannot fit and proves no more: another value of the same length can need a larger symbol, so its `FIT_NOT_PROVEN` stays. A bound value with no `maxLength`, in a symbology of more than one width, warns `FIT_NOT_PROVEN`. The widest GS1-128 of a length is one of letters, far wider than a label of digits: a 44-character element string is proven only in a box that holds 528 modules. - **Not yet.** `gs1` (an element string built from application identifiers) and `hri` (the human-readable line) are declared; an element that states either is refused as `RENDERER_TOO_OLD`. - **`image`** is parsed and placed by renderer `0.2.0` and then refused as `RENDERER_TOO_OLD`, with the box it would have had, until its package is in the renderer. - **`overrides`** replace properties of declared elements at the listed resolutions and are the only place a scene may depend on resolution. They are checked for all three resolutions whatever the request's target. **`samples`** are accepted and ignored by rendering. - **Output.** The whole label is composed on one 1-bit canvas and emitted as uncompressed `^GFA` fields: rows are cut into bands wherever eight or more blank rows separate ink, each band is trimmed to its inked columns, and a band is continued in a further field at 99,999 bytes. Only `^XA`, `^PW`, `^LL`, `^LH`, `^CI28`, `^FO`, `^GFA`, `^FS` and `^XZ` are emitted. - **Diagnostics.** A refusal is one diagnostic: `code`, `severity`, the `element` id, the element's `box` in dots from the top left of the printable area, and `measured` and `limit` as named integers. `SCENE_INVALID` also carries `pointer`, the JSON pointer of the offending place. A diagnostic names only an id the scene declares and points only through schema property names, array indexes and declared ids, so no record value, sample or unknown property name can leave the worker in one. A stock the printer language cannot address stays the opaque `RENDER_FAILED`, as for version 1. ```json { "code": "ELEMENT_OUTSIDE_PRINTABLE_AREA", "severity": "ERROR", "element": "mark", "box": [719, 0, 79, 39], "measured": { "bottomDots": 39, "leftDots": 719, "rightDots": 798, "topDots": 0 }, "limit": { "bottomDots": 155, "leftDots": 0, "rightDots": 764, "topDots": 0 } } ``` ## Limits Sources: 262,144 characters. Context: depth 16, 10,000 nodes, 65,536 characters per string, 262,144 aggregate text characters. Stock: 1 mm–2 m, valid margins, 72–1200 DPI (ZPL additionally bounds width to 9,999 dots and height to 32,000). Assets: 100 entries, 2 MiB each, 8 MiB aggregate; image dimensions ≤4096 and ≤4 million pixels. Expanded HTML: 2 million characters and 20,000 elements. PDF pages: 1–500, checked after rendering. Output: caller limit, hard maximum 100 MiB; normal host default is 20 MiB. ZPL: 1,000 elements; a version 2 scene paints at most 16 times its printable area. Barcodes: 4,000-character payload, scale 1–16, bounded quiet zones, ≤4 million pixels. The PDF engine and font/image decoders can consume resources before post-render limits fire. A qualified container's independent CPU/memory/PID/wall limits remain mandatory for both validation and rendering. These ceilings are not performance guarantees; load testing and font/locale/large-batch qualification remain outstanding. ## Barcode coverage Catalog `qqq-barcodes-9` offers 71 stable IDs: the 47 concrete OkapiBarcode 0.5.7 encoder families plus Han Xin, rMQR, DotCode and Ultracode through the pinned Zint 2.16.0 provider, and the attributed Code 93i and Co-op 2 of 5 encoders. This is a finite explicit catalog, not a claim to encode every standard. See [BARCODES.md](BARCODES.md) and the `CATALOG` response for modes and typed options; physical fitting ([BARCODE-FIT.md](BARCODE-FIT.md)) covers 13 default-option profiles and every other ID reports fit unsupported. Every family has a pinned upstream codeword/pattern vector and raster test; common variants also have independent ZXing decoding. A 4×6 PDF QR fixture is decoded after rasterization at 203 DPI. This coverage means all encoder families in the pinned library, not every symbology ever invented or every possible library option. Physical verification on actual printers/scanners, carrier/GS1 application rules, grading and every mode/option combination remain separate qualification work. No equivalence substitution occurs for unsupported IDs.