This commit is contained in:
j3d1 2026-08-31 17:01:28 +02:00
parent 15dad8e3be
commit 9c9f0f2d9b
4 changed files with 117 additions and 179 deletions

View file

@ -1,6 +1,7 @@
import {loadAnyDCode} from "../vendor/anyd-qr.js";
import {encodeHandleForUrl} from "@/router"
import {drawPixelText, preloadPixelFontRenderer} from "@/pixel-font.js";
import {DERIVED_VARS} from "@/label-layouts.js";
// Re-exported so every caller can preload both the QR encoder and the bitmap-font rasterizer
// (see fontTierFor's `pixel` tiers below) the same way, alongside this file's own preloadQrEncoder.
@ -349,7 +350,7 @@ function measureTextBlock(ctx, lines, referencePx) {
return {width, height};
}
// Converts a resolved content tree (see label-layouts.js's resolveContent) into one ready for layout. See
// Converts a resolved content tree (see templateContent below) into one ready for layout. See
// docs/implementation.md#render-tree-construction.
function buildRenderTree(ctx, node, referencePx) {
if (isSplit(node)) {
@ -483,7 +484,7 @@ function layoutContent(ctx, content, fixedSize, maxLength, referencePx, pxPerMm,
return {tree, length, warnings, qrInfo, textInfo};
}
// The tape-fed layout: draws a fully resolved content tree (see label-layouts.js's resolveContent) at the tape's
// The tape-fed layout: draws a fully resolved content tree (see templateContent below) at the tape's
// real pixel dimensions. See docs/implementation.md#tape-fed-label-drawing. Returns
// {textSizesPx}: each "text" leaf's effective font size, in the tree's own left-to-right,
// top-to-bottom order; {warnings}: {short, message} scan-reliability entries from
@ -627,3 +628,54 @@ export function buildLabelFields(prefill) {
const build = LABEL_FIELD_BUILDERS[prefill.kind];
return build ? build(prefill.components) : {};
}
// Runs each DERIVED_VARS calc against `fields`, adding the result wherever its inputs are
// present, so callers never need to know DERIVED_VARS' {inputs, calc} shape.
export function withDerivedVars(fields) {
const result = {...fields};
for (const [name, {inputs, calc}] of Object.entries(DERIVED_VARS)) {
if (inputs.every(v => result[v])) {
result[name] = calc(result);
}
}
return result;
}
function walkLeaves(node, fn) {
if (Array.isArray(node)) {
node.forEach(child => walkLeaves(child, fn));
} else {
fn(node);
}
}
function mapTree(node, fn) {
return Array.isArray(node) ? node.map(child => mapTree(child, fn)) : fn(node);
}
// Resolved only if every part is - a single string for a QR-family/"text" leaf, every line for a
// multi-line "text" (see label-layouts.js's "owner-id-text" and "item-url-qr-owner-id" templates).
function isResolved(value) {
return Array.isArray(value) ? value.every(isResolved) : value !== undefined && value !== null;
}
// Shared by LabelLayoutPreview.vue's grid and Print.vue's preview, so neither reimplements
// leaf-walking/field-resolving itself.
export function templateIsAvailable(t, fields) {
let available = true;
walkLeaves(t.layout, leaf => {
if (leaf.type !== "empty" && !isResolved(leaf.content(fields))) {
available = false;
}
});
return available;
}
export function templateContent(t, fields) {
if (!templateIsAvailable(t, fields)) {
const err = new Error("This layout needs more fields filled in before it can be drawn.");
err.short = "missing data";
throw err;
}
return mapTree(t.layout, leaf => leaf.type === "empty" ? leaf : {...leaf, value: leaf.content(fields)});
}