stash
This commit is contained in:
parent
7d9f67a77a
commit
3299c97392
13 changed files with 1407 additions and 327 deletions
|
|
@ -1,5 +1,36 @@
|
|||
import QRCode from "qrcode";
|
||||
import {encodeHandleForUrl} from "@/handle-url";
|
||||
import {loadAnyDCode} from "../vendor/anyd-qr.js";
|
||||
import {encodeHandleForUrl} from "@/router"
|
||||
|
||||
// anyd-qr.js's own loadAnyDCode() memoizes the wasm instantiation itself, so calling it more
|
||||
// than once (each of Print.vue and LabelLayoutPreview.vue does, on mount) is free - `anyd` just
|
||||
// mirrors its resolved value so buildRenderTree below can use it synchronously. Until it
|
||||
// resolves, a "qrcode" leaf throws (see encodeQr) the same way an oversized value already does -
|
||||
// callers already have to handle layoutContent throwing, so this reuses that path rather than
|
||||
// adding a second failure mode.
|
||||
let anyd = null;
|
||||
|
||||
export function preloadQrEncoder() {
|
||||
return loadAnyDCode().then(instance => { anyd = instance; });
|
||||
}
|
||||
|
||||
// The three symbologies anyd-qr.js exposes (see its CodeType) - Print.vue's code-type selector
|
||||
// offers exactly these. rMQR's matrix isn't square (see encodeQr's width/height below), unlike
|
||||
// qr/micro-qr, which always are.
|
||||
export const QR_CODE_TYPES = ["qr", "micro-qr", "rmqr"];
|
||||
|
||||
function encodeQr(text, codeType) {
|
||||
if (!anyd) {
|
||||
throw new Error("The QR encoder is still loading — try again in a moment.");
|
||||
}
|
||||
// BitMatrix-alike view over anyd's row-major Uint8Array, matching the shape drawQrLeaf/
|
||||
// snapQrToCrispSize below expect (they predate this and were written against the "qrcode"
|
||||
// package's own modules.size/get()). anyd's matrix already excludes the quiet zone from
|
||||
// width/height (see its ModuleMatrix type), same as the old library's BitMatrix. width/height
|
||||
// are kept separate rather than a single `size` (the old library's own shape, always square)
|
||||
// since rMQR symbols are rectangular.
|
||||
const {width, height, modules} = anyd.encode(codeType, new TextEncoder().encode(text), {ecc: "M"}).matrix;
|
||||
return {width, height, get: (row, col) => modules[row * width + col] !== 0};
|
||||
}
|
||||
|
||||
const TRAILING_PADDING_PX = 3; /* blank columns after the cut, same idea as the leading margin */
|
||||
|
||||
|
|
@ -39,27 +70,46 @@ export function tapeFromStatus(status) {
|
|||
|
||||
See label-layouts.js's LABEL_TEMPLATES for concrete trees.
|
||||
*/
|
||||
const QR_ASPECT = 1; /* a QR code is always square */
|
||||
const TEXT_REFERENCE_PX = 100; /* font size text leaves measure their natural aspect ratio at */
|
||||
|
||||
// Below 10px, a general-purpose sans-serif gets blurry/illegible, so drawTextLeaf switches to
|
||||
// whichever of these bitmap-style fonts (see ../scss/_pixel-fonts.scss) is designed closest to -
|
||||
// and no smaller than - the box's actual effective size. Ordered smallest first; fontFamilyFor
|
||||
// below picks the first tier whose belowPx clears the requested size, sans-serif once none do.
|
||||
// Below 10px, a general-purpose sans-serif gets blurry/illegible, so drawTextLeaf switches to one
|
||||
// of these bitmap-style fonts instead (see ../scss/_pixel-fonts.scss) - Tom Thumb for the smallest
|
||||
// sizes, Silkscreen once there's enough room for its more conventional letterforms.
|
||||
//
|
||||
// Both were chosen only after rendering single letters in a real browser *at raw canvas pixel
|
||||
// sizes* and inspecting the actual pixels - checking that fillText was merely *called* doesn't
|
||||
// confirm anything legible got drawn, and neither does a DPI-adjusted size that was never the
|
||||
// number actually handed to ctx.font. Silkscreen confirmed clean at 8px+. A third candidate,
|
||||
// PICO-8, also rendered cleanly across the whole range, but has no lowercase glyphs at all - it
|
||||
// silently draws lowercase input as uppercase - which rules it out for real label content (item
|
||||
// handles, URLs) that isn't reliably all-caps. Tom Thumb's declared ascent/descent (0 / ~fontPx,
|
||||
// backwards from a normal font) turned out not to be a centering quirk: its actual visible ink is
|
||||
// only ~1/3.2 of its own nominal font-size (confirmed both by measuring actualBoundingBox at
|
||||
// several sizes and by a live-browser check - "16px" reads as roughly 5px of real glyph height),
|
||||
// hence the `scale` below - whatever logical size is requested, the font is actually drawn that
|
||||
// many times larger so its real ink comes out at the intended size. Silkscreen's declared size
|
||||
// already matches its ink, so it has no `scale` (equivalent to 1).
|
||||
//
|
||||
// belowPx and MIN_READABLE_TEXT_PX below are both compared against the *logical* (unscaled)
|
||||
// fontPx, deliberately not adjusted for the tape's dpi: a browser's font rasterizer only ever
|
||||
// sees a raw pixel count, with no notion of "physical size" at all, so that's what determines
|
||||
// whether a glyph's fine detail survives - confirmed by the same real-Chromium testing, where a
|
||||
// raw 4.35px render was a solid blob regardless of what a dpi-scaled version of that number would
|
||||
// have implied.
|
||||
const PIXEL_FONT_TIERS = [
|
||||
{belowPx: 6, family: "Tom Thumb"}, /* designed for ~5px */
|
||||
{belowPx: 7, family: "PICO-8"}, /* designed for ~6px */
|
||||
{belowPx: 10, family: "Silkscreen"}, /* designed for ~7-8px */
|
||||
{belowPx: 8, family: "Tom Thumb", scale: 3.2},
|
||||
{belowPx: 10, family: "Silkscreen"},
|
||||
];
|
||||
|
||||
function fontFamilyFor(fontPx) {
|
||||
return PIXEL_FONT_TIERS.find(t => fontPx < t.belowPx)?.family ?? "sans-serif";
|
||||
return PIXEL_FONT_TIERS.find(t => fontPx < t.belowPx) ?? {family: "sans-serif"};
|
||||
}
|
||||
|
||||
// A pixel font (see PIXEL_FONT_TIERS above) is what makes a size down here still legible - a
|
||||
// plain sans-serif this small would fail the old MIN_READABLE_TEXT_PX=8 floor that predates them.
|
||||
// Below this, drawTextLeaf leaves that one field blank rather than drawing illegible ink - see
|
||||
// there for why that's a quieter failure than rejecting the whole label over it.
|
||||
// Tom Thumb (see PIXEL_FONT_TIERS above) held up down to 5px in the same real-Chromium pixel-level
|
||||
// verification - a plain sans-serif this small would fail the old MIN_READABLE_TEXT_PX=8 floor
|
||||
// that predates it. Below this, drawTextLeaf leaves that one field blank rather than drawing
|
||||
// illegible ink - see there for why that's a quieter failure than rejecting the whole label over
|
||||
// it.
|
||||
const MIN_READABLE_TEXT_PX = 5;
|
||||
|
||||
function isSplit(node) {
|
||||
|
|
@ -90,8 +140,8 @@ function parseMm(value, key) {
|
|||
requesting the direction a split doesn't naturally combine in just inverts its own relation. */
|
||||
function relation(node, ownAxis, wantWidth, pxPerMm) {
|
||||
if (!isSplit(node)) {
|
||||
if (node.type === "qrcode" && node.crispSize !== undefined) {
|
||||
return {a: 0, b: node.crispSize};
|
||||
if (node.type === "qrcode" && node.crispWidth !== undefined) {
|
||||
return {a: 0, b: wantWidth ? node.crispWidth : node.crispHeight};
|
||||
}
|
||||
if (node.type === "qrcode" || node.type === "text") {
|
||||
const aspect = node.aspect;
|
||||
|
|
@ -156,27 +206,29 @@ function positionTree(node, ownAxis, x, y) {
|
|||
|
||||
/* A QR code needs an integer number of pixels per module to render crisply rather than blurring
|
||||
at a fractional scale, so its true size is whatever that rounds down to - almost never the
|
||||
scale-free square a bare aspect ratio of 1 would suggest. Called once every qrcode leaf has a
|
||||
provisional (scale-free) box from a first layoutTree pass, this pins each one's real box.width
|
||||
(in row context) or box.height (in column context - whichever axis its box doesn't already
|
||||
share with its siblings) as `crispSize`, so relation() above starts treating it as a fixed
|
||||
size, the same as an "empty" leaf, instead of one that scales with whatever height/width it's
|
||||
offered. A second relation()/layoutTree() pass (see layoutContent) then resizes everything
|
||||
scale-free box its aspect ratio alone would suggest. Called once every qrcode leaf has a
|
||||
provisional (scale-free) box from a first layoutTree pass, this pins each one's real
|
||||
box.width/box.height as `crispWidth`/`crispHeight`, so relation() above starts treating it as a
|
||||
fixed size, the same as an "empty" leaf, instead of one that scales with whatever height/width
|
||||
it's offered. A second relation()/layoutTree() pass (see layoutContent) then resizes everything
|
||||
else around that real footprint, so nothing downstream reserves - and leaves unfilled - room
|
||||
for a squarer QR code than what actually gets drawn. */
|
||||
for a squarer/differently-shaped code than what actually gets drawn. Kept as two independent
|
||||
dimensions rather than one `crispSize` (as when every code here was a square QR) since an rMQR
|
||||
symbol isn't square - see encodeQr. */
|
||||
function snapQrToCrispSize(node) {
|
||||
if (isSplit(node)) {
|
||||
node.forEach(snapQrToCrispSize);
|
||||
return;
|
||||
}
|
||||
if (node.type === "qrcode") {
|
||||
const modules = node.qr.modules.size;
|
||||
const scale = Math.floor(Math.min(node.box.width, node.box.height) / modules);
|
||||
const {width: modulesW, height: modulesH} = node.qr;
|
||||
const scale = Math.floor(Math.min(node.box.width / modulesW, node.box.height / modulesH));
|
||||
if (!(scale >= 1)) {
|
||||
throw new Error("This text needs a bigger QR code than the tape allows — "
|
||||
throw new Error("This text needs a bigger code than the tape allows — "
|
||||
+ "try a shorter value or a wider tape.");
|
||||
}
|
||||
node.crispSize = modules * scale;
|
||||
node.crispWidth = modulesW * scale;
|
||||
node.crispHeight = modulesH * scale;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -194,18 +246,22 @@ function measureTextBlock(ctx, lines, referencePx) {
|
|||
}
|
||||
|
||||
/* Turns a resolved content tree (see templateContent below - leaf objects carry a `value`
|
||||
rather than a `content` function) into one ready for layout: a QR leaf gets its actual
|
||||
QRCode.create() object, a text leaf gets its measured natural aspect ratio, and an "empty"
|
||||
leaf passes through untouched. Multi-line text (`value` is an array) measures as one leaf,
|
||||
not one per line - splitting it into a column of independently-sized leaves would let each
|
||||
line grow to its own full width, ending up at a different font size than its neighbors, which
|
||||
is legible but not what "one text field" should look like. */
|
||||
function buildRenderTree(ctx, node, referencePx) {
|
||||
rather than a `content` function) into one ready for layout: a QR leaf gets its actual encoded
|
||||
modules (see encodeQr) and an aspect ratio taken from their real width/height - 1 (square) for
|
||||
qr/micro-qr, but not for rMQR, whose symbols are rectangular - a text leaf gets its measured
|
||||
natural aspect ratio, and an "empty" leaf passes through untouched. Multi-line text (`value` is
|
||||
an array) measures as one leaf, not one per line - splitting it into a column of independently-
|
||||
sized leaves would let each line grow to its own full width, ending up at a different font size
|
||||
than its neighbors, which is legible but not what "one text field" should look like. `codeType`
|
||||
is Print.vue's global qr/micro-qr/rmqr choice - see QR_CODE_TYPES - applied to every qrcode leaf
|
||||
in the tree alike, the same way `orientation` applies to the whole tree in layoutContent. */
|
||||
function buildRenderTree(ctx, node, referencePx, codeType) {
|
||||
if (isSplit(node)) {
|
||||
return node.map(child => buildRenderTree(ctx, child, referencePx));
|
||||
return node.map(child => buildRenderTree(ctx, child, referencePx, codeType));
|
||||
}
|
||||
if (node.type === "qrcode") {
|
||||
return {type: "qrcode", aspect: QR_ASPECT, qr: QRCode.create(node.value)};
|
||||
const qr = encodeQr(node.value, codeType);
|
||||
return {type: "qrcode", aspect: qr.width / qr.height, qr};
|
||||
}
|
||||
if (node.type === "text") {
|
||||
const lines = Array.isArray(node.value) ? node.value : [node.value];
|
||||
|
|
@ -216,22 +272,24 @@ function buildRenderTree(ctx, node, referencePx) {
|
|||
}
|
||||
|
||||
function drawQrLeaf(ctx, node) {
|
||||
const modules = node.qr.modules.size;
|
||||
const scale = Math.floor(Math.min(node.box.width, node.box.height) / modules);
|
||||
const size = modules * scale;
|
||||
const left = node.box.x + Math.floor((node.box.width - size) / 2);
|
||||
const top = node.box.y + Math.floor((node.box.height - size) / 2);
|
||||
for (let row = 0; row < modules; row++) {
|
||||
for (let col = 0; col < modules; col++) {
|
||||
if (node.qr.modules.get(row, col)) {
|
||||
const {width: modulesW, height: modulesH} = node.qr;
|
||||
const scale = Math.floor(Math.min(node.box.width / modulesW, node.box.height / modulesH));
|
||||
const w = modulesW * scale;
|
||||
const h = modulesH * scale;
|
||||
const left = node.box.x + Math.floor((node.box.width - w) / 2);
|
||||
const top = node.box.y + Math.floor((node.box.height - h) / 2);
|
||||
for (let row = 0; row < modulesH; row++) {
|
||||
for (let col = 0; col < modulesW; col++) {
|
||||
if (node.qr.get(row, col)) {
|
||||
ctx.fillRect(left + col * scale, top + row * scale, scale, scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the effective font size drawn at (or that would have been, if it's too small to draw -
|
||||
// see below) - drawTree collects these into drawLabel/drawFallbackLabel's textSizesPx.
|
||||
// Returns the effective (raw, un-normalized) font size drawn at - or that would have been, if
|
||||
// it's too small to draw, see below - drawTree collects these into drawLabel/drawFallbackLabel's
|
||||
// textSizesPx.
|
||||
function drawTextLeaf(ctx, node, referencePx) {
|
||||
const fontPx = referencePx * (node.box.height / node.naturalHeight);
|
||||
// Even the smallest PIXEL_FONT_TIERS entry stops being legible below this - rather than
|
||||
|
|
@ -240,27 +298,51 @@ function drawTextLeaf(ctx, node, referencePx) {
|
|||
if (fontPx < MIN_READABLE_TEXT_PX) {
|
||||
return fontPx;
|
||||
}
|
||||
const lineHeight = node.box.height / node.lines.length;
|
||||
const family = fontFamilyFor(fontPx);
|
||||
ctx.font = `${fontPx}px "${family}"`;
|
||||
const {family, scale = 1} = fontFamilyFor(fontPx);
|
||||
const isPixelFont = family !== "sans-serif";
|
||||
// A pixel font's glyphs are meant to land exactly on the pixel grid - node.box.x/y are
|
||||
// ordinary layout math (sums/quotients of affine-solved sizes) and essentially never land on
|
||||
// a whole pixel, so drawing at their exact fractional size/position would misalign a pixel
|
||||
// font's 1px-wide strokes the same as it would any other font. Snapping size and position to
|
||||
// the nearest whole pixel fixes that; sans-serif is left at its exact fractional fit, since
|
||||
// ordinary anti-aliased text is expected to (and looks fine) regardless of position.
|
||||
const snap = isPixelFont ? Math.round : (v) => v;
|
||||
const drawFontPx = snap(fontPx);
|
||||
// `scale` (Tom Thumb only, see PIXEL_FONT_TIERS above) corrects for a font whose declared
|
||||
// size doesn't match its real visible ink - the size actually handed to ctx.font, not
|
||||
// drawFontPx itself, which stays the logical size everything else here (box centering, line
|
||||
// stacking) is measured against.
|
||||
ctx.font = `${drawFontPx * scale}px "${family}"`;
|
||||
// A @font-face family already in use elsewhere on the page loads in time for this, but canvas
|
||||
// text silently falls back to the next font in the stack (there isn't one here, so the
|
||||
// browser default) if drawn before its first-ever load finishes - unlike DOM text, a canvas
|
||||
// fillText never waits or repaints on its own once the real font arrives. Kicking off the load
|
||||
// here means only that very first draw at a given size risks the fallback; every redraw after
|
||||
// it (Print.vue's live preview redraws on every keystroke) picks up the real font.
|
||||
if (family !== "sans-serif") {
|
||||
if (isPixelFont) {
|
||||
document.fonts.load(ctx.font);
|
||||
}
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.textAlign = "center";
|
||||
const centerX = node.box.x + node.box.width / 2;
|
||||
const centerX = snap(node.box.x + node.box.width / 2);
|
||||
const lineHeight = node.box.height / node.lines.length;
|
||||
// Lines stack as a block, each centered under the last - keeps a multi-line field reading as
|
||||
// one unit rather than drifting apart.
|
||||
let y = node.box.y + lineHeight / 2;
|
||||
let sliceTop = node.box.y;
|
||||
for (const line of node.lines) {
|
||||
ctx.fillText(line, centerX, y);
|
||||
y += lineHeight;
|
||||
if (isPixelFont) {
|
||||
// textBaseline:"middle" centers on the font's *declared* ascent/descent (its line
|
||||
// height) - Tom Thumb's are backwards (see PIXEL_FONT_TIERS above) and would center on
|
||||
// nonsense. Centering on actualBoundingBox{Ascent,Descent} instead - this specific
|
||||
// string's real rendered ink (its character height) - costs nothing and stays correct
|
||||
// regardless of whether a pixel font's declared metrics can be trusted.
|
||||
ctx.textBaseline = "alphabetic";
|
||||
const {actualBoundingBoxAscent: up, actualBoundingBoxDescent: down} = ctx.measureText(line);
|
||||
ctx.fillText(line, centerX, snap(sliceTop + (lineHeight + up - down) / 2));
|
||||
} else {
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText(line, centerX, sliceTop + lineHeight / 2);
|
||||
}
|
||||
sliceTop += lineHeight;
|
||||
}
|
||||
return fontPx;
|
||||
}
|
||||
|
|
@ -301,61 +383,98 @@ function drawTree(ctx, node, referencePx, textSizesPx) {
|
|||
}
|
||||
}
|
||||
|
||||
/* Builds, sizes and validates the tree for a fixed `height` (the tape's cross-web printAreaPx, or
|
||||
the fallback preview's reference height) - the one dimension every layout scales from, plus
|
||||
`pxPerMm` to turn "empty" leaves' physical sizes into pixels. `height` and the tree's content
|
||||
fully determine its overall width; `maxLength`, when finite (a fixed-length/die-cut tape),
|
||||
rejects content that doesn't fit rather than shrinking it.
|
||||
/* Builds, sizes and validates the tree for a fixed `fixedSize` (the tape's cross-web printAreaPx,
|
||||
or the fallback preview's reference height) - the one dimension every layout scales from, plus
|
||||
`pxPerMm` to turn "empty" leaves' physical sizes into pixels. `fixedSize` and the tree's content
|
||||
fully determine its overall size along the other, growing axis (the one that runs along the
|
||||
tape as it feeds); `maxLength`, when finite (a fixed-length/die-cut tape), rejects content that
|
||||
doesn't fit rather than shrinking it.
|
||||
|
||||
Sizing runs twice: a first pass treats every qrcode leaf as the scale-free square its aspect
|
||||
ratio of 1 suggests, purely to find out how much room each one would actually be offered; from
|
||||
that, snapQrToCrispSize pins each one's real (smaller, crisp-pixel) size. The second pass then
|
||||
resolves the whole tree again with that real size fixed in, so every sibling and the overall
|
||||
width reflect what's actually drawn rather than the idealized square no QR code ever quite
|
||||
fills. */
|
||||
function layoutContent(ctx, content, height, maxLength, referencePx, pxPerMm) {
|
||||
const tree = buildRenderTree(ctx, content, referencePx);
|
||||
`orientation` picks which axis `fixedSize` binds to: "along" (the default) fixes the tree's
|
||||
height - the tape's cross-web width - and grows its width along the feed direction, same as a
|
||||
plain read top-to-bottom design. "across" fixes the tree's width instead and grows its height,
|
||||
so the design is built turned 90deg from how it'd read "along" - drawLabel/drawFallbackLabel
|
||||
are what actually rotate the drawing back into the physical raster's fixed orientation; nothing
|
||||
here needs to know about that rotation, since relation()/layoutTree() below already solve the
|
||||
tree in either direction symmetrically.
|
||||
|
||||
const measured = relation(tree, false, true, pxPerMm);
|
||||
layoutTree(tree, false, measured.a * height + measured.b, height, pxPerMm);
|
||||
Sizing runs twice: a first pass treats every qrcode leaf as the scale-free box its real
|
||||
width/height ratio suggests, purely to find out how much room each one would actually be
|
||||
offered; from that, snapQrToCrispSize pins each one's real (smaller, crisp-pixel) size. The
|
||||
second pass then resolves the whole tree again with that real size fixed in, so every sibling
|
||||
and the overall size reflect what's actually drawn rather than the idealized box no code ever
|
||||
quite fills. `codeType`, see buildRenderTree. */
|
||||
function layoutContent(ctx, content, fixedSize, maxLength, referencePx, pxPerMm, orientation, codeType) {
|
||||
const tree = buildRenderTree(ctx, content, referencePx, codeType);
|
||||
const alongTape = orientation !== "across";
|
||||
|
||||
const solve = () => {
|
||||
const {a, b} = relation(tree, false, alongTape, pxPerMm);
|
||||
return alongTape
|
||||
? {width: a * fixedSize + b, height: fixedSize}
|
||||
: {width: fixedSize, height: a * fixedSize + b};
|
||||
};
|
||||
|
||||
let {width, height} = solve();
|
||||
layoutTree(tree, false, width, height, pxPerMm);
|
||||
snapQrToCrispSize(tree);
|
||||
|
||||
const {a, b} = relation(tree, false, true, pxPerMm);
|
||||
const width = a * height + b;
|
||||
if (maxLength !== Infinity && width > maxLength) {
|
||||
({width, height} = solve());
|
||||
const length = alongTape ? width : height;
|
||||
if (maxLength !== Infinity && length > maxLength) {
|
||||
throw new Error("This doesn't fit on this tape — "
|
||||
+ "try a shorter value, a different layout, or a bigger label.");
|
||||
}
|
||||
layoutTree(tree, false, width, height, pxPerMm);
|
||||
return {tree, width};
|
||||
return {tree, length};
|
||||
}
|
||||
|
||||
/* The tape-fed layout - draws a fully resolved content tree (see templateContent) at the tape's
|
||||
real pixel dimensions. See DEBUG_LEAF_BORDERS above to outline every leaf's box. Returns
|
||||
real pixel dimensions. `orientation` is "along" (the default) to lay the design out reading
|
||||
along the tape's feed direction, or "across" to turn it 90deg so it reads across the tape
|
||||
instead - either way the physical raster this returns is still exactly
|
||||
printedLength x tape.printAreaPx (that's fixed by the tape/print head, not a choice this
|
||||
makes); "across" just draws the (now width-fixed, see layoutContent) tree through a rotated
|
||||
canvas transform so it lands correctly in that same raster, rather than transposing every box
|
||||
the tree itself computed. See DEBUG_LEAF_BORDERS above to outline every leaf's box. Returns
|
||||
{textSizesPx}: each "text" leaf's effective font size, in the tree's own left-to-right,
|
||||
top-to-bottom order. */
|
||||
export function drawLabel(canvas, tape, content) {
|
||||
top-to-bottom order. `codeType` (default "qr"), see buildRenderTree/QR_CODE_TYPES. */
|
||||
export function drawLabel(canvas, tape, content, orientation = "along", codeType = "qr") {
|
||||
const maxLength = tape.printLengthPx
|
||||
? tape.printLengthPx - tape.leadPx - TRAILING_PADDING_PX
|
||||
: Infinity;
|
||||
const measureCtx = canvas.getContext("2d");
|
||||
const pxPerMm = tape.dpi / 25.4;
|
||||
const {tree, width: contentWidth} = layoutContent(
|
||||
measureCtx, content, tape.printAreaPx, maxLength, TEXT_REFERENCE_PX, pxPerMm);
|
||||
const {tree, length: contentLength} = layoutContent(
|
||||
measureCtx, content, tape.printAreaPx, maxLength, TEXT_REFERENCE_PX, pxPerMm, orientation, codeType);
|
||||
|
||||
const width = tape.printLengthPx || Math.ceil(contentWidth + tape.leadPx + TRAILING_PADDING_PX);
|
||||
canvas.width = width;
|
||||
const printedLength = tape.printLengthPx || Math.ceil(contentLength + tape.leadPx + TRAILING_PADDING_PX);
|
||||
canvas.width = printedLength;
|
||||
canvas.height = tape.printAreaPx;
|
||||
|
||||
const ctx = canvas.getContext("2d", {willReadFrequently: true});
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.fillRect(0, 0, width, canvas.height);
|
||||
ctx.fillRect(0, 0, printedLength, canvas.height);
|
||||
ctx.fillStyle = "#000";
|
||||
|
||||
const originX = tape.leadPx + Math.floor((width - tape.leadPx - TRAILING_PADDING_PX - contentWidth) / 2);
|
||||
positionTree(tree, false, originX, 0);
|
||||
const origin = tape.leadPx
|
||||
+ Math.floor((printedLength - tape.leadPx - TRAILING_PADDING_PX - contentLength) / 2);
|
||||
const textSizesPx = [];
|
||||
drawTree(ctx, tree, TEXT_REFERENCE_PX, textSizesPx);
|
||||
if (orientation === "across") {
|
||||
// The tree was solved width-fixed (see layoutContent) - its width already exactly fills
|
||||
// tape.printAreaPx, so only its (growing) height needs the same along-the-feed centering
|
||||
// originX got above; translate+rotate then carries that tree-local (x, y) box straight
|
||||
// into the physical (printedLength x printAreaPx) raster, a quarter turn at a time.
|
||||
positionTree(tree, false, 0, origin);
|
||||
ctx.save();
|
||||
ctx.translate(0, tape.printAreaPx);
|
||||
ctx.rotate(-Math.PI / 2);
|
||||
drawTree(ctx, tree, TEXT_REFERENCE_PX, textSizesPx);
|
||||
ctx.restore();
|
||||
} else {
|
||||
positionTree(tree, false, origin, 0);
|
||||
drawTree(ctx, tree, TEXT_REFERENCE_PX, textSizesPx);
|
||||
}
|
||||
return {textSizesPx};
|
||||
}
|
||||
|
||||
|
|
@ -365,14 +484,15 @@ const FALLBACK_DPI = 203; /* reference resolution for turning "empty" leaves' m
|
|||
/* The no-webusb preview/PNG - same layout tree and renderer as drawLabel, just scaled from a
|
||||
fixed reference height instead of a real tape's, and with no maxLength (there's no physical
|
||||
tape to run out of, so the canvas just grows to fit) and no printer feed margin, since there's
|
||||
no real print head here to keep clear of. Returns {textSizesPx}, see drawLabel. */
|
||||
export function drawFallbackLabel(canvas, content) {
|
||||
no real print head here to keep clear of. `orientation`/`codeType`, see drawLabel. Returns
|
||||
{textSizesPx}, see drawLabel. */
|
||||
export function drawFallbackLabel(canvas, content, orientation = "along", codeType = "qr") {
|
||||
const measureCtx = canvas.getContext("2d");
|
||||
const pxPerMm = FALLBACK_DPI / 25.4;
|
||||
const {tree, width: contentWidth} = layoutContent(
|
||||
measureCtx, content, FALLBACK_LABEL_HEIGHT_PX, Infinity, TEXT_REFERENCE_PX, pxPerMm);
|
||||
const {tree, length: contentLength} = layoutContent(
|
||||
measureCtx, content, FALLBACK_LABEL_HEIGHT_PX, Infinity, TEXT_REFERENCE_PX, pxPerMm, orientation, codeType);
|
||||
|
||||
canvas.width = Math.ceil(contentWidth);
|
||||
canvas.width = Math.ceil(contentLength);
|
||||
canvas.height = FALLBACK_LABEL_HEIGHT_PX;
|
||||
|
||||
const ctx = canvas.getContext("2d", {willReadFrequently: true});
|
||||
|
|
@ -380,9 +500,18 @@ export function drawFallbackLabel(canvas, content) {
|
|||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.fillStyle = "#000";
|
||||
|
||||
positionTree(tree, false, 0, 0);
|
||||
const textSizesPx = [];
|
||||
drawTree(ctx, tree, TEXT_REFERENCE_PX, textSizesPx);
|
||||
if (orientation === "across") {
|
||||
positionTree(tree, false, 0, 0);
|
||||
ctx.save();
|
||||
ctx.translate(0, FALLBACK_LABEL_HEIGHT_PX);
|
||||
ctx.rotate(-Math.PI / 2);
|
||||
drawTree(ctx, tree, TEXT_REFERENCE_PX, textSizesPx);
|
||||
ctx.restore();
|
||||
} else {
|
||||
positionTree(tree, false, 0, 0);
|
||||
drawTree(ctx, tree, TEXT_REFERENCE_PX, textSizesPx);
|
||||
}
|
||||
return {textSizesPx};
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue