Refuse a measurement that makes the label stop existing
Reported as a barcode rendering 176 by 0. A bar height of zero is what does it: the number went straight into a CSS length, height: 0in is perfectly valid, and an image sized height: 100% of a zero-height box is zero pixels tall. The label comes out full width with nothing on it, which reads as a rendering failure rather than as a field doing exactly what it was told. A cleared field was the same bug wearing a different hat. An emptied number input holds '', not 0, so the page emitted `NaNin`; the browser dropped the declaration and the box fell back to auto height. That one merely looked wrong instead of vanishing, which is arguably worse for finding it. Measurements now go through labelVars(), which will not emit NaN, an empty length, or a zero for anything whose zero means the box stops existing. Padding, the quiet zone and the gap still take zero, because there zero is a real answer, and offsets stay signed. It is a separate module because this is exactly the kind of arithmetic that needs tests rather than a preview: every case in labelVars.spec.js is a value a number input can actually hold. The stylesheet now carries fallbacks on its var() reads as well, but they are a net, not the fix: a custom property set to garbage is substituted and then dropped at computed value time, and the fallback does not apply - it only covers a property that is absent entirely.
This commit is contained in:
80
plugins/tools/frontend/labelVars.js
Normal file
80
plugins/tools/frontend/labelVars.js
Normal file
@@ -0,0 +1,80 @@
|
||||
// The label's measurements, turned into the CSS custom properties the preview
|
||||
// and @page read.
|
||||
//
|
||||
// This exists because of one failure mode. A number input that is cleared does
|
||||
// not hold 0, it holds an empty string, and `'' + 'in'` is not a length. The
|
||||
// browser drops the whole declaration, the box falls back to `height: auto`,
|
||||
// and an image sized `height: 100%` of an auto-height parent computes to ZERO.
|
||||
// The label then renders 176x0: full width, no height, no barcode - which
|
||||
// reads as "the code stopped rendering" and not as "a field is empty".
|
||||
//
|
||||
// So nothing here may emit NaN, an empty string, or a negative length. Every
|
||||
// value falls back to something printable, and the fallbacks are the defaults
|
||||
// the page ships with.
|
||||
|
||||
export const LABEL_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',
|
||||
]
|
||||
|
||||
export const VAR_FALLBACKS = {
|
||||
labelwidth: 1, labelheight: 0.5, padding: 0, codesize: 0.4286, barheight: 0.3,
|
||||
quiet: 0.035, gap: 0.03, labelfont: 7, picturesize: 0.5, pictureopacity: 0.15,
|
||||
}
|
||||
|
||||
/** A length that must be greater than zero for the box to exist at all. */
|
||||
function positive(value, fallback) {
|
||||
const number = Number(value)
|
||||
return Number.isFinite(number) && number > 0 ? number : fallback
|
||||
}
|
||||
|
||||
/** A length that may legitimately be zero - padding, a quiet zone, a gap. */
|
||||
function nonNegative(value, fallback) {
|
||||
const number = Number(value)
|
||||
return Number.isFinite(number) && number >= 0 ? number : fallback
|
||||
}
|
||||
|
||||
/** An offset, which is signed: -0.05in is a real answer. */
|
||||
function offset(value) {
|
||||
const number = Number(value)
|
||||
return Number.isFinite(number) ? number : 0
|
||||
}
|
||||
|
||||
const ALIGNMENTS = ['flex-start', 'center', 'flex-end']
|
||||
|
||||
function alignment(value) {
|
||||
return ALIGNMENTS.includes(value) ? value : 'center'
|
||||
}
|
||||
|
||||
/**
|
||||
* Map the generator's settings to CSS custom properties, in inches.
|
||||
*
|
||||
* Returns a plain object of property name to value, ready to hand to
|
||||
* `style.setProperty`. Every entry is a valid CSS value whatever it was given.
|
||||
*/
|
||||
export function labelVars(state = {}) {
|
||||
const inches = value => value + 'in'
|
||||
const opacity = Number(state.pictureopacity)
|
||||
return {
|
||||
'--tool-label-w': inches(positive(state.labelwidth, VAR_FALLBACKS.labelwidth)),
|
||||
'--tool-label-h': inches(positive(state.labelheight, VAR_FALLBACKS.labelheight)),
|
||||
'--tool-pad': inches(nonNegative(state.padding, VAR_FALLBACKS.padding)),
|
||||
'--tool-code': inches(positive(state.codesize, VAR_FALLBACKS.codesize)),
|
||||
'--tool-barh': inches(positive(state.barheight, VAR_FALLBACKS.barheight)),
|
||||
'--tool-quiet': inches(nonNegative(state.quiet, VAR_FALLBACKS.quiet)),
|
||||
'--tool-gap': inches(nonNegative(state.gap, VAR_FALLBACKS.gap)),
|
||||
'--tool-font': positive(state.labelfont, VAR_FALLBACKS.labelfont) + 'pt',
|
||||
'--tool-align': alignment(state.align),
|
||||
'--tool-valign': alignment(state.valign),
|
||||
'--tool-content-x': inches(offset(state.contentx)),
|
||||
'--tool-content-y': inches(offset(state.contenty)),
|
||||
'--tool-nudge-x': inches(offset(state.nudgex)),
|
||||
'--tool-nudge-y': inches(offset(state.nudgey)),
|
||||
'--tool-pic': inches(positive(state.picturesize, VAR_FALLBACKS.picturesize)),
|
||||
'--tool-pic-opacity': String(
|
||||
Number.isFinite(opacity) && opacity > 0 && opacity <= 1
|
||||
? opacity : VAR_FALLBACKS.pictureopacity),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user