-
+
@@ -181,6 +206,9 @@ import {LABEL_TEMPLATES, BASE_VARS, DERIVED_VARS, withDerivedVars, templateConte
const BLOB_URL = "/vendor/libweblabel.js";
const MAX_ZOOM = 4; /* never magnify the preview more than this */
+const MAX_PREVIEW_HEIGHT_PX = 300; /* never let the on-screen preview grow taller than this */
+const RULER_TICK_MM = 1; /* finest tick spacing the mm ruler draws */
+const RULER_MAJOR_EVERY_MM = 5; /* every Nth tick is taller and labeled with its mm value */
export default {
name: "Print",
@@ -210,6 +238,12 @@ export default {
connected: null,
tape: null,
labelBitmap: null,
+ // The tape-fed preview's current on-screen scale and printed pixel width (see
+ // fitZoom/redraw) - tracked reactively, rather than read straight off the canvas
+ // element, purely so the mm ruler below can recompute its tick positions whenever
+ // either one changes.
+ zoom: 1,
+ printedWidthPx: 0,
// One input per *base* template variable (see label-layouts.js's BASE_VARS) - the
// derived ones (itemUrl, itemHandle) are format strings calculated from these, not
@@ -286,6 +320,25 @@ export default {
canPrint() {
return Boolean(this.tape && this.labelBitmap && !this.busy);
},
+ // On-screen pixels per real millimeter of tape, at the preview's current zoom - what
+ // turns a physical mm into a tick position the ruler can actually draw. Only meaningful
+ // for the tape-fed preview (see redraw's printedWidthPx) - the no-webusb fallback preview
+ // isn't fed from any particular real tape/dpi, so it gets no ruler (see the template).
+ tapePxPerMm() {
+ return this.tape ? (this.tape.dpi / 25.4) * this.zoom : 0;
+ },
+ // Ticks along the tape's length (the printed bitmap's actual width, lead/trailing feed
+ // margin included, since that's real physical tape too).
+ horizontalRulerTicks() {
+ if (!this.tape || !this.printedWidthPx) {
+ return [];
+ }
+ return this.rulerTicks(this.printedWidthPx / (this.tape.dpi / 25.4));
+ },
+ // Ticks across the tape's fixed physical width.
+ verticalRulerTicks() {
+ return this.tape ? this.rulerTicks(this.tape.mediaWidthMm) : [];
+ },
},
watch: {
varValues: {
@@ -324,6 +377,16 @@ export default {
return v.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^./, c => c.toUpperCase());
},
+ // One tick per RULER_TICK_MM from 0 up to totalMm, each positioned in on-screen pixels
+ // via tapePxPerMm - shared by the horizontal/vertical ruler computeds above.
+ rulerTicks(totalMm) {
+ const ticks = [];
+ for (let mm = 0; mm <= totalMm; mm += RULER_TICK_MM) {
+ ticks.push({mm, pos: mm * this.tapePxPerMm, major: mm % RULER_MAJOR_EVERY_MM === 0});
+ }
+ return ticks;
+ },
+
async guard(fn) {
this.error = null;
this.busy = true;
@@ -410,7 +473,8 @@ export default {
const bitmap = canvasToBitmap(canvas);
bitmapToCanvas(canvas, bitmap);
this.labelBitmap = bitmap;
- this.fitZoom(canvas);
+ this.printedWidthPx = canvas.width;
+ this.zoom = this.fitZoom(canvas);
},
redrawFallback() {
@@ -448,16 +512,20 @@ export default {
/* Fit the preview to its card without ever needing a horizontal
scrollbar for a label this small, magnifying short labels up to
- MAX_ZOOM rather than showing them at native (tiny) size. */
+ MAX_ZOOM rather than showing them at native (tiny) size - and never
+ past MAX_PREVIEW_HEIGHT_PX tall, however long/wide the label itself
+ runs. Returns the zoom actually used, so callers that care (see
+ redraw's ruler bookkeeping) don't have to re-derive it. */
fitZoom(canvas) {
const available = canvas.parentElement.clientWidth;
if (!(available > 0)) {
- return;
+ return 1;
}
- const zoom = Math.min(MAX_ZOOM, available / canvas.width);
+ const zoom = Math.min(MAX_ZOOM, available / canvas.width, MAX_PREVIEW_HEIGHT_PX / canvas.height);
canvas.style.width = `${canvas.width * zoom}px`;
canvas.style.height = `${canvas.height * zoom}px`;
canvas.style.imageRendering = zoom >= 1 ? "pixelated" : "auto";
+ return zoom;
},
print() {
@@ -476,7 +544,7 @@ export default {
for (const entry of entries) {
const canvas = entry.target.querySelector("canvas");
if (canvas) {
- this.fitZoom(canvas);
+ this.zoom = this.fitZoom(canvas);
}
}
}, 100);
@@ -528,6 +596,7 @@ export default {
background: rgba(127, 127, 127, .08);
border-radius: .35rem;
text-align: center;
+ max-height: 300px;
}
.label-preview canvas {
@@ -536,6 +605,103 @@ export default {
box-shadow: 0 0 0 1px rgba(127, 127, 127, .5);
}
+/* The tape-fed preview's mm ruler (see Print.vue's template/script) - a horizontal track above
+ the canvas and a vertical one to its left, both ticked in real physical millimeters rather than
+ preview pixels, since what they're measuring is the actual label. */
+.preview-row {
+ display: flex;
+ align-items: flex-start;
+}
+
+/* The single horizontal scroll region for a label wider than its card - the horizontal ruler and
+ the canvas are both direct children of this, so they scroll in lockstep with no JS needed to
+ keep them in sync. */
+.preview-scroll {
+ flex: 1 1 auto;
+ min-width: 0;
+ overflow-x: auto;
+}
+
+/* Overrides the standalone rule above: nested here, .label-preview must neither clip nor center
+ its canvas - overflow-x:visible lets the canvas's true width "bubble up" to .preview-scroll's
+ own scrollbar instead of scrolling twice, and text-align:left keeps the canvas flush with the
+ ruler's zero tick instead of drifting to the middle of whatever spare width this card has. */
+.preview-scroll .label-preview {
+ overflow-x: visible;
+ text-align: left;
+}
+
+.ruler-v {
+ flex: none;
+ width: 2.2rem;
+ color: rgba(127, 127, 127, .9);
+}
+
+/* Matches .ruler-h's own height below - the vertical ruler's ticks start only after this, so tick
+ 0 lines up with the canvas's top edge rather than the horizontal ruler sitting above it. */
+.ruler-v-corner {
+ height: 1.6rem;
+}
+
+.ruler-v-ticks {
+ position: relative;
+ margin-top: .75rem; /* .label-preview's own padding, so tick 0 meets the canvas, not its box */
+}
+
+.ruler-h {
+ position: relative;
+ height: 1.6rem;
+ margin-left: .75rem; /* .label-preview's own padding, so tick 0 meets the canvas, not its box */
+ color: rgba(127, 127, 127, .9);
+}
+
+.ruler .tick {
+ position: absolute;
+ background: currentColor;
+}
+
+/* Ticks anchor to the edge nearest the canvas (right for the vertical ruler, bottom for the
+ horizontal one) and grow outward from it, so they read as pointing at the label; the mm labels
+ sit on the opposite, outer edge, out of the ticks' way. */
+.ruler-v-ticks .tick {
+ right: 0;
+ width: .4rem;
+ height: 1px;
+}
+
+.ruler-v-ticks .tick-major {
+ width: .7rem;
+}
+
+.ruler-h .tick {
+ bottom: 0;
+ width: 1px;
+ height: .4rem;
+}
+
+.ruler-h .tick-major {
+ height: .7rem;
+}
+
+.tick-label {
+ position: absolute;
+ font-size: .6rem;
+ line-height: 1;
+ white-space: nowrap;
+}
+
+/* transform, not a fixed em nudge, so the label's actual center - not its edge - lands on the
+ tick's mm position (t.pos, set inline), whatever the text's width/height happens to be. */
+.ruler-v-ticks .tick-label {
+ left: 0;
+ transform: translateY(-50%);
+}
+
+.ruler-h .tick-label {
+ top: 0;
+ transform: translateX(-50%);
+}
+
.copies-input {
width: 5.5rem;
}