Compare commits

..
Author SHA1 Message Date
james.maes fda9ad0de6 Merge pull request 'feat: scene 2 foundations: version 1 compatibility corpus, coverage and fuzz gates, preview ports (P0, P13, P6a)' (#13) from feat/scene2-foundations into develop
build / maven (push) Successful in 10m41s
maven / build (push) Successful in 10m41s
2026-11-12 15:15:18 +00:00
james.maesandClaude Fable 5.2 c6aa0bf6b0 feat(foundation): preview, diagnostic and render target ports with registration hooks (P6a)
build / maven (push) Successful in 6m19s
maven / build (push) Successful in 6m19s
The contracts the preview, bridge and render target packages plug into. No
label prints differently and no existing host adapter changes.

GenerationPorts:
- Diagnostic (with Severity): a code from a closed vocabulary, an author
  element id, a box and named integers. It has no field that holds free text.
- PreviewRequest, PreviewPage and Preview, and IsolatedRenderer.preview as a
  default that refuses, so an adapter written before it compiles unchanged.
- 15 version 2 FailureCode values after the 13 released ones, and
  RenderException(FailureCode, Diagnostic) beside the existing constructor.
- Integer targetDpi on RenderRequest (absent, 203, 300 or 600); the
  seven-argument constructor is kept and nothing reads the value yet.

Registration points, each an empty class that is already called:
GenerationPreviewProcesses, GenerationAssetProcesses,
GenerationUpgradeProcesses and GenerationAssetTables from the producer, and
GenerationPreviewWidget.section(table) from GenerationTables.

Bridge: IsolatedRendererBridge maps any FailureCode name from a 0.1.0 worker
to a typed refusal, so growing the enum alone would have widened what such a
worker can name. The 13 released codes are now the set a 0.1.0 worker may
name; BridgeTest asserts the other 15 stay generic.

Tests: record validation, default refusals, source compatibility of the
existing constructors, and that the producer registers exactly the tables,
processes, choices, widgets and template sections it did before (the lists
were captured from the untouched v1.1.0-RC.2 tag).

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-11 17:28:43 -06:00
james.maesandClaude Fable 5.2 3ba676106f build(coverage): gate renderer and bridge coverage at baseline; seeded mutation campaigns (P13)
Coverage gates:
- renderer and bridges/renderer gain JaCoCo (prepare-agent, report, check at
  verify) with a bundle rule at the measured baseline, rounded down: renderer
  94% instructions and 91% lines, bridge 85% instructions and 82% lines.
- New code is held to 100% line coverage from its first commit: packages
  com.kingsrook.qbits.renderer.scene2 and below, AssetInspector, ZplRaster,
  and the new foundation classes the label layout design names. The rules
  match nothing until those exist, so later packages do not edit these poms.
- The foundation's 85% instruction and 90% class rule is unchanged.

Fuzz scaffolding:
- MutationCampaign in each module's test tree: a seeded, bounded mutation
  harness with no clock and no unseeded randomness; a finding replays from
  its seed and iteration.
- One campaign per module in the normal test phase: what the version 1 scene
  parser accepts is a version 1 scene (checked by a second parser), and what
  the host printer gate accepts holds only the renderer's command set.
- docs/security/fuzzing.md: targets, properties, limits, seeds and replay.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-11 17:28:42 -06:00
james.maesandClaude Fable 5.2 ea776391ca test(renderer): version 1 scene compatibility corpus from v1.1.0-RC.2 (P0)
Version 1 scenes (qqq-zpl-scene/1) are frozen. This adds 42 render requests
and the exact ZPL the released renderer returned for each, so later work can
prove that labels already printing are untouched.

- renderer/src/test/resources/compat-v1: cases, stored ZPL, SHA256SUMS and a
  README with provenance and rules. The bytes were produced by the source of
  tag v1.1.0-RC.2 (e9ffba48), not by this branch, and the same test passed
  against the tag on Temurin 21.0.12 (Windows) and 21.0.9 (the CI image).
- renderer ZplSceneOneCompatibilityTest: fails on the first differing byte,
  on a manifest that does not name exactly the cases, and on a corpus that
  misses a version 1 element type, property or resolution.
- bridges/renderer ZplSceneOneCompatibilityTest: every stored output has its
  manifest hash and passes the unchanged ZplOutputPolicy. It reads the
  renderer's directory in place, so there is one copy of the corpus.

Covered: the four shapes Forge publishes at 203 and 300 dpi (GS1-128 with
fit and quiet, text in font 0, rasterText with fontAsset), the README and
BARCODE-FIT.md examples, and one scene per element and option at 203, 300
and 600 dpi. All values are synthetic.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-11 17:28:25 -06:00
CI 9a355670c0 Bump version to 1.4.0-SNAPSHOT [skip ci] 2026-11-10 23:13:57 +00:00
james.maes 228931a937 Merge pull request 'fix(postgres): accept full_page_writes off on Amazon Aurora PostgreSQL only' (#9) from fix/aurora-full-page-writes into develop
build / maven (push) Successful in 16m12s
maven / build (push) Successful in 16m12s
2026-11-10 22:57:56 +00:00
james.maesandClaude Fable 5.2 cfbf0744f9 fix(postgres): accept full_page_writes off on Amazon Aurora PostgreSQL only
build / maven (push) Successful in 6m14s
maven / build (push) Successful in 6m14s
Generation qualification refused every Amazon Aurora PostgreSQL server:
Aurora always reports full_page_writes = off and has no parameter to change
it, because its storage layer does not write torn pages.

The settings query now also returns
to_regprocedure('aurora_version()') IS NOT NULL (true on Aurora, false on
stock PostgreSQL, never an error). full_page_writes = off is accepted only
when that is true. Stock PostgreSQL must still report it on, and fsync on,
synchronous_commit on or remote_apply and every other check are unchanged
on both. The refusal text now says full-page writes are not required on
Aurora.

Tests run on a real postgres:17 engine whose settings are really changed;
only Aurora is simulated, by creating a function named aurora_version() in
the test database.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-09 20:25:55 -06:00
CI 1dc265647d Bump version to 1.3.0-SNAPSHOT [skip ci] 2026-11-09 02:46:35 +00:00
james.maes e762dac5d5 Merge pull request 'build(renderer): build engine and service images from a released version' (#7) from fix/renderer-images into develop
build / maven (push) Successful in 6m43s
maven / build (push) Successful in 6m43s
2026-11-09 02:39:57 +00:00
james.maesandClaude Sonnet 5.5 e19feb01a8 build(renderer): build the engine and service images from a released version
build / maven (push) Successful in 6m2s
maven / build (push) Successful in 6m2s
Both Dockerfiles copied 0.1.0-SNAPSHOT jars that the reactor no longer produces
(version is ${revision}), so neither built a released version. They now resolve the
released qbit-document-renderer and qqq-remote-renderer-transport (and their runtime
libraries) at an exact, required RENDERER_VERSION / TRANSPORT_VERSION from the
QRun-IO Gitea Maven registry and Maven Central, refusing SNAPSHOT and local
(*forge*) versions. The reviewed hardening is unchanged: digest-pinned
BUILD_IMAGE/JAVA_IMAGE, non-root 65532, read-only-root friendly, Zint built from
vendor/. The reactor build context is no longer needed.

Adds IMAGE-PUBLISH.md (repeatable build/scan/publish steps and the 1.0.0 pins) and
image-pins.sh (prints the gg-forge-cd classpath and SHA256 pins from a published
image). Images for 1.0.0 were built, smoke-tested over mTLS and published to GHCR
under gg-engineering by hand; no workflow publishes images.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 20:07:14 -06:00
CI d8a57607fd Bump version to 1.2.0-SNAPSHOT [skip ci] 2026-11-07 19:19:02 +00:00
james.maes 889296d8f5 Merge pull request 'build(deps): align with Gitea registry (parent 2.2.1, QQQ 4.1.0-RC.26)' (#6) from deps/align-gitea into develop
build / maven (push) Successful in 11m52s
maven / build (push) Successful in 11m52s
2026-11-07 19:07:22 +00:00
james.maesandClaude Opus 5.5 e085859a25 build(deps): move qqq.version into <properties>
build / maven (push) Successful in 7m0s
maven / build (push) Successful in 7m0s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 18:59:59 +00:00
james.maesandClaude Opus 5.5 4f850c2cf3 build(deps): align with Gitea registry (parent 2.2.1, QQQ 4.1.0-RC.26)
build / maven (push) Failing after 9s
maven / build (push) Failing after 9s
bridges/printing/pom.xml: qqq.version: 4.1.0-RC.22 -> 4.1.0-RC.26
bridges/printing/pom.xml: qbit-record-files.version: 0.1.0-RC.1 -> 0.4.0-RC.1
bridges/printing/pom.xml: qbit-printing.version: 1.0.0 -> 1.2.0-RC.1
bridges/record-files/pom.xml: qqq.version: 4.1.0-RC.22 -> 4.1.0-RC.26
bridges/record-files/pom.xml: qbit-record-files.version: 0.1.0-RC.1 -> 0.4.0-RC.1
bridges/scripting/pom.xml: qqq.version: 4.1.0-RC.22 -> 4.1.0-RC.26
bridges/scripting/pom.xml: qbit-scripting.version: 1.0.0 -> 1.1.0-RC.3
foundation/pom.xml: qqq.version: 4.1.0-RC.22 -> 4.1.0-RC.26
pom.xml: parent -> 2.2.1
pom.xml: added qqq.version=4.1.0-RC.26 (parent BOM import)
pom.xml: revision -> 1.1.0-SNAPSHOT

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 18:59:01 +00:00
CI f9adf1d3d3 Bump version to 0.3.0-SNAPSHOT [skip ci] 2026-11-06 23:35:57 +00:00
james.maes 2dcee6fbea Merge pull request 'ci: pin shared workflow to v1' (#4) from ci/pin-v1-develop into develop
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
QRun-IO/ci-policy: native / native-result (push) Successful in 6s
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: ci / original-voyage (push) Skipped
QRun-IO/ci-policy: ci / original-cli (push) Skipped
QRun-IO/ci-policy: ci / ci-result (push) Waiting to run
build / maven (push) Successful in 19m19s
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 9cb8c13124a99d18da83cbcd4faa795859ac9e55] (push) Successful in 9s
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / original-business (push) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Skipped
maven / build (push) Successful in 19m19s
2026-11-06 23:11:03 +00:00
James MaesandClaude Opus 5.5 34a3666bbf Merge develop: adopt the released Document Generation 1.0 changes
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 9cb8c13124a99d18da83cbcd4faa795859ac9e55] (pull_request) Successful in 7s
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
QRun-IO/ci-policy: ci / ci-result (pull_request) Waiting to run
build / maven (push) Successful in 8m5s
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 6s
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
maven / build (push) Successful in 8m5s
develop (PR #5) already fixed the renderer BarcodeDeclaredCapabilityTest
gaps, moved the family to QQQ 4.1.0-RC.22 and pinned the released Record
Files 0.1.0-RC.1 and Scripting 1.0.0, which is what resolves the scripting
bridge's "reviewed core isolation marker metadata" errors from run 6360.
Take develop's implementation and docs wholesale so this branch only
carries its own change: pinning the shared Maven workflow to v1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 16:54:15 -06:00
James MaesandClaude Opus 5.5 6a3153456e build: align the family on QQQ 4.1.0-RC.22
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 9cb8c13124a99d18da83cbcd4faa795859ac9e55] (pull_request) Successful in 6s
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 4s
build / maven (push) Successful in 8m48s
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 5s
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
maven / build (push) Successful in 8m48s
The published qbit-scripting 0.1.0-SNAPSHOT (and its tests jar) is built
against QQQ Core 4.1.0-RC.22, whose Script entity carries the reviewed
executionMode isolation marker. The scripting bridge pinned RC.21 through
dependencyManagement, so ScriptingQBitProducer rejected the core script
table ("Scripting requires the reviewed core isolation marker metadata")
in every PublishedScriptContextAdapterTest. qbit-record-files and
qbit-printing have also moved to RC.22, so move foundation and all bridges
together rather than pinning older sibling snapshots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 16:53:07 -06:00
CI 9073e9bf1c Bump version to 0.2.0-SNAPSHOT [skip ci] 2026-11-06 21:07:16 +00:00
james.maes 98eef612d0 Merge pull request 'Document Generation 1.0: fix renderer tests; pin released Record Files, Scripting and Core RC.22' (#5) from kof22/renderer-fix into develop
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: ci / original-voyage (push) Skipped
QRun-IO/ci-policy: ci / original-cli (push) Skipped
QRun-IO/ci-policy: ci / ci-result (push) Successful in 16s
build / maven (push) Successful in 11m33s
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 539bec410f7926fec3ef7fabe51934a91e0248be] (push) Successful in 12s
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: native / native-result (push) Successful in 14s
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / original-business (push) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Skipped
maven / build (push) Successful in 11m33s
2026-11-06 20:19:06 +00:00
james.maesandClaude Sonnet 5.5 cb420158ba release: prepare Document Generation 1.0 (released Record Files, Scripting, Core RC.22)
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 539bec410f7926fec3ef7fabe51934a91e0248be] (pull_request) Successful in 10s
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 9s
build / maven (push) Successful in 12m21s
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 10s
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
maven / build (push) Successful in 12m21s
Pin QQQ Core 4.1.0-RC.22, Record Files 0.1.0-RC.1 and Scripting 1.0.0 in place
of the SNAPSHOT placeholders, and replace the unreleased-checkpoint text.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-06 13:11:27 -06:00
james.maesandClaude Sonnet 5.5 10b3a716b5 fix(renderer): refuse barHeight where it has no effect; check the provider at the family version
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 539bec410f7926fec3ef7fabe51934a91e0248be] (pull_request) Successful in 13s
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 12s
build / maven (push) Failing after 16m35s
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 11s
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
maven / build (push) Failing after 16m35s
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
barHeight was advertised and accepted for every encoder although the sixteen
fixed-geometry encoders (matrix symbols and postal four-state codes) ignore it.
It is now neither advertised nor accepted for them and is refused with the
closed code UNSUPPORTED_BARCODE_OPTION, as is GS1 DataBar in stacked mode. The
worker maps that code through, and VALIDATE refuses literal unsupported options
before any data exists.

The patched OpenHTML qualification compared against the old literal version;
it now compares against the family version surefire passes in.

All 174 renderer tests pass, including the four that failed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-06 13:11:26 -06:00
James MaesandClaude Opus 5.5 207e3ce034 fix(renderer): make the renderer test suite pass on the shared CI workflow
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 539bec410f7926fec3ef7fabe51934a91e0248be] (pull_request) Successful in 7s
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 6s
build / maven (push) Failing after 10m0s
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 6s
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
maven / build (push) Failing after 9m59s
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
- BackgroundImageQualification expected the patched OpenHTML provider at the
  old fixed version 1.1.105-qqq-p1, but PR #1 publishes the vendored module at
  the family revision. The exact-GAV check now takes the expected version from
  the build's own ${revision} (surefire system property) and fails closed if
  absent; group, artifact, single-class-definition and companion checks are
  unchanged.
- barHeight was advertised and accepted for every Okapi family but silently
  ignored by fixed-geometry encoders. It is now advertised/accepted only where
  it changes the symbol; the 16 fixed-geometry families and gs1-databar in
  STACKED mode refuse it with UNSUPPORTED_BARCODE_OPTION (catalog metadata
  carries notWithMode=STACKED).
- UNSUPPORTED_BARCODE_OPTION now crosses the worker boundary as its closed
  code (foundation already models it), and PDF VALIDATE checks literal
  (template-free) barcode options of in-process encoders.

Resolves the 3 known BarcodeDeclaredCapabilityTest failures and the
PDF_PROVIDER_GAV_MISMATCH error; no tests skipped or weakened.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 11:37:12 -06:00
james.maesandClaude Opus 5.5 063b1e8136 ci: pin shared workflow to v1
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
QRun-IO/ci-policy: ci / ci-result (pull_request) Failing after 10s
build / maven (push) Failing after 10m5s
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 1e25bb5a243dfdcd95d4b9a4f780200ff8c8778a] (pull_request) Successful in 13s
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 13s
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Failing after 10s
maven / build (push) Failing after 10m5s
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 15:55:32 +00:00
james.maes 72de6f87e5 Merge pull request 'Make the reactor root the single version source; publish vendored OpenHTML at the family version' (#1) from kof22/single-revision into develop
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: ci / original-voyage (push) Skipped
QRun-IO/ci-policy: ci / original-cli (push) Skipped
QRun-IO/ci-policy: native / native-result (push) Successful in 6s
QRun-IO/ci-policy: ci / ci-result (push) Failing after 5s
build / maven (push) Failing after 1h3m32s
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / original-business (push) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 1e25bb5a243dfdcd95d4b9a4f780200ff8c8778a] (push) Successful in 5s
QRun-IO/ci-policy: ci / shared-pipeline (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Failing after 6s
maven / build (push) Failing after 10m58s
2026-11-06 15:35:27 +00:00
james.maes 97a4ac6c92 Merge pull request 'ci: use shared ci-workflows Maven workflow' (#2) from ci/ci-workflows into develop
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: ci / original-voyage (push) Skipped
QRun-IO/ci-policy: ci / original-cli (push) Skipped
QRun-IO/ci-policy: ci / ci-result (push) Failing after 8s
build / maven (push) Failing after 11m48s
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 1e25bb5a243dfdcd95d4b9a4f780200ff8c8778a] (push) Successful in 9s
QRun-IO/ci-policy: native / native-result (push) Successful in 9s
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / original-business (push) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Failing after 7s
maven / build (push) Failing after 11m48s
2026-11-06 15:34:32 +00:00
james.maes d3c0062343 Merge pull request 'feat(authoring): rename document types, stock and templates natively' (#3) from feat/definition-rename-20261106 into develop
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: ci / original-voyage (push) Skipped
QRun-IO/ci-policy: ci / original-cli (push) Skipped
QRun-IO/ci-policy: ci / ci-result (push) Failing after 9s
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: native / native-result (push) Successful in 9s
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / original-business (push) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 1e25bb5a243dfdcd95d4b9a4f780200ff8c8778a] (push) Successful in 11s
QRun-IO/ci-policy: ci / shared-pipeline (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Failing after 8s
2026-11-06 15:32:50 +00:00
james.maesandClaude Fable 5.1 313f081026 feat(authoring): rename document types, stock and templates natively
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
QRun-IO/ci-policy: ci / ci-result (pull_request) Failing after 11s
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 1e25bb5a243dfdcd95d4b9a4f780200ff8c8778a] (pull_request) Successful in 12s
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 12s
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Failing after 9s
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
Add DocumentDefinitionService.rename and the native authoring actions
document_generation_{type,stock,template}_author_rename. The form loads
the current name and saves only the trimmed name; names must be nonblank,
at most 200 characters and free of control characters. The configure
permission is checked at discovery and again at save, and the native
UpdateAction keeps the host's field-level audit. GenerationStore.update
now admits an id+name-only write for type and stock rows, so geometry,
bindings, drafts, published revisions and queued pins stay immutable.
No schema change.

Carried locally by GG Forge as definition-rename.patch (pin
0.1.0-forge.ee782b7d6ea8-rename1); upstreamed so a published release can
replace that pin. The prepare-step selection message is generalised to
cover type and stock rows. Docs: AUTHORING.md describes the action,
README and RELEASE-STATUS record the addition and its verification
(./mvnw -pl foundation -am verify: 232 tests, Checkstyle and coverage
gates met, including the Docker MySQL and PostgreSQL suites).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
2026-11-06 08:59:39 -06:00
james.maesandClaude Opus 5.5 79c5b5453f ci: use shared ci-workflows Maven workflow
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow d20a578c7d818bc7ca32a2ac2c675da4e4f27385] (pull_request) Successful in 8s
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
QRun-IO/ci-policy: ci / ci-result (pull_request) Failing after 4s
build / maven (push) Failing after 1m13s
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 7s
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Failing after 8s
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
maven / build (push) Failing after 1m13s
Replace the platform-ci release callers with one caller of
KofTwentyTwo/ci-workflows maven.yaml (qqq-orb port).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 23:15:23 +00:00
137 changed files with 5904 additions and 67 deletions

No files matched your search

+7
View File
@@ -0,0 +1,7 @@
name: build
on:
push:
jobs:
maven:
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/maven.yaml@v1
secrets: inherit
+3 -3
View File
@@ -1,6 +1,6 @@
# QBit Document Generation
> **Paused, unreleased work in progress — 2026-11-04.** Implementation has stopped. This is a source checkpoint, not a production-ready release or a complete installable package set.
> **Version 1.0.** Published to the KOF22 Gitea Maven registry (`com.kingsrook.qbits`). Requires QQQ Core `4.1.0-RC.22`; the bridges need Record Files `0.1.0-RC.1` and Scripting `1.0.0`. See [status](docs/RELEASE-STATUS.md).
Start with [current status and build prerequisites](docs/RELEASE-STATUS.md) and [known issues](docs/KNOWN-ISSUES.md). These documents supersede older release-readiness statements below.
@@ -20,7 +20,7 @@ The QBit owns document types, stock, table bindings, template drafts, immutable
- Matching durable retained-file receipts and strict audit callbacks before success or printing. Request tables contain receipt/provenance fields, never document bytes or context bodies.
- Reprint through current retained-version authorization, without rerendering. Queue acceptance is reported as `QUEUED`, never as physical printing.
- Stable request keys prevent blind duplicate execution. Incomplete work requires reconciliation; this implementation does not claim exactly-once processing.
- Native definition/source editing, immutable retained previews and optimistic draft/publication forms in a Document Generation workspace. See [authoring](docs/AUTHORING.md).
- Native definition/source editing, display-name renames for types, stock and templates, immutable retained previews and optimistic draft/publication forms in a Document Generation workspace. See [authoring](docs/AUTHORING.md).
- Durable generation-owned queue/items, independent strict audit, bounded admission/cursors and conservative recovery. Native source record/batch/status/download/reprint processes call the same services. See [queue](docs/DURABLE-QUEUE.md).
- Explicit opt-in adapter to existing core Jobs with cooperative checkpoints; no additional scheduler. See [Jobs](docs/JOBS.md) for verification state and host requirements.
@@ -72,7 +72,7 @@ The root `pom.xml` is an aggregator only and lists every module, so one build pr
Run `./mvnw verify` (`.\mvnw.cmd verify` on Windows) at the repository root. The foundation's tests include disposable MySQL 8.4 and PostgreSQL suites started with Testcontainers, so Docker access is required. They verify publication concurrency/commit/rollback, physical pooled-connection restoration, mapping/settings drift refusal, bounded lock contention, current-authorized context selection and immutable pins, audit/receipt failures, restart and incomplete results. They do not qualify production storage, hostile workers or physical printer output.
The build enforces Checkstyle plus 85% instruction/90% class coverage in the foundation. The published `qbit-build-parent` POMs leave `${revision}` in their own version, which causes a Maven parent-resolution warning despite a successful build. That upstream publication issue remains open; it is neither suppressed nor patched.
The build enforces Checkstyle plus 85% instruction/90% class coverage in the foundation, and holds the renderer (94% instructions, 91% lines) and its bridge (85% instructions, 82% lines) to their measured baselines; the new packages and classes named in each `pom.xml` must reach 100% line coverage. Bounded mutation campaigns over the scene parser and the host printer gate run in the test phase; see [docs/security/fuzzing.md](docs/security/fuzzing.md). The published `qbit-build-parent` POMs leave `${revision}` in their own version, which causes a Maven parent-resolution warning despite a successful build. That upstream publication issue remains open; it is neither suppressed nor patched.
## Maven publication
+2 -2
View File
@@ -4,9 +4,9 @@
<parent><groupId>com.kingsrook.qbits</groupId><artifactId>qbit-document-generation-reactor</artifactId><version>${revision}</version><relativePath>../../pom.xml</relativePath></parent>
<groupId>com.kingsrook.qbits</groupId><artifactId>qbit-document-generation-printing</artifactId><version>${revision}</version>
<name>Document Generation Printing Bridge</name>
<properties><qqq.version>4.1.0-RC.21</qqq.version><!-- Owner-supplied sibling QBit coordinates (not yet published): set to the released version --><qbit-record-files.version>0.1.0-SNAPSHOT</qbit-record-files.version><maven.compiler.release>21</maven.compiler.release><maven.compiler.proc>none</maven.compiler.proc><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<properties><qqq.version>4.1.0-RC.26</qqq.version><!-- Released sibling QBit coordinates from the Gitea Maven registry --><qbit-record-files.version>0.4.0-RC.1</qbit-record-files.version><maven.compiler.release>21</maven.compiler.release><maven.compiler.proc>none</maven.compiler.proc><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- Released Printing 1.0.0: RetainedPrintFacade.Acceptance carries title/optionsJson and PrintingService enforces options.mediaProfile id+revision. -->
<qbit-printing.version>1.0.0</qbit-printing.version><enforcer.skip>true</enforcer.skip></properties>
<qbit-printing.version>1.2.0-RC.1</qbit-printing.version><enforcer.skip>true</enforcer.skip></properties>
<dependencyManagement><dependencies><dependency><groupId>com.kingsrook.qqq</groupId><artifactId>qqq-bom-pom</artifactId><version>${qqq.version}</version><type>pom</type><scope>import</scope></dependency>
<dependency><groupId>com.kingsrook.qqq</groupId><artifactId>qqq-backend-core</artifactId><version>${qqq.version}</version></dependency>
<dependency><groupId>com.kingsrook.qqq</groupId><artifactId>qqq-backend-module-rdbms</artifactId><version>${qqq.version}</version></dependency>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent><groupId>com.kingsrook.qbits</groupId><artifactId>qbit-document-generation-reactor</artifactId><version>${revision}</version><relativePath>../../pom.xml</relativePath></parent>
<groupId>com.kingsrook.qbits</groupId><artifactId>qbit-document-generation-record-files</artifactId><version>${revision}</version>
<name>Document Generation Record Files Bridge</name>
<properties><qqq.version>4.1.0-RC.21</qqq.version><!-- Owner-supplied sibling QBit coordinates (not yet published): set to the released version --><qbit-record-files.version>0.1.0-SNAPSHOT</qbit-record-files.version><maven.compiler.release>21</maven.compiler.release><maven.compiler.proc>none</maven.compiler.proc><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><enforcer.skip>true</enforcer.skip></properties>
<properties><qqq.version>4.1.0-RC.26</qqq.version><!-- Released sibling QBit coordinates from the Gitea Maven registry --><qbit-record-files.version>0.4.0-RC.1</qbit-record-files.version><maven.compiler.release>21</maven.compiler.release><maven.compiler.proc>none</maven.compiler.proc><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><enforcer.skip>true</enforcer.skip></properties>
<dependencyManagement><dependencies><dependency><groupId>com.kingsrook.qqq</groupId><artifactId>qqq-bom-pom</artifactId><version>${qqq.version}</version><type>pom</type><scope>import</scope></dependency>
<dependency><groupId>com.kingsrook.qqq</groupId><artifactId>qqq-backend-core</artifactId><version>${qqq.version}</version></dependency>
<dependency><groupId>com.kingsrook.qqq</groupId><artifactId>qqq-backend-module-rdbms</artifactId><version>${qqq.version}</version></dependency>
+17 -4
View File
@@ -1,9 +1,22 @@
# Build only on an operator-approved, immutable existing renderer image.
# This file does not build/download the engine or confer isolation qualification.
# Build only on an operator-approved, immutable existing renderer image (RENDERER_IMAGE, the engine image from
# renderer/Dockerfile). This file does not build the engine or confer isolation qualification.
# The service is the RELEASED transport artifact, resolved at the exact TRANSPORT_VERSION from the QRun-IO Gitea Maven
# registry and Maven Central (no SNAPSHOT, no local build, no default). BUILD_IMAGE must be an immutable digest.
# docker build --build-arg BUILD_IMAGE=... --build-arg RENDERER_IMAGE=... --build-arg TRANSPORT_VERSION=1.0.0 bridges/remote-renderer/
ARG BUILD_IMAGE
ARG RENDERER_IMAGE
FROM ${BUILD_IMAGE} AS release
ARG TRANSPORT_VERSION
WORKDIR /release
COPY release-image/pom.xml ./pom.xml
RUN test -n "${TRANSPORT_VERSION}" && case "${TRANSPORT_VERSION}" in *SNAPSHOT*|*forge*) echo "TRANSPORT_VERSION must be a released version" >&2; exit 1;; esac \
&& mvn -B -Dtransport.version="${TRANSPORT_VERSION}" -Dmaven.repo.local=/release/.m2 package \
&& mv "target/lib/qqq-remote-renderer-transport-${TRANSPORT_VERSION}.jar" target/service.jar \
&& test -f target/service.jar && ls target/lib
FROM ${RENDERER_IMAGE}
COPY transport/target/qqq-remote-renderer-transport-0.1.0-SNAPSHOT.jar /remote/service.jar
COPY transport/target/lib/ /remote/lib/
COPY --from=release /release/target/service.jar /remote/service.jar
COPY --from=release /release/target/lib/ /remote/lib/
COPY LICENSE NOTICE /usr/share/doc/qqq-remote-renderer/
USER 65532:65532
EXPOSE 18445
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Image-assembly project, not a published module. It resolves the RELEASED remote renderer transport service and its runtime
libraries from the QRun-IO Gitea Maven registry (and Maven Central) at one exact version, so the service image
carries release bytes and not a local build. transport.version has no default: pass -Dtransport.version=<released>. -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kingsrook.qbits</groupId><artifactId>qqq-remote-renderer-image</artifactId><version>0</version><packaging>pom</packaging>
<repositories>
<repository><id>central</id><url>https://repo.maven.apache.org/maven2</url><snapshots><enabled>false</enabled></snapshots></repository>
<repository><id>qrun-io</id><url>https://git.kof22.com/api/packages/QRun-IO/maven</url><snapshots><enabled>false</enabled></snapshots><releases><checksumPolicy>fail</checksumPolicy></releases></repository>
</repositories>
<dependencies>
<dependency><groupId>com.kingsrook.qbits</groupId><artifactId>qqq-remote-renderer-transport</artifactId><version>${transport.version}</version></dependency>
</dependencies>
<build><plugins>
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-dependency-plugin</artifactId><version>3.9.0</version>
<executions><execution><id>release-libraries</id><phase>package</phase><goals><goal>copy-dependencies</goal></goals>
<configuration><includeScope>runtime</includeScope><outputDirectory>${project.build.directory}/lib</outputDirectory></configuration>
</execution></executions></plugin>
</plugins></build>
</project>
+17
View File
@@ -37,6 +37,23 @@
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>3.5.4</version><configuration><dependenciesToScan combine.self="override"/></configuration></plugin>
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-dependency-plugin</artifactId><version>3.8.1</version><executions><execution><id>runtime-distribution</id><phase>package</phase><goals><goal>copy-dependencies</goal></goals><configuration><includeScope>runtime</includeScope><outputDirectory>${project.build.directory}/lib</outputDirectory></configuration></execution></executions></plugin>
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-checkstyle-plugin</artifactId><version>3.3.1</version><dependencies><dependency><groupId>com.puppycrawl.tools</groupId><artifactId>checkstyle</artifactId><version>10.12.6</version></dependency></dependencies><configuration><configLocation>${project.basedir}/checkstyle/config.xml</configLocation><propertyExpansion>checkstyle.reactor.root=${project.basedir}</propertyExpansion><includeTestSourceDirectory>true</includeTestSourceDirectory><consoleOutput>true</consoleOutput><violationSeverity>warning</violationSeverity><failOnViolation>true</failOnViolation></configuration><executions><execution><phase>validate</phase><goals><goal>check</goal></goals></execution></executions></plugin>
<plugin>
<!-- Coverage gates. The BUNDLE rule is the measured baseline of the existing bridge, rounded down (2026-11-11: 85.6% of instructions and 82.6% of lines); raise it when coverage rises and
never lower it. The PACKAGE and CLASS rules hold new code to 100% line coverage from its first commit; a pattern matches nothing until its package or class exists, so the package that adds
it needs no change here. Nothing is excluded: JaCoCo skips only code annotated as generated, and this build defines no such annotation. There is no skip switch. -->
<groupId>org.jacoco</groupId><artifactId>jacoco-maven-plugin</artifactId><version>0.8.15</version><executions>
<execution><goals><goal>prepare-agent</goal></goals></execution>
<execution><id>report</id><phase>verify</phase><goals><goal>report</goal></goals></execution>
<execution><id>coverage</id><phase>verify</phase><goals><goal>check</goal></goals><configuration><rules>
<rule><element>BUNDLE</element><limits>
<limit><counter>INSTRUCTION</counter><value>COVEREDRATIO</value><minimum>0.85</minimum></limit>
<limit><counter>LINE</counter><value>COVEREDRATIO</value><minimum>0.82</minimum></limit>
</limits></rule>
<rule><element>CLASS</element><includes><include>com.kingsrook.qbits.documentgeneration.renderer.ZplRaster</include><include>com.kingsrook.qbits.documentgeneration.renderer.ZplRaster.*</include></includes><limits>
<limit><counter>LINE</counter><value>COVEREDRATIO</value><minimum>1.00</minimum></limit>
</limits></rule>
</rules></configuration></execution>
</executions></plugin>
<plugin>
<!-- Publish a self-contained POM: ${revision}/property versions are resolved at build time, so a consumer needs no -Drevision -->
<groupId>org.codehaus.mojo</groupId><artifactId>flatten-maven-plugin</artifactId><version>1.7.3</version>
@@ -43,6 +43,13 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
*******************************************************************************/
public final class IsolatedRendererBridge implements GenerationPorts.IsolatedRenderer
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// the refusals renderer 0.1.0 can name. FailureCode also holds the version 2 scene refusals; a 0.1.0 worker //
// cannot produce those, so one that names them is rejected like any other unknown code //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
private static final Set<GenerationPorts.FailureCode> RENDERER_0_1_REFUSALS = java.util.EnumSet.range(
GenerationPorts.FailureCode.BARCODE_FIT_TOO_SMALL, GenerationPorts.FailureCode.UNSUPPORTED_BARCODE_OPTION);
private final RendererLimits config;
private final AssetReader assets;
private final Exchange exchange;
@@ -268,6 +275,10 @@ public final class IsolatedRendererBridge implements GenerationPorts.IsolatedRen
{
throw new IllegalArgumentException("WORKER_ERROR_REJECTED");
}
if(!RENDERER_0_1_REFUSALS.contains(code))
{
throw new IllegalArgumentException("WORKER_ERROR_REJECTED");
}
throw new GenerationPorts.RenderException(code);
}
int expectedFields = operation.equals("VALIDATE") ? 5 : 10;
@@ -183,13 +183,25 @@ class BridgeTest
@Test
void exactWorkerErrorsPreserveClosedFailureCodes() throws Exception
{
assertEquals(13, GenerationPorts.FailureCode.values().length);
var released = java.util.EnumSet.range(GenerationPorts.FailureCode.BARCODE_FIT_TOO_SMALL, GenerationPorts.FailureCode.UNSUPPORTED_BARCODE_OPTION);
assertEquals(13, released.size());
assertEquals(28, GenerationPorts.FailureCode.values().length);
for(var code : GenerationPorts.FailureCode.values())
{
var bridge = new IsolatedRendererBridge(config(directory), (actor, asset, maximum) -> new byte[0], (id, input, maximum) ->
(RendererJson.JSON.writeValueAsString(Map.of("protocol", RendererJson.PROTOCOL, "requestId", id, "outcome", "ERROR",
"rendererVersion", "qqq-renderer-0.1.0", "catalogVersion", "qqq-barcodes-9", "code", code.name())) + "\n").getBytes(StandardCharsets.UTF_8));
var request = request(Map.of(), List.of());
if(!released.contains(code))
{
// A version 2 scene refusal named by a 0.1.0 worker stays as unknown as it was before the vocabulary grew.
for(var generic : List.of(assertThrows(QException.class, () -> bridge.render(request)), assertThrows(QException.class, () -> bridge.validate("actor-1", request.revision()))))
{
assertEquals(QException.class, generic.getClass(), code.name());
assertEquals("Isolated renderer rejected or could not complete the request.", generic.getMessage());
}
continue;
}
var failure = assertThrows(GenerationPorts.RenderException.class, () -> bridge.render(request));
assertEquals(code, failure.code());
assertEquals(code.name(), failure.getMessage());
@@ -0,0 +1,218 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.documentgeneration.renderer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.SplittableRandom;
import java.util.function.Predicate;
/*******************************************************************************
** Seeded, bounded mutation campaign for a parser of untrusted input. A seed, an
** iteration number, the corpus and the dictionary fully determine every input:
** there is no clock and no unseeded randomness, so a finding replays exactly.
** Targets, limits and the replay command are in `docs/security/fuzzing.md`. The
** same class is kept in the test tree of `renderer`.
*******************************************************************************/
final class MutationCampaign
{
static final int MAXIMUM_ITERATIONS = 100_000;
static final int MAXIMUM_INPUT_BYTES = 1024 * 1024;
private static final byte[] INTERESTING = {0, 9, 10, 13, ' ', '"', '\\', '^', '~', '_', ',', '{', '}', '[', ']', '0', '9', '-', (byte) 0x7F, (byte) 0x80, (byte) 0xC0, (byte) 0xFF};
private final List<byte[]> corpus;
private final List<byte[]> dictionary;
/***************************************************************************
** The parser under test. Returning normally means the input was accepted; a
** target asserts its own property on what it accepts.
***************************************************************************/
@FunctionalInterface
interface Target
{
/************************************************************************
** Parse one generated input.
************************************************************************/
void accept(byte[] input) throws Exception;
}
/***************************************************************************
** An input that was neither accepted nor refused in the declared way, with
** the coordinates that regenerate it: `input(seed, iteration)`.
***************************************************************************/
record Finding(long seed, int iteration, String failure)
{
}
/***************************************************************************
** What a campaign saw. A campaign that accepted or refused nothing has not
** exercised both sides of the parser, so callers assert on the counts too.
***************************************************************************/
record Result(int accepted, int refused, List<Finding> findings)
{
}
/***************************************************************************
** Valid inputs to start from, and tokens of the format to splice in.
***************************************************************************/
MutationCampaign(List<byte[]> corpus, List<String> dictionary)
{
if(corpus.isEmpty() || corpus.stream().anyMatch(seedInput -> seedInput.length > MAXIMUM_INPUT_BYTES))
{
throw new IllegalArgumentException("A campaign needs a corpus of inputs within the input limit.");
}
this.corpus = corpus.stream().map(byte[]::clone).toList();
this.dictionary = dictionary.stream().map(token -> token.getBytes(StandardCharsets.UTF_8)).toList();
}
/***************************************************************************
** The seeds for this run: the fixed ones, or only `-Dfuzz.seed=<n>` when a
** finding is replayed or a longer campaign is run by hand.
***************************************************************************/
static long[] seeds(long... fixed)
{
Long replay = Long.getLong("fuzz.seed");
return (replay == null ? fixed : new long[] {replay});
}
/***************************************************************************
** The iteration count for this run: `-Dfuzz.iterations=<n>`, never unbounded.
***************************************************************************/
static int iterations(int fallback)
{
int iterations = Integer.getInteger("fuzz.iterations", fallback);
if(iterations < 1 || iterations > MAXIMUM_ITERATIONS)
{
throw new IllegalArgumentException("fuzz.iterations must be between 1 and " + MAXIMUM_ITERATIONS);
}
return (iterations);
}
/***************************************************************************
** The input of one iteration: a corpus entry with one to four mutations.
***************************************************************************/
byte[] input(long seed, int iteration)
{
SplittableRandom random = new SplittableRandom(seed + 0x9E3779B97F4A7C15L * (iteration + 1L));
byte[] value = corpus.get(random.nextInt(corpus.size())).clone();
int steps = 1 + random.nextInt(4);
for(int step = 0; step < steps; step++)
{
value = mutate(value, random);
}
return (value.length > MAXIMUM_INPUT_BYTES ? Arrays.copyOf(value, MAXIMUM_INPUT_BYTES) : value);
}
/***************************************************************************
** Run iterations 0 to `iterations - 1`. A thrown failure that `refusal`
** recognises is the parser saying no; any other, including a failed assertion
** in the target and a stack overflow, is a finding.
***************************************************************************/
Result run(long seed, int iterations, Target target, Predicate<Throwable> refusal)
{
if(iterations < 1 || iterations > MAXIMUM_ITERATIONS)
{
throw new IllegalArgumentException("A campaign runs between 1 and " + MAXIMUM_ITERATIONS + " iterations.");
}
int accepted = 0;
int refused = 0;
List<Finding> findings = new ArrayList<>();
for(int iteration = 0; iteration < iterations; iteration++)
{
try
{
target.accept(input(seed, iteration));
accepted++;
}
catch(Exception | AssertionError | StackOverflowError failure)
{
if(refusal.test(failure))
{
refused++;
}
else
{
findings.add(new Finding(seed, iteration, failure.toString()));
}
}
}
return (new Result(accepted, refused, List.copyOf(findings)));
}
/***************************************************************************
** One structural or byte-level change at a random position.
***************************************************************************/
private byte[] mutate(byte[] value, SplittableRandom random)
{
int at = value.length == 0 ? 0 : random.nextInt(value.length);
int span = value.length == 0 ? 0 : 1 + random.nextInt(Math.min(16, value.length - at));
byte[] noise = new byte[1 + random.nextInt(8)];
random.nextBytes(noise);
return (switch(random.nextInt(8))
{
case 0 -> splice(value, at, span, new byte[0]);
case 1 -> splice(value, at, 0, Arrays.copyOfRange(value, at, at + span));
case 2 -> Arrays.copyOf(value, at);
case 3 -> splice(value, at, 0, dictionary.isEmpty() ? noise : dictionary.get(random.nextInt(dictionary.size())));
case 4 -> splice(value, at, 0, noise);
case 5 -> splice(value, at, Math.min(1, value.length), new byte[] {INTERESTING[random.nextInt(INTERESTING.length)]});
case 6 -> splice(value, at, Math.min(1, value.length), new byte[] {noise[0]});
default -> splice(value, at, Math.min(1, value.length), value.length == 0 ? noise : new byte[] {(byte) (value[at] ^ (1 << random.nextInt(8)))});
});
}
/***************************************************************************
** Replace `removed` bytes at `at` with `inserted`.
***************************************************************************/
private static byte[] splice(byte[] value, int at, int removed, byte[] inserted)
{
byte[] result = new byte[value.length - removed + inserted.length];
System.arraycopy(value, 0, result, 0, at);
System.arraycopy(inserted, 0, result, at, inserted.length);
System.arraycopy(value, at + removed, result, at + inserted.length, value.length - at - removed);
return (result);
}
}
@@ -0,0 +1,175 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.documentgeneration.renderer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.HexFormat;
import java.util.List;
import java.util.Set;
import com.kingsrook.qbits.documentgeneration.GenerationPorts;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
/*******************************************************************************
** The mutation harness is deterministic and reports what it should, and one
** bounded campaign runs in the normal test phase over the host printer gate.
** Seeds, limits and the replay command: `docs/security/fuzzing.md`.
*******************************************************************************/
class MutationCampaignTest
{
private static final long[] SEEDS = {20261111L, 7919L};
private static final int ITERATIONS = 20_000;
private static final Path CORPUS = Path.of("..", "..", "renderer", "src", "test", "resources", "compat-v1", "zpl");
private static final GenerationPorts.Stock STOCK = new GenerationPorts.Stock(76200, 50800, 0, 0, 0, 0, 203);
private static final Set<String> COMMANDS = Set.of("XA", "XZ", "PW", "LL", "LH", "CI", "FO", "A0", "FH", "FD", "FS", "GB", "GF");
/***************************************************************************
** A seed and an iteration name one input, on every run and every machine.
***************************************************************************/
@Test
void aSeedAndAnIterationAlwaysGiveTheSameInput()
{
MutationCampaign campaign = new MutationCampaign(List.of("^XA^FO10,20^GB30,40,2^FS^XZ".getBytes(StandardCharsets.US_ASCII)), List.of("^FO", "^GB"));
MutationCampaign again = new MutationCampaign(List.of("^XA^FO10,20^GB30,40,2^FS^XZ".getBytes(StandardCharsets.US_ASCII)), List.of("^FO", "^GB"));
Set<String> distinct = new HashSet<>();
for(int iteration = 0; iteration < 200; iteration++)
{
byte[] input = campaign.input(42L, iteration);
assertArrayEquals(input, again.input(42L, iteration));
assertTrue(input.length <= MutationCampaign.MAXIMUM_INPUT_BYTES);
distinct.add(HexFormat.of().formatHex(input));
}
assertTrue(distinct.size() > 100, "mutations should rarely repeat: " + distinct.size());
assertNotEquals(HexFormat.of().formatHex(campaign.input(42L, 0)), HexFormat.of().formatHex(campaign.input(43L, 0)));
}
/***************************************************************************
** The harness fails when it should: a crash and an unsafe acceptance are both
** findings, each carrying the coordinates that regenerate its input.
***************************************************************************/
@Test
void aCrashAndAnUnsafeAcceptanceAreFindingsThatReplay()
{
MutationCampaign campaign = new MutationCampaign(List.of("0123456789".getBytes(StandardCharsets.US_ASCII)), List.of());
MutationCampaign.Result result = campaign.run(42L, 300, input ->
{
if(input.length % 3 == 0)
{
throw new IllegalArgumentException("REFUSED");
}
if(input.length % 3 == 1)
{
throw new IllegalStateException("crash");
}
assertTrue(input.length < 11, "accepted an input it should have refused");
}, IllegalArgumentException.class::isInstance);
assertEquals(300, result.accepted() + result.refused() + result.findings().size());
assertTrue(result.accepted() > 0 && result.refused() > 0);
assertTrue(result.findings().stream().anyMatch(finding -> finding.failure().startsWith("java.lang.IllegalStateException")));
assertTrue(result.findings().stream().anyMatch(finding -> finding.failure().contains("AssertionFailedError")));
for(MutationCampaign.Finding finding : result.findings())
{
int length = campaign.input(finding.seed(), finding.iteration()).length;
assertTrue(length % 3 == 1 || (length % 3 == 2 && length >= 11), finding.toString());
}
}
/***************************************************************************
** A campaign is always bounded and always has something to mutate.
***************************************************************************/
@Test
void campaignsAreBounded()
{
MutationCampaign campaign = new MutationCampaign(List.of(new byte[0]), List.of());
assertThrows(IllegalArgumentException.class, () -> campaign.run(1L, 0, input -> {}, failure -> true));
assertThrows(IllegalArgumentException.class, () -> campaign.run(1L, MutationCampaign.MAXIMUM_ITERATIONS + 1, input -> {}, failure -> true));
assertThrows(IllegalArgumentException.class, () -> new MutationCampaign(List.of(), List.of()));
assertThrows(IllegalArgumentException.class, () -> new MutationCampaign(List.of(new byte[MutationCampaign.MAXIMUM_INPUT_BYTES + 1]), List.of()));
assertEquals(50, campaign.run(1L, 50, input -> {}, failure -> false).accepted());
}
/***************************************************************************
** Property: whatever the host printer gate accepts holds only the commands
** the renderer may emit, and no tilde command. Every other input is refused
** with the gate's own IllegalArgumentException, or with the strict decoder's
** CharacterCodingException for field text that is not UTF-8; any other
** failure is a finding.
***************************************************************************/
@Test
void theHostPrinterGateAcceptsOnlyTheRendererCommandSet() throws Exception
{
List<byte[]> corpus = new ArrayList<>();
for(String name : List.of("box-203", "text-font0-203", "image-203", "barcode-scale-203", "raster-text-rotation-203"))
{
byte[] released = Files.readAllBytes(CORPUS.resolve(name + ".zpl"));
ZplOutputPolicy.validate(released, STOCK);
corpus.add(released);
}
MutationCampaign campaign = new MutationCampaign(corpus, List.of("^XA", "^XZ\n", "^FO", "^GFA,", "^GB", "^FS\n", "^A0N,", "^FH_^FD", "^PW", "^LL", "^LH0,0", "^CI28\n",
"~DG", "~JR", "^XG", "^DF", "^JU", "^FX", "^FB", "99999", "0", ",", "\n", "_5E", "_7E"));
int iterations = MutationCampaign.iterations(ITERATIONS);
for(long seed : MutationCampaign.seeds(SEEDS))
{
MutationCampaign.Result result = campaign.run(seed, iterations, MutationCampaignTest::gate,
failure -> failure instanceof IllegalArgumentException || failure instanceof CharacterCodingException);
assertEquals(List.of(), result.findings(), "replay with -Dfuzz.seed=" + seed + " -Dfuzz.iterations=" + iterations);
assertFalse(result.accepted() == 0 || result.refused() == 0, "the campaign must reach both acceptance and refusal: " + result);
}
}
/***************************************************************************
** The target, and an independent reading of what it let through.
***************************************************************************/
private static void gate(byte[] zpl) throws Exception
{
ZplOutputPolicy.validate(zpl, STOCK);
for(int index = 0; index < zpl.length; index++)
{
assertNotEquals('~', (char) zpl[index], "accepted a tilde command");
if(zpl[index] == '^')
{
assertTrue(index + 2 < zpl.length && COMMANDS.contains(new String(zpl, index + 1, 2, StandardCharsets.US_ASCII)), "accepted a command outside the renderer set");
}
}
}
}
@@ -0,0 +1,80 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.documentgeneration.renderer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.util.HexFormat;
import java.util.List;
import com.fasterxml.jackson.databind.JsonNode;
import com.kingsrook.qbits.documentgeneration.GenerationPorts;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
/*******************************************************************************
** The released version 1 bytes, as the host receives them: each stored ZPL of
** the renderer's `compat-v1` corpus is exactly the bytes its manifest names and
** passes the unchanged host printer gate for the stock it was rendered for. The
** corpus has one copy, in the renderer module, so the two tests cannot drift.
*******************************************************************************/
class ZplSceneOneCompatibilityTest
{
private static final Path CORPUS = Path.of("..", "..", "renderer", "src", "test", "resources", "compat-v1");
/***************************************************************************
** One manifest line per stored output: hash, two spaces, path.
***************************************************************************/
static List<String> outputs() throws Exception
{
List<String> lines = Files.readAllLines(CORPUS.resolve("SHA256SUMS"), StandardCharsets.US_ASCII);
assertFalse(lines.isEmpty());
return (lines);
}
/***************************************************************************
** A released label that the host gate refused could never have printed.
***************************************************************************/
@ParameterizedTest(name = "{0}")
@MethodSource("outputs")
void releasedBytesAreIntactAndPassTheHostPrinterGate(String line) throws Exception
{
assertTrue(line.matches("[0-9a-f]{64} zpl/[a-z0-9-]+\\.zpl"), line);
String name = line.substring(70, line.length() - 4);
byte[] bytes = Files.readAllBytes(CORPUS.resolve("zpl").resolve(name + ".zpl"));
assertEquals(line.substring(0, 64), HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(bytes)), name);
JsonNode stock = RendererJson.JSON.readTree(Files.readAllBytes(CORPUS.resolve("cases").resolve(name + ".json"))).path("stock");
GenerationPorts.Stock printed = new GenerationPorts.Stock(stock.path("widthMicrons").intValue(), stock.path("heightMicrons").intValue(), stock.path("topMicrons").intValue(),
stock.path("rightMicrons").intValue(), stock.path("bottomMicrons").intValue(), stock.path("leftMicrons").intValue(), stock.path("dpi").intValue());
assertDoesNotThrow(() -> ZplOutputPolicy.validate(bytes, printed), name);
}
}
+1 -1
View File
@@ -5,7 +5,7 @@
<groupId>com.kingsrook.qbits</groupId><artifactId>qbit-document-generation-scripting</artifactId><version>${revision}</version>
<name>Document Generation Scripting Bridge</name>
<licenses><license><name>GNU Affero General Public License v3.0</name><url>https://www.gnu.org/licenses/agpl-3.0-standalone.html</url><distribution>repo</distribution></license></licenses>
<properties><qqq.version>4.1.0-RC.21</qqq.version><!-- Owner-supplied sibling QBit coordinates (not yet published): set to the released version --><qbit-scripting.version>0.1.0-SNAPSHOT</qbit-scripting.version><maven.compiler.release>21</maven.compiler.release><maven.compiler.proc>none</maven.compiler.proc><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><enforcer.skip>true</enforcer.skip></properties>
<properties><qqq.version>4.1.0-RC.26</qqq.version><!-- Released sibling QBit coordinates from the Gitea Maven registry --><qbit-scripting.version>1.1.0-RC.3</qbit-scripting.version><maven.compiler.release>21</maven.compiler.release><maven.compiler.proc>none</maven.compiler.proc><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><enforcer.skip>true</enforcer.skip></properties>
<dependencyManagement><dependencies>
<dependency><groupId>com.kingsrook.qqq</groupId><artifactId>qqq-backend-core</artifactId><version>${qqq.version}</version></dependency>
<dependency><groupId>com.kingsrook.qqq</groupId><artifactId>qqq-backend-module-rdbms</artifactId><version>${qqq.version}</version></dependency>
+2
View File
@@ -12,6 +12,8 @@ The QBit adds a Document Generation workspace using standard QQQ tables and proc
Select a template and use **Edit template source**. PDF source uses the existing HTML code editor and FreeMarker HTML/CSS syntax; ZPL source uses the JSON editor and the renderer's restricted scene schema. These are separate source documents sharing the same pinned context script and assets. This is a source editor, not a visual layout designer or arbitrary raw ZPL terminal.
Use **Rename** on a document type, stock size or template to change only its display name. The form loads the current name; the saved name is trimmed, must be nonblank, at most 200 characters and free of control characters. Identity, stock geometry, bindings, drafts, published revisions and queued pins are untouched, and ordinary table editing of these rows stays denied. The action requires `document-generation.configure` at discovery and again at save, and the native update keeps the host's configured field-level audit.
Use **Select or clear published context script** to attach a context-tested immutable script publication. Search by project name in the ordinary QQQ selector. The optional scripting adapter checks current project/execute permissions and successful context testing of the exact published build, then obtains its artifact/hash pin server-side. No internal reference needs to be typed or copied from the IDE. Selection changes only the draft; publish the template explicitly. A newer script publication never changes an existing template or queued revision. Clearing requires the explicit checkbox, and source editing preserves the current selection even if a client submits a forged internal pin. Discovery is intentionally bounded; narrow the project search when necessary.
An edit captures the current draft fingerprint. Saving under a row lock compares the original fingerprint, so stale editors receive an error rather than overwriting another save. Publication performs isolated validation, then locks and compares the draft again before atomically inserting its immutable revision and changing the active revision. If validation overlapped a draft edit, publication refuses the changed draft. The unversioned three-argument Java `saveDraft` remains an explicit overwrite API for trusted integrations; native source forms use `saveSources` with the fingerprint and preserve their owner-resolved context pin.
+1 -1
View File
@@ -26,7 +26,7 @@ Submitted records provide only IDs. The source body is always fetched afresh thr
## Database/deployment constraints
The durable queue supports file-backed H2 with configured and actual WRITE_DELAY=0, or native MySQL 8.4 under the live checks in [MYSQL.md](MYSQL.md), or native PostgreSQL 17 under the equivalent live checks in `GenerationPostgres`. Both require fresh independent writable connections and the owned tables on one backend. Ambient QQQ transactions are refused. Native mapping drift and unqualified physical targets are denied. Owned statements have five-second JDBC deadlines; MySQL row/metadata lock waits must each be 1–5 seconds. H2 tests pin 2.5.252 and close all connection providers before a cold reopen. Actual pinned MySQL 8.4.13 tests cover concurrent claims/edits, physical pooled isolation restoration, weakened durability/settings, index/engine/mapping drift, bounded lock contention and server restart. PostgreSQL/Aurora are not implicitly supported. Schema changes are operator migrations; metadata registration does not execute DDL. Host power-loss and volume/backup restoration remain deployment acceptance work.
The durable queue supports file-backed H2 with configured and actual WRITE_DELAY=0, or native MySQL 8.4 under the live checks in [MYSQL.md](MYSQL.md), or native PostgreSQL 17 under the equivalent live checks in `GenerationPostgres`. Both require fresh independent writable connections and the owned tables on one backend. Ambient QQQ transactions are refused. Native mapping drift and unqualified physical targets are denied. Owned statements have five-second JDBC deadlines; MySQL row/metadata lock waits must each be 1–5 seconds. H2 tests pin 2.5.252 and close all connection providers before a cold reopen. Actual pinned MySQL 8.4.13 tests cover concurrent claims/edits, physical pooled isolation restoration, weakened durability/settings, index/engine/mapping drift, bounded lock contention and server restart. Amazon Aurora PostgreSQL 17 passes through the same PostgreSQL checks with one difference: Aurora always runs with `full_page_writes` off and has no parameter to change it, because its storage layer does not write torn pages, so `GenerationPostgres` accepts that value when the server resolves `aurora_version()` (`to_regprocedure('aurora_version()') IS NOT NULL`, evaluated in the settings query). Stock PostgreSQL must still report `full_page_writes` on, and `fsync`, `synchronous_commit` and every other check are unchanged on Aurora. The test for this rule runs on a real `postgres:17` engine with a simulated `aurora_version()` function; no test in this repository runs on Aurora itself. Aurora MySQL is not qualified (see [MYSQL.md](MYSQL.md)). Schema changes are operator migrations; metadata registration does not execute DDL. Host power-loss and volume/backup restoration remain deployment acceptance work.
No queue/render/file/script code is executed through a trusted host JavaScript VM. The renderer and scripting integrations continue to require qualified Linux/runsc; this workstation has no runsc runtime, so hostile execution remains unavailable. The queue does not weaken those gates.
+4 -8
View File
@@ -1,10 +1,6 @@
# Known issues and unfinished work
# Known issues
Updated: 2026-11-04. These are unresolved items, not a plan currently being executed.
- Renderer verification has **3 failing tests out of 174**: `BarcodeDeclaredCapabilityTest.barHeightIsAdvertisedAcceptedAndEffectiveTogether`, `gs1DatabarBarHeightDependsOnMode`, and `unsupportedOptionIsAClosedWorkerCode`. The imported main implementation and capability tests disagree; do not hide this by skipping tests or advertising unsupported options.
- The Record Files and Scripting bridge dependencies, including their `tests` classifier artifacts, are not available as a completed public release cohort. The complete root reactor is not release-qualified.
- Earlier foundation runs hit local socket exhaustion/PostgreSQL connection resets. The final 225-test run passed; the transport failures remain a reproducibility concern.
- Renderer image deployment, hostile-template containment, production capacity and physical barcode/printing acceptance are not established by the Maven tests. Source-first templates are implemented; a visual designer is outside this version.
- Renderer image deployment, hostile-template containment, production capacity and physical barcode and printing acceptance are not established by the Maven tests. Source-first templates are implemented; a visual designer is outside this version.
- Barcode `barHeight` is accepted only where it changes the output. The sixteen fixed-geometry encoders (matrix symbols and postal four-state codes) neither advertise nor accept it and refuse it with `UNSUPPORTED_BARCODE_OPTION`, as does GS1 DataBar in stacked mode.
- Native Zint and patched OpenHTML retain their own licenses and corresponding source. The checked-in archives and hash files must ship with the relevant distribution.
- Some historical design/verification notes describe earlier private builds. This status and this issue list supersede their release claims.
- Some historical design and verification notes describe earlier private builds; this status and this issue list supersede their release claims.
+3 -5
View File
@@ -1,6 +1,6 @@
# Document Generation — current status
Updated: 2026-11-04. **Work stopped at the user's request. Unreleased work in progress; not a production 1.0 release.**
**Version 1.0**, built and published by the shared `ci-workflows` Maven pipeline to the KOF22 Gitea Maven registry (`com.kingsrook.qbits`). `mvn deploy` runs every module's tests once. Depends on QQQ Core `4.1.0-RC.22`, Record Files `0.1.0-RC.1` and Scripting `1.0.0`.
## Purpose
@@ -16,10 +16,8 @@ Use Java 21 and the included Maven wrapper. Component example: `./mvnw -pl found
## Evidence retained
Foundation: 225 tests passed with zero failures/errors/skips, Checkstyle clean and coverage gate met. Renderer bridge: 52 tests passed; remote transport: 19 passed. A clean consumer resolved the foundation/remote modules. The patched OpenHTML module passed 15 tests. These are component results, not a complete released-system acceptance.
Foundation: 225 tests passed with zero failures/errors/skips, Checkstyle clean and coverage gate met. The definition-rename change (native `document_generation_{type,stock,template}_author_rename` actions, see [authoring](AUTHORING.md)) was first qualified by GG Forge as a local patch (37 rename-related tests on H2, MySQL and PostgreSQL 17) and then re-verified on this branch with `./mvnw -pl foundation -am verify`: 232 tests passed with zero failures/errors/skips, Checkstyle clean and coverage gate met, including the Docker MySQL 8.4 and PostgreSQL 17 suites. No schema change. Renderer bridge: 52 tests passed; remote transport: 19 passed. A clean consumer resolved the foundation/remote modules. The patched OpenHTML module passed 15 tests. These are component results, not a complete released-system acceptance.
## Source and release state
The starting source was extracted from Website-Backend commit b6fb23825a87412397dec703b9617148ae7eb601, then adapted for standalone packaging. This repository preserves the resulting source and existing licenses. It does not declare a published artifact cohort, completed deployment or complete acceptance.
See [known issues](KNOWN-ISSUES.md) and the README for the existing API and integration documentation. No further implementation, tests, releases or deployment should be started without explicit user instruction. The outstanding dependency order is Core, Collaboration, Scripting/Record Files, then the integrated Generation bridges and matching UI.
The starting source was extracted from Website-Backend commit b6fb23825a87412397dec703b9617148ae7eb601, then adapted for standalone packaging; existing licenses are preserved. See [known issues](KNOWN-ISSUES.md) and the README for the API and integration documentation.
+70
View File
@@ -0,0 +1,70 @@
# Parser fuzzing
## Scope and properties
K22-TEST-25 applies: this repository parses untrusted input at two boundaries. Templates are written by users,
and the isolated renderer is treated as compromisable, so its output is untrusted to the host.
| Target | Module | Input | Campaign |
|---|---|---|---|
| Version 1 scene parser, `ZplRenderer.validate` | `renderer` | A `qqq-zpl-scene/1` source | `MutationCampaignTest.theVersionOneSceneParserAcceptsOnlyVersionOneScenes` |
| Host printer gate, `ZplOutputPolicy.validate` | `bridges/renderer` | ZPL bytes returned by the worker | `MutationCampaignTest.theHostPrinterGateAcceptsOnlyTheRendererCommandSet` |
The harness is `MutationCampaign`, one copy in each module's test tree (the modules share no test code). It
mutates valid inputs from the version 1 compatibility corpus (`renderer/src/test/resources/compat-v1`): bit
flips, byte and range edits, truncation, duplication and tokens of the format spliced in. Each campaign checks
unsafe acceptance, not only crashes:
- **Scene parser.** An accepted input, read again by a second and differently configured JSON parser, is a
version 1 scene of known element types within the element limit. Any exception is a refusal, because the
worker turns every exception into its opaque error answer. A stack overflow or a failed acceptance check is a
finding.
- **Printer gate.** An accepted input contains only the commands the renderer may emit and no tilde command.
A refusal is the gate's `IllegalArgumentException`, or the strict decoder's `CharacterCodingException` for
field text that is not UTF-8. Any other failure, or a failed acceptance check, is a finding.
Each campaign also asserts that it reached both acceptance and refusal, so a campaign that only ever refuses
cannot pass.
This is seeded generated-input fuzzing, not coverage-guided instrumentation. Do not claim OSS-Fuzz or
ClusterFuzzLite enrolment.
## Running and replaying
The campaigns run in the normal test phase, so every `./mvnw verify`, and therefore every CI build of a pull
request or a push, runs them. No network, clock or unseeded randomness is used: a seed and an iteration
number determine the input.
```sh
# What CI runs: seeds 20261111 and 7919, 20000 iterations each, per target.
./mvnw -B -pl renderer,bridges/renderer -am verify
# Replay a finding, or run a longer campaign before a MINOR or MAJOR release (both seeds at 100000).
./mvnw -B -pl renderer -am test -Dtest=MutationCampaignTest -Dsurefire.failIfNoSpecifiedTests=false -Dfuzz.seed=20261111 -Dfuzz.iterations=100000
./mvnw -B -pl bridges/renderer -am test -Dtest=MutationCampaignTest -Dsurefire.failIfNoSpecifiedTests=false -Dfuzz.seed=20261111 -Dfuzz.iterations=100000
```
A failure lists each finding as `Finding[seed, iteration, failure]`. `MutationCampaign.input(seed, iteration)`
returns that exact input against the same checkout. Reduce it to a named regression test beside the parser's
other tests, fix the parser, and rerun both seeds. Report a suspected vulnerability privately rather than in a
public issue.
Limits: inputs are at most 1 MiB, a campaign is at most 100000 iterations, and the corpus entries used are
under 20 KiB. The harness writes no files, launches no process and reads no credentials. Generated ZPL is never
sent to a printer and generated scenes are only validated, not rendered.
## Adding a target
A new parser of untrusted input adds one test method to the `MutationCampaignTest` of its module, with a
corpus of valid inputs, a dictionary of the format's tokens, the refusal it declares and an independent check
of what it accepts, and adds a row to the table above. The version 2 scene parser and `ZplRaster` are the next
two targets.
## Review record
- **Added:** 2026-11-11, with the two campaigns above.
- The first run of the printer gate campaign showed the `CharacterCodingException` refusal, which the property
had not declared. It is a refusal, not a defect; the property now names it.
- Not yet done: a weekly long campaign and the `docs/security/assurance.json` record that K22-TEST-25 also asks
for. The long campaign is the manual command above until a scheduled job exists.
- Reassess the targets when a parser boundary changes.
+17 -1
View File
@@ -7,7 +7,7 @@
<version>${revision}</version>
<name>QBit Document Generation</name>
<description>User-defined document templates and generation coordination for QQQ</description>
<properties><qqq.version>4.1.0-RC.21</qqq.version><maven.compiler.source>21</maven.compiler.source><maven.compiler.target>21</maven.compiler.target><maven.compiler.proc>none</maven.compiler.proc><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><enforcer.skip>false</enforcer.skip></properties>
<properties><qqq.version>4.1.0-RC.26</qqq.version><maven.compiler.source>21</maven.compiler.source><maven.compiler.target>21</maven.compiler.target><maven.compiler.proc>none</maven.compiler.proc><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><enforcer.skip>false</enforcer.skip></properties>
<licenses><license><name>Apache License, Version 2.0</name><url>https://www.apache.org/licenses/LICENSE-2.0</url><distribution>repo</distribution></license></licenses>
<dependencyManagement><dependencies><dependency><groupId>com.kingsrook.qqq</groupId><artifactId>qqq-bom-pom</artifactId><version>${qqq.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>
<dependencies>
@@ -48,6 +48,22 @@
<execution><id>coverage</id><phase>verify</phase><goals><goal>check</goal></goals><configuration><rules><rule><element>BUNDLE</element><limits>
<limit><counter>INSTRUCTION</counter><value>COVEREDRATIO</value><minimum>0.85</minimum></limit>
<limit><counter>CLASS</counter><value>COVEREDRATIO</value><minimum>0.90</minimum></limit>
</limits></rule>
<!-- Classes added since the label layout design (section 10.7) are held to 100% line coverage from their first commit. A pattern matches nothing until its class exists,
so a later package only adds its class here if the design did not already name it. The BUNDLE rule above is unchanged and is never lowered. -->
<rule><element>CLASS</element><includes>
<include>com.kingsrook.qbits.documentgeneration.GenerationPorts.Diagnostic*</include>
<include>com.kingsrook.qbits.documentgeneration.GenerationPorts.Preview*</include>
<include>com.kingsrook.qbits.documentgeneration.GenerationPreviewProcesses*</include>
<include>com.kingsrook.qbits.documentgeneration.GenerationPreviewWidget*</include>
<include>com.kingsrook.qbits.documentgeneration.GenerationAssetProcesses*</include>
<include>com.kingsrook.qbits.documentgeneration.GenerationAssetTables*</include>
<include>com.kingsrook.qbits.documentgeneration.GenerationAssetStore*</include>
<include>com.kingsrook.qbits.documentgeneration.GenerationUpgradeProcesses*</include>
<include>com.kingsrook.qbits.documentgeneration.DocumentPreviewService*</include>
<include>com.kingsrook.qbits.documentgeneration.DocumentAvailability*</include>
</includes><limits>
<limit><counter>LINE</counter><value>COVEREDRATIO</value><minimum>1.00</minimum></limit>
</limits></rule></rules></configuration></execution>
</executions></plugin>
<plugin>
@@ -91,8 +91,32 @@ public class DocumentDefinitionService
return (GenerationStore.insert(GenerationTables.TYPE, new QRecord().withValue("name", text(name, 200))).getValueInteger("id"));
}
/***************************************************************************
** Rename a definition without changing its identity, geometry or revisions.
** Native UpdateAction retains the host's ordinary field-change audit path.
***************************************************************************/
public void rename(String table, Integer id, String name) throws QException
{
GenerationSecurity.require("configure");
if(table == null || !List.of(GenerationTables.TYPE, GenerationTables.STOCK, GenerationTables.TEMPLATE).contains(table))
{
throw new QBadRequestException("Only document types, stock and templates can be renamed.");
}
String label = text(name, 200).strip();
if(label.codePoints().anyMatch(Character::isISOControl))
{
throw new QBadRequestException("Document names cannot contain control characters.");
}
GenerationStore.atomic(List.of(table), () ->
{
GenerationStore.get(table, id);
GenerationStore.update(table, new QRecord().withValue("id", id).withValue("name", label));
return (null);
});
}
/*******************************************************************************
** Stock definitions are immutable; changing a draft selects a new stock row.
** Stock geometry is immutable; changing a draft selects a new stock row.
*******************************************************************************/
public Integer createStock(String name, GenerationPorts.Stock stock) throws QException
{
@@ -24,6 +24,7 @@ package com.kingsrook.qbits.documentgeneration;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.stream.Stream;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerMultiOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
@@ -138,7 +139,7 @@ public class DocumentGenerationQBitProducer implements QBitMetaDataProducer<Docu
QBitMetaData bit = getQBitMetaData();
instance.addQBit(bit);
MetaDataProducerMultiOutput output = new MetaDataProducerMultiOutput();
GenerationTables.tables(config).forEach(table ->
Stream.concat(GenerationTables.tables(config).stream(), GenerationAssetTables.tables(config).stream()).forEach(table ->
{
table.setSourceQBitName(bit.getName());
output.add(table);
@@ -146,6 +147,9 @@ public class DocumentGenerationQBitProducer implements QBitMetaDataProducer<Docu
output.addSelfToInstance(instance);
GenerationProcesses.register(instance, config.getSourceTables());
GenerationAuthoringProcesses.register(instance, config);
GenerationPreviewProcesses.register(instance, config);
GenerationAssetProcesses.register(instance, config);
GenerationUpgradeProcesses.register(instance, config);
return (new MetaDataProducerMultiOutput());
}
}
@@ -0,0 +1,46 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.documentgeneration;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
/*******************************************************************************
** Processes that upload and manage built-in template assets such as fonts.
** The producer already calls this registration point; it registers nothing yet.
*******************************************************************************/
final class GenerationAssetProcesses
{
/*******************************************************************************
** Metadata namespace.
*******************************************************************************/
private GenerationAssetProcesses()
{
}
/*******************************************************************************
** Called once by the producer after the generation and authoring processes.
*******************************************************************************/
static void register(QInstance instance, DocumentGenerationQBitConfig config) throws QException
{
}
}
@@ -0,0 +1,47 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.documentgeneration;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
/*******************************************************************************
** Tables of the built-in asset store. The producer already registers whatever
** this returns, with the QBit's own tables; it returns none yet.
*******************************************************************************/
final class GenerationAssetTables
{
/*******************************************************************************
** Metadata namespace.
*******************************************************************************/
private GenerationAssetTables()
{
}
/*******************************************************************************
** Complete table metadata, on the configured backend, for the producer to add.
*******************************************************************************/
static List<QTableMetaData> tables(DocumentGenerationQBitConfig config)
{
return (List.of());
}
}
@@ -68,7 +68,7 @@ public final class GenerationAuthoringProcesses
public static final String FORMAT = "document_generation_formats";
public static final String SOURCES = "document_generation_source_tables";
public static final String PREVIEW_SOURCE = "document_generation_preview_source";
private static final List<String> OPERATIONS = List.of("create", "edit", "publish", "preview", "asset", "catalog", "context", "removeAsset", "replaceAsset", "naming", "packingSlip");
private static final List<String> OPERATIONS = List.of("create", "rename", "edit", "publish", "preview", "asset", "catalog", "context", "removeAsset", "replaceAsset", "naming", "packingSlip");
/*******************************************************************************
** Metadata namespace.
@@ -127,6 +127,10 @@ public final class GenerationAuthoringProcesses
register(instance, GenerationTables.TEMPLATE, "packingSlip", "New packing slip");
}
register(instance, GenerationTables.TEMPLATE, "edit", "Edit layout");
for(String table : List.of(GenerationTables.TYPE, GenerationTables.STOCK, GenerationTables.TEMPLATE))
{
register(instance, table, "rename", "Rename");
}
register(instance, GenerationTables.TEMPLATE, "naming", "Edit output filename");
register(instance, GenerationTables.TEMPLATE, "publish", "Validate and publish draft");
register(instance, GenerationTables.TEMPLATE, "preview", "Preview draft");
@@ -262,6 +266,10 @@ public final class GenerationAuthoringProcesses
*******************************************************************************/
private static List<String> fields(String table, String operation)
{
if("rename".equals(operation))
{
return (List.of("name"));
}
if("naming".equals(operation))
{
return (List.of("outputBaseName", "outputNameContextKey"));
@@ -372,9 +380,16 @@ public final class GenerationAuthoringProcesses
{
if(input.getRecords() == null || input.getRecords().size() != 1)
{
throw new QBadRequestException("Select one document template.");
throw new QBadRequestException("Select one document definition.");
}
Integer id = input.getRecords().getFirst().getValueInteger("id");
if("rename".equals(operation))
{
QRecord record = GenerationStore.get(input.getProcess().getTableName(), id);
output.addValue("templateId", id);
output.addValue("name", record.getValueString("name"));
return;
}
var draft = new DocumentDefinitionService(GenerationProcesses.config(QContext.getQInstance())).loadDraft(id);
output.addValue("templateId", id);
output.addValue("expectedFingerprint", draft.fingerprint());
@@ -495,6 +510,11 @@ public final class GenerationAuthoringProcesses
service.saveOutputNaming(id, input.getValueString("outputBaseName"), input.getValueString("outputNameContextKey"), input.getValueString("expectedFingerprint"));
state = "OUTPUT_NAMING_SAVED";
}
else if("rename".equals(operation))
{
service.rename(table, id, input.getValueString("name"));
state = "RENAMED";
}
else if("edit".equals(operation))
{
service.saveSources(id, input.getValueInteger("stockId"), input.getValueString("pdfSource"), input.getValueString("zplSource"),
@@ -38,6 +38,17 @@ public final class GenerationPorts
{
}
/***************************************************************************
** A render target is absent or one of the resolutions the renderer supports.
***************************************************************************/
private static void renderTarget(Integer dpi)
{
if(dpi != null && !List.of(203, 300, 600).contains(dpi))
{
throw new IllegalArgumentException("A render target is 203, 300 or 600 dpi.");
}
}
/*******************************************************************************
** Initial output contracts; new formats require a qualified renderer adapter.
*******************************************************************************/
@@ -93,7 +104,99 @@ public final class GenerationPorts
SCANNER_BARCODE_PAYLOAD_UNSUPPORTED,
SCANNER_BARCODE_CHECKSUM_REQUIRED,
SCANNER_BARCODE_CHECKSUM_MISMATCH,
UNSUPPORTED_BARCODE_OPTION
UNSUPPORTED_BARCODE_OPTION,
//////////////////////////////////////////////////////////////////////////////////////
// version 2 scene refusals; no released renderer names these for a version 1 scene //
//////////////////////////////////////////////////////////////////////////////////////
SCENE_INVALID,
STOCK_MISMATCH,
TARGET_REQUIRES_SCENE_2,
ELEMENT_OUTSIDE_PRINTABLE_AREA,
TEXT_DOES_NOT_FIT,
VALUE_MISSING,
VALUE_TOO_LONG,
FONT_UNKNOWN,
FONT_MISSING_GLYPH,
RESERVED_FONT_NAME,
BARCODE_DOES_NOT_FIT,
GS1_INVALID,
ASSET_UNKNOWN,
PREVIEW_TOO_LARGE,
RENDERER_TOO_OLD
}
/***************************************************************************
** One renderer finding about a layout: a code from a closed vocabulary, the
** element id the template author wrote, and numbers. It has no free text, so
** no record value, context text or exception message can travel in it.
***************************************************************************/
public record Diagnostic(String code, Severity severity, String element, List<Integer> box, Map<String, Integer> measured, Map<String, Integer> limit)
{
private static final java.util.Set<String> WARNINGS = java.util.Set.of("TEXT_TRUNCATED", "TEXT_SMALL_FOR_DPI", "TEXT_LIGHT_FACE", "FIT_NOT_PROVEN",
"BARCODE_MODULE_BELOW_RECOMMENDED", "QUIET_ZONE_INTRUDED", "ELEMENTS_OVERLAP", "IMAGE_UPSCALED", "DEVICE_UNIT_USED", "PRINTER_FONT_NOT_SHOWN");
private static final java.util.Set<String> INFORMATION = java.util.Set.of("TEXT_SHRUNK", "TEXT_WRAPPED", "BARCODE_MODULE", "IMAGE_SCALED");
/************************************************************************
** An error stops printing; a warning and information do not.
************************************************************************/
public enum Severity
{
ERROR, WARNING, INFO
}
/************************************************************************
** Admit only the vocabulary: an error is a FailureCode name, a warning or
** information is one of the fixed names. The element is absent or an
** author-written id, the box is absent or x, y, width and height in dots,
** and measured values and limits are at most eight named integers each.
************************************************************************/
public Diagnostic
{
if(severity == null || code == null || !known(code, severity))
{
throw new IllegalArgumentException("Unknown diagnostic code for its severity.");
}
if(element != null && !element.matches("[A-Za-z][A-Za-z0-9_-]{0,31}"))
{
throw new IllegalArgumentException("Invalid diagnostic element id.");
}
if(box != null && (box.size() != 4 || box.stream().anyMatch(java.util.Objects::isNull) || box.get(2) < 0 || box.get(3) < 0))
{
throw new IllegalArgumentException("A diagnostic box is x, y, width and height in dots.");
}
box = box == null ? null : List.copyOf(box);
measured = numbers(measured);
limit = numbers(limit);
}
/************************************************************************
** The closed vocabulary, by severity.
************************************************************************/
private static boolean known(String code, Severity severity)
{
return (switch(severity)
{
case ERROR -> java.util.Arrays.stream(FailureCode.values()).anyMatch(failure -> failure.name().equals(code));
case WARNING -> WARNINGS.contains(code);
case INFO -> INFORMATION.contains(code);
});
}
/************************************************************************
** Freeze named integers; a name is a short identifier, never a sentence.
************************************************************************/
private static Map<String, Integer> numbers(Map<String, Integer> values)
{
if(values == null)
{
return (Map.of());
}
if(values.size() > 8 || values.entrySet().stream().anyMatch(entry -> entry.getKey() == null || !entry.getKey().matches("[a-z][A-Za-z0-9]{0,31}") || entry.getValue() == null))
{
throw new IllegalArgumentException("Diagnostic values are at most eight named integers.");
}
return (Map.copyOf(values));
}
}
/***************************************************************************
@@ -103,14 +206,29 @@ public final class GenerationPorts
{
private static final long serialVersionUID = 1L;
private final FailureCode code;
private final transient Diagnostic diagnostic;
/***************************************************************************
** Construct a native process error whose public message is the safe code.
***************************************************************************/
public RenderException(FailureCode code)
{
this(code, null);
}
/***************************************************************************
** A refusal that also says where and by how much: the diagnostic, when
** present, is the error with this same code. The message stays the code.
***************************************************************************/
public RenderException(FailureCode code, Diagnostic diagnostic)
{
super(java.util.Objects.requireNonNull(code).name());
if(diagnostic != null && (diagnostic.severity() != Diagnostic.Severity.ERROR || !code.name().equals(diagnostic.code())))
{
throw new IllegalArgumentException("A refusal's diagnostic must be the error with the same code.");
}
this.code = code;
this.diagnostic = diagnostic;
}
/***************************************************************************
@@ -120,6 +238,14 @@ public final class GenerationPorts
{
return (code);
}
/***************************************************************************
** The detail the worker gave with the refusal, or null when it gave none.
***************************************************************************/
public Diagnostic diagnostic()
{
return (diagnostic);
}
}
/*******************************************************************************
@@ -223,7 +349,99 @@ public final class GenerationPorts
** Bounded worker job with all revision inputs captured before dispatch.
*******************************************************************************/
public record RenderRequest(String requestId, String actorId, Source source, Format format, Revision revision,
Map<String, Object> context, Integer maximumBytes) {}
Map<String, Object> context, Integer maximumBytes, Integer targetDpi)
{
/***************************************************************************
** The render target is absent (the stock's own resolution) or a resolution
** the renderer supports.
***************************************************************************/
public RenderRequest
{
renderTarget(targetDpi);
}
/***************************************************************************
** Existing callers render at the stock's own resolution.
***************************************************************************/
public RenderRequest(String requestId, String actorId, Source source, Format format, Revision revision,
Map<String, Object> context, Integer maximumBytes)
{
this(requestId, actorId, source, format, revision, context, maximumBytes, null);
}
}
/***************************************************************************
** A layout preview job: the print path stopped before retention. It has no
** request id and no source record because nothing is written for it.
***************************************************************************/
public record PreviewRequest(String actorId, Format format, Revision revision, Map<String, Object> context, Integer targetDpi, Integer maximumBytes)
{
/***************************************************************************
** Refuse a job that names no format or content, an unsupported render
** target or no output bound.
***************************************************************************/
public PreviewRequest
{
if(format == null || revision == null || maximumBytes == null || maximumBytes < 1)
{
throw new IllegalArgumentException("A preview needs a format, a revision and an output limit.");
}
renderTarget(targetDpi);
}
}
/***************************************************************************
** One preview page: a PNG whose pixels are printer dots at the render target.
***************************************************************************/
public record PreviewPage(byte[] png, Integer widthDots, Integer heightDots)
{
private static final byte[] SIGNATURE = {(byte) 0x89, 'P', 'N', 'G', 13, 10, 26, 10};
/***************************************************************************
** Only a PNG with a stated size is a page; the bytes cannot change later.
***************************************************************************/
public PreviewPage
{
if(png == null || png.length < SIGNATURE.length || !java.util.Arrays.equals(png, 0, SIGNATURE.length, SIGNATURE, 0, SIGNATURE.length)
|| widthDots == null || widthDots < 1 || heightDots == null || heightDots < 1)
{
throw new IllegalArgumentException("A preview page is a PNG with its size in dots.");
}
png = png.clone();
}
/***************************************************************************
** Never expose the stored byte array to a consumer.
***************************************************************************/
@Override
public byte[] png()
{
return (png.clone());
}
}
/***************************************************************************
** What a preview shows: the pages, whether they are exactly what prints,
** whether the layout can print at all, every diagnostic, and the size and
** hash of the output the pages were decoded from.
***************************************************************************/
public record Preview(List<PreviewPage> pages, Boolean exact, Boolean printable, List<Diagnostic> diagnostics, Long outputBytes, String outputSha256)
{
/***************************************************************************
** Freeze the lists and refuse a contradiction: a layout with an error
** diagnostic is not printable.
***************************************************************************/
public Preview
{
pages = List.copyOf(pages);
diagnostics = List.copyOf(diagnostics);
if(exact == null || printable == null || outputBytes == null || outputBytes < 0 || outputSha256 == null || !outputSha256.matches("[a-f0-9]{64}")
|| (printable && diagnostics.stream().anyMatch(diagnostic -> diagnostic.severity() == Diagnostic.Severity.ERROR)))
{
throw new IllegalArgumentException("Invalid preview result.");
}
}
}
/*******************************************************************************
** Temporary bounded worker result, discarded after retained intake.
@@ -393,6 +611,16 @@ public final class GenerationPorts
throw new QException("Renderer barcode reference is unavailable.");
}
/***********************************************************************
** Optional layout preview: the print path stopped before retention, with
** the pages decoded from the validated output. A renderer that does not
** provide it refuses, so an existing adapter is unchanged.
***********************************************************************/
default Preview preview(PreviewRequest request) throws QException
{
throw new QException("Renderer preview is unavailable.");
}
/*******************************************************************************
** Authorize assets and validate user source outside the application JVM.
*******************************************************************************/
@@ -38,7 +38,8 @@ import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaDat
/*******************************************************************************
** Qualification of the native QQQ PostgreSQL17 backend (vendor and backend type
** "postgres") and this QBit's owned tables. Observes the live connection only;
** it never changes session or server settings, schema or data.
** it never changes session or server settings, schema or data. Amazon Aurora
** PostgreSQL is the one accepted variant: see durable().
*******************************************************************************/
final class GenerationPostgres
{
@@ -46,7 +47,11 @@ final class GenerationPostgres
private static final String SETTINGS = "SELECT current_setting('server_version_num')::int, current_setting('transaction_read_only'), pg_is_in_recovery(),"
+ " current_setting('synchronous_commit'), current_setting('fsync'), current_setting('full_page_writes'), current_setting('TimeZone'),"
+ " (SELECT setting::bigint FROM pg_settings WHERE name='lock_timeout'), current_setting('transaction_isolation'), current_schema(), current_database()";
+ " (SELECT setting::bigint FROM pg_settings WHERE name='lock_timeout'), current_setting('transaction_isolation'), current_schema(), current_database(),"
+ " to_regprocedure('aurora_version()') IS NOT NULL";
private static final String REQUIREMENTS = "Generation requires synchronous PostgreSQL commits, full-page writes (except on Amazon Aurora PostgreSQL),"
+ " a primary writable server, READ COMMITTED and UTC sessions";
private static final String TABLE = "SELECT c.relpersistence FROM pg_class c JOIN pg_namespace n ON n.oid=c.relnamespace"
+ " WHERE n.nspname=current_schema() AND c.relname=? AND c.relkind='r'";
@@ -91,7 +96,25 @@ final class GenerationPostgres
}
/***************************************************************************
** Exact server, durability, session and owned-table checks for PostgreSQL 17.
** Commit durability of the observed server. Every server needs
** synchronous_commit on or remote_apply and fsync on. Stock PostgreSQL also
** needs full_page_writes on, which protects crash recovery from torn pages.
** Amazon Aurora PostgreSQL runs with full_page_writes off and offers no
** parameter to change it, because its storage layer does not write torn
** pages; so off is accepted there, and only there. Aurora is detected in the
** settings query by to_regprocedure('aurora_version()') IS NOT NULL, which is
** true on Aurora, false on stock PostgreSQL and never raises an error (calling
** a missing function would abort the transaction).
***************************************************************************/
static boolean durable(String synchronousCommit, String fsync, String fullPageWrites, boolean aurora)
{
return (("on".equals(synchronousCommit) || "remote_apply".equals(synchronousCommit)) && "on".equals(fsync)
&& ("on".equals(fullPageWrites) || aurora && "off".equals(fullPageWrites)));
}
/***************************************************************************
** Exact server, durability, session and owned-table checks for PostgreSQL 17,
** with the single Amazon Aurora PostgreSQL allowance stated on durable().
***************************************************************************/
static void qualify(Connection connection, RDBMSBackendMetaData backend) throws SQLException, QException
{
@@ -105,7 +128,7 @@ final class GenerationPostgres
{
if(!row.next())
{
throw new QException("Generation requires synchronous PostgreSQL commits, full-page writes, a primary writable server, READ COMMITTED and UTC sessions.");
throw new QException(REQUIREMENTS + ".");
}
List<String> weak = new java.util.ArrayList<>();
if(row.getInt(1) / 10000 != 17 || row.getString(10) == null || !Objects.equals(backend.getDatabaseName(), row.getString(11)))
@@ -116,7 +139,7 @@ final class GenerationPostgres
{
weak.add("writable primary");
}
if(!Set.of("on", "remote_apply").contains(row.getString(4)) || !"on".equals(row.getString(5)) || !"on".equals(row.getString(6)))
if(!durable(row.getString(4), row.getString(5), row.getString(6), row.getBoolean(12)))
{
weak.add("synchronous commit");
}
@@ -130,7 +153,7 @@ final class GenerationPostgres
}
if(!weak.isEmpty())
{
throw new QException("Generation requires synchronous PostgreSQL commits, full-page writes, a primary writable server, READ COMMITTED and UTC sessions; not satisfied: " + String.join(", ", weak));
throw new QException(REQUIREMENTS + "; not satisfied: " + String.join(", ", weak));
}
long lockTimeout = row.getLong(8);
if(lockTimeout < 1 || lockTimeout > 5000)
@@ -0,0 +1,46 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.documentgeneration;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
/*******************************************************************************
** Layout preview processes for a template: sample, stress and record data.
** The producer already calls this registration point; it registers nothing yet.
*******************************************************************************/
final class GenerationPreviewProcesses
{
/*******************************************************************************
** Metadata namespace.
*******************************************************************************/
private GenerationPreviewProcesses()
{
}
/*******************************************************************************
** Called once by the producer after the generation and authoring processes.
*******************************************************************************/
static void register(QInstance instance, DocumentGenerationQBitConfig config) throws QException
{
}
}
@@ -0,0 +1,45 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.documentgeneration;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
/*******************************************************************************
** The "Label preview" section of the template record screen. The template
** table already offers itself here after its own sections; nothing is added yet.
*******************************************************************************/
final class GenerationPreviewWidget
{
/*******************************************************************************
** Metadata namespace.
*******************************************************************************/
private GenerationPreviewWidget()
{
}
/*******************************************************************************
** Called once for the template table, after its standard sections.
*******************************************************************************/
static void section(QTableMetaData table)
{
}
}
@@ -245,7 +245,7 @@ final class GenerationStore
}
/*******************************************************************************
** Updates are limited to drafts, active publication references and job state.
** Updates allow definition names, drafts, publication references and job state.
** Batch completion is a monotonic marker only; its accepted pins never change.
*******************************************************************************/
static void update(String table, QRecord row) throws QException
@@ -256,7 +256,11 @@ final class GenerationStore
&& row.getValues().keySet().equals(java.util.Set.of("id", "completed"))
&& row.getValueInteger("id") != null && row.getValueInteger("id") > 0
&& Boolean.TRUE.equals(row.getValue("completed"));
if(!GenerationTables.TEMPLATE.equals(table) && !GenerationTables.REQUEST.equals(table) && !batchCompletion)
boolean definitionName = List.of(GenerationTables.TYPE, GenerationTables.STOCK).contains(table)
&& row.getValues().keySet().equals(java.util.Set.of("id", "name"))
&& row.getValueInteger("id") != null && row.getValueInteger("id") > 0
&& row.getValueString("name") != null && !row.getValueString("name").isBlank() && row.getValueString("name").length() <= 200;
if(!GenerationTables.TEMPLATE.equals(table) && !GenerationTables.REQUEST.equals(table) && !batchCompletion && !definitionName)
{
throw (new QException("Immutable Document Generation record."));
}
@@ -182,6 +182,7 @@ public final class GenerationTables
table.withSection(new QFieldSection("advanced", new QIcon("settings"), Tier.T3, List.of("id", "createDate", "scriptArtifact", "assetIds")).withLabel("Advanced references")
.withCollapsible(CollapsibleMetaData.INITIALLY_CLOSED)
.withHelpContent(help("These are exact stored identities for support and provenance. Change scripts and included assets through the named actions; do not edit integrity references.")));
GenerationPreviewWidget.section(table);
}
/*******************************************************************************
@@ -0,0 +1,46 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.documentgeneration;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
/*******************************************************************************
** The authoring action that converts a version 1 scene to version 2.
** The producer already calls this registration point; it registers nothing yet.
*******************************************************************************/
final class GenerationUpgradeProcesses
{
/*******************************************************************************
** Metadata namespace.
*******************************************************************************/
private GenerationUpgradeProcesses()
{
}
/*******************************************************************************
** Called once by the producer after the generation and authoring processes.
*******************************************************************************/
static void register(QInstance instance, DocumentGenerationQBitConfig config) throws QException
{
}
}
@@ -120,6 +120,30 @@ class DocumentPublicationJdbcTest
/***************************************************************************
** Native DML renames commit without changing stock geometry or publication.
***************************************************************************/
@Test
void definitionRenamesPreservePublishedSnapshot() throws Exception
{
Integer revisionId = definitions.publish(template);
var before = GenerationStore.get(GenerationTables.TEMPLATE, template);
var published = new java.util.HashMap<>(GenerationStore.get(GenerationTables.REVISION, revisionId).getValues());
Integer stockId = before.getValueInteger("stockId");
Integer typeId = before.getValueInteger("typeId");
var stock = new java.util.HashMap<>(GenerationStore.get(GenerationTables.STOCK, stockId).getValues());
definitions.rename(GenerationTables.TYPE, typeId, "Readable type");
definitions.rename(GenerationTables.STOCK, stockId, "A4 paper");
definitions.rename(GenerationTables.TEMPLATE, template, "Readable document");
stock.put("name", "A4 paper");
assertThat(GenerationStore.get(GenerationTables.STOCK, stockId).getValues()).isEqualTo(stock);
assertThat(GenerationStore.get(GenerationTables.REVISION, revisionId).getValues()).isEqualTo(published);
assertThat(value("SELECT published_revision_id FROM document_generation_template WHERE id=" + template)).isEqualTo(revisionId);
assertThat(GenerationStore.get(GenerationTables.TYPE, typeId).getValueString("name")).isEqualTo("Readable type");
assertThat(GenerationStore.get(GenerationTables.TEMPLATE, template).getValueString("name")).isEqualTo("Readable document");
}
/***************************************************************************
** Drop the test context, never reuse another fixture's transaction.
***************************************************************************/
@@ -0,0 +1,305 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.documentgeneration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.kingsrook.qbits.documentgeneration.GenerationPorts.Diagnostic;
import com.kingsrook.qbits.documentgeneration.GenerationPorts.Diagnostic.Severity;
import com.kingsrook.qbits.documentgeneration.GenerationPorts.FailureCode;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
/*******************************************************************************
** The preview, diagnostic and render target ports: what each value admits, what
** the defaults refuse, and that every existing constructor and adapter still
** compiles and means what it did.
*******************************************************************************/
class GenerationPortsContractTest
{
private static final GenerationPorts.Stock STOCK = new GenerationPorts.Stock(101600, 25400, 3000, 3000, 3000, 3000, 203);
private static final GenerationPorts.Revision REVISION = new GenerationPorts.Revision(1, 2, "orders", STOCK, null, "{}", null, List.of());
private static final GenerationPorts.Source SOURCE = new GenerationPorts.Source("orders", "1");
private static final byte[] PNG = {(byte) 0x89, 'P', 'N', 'G', 13, 10, 26, 10, 0};
private static final String SHA = "0".repeat(64);
/***************************************************************************
** An adapter written before the preview port: it implements what it always
** did, and this class compiling is the source compatibility proof.
***************************************************************************/
private static final class ExistingAdapter implements GenerationPorts.IsolatedRenderer
{
/************************************************************************
** Accept every revision.
************************************************************************/
@Override
public void validate(String actorId, GenerationPorts.Revision revision)
{
}
/************************************************************************
** Return fixed bytes.
************************************************************************/
@Override
public GenerationPorts.Content render(GenerationPorts.RenderRequest request)
{
return (new GenerationPorts.Content(new byte[] {1}));
}
}
/***************************************************************************
** The seven-argument request and the one-argument refusal are unchanged, and
** an existing adapter refuses the optional operations it never had.
***************************************************************************/
@Test
void existingConstructorsAndAdaptersMeanWhatTheyDid() throws Exception
{
var request = new GenerationPorts.RenderRequest("request-1", "actor-1", SOURCE, GenerationPorts.Format.ZPL, REVISION, Map.of("record", Map.of()), 1024);
assertNull(request.targetDpi());
assertEquals(request, new GenerationPorts.RenderRequest("request-1", "actor-1", SOURCE, GenerationPorts.Format.ZPL, REVISION, Map.of("record", Map.of()), 1024, null));
var refusal = new GenerationPorts.RenderException(FailureCode.BARCODE_FIT_TOO_SMALL);
assertEquals(FailureCode.BARCODE_FIT_TOO_SMALL, refusal.code());
assertEquals("BARCODE_FIT_TOO_SMALL", refusal.getMessage());
assertNull(refusal.diagnostic());
assertThrows(NullPointerException.class, () -> new GenerationPorts.RenderException(null));
var adapter = new ExistingAdapter();
assertArrayEquals(new byte[] {1}, adapter.render(request).bytes());
adapter.validate("actor-1", REVISION);
assertEquals("Renderer preview is unavailable.", assertThrows(QException.class,
() -> adapter.preview(new GenerationPorts.PreviewRequest("actor-1", GenerationPorts.Format.ZPL, REVISION, Map.of(), null, 1024))).getMessage());
assertEquals("Renderer barcode reference is unavailable.", assertThrows(QException.class, adapter::barcodeReference).getMessage());
}
/***************************************************************************
** Stored error codes are read back by name, so the released names keep their
** order and the version 2 refusals follow them.
***************************************************************************/
@Test
void failureCodesKeepTheReleasedNamesThenAddTheSceneTwoRefusals()
{
List<String> names = Arrays.stream(FailureCode.values()).map(Enum::name).toList();
assertEquals(List.of("BARCODE_FIT_TOO_SMALL", "BARCODE_FIT_UNSUPPORTED", "BARCODE_FIT_BOX_OUTSIDE_STOCK", "BARCODE_FIT_GEOMETRY_MISMATCH", "INVALID_BARCODE_FIT_PROFILE",
"INVALID_BARCODE_FIT_REQUEST", "BARCODE_SYMBOLOGY_NOT_ALLOWED", "SYMBOLOGY_ALLOWLIST_REQUIRED", "SCANNER_BARCODE_PROFILE_UNSUPPORTED", "SCANNER_BARCODE_PAYLOAD_UNSUPPORTED",
"SCANNER_BARCODE_CHECKSUM_REQUIRED", "SCANNER_BARCODE_CHECKSUM_MISMATCH", "UNSUPPORTED_BARCODE_OPTION"), names.subList(0, 13));
assertEquals(List.of("SCENE_INVALID", "STOCK_MISMATCH", "TARGET_REQUIRES_SCENE_2", "ELEMENT_OUTSIDE_PRINTABLE_AREA", "TEXT_DOES_NOT_FIT", "VALUE_MISSING", "VALUE_TOO_LONG",
"FONT_UNKNOWN", "FONT_MISSING_GLYPH", "RESERVED_FONT_NAME", "BARCODE_DOES_NOT_FIT", "GS1_INVALID", "ASSET_UNKNOWN", "PREVIEW_TOO_LARGE", "RENDERER_TOO_OLD"), names.subList(13, names.size()));
}
/***************************************************************************
** A render target is absent or a resolution the renderer supports, for a
** render and for a preview alike.
***************************************************************************/
@ParameterizedTest
@ValueSource(ints = {0, 72, 200, 204, 301, 1200, -203})
void anUnsupportedRenderTargetIsRefused(int dpi)
{
assertThrows(IllegalArgumentException.class, () -> new GenerationPorts.RenderRequest("request-1", "actor-1", SOURCE, GenerationPorts.Format.ZPL, REVISION, Map.of(), 1024, dpi));
assertThrows(IllegalArgumentException.class, () -> new GenerationPorts.PreviewRequest("actor-1", GenerationPorts.Format.ZPL, REVISION, Map.of(), dpi, 1024));
}
/***************************************************************************
** Each supported resolution is carried unchanged.
***************************************************************************/
@ParameterizedTest
@ValueSource(ints = {203, 300, 600})
void aSupportedRenderTargetIsCarried(int dpi)
{
assertEquals(dpi, new GenerationPorts.RenderRequest("request-1", "actor-1", SOURCE, GenerationPorts.Format.ZPL, REVISION, Map.of(), 1024, dpi).targetDpi());
assertEquals(dpi, new GenerationPorts.PreviewRequest("actor-1", GenerationPorts.Format.ZPL, REVISION, Map.of(), dpi, 1024).targetDpi());
}
/***************************************************************************
** A preview job names its format, its content and an output bound.
***************************************************************************/
@Test
void aPreviewRequestNeedsAFormatARevisionAndALimit()
{
assertThrows(IllegalArgumentException.class, () -> new GenerationPorts.PreviewRequest("actor-1", null, REVISION, Map.of(), null, 1024));
assertThrows(IllegalArgumentException.class, () -> new GenerationPorts.PreviewRequest("actor-1", GenerationPorts.Format.ZPL, null, Map.of(), null, 1024));
assertThrows(IllegalArgumentException.class, () -> new GenerationPorts.PreviewRequest("actor-1", GenerationPorts.Format.ZPL, REVISION, Map.of(), null, null));
assertThrows(IllegalArgumentException.class, () -> new GenerationPorts.PreviewRequest("actor-1", GenerationPorts.Format.ZPL, REVISION, Map.of(), null, 0));
assertEquals(REVISION, new GenerationPorts.PreviewRequest("actor-1", GenerationPorts.Format.PDF, REVISION, Map.of(), null, 1).revision());
}
/***************************************************************************
** A page is a PNG with a size, and neither the caller nor a consumer can
** change its bytes afterwards.
***************************************************************************/
@Test
void aPreviewPageIsAnImmutablePngWithItsSize()
{
byte[] supplied = PNG.clone();
var page = new GenerationPorts.PreviewPage(supplied, 812, 203);
supplied[8] = 9;
page.png()[8] = 9;
assertArrayEquals(PNG, page.png());
assertEquals(812, page.widthDots());
assertEquals(203, page.heightDots());
byte[] jpeg = PNG.clone();
jpeg[1] = 'J';
for(byte[] bytes : new byte[][] {null, new byte[0], Arrays.copyOf(PNG, 7), jpeg})
{
assertThrows(IllegalArgumentException.class, () -> new GenerationPorts.PreviewPage(bytes, 812, 203));
}
assertThrows(IllegalArgumentException.class, () -> new GenerationPorts.PreviewPage(PNG, null, 203));
assertThrows(IllegalArgumentException.class, () -> new GenerationPorts.PreviewPage(PNG, 0, 203));
assertThrows(IllegalArgumentException.class, () -> new GenerationPorts.PreviewPage(PNG, 812, null));
assertThrows(IllegalArgumentException.class, () -> new GenerationPorts.PreviewPage(PNG, 812, 0));
}
/***************************************************************************
** A preview freezes its lists, states its output exactly, and cannot call a
** layout with an error printable.
***************************************************************************/
@Test
void aPreviewIsFrozenAndCannotContradictItsDiagnostics()
{
var page = new GenerationPorts.PreviewPage(PNG, 812, 203);
var error = new Diagnostic("TEXT_DOES_NOT_FIT", Severity.ERROR, "caption", null, null, null);
var warning = new Diagnostic("PRINTER_FONT_NOT_SHOWN", Severity.WARNING, null, null, null, null);
List<GenerationPorts.PreviewPage> pages = new ArrayList<>(List.of(page));
List<Diagnostic> diagnostics = new ArrayList<>(List.of(warning));
var preview = new GenerationPorts.Preview(pages, false, true, diagnostics, 1234L, SHA);
pages.clear();
diagnostics.clear();
assertEquals(List.of(page), preview.pages());
assertEquals(List.of(warning), preview.diagnostics());
assertThrows(UnsupportedOperationException.class, () -> preview.pages().clear());
assertThrows(UnsupportedOperationException.class, () -> preview.diagnostics().clear());
assertEquals(false, new GenerationPorts.Preview(List.of(), true, false, List.of(error), 0L, SHA).printable());
assertThrows(IllegalArgumentException.class, () -> new GenerationPorts.Preview(List.of(page), true, true, List.of(warning, error), 1234L, SHA));
assertThrows(IllegalArgumentException.class, () -> new GenerationPorts.Preview(List.of(page), null, true, List.of(), 1234L, SHA));
assertThrows(IllegalArgumentException.class, () -> new GenerationPorts.Preview(List.of(page), true, null, List.of(), 1234L, SHA));
assertThrows(IllegalArgumentException.class, () -> new GenerationPorts.Preview(List.of(page), true, true, List.of(), null, SHA));
assertThrows(IllegalArgumentException.class, () -> new GenerationPorts.Preview(List.of(page), true, true, List.of(), -1L, SHA));
assertThrows(IllegalArgumentException.class, () -> new GenerationPorts.Preview(List.of(page), true, true, List.of(), 1234L, null));
assertThrows(IllegalArgumentException.class, () -> new GenerationPorts.Preview(List.of(page), true, true, List.of(), 1234L, SHA.toUpperCase().replace('0', 'A')));
assertThrows(NullPointerException.class, () -> new GenerationPorts.Preview(null, true, true, List.of(), 1234L, SHA));
assertThrows(NullPointerException.class, () -> new GenerationPorts.Preview(List.of(page), true, true, null, 1234L, SHA));
}
/***************************************************************************
** The design's example diagnostic, and every code of the first release under
** its own severity only.
***************************************************************************/
@Test
void aDiagnosticAdmitsOnlyItsClosedVocabulary()
{
var example = new Diagnostic("TEXT_DOES_NOT_FIT", Severity.ERROR, "caption", List.of(24, 131, 764, 56),
Map.of("widthDots", 902, "sizeCentipoints", 700, "lines", 2), Map.of("widthDots", 764, "lines", 2));
assertEquals(List.of(24, 131, 764, 56), example.box());
assertEquals(902, example.measured().get("widthDots"));
assertEquals(2, example.limit().get("lines"));
for(FailureCode code : FailureCode.values())
{
assertEquals(code.name(), new Diagnostic(code.name(), Severity.ERROR, null, null, null, null).code());
assertThrows(IllegalArgumentException.class, () -> new Diagnostic(code.name(), Severity.WARNING, null, null, null, null));
assertThrows(IllegalArgumentException.class, () -> new Diagnostic(code.name(), Severity.INFO, null, null, null, null));
}
for(String code : List.of("TEXT_TRUNCATED", "TEXT_SMALL_FOR_DPI", "TEXT_LIGHT_FACE", "FIT_NOT_PROVEN", "BARCODE_MODULE_BELOW_RECOMMENDED", "QUIET_ZONE_INTRUDED", "ELEMENTS_OVERLAP",
"IMAGE_UPSCALED", "DEVICE_UNIT_USED", "PRINTER_FONT_NOT_SHOWN"))
{
assertEquals(Severity.WARNING, new Diagnostic(code, Severity.WARNING, null, null, null, null).severity());
assertThrows(IllegalArgumentException.class, () -> new Diagnostic(code, Severity.ERROR, null, null, null, null));
assertThrows(IllegalArgumentException.class, () -> new Diagnostic(code, Severity.INFO, null, null, null, null));
}
for(String code : List.of("TEXT_SHRUNK", "TEXT_WRAPPED", "BARCODE_MODULE", "IMAGE_SCALED"))
{
assertEquals(Severity.INFO, new Diagnostic(code, Severity.INFO, null, null, null, null).severity());
assertThrows(IllegalArgumentException.class, () -> new Diagnostic(code, Severity.ERROR, null, null, null, null));
assertThrows(IllegalArgumentException.class, () -> new Diagnostic(code, Severity.WARNING, null, null, null, null));
}
assertThrows(IllegalArgumentException.class, () -> new Diagnostic("RENDER_FAILED", Severity.ERROR, null, null, null, null));
assertThrows(IllegalArgumentException.class, () -> new Diagnostic("Customer Jane Example could not be printed", Severity.ERROR, null, null, null, null));
assertThrows(IllegalArgumentException.class, () -> new Diagnostic(null, Severity.ERROR, null, null, null, null));
assertThrows(IllegalArgumentException.class, () -> new Diagnostic("TEXT_DOES_NOT_FIT", null, null, null, null, null));
}
/***************************************************************************
** Nothing but an author-written id and numbers fits beside the code: a
** sentence, a record value or a message has no field that will hold it.
***************************************************************************/
@Test
void aDiagnosticCannotHoldFreeText()
{
assertEquals("order_id-2", new Diagnostic("VALUE_MISSING", Severity.ERROR, "order_id-2", null, null, null).element());
for(String element : List.of("", "9caption", "two words", "Jane Example, 1 Test Street", "a".repeat(33), "caption\n", "/record/name"))
{
assertThrows(IllegalArgumentException.class, () -> new Diagnostic("VALUE_MISSING", Severity.ERROR, element, null, null, null), element);
}
for(List<Integer> box : List.of(List.<Integer>of(), List.of(1, 2, 3), List.of(1, 2, 3, 4, 5), List.of(1, 2, -3, 4), List.of(1, 2, 3, -4), Arrays.asList(1, null, 3, 4)))
{
assertThrows(IllegalArgumentException.class, () -> new Diagnostic("ELEMENT_OUTSIDE_PRINTABLE_AREA", Severity.ERROR, "caption", box, null, null), String.valueOf(box));
}
assertEquals(List.of(-12, -4, 0, 0), new Diagnostic("ELEMENT_OUTSIDE_PRINTABLE_AREA", Severity.ERROR, "caption", List.of(-12, -4, 0, 0), null, null).box());
Map<String, Integer> nullValue = new HashMap<>();
nullValue.put("widthDots", null);
Map<String, Integer> nullName = new HashMap<>();
nullName.put(null, 1);
Map<String, Integer> tooMany = new HashMap<>();
for(int index = 0; index < 9; index++)
{
tooMany.put("value" + index, index);
}
for(Map<String, Integer> values : List.of(Map.of("Width", 1), Map.of("width dots", 1), Map.of("the value Jane Example is too long", 1), Map.of("w".repeat(33), 1), nullValue, nullName, tooMany))
{
assertThrows(IllegalArgumentException.class, () -> new Diagnostic("TEXT_DOES_NOT_FIT", Severity.ERROR, "caption", null, values, null));
assertThrows(IllegalArgumentException.class, () -> new Diagnostic("TEXT_DOES_NOT_FIT", Severity.ERROR, "caption", null, null, values));
}
List<Integer> box = new ArrayList<>(List.of(1, 2, 3, 4));
Map<String, Integer> measured = new HashMap<>(Map.of("widthDots", 902));
var diagnostic = new Diagnostic("TEXT_DOES_NOT_FIT", Severity.ERROR, "caption", box, measured, null);
box.set(0, 99);
measured.put("widthDots", 1);
assertEquals(List.of(1, 2, 3, 4), diagnostic.box());
assertEquals(Map.of("widthDots", 902), diagnostic.measured());
assertEquals(Map.of(), diagnostic.limit());
assertThrows(UnsupportedOperationException.class, () -> diagnostic.box().set(0, 99));
assertThrows(UnsupportedOperationException.class, () -> diagnostic.measured().put("widthDots", 1));
}
/***************************************************************************
** A refusal carries only the error diagnostic with its own code, and its
** public message is still the code alone.
***************************************************************************/
@Test
void aRefusalCarriesOnlyItsOwnErrorDiagnostic()
{
var detail = new Diagnostic("TEXT_DOES_NOT_FIT", Severity.ERROR, "caption", List.of(24, 131, 764, 56), Map.of("widthDots", 902), Map.of("widthDots", 764));
var refusal = new GenerationPorts.RenderException(FailureCode.TEXT_DOES_NOT_FIT, detail);
assertEquals(FailureCode.TEXT_DOES_NOT_FIT, refusal.code());
assertEquals(detail, refusal.diagnostic());
assertEquals("TEXT_DOES_NOT_FIT", refusal.getMessage());
assertNull(new GenerationPorts.RenderException(FailureCode.TEXT_DOES_NOT_FIT, null).diagnostic());
assertThrows(IllegalArgumentException.class, () -> new GenerationPorts.RenderException(FailureCode.VALUE_MISSING, detail));
assertThrows(IllegalArgumentException.class, () -> new GenerationPorts.RenderException(FailureCode.TEXT_DOES_NOT_FIT, new Diagnostic("TEXT_SHRUNK", Severity.INFO, "caption", null, null, null)));
assertThrows(NullPointerException.class, () -> new GenerationPorts.RenderException(null, detail));
}
}
@@ -807,6 +807,93 @@ class GenerationProcessesTest
return (output);
}
/***************************************************************************
** Rename forms change only the display name and preserve published bytes/pins.
***************************************************************************/
@Test
void renameDefinitionsPreservesPublishedRevisionAndStockGeometry() throws Exception
{
QRecord original = GenerationStore.get(GenerationTables.TEMPLATE, template);
int revisionId = original.getValueInteger("publishedRevisionId");
Map<String, java.io.Serializable> revision = new HashMap<>(GenerationStore.get(GenerationTables.REVISION, revisionId).getValues());
for(String table : List.of(GenerationTables.TYPE, GenerationTables.STOCK, GenerationTables.TEMPLATE))
{
int id = table.equals(GenerationTables.TYPE) ? original.getValueInteger("typeId")
: table.equals(GenerationTables.STOCK) ? original.getValueInteger("stockId") : template;
Map<String, java.io.Serializable> before = new HashMap<>(GenerationStore.get(table, id).getValues());
var process = instance.getProcess(GenerationAuthoringProcesses.name(table, "rename"));
assertThat(process.getFrontendStep("edit").getFormFields()).extracting(field -> field.getName()).containsExactly("name");
var input = author(table, "rename", id);
assertThat(input.getValueString("name")).isEqualTo(before.get("name"));
input.addValue("name", " Readable label ");
saveAuthor(input);
before.put("name", "Readable label");
assertThat(GenerationStore.get(table, id).getValues()).isEqualTo(before);
}
assertThat(GenerationStore.get(GenerationTables.REVISION, revisionId).getValues()).isEqualTo(revision);
}
/***************************************************************************
** Renames use ordinary QQQ field auditing rather than an untracked SQL write.
***************************************************************************/
@Test
void renameEmitsConfiguredNativeFieldAudit() throws Exception
{
QContext.getQSession().getUser().setFullName("Synthetic Alice");
new com.kingsrook.qqq.backend.core.model.audits.AuditsMetaDataProvider()
.defineAll(instance, instance.getTable(GenerationTables.TEMPLATE).getBackendName(), null);
instance.getTable(GenerationTables.TEMPLATE).setAuditRules(
new com.kingsrook.qqq.backend.core.model.metadata.audits.QAuditRules()
.withAuditLevel(com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel.FIELD));
new QInstanceEnricher(instance).enrich();
new DocumentDefinitionService(config).rename(GenerationTables.TEMPLATE, template, "Readable document");
var query = new com.kingsrook.qqq.backend.core.actions.tables.QueryAction();
var auditQuery = new com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput("auditDetail");
auditQuery.withPermissionBypass("Test fixture inspects native audit output after the authorized rename.");
var details = query.execute(auditQuery).getRecords();
assertThat(details).anyMatch(row -> "name".equals(row.getValueString("fieldName"))
&& row.getValueString("message").contains("Readable document"));
}
/***************************************************************************
** Invalid names and unowned immutable rows cannot be changed through rename.
***************************************************************************/
@Test
void renameRejectsInvalidNamesAndCannotChangeStockDimensions() throws Exception
{
var service = new DocumentDefinitionService(config);
for(String name : List.of(" ", "x".repeat(201), "bad\nname"))
{
assertThatThrownBy(() -> service.rename(GenerationTables.TEMPLATE, template, name)).isInstanceOf(QException.class);
}
assertThatThrownBy(() -> service.rename(GenerationTables.TEMPLATE, template, null)).isInstanceOf(QException.class);
assertThatThrownBy(() -> service.rename(GenerationTables.REVISION, 1, "changed")).isInstanceOf(QException.class);
assertThatThrownBy(() -> service.rename(null, 1, "changed")).isInstanceOf(QException.class);
int stockId = GenerationStore.get(GenerationTables.TEMPLATE, template).getValueInteger("stockId");
assertThatThrownBy(() -> GenerationStore.update(GenerationTables.STOCK,
new QRecord().withValue("id", stockId).withValue("name", "bad").withValue("widthMicrons", 1)))
.isInstanceOf(QException.class).hasMessageContaining("Immutable");
}
/***************************************************************************
** Configure permission and read-only credential ceilings apply at save time.
***************************************************************************/
@Test
void renameRechecksConfigureAndCredentialCeiling() throws Exception
{
var input = author(GenerationTables.TEMPLATE, "rename", template);
input.addValue("name", "Blocked");
QContext.getQSession().withCredentialScope(new com.kingsrook.qqq.backend.core.model.session.QCredentialScope().withReadOnly(true));
assertThatThrownBy(() -> saveAuthor(input)).isInstanceOf(com.kingsrook.qqq.backend.core.exceptions.QPermissionDeniedException.class);
QContext.getQSession().setCredentialScope(null);
QContext.getQSession().getPermissions().remove("document-generation.configure");
assertThatThrownBy(() -> saveAuthor(input)).isInstanceOf(com.kingsrook.qqq.backend.core.exceptions.QPermissionDeniedException.class);
}
/*******************************************************************************
** Users can create types/stock/bindings/templates and edit source entirely in QQQ.
*******************************************************************************/
@@ -0,0 +1,111 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.documentgeneration;
import java.util.List;
import java.util.TreeSet;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
/*******************************************************************************
** The registration points for the preview, asset and upgrade packages exist and
** are called, and add nothing: a host that registers the QBit gets exactly the
** tables, processes, choices, widgets and template sections it got before.
*******************************************************************************/
class GenerationRegistrationHooksTest
{
/***************************************************************************
** The complete default wiring, as the released foundation registers it.
***************************************************************************/
@Test
void theProducerRegistersExactlyWhatItDidBeforeTheHooks() throws Exception
{
QInstance instance = instance();
new DocumentGenerationQBitProducer().withQBitConfig(config()).produce(instance);
assertEquals(List.of("document_generation_asset", "document_generation_batch", "document_generation_binding", "document_generation_control", "document_generation_event",
"document_generation_item", "document_generation_queue", "document_generation_request", "document_generation_revision", "document_generation_stock",
"document_generation_template", "document_generation_type", "orders"),
List.copyOf(new TreeSet<>(instance.getTables().keySet())), "tables");
assertEquals(List.of("document_generation_binding_author_create", "document_generation_queue_document_advance", "document_generation_queue_document_cancel",
"document_generation_queue_document_reconcileItem", "document_generation_queue_document_recover", "document_generation_queue_document_status",
"document_generation_request_document_download", "document_generation_request_document_reconcile", "document_generation_request_document_reprint",
"document_generation_request_document_status", "document_generation_stock_author_create", "document_generation_stock_author_rename",
"document_generation_template_author_asset", "document_generation_template_author_catalog", "document_generation_template_author_context",
"document_generation_template_author_create", "document_generation_template_author_edit", "document_generation_template_author_naming",
"document_generation_template_author_packingSlip", "document_generation_template_author_preview", "document_generation_template_author_publish",
"document_generation_template_author_removeAsset", "document_generation_template_author_rename", "document_generation_template_author_replaceAsset",
"document_generation_type_author_create", "document_generation_type_author_rename", "orders_document_enqueue", "orders_document_generate"),
List.copyOf(new TreeSet<>(instance.getProcesses().keySet())), "processes");
assertEquals(List.of("document_generation_asset", "document_generation_context_publications", "document_generation_formats", "document_generation_preview_source",
"document_generation_preview_source_orders", "document_generation_retained_assets", "document_generation_revision",
"document_generation_source_tables", "document_generation_stock", "document_generation_template", "document_generation_type"),
List.copyOf(new TreeSet<>(instance.getPossibleValueSources().keySet())), "possible value sources");
assertEquals(List.of(),
List.copyOf(new TreeSet<>(instance.getWidgets().keySet())), "widgets");
assertEquals(List.of("overview", "layout", "context", "advanced"),
instance.getTable(GenerationTables.TEMPLATE).getSections().stream().map(QFieldSection::getName).toList(), "template sections");
}
/***************************************************************************
** Each registration point, called directly, changes nothing.
***************************************************************************/
@Test
void eachRegistrationPointAddsNothingYet() throws Exception
{
DocumentGenerationQBitConfig config = config();
assertEquals(List.of(), GenerationAssetTables.tables(config));
QInstance instance = instance();
GenerationPreviewProcesses.register(instance, config);
GenerationAssetProcesses.register(instance, config);
GenerationUpgradeProcesses.register(instance, config);
assertEquals(List.of(), List.copyOf(instance.getProcesses().keySet()));
assertEquals(List.of("orders"), List.copyOf(instance.getTables().keySet()));
QTableMetaData table = new QTableMetaData().withName("template");
GenerationPreviewWidget.section(table);
assertEquals(new QTableMetaData().withName("template").getSections(), table.getSections());
}
/***************************************************************************
** The smallest host: one backend and one source table.
***************************************************************************/
private static QInstance instance()
{
QInstance instance = new QInstance();
instance.addBackend(new QBackendMetaData().withName("memory").withBackendType(MemoryBackendModule.class));
instance.addTable(new QTableMetaData().withName("orders").withBackendName("memory").withPrimaryKeyField("id").withField(new QFieldMetaData("id", QFieldType.INTEGER)));
return (instance);
}
/***************************************************************************
** Default limits, with one source table so the source choices are registered.
***************************************************************************/
private static DocumentGenerationQBitConfig config()
{
return (new DocumentGenerationQBitConfig().withBackendName("memory").withSourceTables(List.of("orders")));
}
}
@@ -52,13 +52,24 @@ final class PostgresGenerationDatabase implements AutoCloseable
** Starts only this suite's loopback container with durable settings.
***************************************************************************/
PostgresGenerationDatabase()
{
this(true);
}
/***************************************************************************
** Pinned durability settings come from the server command line, which no
** ALTER SYSTEM can override. An unpinned engine starts with the same values
** (the PostgreSQL defaults) and lets a test weaken them through system().
***************************************************************************/
PostgresGenerationDatabase(boolean pinned)
{
// pgjdbc announces the JVM zone as the session TimeZone; qualification requires UTC.
previousZone = java.util.TimeZone.getDefault();
java.util.TimeZone.setDefault(java.util.TimeZone.getTimeZone("UTC"));
container = new GenericContainer<>(IMAGE)
.withEnv("POSTGRES_PASSWORD", rootPassword).withEnv("POSTGRES_DB", "bootstrap")
.withCommand("postgres", "-c", "fsync=on", "-c", "synchronous_commit=on", "-c", "full_page_writes=on", "-c", "max_connections=60")
.withCommand(pinned ? new String[] { "postgres", "-c", "fsync=on", "-c", "synchronous_commit=on", "-c", "full_page_writes=on", "-c", "max_connections=60" }
: new String[] { "postgres", "-c", "max_connections=60" })
.withExposedPorts(5432)
.withCreateContainerCmdModifier(command -> command.getHostConfig().withMemory(768L * 1024 * 1024).withNanoCPUs(2_000_000_000L)
.withPortBindings(FixtureEndpoint.publishedBinding(5432)))
@@ -143,6 +154,38 @@ final class PostgresGenerationDatabase implements AutoCloseable
}
}
/***************************************************************************
** Change one reloadable server setting of an unpinned engine (ALTER SYSTEM and
** a configuration reload), then wait until a new session actually reports it.
** Fixed fixture names and values only, never application input.
***************************************************************************/
void system(String name, String value) throws Exception
{
ConnectionManager.closeConnectionProviders();
try(var connection = DriverManager.getConnection(server + "bootstrap" + OPTIONS, "postgres", rootPassword); var statement = connection.createStatement())
{
statement.execute("ALTER SYSTEM SET " + name + " = '" + value + "'");
statement.execute("SELECT pg_reload_conf()");
}
long deadline = System.nanoTime() + java.time.Duration.ofSeconds(30).toNanos();
while(true)
{
try(var connection = DriverManager.getConnection(server + "bootstrap" + OPTIONS, "postgres", rootPassword); var statement = connection.createStatement();
var row = statement.executeQuery("SELECT current_setting('" + name + "')"))
{
if(row.next() && value.equals(row.getString(1)))
{
return;
}
}
if(System.nanoTime() >= deadline)
{
throw new IllegalStateException("PostgreSQL fixture did not apply " + name);
}
Thread.sleep(100);
}
}
/***************************************************************************
** Cold server restart retains only this container's data layer (committed
** data must survive; Docker may publish a different loopback port).
@@ -41,6 +41,9 @@ class PostgresGenerationQueueTest extends GenerationQueueJdbcTest
{
private static PostgresGenerationDatabase postgres;
// The engine the fixture calls of the running test address: the shared pinned one unless that test started its own.
private PostgresGenerationDatabase current;
/***********************************************************************
** One exact disposable engine per suite; all test cases use new databases.
***********************************************************************/
@@ -68,7 +71,7 @@ class PostgresGenerationQueueTest extends GenerationQueueJdbcTest
@Override
protected RDBMSBackendMetaData database(String name, String defaultUrl) throws Exception
{
return (postgres.database(name));
return (engine().database(name));
}
/***********************************************************************
@@ -77,7 +80,7 @@ class PostgresGenerationQueueTest extends GenerationQueueJdbcTest
@Override
protected Connection connection() throws Exception
{
return (postgres.connection());
return (engine().connection());
}
/***********************************************************************
@@ -86,7 +89,15 @@ class PostgresGenerationQueueTest extends GenerationQueueJdbcTest
@Override
protected void sql(String source) throws Exception
{
postgres.sql(source);
engine().sql(source);
}
/***********************************************************************
** The shared pinned engine, or the unpinned one a single test started.
***********************************************************************/
private PostgresGenerationDatabase engine()
{
return (current == null ? postgres : current);
}
/***********************************************************************
@@ -178,6 +189,86 @@ class PostgresGenerationQueueTest extends GenerationQueueJdbcTest
assertThat(queue.status(enqueue("restored"), -1, 10).items()).hasSize(2);
}
/***************************************************************************
** Amazon Aurora PostgreSQL always runs with full_page_writes off (its storage
** layer does not write torn pages), so that one setting is accepted there and
** nowhere else. The engine is a real PostgreSQL 17 whose settings are really
** changed. Only Aurora itself is simulated: a function named aurora_version()
** is created in the test database, so the same to_regprocedure detection that
** is true on Aurora resolves here.
***************************************************************************/
@Test
void fullPageWritesOffIsAcceptedOnlyOnAuroraAndNothingElseIsRelaxed() throws Exception
{
current = new PostgresGenerationDatabase(false);
try
{
// Rebuild the instance, schema and definitions on the unpinned engine (durable defaults, so this qualifies).
cleanup();
setup();
assertThat(queue.advance(enqueue("stock-durable")).state()).isEqualTo("GENERATED");
// Stock PostgreSQL with full_page_writes off is refused, and the refusal states the rule.
current.system("full_page_writes", "off");
assertThatThrownBy(() -> enqueue("stock-torn-pages")).isInstanceOf(QException.class)
.hasMessageContaining("full-page writes (except on Amazon Aurora PostgreSQL)").hasMessageContaining("not satisfied: synchronous commit");
// The same server, now resolving aurora_version(), is accepted and work really commits.
sql("CREATE FUNCTION aurora_version() RETURNS text LANGUAGE sql IMMUTABLE AS $$ SELECT '17.9'::text $$");
ConnectionManager.closeConnectionProviders();
assertThat(queue.advance(enqueue("aurora")).state()).isEqualTo("GENERATED");
// Aurora relaxes nothing else: fsync off is refused.
current.system("fsync", "off");
assertThatThrownBy(() -> enqueue("aurora-no-fsync")).isInstanceOf(QException.class).hasMessageContaining("not satisfied: synchronous commit");
current.system("fsync", "on");
// Aurora relaxes nothing else: asynchronous commit is refused.
sql("ALTER ROLE generation_app SET synchronous_commit='off'");
ConnectionManager.closeConnectionProviders();
assertThatThrownBy(() -> enqueue("aurora-async-commit")).isInstanceOf(QException.class).hasMessageContaining("not satisfied: synchronous commit");
sql("ALTER ROLE generation_app SET synchronous_commit='on'");
ConnectionManager.closeConnectionProviders();
assertThat(enqueue("aurora-restored")).isNotNull();
// Without the function the server is stock PostgreSQL again and is refused again.
sql("DROP FUNCTION aurora_version()");
ConnectionManager.closeConnectionProviders();
assertThatThrownBy(() -> enqueue("stock-again")).isInstanceOf(QException.class).hasMessageContaining("not satisfied: synchronous commit");
}
finally
{
ConnectionManager.closeConnectionProviders();
current.close();
current = null;
}
}
/***************************************************************************
** The durability decision itself, for the combinations the live case does
** not reach: no server and nothing simulated, only the values of one row.
***************************************************************************/
@Test
void durabilityRuleAcceptsOnlyTheAuroraFullPageWritesException()
{
assertThat(GenerationPostgres.durable("on", "on", "on", false)).isTrue();
assertThat(GenerationPostgres.durable("remote_apply", "on", "on", false)).isTrue();
assertThat(GenerationPostgres.durable("on", "on", "on", true)).isTrue();
assertThat(GenerationPostgres.durable("on", "on", "off", true)).isTrue();
assertThat(GenerationPostgres.durable("remote_apply", "on", "off", true)).isTrue();
assertThat(GenerationPostgres.durable("on", "on", "off", false)).isFalse();
assertThat(GenerationPostgres.durable("on", "off", "on", false)).isFalse();
assertThat(GenerationPostgres.durable("on", "off", "off", true)).isFalse();
for(String weak : List.of("off", "local", "remote_write"))
{
assertThat(GenerationPostgres.durable(weak, "on", "on", false)).isFalse();
assertThat(GenerationPostgres.durable(weak, "on", "off", true)).isFalse();
}
assertThat(GenerationPostgres.durable(null, "on", "on", true)).isFalse();
assertThat(GenerationPostgres.durable("on", null, "on", true)).isFalse();
assertThat(GenerationPostgres.durable("on", "on", null, true)).isFalse();
}
/***************************************************************************
** The native identity must be the postgres module on a real PostgreSQL17 server, not a vendor label.
***************************************************************************/
+2 -2
View File
@@ -5,7 +5,7 @@
no -Drevision, so a module that declared its own revision would stay at the SNAPSHOT. The root takes qbit-build-parent 2.1.1 (the version foundation already used);
modules that had no parent now inherit its defaults and this Apache-2.0 license (the vendored OpenHTML module keeps its explicit LGPL-2.1+; bridges/scripting keeps
its previously inherited AGPL-3.0 explicitly). Each module still keeps its own dependency boundary and builds on its own through its relative parent. -->
<parent><groupId>com.kingsrook</groupId><artifactId>qbit-build-parent</artifactId><version>2.1.1</version><relativePath/></parent>
<parent><groupId>com.kingsrook</groupId><artifactId>qbit-build-parent</artifactId><version>2.2.1</version><relativePath/></parent>
<groupId>com.kingsrook.qbits</groupId>
<artifactId>qbit-document-generation-reactor</artifactId>
<version>${revision}</version>
@@ -14,7 +14,7 @@
<url>https://github.com/Kingsrook/qbit-bom</url>
<scm><connection>scm:git:https://github.com/Kingsrook/qbit-bom.git</connection><developerConnection>scm:git:ssh://git@github.com/Kingsrook/qbit-bom.git</developerConnection><url>https://github.com/Kingsrook/qbit-bom</url><tag>HEAD</tag></scm>
<licenses><license><name>Apache License, Version 2.0</name><url>https://www.apache.org/licenses/LICENSE-2.0</url><distribution>repo</distribution></license></licenses>
<properties><revision>0.1.0-SNAPSHOT</revision><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><enforcer.skip>true</enforcer.skip></properties>
<properties><revision>1.4.0-SNAPSHOT</revision><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><enforcer.skip>true</enforcer.skip><qqq.version>4.1.0-RC.26</qqq.version></properties>
<!-- Every module of the repository, so one release builds and publishes every GAV. Maven orders the reactor by dependency (the patched OpenHTML
module before the renderer; foundation before the bridges). Build a subset with -pl <module> -am. -->
<modules>
+16 -15
View File
@@ -10,18 +10,18 @@ COPY Dockerfile ./
RUN apt-get update && apt-get install -y --no-install-recommends gcc g++ libc6-dev make cmake patch \
&& sh ./build-zint.sh
# The renderer's version comes from its parent, the repository reactor POM (../pom.xml). Build from this directory with that file as a named BuildKit context:
# docker build --build-context reactor=<repository root> ... (the reactor POM is copied to /pom.xml, where ../pom.xml resolves for /renderer/pom.xml)
FROM ${BUILD_IMAGE} AS build
COPY --from=reactor pom.xml /pom.xml
WORKDIR /renderer
COPY vendor/openhtml-1.1.105-qqq-p1 ./vendor/openhtml-1.1.105-qqq-p1
RUN sh ./vendor/openhtml-1.1.105-qqq-p1/build-openhtml.sh /renderer/openhtml-build /renderer/.m2
COPY pom.xml ./
COPY LICENSE NOTICE ./
COPY checkstyle ./checkstyle
COPY src ./src
RUN mvn -B -Dmaven.repo.local=/renderer/.m2 verify
# The engine and its libraries are the RELEASED artifacts, resolved at the exact RENDERER_VERSION from the QRun-IO
# Gitea Maven registry and Maven Central (Maven checks each registry checksum). No SNAPSHOT, no local build and no
# local-cache coordinate: the version is a required build argument with no default.
# docker build --build-arg BUILD_IMAGE=... --build-arg JAVA_IMAGE=... --build-arg RENDERER_VERSION=1.0.0 renderer/
FROM ${BUILD_IMAGE} AS release
ARG RENDERER_VERSION
WORKDIR /release
COPY release-image/pom.xml ./pom.xml
RUN test -n "${RENDERER_VERSION}" && case "${RENDERER_VERSION}" in *SNAPSHOT*|*forge*) echo "RENDERER_VERSION must be a released version" >&2; exit 1;; esac \
&& mvn -B -Drenderer.version="${RENDERER_VERSION}" -Dmaven.repo.local=/release/.m2 package \
&& mv "target/lib/qbit-document-renderer-${RENDERER_VERSION}.jar" target/renderer.jar \
&& test -f target/renderer.jar && ls target/lib
FROM ${JAVA_IMAGE}
# Reviewed runtime family: Temurin 21.0.13+9 on Ubuntu noble. The approved digest
@@ -37,8 +37,9 @@ RUN . /etc/os-release \
WORKDIR /renderer
COPY --from=zint-build /zint-build/build/frontend/zint /usr/local/bin/zint
COPY --from=zint-build /zint-build/corresponding-source /usr/share/doc/qqq-zint
COPY --from=build /renderer/openhtml-build/corresponding-source /usr/share/doc/qqq-openhtml
COPY --from=build /renderer/target/qbit-document-renderer-0.1.0-SNAPSHOT.jar ./renderer.jar
COPY --from=build /renderer/target/lib ./lib
# LGPL corresponding source for the vendored OpenHTML module that the released openhtmltopdf-pdfbox jar was built from.
COPY vendor/openhtml-1.1.105-qqq-p1 /usr/share/doc/qqq-openhtml
COPY --from=release /release/target/renderer.jar ./renderer.jar
COPY --from=release /release/target/lib ./lib
USER 65532:65532
ENTRYPOINT ["java", "-Xmx256m", "-XX:ActiveProcessorCount=1", "-Djava.awt.headless=true", "-Dpdfbox.fontcache=/tmp", "-jar", "/renderer/renderer.jar"]
+120
View File
@@ -0,0 +1,120 @@
# Renderer container images: build from a released version and publish
Two images, built in this order. Both are built from **released** Maven artifacts (QRun-IO Gitea registry
`https://git.kof22.com/api/packages/QRun-IO/maven` and Maven Central); there is no SNAPSHOT, local-cache or reactor
input. The version is a required build argument with no default, and the Dockerfiles refuse a `SNAPSHOT` or `forge`
version.
| Image | Dockerfile (context) | Version argument | Contents |
|---|---|---|---|
| `ghcr.io/gg-engineering/qqq-document-renderer` | `renderer/Dockerfile` (`renderer/`) | `RENDERER_VERSION` (`qbit-document-renderer`) | engine, its libraries, patched Zint built from `renderer/vendor`, Temurin runtime |
| `ghcr.io/gg-engineering/qqq-remote-renderer` | `bridges/remote-renderer/Dockerfile` (`bridges/remote-renderer/`) | `TRANSPORT_VERSION` (`qqq-remote-renderer-transport`), `RENDERER_IMAGE` | the mTLS service, on top of the engine image |
The service image is the one Forge deploys. The adapter (`qbit-document-generation-remote-renderer`) is a client-side
library and is not in either image.
## Version choice
`1.0.0` is the released, non-RC version and the one `gg-forge` pins for the document-generation family. `1.1.0-RC.1`
contains the same `renderer/` and `bridges/remote-renderer/` sources as `1.0.0` (only POM/CI alignment differs), so it
adds nothing for the renderer and is a release candidate. Build the version the host library pins.
## Reviewed base images
`BUILD_IMAGE` and `JAVA_IMAGE` must be immutable digests. `JAVA_IMAGE` is the reviewed runtime in `DEPLOYMENT.md`.
`BUILD_IMAGE` only resolves artifacts; no layer of it reaches the final image.
```sh
BUILD_IMAGE=maven@sha256:99e61abcff91a9b1333463bd8451fb18495d6eba9250ac66a338b518f8278320 # maven:3.9-eclipse-temurin-21
JAVA_IMAGE=eclipse-temurin@sha256:db724162e4ff5f71fa0a2815026314d9e5e0e9a139f49be1d4717e6930e93341 # 21.0.13_9-jre-noble (amd64)
VERSION=1.0.0
```
## Build, smoke, push
Disable BuildKit provenance and SBOM attestations so each tag is a single OCI image manifest (the digest the chart pins).
```sh
docker build --provenance=false --sbom=false --build-arg BUILD_IMAGE=$BUILD_IMAGE --build-arg JAVA_IMAGE=$JAVA_IMAGE --build-arg RENDERER_VERSION=$VERSION -t qqq-document-renderer:$VERSION renderer
docker build --provenance=false --sbom=false --build-arg BUILD_IMAGE=$BUILD_IMAGE --build-arg RENDERER_IMAGE=qqq-document-renderer:$VERSION --build-arg TRANSPORT_VERSION=$VERSION -t qqq-remote-renderer:$VERSION bridges/remote-renderer
# Optional scan of the final image
docker save qqq-remote-renderer:$VERSION -o svc.tar
docker run --rm -v "$PWD/svc.tar:/svc.tar:ro" aquasec/trivy:latest image --input /svc.tar --severity HIGH,CRITICAL --scanners vuln
# Publish (docker must be logged in to ghcr.io with a token that has write:packages)
for n in qqq-document-renderer qqq-remote-renderer; do
docker tag $n:$VERSION ghcr.io/gg-engineering/$n:$VERSION
docker push ghcr.io/gg-engineering/$n:$VERSION # prints the manifest digest
done
```
New GHCR packages are private. Package access is per package and is managed in the GitHub UI (Package settings,
Manage access); the REST API cannot grant it. The credential behind `ghcr-pull-secret` must be able to read both packages.
## Pins for gg-forge-cd
`sh renderer/image-pins.sh ghcr.io/gg-engineering/qqq-remote-renderer@sha256:<manifest digest>` pulls the published
image and prints the `echoRenderer.*` image, classpath and file-hash pins from the image itself. `imageId` is
`docker image inspect --format '{{.Id}}'`; with the containerd image store this equals the manifest digest, otherwise
it is the config digest. Whatever value is chosen must be the same on Forge and the service.
Values for `1.0.0` as published (the java, zint and patchset hashes match the 2026-11-05 local qualification; the
engine JAR and the OpenHTML JAR differ because they are the released artifacts):
```yaml
echoRenderer:
image:
repository: ghcr.io/gg-engineering/qqq-remote-renderer
digest: sha256:4152a4253e543068bedfd438775ed5327807bdddf679bc5a8e6eab26855e09ea
imageId: sha256:4152a4253e543068bedfd438775ed5327807bdddf679bc5a8e6eab26855e09ea
classpath:
- /renderer/renderer.jar
- /renderer/lib/commons-logging-1.4.0.jar
- /renderer/lib/fontbox-3.0.8.jar
- /renderer/lib/freemarker-2.3.35.jar
- /renderer/lib/graphics2d-3.0.1.jar
- /renderer/lib/jackson-annotations-2.22.jar
- /renderer/lib/jackson-core-2.22.4.jar
- /renderer/lib/jackson-databind-2.22.4.jar
- /renderer/lib/jsoup-1.23.2.jar
- /renderer/lib/okapibarcode-0.5.7.jar
- /renderer/lib/openhtmltopdf-core-1.1.105.jar
- /renderer/lib/openhtmltopdf-pdfbox-1.0.0.jar
- /renderer/lib/pdfbox-3.0.8.jar
- /renderer/lib/pdfbox-io-3.0.8.jar
- /renderer/lib/slf4j-api-2.0.17.jar
- /renderer/lib/slf4j-nop-2.0.17.jar
- /renderer/lib/xmpbox-3.0.8.jar
fileHashes:
/opt/java/openjdk/bin/java: 7afc28801f744050aeba646b20e7314265279f76f9d0273b21a2b66411e1bf6d
/renderer/renderer.jar: 0061e1bce631e468b0d7d08735bb764ba2b6d85260c79c144beabad7ffbc19db
/usr/local/bin/zint: ea8dc40c917abd7e8ccc824ef0adafed245a2425abbdfac6a30faeb8e4642233
/usr/share/doc/qqq-zint/patchset-id.txt: a287946580d5eeb8691ac1d98d6a7f50bf22dcc5dc0ef039d7033e76e15c52d2
/renderer/lib/commons-logging-1.4.0.jar: d175dbd751dd782a63bde28c7a039520e971f25e84b79c19b8435edc3603e0dc
/renderer/lib/fontbox-3.0.8.jar: a1915c24e3edbe0ecec93896dfbf6d41427810b663ade97bd4e8bae86ec3fdab
/renderer/lib/freemarker-2.3.35.jar: 0fac87dddd78f1223139e8ef88e819c7f483c0a3835cdf5982ad5e4576d1d896
/renderer/lib/graphics2d-3.0.1.jar: 4849aaa6f536d75dea1b835a71086702216787036f42716adfad438c6e7928c0
/renderer/lib/jackson-annotations-2.22.jar: 21ddb598807d3a51a876704eb979d9296e1c6a6f47ab1826ff88c6d6a127a2d0
/renderer/lib/jackson-core-2.22.4.jar: 2d30c375cddea9657a1e1ef97547a2694b663b355facb4de53a097782b312d1a
/renderer/lib/jackson-databind-2.22.4.jar: d4cbf8947087f305869826801913abdc50735a1fab2074a1993e7bc67ca974a3
/renderer/lib/jsoup-1.23.2.jar: e9d8c856856680427f096d156f02a414289985c930998fcb519ce5be39b42003
/renderer/lib/okapibarcode-0.5.7.jar: f33b7d17b7040aa1d9c23b1f3bb274e492b786c340b02d9557bcc027ac874355
/renderer/lib/openhtmltopdf-core-1.1.105.jar: d5951a8407c3be41108698066e6d67cf952e526be39d4e49b0b9fbcee761de7d
/renderer/lib/openhtmltopdf-pdfbox-1.0.0.jar: ec2865bfa45513e63adaa1a3c6387eb6bbd481e6b23f4d68a33cfedfc753cfe8
/renderer/lib/pdfbox-3.0.8.jar: 97647cfbde61ebcfc06b4cf8c9b0ffcaaee073396eceb4a7f6836a9b9128903c
/renderer/lib/pdfbox-io-3.0.8.jar: 36a0e04001010b4c764857817412b96339930b19755e728959805cc0352061b2
/renderer/lib/slf4j-api-2.0.17.jar: 7b751d952061954d5abfed7181c1f645d336091b679891591d63329c622eb832
/renderer/lib/slf4j-nop-2.0.17.jar: 3716f83649ec66161a2edefd4f49df34d1dd1c51cdcf941996c6987260f0a829
/renderer/lib/xmpbox-3.0.8.jar: f044d2e316e57714a0adf272ad8abf4a863b8978fd17dc5b86dcfa8135469b10
```
Not covered by these pins: `identity.*`, `certificateNamespaces`, TLS identities, and chart switches.
## What was and was not verified (1.0.0 build)
* Verified: `renderer` (174) and transport (19) tests and Checkstyle pass on this source (same sources as the `v1.0.0` tag);
both images build; the service image runs read-only, non-root, all capabilities dropped, and served CATALOG, PDF
(Code128, GS1-128, Zint DotCode) and ZPL renders over a real mutual-TLS handshake with ephemeral certificates.
* Not verified: no cluster run, no handshake against Forge, no gVisor or hostile-input qualification (the service is
`TRUSTED_LAB` only), no byte-for-byte rebuild of the OpenHTML jar from `vendor/`.
+25
View File
@@ -0,0 +1,25 @@
#!/bin/sh
# Print the gg-forge-cd echoRenderer.* pins (non-secret) for a PUBLISHED service image, read from the image itself:
# sh renderer/image-pins.sh ghcr.io/gg-engineering/qqq-remote-renderer@sha256:<manifest digest>
# The classpath is /renderer/renderer.jar followed by /renderer/lib/*.jar in byte order; hashes are SHA256 of the files in the image.
set -eu
image=${1:?image reference by digest required}
case "$image" in *@sha256:*) ;; *) echo 'Reference the image by @sha256: digest' >&2; exit 2;; esac
repository=${image%@*}
digest=${image#*@}
docker pull "$image" >/dev/null
image_id=$(docker image inspect "$image" --format '{{.Id}}')
echo "echoRenderer:"
echo " image:"
echo " repository: $repository"
echo " digest: $digest"
echo " imageId: $image_id"
docker run --rm --pull never --network none --user 65532:65532 --entrypoint sh "$image" -c '
set -eu
echo " classpath:"
echo " - /renderer/renderer.jar"
for f in $(LC_ALL=C ls /renderer/lib/*.jar); do echo " - $f"; done
echo " fileHashes:"
for f in /opt/java/openjdk/bin/java /renderer/renderer.jar /usr/local/bin/zint /usr/share/doc/qqq-zint/patchset-id.txt $(LC_ALL=C ls /renderer/lib/*.jar); do
printf " %s: %s\n" "$f" "$(sha256sum "$f" | cut -d" " -f1)"
done'
+21 -1
View File
@@ -32,10 +32,30 @@
</execution></executions>
</plugin>
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.14.1</version><configuration><showWarnings>true</showWarnings><failOnWarning>true</failOnWarning><compilerArgs><arg>-XDcompilePolicy=simple</arg><arg>--should-stop=ifError=FLOW</arg><arg>-XDaddTypeAnnotationsToSymbol=true</arg><arg>-Xplugin:ErrorProne -Xep:UnnecessaryParentheses:OFF -Xep:NotJavadoc:OFF</arg><arg>-Xlint:all</arg><arg>-Werror</arg></compilerArgs><annotationProcessorPaths><path><groupId>com.google.errorprone</groupId><artifactId>error_prone_core</artifactId><version>2.49.0</version></path></annotationProcessorPaths></configuration></plugin>
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>3.5.4</version><configuration><dependenciesToScan combine.self="override"/><systemPropertyVariables><java.awt.headless>true</java.awt.headless><pdfbox.fontcache>${project.build.directory}</pdfbox.fontcache></systemPropertyVariables></configuration></plugin>
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>3.5.4</version><configuration><dependenciesToScan combine.self="override"/><systemPropertyVariables><kof22.provider.version>${revision}</kof22.provider.version><java.awt.headless>true</java.awt.headless><pdfbox.fontcache>${project.build.directory}</pdfbox.fontcache></systemPropertyVariables></configuration></plugin>
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-dependency-plugin</artifactId><version>3.9.0</version><executions><execution><id>runtime-libraries</id><phase>package</phase><goals><goal>copy-dependencies</goal></goals><configuration><includeScope>runtime</includeScope><outputDirectory>${project.build.directory}/lib</outputDirectory></configuration></execution></executions></plugin>
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><version>3.4.2</version><configuration><archive><manifest><mainClass>com.kingsrook.qbits.renderer.RendererMain</mainClass><addClasspath>true</addClasspath><classpathPrefix>lib/</classpathPrefix></manifest></archive></configuration></plugin>
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-checkstyle-plugin</artifactId><version>3.3.1</version><dependencies><dependency><groupId>com.puppycrawl.tools</groupId><artifactId>checkstyle</artifactId><version>10.12.6</version></dependency></dependencies><configuration><configLocation>${project.basedir}/checkstyle/config.xml</configLocation><propertyExpansion>checkstyle.reactor.root=${project.basedir}</propertyExpansion><includeTestSourceDirectory>true</includeTestSourceDirectory><consoleOutput>true</consoleOutput><violationSeverity>warning</violationSeverity><failOnViolation>true</failOnViolation></configuration><executions><execution><phase>validate</phase><goals><goal>check</goal></goals></execution></executions></plugin>
<plugin>
<!-- Coverage gates. The BUNDLE rule is the measured baseline of the existing renderer, rounded down (2026-11-11: 94.0% of instructions and 91.3% of lines); raise it when coverage rises and
never lower it. The PACKAGE and CLASS rules hold new code to 100% line coverage from its first commit; a pattern matches nothing until its package or class exists, so the package that adds
it needs no change here. Nothing is excluded: JaCoCo skips only code annotated as generated, and this build defines no such annotation. There is no skip switch. -->
<groupId>org.jacoco</groupId><artifactId>jacoco-maven-plugin</artifactId><version>0.8.15</version><executions>
<execution><goals><goal>prepare-agent</goal></goals></execution>
<execution><id>report</id><phase>verify</phase><goals><goal>report</goal></goals></execution>
<execution><id>coverage</id><phase>verify</phase><goals><goal>check</goal></goals><configuration><rules>
<rule><element>BUNDLE</element><limits>
<limit><counter>INSTRUCTION</counter><value>COVEREDRATIO</value><minimum>0.94</minimum></limit>
<limit><counter>LINE</counter><value>COVEREDRATIO</value><minimum>0.91</minimum></limit>
</limits></rule>
<rule><element>PACKAGE</element><includes><include>com.kingsrook.qbits.renderer.scene2</include><include>com.kingsrook.qbits.renderer.scene2.*</include></includes><limits>
<limit><counter>LINE</counter><value>COVEREDRATIO</value><minimum>1.00</minimum></limit>
</limits></rule>
<rule><element>CLASS</element><includes><include>com.kingsrook.qbits.renderer.AssetInspector</include><include>com.kingsrook.qbits.renderer.AssetInspector.*</include></includes><limits>
<limit><counter>LINE</counter><value>COVEREDRATIO</value><minimum>1.00</minimum></limit>
</limits></rule>
</rules></configuration></execution>
</executions></plugin>
<plugin>
<!-- Publish a self-contained POM: ${revision} is resolved at build time, so a consumer needs no -Drevision -->
<groupId>org.codehaus.mojo</groupId><artifactId>flatten-maven-plugin</artifactId><version>1.7.3</version>
+21
View File
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Image-assembly project, not a published module. It resolves the RELEASED renderer engine and its runtime
libraries from the QRun-IO Gitea Maven registry (and Maven Central) at one exact version, so the renderer image
carries release bytes and not a local build. renderer.version has no default: pass -Drenderer.version=<released>. -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kingsrook.qbits</groupId><artifactId>qbit-document-renderer-image</artifactId><version>0</version><packaging>pom</packaging>
<repositories>
<repository><id>central</id><url>https://repo.maven.apache.org/maven2</url><snapshots><enabled>false</enabled></snapshots></repository>
<repository><id>qrun-io</id><url>https://git.kof22.com/api/packages/QRun-IO/maven</url><snapshots><enabled>false</enabled></snapshots><releases><checksumPolicy>fail</checksumPolicy></releases></repository>
</repositories>
<dependencies>
<dependency><groupId>com.kingsrook.qbits</groupId><artifactId>qbit-document-renderer</artifactId><version>${renderer.version}</version></dependency>
</dependencies>
<build><plugins>
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-dependency-plugin</artifactId><version>3.9.0</version>
<executions><execution><id>release-libraries</id><phase>package</phase><goals><goal>copy-dependencies</goal></goals>
<configuration><includeScope>runtime</includeScope><outputDirectory>${project.build.directory}/lib</outputDirectory></configuration>
</execution></executions></plugin>
</plugins></build>
</project>
@@ -51,6 +51,15 @@ public final class BarcodeCatalog
{
private static final Map<String, Supplier<Symbol>> FACTORIES = factories();
/*******************************************************************************
** Encoders whose geometry is fixed by their specification (matrix symbols and
** postal four-state codes): barHeight changes nothing for them, so it is
** neither advertised nor accepted. BarcodeDeclaredCapabilityTest proves this
** set is exactly the encoders on which barHeight has no effect.
*******************************************************************************/
private static final java.util.Set<String> FIXED_GEOMETRY = java.util.Set.of("australia-post", "aztec", "aztec-rune", "code-one", "code16k",
"code49", "datamatrix", "grid-matrix", "japan-post", "kix", "maxicode", "micro-qr", "qr", "royal-mail", "swiss-qr", "upn-qr");
/*******************************************************************************
** Utility class.
@@ -142,7 +151,14 @@ public final class BarcodeCatalog
{
Symbol symbol = FACTORIES.get(id).get();
Map<String, Object> options = new LinkedHashMap<>();
options.put("barHeight", Map.of("type", "integer", "minimum", 1, "maximum", 1000));
if("gs1-databar".equals(id))
{
options.put("barHeight", Map.of("type", "integer", "minimum", 1, "maximum", 1000, "notWithMode", "STACKED"));
}
else if(!FIXED_GEOMETRY.contains(id))
{
options.put("barHeight", Map.of("type", "integer", "minimum", 1, "maximum", 1000));
}
if(symbol instanceof Coop2Of5)
{
options.put("checkDigit", Map.of("type", "string", "values", List.of("NONE", "ADD", "VALIDATE"), "default", "NONE"));
@@ -380,6 +396,10 @@ public final class BarcodeCatalog
}
Symbol symbol = factory.get();
structuredAppend(type, symbol, options);
if(options.has("barHeight") && (FIXED_GEOMETRY.contains(type) || ("gs1-databar".equals(type) && options.has("mode") && "STACKED".equals(options.get("mode").asText()))))
{
throw new IllegalArgumentException("UNSUPPORTED_BARCODE_OPTION");
}
for(Map.Entry<String, JsonNode> field : options.properties())
{
if(List.of("qr", "datamatrix", "aztec").contains(type) && field.getKey().startsWith("structuredAppend"))
@@ -96,6 +96,36 @@ public final class PdfRenderer
public static void validate(RenderInput input) throws Exception
{
template(input.source);
for(Element element : Jsoup.parse(input.source).select("qqq-barcode"))
{
refuseUnsupportedOptions(element.attr("type"), element.attr("options"));
}
}
/*******************************************************************************
** Literal barcode options are checked against the catalog before any data
** exists, so an option the encoder cannot honour is refused at validation as
** its own closed code. Templated type or options are checked when rendered,
** and any other problem is left for the render to report.
*******************************************************************************/
private static void refuseUnsupportedOptions(String type, String options)
{
if(options.isBlank() || type.contains("${") || type.contains("<#") || options.contains("${") || options.contains("<#"))
{
return;
}
try
{
BarcodeCatalog.create(type, Wire.JSON.readTree(options));
}
catch(Exception exception)
{
if(exception instanceof IllegalArgumentException refusal && "UNSUPPORTED_BARCODE_OPTION".equals(refusal.getMessage()))
{
throw refusal;
}
}
}
@@ -103,7 +103,8 @@ public final class RendererMain
"BARCODE_FIT_TOO_SMALL", "BARCODE_FIT_UNSUPPORTED", "BARCODE_FIT_BOX_OUTSIDE_STOCK",
"BARCODE_FIT_GEOMETRY_MISMATCH", "INVALID_BARCODE_FIT_PROFILE", "INVALID_BARCODE_FIT_REQUEST",
"BARCODE_SYMBOLOGY_NOT_ALLOWED", "SYMBOLOGY_ALLOWLIST_REQUIRED", "SCANNER_BARCODE_PROFILE_UNSUPPORTED",
"SCANNER_BARCODE_PAYLOAD_UNSUPPORTED", "SCANNER_BARCODE_CHECKSUM_REQUIRED", "SCANNER_BARCODE_CHECKSUM_MISMATCH").contains(message))
"SCANNER_BARCODE_PAYLOAD_UNSUPPORTED", "SCANNER_BARCODE_CHECKSUM_REQUIRED", "SCANNER_BARCODE_CHECKSUM_MISMATCH",
"UNSUPPORTED_BARCODE_OPTION").contains(message))
{
return (message);
}
@@ -84,16 +84,32 @@ public final class BackgroundImageQualification
cases += 2;
}
return (Map.of("cases", cases, "surfaces", List.of("element", "pseudo", "page"), "exactRgbAlphaAndDimensions", true,
"bothAliasPaintOrders", true, "provider", "com.kingsrook.thirdparty:openhtmltopdf-pdfbox:1.1.105-qqq-p1",
"bothAliasPaintOrders", true, "provider", "com.kingsrook.thirdparty:openhtmltopdf-pdfbox:" + providerVersion(),
"qualification", "trusted-product-fixtures-only"));
}
/***************************************************************************
** The patched OpenHTML provider is published at the family version of this
** reactor, which surefire passes in; there is no second literal to keep in sync.
***************************************************************************/
private static String providerVersion()
{
String version = System.getProperty("kof22.provider.version");
if(version == null || version.isBlank())
{
throw new IllegalArgumentException("PDF_PROVIDER_VERSION_PROPERTY_MISSING");
}
return (version);
}
/***************************************************************************
** One provider class must come from the private patched GAV, with unchanged official companion versions.
***************************************************************************/
static void verifyProvider() throws Exception
{
verifyArtifact(com.openhtmltopdf.pdfboxout.PdfBoxFastOutputDevice.class, "com.kingsrook.thirdparty", "openhtmltopdf-pdfbox", "1.1.105-qqq-p1");
verifyArtifact(com.openhtmltopdf.pdfboxout.PdfBoxFastOutputDevice.class, "com.kingsrook.thirdparty", "openhtmltopdf-pdfbox", providerVersion());
verifyArtifact(com.openhtmltopdf.css.parser.CSSParser.class, "io.github.openhtmltopdf", "openhtmltopdf-core", "1.1.105");
verifyArtifact(org.apache.pdfbox.pdmodel.PDDocument.class, "org.apache.pdfbox", "pdfbox", "3.0.8");
}
@@ -0,0 +1,218 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.renderer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.SplittableRandom;
import java.util.function.Predicate;
/*******************************************************************************
** Seeded, bounded mutation campaign for a parser of untrusted input. A seed, an
** iteration number, the corpus and the dictionary fully determine every input:
** there is no clock and no unseeded randomness, so a finding replays exactly.
** Targets, limits and the replay command are in `docs/security/fuzzing.md`. The
** same class is kept in the test tree of `bridges/renderer`.
*******************************************************************************/
final class MutationCampaign
{
static final int MAXIMUM_ITERATIONS = 100_000;
static final int MAXIMUM_INPUT_BYTES = 1024 * 1024;
private static final byte[] INTERESTING = {0, 9, 10, 13, ' ', '"', '\\', '^', '~', '_', ',', '{', '}', '[', ']', '0', '9', '-', (byte) 0x7F, (byte) 0x80, (byte) 0xC0, (byte) 0xFF};
private final List<byte[]> corpus;
private final List<byte[]> dictionary;
/***************************************************************************
** The parser under test. Returning normally means the input was accepted; a
** target asserts its own property on what it accepts.
***************************************************************************/
@FunctionalInterface
interface Target
{
/************************************************************************
** Parse one generated input.
************************************************************************/
void accept(byte[] input) throws Exception;
}
/***************************************************************************
** An input that was neither accepted nor refused in the declared way, with
** the coordinates that regenerate it: `input(seed, iteration)`.
***************************************************************************/
record Finding(long seed, int iteration, String failure)
{
}
/***************************************************************************
** What a campaign saw. A campaign that accepted or refused nothing has not
** exercised both sides of the parser, so callers assert on the counts too.
***************************************************************************/
record Result(int accepted, int refused, List<Finding> findings)
{
}
/***************************************************************************
** Valid inputs to start from, and tokens of the format to splice in.
***************************************************************************/
MutationCampaign(List<byte[]> corpus, List<String> dictionary)
{
if(corpus.isEmpty() || corpus.stream().anyMatch(seedInput -> seedInput.length > MAXIMUM_INPUT_BYTES))
{
throw new IllegalArgumentException("A campaign needs a corpus of inputs within the input limit.");
}
this.corpus = corpus.stream().map(byte[]::clone).toList();
this.dictionary = dictionary.stream().map(token -> token.getBytes(StandardCharsets.UTF_8)).toList();
}
/***************************************************************************
** The seeds for this run: the fixed ones, or only `-Dfuzz.seed=<n>` when a
** finding is replayed or a longer campaign is run by hand.
***************************************************************************/
static long[] seeds(long... fixed)
{
Long replay = Long.getLong("fuzz.seed");
return (replay == null ? fixed : new long[] {replay});
}
/***************************************************************************
** The iteration count for this run: `-Dfuzz.iterations=<n>`, never unbounded.
***************************************************************************/
static int iterations(int fallback)
{
int iterations = Integer.getInteger("fuzz.iterations", fallback);
if(iterations < 1 || iterations > MAXIMUM_ITERATIONS)
{
throw new IllegalArgumentException("fuzz.iterations must be between 1 and " + MAXIMUM_ITERATIONS);
}
return (iterations);
}
/***************************************************************************
** The input of one iteration: a corpus entry with one to four mutations.
***************************************************************************/
byte[] input(long seed, int iteration)
{
SplittableRandom random = new SplittableRandom(seed + 0x9E3779B97F4A7C15L * (iteration + 1L));
byte[] value = corpus.get(random.nextInt(corpus.size())).clone();
int steps = 1 + random.nextInt(4);
for(int step = 0; step < steps; step++)
{
value = mutate(value, random);
}
return (value.length > MAXIMUM_INPUT_BYTES ? Arrays.copyOf(value, MAXIMUM_INPUT_BYTES) : value);
}
/***************************************************************************
** Run iterations 0 to `iterations - 1`. A thrown failure that `refusal`
** recognises is the parser saying no; any other, including a failed assertion
** in the target and a stack overflow, is a finding.
***************************************************************************/
Result run(long seed, int iterations, Target target, Predicate<Throwable> refusal)
{
if(iterations < 1 || iterations > MAXIMUM_ITERATIONS)
{
throw new IllegalArgumentException("A campaign runs between 1 and " + MAXIMUM_ITERATIONS + " iterations.");
}
int accepted = 0;
int refused = 0;
List<Finding> findings = new ArrayList<>();
for(int iteration = 0; iteration < iterations; iteration++)
{
try
{
target.accept(input(seed, iteration));
accepted++;
}
catch(Exception | AssertionError | StackOverflowError failure)
{
if(refusal.test(failure))
{
refused++;
}
else
{
findings.add(new Finding(seed, iteration, failure.toString()));
}
}
}
return (new Result(accepted, refused, List.copyOf(findings)));
}
/***************************************************************************
** One structural or byte-level change at a random position.
***************************************************************************/
private byte[] mutate(byte[] value, SplittableRandom random)
{
int at = value.length == 0 ? 0 : random.nextInt(value.length);
int span = value.length == 0 ? 0 : 1 + random.nextInt(Math.min(16, value.length - at));
byte[] noise = new byte[1 + random.nextInt(8)];
random.nextBytes(noise);
return (switch(random.nextInt(8))
{
case 0 -> splice(value, at, span, new byte[0]);
case 1 -> splice(value, at, 0, Arrays.copyOfRange(value, at, at + span));
case 2 -> Arrays.copyOf(value, at);
case 3 -> splice(value, at, 0, dictionary.isEmpty() ? noise : dictionary.get(random.nextInt(dictionary.size())));
case 4 -> splice(value, at, 0, noise);
case 5 -> splice(value, at, Math.min(1, value.length), new byte[] {INTERESTING[random.nextInt(INTERESTING.length)]});
case 6 -> splice(value, at, Math.min(1, value.length), new byte[] {noise[0]});
default -> splice(value, at, Math.min(1, value.length), value.length == 0 ? noise : new byte[] {(byte) (value[at] ^ (1 << random.nextInt(8)))});
});
}
/***************************************************************************
** Replace `removed` bytes at `at` with `inserted`.
***************************************************************************/
private static byte[] splice(byte[] value, int at, int removed, byte[] inserted)
{
byte[] result = new byte[value.length - removed + inserted.length];
System.arraycopy(value, 0, result, 0, at);
System.arraycopy(inserted, 0, result, at, inserted.length);
System.arraycopy(value, at + removed, result, at + inserted.length, value.length - at - removed);
return (result);
}
}
@@ -0,0 +1,181 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.renderer;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
/*******************************************************************************
** The mutation harness is deterministic and reports what it should, and one
** bounded campaign runs in the normal test phase over the version 1 scene
** parser. Seeds, limits and the replay command: `docs/security/fuzzing.md`.
*******************************************************************************/
class MutationCampaignTest
{
private static final long[] SEEDS = {20261111L, 7919L};
private static final int ITERATIONS = 20_000;
private static final Set<String> ELEMENTS = Set.of("text", "rasterText", "box", "image", "barcode");
////////////////////////////////////////////////////////////////////////////////////////////////
// a second, differently configured parser, so the acceptance check does not reuse the target //
////////////////////////////////////////////////////////////////////////////////////////////////
private static final ObjectMapper INDEPENDENT = new ObjectMapper();
/***************************************************************************
** A seed and an iteration name one input, on every run and every machine.
***************************************************************************/
@Test
void aSeedAndAnIterationAlwaysGiveTheSameInput()
{
MutationCampaign campaign = new MutationCampaign(List.of("^XA^FO10,20^GB30,40,2^FS^XZ".getBytes(StandardCharsets.US_ASCII)), List.of("^FO", "^GB"));
MutationCampaign again = new MutationCampaign(List.of("^XA^FO10,20^GB30,40,2^FS^XZ".getBytes(StandardCharsets.US_ASCII)), List.of("^FO", "^GB"));
Set<String> distinct = new java.util.HashSet<>();
for(int iteration = 0; iteration < 200; iteration++)
{
byte[] input = campaign.input(42L, iteration);
assertArrayEquals(input, again.input(42L, iteration));
assertTrue(input.length <= MutationCampaign.MAXIMUM_INPUT_BYTES);
distinct.add(Wire.sha256(input));
}
assertTrue(distinct.size() > 100, "mutations should rarely repeat: " + distinct.size());
assertNotEquals(Wire.sha256(campaign.input(42L, 0)), Wire.sha256(campaign.input(43L, 0)));
}
/***************************************************************************
** The harness fails when it should: a crash and an unsafe acceptance are both
** findings, each carrying the coordinates that regenerate its input.
***************************************************************************/
@Test
void aCrashAndAnUnsafeAcceptanceAreFindingsThatReplay()
{
MutationCampaign campaign = new MutationCampaign(List.of("0123456789".getBytes(StandardCharsets.US_ASCII)), List.of());
MutationCampaign.Result result = campaign.run(42L, 300, input ->
{
if(input.length % 3 == 0)
{
throw new IllegalArgumentException("REFUSED");
}
if(input.length % 3 == 1)
{
throw new IllegalStateException("crash");
}
assertTrue(input.length < 11, "accepted an input it should have refused");
}, IllegalArgumentException.class::isInstance);
assertEquals(300, result.accepted() + result.refused() + result.findings().size());
assertTrue(result.accepted() > 0 && result.refused() > 0);
assertTrue(result.findings().stream().anyMatch(finding -> finding.failure().startsWith("java.lang.IllegalStateException")));
assertTrue(result.findings().stream().anyMatch(finding -> finding.failure().contains("AssertionFailedError")));
for(MutationCampaign.Finding finding : result.findings())
{
int length = campaign.input(finding.seed(), finding.iteration()).length;
assertTrue(length % 3 == 1 || (length % 3 == 2 && length >= 11), finding.toString());
}
}
/***************************************************************************
** A campaign is always bounded and always has something to mutate.
***************************************************************************/
@Test
void campaignsAreBounded()
{
MutationCampaign campaign = new MutationCampaign(List.of(new byte[0]), List.of());
assertThrows(IllegalArgumentException.class, () -> campaign.run(1L, 0, input -> {}, failure -> true));
assertThrows(IllegalArgumentException.class, () -> campaign.run(1L, MutationCampaign.MAXIMUM_ITERATIONS + 1, input -> {}, failure -> true));
assertThrows(IllegalArgumentException.class, () -> new MutationCampaign(List.of(), List.of()));
assertThrows(IllegalArgumentException.class, () -> new MutationCampaign(List.of(new byte[MutationCampaign.MAXIMUM_INPUT_BYTES + 1]), List.of()));
assertEquals(50, campaign.run(1L, 50, input -> {}, failure -> false).accepted());
}
/***************************************************************************
** Property: whatever the version 1 scene parser accepts is, read by a second
** parser, a version 1 scene of known elements within the element limit. Every
** other input is refused with an exception (the worker turns any exception
** into its opaque error answer); a stack overflow would be a finding.
***************************************************************************/
@Test
void theVersionOneSceneParserAcceptsOnlyVersionOneScenes() throws Exception
{
List<byte[]> corpus = new ArrayList<>();
for(String name : List.of("text-font0-203", "box-203", "barcode-scale-203", "barcode-fit-matrix-203", "barcode-fit-linear-203"))
{
try(InputStream stream = MutationCampaignTest.class.getResourceAsStream("/compat-v1/cases/" + name + ".json"))
{
corpus.add(Wire.JSON.writeValueAsBytes(Wire.JSON.readTree(stream).get("scene")));
}
}
MutationCampaign campaign = new MutationCampaign(corpus, List.of("\"type\"", "\"barcode\"", "\"rasterText\"", "\"fit\"", "\"valuePath\"", "\"symbologyPath\"", "\"allowedSymbologies\"",
"\"qqq-zpl-scene/1\"", "\"qqq-zpl-scene/2\"", "{", "}", "[", "]", ":", ",", "99999999999", "-1", "null", "\"/\"", "\\u0000"));
int iterations = MutationCampaign.iterations(ITERATIONS);
for(long seed : MutationCampaign.seeds(SEEDS))
{
MutationCampaign.Result result = campaign.run(seed, iterations, MutationCampaignTest::validateScene, Exception.class::isInstance);
assertEquals(List.of(), result.findings(), "replay with -Dfuzz.seed=" + seed + " -Dfuzz.iterations=" + iterations);
assertFalse(result.accepted() == 0 || result.refused() == 0, "the campaign must reach both acceptance and refusal: " + result);
}
}
/***************************************************************************
** The target: static validation of one scene on a stock all corpus scenes fit.
***************************************************************************/
private static void validateScene(byte[] scene) throws Exception
{
String source = new String(scene, StandardCharsets.UTF_8);
ObjectNode request = Wire.JSON.createObjectNode();
request.put("protocol", Wire.PROTOCOL).put("requestId", "mutation-campaign").put("operation", "VALIDATE").put("format", "ZPL").put("maximumBytes", 1024 * 1024).put("source", source);
request.set("stock", Wire.JSON.valueToTree(Map.of("widthMicrons", 76200, "heightMicrons", 152400, "topMicrons", 2000, "rightMicrons", 2000, "bottomMicrons", 2000, "leftMicrons", 2000, "dpi", 203)));
request.putObject("context");
request.putArray("assets");
ZplRenderer.validate(new RenderInput(request));
JsonNode accepted = INDEPENDENT.readTree(source);
assertEquals("qqq-zpl-scene/1", accepted.path("schema").textValue());
assertTrue(accepted.path("elements").isArray() && accepted.path("elements").size() <= 1000);
for(JsonNode element : accepted.path("elements"))
{
assertTrue(ELEMENTS.contains(element.path("type").textValue()), () -> "accepted element type " + element.path("type"));
}
}
}
@@ -0,0 +1,229 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.renderer;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.stream.Stream;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
/*******************************************************************************
** Version 1 scenes are frozen. Every scene of the corpus in `compat-v1` must
** render to exactly the bytes the released renderer (tag `v1.1.0-RC.2`) produced
** for it. The stored bytes are never regenerated: a difference here is a change
** to labels that are already printing, and is fixed in the code, not the corpus.
*******************************************************************************/
class ZplSceneOneCompatibilityTest
{
private static final String ROOT = "/compat-v1/";
////////////////////////////////////////////////////////////////////////////////////////////
// every property a version 1 element reads, by element type; the corpus must use each one //
////////////////////////////////////////////////////////////////////////////////////////////
private static final Map<String, Set<String>> OPTIONS = Map.of(
"text", Set.of("x", "y", "width", "height", "value", "valuePath"),
"rasterText", Set.of("x", "y", "fontAsset", "fontSize", "width", "height", "rotation", "align", "lineHeight", "threshold", "value", "valuePath"),
"box", Set.of("x", "y", "width", "height", "thickness"),
"image", Set.of("x", "y", "asset"),
"barcode", Set.of("x", "y", "symbology", "symbologyPath", "allowedSymbologies", "value", "valuePath", "scale", "quiet", "options", "payloadEncoding",
"fit", "scannerSymbologyPath", "aimIdentifierPath"));
/***************************************************************************
** Each case file is one render request: a stock, a context, assets and a scene.
***************************************************************************/
static List<String> cases() throws Exception
{
try(Stream<Path> files = Files.list(Path.of(ZplSceneOneCompatibilityTest.class.getResource(ROOT + "cases").toURI())))
{
return (files.map(file -> file.getFileName().toString()).filter(name -> name.endsWith(".json")).map(name -> name.substring(0, name.length() - 5)).sorted().toList());
}
}
/***************************************************************************
** The released bytes, or the first position at which this build differs. What
** this build produced is left in `target/compat-v1-actual` for inspection.
***************************************************************************/
@ParameterizedTest(name = "{0}")
@MethodSource("cases")
void rendersExactlyTheReleasedBytes(String name) throws Exception
{
byte[] actual = render(name);
byte[] expected = resource("zpl/" + name + ".zpl");
if(expected == null || !Arrays.equals(expected, actual))
{
Path directory = Files.createDirectories(Path.of("target", "compat-v1-actual"));
Files.write(directory.resolve(name + ".zpl"), actual);
}
assertNotNull(expected, "No released bytes are stored for " + name);
int difference = Arrays.mismatch(expected, actual);
if(difference >= 0)
{
fail("Version 1 scene " + name + " no longer renders the released bytes: first difference at byte " + difference
+ " (released " + expected.length + " bytes, now " + actual.length + ")");
}
assertEquals(manifest().get(name), Wire.sha256(actual));
}
/***************************************************************************
** The manifest names exactly the cases, and each stored file has its hash, so
** a case cannot be dropped or its bytes edited without this failing.
***************************************************************************/
@Test
void manifestNamesExactlyTheCasesAndTheirStoredBytes() throws Exception
{
Map<String, String> manifest = manifest();
assertEquals(cases(), List.copyOf(manifest.keySet()));
assertFalse(manifest.isEmpty());
for(Map.Entry<String, String> entry : manifest.entrySet())
{
byte[] stored = resource("zpl/" + entry.getKey() + ".zpl");
assertNotNull(stored, entry.getKey());
assertEquals(entry.getValue(), Wire.sha256(stored), entry.getKey());
}
}
/***************************************************************************
** The corpus is only a proof if it reaches every element type and every
** property version 1 reads, at each resolution the renderer accepts.
***************************************************************************/
@Test
void corpusUsesEveryVersionOneElementOptionAndResolution() throws Exception
{
Map<String, Set<String>> used = new TreeMap<>();
Set<Integer> resolutions = new TreeSet<>();
for(String name : cases())
{
JsonNode definition = definition(name);
resolutions.add(definition.path("stock").path("dpi").intValue());
assertEquals("qqq-zpl-scene/1", definition.path("scene").path("schema").textValue(), name);
for(JsonNode element : definition.path("scene").path("elements"))
{
Set<String> properties = used.computeIfAbsent(element.path("type").textValue(), type -> new HashSet<>());
element.fieldNames().forEachRemaining(properties::add);
}
}
assertEquals(Set.of(203, 300, 600), resolutions);
assertEquals(OPTIONS.keySet(), used.keySet());
for(Map.Entry<String, Set<String>> entry : OPTIONS.entrySet())
{
assertTrue(used.get(entry.getKey()).containsAll(entry.getValue()), () -> entry.getKey() + " is missing one of " + entry.getValue());
}
}
/***************************************************************************
** Render through the public protocol entry point, as the isolated worker does.
***************************************************************************/
private static byte[] render(String name) throws Exception
{
JsonNode definition = definition(name);
ObjectNode request = Wire.JSON.createObjectNode();
request.put("protocol", Wire.PROTOCOL).put("requestId", "compat-v1-" + name).put("operation", "RENDER").put("format", "ZPL").put("maximumBytes", 8 * 1024 * 1024);
request.set("stock", definition.get("stock"));
request.set("context", definition.get("context"));
request.put("source", Wire.JSON.writeValueAsString(definition.get("scene")));
var assets = request.putArray("assets");
for(JsonNode declared : definition.path("assets"))
{
byte[] bytes;
try(InputStream stream = ZplSceneOneCompatibilityTest.class.getResourceAsStream(declared.path("resource").textValue()))
{
bytes = java.util.Objects.requireNonNull(stream, declared.path("resource").textValue()).readAllBytes();
}
ObjectNode asset = ((ObjectNode) declared.deepCopy()).put("base64", Base64.getEncoder().encodeToString(bytes)).put("sha256", Wire.sha256(bytes));
asset.remove("resource");
assets.add(asset);
}
Map<String, Object> response = RendererMain.process(request);
assertEquals("OK", response.get("outcome"), name);
return (Base64.getDecoder().decode((String) response.get("base64")));
}
/***************************************************************************
** One case definition.
***************************************************************************/
private static JsonNode definition(String name) throws Exception
{
return (Wire.JSON.readTree(java.util.Objects.requireNonNull(resource("cases/" + name + ".json"), name)));
}
/***************************************************************************
** `SHA256SUMS` in the form `sha256sum` writes and checks: hash, two spaces, path.
***************************************************************************/
private static Map<String, String> manifest() throws Exception
{
Map<String, String> hashes = new TreeMap<>();
byte[] sums = resource("SHA256SUMS");
for(String line : sums == null ? new String[0] : new String(sums, StandardCharsets.US_ASCII).split("\n"))
{
assertTrue(line.matches("[0-9a-f]{64} zpl/[a-z0-9-]+\\.zpl"), line);
hashes.put(line.substring(70, line.length() - 4), line.substring(0, 64));
}
return (hashes);
}
/***************************************************************************
** Corpus bytes, or null when the corpus has no such file.
***************************************************************************/
private static byte[] resource(String path) throws Exception
{
try(InputStream stream = ZplSceneOneCompatibilityTest.class.getResourceAsStream(ROOT + path))
{
return (stream == null ? null : stream.readAllBytes());
}
}
}
@@ -0,0 +1,47 @@
# Version 1 compatibility corpus
Scenes with schema `qqq-zpl-scene/1` are frozen: a label that prints today must print the same bytes after any
later change. This directory holds 42 render requests and the exact ZPL the released renderer produced for each.
| Path | Content |
|---|---|
| `cases/<name>.json` | One render request: `stock` (with its `dpi`), `context`, `assets` and the `scene`. |
| `zpl/<name>.zpl` | The bytes the released renderer returned for that request. |
| `SHA256SUMS` | The SHA-256 of every stored output, in the form `sha256sum -c` reads. |
| `assets/test-mark.png` | A synthetic 48 x 24 image: a frame, a grey ramp and a transparency ramp. |
Fonts come from the existing test resources (`/fonts/Abel-Regular.ttf`, `/fonts/jetbrains-mono/`).
## What it covers
- The shapes Forge publishes, at the 203 and 300 dpi its printers report: the plain Echo label (dynamic symbology
from an allowlist, fitted), the scanner-fidelity label (scanner label and AIM identifier), the compact GS1-128
label (`fit` and `quiet`, with the human-readable line as `text` in font 0) and the centred GS1-128 document
(`rasterText` with `fontAsset`; its barcode carries an `align` key, which version 1 ignores).
- The examples of `renderer/README.md` and `renderer/BARCODE-FIT.md`, verbatim.
- One scene per element type and option at 203, 300 and 600 dpi: `text`, `box`, `image`, `rasterText` (alignment,
rotation, line height, threshold, wrapping, three faces) and `barcode` (scale, options, bound values, and all 13
fit-qualified symbologies).
Every value is synthetic (`TEST-0001`, `(01)00012345678905(11)260101(21)TEST0001`, `example.invalid`).
Not covered, because they need the native Zint provider that exists only in the renderer image: `payloadEncoding`
`BASE64` and the Zint-backed symbologies. The image qualifications cover those.
## Where the bytes came from
They were produced by the source of tag `v1.1.0-RC.2` (commit `e9ffba484e8d97f9853cb071cdac14f3bfd7acda`,
renderer `qqq-renderer-0.1.0`, catalog `qqq-barcodes-9`), not by the branch that added this directory:
`ZplSceneOneCompatibilityTest` and `cases/` were copied into a checkout of that tag and run there with no stored
outputs. The test fails in that state and leaves what it rendered in `target/compat-v1-actual`; those files are
`zpl/`. The run was on Temurin 21.0.12 on Windows, and the same test then passed against the same tag on Temurin
21.0.9 on Linux (the CI image), so the `rasterText` pixels do not depend on either platform.
## Rules
- Never regenerate `zpl/` or `SHA256SUMS`, and never edit a case. If `ZplSceneOneCompatibilityTest` fails, the
code changed what a version 1 scene prints; fix the code. The first differing byte is in the failure message
and the new bytes are in `renderer/target/compat-v1-actual`.
- A new case may be added only with bytes produced the same way, from a tag that is in production.
- `bridges/renderer` reads this directory in place (its own `ZplSceneOneCompatibilityTest` proves each stored
output passes `ZplOutputPolicy`), so there is one copy.
@@ -0,0 +1,42 @@
ff0e556b910d9cf3a62cec78f388788c7a466942d8d43462d6cc8ff93d8a961c zpl/barcode-fit-linear-203.zpl
29f10d73be75d3c50d6f4b6a034a5d3819173593effa13e043d62f8a9b49e342 zpl/barcode-fit-linear-300.zpl
6ba73d75d91b5b7a204b2d8cc0c691438a0608872a0904ba8aa3fc3542c4a04e zpl/barcode-fit-linear-600.zpl
7c38e5f1d3816375b002f34a97473d543b57f538d91a6b93aec56f7fad96fc8c zpl/barcode-fit-matrix-203.zpl
dfaecb170fabd6a9d0214075f24f11c374edfd36a657e7d1acc0fe9fb480b365 zpl/barcode-fit-matrix-300.zpl
c2f397bc2503f087b342038c455f7aff9f279139dabce79f12d2e03f3a8ed242 zpl/barcode-fit-matrix-600.zpl
bde75c5c99ef064df4159385686c0a068ae0a82310a31b7703f16d8473d25f26 zpl/barcode-scale-203.zpl
02b16ec29d0cb9452cc787ca970181e53f6d88a5fa495aa16b6e131309c02b5f zpl/barcode-scale-300.zpl
7ce8d401428ff1c1ccf7587d1b36e5810a0c8762ffb5d217cf7e787b3a4d5806 zpl/barcode-scale-600.zpl
d24c7334422a6268566f6fc5826ed1a6f746a3c06fee15f251eabae93b15b0c5 zpl/box-203.zpl
97ee29fa76f9359351101c58aa9a72ca81b5e355cc609e42d1780af817f37562 zpl/box-300.zpl
d6874a29d5459c3d2f4b027108c9829cb9b0d5bdff7878fc8c4a5da8c55d5c72 zpl/box-600.zpl
89fe7d52804801fae99e53bfabada63eb08f85222f599abaa4bb4aea26869f64 zpl/forge-gs1-compact-203.zpl
5ac73d061da31c6a45da37e91eecb092803a5036cd8e39b96af26f109e2310f9 zpl/forge-gs1-compact-300.zpl
90065471a443d35c30a9426f9d0ac62773114ff57d484bea373b444e8ef3ee9a zpl/forge-gs1-raster-203.zpl
205a0ac35ce6df151b8c7feac081e8b1cab3fbdc8ca6a685e601a2782ddf3869 zpl/forge-gs1-raster-300.zpl
a626755d7fe9499b5b9b2dbb4bc64bdb60ba5857f267115b4762ab32276e8b30 zpl/forge-plain-code128-203.zpl
40552f1f04f58b67d69464e1b58a2ac3eeda98ac5af6d1a96a8b1b128637add5 zpl/forge-plain-code128-300.zpl
8d5391528d92f8afe8d6fb62df9f9ce3b6baa6bf3bfdf4133a7cfe60399b74e1 zpl/forge-plain-qr-203.zpl
a626755d7fe9499b5b9b2dbb4bc64bdb60ba5857f267115b4762ab32276e8b30 zpl/forge-scanner-code128-203.zpl
b5ea4f928c670ee06027b05406d45ab829c29d1676b05ca34728b350dfa60582 zpl/forge-scanner-ean13-203.zpl
b2b41c26316c843ef2156179a57e145c57f5006bcdc8f1497d2d263c743b948d zpl/forge-scanner-ean13-300.zpl
e45cb088bd56c470aa2ad0f7904e7cf97a75ada21b2cf0075679accc0a0456b6 zpl/image-203.zpl
b17fdf526032eb4fe6892f7737cb6c7a1069305c4ff85a9a4d902ebf29a1d778 zpl/image-300.zpl
0ae0dde4208c8696bfd36dd427e8f7735dbce3961572b6225f5cf4868ae55b2c zpl/image-600.zpl
0b100f9a37a78ceede0dbaaec9ba1e9bbf352b7675bd8900ede3faa5dc855ce7 zpl/raster-text-layout-203.zpl
ba4a8eea59afcfd67dbcf2982f70cc3b7083dd3259c439793d79f4cae78f6483 zpl/raster-text-layout-300.zpl
d7682aecebdbcc80fd2ade6cb2108cccd991fe05af35a28554111b00cdceb442 zpl/raster-text-layout-600.zpl
44231f071dc6131a13d9624454dcb796a7355d167040dfdc68ca7acefb12e06d zpl/raster-text-rotation-203.zpl
2a3a8a2529279a2ffe79771ea2f18aa66b08357a5ef5e49d52eaba60d3b2e781 zpl/raster-text-rotation-300.zpl
5e68955f87763277798413c26b9486f4ef5a60f3458c734612acbe6e9de813c3 zpl/raster-text-rotation-600.zpl
d4a61011f45eb4057dcbd5e2f7ee44e911b1c6ba9bba512a94ed45906143cce8 zpl/readme-barcode-fit-203.zpl
fc4fe98d71292b4cedb2bc15343c6b0ae1e71f3ad606422cd197429cf7d8658e zpl/readme-barcode-fit-300.zpl
4ff078a28668055a8a480fe909185c13977fdb665ab5d2acac882267e72a6fea zpl/readme-raster-text-203.zpl
850b76e86ca32fbee963052b3df69b431f021f76080b192b2954e11c9efde84a zpl/readme-raster-text-300.zpl
d094fd9bebc3d2131b7df1a5cb5b0fafcaefb226045f42985b91261e39e27ca4 zpl/readme-raster-text-600.zpl
f4ce3ef8723bc893784b1dbcb93ff831f22bdc0959b9500b0644a9ecdf9ce781 zpl/readme-zpl-scene-203.zpl
198762809d8d21e49c71dbcfcefb2f61315e5c182b0992ed136b10bb3968b5b0 zpl/readme-zpl-scene-300.zpl
48271003bc87ec1c14f53ccdb6810f810d08b63079169258915080a0f89a0688 zpl/readme-zpl-scene-600.zpl
2fd2eb3b5c9d761946b5c74656fc88838ed6f79848ea5eb296171539e25cd2e6 zpl/text-font0-203.zpl
dbd3697539e011d6c9d4c34185b849184fc22d5b836819df200d98478720ce06 zpl/text-font0-300.zpl
9ac70a9aa2bb8a5d41c13143617d338f1c3a50b0fea0f01d5054869c0bc3f31c zpl/text-font0-600.zpl
Binary file not shown.

After

Width:  |  Height:  |  Size: 333 B

@@ -0,0 +1,127 @@
{
"description": "Every fit-qualified linear symbology in a fixed physical box.",
"stock": {
"widthMicrons": 76200,
"heightMicrons": 152400,
"topMicrons": 2000,
"rightMicrons": 2000,
"bottomMicrons": 2000,
"leftMicrons": 2000,
"dpi": 203
},
"context": {},
"assets": [],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "barcode",
"x": 16,
"y": 16,
"symbology": "code128",
"value": "TEST-0001",
"quiet": 10,
"fit": {
"widthMicrons": 60000,
"heightMicrons": 12000
}
},
{
"type": "barcode",
"x": 16,
"y": 144,
"symbology": "code39",
"value": "TEST 0001",
"quiet": 10,
"fit": {
"widthMicrons": 60000,
"heightMicrons": 12000
}
},
{
"type": "barcode",
"x": 16,
"y": 272,
"symbology": "code93",
"value": "TEST-0001",
"quiet": 10,
"fit": {
"widthMicrons": 60000,
"heightMicrons": 12000
}
},
{
"type": "barcode",
"x": 16,
"y": 400,
"symbology": "itf",
"value": "0012345678",
"quiet": 10,
"fit": {
"widthMicrons": 60000,
"heightMicrons": 12000
}
},
{
"type": "barcode",
"x": 16,
"y": 528,
"symbology": "gs1-128",
"value": "[01]00012345678905[11]260101[21]TEST0001",
"quiet": 10,
"fit": {
"widthMicrons": 60000,
"heightMicrons": 12000
}
},
{
"type": "barcode",
"x": 16,
"y": 656,
"symbology": "ean13",
"value": "001234567890",
"quiet": 10,
"fit": {
"widthMicrons": 60000,
"heightMicrons": 12000
}
},
{
"type": "barcode",
"x": 16,
"y": 784,
"symbology": "ean8",
"value": "0012345",
"quiet": 10,
"fit": {
"widthMicrons": 60000,
"heightMicrons": 12000
}
},
{
"type": "barcode",
"x": 16,
"y": 912,
"symbology": "upca",
"value": "01234567890",
"quiet": 10,
"fit": {
"widthMicrons": 60000,
"heightMicrons": 12000
}
},
{
"type": "barcode",
"x": 16,
"y": 1040,
"symbology": "upce",
"value": "0123456",
"quiet": 10,
"fit": {
"widthMicrons": 60000,
"heightMicrons": 12000
}
}
]
}
}
@@ -0,0 +1,127 @@
{
"description": "Every fit-qualified linear symbology in a fixed physical box.",
"stock": {
"widthMicrons": 76200,
"heightMicrons": 152400,
"topMicrons": 2000,
"rightMicrons": 2000,
"bottomMicrons": 2000,
"leftMicrons": 2000,
"dpi": 300
},
"context": {},
"assets": [],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "barcode",
"x": 24,
"y": 24,
"symbology": "code128",
"value": "TEST-0001",
"quiet": 10,
"fit": {
"widthMicrons": 60000,
"heightMicrons": 12000
}
},
{
"type": "barcode",
"x": 24,
"y": 213,
"symbology": "code39",
"value": "TEST 0001",
"quiet": 10,
"fit": {
"widthMicrons": 60000,
"heightMicrons": 12000
}
},
{
"type": "barcode",
"x": 24,
"y": 402,
"symbology": "code93",
"value": "TEST-0001",
"quiet": 10,
"fit": {
"widthMicrons": 60000,
"heightMicrons": 12000
}
},
{
"type": "barcode",
"x": 24,
"y": 591,
"symbology": "itf",
"value": "0012345678",
"quiet": 10,
"fit": {
"widthMicrons": 60000,
"heightMicrons": 12000
}
},
{
"type": "barcode",
"x": 24,
"y": 780,
"symbology": "gs1-128",
"value": "[01]00012345678905[11]260101[21]TEST0001",
"quiet": 10,
"fit": {
"widthMicrons": 60000,
"heightMicrons": 12000
}
},
{
"type": "barcode",
"x": 24,
"y": 969,
"symbology": "ean13",
"value": "001234567890",
"quiet": 10,
"fit": {
"widthMicrons": 60000,
"heightMicrons": 12000
}
},
{
"type": "barcode",
"x": 24,
"y": 1158,
"symbology": "ean8",
"value": "0012345",
"quiet": 10,
"fit": {
"widthMicrons": 60000,
"heightMicrons": 12000
}
},
{
"type": "barcode",
"x": 24,
"y": 1347,
"symbology": "upca",
"value": "01234567890",
"quiet": 10,
"fit": {
"widthMicrons": 60000,
"heightMicrons": 12000
}
},
{
"type": "barcode",
"x": 24,
"y": 1536,
"symbology": "upce",
"value": "0123456",
"quiet": 10,
"fit": {
"widthMicrons": 60000,
"heightMicrons": 12000
}
}
]
}
}
@@ -0,0 +1,127 @@
{
"description": "Every fit-qualified linear symbology in a fixed physical box.",
"stock": {
"widthMicrons": 76200,
"heightMicrons": 152400,
"topMicrons": 2000,
"rightMicrons": 2000,
"bottomMicrons": 2000,
"leftMicrons": 2000,
"dpi": 600
},
"context": {},
"assets": [],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "barcode",
"x": 48,
"y": 48,
"symbology": "code128",
"value": "TEST-0001",
"quiet": 10,
"fit": {
"widthMicrons": 60000,
"heightMicrons": 12000
}
},
{
"type": "barcode",
"x": 48,
"y": 426,
"symbology": "code39",
"value": "TEST 0001",
"quiet": 10,
"fit": {
"widthMicrons": 60000,
"heightMicrons": 12000
}
},
{
"type": "barcode",
"x": 48,
"y": 804,
"symbology": "code93",
"value": "TEST-0001",
"quiet": 10,
"fit": {
"widthMicrons": 60000,
"heightMicrons": 12000
}
},
{
"type": "barcode",
"x": 48,
"y": 1182,
"symbology": "itf",
"value": "0012345678",
"quiet": 10,
"fit": {
"widthMicrons": 60000,
"heightMicrons": 12000
}
},
{
"type": "barcode",
"x": 48,
"y": 1560,
"symbology": "gs1-128",
"value": "[01]00012345678905[11]260101[21]TEST0001",
"quiet": 10,
"fit": {
"widthMicrons": 60000,
"heightMicrons": 12000
}
},
{
"type": "barcode",
"x": 48,
"y": 1938,
"symbology": "ean13",
"value": "001234567890",
"quiet": 10,
"fit": {
"widthMicrons": 60000,
"heightMicrons": 12000
}
},
{
"type": "barcode",
"x": 48,
"y": 2316,
"symbology": "ean8",
"value": "0012345",
"quiet": 10,
"fit": {
"widthMicrons": 60000,
"heightMicrons": 12000
}
},
{
"type": "barcode",
"x": 48,
"y": 2694,
"symbology": "upca",
"value": "01234567890",
"quiet": 10,
"fit": {
"widthMicrons": 60000,
"heightMicrons": 12000
}
},
{
"type": "barcode",
"x": 48,
"y": 3072,
"symbology": "upce",
"value": "0123456",
"quiet": 10,
"fit": {
"widthMicrons": 60000,
"heightMicrons": 12000
}
}
]
}
}
@@ -0,0 +1,71 @@
{
"description": "Every fit-qualified matrix and stacked symbology in a fixed physical box, with the minimum quiet zone.",
"stock": {
"widthMicrons": 76200,
"heightMicrons": 152400,
"topMicrons": 2000,
"rightMicrons": 2000,
"bottomMicrons": 2000,
"leftMicrons": 2000,
"dpi": 203
},
"context": {
"record": {
"value": "TEST-0001"
}
},
"assets": [],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "barcode",
"x": 16,
"y": 16,
"symbology": "qr",
"valuePath": "/record/value",
"quiet": 4,
"fit": {
"widthMicrons": 15000,
"heightMicrons": 15000
}
},
{
"type": "barcode",
"x": 16,
"y": 160,
"symbology": "datamatrix",
"valuePath": "/record/value",
"quiet": 4,
"fit": {
"widthMicrons": 15000,
"heightMicrons": 15000
}
},
{
"type": "barcode",
"x": 16,
"y": 304,
"symbology": "aztec",
"valuePath": "/record/value",
"quiet": 4,
"fit": {
"widthMicrons": 15000,
"heightMicrons": 15000
}
},
{
"type": "barcode",
"x": 16,
"y": 448,
"symbology": "pdf417",
"valuePath": "/record/value",
"quiet": 4,
"fit": {
"widthMicrons": 40000,
"heightMicrons": 15000
}
}
]
}
}
@@ -0,0 +1,71 @@
{
"description": "Every fit-qualified matrix and stacked symbology in a fixed physical box, with the minimum quiet zone.",
"stock": {
"widthMicrons": 76200,
"heightMicrons": 152400,
"topMicrons": 2000,
"rightMicrons": 2000,
"bottomMicrons": 2000,
"leftMicrons": 2000,
"dpi": 300
},
"context": {
"record": {
"value": "TEST-0001"
}
},
"assets": [],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "barcode",
"x": 24,
"y": 24,
"symbology": "qr",
"valuePath": "/record/value",
"quiet": 4,
"fit": {
"widthMicrons": 15000,
"heightMicrons": 15000
}
},
{
"type": "barcode",
"x": 24,
"y": 237,
"symbology": "datamatrix",
"valuePath": "/record/value",
"quiet": 4,
"fit": {
"widthMicrons": 15000,
"heightMicrons": 15000
}
},
{
"type": "barcode",
"x": 24,
"y": 450,
"symbology": "aztec",
"valuePath": "/record/value",
"quiet": 4,
"fit": {
"widthMicrons": 15000,
"heightMicrons": 15000
}
},
{
"type": "barcode",
"x": 24,
"y": 663,
"symbology": "pdf417",
"valuePath": "/record/value",
"quiet": 4,
"fit": {
"widthMicrons": 40000,
"heightMicrons": 15000
}
}
]
}
}
@@ -0,0 +1,71 @@
{
"description": "Every fit-qualified matrix and stacked symbology in a fixed physical box, with the minimum quiet zone.",
"stock": {
"widthMicrons": 76200,
"heightMicrons": 152400,
"topMicrons": 2000,
"rightMicrons": 2000,
"bottomMicrons": 2000,
"leftMicrons": 2000,
"dpi": 600
},
"context": {
"record": {
"value": "TEST-0001"
}
},
"assets": [],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "barcode",
"x": 48,
"y": 48,
"symbology": "qr",
"valuePath": "/record/value",
"quiet": 4,
"fit": {
"widthMicrons": 15000,
"heightMicrons": 15000
}
},
{
"type": "barcode",
"x": 48,
"y": 474,
"symbology": "datamatrix",
"valuePath": "/record/value",
"quiet": 4,
"fit": {
"widthMicrons": 15000,
"heightMicrons": 15000
}
},
{
"type": "barcode",
"x": 48,
"y": 900,
"symbology": "aztec",
"valuePath": "/record/value",
"quiet": 4,
"fit": {
"widthMicrons": 15000,
"heightMicrons": 15000
}
},
{
"type": "barcode",
"x": 48,
"y": 1326,
"symbology": "pdf417",
"valuePath": "/record/value",
"quiet": 4,
"fit": {
"widthMicrons": 40000,
"heightMicrons": 15000
}
}
]
}
}
@@ -0,0 +1,63 @@
{
"description": "Barcodes at an explicit scale: typed options, the explicit UTF8 payload encoding, a bound value and a matrix symbology.",
"stock": {
"widthMicrons": 76200,
"heightMicrons": 50800,
"topMicrons": 0,
"rightMicrons": 0,
"bottomMicrons": 0,
"leftMicrons": 0,
"dpi": 203
},
"context": {
"record": {
"url": "https://example.invalid/test/0001"
}
},
"assets": [],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "barcode",
"x": 20,
"y": 20,
"symbology": "code128",
"scale": 2,
"quiet": 10,
"value": "TEST-0001",
"options": {
"barHeight": 40
}
},
{
"type": "barcode",
"x": 20,
"y": 130,
"symbology": "code39",
"scale": 1,
"quiet": 10,
"value": "TEST 0001",
"payloadEncoding": "UTF8"
},
{
"type": "barcode",
"x": 20,
"y": 230,
"symbology": "qr",
"scale": 3,
"quiet": 4,
"valuePath": "/record/url"
},
{
"type": "barcode",
"x": 200,
"y": 230,
"symbology": "datamatrix",
"scale": 4,
"quiet": 4,
"value": "TEST-0001"
}
]
}
}
@@ -0,0 +1,63 @@
{
"description": "Barcodes at an explicit scale: typed options, the explicit UTF8 payload encoding, a bound value and a matrix symbology.",
"stock": {
"widthMicrons": 76200,
"heightMicrons": 50800,
"topMicrons": 0,
"rightMicrons": 0,
"bottomMicrons": 0,
"leftMicrons": 0,
"dpi": 300
},
"context": {
"record": {
"url": "https://example.invalid/test/0001"
}
},
"assets": [],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "barcode",
"x": 30,
"y": 30,
"symbology": "code128",
"scale": 3,
"quiet": 10,
"value": "TEST-0001",
"options": {
"barHeight": 40
}
},
{
"type": "barcode",
"x": 30,
"y": 192,
"symbology": "code39",
"scale": 1,
"quiet": 10,
"value": "TEST 0001",
"payloadEncoding": "UTF8"
},
{
"type": "barcode",
"x": 30,
"y": 340,
"symbology": "qr",
"scale": 4,
"quiet": 4,
"valuePath": "/record/url"
},
{
"type": "barcode",
"x": 296,
"y": 340,
"symbology": "datamatrix",
"scale": 6,
"quiet": 4,
"value": "TEST-0001"
}
]
}
}
@@ -0,0 +1,63 @@
{
"description": "Barcodes at an explicit scale: typed options, the explicit UTF8 payload encoding, a bound value and a matrix symbology.",
"stock": {
"widthMicrons": 76200,
"heightMicrons": 50800,
"topMicrons": 0,
"rightMicrons": 0,
"bottomMicrons": 0,
"leftMicrons": 0,
"dpi": 600
},
"context": {
"record": {
"url": "https://example.invalid/test/0001"
}
},
"assets": [],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "barcode",
"x": 59,
"y": 59,
"symbology": "code128",
"scale": 6,
"quiet": 10,
"value": "TEST-0001",
"options": {
"barHeight": 40
}
},
{
"type": "barcode",
"x": 59,
"y": 384,
"symbology": "code39",
"scale": 3,
"quiet": 10,
"value": "TEST 0001",
"payloadEncoding": "UTF8"
},
{
"type": "barcode",
"x": 59,
"y": 680,
"symbology": "qr",
"scale": 9,
"quiet": 4,
"valuePath": "/record/url"
},
{
"type": "barcode",
"x": 591,
"y": 680,
"symbology": "datamatrix",
"scale": 12,
"quiet": 4,
"value": "TEST-0001"
}
]
}
}
@@ -0,0 +1,51 @@
{
"description": "Boxes: an outline, a filled square, a horizontal rule and a vertical rule.",
"stock": {
"widthMicrons": 76200,
"heightMicrons": 50800,
"topMicrons": 0,
"rightMicrons": 0,
"bottomMicrons": 0,
"leftMicrons": 0,
"dpi": 203
},
"context": {},
"assets": [],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "box",
"x": 20,
"y": 20,
"width": 200,
"height": 100,
"thickness": 3
},
{
"type": "box",
"x": 240,
"y": 20,
"width": 60,
"height": 60,
"thickness": 30
},
{
"type": "box",
"x": 20,
"y": 140,
"width": 500,
"height": 2,
"thickness": 2
},
{
"type": "box",
"x": 560,
"y": 20,
"width": 2,
"height": 200,
"thickness": 2
}
]
}
}
@@ -0,0 +1,51 @@
{
"description": "Boxes: an outline, a filled square, a horizontal rule and a vertical rule.",
"stock": {
"widthMicrons": 76200,
"heightMicrons": 50800,
"topMicrons": 0,
"rightMicrons": 0,
"bottomMicrons": 0,
"leftMicrons": 0,
"dpi": 300
},
"context": {},
"assets": [],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "box",
"x": 30,
"y": 30,
"width": 296,
"height": 148,
"thickness": 4
},
{
"type": "box",
"x": 355,
"y": 30,
"width": 89,
"height": 89,
"thickness": 44
},
{
"type": "box",
"x": 30,
"y": 207,
"width": 739,
"height": 3,
"thickness": 3
},
{
"type": "box",
"x": 828,
"y": 30,
"width": 3,
"height": 296,
"thickness": 3
}
]
}
}
@@ -0,0 +1,51 @@
{
"description": "Boxes: an outline, a filled square, a horizontal rule and a vertical rule.",
"stock": {
"widthMicrons": 76200,
"heightMicrons": 50800,
"topMicrons": 0,
"rightMicrons": 0,
"bottomMicrons": 0,
"leftMicrons": 0,
"dpi": 600
},
"context": {},
"assets": [],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "box",
"x": 59,
"y": 59,
"width": 591,
"height": 296,
"thickness": 9
},
{
"type": "box",
"x": 709,
"y": 59,
"width": 177,
"height": 177,
"thickness": 89
},
{
"type": "box",
"x": 59,
"y": 414,
"width": 1478,
"height": 6,
"thickness": 6
},
{
"type": "box",
"x": 1655,
"y": 59,
"width": 6,
"height": 591,
"thickness": 6
}
]
}
}
@@ -0,0 +1,44 @@
{
"description": "Forge compact GS1-128 label: fitted bars with quiet zone and the human-readable line in printer font 0.",
"stock": {
"widthMicrons": 101600,
"heightMicrons": 25400,
"topMicrons": 3000,
"rightMicrons": 3000,
"bottomMicrons": 3000,
"leftMicrons": 3000,
"dpi": 203
},
"context": {
"record": {
"rendererValue": "[01]00012345678905[11]260101[21]TEST0001",
"humanReadable": "(01)00012345678905(11)260101(21)TEST0001"
}
},
"assets": [],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "barcode",
"symbology": "gs1-128",
"valuePath": "/record/rendererValue",
"quiet": 10,
"x": 32,
"y": 32,
"fit": {
"widthMicrons": 93600,
"heightMicrons": 12400
}
},
{
"type": "text",
"valuePath": "/record/humanReadable",
"x": 32,
"y": 144,
"height": 24,
"width": 10
}
]
}
}
@@ -0,0 +1,44 @@
{
"description": "Forge compact GS1-128 label: fitted bars with quiet zone and the human-readable line in printer font 0.",
"stock": {
"widthMicrons": 101600,
"heightMicrons": 25400,
"topMicrons": 3000,
"rightMicrons": 3000,
"bottomMicrons": 3000,
"leftMicrons": 3000,
"dpi": 300
},
"context": {
"record": {
"rendererValue": "[01]00012345678905[11]260101[21]TEST0001",
"humanReadable": "(01)00012345678905(11)260101(21)TEST0001"
}
},
"assets": [],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "barcode",
"symbology": "gs1-128",
"valuePath": "/record/rendererValue",
"quiet": 10,
"x": 48,
"y": 48,
"fit": {
"widthMicrons": 93600,
"heightMicrons": 12400
}
},
{
"type": "text",
"valuePath": "/record/humanReadable",
"x": 48,
"y": 212,
"height": 36,
"width": 15
}
]
}
}
@@ -0,0 +1,57 @@
{
"description": "The Forge centred GS1-128 document drawn for a 203 dpi printer.",
"stock": {
"widthMicrons": 103504,
"heightMicrons": 25400,
"topMicrons": 3000,
"rightMicrons": 3000,
"bottomMicrons": 3000,
"leftMicrons": 3000,
"dpi": 203
},
"context": {
"record": {
"rendererValue": "[01]00012345678905[11]260101[21]TEST0001",
"humanReadable": "(01)00012345678905(11)260101(21)TEST0001"
}
},
"assets": [
{
"name": "forge-echo-abel-regular",
"resource": "/fonts/Abel-Regular.ttf",
"mediaType": "font/ttf",
"fontFamily": "Abel",
"fontWeight": 400,
"fontStyle": "normal"
}
],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "barcode",
"symbology": "gs1-128",
"valuePath": "/record/rendererValue",
"quiet": 10,
"x": 32,
"y": 32,
"fit": {
"widthMicrons": 95504,
"heightMicrons": 16000
},
"align": "center"
},
{
"type": "rasterText",
"valuePath": "/record/humanReadable",
"x": 32,
"y": 162,
"fontAsset": "forge-echo-abel-regular",
"fontSize": 30,
"width": 763,
"height": 40,
"align": "center"
}
]
}
}
@@ -0,0 +1,57 @@
{
"description": "Forge centred GS1-128 document: fitted bars carrying an align key that version 1 ignores, and the human-readable line as rasterText in an uploaded face.",
"stock": {
"widthMicrons": 103504,
"heightMicrons": 25400,
"topMicrons": 3000,
"rightMicrons": 3000,
"bottomMicrons": 3000,
"leftMicrons": 3000,
"dpi": 300
},
"context": {
"record": {
"rendererValue": "[01]00012345678905[11]260101[21]TEST0001",
"humanReadable": "(01)00012345678905(11)260101(21)TEST0001"
}
},
"assets": [
{
"name": "forge-echo-abel-regular",
"resource": "/fonts/Abel-Regular.ttf",
"mediaType": "font/ttf",
"fontFamily": "Abel",
"fontWeight": 400,
"fontStyle": "normal"
}
],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "barcode",
"symbology": "gs1-128",
"valuePath": "/record/rendererValue",
"quiet": 10,
"x": 36,
"y": 36,
"fit": {
"widthMicrons": 95504,
"heightMicrons": 16000
},
"align": "center"
},
{
"type": "rasterText",
"valuePath": "/record/humanReadable",
"x": 36,
"y": 206,
"fontAsset": "forge-echo-abel-regular",
"fontSize": 44,
"width": 1128,
"height": 58,
"align": "center"
}
]
}
}
@@ -0,0 +1,44 @@
{
"description": "Forge plain Echo label: dynamic symbology from an allowlist, fitted to the whole label less its inset.",
"stock": {
"widthMicrons": 101600,
"heightMicrons": 25400,
"topMicrons": 3000,
"rightMicrons": 3000,
"bottomMicrons": 3000,
"leftMicrons": 3000,
"dpi": 203
},
"context": {
"record": {
"rendererSymbology": "code128",
"barcode": "TEST-0001"
}
},
"assets": [],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "barcode",
"symbologyPath": "/record/rendererSymbology",
"allowedSymbologies": [
"code128",
"qr",
"code39",
"datamatrix",
"pdf417",
"aztec"
],
"valuePath": "/record/barcode",
"x": 32,
"y": 32,
"quiet": 10,
"fit": {
"widthMicrons": 93600,
"heightMicrons": 17400
}
}
]
}
}
@@ -0,0 +1,44 @@
{
"description": "Forge plain Echo label: dynamic symbology from an allowlist, fitted to the whole label less its inset.",
"stock": {
"widthMicrons": 101600,
"heightMicrons": 25400,
"topMicrons": 3000,
"rightMicrons": 3000,
"bottomMicrons": 3000,
"leftMicrons": 3000,
"dpi": 300
},
"context": {
"record": {
"rendererSymbology": "code128",
"barcode": "TEST-0001"
}
},
"assets": [],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "barcode",
"symbologyPath": "/record/rendererSymbology",
"allowedSymbologies": [
"code128",
"qr",
"code39",
"datamatrix",
"pdf417",
"aztec"
],
"valuePath": "/record/barcode",
"x": 48,
"y": 48,
"quiet": 10,
"fit": {
"widthMicrons": 93600,
"heightMicrons": 17400
}
}
]
}
}
@@ -0,0 +1,44 @@
{
"description": "Forge plain Echo label resolving a matrix symbology from the same allowlist.",
"stock": {
"widthMicrons": 101600,
"heightMicrons": 25400,
"topMicrons": 3000,
"rightMicrons": 3000,
"bottomMicrons": 3000,
"leftMicrons": 3000,
"dpi": 203
},
"context": {
"record": {
"rendererSymbology": "qr",
"barcode": "TEST-0001"
}
},
"assets": [],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "barcode",
"symbologyPath": "/record/rendererSymbology",
"allowedSymbologies": [
"code128",
"qr",
"code39",
"datamatrix",
"pdf417",
"aztec"
],
"valuePath": "/record/barcode",
"x": 32,
"y": 32,
"quiet": 10,
"fit": {
"widthMicrons": 93600,
"heightMicrons": 17400
}
}
]
}
}
@@ -0,0 +1,52 @@
{
"description": "Forge scanner-fidelity Echo label for a symbology whose payload is passed through unchanged.",
"stock": {
"widthMicrons": 101600,
"heightMicrons": 25400,
"topMicrons": 3000,
"rightMicrons": 3000,
"bottomMicrons": 3000,
"leftMicrons": 3000,
"dpi": 203
},
"context": {
"record": {
"rendererSymbology": "code128",
"barcode": "TEST-0001",
"symbology": "CODE128",
"aimIdentifier": "]C0"
}
},
"assets": [],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "barcode",
"symbologyPath": "/record/rendererSymbology",
"allowedSymbologies": [
"code128",
"qr",
"code39",
"datamatrix",
"pdf417",
"aztec",
"ean13",
"ean8",
"upca",
"upce"
],
"valuePath": "/record/barcode",
"x": 32,
"y": 32,
"quiet": 10,
"fit": {
"widthMicrons": 93600,
"heightMicrons": 17400
},
"scannerSymbologyPath": "/record/symbology",
"aimIdentifierPath": "/record/aimIdentifier"
}
]
}
}
@@ -0,0 +1,52 @@
{
"description": "Forge scanner-fidelity Echo label: the recorded scanner label and AIM identifier are verified and an EAN-13 is reproduced.",
"stock": {
"widthMicrons": 101600,
"heightMicrons": 25400,
"topMicrons": 3000,
"rightMicrons": 3000,
"bottomMicrons": 3000,
"leftMicrons": 3000,
"dpi": 203
},
"context": {
"record": {
"rendererSymbology": "ean13",
"barcode": "0012345678905",
"symbology": "LABEL-TYPE-EAN13",
"aimIdentifier": "]E0"
}
},
"assets": [],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "barcode",
"symbologyPath": "/record/rendererSymbology",
"allowedSymbologies": [
"code128",
"qr",
"code39",
"datamatrix",
"pdf417",
"aztec",
"ean13",
"ean8",
"upca",
"upce"
],
"valuePath": "/record/barcode",
"x": 32,
"y": 32,
"quiet": 10,
"fit": {
"widthMicrons": 93600,
"heightMicrons": 17400
},
"scannerSymbologyPath": "/record/symbology",
"aimIdentifierPath": "/record/aimIdentifier"
}
]
}
}
@@ -0,0 +1,52 @@
{
"description": "Forge scanner-fidelity Echo label: the recorded scanner label and AIM identifier are verified and an EAN-13 is reproduced.",
"stock": {
"widthMicrons": 101600,
"heightMicrons": 25400,
"topMicrons": 3000,
"rightMicrons": 3000,
"bottomMicrons": 3000,
"leftMicrons": 3000,
"dpi": 300
},
"context": {
"record": {
"rendererSymbology": "ean13",
"barcode": "0012345678905",
"symbology": "LABEL-TYPE-EAN13",
"aimIdentifier": "]E0"
}
},
"assets": [],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "barcode",
"symbologyPath": "/record/rendererSymbology",
"allowedSymbologies": [
"code128",
"qr",
"code39",
"datamatrix",
"pdf417",
"aztec",
"ean13",
"ean8",
"upca",
"upce"
],
"valuePath": "/record/barcode",
"x": 48,
"y": 48,
"quiet": 10,
"fit": {
"widthMicrons": 93600,
"heightMicrons": 17400
},
"scannerSymbologyPath": "/record/symbology",
"aimIdentifierPath": "/record/aimIdentifier"
}
]
}
}
@@ -0,0 +1,37 @@
{
"description": "Image assets print at their pixel size; grey and partly transparent pixels are thresholded.",
"stock": {
"widthMicrons": 76200,
"heightMicrons": 50800,
"topMicrons": 0,
"rightMicrons": 0,
"bottomMicrons": 0,
"leftMicrons": 0,
"dpi": 203
},
"context": {},
"assets": [
{
"name": "logo",
"resource": "/compat-v1/assets/test-mark.png",
"mediaType": "image/png"
}
],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "image",
"x": 20,
"y": 20,
"asset": "logo"
},
{
"type": "image",
"x": 100,
"y": 60,
"asset": "logo"
}
]
}
}
@@ -0,0 +1,37 @@
{
"description": "Image assets print at their pixel size; grey and partly transparent pixels are thresholded.",
"stock": {
"widthMicrons": 76200,
"heightMicrons": 50800,
"topMicrons": 0,
"rightMicrons": 0,
"bottomMicrons": 0,
"leftMicrons": 0,
"dpi": 300
},
"context": {},
"assets": [
{
"name": "logo",
"resource": "/compat-v1/assets/test-mark.png",
"mediaType": "image/png"
}
],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "image",
"x": 30,
"y": 30,
"asset": "logo"
},
{
"type": "image",
"x": 148,
"y": 89,
"asset": "logo"
}
]
}
}
@@ -0,0 +1,37 @@
{
"description": "Image assets print at their pixel size; grey and partly transparent pixels are thresholded.",
"stock": {
"widthMicrons": 76200,
"heightMicrons": 50800,
"topMicrons": 0,
"rightMicrons": 0,
"bottomMicrons": 0,
"leftMicrons": 0,
"dpi": 600
},
"context": {},
"assets": [
{
"name": "logo",
"resource": "/compat-v1/assets/test-mark.png",
"mediaType": "image/png"
}
],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "image",
"x": 59,
"y": 59,
"asset": "logo"
},
{
"type": "image",
"x": 296,
"y": 177,
"asset": "logo"
}
]
}
}
@@ -0,0 +1,96 @@
{
"description": "rasterText layout options: default left, centre and right alignment, a threshold either side of the default, an explicit line height with LF, and automatic wrapping.",
"stock": {
"widthMicrons": 76200,
"heightMicrons": 50800,
"topMicrons": 0,
"rightMicrons": 0,
"bottomMicrons": 0,
"leftMicrons": 0,
"dpi": 203
},
"context": {
"record": {
"name": "Centre Test 0001"
}
},
"assets": [
{
"name": "mono",
"resource": "/fonts/jetbrains-mono/JetBrainsMono-Regular.ttf",
"mediaType": "font/ttf",
"fontFamily": "Test Mono",
"fontWeight": 400,
"fontStyle": "normal"
},
{
"name": "forge-echo-abel-regular",
"resource": "/fonts/Abel-Regular.ttf",
"mediaType": "font/ttf",
"fontFamily": "Abel",
"fontWeight": 400,
"fontStyle": "normal"
}
],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "rasterText",
"x": 20,
"y": 20,
"fontAsset": "mono",
"fontSize": 24,
"width": 400,
"height": 40,
"value": "LEFT TEST 0001"
},
{
"type": "rasterText",
"x": 20,
"y": 70,
"fontAsset": "mono",
"fontSize": 24,
"width": 400,
"height": 40,
"valuePath": "/record/name",
"align": "center"
},
{
"type": "rasterText",
"x": 20,
"y": 120,
"fontAsset": "mono",
"fontSize": 24,
"width": 400,
"height": 40,
"value": "RIGHT TEST 0001",
"align": "right",
"threshold": 96
},
{
"type": "rasterText",
"x": 20,
"y": 170,
"fontAsset": "forge-echo-abel-regular",
"fontSize": 28,
"width": 400,
"height": 100,
"value": "LINE ONE\nLINE TWO\nLINE THREE",
"lineHeight": 30,
"threshold": 200,
"align": "left"
},
{
"type": "rasterText",
"x": 20,
"y": 280,
"fontAsset": "forge-echo-abel-regular",
"fontSize": 24,
"width": 300,
"height": 120,
"value": "Automatic wrapping of a longer synthetic test sentence 0001"
}
]
}
}
@@ -0,0 +1,96 @@
{
"description": "rasterText layout options: default left, centre and right alignment, a threshold either side of the default, an explicit line height with LF, and automatic wrapping.",
"stock": {
"widthMicrons": 76200,
"heightMicrons": 50800,
"topMicrons": 0,
"rightMicrons": 0,
"bottomMicrons": 0,
"leftMicrons": 0,
"dpi": 300
},
"context": {
"record": {
"name": "Centre Test 0001"
}
},
"assets": [
{
"name": "mono",
"resource": "/fonts/jetbrains-mono/JetBrainsMono-Regular.ttf",
"mediaType": "font/ttf",
"fontFamily": "Test Mono",
"fontWeight": 400,
"fontStyle": "normal"
},
{
"name": "forge-echo-abel-regular",
"resource": "/fonts/Abel-Regular.ttf",
"mediaType": "font/ttf",
"fontFamily": "Abel",
"fontWeight": 400,
"fontStyle": "normal"
}
],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "rasterText",
"x": 30,
"y": 30,
"fontAsset": "mono",
"fontSize": 35,
"width": 591,
"height": 59,
"value": "LEFT TEST 0001"
},
{
"type": "rasterText",
"x": 30,
"y": 103,
"fontAsset": "mono",
"fontSize": 35,
"width": 591,
"height": 59,
"valuePath": "/record/name",
"align": "center"
},
{
"type": "rasterText",
"x": 30,
"y": 177,
"fontAsset": "mono",
"fontSize": 35,
"width": 591,
"height": 59,
"value": "RIGHT TEST 0001",
"align": "right",
"threshold": 96
},
{
"type": "rasterText",
"x": 30,
"y": 251,
"fontAsset": "forge-echo-abel-regular",
"fontSize": 41,
"width": 591,
"height": 148,
"value": "LINE ONE\nLINE TWO\nLINE THREE",
"lineHeight": 44,
"threshold": 200,
"align": "left"
},
{
"type": "rasterText",
"x": 30,
"y": 414,
"fontAsset": "forge-echo-abel-regular",
"fontSize": 35,
"width": 443,
"height": 177,
"value": "Automatic wrapping of a longer synthetic test sentence 0001"
}
]
}
}
@@ -0,0 +1,96 @@
{
"description": "rasterText layout options: default left, centre and right alignment, a threshold either side of the default, an explicit line height with LF, and automatic wrapping.",
"stock": {
"widthMicrons": 76200,
"heightMicrons": 50800,
"topMicrons": 0,
"rightMicrons": 0,
"bottomMicrons": 0,
"leftMicrons": 0,
"dpi": 600
},
"context": {
"record": {
"name": "Centre Test 0001"
}
},
"assets": [
{
"name": "mono",
"resource": "/fonts/jetbrains-mono/JetBrainsMono-Regular.ttf",
"mediaType": "font/ttf",
"fontFamily": "Test Mono",
"fontWeight": 400,
"fontStyle": "normal"
},
{
"name": "forge-echo-abel-regular",
"resource": "/fonts/Abel-Regular.ttf",
"mediaType": "font/ttf",
"fontFamily": "Abel",
"fontWeight": 400,
"fontStyle": "normal"
}
],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "rasterText",
"x": 59,
"y": 59,
"fontAsset": "mono",
"fontSize": 71,
"width": 1182,
"height": 118,
"value": "LEFT TEST 0001"
},
{
"type": "rasterText",
"x": 59,
"y": 207,
"fontAsset": "mono",
"fontSize": 71,
"width": 1182,
"height": 118,
"valuePath": "/record/name",
"align": "center"
},
{
"type": "rasterText",
"x": 59,
"y": 355,
"fontAsset": "mono",
"fontSize": 71,
"width": 1182,
"height": 118,
"value": "RIGHT TEST 0001",
"align": "right",
"threshold": 96
},
{
"type": "rasterText",
"x": 59,
"y": 502,
"fontAsset": "forge-echo-abel-regular",
"fontSize": 83,
"width": 1182,
"height": 296,
"value": "LINE ONE\nLINE TWO\nLINE THREE",
"lineHeight": 89,
"threshold": 200,
"align": "left"
},
{
"type": "rasterText",
"x": 59,
"y": 828,
"fontAsset": "forge-echo-abel-regular",
"fontSize": 71,
"width": 887,
"height": 355,
"value": "Automatic wrapping of a longer synthetic test sentence 0001"
}
]
}
}
@@ -0,0 +1,108 @@
{
"description": "rasterText quarter turns, and the bold and italic faces of one family.",
"stock": {
"widthMicrons": 76200,
"heightMicrons": 50800,
"topMicrons": 0,
"rightMicrons": 0,
"bottomMicrons": 0,
"leftMicrons": 0,
"dpi": 203
},
"context": {},
"assets": [
{
"name": "mono",
"resource": "/fonts/jetbrains-mono/JetBrainsMono-Regular.ttf",
"mediaType": "font/ttf",
"fontFamily": "Test Mono",
"fontWeight": 400,
"fontStyle": "normal"
},
{
"name": "monoBold",
"resource": "/fonts/jetbrains-mono/JetBrainsMono-Bold.ttf",
"mediaType": "font/ttf",
"fontFamily": "Test Mono",
"fontWeight": 700,
"fontStyle": "normal"
},
{
"name": "headingItalic",
"resource": "/fonts/jetbrains-mono/JetBrainsMono-Italic.ttf",
"mediaType": "font/ttf",
"fontFamily": "Test Mono",
"fontWeight": 400,
"fontStyle": "italic"
}
],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "rasterText",
"x": 20,
"y": 20,
"fontAsset": "mono",
"fontSize": 22,
"width": 200,
"height": 40,
"value": "TURN 0",
"rotation": 0
},
{
"type": "rasterText",
"x": 240,
"y": 20,
"fontAsset": "mono",
"fontSize": 22,
"width": 200,
"height": 40,
"value": "TURN 90",
"rotation": 90
},
{
"type": "rasterText",
"x": 20,
"y": 80,
"fontAsset": "mono",
"fontSize": 22,
"width": 200,
"height": 40,
"value": "TURN 180",
"rotation": 180
},
{
"type": "rasterText",
"x": 300,
"y": 20,
"fontAsset": "mono",
"fontSize": 22,
"width": 200,
"height": 40,
"value": "TURN 270",
"rotation": 270
},
{
"type": "rasterText",
"x": 20,
"y": 240,
"fontAsset": "monoBold",
"fontSize": 22,
"width": 200,
"height": 40,
"value": "BOLD 0001"
},
{
"type": "rasterText",
"x": 20,
"y": 300,
"fontAsset": "headingItalic",
"fontSize": 22,
"width": 200,
"height": 40,
"value": "ITALIC 0001"
}
]
}
}
@@ -0,0 +1,108 @@
{
"description": "rasterText quarter turns, and the bold and italic faces of one family.",
"stock": {
"widthMicrons": 76200,
"heightMicrons": 50800,
"topMicrons": 0,
"rightMicrons": 0,
"bottomMicrons": 0,
"leftMicrons": 0,
"dpi": 300
},
"context": {},
"assets": [
{
"name": "mono",
"resource": "/fonts/jetbrains-mono/JetBrainsMono-Regular.ttf",
"mediaType": "font/ttf",
"fontFamily": "Test Mono",
"fontWeight": 400,
"fontStyle": "normal"
},
{
"name": "monoBold",
"resource": "/fonts/jetbrains-mono/JetBrainsMono-Bold.ttf",
"mediaType": "font/ttf",
"fontFamily": "Test Mono",
"fontWeight": 700,
"fontStyle": "normal"
},
{
"name": "headingItalic",
"resource": "/fonts/jetbrains-mono/JetBrainsMono-Italic.ttf",
"mediaType": "font/ttf",
"fontFamily": "Test Mono",
"fontWeight": 400,
"fontStyle": "italic"
}
],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "rasterText",
"x": 30,
"y": 30,
"fontAsset": "mono",
"fontSize": 33,
"width": 296,
"height": 59,
"value": "TURN 0",
"rotation": 0
},
{
"type": "rasterText",
"x": 355,
"y": 30,
"fontAsset": "mono",
"fontSize": 33,
"width": 296,
"height": 59,
"value": "TURN 90",
"rotation": 90
},
{
"type": "rasterText",
"x": 30,
"y": 118,
"fontAsset": "mono",
"fontSize": 33,
"width": 296,
"height": 59,
"value": "TURN 180",
"rotation": 180
},
{
"type": "rasterText",
"x": 443,
"y": 30,
"fontAsset": "mono",
"fontSize": 33,
"width": 296,
"height": 59,
"value": "TURN 270",
"rotation": 270
},
{
"type": "rasterText",
"x": 30,
"y": 355,
"fontAsset": "monoBold",
"fontSize": 33,
"width": 296,
"height": 59,
"value": "BOLD 0001"
},
{
"type": "rasterText",
"x": 30,
"y": 443,
"fontAsset": "headingItalic",
"fontSize": 33,
"width": 296,
"height": 59,
"value": "ITALIC 0001"
}
]
}
}
@@ -0,0 +1,108 @@
{
"description": "rasterText quarter turns, and the bold and italic faces of one family.",
"stock": {
"widthMicrons": 76200,
"heightMicrons": 50800,
"topMicrons": 0,
"rightMicrons": 0,
"bottomMicrons": 0,
"leftMicrons": 0,
"dpi": 600
},
"context": {},
"assets": [
{
"name": "mono",
"resource": "/fonts/jetbrains-mono/JetBrainsMono-Regular.ttf",
"mediaType": "font/ttf",
"fontFamily": "Test Mono",
"fontWeight": 400,
"fontStyle": "normal"
},
{
"name": "monoBold",
"resource": "/fonts/jetbrains-mono/JetBrainsMono-Bold.ttf",
"mediaType": "font/ttf",
"fontFamily": "Test Mono",
"fontWeight": 700,
"fontStyle": "normal"
},
{
"name": "headingItalic",
"resource": "/fonts/jetbrains-mono/JetBrainsMono-Italic.ttf",
"mediaType": "font/ttf",
"fontFamily": "Test Mono",
"fontWeight": 400,
"fontStyle": "italic"
}
],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "rasterText",
"x": 59,
"y": 59,
"fontAsset": "mono",
"fontSize": 65,
"width": 591,
"height": 118,
"value": "TURN 0",
"rotation": 0
},
{
"type": "rasterText",
"x": 709,
"y": 59,
"fontAsset": "mono",
"fontSize": 65,
"width": 591,
"height": 118,
"value": "TURN 90",
"rotation": 90
},
{
"type": "rasterText",
"x": 59,
"y": 236,
"fontAsset": "mono",
"fontSize": 65,
"width": 591,
"height": 118,
"value": "TURN 180",
"rotation": 180
},
{
"type": "rasterText",
"x": 887,
"y": 59,
"fontAsset": "mono",
"fontSize": 65,
"width": 591,
"height": 118,
"value": "TURN 270",
"rotation": 270
},
{
"type": "rasterText",
"x": 59,
"y": 709,
"fontAsset": "monoBold",
"fontSize": 65,
"width": 591,
"height": 118,
"value": "BOLD 0001"
},
{
"type": "rasterText",
"x": 59,
"y": 887,
"fontAsset": "headingItalic",
"fontSize": 65,
"width": 591,
"height": 118,
"value": "ITALIC 0001"
}
]
}
}
@@ -0,0 +1,45 @@
{
"description": "renderer/BARCODE-FIT.md dynamic scanner-profile example, verbatim.",
"stock": {
"widthMicrons": 101600,
"heightMicrons": 152400,
"topMicrons": 0,
"rightMicrons": 0,
"bottomMicrons": 0,
"leftMicrons": 0,
"dpi": 203
},
"context": {
"barcode": {
"type": "qr",
"value": "TEST-0001",
"label": "QRCODE",
"aim": "]Q1"
}
},
"assets": [],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "barcode",
"symbologyPath": "/barcode/type",
"allowedSymbologies": [
"code128",
"qr",
"ean13"
],
"valuePath": "/barcode/value",
"scannerSymbologyPath": "/barcode/label",
"aimIdentifierPath": "/barcode/aim",
"x": 0,
"y": 0,
"quiet": 10,
"fit": {
"widthMicrons": 90000,
"heightMicrons": 50000
}
}
]
}
}
@@ -0,0 +1,45 @@
{
"description": "renderer/BARCODE-FIT.md dynamic scanner-profile example, verbatim.",
"stock": {
"widthMicrons": 101600,
"heightMicrons": 152400,
"topMicrons": 0,
"rightMicrons": 0,
"bottomMicrons": 0,
"leftMicrons": 0,
"dpi": 300
},
"context": {
"barcode": {
"type": "qr",
"value": "TEST-0001",
"label": "QRCODE",
"aim": "]Q1"
}
},
"assets": [],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "barcode",
"symbologyPath": "/barcode/type",
"allowedSymbologies": [
"code128",
"qr",
"ean13"
],
"valuePath": "/barcode/value",
"scannerSymbologyPath": "/barcode/label",
"aimIdentifierPath": "/barcode/aim",
"x": 0,
"y": 0,
"quiet": 10,
"fit": {
"widthMicrons": 90000,
"heightMicrons": 50000
}
}
]
}
}
@@ -0,0 +1,45 @@
{
"description": "renderer/README.md rasterText example, verbatim.",
"stock": {
"widthMicrons": 101600,
"heightMicrons": 152400,
"topMicrons": 0,
"rightMicrons": 0,
"bottomMicrons": 0,
"leftMicrons": 0,
"dpi": 203
},
"context": {
"record": {
"customerName": "Test Customer 0001"
}
},
"assets": [
{
"name": "headingItalic",
"resource": "/fonts/jetbrains-mono/JetBrainsMono-Italic.ttf",
"mediaType": "font/ttf",
"fontFamily": "Test Mono",
"fontWeight": 400,
"fontStyle": "italic"
}
],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "rasterText",
"x": 20,
"y": 250,
"fontAsset": "headingItalic",
"fontSize": 32,
"width": 500,
"height": 120,
"valuePath": "/record/customerName",
"align": "left",
"rotation": 0,
"threshold": 128
}
]
}
}
@@ -0,0 +1,45 @@
{
"description": "renderer/README.md rasterText example, verbatim.",
"stock": {
"widthMicrons": 101600,
"heightMicrons": 152400,
"topMicrons": 0,
"rightMicrons": 0,
"bottomMicrons": 0,
"leftMicrons": 0,
"dpi": 300
},
"context": {
"record": {
"customerName": "Test Customer 0001"
}
},
"assets": [
{
"name": "headingItalic",
"resource": "/fonts/jetbrains-mono/JetBrainsMono-Italic.ttf",
"mediaType": "font/ttf",
"fontFamily": "Test Mono",
"fontWeight": 400,
"fontStyle": "italic"
}
],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "rasterText",
"x": 20,
"y": 250,
"fontAsset": "headingItalic",
"fontSize": 32,
"width": 500,
"height": 120,
"valuePath": "/record/customerName",
"align": "left",
"rotation": 0,
"threshold": 128
}
]
}
}
@@ -0,0 +1,45 @@
{
"description": "renderer/README.md rasterText example, verbatim.",
"stock": {
"widthMicrons": 101600,
"heightMicrons": 152400,
"topMicrons": 0,
"rightMicrons": 0,
"bottomMicrons": 0,
"leftMicrons": 0,
"dpi": 600
},
"context": {
"record": {
"customerName": "Test Customer 0001"
}
},
"assets": [
{
"name": "headingItalic",
"resource": "/fonts/jetbrains-mono/JetBrainsMono-Italic.ttf",
"mediaType": "font/ttf",
"fontFamily": "Test Mono",
"fontWeight": 400,
"fontStyle": "italic"
}
],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "rasterText",
"x": 20,
"y": 250,
"fontAsset": "headingItalic",
"fontSize": 32,
"width": 500,
"height": 120,
"valuePath": "/record/customerName",
"align": "left",
"rotation": 0,
"threshold": 128
}
]
}
}
@@ -0,0 +1,60 @@
{
"description": "renderer/README.md ZPL authoring example, verbatim.",
"stock": {
"widthMicrons": 101600,
"heightMicrons": 152400,
"topMicrons": 0,
"rightMicrons": 0,
"bottomMicrons": 0,
"leftMicrons": 0,
"dpi": 203
},
"context": {
"order": {
"id": "ORD-TEST-0001"
}
},
"assets": [
{
"name": "logo",
"resource": "/compat-v1/assets/test-mark.png",
"mediaType": "image/png"
}
],
"scene": {
"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"
}
]
}
}
@@ -0,0 +1,60 @@
{
"description": "renderer/README.md ZPL authoring example, verbatim.",
"stock": {
"widthMicrons": 101600,
"heightMicrons": 152400,
"topMicrons": 0,
"rightMicrons": 0,
"bottomMicrons": 0,
"leftMicrons": 0,
"dpi": 300
},
"context": {
"order": {
"id": "ORD-TEST-0001"
}
},
"assets": [
{
"name": "logo",
"resource": "/compat-v1/assets/test-mark.png",
"mediaType": "image/png"
}
],
"scene": {
"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"
}
]
}
}
@@ -0,0 +1,60 @@
{
"description": "renderer/README.md ZPL authoring example, verbatim.",
"stock": {
"widthMicrons": 101600,
"heightMicrons": 152400,
"topMicrons": 0,
"rightMicrons": 0,
"bottomMicrons": 0,
"leftMicrons": 0,
"dpi": 600
},
"context": {
"order": {
"id": "ORD-TEST-0001"
}
},
"assets": [
{
"name": "logo",
"resource": "/compat-v1/assets/test-mark.png",
"mediaType": "image/png"
}
],
"scene": {
"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"
}
]
}
}
@@ -0,0 +1,81 @@
{
"description": "Printer font 0 text: literal and bound values, number and boolean scalars, and UTF-8 with characters that are ZPL command prefixes.",
"stock": {
"widthMicrons": 76200,
"heightMicrons": 50800,
"topMicrons": 0,
"rightMicrons": 0,
"bottomMicrons": 0,
"leftMicrons": 0,
"dpi": 203
},
"context": {
"order": {
"id": "ORD-TEST-0001",
"quantity": 12,
"shipped": false
}
},
"assets": [],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "text",
"x": 20,
"y": 20,
"width": 12,
"height": 24,
"value": "TEST LABEL 0001"
},
{
"type": "text",
"x": 20,
"y": 60,
"width": 10,
"height": 20,
"valuePath": "/order/id"
},
{
"type": "text",
"x": 20,
"y": 100,
"width": 8,
"height": 16,
"valuePath": "/order/quantity"
},
{
"type": "text",
"x": 20,
"y": 130,
"width": 8,
"height": 16,
"value": true
},
{
"type": "text",
"x": 20,
"y": 160,
"width": 8,
"height": 16,
"value": 3.5
},
{
"type": "text",
"x": 20,
"y": 190,
"width": 14,
"height": 28,
"value": "Prüfung € ^~_ 0001"
},
{
"type": "text",
"x": 20,
"y": 230,
"width": 8,
"height": 16,
"valuePath": "/order/shipped"
}
]
}
}
@@ -0,0 +1,81 @@
{
"description": "Printer font 0 text: literal and bound values, number and boolean scalars, and UTF-8 with characters that are ZPL command prefixes.",
"stock": {
"widthMicrons": 76200,
"heightMicrons": 50800,
"topMicrons": 0,
"rightMicrons": 0,
"bottomMicrons": 0,
"leftMicrons": 0,
"dpi": 300
},
"context": {
"order": {
"id": "ORD-TEST-0001",
"quantity": 12,
"shipped": false
}
},
"assets": [],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "text",
"x": 30,
"y": 30,
"width": 18,
"height": 35,
"value": "TEST LABEL 0001"
},
{
"type": "text",
"x": 30,
"y": 89,
"width": 15,
"height": 30,
"valuePath": "/order/id"
},
{
"type": "text",
"x": 30,
"y": 148,
"width": 12,
"height": 24,
"valuePath": "/order/quantity"
},
{
"type": "text",
"x": 30,
"y": 192,
"width": 12,
"height": 24,
"value": true
},
{
"type": "text",
"x": 30,
"y": 236,
"width": 12,
"height": 24,
"value": 3.5
},
{
"type": "text",
"x": 30,
"y": 281,
"width": 21,
"height": 41,
"value": "Prüfung € ^~_ 0001"
},
{
"type": "text",
"x": 30,
"y": 340,
"width": 12,
"height": 24,
"valuePath": "/order/shipped"
}
]
}
}
@@ -0,0 +1,81 @@
{
"description": "Printer font 0 text: literal and bound values, number and boolean scalars, and UTF-8 with characters that are ZPL command prefixes.",
"stock": {
"widthMicrons": 76200,
"heightMicrons": 50800,
"topMicrons": 0,
"rightMicrons": 0,
"bottomMicrons": 0,
"leftMicrons": 0,
"dpi": 600
},
"context": {
"order": {
"id": "ORD-TEST-0001",
"quantity": 12,
"shipped": false
}
},
"assets": [],
"scene": {
"schema": "qqq-zpl-scene/1",
"elements": [
{
"type": "text",
"x": 59,
"y": 59,
"width": 35,
"height": 71,
"value": "TEST LABEL 0001"
},
{
"type": "text",
"x": 59,
"y": 177,
"width": 30,
"height": 59,
"valuePath": "/order/id"
},
{
"type": "text",
"x": 59,
"y": 296,
"width": 24,
"height": 47,
"valuePath": "/order/quantity"
},
{
"type": "text",
"x": 59,
"y": 384,
"width": 24,
"height": 47,
"value": true
},
{
"type": "text",
"x": 59,
"y": 473,
"width": 24,
"height": 47,
"value": 3.5
},
{
"type": "text",
"x": 59,
"y": 562,
"width": 41,
"height": 83,
"value": "Prüfung € ^~_ 0001"
},
{
"type": "text",
"x": 59,
"y": 680,
"width": 24,
"height": 47,
"valuePath": "/order/shipped"
}
]
}
}
@@ -0,0 +1,11 @@
^XA^PW609^LL1218^LH0,0^CI28
^FO16,16^GFA,1080,1080,18,000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800003486E28D1BA3714DCBBDB3336674C75800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000^FS
^FO16,144^GFA,1260,1260,21,0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D00000025B55B2D655ACAB6535AA6D536A9B5A5696D0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000^FS
^FO16,272^GFA,1080,1080,18,000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000002BDA6C96B34D2E8A4522948CD692BD0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000^FS
^FO16,400^GFA,900,900,15,000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800002AB8E2E8AE3BA28E8E2A2B8EE800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000^FS
^FO16,528^GFA,2280,2280,38,00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000034E7AECD9B333676EBB10B36F44CC49C9B3666CDC97BB71468DD1B8AEF6CCCD911B1D600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000^FS
^FO16,656^GFA,975,975,15,0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A800000280000000000A00000000002800000280000000000A00000000002800000280000000000A00000000002800000280000000000A00000000002800000280000000000A000000000028000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000^FS
^FO16,784^GFA,715,715,11,00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D128000028D1A649AA15C9D12800002800000028000000280000280000002800000028000028000000280000002800002800000028000000280000280000002800000028000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000^FS
^FO16,912^GFA,975,975,15,0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D324DEA362AA112474E53A80000028D000000000A0000000013A80000028D000000000A0000000013A80000028D000000000A0000000013A80000028D000000000A0000000013A80000028D000000000A0000000013A8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000^FS
^FO16,1040^GFA,585,585,9,000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA800002B326F4EB95EA80000280000000000A80000280000000000A80000280000000000A80000280000000000A80000280000000000A800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000^FS
^XZ
@@ -0,0 +1,11 @@
^XA^PW900^LL1800^LH0,0^CI28
^FO24,24^GFA,4320,4320,36,00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000F30C03CFC0CC0F303CFCC0F3F0330F3F0CFCFF3CF0F0F0F3C3C3F30F03F33C0000000000Line truncated
^FO24,213^GFA,4920,4920,41,0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF3330F3CCCC3CF33CC333CC33CF300000000000C33CF3333CF0CF33C3333CCF0CCCF3C330F33CCCC3CF333Line truncated
^FO24,402^GFA,4200,4200,35,0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000000CCFF3CC3CF0C33CCF0F30F30CFCC0CC30330C0CC330C0F0F33CC30CCFF300000000Line truncated
^FO24,591^GFA,3600,3600,30,00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCFCC0000000000CCCCFC0FC0CFCC0CCFC0FCFCC0CC0FCC0FC0CCC0CCFC0FCLine truncated
^FO24,780^GFA,9000,9000,75,00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F30FC3FCCFCF0F3C3CF0F0F0F3C3F3CFCCFCF0300CF0F3CFF3030F0F030C3F0C3CF0F3C3C3CF0F3F0C33FCFCF3F03303CC0F3F303CFC0CCFCFF3CF0F0F0F3C30303CF03F33C00000000000F30FC3FCCFCF0F3C3CF0F0F0F3C3F3CFCCFCF0300CF0F3CFF3030F0F030C3F0C3CF0F3C3C3CF0F3F0C33FCFCF3F03303CC0F3F303CFC0CCFCFF3CF0F0F0F3C30303CF03F33C00000000000F30FC3FCCFCF0F3C3CF0F0F0F3C3F3CFCCFCF0300CF0F3CFF3030F0F030C3F0C3CF0F3C3C3CF0F3F0C33FCFCF3F03303CC0F3F303CFC0CCFCFF3CF0F0F0F3C30303CF03F33C00000000000F30FC3FCCFCF0F3C3CF0F0F0F3C3F3CFCCFCF0300CF0F3CFF3030F0F030C3F0C3CF0F3C3C3CF0F3F0C33FCFCF3F03303CC0F3F303CFC0CCFCFF3CF0F0F0F3C30303CF03F33C00000000000F30FC3FCCFCF0F3C3CF0F0F0F3C3F3CFCCFCF0300CF0F3CFF3030F0F030C3F0C3CF0F3C3C3CF0F3F0C33FCFCF3F03303CC0F3F303CFC0CCFCFF3CF0F0F0F3C30303CF03F33C00000000000F30FC3FCCFCF0F3C3CF0F0F0F3C3F3CFCCFCF0300CF0F3CFF3030F0F030C3F0C3CF0F3C3C3CF0F3F0C33FCFCF3F03303CC0F3F303CFC0CCFCFF3CF0F0F0F3C30303CF03F33C00000000000F30FC3FCCFCF0F3C3CF0F0F0F3C3F3CFCCFCF0300CF0F3CFF3030F0F030C3F0C3CF0F3C3C3CF0F3F0C33FCFCF3F03303CC0F3F303CFC0CCFCFF3CF0F0F0F3C30303CF03F33C00000000000F30FC3FCCFCF0F3C3CF0F0F0F3C3F3CFCCFCF0300CF0F3CFF3030F0F030C3F0C3CF0F3C3C3CF0F3F0C33FCFCF3F03303CC0F3F303CFC0CCFCFF3CF0F0F0F3C30303CF03F33C00000000000F30FC3FCCFCF0F3C3CF0F0F0F3C3F3CFCCFCF0300CF0F3CFF3030F0F030C3F0C3CF0F3C3C3CF0F3F0C33FCFCF3F03303CC0F3F303CFC0CCFCFF3CF0F0F0F3C30303CF03F33C00000000000F30FC3FCCFCF0F3C3CF0F0F0F3C3F3CFCCFCF0300CF0F3CFF3030F0F030C3F0C3CF0F3C3C3CF0F3F0C33FCFCF3F03303CC0F3F303CFC0CCFCFF3CF0F0F0F3C30303CF03F33C00000000000F30FC3FCCFCF0F3C3CF0F0F0F3C3F3CFCCFCF0300CF0F3CFF3030F0F030C3F0C3CF0F3C3C3CF0F3F0C33FCFCF3F03303CC0F3F303CFC0CCFCFF3CF0F0F0F3C30303CF03F33C00000000000F30FC3FCCFCF0F3C3CF0F0F0F3C3F3CFCCFCF0300CF0F3CFF3030F0F030C3F0C3CF0F3C3C3CF0F3F0C33FCFCF3F03303CC0F3F303CFC0CCFCFF3CF0F0F0F3C30303CF03F33C00000000000F30FC3FCCFCF0F3C3CF0F0F0F3C3F3CFCCFCF0300CF0F3CFF3030F0F030C3F0C3CF0F3C3C3CF0F3F0C33FCFCF3F03303CC0F3F303CFC0CCFCFF3CF0F0F0F3C30303CF03F33C00000000000F30FC3FCCFCF0F3C3Line truncated
^FO24,969^GFA,3770,3770,29,0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303FLine truncated
^FO24,1158^GFA,2860,2860,22,000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC0F303CC3C30C3CCCC0333F0C3F3030CC0000000000CC00000000000000CC00000000000000CC0000000000CC00000000000000CC00000000000000CC0000000000CC00000000000000CC00000000000000CC0000000000CC00000000000000CC00000000000000CC0000000000CC00000000000000CC00000000000000CC0000000000CC00000000000000CC00000000000000CC0000000000CC00000000000000CC00000000000000CC0000000000CC00000000000000CC00000000000000CC0000000000CC00000000000000CC00000000000000CC0000000000CC00000000000000CC00000000000000CC0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Line truncated
^FO24,1347^GFA,3770,3770,29,0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303F30FC330FCCC0000000000CC0F30F0C30F3FCCC0F3C0CCCCC03030C303Line truncated
^FO24,1536^GFA,2340,2340,18,00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CCF0F0C3CFF30FCCFC333FCCCC0000000000CC000000000000000000000CCC0000000000CC000000000000000000000CCC0000000000CC000000000000000000000CCC0000000000CC000000000000000000000CCC0000000000CC000000000000000000000CCC0000000000CC000000000000000000000CCC0000000000CC000000000000000000000CCC0000000000CC000000000000000000000CCC0000000000CC000000000000000000000CCC0000000000CC000000000000000000000CCC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000^FS
^XZ
@@ -0,0 +1,11 @@
^XA^PW1800^LL3600^LH0,0^CI28
^FO48,48^GFA,17280,17280,72,000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Line truncated
^FO48,426^GFA,19440,19440,81,00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Line truncated
^FO48,804^GFA,16560,16560,69,00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Line truncated
^FO48,1182^GFA,14400,14400,60,0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F0F0F0F0FFF000FFF000F0FFF0F000F0F0FFF000FFF0FFF0F000F0F000FFF0F000FFF000F0F0F000F0F0FFF000FFF0FFF0F000000000000000000000F0F0F0F0FFF000FFF000F0FFF0F000F0F0FFF00Line truncated
^FO48,1560^GFA,35760,35760,149,000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Line truncated
^FO48,1938^GFA,15080,15080,58,000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F0F000FF0F00FF00F00F00FF0FFFF0F0F000FF0FF000F0F0F0F0F0000F000F00F00F000FFF0F00FFF00F0F00FFF0F0F000000000000000000000F0F000FF0F00FF00F00F00FF0FFFF0F0F000FF0FF000F0F0F0F0F0000F000F00F00F000FFF0F00FFF00F0F00FFF0F0F000000000000000000000F0F000FF0F00FF00F00F00FF0FFFF0F0F000FF0FF000F0F0F0F0F0000F000F00F00F000FFF0F00FFF00F0F0Line truncated
^FO48,2316^GFA,11440,11440,44,00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F0F000FF0F000FF0F00FF00F00F00FF0F0F0F0000F0F0FFF00F00FFF0F000F00F0F000000000000000000000F0F000FF0F000FF0F00FF00F00F00FF0F0F0F0000F0F0FFF00F00FFF0F000F00F0F000000000000000000000F0F000FF0F000FF0F00FF00F00F00FF0F0F0F0000F0F0FFF00F00FFF0F000F00F0F000000000000000000000F0F000FF0F000FF0F00FF00F00F00FF0F0F0F0000F0F0FFF00F00FFF0F000F00F0F000000000000000000000F0F000FF0F000FF0F00FF00F00F00FF0F0F0F0000F0F0FFF00F00FFF0F000F00F0F000000000000000000000F0F000FF0F000FF0F00FF00F00F00FF0F0F0F0000F0F0FFF00F00FFF0F000F00F0F000000000000000000000F0F000FF0F000FF0F00FF00F00F00FF0F0F0F0000F0F0FFF00F00FFF0F000F00F0F000000000000000000000F0F000FF0F000FF0F00FF00F00F00FF0F0F0F0000F0F0FFF00F00FFF0F000F00F0F000000000000000000000F0F000FF0F000FF0F00FF00F00F00FF0F0F0F0000F0F0FFF00F00FFF0F000F00F0F000000000000000000000F0F000FF0F000FF0F00FF00F00F00FF0F0F0F0000F0F0FFF00F00FFF0F000F00F0F000000000000000000000F0F000FF0F000FF0F00FF00F00F00FF0F0F0F0000F0F0FFF00F00FFF0F000F00F0F000000000000000000000F0F000FF0F000FF0F00FF00F00F00FF0F0F0F0000F0F0FFF00F00FFF0F000F00F0F000000000000000000000F0F000FF0F000FF0F00FF00F00F00FF0F0F0F0000F0F0FFF00F00FFF0F000F00F0F000000000000000000000F0F000FF0F000FF0F00FF00F00F00FF0F0F0F0000F0F0FFF00F00FFF0F000F00F0F000000000000000000000F0F000FF0F000FF0F00FF00F00F00FF0F0F0F0000F0F0FFF00F00FFF0F000F00F0F000000000000000000000F0F000FF0F000FF0F00FF00F00F00FF0F0F0F0000F0F0FFF00F00FFF0F000F00F0F000000000000000000000F0F000FF0F000FF0F00FF00F00F00FFLine truncated
^FO48,2694^GFA,15080,15080,58,000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F0F000FF0F00FF00F00F00FF0FFFF0F0F000FF0FF000F0F0F0F0F0000F000F00F00F000FFF0F00FFF00F0F00FFF0F0F000000000000000000000F0F000FF0F00FF00F00F00FF0FFFF0F0F000FF0FF000F0F0F0F0F0000F000F00F00F000FFF0F00FFF00F0F00FFF0F0F000000000000000000000F0F000FF0F00FF00F00F00FF0FFFF0F0F000FF0FF000F0F0F0F0F0000F000F00F00F000FFF0F00FFF00F0F0Line truncated
^FO48,3072^GFA,9360,9360,36,0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F000000000000000000000F0F0FF00FF00F00FF0FFFF0F00FFF0F0FFF00F0F0FFFF0F0F0F00000000000000Line truncated
^XZ
@@ -0,0 +1,6 @@
^XA^PW609^LL1218^LH0,0^CI28
^FO16,16^GFA,1740,1740,15,0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FFFFFFF0F0F000FFFFFFF000000000FFFFFFF0F0F000FFFFFFF000000000FFFFFFF0F0F000FFFFFFF000000000FFFFFFF0F0F000FFFFFFF000000000F00000F00F0FF0F00000F000000000F00000F00F0FF0F00000F000000000F00000F00F0FF0F00000F000000000F00000F00F0FF0F00000F000000000F0FFF0F00F00F0F0FFF0F000000000F0FFF0F00F00F0F0FFF0F000000000F0FFF0F00F00F0F0FFF0F000000000F0FFF0F00F00F0F0FFF0F000000000F0FFF0F0F000F0F0FFF0F000000000F0FFF0F0F000F0F0FFF0F000000000F0FFF0F0F000F0F0FFF0F000000000F0FFF0F0F000F0F0FFF0F000000000F0FFF0F00F0F00F0FFF0F000000000F0FFF0F00F0F00F0FFF0F000000000F0FFF0F00F0F00F0FFF0F000000000F0FFF0F00F0F00F0FFF0F000000000F00000F000FF00F00000F000000000F00000F000FF00F00000F000000000F00000F000FF00F00000F000000000F00000F000FF00F00000F000000000FFFFFFF0F0F0F0FFFFFFF000000000FFFFFFF0F0F0F0FFFFFFF000000000FFFFFFF0F0F0F0FFFFFFF000000000FFFFFFF0F0F0F0FFFFFFF000000000000000000FF0F00000000000000000000000000FF0F00000000000000000000000000FF0F00000000000000000000000000FF0F0000000000000000000F0FFF0F0F0FF000F00F00000000000F0FFF0F0F0FF000F00F00000000000F0FFF0F0F0FF000F00F00000000000F0FFF0F0F0FF000F00F0000000000F0FF000F00F0FFF0FF0F0000000000F0FF000F00F0FFF0FF0F0000000000F0FF000F00F0FFF0FF0F0000000000F0FF000F00F0FFF0FF0F0000000000F00FFF0F00F0000F00FF0000000000F00FFF0F00F0000F00FF0000000000F00FFF0F00F0000F00FF0000000000F00FFF0F00F0000F00FF000000000F00F0000FFFFFFFF0F0FF000000000F00F0000FFFFFFFF0F0FF000000000F00F0000FFFFFFFF0F0FF000000000F00F0000FFFFFFFF0F0FF000000000FFFFF0FFFF0F00F0FFF0F000000000FFFFF0FFFF0F00F0FFF0F000000000FFFFF0FFFF0F00F0FFF0F000000000FFFFF0FFFF0F00F0FFF0F00000000000000000FF000F0FFF00000000000000000000FF000F0FFF00000000000000000000FF000F0FFF00000000000000000000FF000F0FFF000000000000FFFFFFF00F00FFF0FF0F0000000000FFFFFFF00F00FFF0FF0F0000000000FFFFFFF00F00FFF0FF0F0000000000FFFFFFF00F00FFF0FF0F0000000000F00000F0FF0F000F00FF0000000000F00000F0FF0F000F00FF0000000000F00000F0FF0F000F00FF0000000000F00000F0FF0F000F00FF0000000000F0FFF0F0F0F0FFF0F0FFF000000000F0FFF0F0F0F0FFF0F0FFF000000000F0FFF0F0F0F0FFF0F0FFF000000000F0FFF0F0F0F0FFF0F0FFF000000000F0FFF0F00FF000FF00FF0000000000F0FFF0F00FF000FF00FF0000000000F0FFF0F00FF000FF00FF0000000000F0FFF0F00FF000FF00FF0000000000F0FFF0F0F0F0FF00FFF0F000000000F0FFF0F0F0F0FF00FFF0F000000000F0FFF0F0F0F0FF00FFF0F000000000F0FFF0F0F0F0FF00FFF0F000000000F00000F000F00FFF000FF000000000F00000F000F00FFF000FF000000000F00000F000F00FFF000FF000000000F00000F000F00FFF000FF000000000FFFFFFF00FFFFF00F0F0F000000000FFFFFFF00FFFFF00F0F0F000000000FFFFFFF00FFFFF00F0F0F000000000FFFFFFF00FFFFF00F0F0F00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000^FS
^FO16,160^GFA,1540,1540,14,0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F83E0F83E0F83E0F800000000000F83E0F83E0F83E0F800000000000F83E0F83E0F83E0F800000000000F83E0F83E0F83E0F800000000000F83E0F83E0F83E0F800000000000F83FF0001F0000007C0000000000F83FF0001F0000007C0000000000F83FF0001F0000007C0000000000F83FF0001F0000007C0000000000F83FF0001F0000007C0000000000F8000003E0FFFE00000000000000F8000003E0FFFE00000000000000F8000003E0FFFE00000000000000F8000003E0FFFE00000000000000F8000003E0FFFE00000000000000FFFE0F83E0003FF07C0000000000FFFE0F83E0003FF07C0000000000FFFE0F83E0003FF07C0000000000FFFE0F83E0003FF07C0000000000FFFE0F83E0003FF07C0000000000F83E0FFC000001FF800000000000F83E0FFC000001FF800000000000F83E0FFC000001FF800000000000F83E0FFC000001FF800000000000F83E0FFC000001FF800000000000FFC0007C00FFC0007C0000000000FFC0007C00FFC0007C0000000000FFC0007C00FFC0007C0000000000FFC0007C00FFC0007C0000000000FFC0007C00FFC0007C0000000000F801F003FF000000000000000000F801F003FF000000000000000000F801F003FF000000000000000000F801F003FF000000000000000000F801F003FF000000000000000000F801FF83E0FFC00FFC0000000000F801FF83E0FFC00FFC0000000000F801FF83E0FFC00FFC0000000000F801FF83E0FFC00FFC0000000000F801FF83E0FFC00FFC0000000000FFFFFFFC1F07FE0F800000000000FFFFFFFC1F07FE0F800000000000FFFFFFFC1F07FE0F800000000000FFFFFFFC1F07FE0F800000000000FFFFFFFC1F07FE0F800000000000F83FFFFFFF07FFF07C0000000000F83FFFFFFF07FFF07C0000000000F83FFFFFFF07FFF07C0000000000F83FFFFFFF07FFF07C0000000000F83FFFFFFF07FFF07C0000000000FFC1F07C1FF8000F800000000000FFC1F07C1FF8000F800000000000FFC1F07C1FF8000F800000000000FFC1F07C1FF8000F800000000000FFC1F07C1FF8000F800000000000FFFFFFFC00FFC0007C0000000000FFFFFFFC00FFC0007C0000000000FFFFFFFC00FFC0007C0000000000FFFFFFFC00FFC0007C0000000000FFFFFFFC00FFC0007C0000000000FFC0007FE0F801F0000000000000FFC0007FE0F801F0000000000000FFC0007FE0F801F0000000000000FFC0007FE0F801F0000000000000FFC0007FE0F801F0000000000000FFFFFFFFFFFFFFFFFC0000000000FFFFFFFFFFFFFFFFFC0000000000FFFFFFFFFFFFFFFFFC0000000000FFFFFFFFFFFFFFFFFC0000000000FFFFFFFFFFFFFFFFFC0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000^FS
^FO16,304^GFA,1725,1725,15,00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003E007FFF003E0000000000000000003E007FFF003E0000000000000000003E007FFF003E0000000000000000003E007FFF003E0000000000000000003E007FFF003E0000000000000000F83E007FFFFFFE0FFC000000000000F83E007FFFFFFE0FFC000000000000F83E007FFFFFFE0FFC000000000000F83E007FFFFFFE0FFC000000000000F83E007FFFFFFE0FFC000000000000F83FF00000F8000FFC000000000000F83FF00000F8000FFC000000000000F83FF00000F8000FFC000000000000F83FF00000F8000FFC000000000000F83FF00000F8000FFC000000000000F83FFFFFFFFFFFFFFFE00000000000F83FFFFFFFFFFFFFFFE00000000000F83FFFFFFFFFFFFFFFE00000000000F83FFFFFFFFFFFFFFFE00000000000F83FFFFFFFFFFFFFFFE0000000000007C1F000000001F003E0000000000007C1F000000001F003E0000000000007C1F000000001F003E0000000000007C1F000000001F003E0000000000007C1F000000001F003E00000000000F801F07FFFFFC1F07C000000000000F801F07FFFFFC1F07C000000000000F801F07FFFFFC1F07C000000000000F801F07FFFFFC1F07C000000000000F801F07FFFFFC1F07C000000000000F801F07C0007C1FF83E00000000000F801F07C0007C1FF83E00000000000F801F07C0007C1FF83E00000000000F801F07C0007C1FF83E00000000000F801F07C0007C1FF83E00000000000F83FF07C1F07C1FFFFE00000000000F83FF07C1F07C1FFFFE00000000000F83FF07C1F07C1FFFFE00000000000F83FF07C1F07C1FFFFE00000000000F83FF07C1F07C1FFFFE0000000000007FFF07C0007C1FF8000000000000007FFF07C0007C1FF8000000000000007FFF07C0007C1FF8000000000000007FFF07C0007C1FF8000000000000007FFF07C0007C1FF8000000000000007FFF07FFFFFC1F003E0000000000007FFF07FFFFFC1F003E0000000000007FFF07FFFFFC1F003E0000000000007FFF07FFFFFC1F003E0000000000007FFF07FFFFFC1F003E0000000000007C1F000000001FFFFE0000000000007C1F000000001FFFFE0000000000007C1F000000001FFFFE0000000000007C1F000000001FFFFE0000000000007C1F000000001FFFFE000000000000001FFFFFFFFFFFF800000000000000001FFFFFFFFFFFF800000000000000001FFFFFFFFFFFF800000000000000001FFFFFFFFFFFF800000000000000001FFFFFFFFFFFF800000000000000000007C1F003E00000000000000000000007C1F003E00000000000000000000007C1F003E00000000000000000000007C1F003E00000000000000000000007C1F003E0000000000000000F801FF83E0F83E0FFFE00000000000F801FF83E0F83E0FFFE00000000000F801FF83E0F83E0FFFE00000000000F801FF83E0F83E0FFFE00000000000F801FF83E0F83E0FFFE00000000000003FFF80000000007FE00000000000003FFF80000000007FE00000000000003FFF80000000007FE00000000000003FFF80000000007FE00000000000003FFF80000000007FE000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000^FS
^FO16,448^GFA,1624,1624,28,000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FFFF33303FF333FF0F3303C003CFC0FC3CFF333FC03FFF30330C0000FFFF33303FF333FF0F3303C003CFC0FC3CFF333FC03FFF30330C0000FFFF33303FF333FF0F3303C003CFC0FC3CFF333FC03FFF30330C0000FFFF33303FF333FF0F3303C003CFC0FC3CFF333FC03FFF30330C0000FFFF33303FF333FF0F3303C003CFC0FC3CFF333FC03FFF30330C0000FFFF33303FF333FF0F3303C003CFC0FC3CFF333FC03FFF30330C0000FFFF33303FF3303C0C3C33FFC30FF303FCFF330C003FFF30330C0000FFFF33303FF3303C0C3C33FFC30FF303FCFF330C003FFF30330C0000FFFF33303FF3303C0C3C33FFC30FF303FCFF330C003FFF30330C0000FFFF33303FF3303C0C3C33FFC30FF303FCFF330C003FFF30330C0000FFFF33303FF3303C0C3C33FFC30FF303FCFF330C003FFF30330C0000FFFF33303FF3303C0C3C33FFC30FF303FCFF330C003FFF30330C0000FFFF33303F333FFC0F333FF003F333FFC0F3303FF03FFF30330C0000FFFF33303F333FFC0F333FF003F333FFC0F3303FF03FFF30330C0000FFFF33303F333FFC0F333FF003F333FFC0F3303FF03FFF30330C0000FFFF33303F333FFC0F333FF003F333FFC0F3303FF03FFF30330C0000FFFF33303F333FFC0F333FF003F333FFC0F3303FF03FFF30330C0000FFFF33303F333FFC0F333FF003F333FFC0F3303FF03FFF30330C0000FFFF33303CCFF0FFCF30F0FC030F00F300CCFF0FF03FFF30330C0000FFFF33303CCFF0FFCF30F0FC030F00F300CCFF0FF03FFF30330C0000FFFF33303CCFF0FFCF30F0FC030F00F300CCFF0FF03FFF30330C0000FFFF33303CCFF0FFCF30F0FC030F00F300CCFF0FF03FFF30330C0000FFFF33303CCFF0FFCF30F0FC030F00F300CCFF0FF03FFF30330C0000FFFF33303CCFF0FFCF30F0FC030F00F300CCFF0FF03FFF30330C0000FFFF33303CCFC0300FFC03C333FC30F0F0FF33F0FC3FFF30330C0000FFFF33303CCFC0300FFC03C333FC30F0F0FF33F0FC3FFF30330C0000FFFF33303CCFC0300FFC03C333FC30F0F0FF33F0FC3FFF30330C0000FFFF33303CCFC0300FFC03C333FC30F0F0FF33F0FC3FFF30330C0000FFFF33303CCFC0300FFC03C333FC30F0F0FF33F0FC3FFF30330C0000FFFF33303CCFC0300FFC03C333FC30F0F0FF33F0FC3FFF30330C0000FFFF33303FCCFF300CFC00C0F300FCF0FCFF33FC033FFF30330C0000FFFF33303FCCFF300CFC00C0F300FCF0FCFF33FC033FFF30330C0000FFFF33303FCCFF300CFC00C0F300FCF0FCFF33FC033FFF30330C0000FFFF33303FCCFF300CFC00C0F300FCF0FCFF33FC033FFF30330C0000FFFF33303FCCFF300CFC00C0F300FCF0FCFF33FC033FFF30330C0000FFFF33303FCCFF300CFC00C0F300FCF0FCFF33FC033FFF30330C0000FFFF33303F30FCFFCF00F03303C3CF3C00F30FCFF03FFF30330C0000FFFF33303F30FCFFCF00F03303C3CF3C00F30FCFF03FFF30330C0000FFFF33303F30FCFFCF00F03303C3CF3C00F30FCFF03FFF30330C0000FFFF33303F30FCFFCF00F03303C3CF3C00F30FCFF03FFF30330C0000FFFF33303F30FCFFCF00F03303C3CF3C00F30FCFF03FFF30330C0000FFFF33303F30FCFFCF00F03303C3CF3C00F30FCFF03FFF30330C000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000^FS
^XZ
@@ -0,0 +1,6 @@
^XA^PW900^LL1800^LH0,0^CI28
^FO24,24^GFA,3828,3828,22,000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FFFFFFFFFFC0FC0FC0000FFFFFFFFFFC000000000000FFFFFFFFFFC0FC0FC0000FFFFFFFFFFC000000000000FFFFFFFFFFC0FC0FC0000FFFFFFFFFFC000000000000FFFFFFFFFFC0FC0FC0000FFFFFFFFFFC000000000000FFFFFFFFFFC0FC0FC0000FFFFFFFFFFC000000000000FFFFFFFFFFC0FC0FC0000FFFFFFFFFFC000000000000FC0000000FC003F03FFC0FC0000000FC000000000000FC0000000FC003F03FFC0FC0000000FC000000000000FC0000000FC003F03FFC0FC0000000FC000000000000FC0000000FC003F03FFC0FC0000000FC000000000000FC0000000FC003F03FFC0FC0000000FC000000000000FC0000000FC003F03FFC0FC0000000FC000000000000FC0FFFFC0FC003F000FC0FC0FFFFC0FC000000000000FC0FFFFC0FC003F000FC0FC0FFFFC0FC000000000000FC0FFFFC0FC003F000FC0FC0FFFFC0FC000000000000FC0FFFFC0FC003F000FC0FC0FFFFC0FC000000000000FC0FFFFC0FC003F000FC0FC0FFFFC0FC000000000000FC0FFFFC0FC003F000FC0FC0FFFFC0FC000000000000FC0FFFFC0FC0FC0000FC0FC0FFFFC0FC000000000000FC0FFFFC0FC0FC0000FC0FC0FFFFC0FC000000000000FC0FFFFC0FC0FC0000FC0FC0FFFFC0FC000000000000FC0FFFFC0FC0FC0000FC0FC0FFFFC0FC000000000000FC0FFFFC0FC0FC0000FC0FC0FFFFC0FC000000000000FC0FFFFC0FC0FC0000FC0FC0FFFFC0FC000000000000FC0FFFFC0FC003F03F000FC0FFFFC0FC000000000000FC0FFFFC0FC003F03F000FC0FFFFC0FC000000000000FC0FFFFC0FC003F03F000FC0FFFFC0FC000000000000FC0FFFFC0FC003F03F000FC0FFFFC0FC000000000000FC0FFFFC0FC003F03F000FC0FFFFC0FC000000000000FC0FFFFC0FC003F03F000FC0FFFFC0FC000000000000FC0000000FC0000FFF000FC0000000FC000000000000FC0000000FC0000FFF000FC0000000FC000000000000FC0000000FC0000FFF000FC0000000FC000000000000FC0000000FC0000FFF000FC0000000FC000000000000FC0000000FC0000FFF000FC0000000FC000000000000FC0000000FC0000FFF000FC0000000FC000000000000FFFFFFFFFFC0FC0FC0FC0FFFFFFFFFFC000000000000FFFFFFFFFFC0FC0FC0FC0FFFFFFFFFFC000000000000FFFFFFFFFFC0FC0FC0FC0FFFFFFFFFFC000000000000FFFFFFFFFFC0FC0FC0FC0FFFFFFFFFFC000000000000FFFFFFFFFFC0FC0FC0FC0FFFFFFFFFFC000000000000FFFFFFFFFFC0FC0FC0FC0FFFFFFFFFFC00000000000000000000000003FFC0FC00000000000000000000000000000000000003FFC0FC00000000000000000000000000000000000003FFC0FC00000000000000000000000000000000000003FFC0FC00000000000000000000000000000000000003FFC0FC00000000000000000000000000000000000003FFC0FC000000000000000000000000000FC0FFFFC0FC0FC0FFF00003F000FC000000000000000FC0FFFFC0FC0FC0FFF00003F000FC000000000000000FC0FFFFC0FC0FC0FFF00003F000FC000000000000000FC0FFFFC0FC0FC0FFF00003F000FC000000000000000FC0FFFFC0FC0FC0FFF00003F000FC000000000000000FC0FFFFC0FC0FC0FFF00003F000FC00000000000003F03FFC0000FC003F03FFFF03FFC0FC00000000000003F03FFC0000FC003F03FFFF03FFC0FC00000000000003F03FFC0000FC003F03FFFF03FFC0FC00000000000003F03FFC0000FC003F03FFFF03FFC0FC00000000000003F03FFC0000FC003F03FFFF03FFC0FC00000000000003F03FFC0000FC003F03FFFF03FFC0FC00000000000003F000FFFFC0FC003F000000FC003FFC00000000000003F000FFFFC0FC003F000000FC003FFC00000000000003F000FFFFC0FC003F000000FC003FFC00000000000003F000FFFFC0FC003F000000FC003FFC00000000000003F000FFFFC0FC003F000000FC003FFC00000000000003F000FFFFC0FC003F000000FC003FFC000000000000FC003F000000FFFFFFFFFFFF03F03FFC000000000000FC003F000000FFFFFFFFFFFF03F03FFC000000000000FC003F000000FFFFFFFFFFFF03F03FFC000000000000FC003F000000FFFFFFFFFFFF03F03FFC000000000000FC003F000000FFFFFFFFFFFF03F03FFC000000000000FC003F000000FFFFFFFFFFFF03F03FFC000000000000FFFFFFFC0FFFFFF03F000FC0FFFFC0FC000000000000FFFFFFFC0FFFFFF03F000FC0FFFFC0FC000000000000FFFFFFFC0FFFFFF03F000FC0FFFFC0FC000000000000FFFFFFFC0FFFFFF03F000FC0FFFFC0FC000000000000FFFFFFFC0FFFFFF03F000FC0FFFFC0FC000000000000FFFFFFFC0FFFFFF03F000FC0FFFFC0FC000000000000000000000000FFF00003F03FFFF00000000000000000000000000000FFF00003F03FFFF00000000000000000000000000000FFF00003F03FFFF00000000000000000000000000000FFF00003F03FFFF00000000000000000000000000000FFF00003F03FFFF00000000000000000000000000000FFF00003F03FFFF00000000000000000FFFFFFFFFFC003F000FFFFC0FFF03F00000000000000FFFFFFFFFFC003F000FFFFC0FFF03F00000000000000FFFFFFFFFFC003F000FFFFC0FFF03F00000000000000FFFFFFFFFFC003F000FFFFC0FFF03F00000000000000FFFFFFFFFFC003F000FFFFC0FFF03F000000000Line truncated
^FO24,237^GFA,3872,3872,22,000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FF00FF00FF00FF00FF00FF00FF000000000000000000FF00FF00FF00FF00FF00FF00FF000000000000000000FF00FF00FF00FF00FF00FF00FF000000000000000000FF00FF00FF00FF00FF00FF00FF000000000000000000FF00FF00FF00FF00FF00FF00FF000000000000000000FF00FF00FF00FF00FF00FF00FF000000000000000000FF00FF00FF00FF00FF00FF00FF000000000000000000FF00FF00FF00FF00FF00FF00FF000000000000000000FF00FFFF000000FF0000000000FF0000000000000000FF00FFFF000000FF0000000000FF0000000000000000FF00FFFF000000FF0000000000FF0000000000000000FF00FFFF000000FF0000000000FF0000000000000000FF00FFFF000000FF0000000000FF0000000000000000FF00FFFF000000FF0000000000FF0000000000000000FF00FFFF000000FF0000000000FF0000000000000000FF00FFFF000000FF0000000000FF0000000000000000FF0000000000FF00FFFFFF0000000000000000000000FF0000000000FF00FFFFFF0000000000000000000000FF0000000000FF00FFFFFF0000000000000000000000FF0000000000FF00FFFFFF0000000000000000000000FF0000000000FF00FFFFFF0000000000000000000000FF0000000000FF00FFFFFF0000000000000000000000FF0000000000FF00FFFFFF0000000000000000000000FF0000000000FF00FFFFFF0000000000000000000000FFFFFF00FF00FF000000FFFF00FF0000000000000000FFFFFF00FF00FF000000FFFF00FF0000000000000000FFFFFF00FF00FF000000FFFF00FF0000000000000000FFFFFF00FF00FF000000FFFF00FF0000000000000000FFFFFF00FF00FF000000FFFF00FF0000000000000000FFFFFF00FF00FF000000FFFF00FF0000000000000000FFFFFF00FF00FF000000FFFF00FF0000000000000000FFFFFF00FF00FF000000FFFF00FF0000000000000000FF00FF00FFFF0000000000FFFF000000000000000000FF00FF00FFFF0000000000FFFF000000000000000000FF00FF00FFFF0000000000FFFF000000000000000000FF00FF00FFFF0000000000FFFF000000000000000000FF00FF00FFFF0000000000FFFF000000000000000000FF00FF00FFFF0000000000FFFF000000000000000000FF00FF00FFFF0000000000FFFF000000000000000000FF00FF00FFFF0000000000FFFF000000000000000000FFFF000000FF0000FFFF000000FF0000000000000000FFFF000000FF0000FFFF000000FF0000000000000000FFFF000000FF0000FFFF000000FF0000000000000000FFFF000000FF0000FFFF000000FF0000000000000000FFFF000000FF0000FFFF000000FF0000000000000000FFFF000000FF0000FFFF000000FF0000000000000000FFFF000000FF0000FFFF000000FF0000000000000000FFFF000000FF0000FFFF000000FF0000000000000000FF0000FF0000FFFF0000000000000000000000000000FF0000FF0000FFFF0000000000000000000000000000FF0000FF0000FFFF0000000000000000000000000000FF0000FF0000FFFF0000000000000000000000000000FF0000FF0000FFFF0000000000000000000000000000FF0000FF0000FFFF0000000000000000000000000000FF0000FF0000FFFF0000000000000000000000000000FF0000FF0000FFFF0000000000000000000000000000FF0000FFFF00FF00FFFF0000FFFF0000000000000000FF0000FFFF00FF00FFFF0000FFFF0000000000000000FF0000FFFF00FF00FFFF0000FFFF0000000000000000FF0000FFFF00FF00FFFF0000FFFF0000000000000000FF0000FFFF00FF00FFFF0000FFFF0000000000000000FF0000FFFF00FF00FFFF0000FFFF0000000000000000FF0000FFFF00FF00FFFF0000FFFF0000000000000000FF0000FFFF00FF00FFFF0000FFFF0000000000000000FFFFFFFFFFFF00FF00FFFF00FF000000000000000000FFFFFFFFFFFF00FF00FFFF00FF000000000000000000FFFFFFFFFFFF00FF00FFFF00FF000000000000000000FFFFFFFFFFFF00FF00FFFF00FF000000000000000000FFFFFFFFFFFF00FF00FFFF00FF000000000000000000FFFFFFFFFFFF00FF00FFFF00FF000000000000000000FFFFFFFFFFFF00FF00FFFF00FF000000000000000000FFFFFFFFFFFF00FF00FFFF00FF000000000000000000FF00FFFFFFFFFFFF00FFFFFF00FF0000000000000000FF00FFFFFFFFFFFF00FFFFFF00FF0000000000000000FF00FFFFFFFFFFFF00FFFFFF00FF0000000000000000FF00FFFFFFFFFFFF00FFFFFF00FF0000000000000000FF00FFFFFFFFFFFF00FFFFFF00FF0000000000000000FF00FFFFFFFFFFFF00FFFFFF00FF0000000000000000FF00FFFFFFFFFFFF00FFFFFF00FF0000000000000000FF00FFFFFFFFFFFF00FFFFFF00FF0000000000000000FFFF00FF00FF00FFFF000000FF0000000000Line truncated
^FO24,450^GFA,3381,3381,21,00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003F8001FFFFF0003F800000000000000000000000003F8001FFFFF0003F800000000000000000000000003F8001FFFFF0003F800000000000000000000000003F8001FFFFF0003F800000000000000000000000003F8001FFFFF0003F800000000000000000000000003F8001FFFFF0003F800000000000000000000000003F8001FFFFF0003F80000000000000000000000FE03F8001FFFFFFFFFF80FFFC00000000000000000FE03F8001FFFFFFFFFF80FFFC00000000000000000FE03F8001FFFFFFFFFF80FFFC00000000000000000FE03F8001FFFFFFFFFF80FFFC00000000000000000FE03F8001FFFFFFFFFF80FFFC00000000000000000FE03F8001FFFFFFFFFF80FFFC00000000000000000FE03F8001FFFFFFFFFF80FFFC00000000000000000FE03FFF0000000FE00000FFFC00000000000000000FE03FFF0000000FE00000FFFC00000000000000000FE03FFF0000000FE00000FFFC00000000000000000FE03FFF0000000FE00000FFFC00000000000000000FE03FFF0000000FE00000FFFC00000000000000000FE03FFF0000000FE00000FFFC00000000000000000FE03FFF0000000FE00000FFFC00000000000000000FE03FFFFFFFFFFFFFFFFFFFFFF8000000000000000FE03FFFFFFFFFFFFFFFFFFFFFF8000000000000000FE03FFFFFFFFFFFFFFFFFFFFFF8000000000000000FE03FFFFFFFFFFFFFFFFFFFFFF8000000000000000FE03FFFFFFFFFFFFFFFFFFFFFF8000000000000000FE03FFFFFFFFFFFFFFFFFFFFFF8000000000000000FE03FFFFFFFFFFFFFFFFFFFFFF800000000000000001FC07F0000000000007F0003F800000000000000001FC07F0000000000007F0003F800000000000000001FC07F0000000000007F0003F800000000000000001FC07F0000000000007F0003F800000000000000001FC07F0000000000007F0003F800000000000000001FC07F0000000000007F0003F800000000000000001FC07F0000000000007F0003F8000000000000000FE0007F01FFFFFFFFC07F01FC00000000000000000FE0007F01FFFFFFFFC07F01FC00000000000000000FE0007F01FFFFFFFFC07F01FC00000000000000000FE0007F01FFFFFFFFC07F01FC00000000000000000FE0007F01FFFFFFFFC07F01FC00000000000000000FE0007F01FFFFFFFFC07F01FC00000000000000000FE0007F01FFFFFFFFC07F01FC00000000000000000FE0007F01FC00001FC07FFE03F8000000000000000FE0007F01FC00001FC07FFE03F8000000000000000FE0007F01FC00001FC07FFE03F8000000000000000FE0007F01FC00001FC07FFE03F8000000000000000FE0007F01FC00001FC07FFE03F8000000000000000FE0007F01FC00001FC07FFE03F8000000000000000FE0007F01FC00001FC07FFE03F8000000000000000FE03FFF01FC07F01FC07FFFFFF8000000000000000FE03FFF01FC07F01FC07FFFFFF8000000000000000FE03FFF01FC07F01FC07FFFFFF8000000000000000FE03FFF01FC07F01FC07FFFFFF8000000000000000FE03FFF01FC07F01FC07FFFFFF8000000000000000FE03FFF01FC07F01FC07FFFFFF8000000000000000FE03FFF01FC07F01FC07FFFFFF800000000000000001FFFFF01FC00001FC07FFE000000000000000000001FFFFF01FC00001FC07FFE000000000000000000001FFFFF01FC00001FC07FFE000000000000000000001FFFFF01FC00001FC07FFE000000000000000000001FFFFF01FC00001FC07FFE000000000000000000001FFFFF01FC00001FC07FFE000000000000000000001FFFFF01FC00001FC07FFE000000000000000000001FFFFF01FFFFFFFFC07F0003F800000000000000001FFFFF01FFFFFFFFC07F0003F800000000000000001FFFFF01FFFFFFFFC07F0003F800000000000000001FFFFF01FFFFFFFFC07F0003F800000000000000001FFFFF01FFFFFFFFC07F0003F800000000000000001FFFFF01FFFFFFFFC07F0003F800000000000000001FFFFF01FFFFFFFFC07F0003F800000000000000001FC07F0000000000007FFFFFF800000000000000001FC07F0000000000007FFFFFF800000000000000001FC07F0000000000007FFFFFF800000000000000001FC07F0000000000007FFFFFF800000000000000001FC07F0000000000007FFFFFF800000000000000001FC07F0000000000007FFFFFF800000000000000001FC07F0000000000007FFFFFF8000000000000000000007FFFFFFFFFFFFFFFFE0000000000000000000000007FFFFFFFFFFFFFFFFE0000000000000000000000007FFFFFFFFFFFFFFFFE0000000000000000000000007FFFFFFFFFFFFFFFFE0000000000000000000000007FFFFFFFFFFFFFFFFE0000000000000000000000007FFFFFFFFFFFFFFFFE0000000000000000000000007FFFFFFFFFFFFFFFFE0000000000000000000000000001FC07F0003F80000000000000000000000000000001FC07F0003F80000000000000000000000000000001FC07F0003F80000000000000000000000000000001FC07F0003F80000000000000000000000000000001FC07F0003F80000000000000000000000000000001FC07F0003F80000000000000000000000000000001Line truncated
^FO24,663^GFA,6496,6496,56,00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FFFFFFFF0F0F0F000FFFFF0F0F0FFFFF00FF0F0F000FF000000FF0FFF000FFF00FF0FFFF0F0F0FFFF0000FFFFFFF0F000F0F00F000000000FFFFFFFF0F0F0F000FFFFF0F0F0FFFFF00FF0F0F000FF000000FF0FFF000FFF00FF0FFFF0F0F0FFFF0000FFFFFFF0F000F0F00F000000000FFFFFFFF0F0F0F000FFFFF0F0F0FFFFF00FF0F0F000FF000000FF0FFF000FFF00FF0FFFF0F0F0FFFF0000FFFFFFF0F000F0F00F000000000FFFFFFFF0F0F0F000FFFFF0F0F0FFFFF00FF0F0F000FF000000FF0FFF000FFF00FF0FFFF0F0F0FFFF0000FFFFFFF0F000F0F00F000000000FFFFFFFF0F0F0F000FFFFF0F0F0FFFFF00FF0F0F000FF000000FF0FFF000FFF00FF0FFFF0F0F0FFFF0000FFFFFFF0F000F0F00F000000000FFFFFFFF0F0F0F000FFFFF0F0F0FFFFF00FF0F0F000FF000000FF0FFF000FFF00FF0FFFF0F0F0FFFF0000FFFFFFF0F000F0F00F000000000FFFFFFFF0F0F0F000FFFFF0F0F0FFFFF00FF0F0F000FF000000FF0FFF000FFF00FF0FFFF0F0F0FFFF0000FFFFFFF0F000F0F00F000000000FFFFFFFF0F0F0F000FFFFF0F0F0FFFFF00FF0F0F000FF000000FF0FFF000FFF00FF0FFFF0F0F0FFFF0000FFFFFFF0F000F0F00F000000000FFFFFFFF0F0F0F000FFFFF0F0F0FFFFF00FF0F0F000FF000000FF0FFF000FFF00FF0FFFF0F0F0FFFF0000FFFFFFF0F000F0F00F000000000FFFFFFFF0F0F0F000FFFFF0F0F0FFFFF00FF0F0F000FF000000FF0FFF000FFF00FF0FFFF0F0F0FFFF0000FFFFFFF0F000F0F00F000000000FFFFFFFF0F0F0F000FFFFF0F0F0FFFFF00FF0F0F000FF000000FF0FFF000FFF00FF0FFFF0F0F0FFFF0000FFFFFFF0F000F0F00F000000000FFFFFFFF0F0F0F000FFFFF0F0F0FFFFF00FF0F0F000FF000000FF0FFF000FFF00FF0FFFF0F0F0FFFF0000FFFFFFF0F000F0F00F000000000FFFFFFFF0F0F0F000FFFFF0F0F000FF000F00FF00F0FFFFFF00F00FFFF0F000FFFF0FFFF0F0F00F000000FFFFFFF0F000F0F00F000000000FFFFFFFF0F0F0F000FFFFF0F0F000FF000F00FF00F0FFFFFF00F00FFFF0F000FFFF0FFFF0F0F00F000000FFFFFFF0F000F0F00F000000000FFFFFFFF0F0F0F000FFFFF0F0F000FF000F00FF00F0FFFFFF00F00FFFF0F000FFFF0FFFF0F0F00F000000FFFFFFF0F000F0F00F000000000FFFFFFFF0F0F0F000FFFFF0F0F000FF000F00FF00F0FFFFFF00F00FFFF0F000FFFF0FFFF0F0F00F000000FFFFFFF0F000F0F00F000000000FFFFFFFF0F0F0F000FFFFF0F0F000FF000F00FF00F0FFFFFF00F00FFFF0F000FFFF0FFFF0F0F00F000000FFFFFFF0F000F0F00F000000000FFFFFFFF0F0F0F000FFFFF0F0F000FF000F00FF00F0FFFFFF00F00FFFF0F000FFFF0FFFF0F0F00F000000FFFFFFF0F000F0F00F000000000FFFFFFFF0F0F0F000FFFFF0F0F000FF000F00FF00F0FFFFFF00F00FFFF0F000FFFF0FFFF0F0F00F000000FFFFFFF0F000F0F00F000000000FFFFFFFF0F0F0F000FFFFF0F0F000FF000F00FF00F0FFFFFF00F00FFFF0F000FFFF0FFFF0F0F00F000000FFFFFFF0F000F0F00F000000000FFFFFFFF0F0F0F000FFFFF0F0F000FF000F00FF00F0FFFFFF00F00FFFF0F000FFFF0FFFF0F0F00F000000FFFFFFF0F000F0F00F000000000FFFFFFFF0F0F0F000FFFFF0F0F000FF000F00FF00F0FFFFFF00F00FFFF0F000FFFF0FFFF0F0F00F000000FFFFFFF0F000F0F00F000000000FFFFFFFF0F0F0F000FFFFF0F0F000FF000F00FF00F0FFFFFF00F00FFFF0F000FFFF0FFFF0F0F00F000000FFFFFFF0F000F0F00F000000000FFFFFFFF0F0F0F000FFFFF0F0F000FF000F00FF00F0FFFFFF00F00FFFF0F000FFFF0FFFF0F0F00F000000FFFFFFF0F000F0F00F000000000FFFFFFFF0F0F0F000FFF0F0F0FFFFFF000FF0F0F0FFFFF00000FFF0F0F0FFFFFF000FF0F0F000FFFFF000FFFFFFF0F000F0F00F000000000FFFFFFFF0F0F0F000FFF0F0F0FFFFFF000FF0F0F0FFFFF00000FFF0F0F0FFFFFF000FF0F0F000FFFFF000FFFFFFF0F000F0F00F000000000FFFFFFFF0F0F0F000FFF0F0F0FFFFFF000FF0F0F0FFFFF00000FFF0F0F0FFFFFF000FF0F0F000FFFFF000FFFFFFF0F000F0F00F000000000FFFFFFFF0F0F0F000FFF0F0F0FFFFFF000FF0F0F0FFFFF00000FFF0F0F0FFFFFF000FF0F0F000FFFFF000FFFFFFF0F000F0F00F000000000FFFFFFFF0F0F0F000FFF0F0F0FFFFFF000FF0F0FLine truncated
^XZ
Loaded 100 of 137 files, more files were not shown because too many files have changed in this diff. Show more