Files
James MaesandClaude Sonnet 5.5 ab3f357599 build: publish the vendored OpenHTML module at the family version and flatten the reactor root
The qrun publisher declares one version per reactor and the capture step looks for
target/<artifact>-<root version>.jar, so the vendored OpenHTML module (own version
1.1.105-qqq-p1) could not be published with the family. Keep the GAV group and artifact
(com.kingsrook.thirdparty:openhtmltopdf-pdfbox), make its version the family ${revision}
(default 0.1.0-SNAPSHOT, like the other modules), flatten its consumer POM, and point
renderer at ${revision}. Java sources and renderer behaviour are unchanged.

The module keeps its explicit LGPL-2.1-or-later license and records upstream provenance
(tag 1.1.105, commit 54b31598, patch level p1, upstream project URL and scm) in properties,
url and scm; it does not inherit the Apache license of the reactor. The hash-pinned
complete-source archive is regenerated with the same module/pom.xml (inputs.sha256 updated;
module/pom.xml is again identical to the module pom) and build-openhtml.sh derives the
artifact names from the module revision. The reactor root is itself a pom-packaging module
the publisher checks, so it gets the flatten plugin (otherwise ${revision} stays unresolved).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 17:10:18 -06:00

12 KiB
Raw Permalink Blame History

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.

Source build

The PDF output dependency is a separately built, narrowly patched OpenHTML module under com.kingsrook.thirdparty:openhtmltopdf-pdfbox:<family revision> (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. 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.

{
  "protocol": "qbit-document-renderer/1",
  "requestId": "generation-attempt-id",
  "operation": "RENDER",
  "format": "PDF",
  "source": "<h1>${title}</h1>",
  "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.1.0", catalogVersion: "qqq-barcodes-9", mediaType, base64, SHA-256, decoded byte length and pageCount. Validation returns no document bytes. Errors return outcome: "ERROR", code: "RENDER_FAILED"; no source/context or exception stack is included. 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.

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.

<style>
  body { font-family: UserFont; font-size: 10pt; }
  table { width: 100%; border-collapse: collapse; }
  td { border: 1px solid #ccc; padding: 4mm; }
</style>
<h1>Order ${order.id}</h1>
<img src="asset:logo">
<table>
<#list items as item>
  <tr><td>${item.name}</td><td>${item.quantity}</td></tr>
</#list>
</table>
<qqq-barcode type="code128" value="${order.id}" scale="2" quiet="12"></qqq-barcode>

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.

{
  "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:

{"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. 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.

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. 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 and the CATALOG response for modes and typed options; physical fitting (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.