Label generator: back-side codes, pictures, two-column layout, measurement guards

This commit is contained in:
2026-08-21 12:38:08 -04:00
parent 74392ccad2
commit 2719589c6e
4 changed files with 214 additions and 44 deletions

View File

@@ -342,6 +342,7 @@ import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick } from 'vue'
import { LABEL_PRESETS, qrModuleCount, qrFit, qrSvgDataUri, barcodeSvgDataUri }
from '@/utils/codes'
import { parseCsv, buildPages, MAX_LABELS } from '../labelPages'
import { labelVars, LABEL_VAR_NAMES } from '../labelVars'
const PRESETS = LABEL_PRESETS
@@ -591,43 +592,44 @@ async function renderAll() {
// @page cannot read a scoped style, so the sizing lives in custom properties on
// the root element. Chromium honors var() in @page size.
const VAR_NAMES = [
'--tool-label-w', '--tool-label-h', '--tool-pad', '--tool-code', '--tool-barh',
'--tool-quiet', '--tool-gap', '--tool-font', '--tool-align', '--tool-valign',
'--tool-content-x', '--tool-content-y', '--tool-nudge-x', '--tool-nudge-y',
'--tool-pic', '--tool-pic-opacity',
]
//
// The values are built in ../labelVars.js, which is also where the rule lives
// that none of them may be NaN or empty: a cleared number field used to emit
// `NaNin`, the browser dropped the declaration, and a box that fell back to
// auto height rendered the barcode 176 wide by 0 high.
function applyVars() {
const root = document.documentElement
root.style.setProperty('--tool-label-w', labelwidth.value + 'in')
root.style.setProperty('--tool-label-h', labelheight.value + 'in')
root.style.setProperty('--tool-pad', padding.value + 'in')
root.style.setProperty('--tool-code', codesize.value + 'in')
root.style.setProperty('--tool-barh', barheight.value + 'in')
// Both symbologies need clear space: a QR wants a quiet zone around it, and
// CODE128 wants one at each end (10x the narrow bar). It is the code's own
// margin and nothing else's - the space between the code and the caption is
// --tool-gap, so a tight caption no longer means shaving the quiet zone.
root.style.setProperty('--tool-quiet', quiet.value + 'in')
root.style.setProperty('--tool-gap', gap.value + 'in')
root.style.setProperty('--tool-font', labelfont.value + 'pt')
root.style.setProperty('--tool-align', align.value)
root.style.setProperty('--tool-valign', valign.value)
// Content offset moves what is printed WITHIN the label. Media offset moves
// the label box itself on the stock. They are different fixes: the first
// composes a label, the second corrects a printer whose origin is off.
root.style.setProperty('--tool-content-x', contentx.value + 'in')
root.style.setProperty('--tool-content-y', contenty.value + 'in')
root.style.setProperty('--tool-nudge-x', nudgex.value + 'in')
root.style.setProperty('--tool-nudge-y', nudgey.value + 'in')
root.style.setProperty('--tool-pic', picturesize.value + 'in')
root.style.setProperty('--tool-pic-opacity', String(pictureopacity.value))
const vars = labelVars({
labelwidth: labelwidth.value,
labelheight: labelheight.value,
padding: padding.value,
codesize: codesize.value,
barheight: barheight.value,
// Both symbologies need clear space: a QR wants a quiet zone around it, and
// CODE128 wants one at each end (10x the narrow bar). It is the code's own
// margin and nothing else's - the space between the code and the caption is
// the gap, so a tight caption no longer means shaving the quiet zone.
quiet: quiet.value,
gap: gap.value,
labelfont: labelfont.value,
align: align.value,
valign: valign.value,
// Content offset moves what is printed WITHIN the label. Media offset moves
// the label box itself on the stock. They are different fixes: the first
// composes a label, the second corrects a printer whose origin is off.
contentx: contentx.value,
contenty: contenty.value,
nudgex: nudgex.value,
nudgey: nudgey.value,
picturesize: picturesize.value,
pictureopacity: pictureopacity.value,
})
for (const [name, value] of Object.entries(vars)) root.style.setProperty(name, value)
}
function clearVars() {
const root = document.documentElement
for (const name of VAR_NAMES) root.style.removeProperty(name)
for (const name of LABEL_VAR_NAMES) root.style.removeProperty(name)
}
function applyPreset() {
@@ -748,7 +750,7 @@ watch([pages, codetype, backcodetype, errorcorrection], renderAll)
<style scoped>
/* One page per side. Chromium reads var() here; the print dialog must be set
to Margins = None and Scale = 100%. */
@page { size: var(--tool-label-w) var(--tool-label-h); margin: 0; }
@page { size: var(--tool-label-w, 1in) var(--tool-label-h, 0.5in); margin: 0; }
.code-generator { width: 100%; }
@@ -875,9 +877,9 @@ watch([pages, codetype, backcodetype, errorcorrection], renderAll)
media offset moves it, and that exists for a printer whose origin is off. */
.label {
box-sizing: border-box;
width: var(--tool-label-w);
height: var(--tool-label-h);
padding: var(--tool-pad);
width: var(--tool-label-w, 1in);
height: var(--tool-label-h, 0.5in);
padding: var(--tool-pad, 0);
background: #fff;
color: #000;
overflow: hidden;
@@ -898,7 +900,7 @@ watch([pages, codetype, backcodetype, errorcorrection], renderAll)
.content {
display: flex;
align-items: center;
gap: var(--tool-gap);
gap: var(--tool-gap, 0.03in);
position: relative;
z-index: 1;
transform: translate(var(--tool-content-x, 0), var(--tool-content-y, 0));
@@ -908,7 +910,7 @@ watch([pages, codetype, backcodetype, errorcorrection], renderAll)
.codegroup {
display: flex;
align-items: center;
gap: var(--tool-gap);
gap: var(--tool-gap, 0.03in);
}
.label.layout-stack .codegroup { flex-direction: column; justify-content: center; }
@@ -923,8 +925,8 @@ watch([pages, codetype, backcodetype, errorcorrection], renderAll)
.picturebox {
flex: 0 0 auto;
width: var(--tool-pic);
height: var(--tool-pic);
width: var(--tool-pic, 0.5in);
height: var(--tool-pic, 0.5in);
display: flex;
align-items: center;
justify-content: center;
@@ -952,9 +954,9 @@ watch([pages, codetype, backcodetype, errorcorrection], renderAll)
.codebox {
flex: 0 0 auto;
margin: var(--tool-quiet);
width: var(--tool-code);
height: var(--tool-code);
margin: var(--tool-quiet, 0.035in);
width: var(--tool-code, 0.43in);
height: var(--tool-code, 0.43in);
display: flex;
align-items: center;
justify-content: center;
@@ -969,7 +971,7 @@ watch([pages, codetype, backcodetype, errorcorrection], renderAll)
/* Never below 40% of the label: past that a CODE128 is too few narrow bars
to scan, so it is better for the caption to wrap or clip. */
min-width: 40%;
height: var(--tool-barh);
height: var(--tool-barh, 0.3in);
}
.label.layout-stack.type-barcode .codebox { min-width: 100%; }
@@ -998,7 +1000,7 @@ watch([pages, codetype, backcodetype, errorcorrection], renderAll)
min-width: 0;
text-align: center;
font-family: "Arial Narrow", Arial, Helvetica, sans-serif;
font-size: var(--tool-font);
font-size: var(--tool-font, 7pt);
font-weight: 700;
line-height: 1.05;
overflow-wrap: anywhere;