labels: one module knows how to draw a code, seven views stop guessing
Three core pages and four plugin pages each imported qrcode and jsbarcode directly, and each carried its own answer to the same questions: what margin, what width, which error correction, how big a module must be before a scanner can read it. The answers had already drifted - margin 0 in one place and 2 in another, width 150 against 160 - and on a label that is the difference between a sticker that scans and one that does not. frontend/src/utils/codes.js owns it now: the label-stock presets, the quiet-zone and margin defaults, CODE128 with no printed value, and the printer-resolution arithmetic that only the Tech Tools generator had. A view passes what is specific to its own label and nothing else - MachineBadge still asks for CODE39, because the badge readers predate the shop-floor scanners and decode nothing else, and that is exactly the kind of thing a call site should say out loud. views/print/qrLogo.js is folded in rather than left as a second half-shared helper that only some of the pages reached into. The check script now fails a build that imports either library outside that module. Without it this re-forks within a month: the next label page starts by copying the nearest existing one, which is how it happened the first time. Tests cover the part no amount of looking at a screen verifies - a QR that looks fine at 96 dpi on a monitor can be unreadable at 203 dpi on half-inch stock.
This commit is contained in:
@@ -163,20 +163,14 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick } from 'vue'
|
||||
import QRCode from 'qrcode'
|
||||
import JsBarcode from 'jsbarcode'
|
||||
import { LABEL_PRESETS, qrModuleCount, qrFit, qrSvgDataUri, barcodeSvgDataUri }
|
||||
from '@/utils/codes'
|
||||
|
||||
// Rendering thousands of codes locks the tab, so stop at a number a person
|
||||
// would actually feed a label printer in one run and SAY what was dropped.
|
||||
const MAX_LABELS = 500
|
||||
|
||||
const PRESETS = [
|
||||
{ id: 'zebra1x05', name: '1.00 x 0.50 in (Zebra gap)', labelwidth: 1.0, labelheight: 0.5, codesize: 0.4286, padding: 0, quiet: 0.035, labelfont: 7 },
|
||||
{ id: 'zebra2x1', name: '2.00 x 1.00 in', labelwidth: 2.0, labelheight: 1.0, codesize: 0.85, padding: 0.03, quiet: 0.05, labelfont: 10 },
|
||||
{ id: 'zebra225x125', name: '2.25 x 1.25 in', labelwidth: 2.25, labelheight: 1.25, codesize: 1.05, padding: 0.04, quiet: 0.06, labelfont: 11 },
|
||||
{ id: 'zebra4x6', name: '4.00 x 6.00 in (shipping)', labelwidth: 4.0, labelheight: 6.0, codesize: 3.0, padding: 0.15, quiet: 0.12, labelfont: 20 },
|
||||
{ id: 'badge', name: '2.13 x 3.38 in (badge)', labelwidth: 2.13, labelheight: 3.38, codesize: 1.5, padding: 0.15, quiet: 0.1, labelfont: 12 },
|
||||
]
|
||||
const PRESETS = LABEL_PRESETS
|
||||
|
||||
const source = ref('single')
|
||||
const codetype = ref('qr')
|
||||
@@ -326,25 +320,20 @@ const visibleLabels = computed(() =>
|
||||
const qrModules = computed(() => {
|
||||
const content = labels.value[0]?.content
|
||||
if (!content || codetype.value !== 'qr') return 0
|
||||
try {
|
||||
return QRCode.create(content, { errorCorrectionLevel: errorcorrection.value }).modules.size
|
||||
} catch {
|
||||
return 0
|
||||
}
|
||||
return qrModuleCount(content, errorcorrection.value)
|
||||
})
|
||||
|
||||
const fitAdvice = computed(() => {
|
||||
const modules = qrModules.value
|
||||
if (!modules) return ''
|
||||
const dotsPerModule = (codesize.value * dpi.value) / modules
|
||||
const whole = Math.floor(dotsPerModule)
|
||||
if (whole < 2) {
|
||||
const fit = qrFit(labels.value[0]?.content, {
|
||||
sizeInches: codesize.value, dpi: dpi.value, errorCorrection: errorcorrection.value,
|
||||
})
|
||||
if (!fit || codetype.value !== 'qr') return ''
|
||||
const { modules, dotsPerModule, wholeDots: whole, snapped, moduleMm } = fit
|
||||
if (fit.tooSmall) {
|
||||
return `${modules} modules at ${dotsPerModule.toFixed(2)} dots each - too small to scan `
|
||||
+ `reliably. Shorten the content, drop error correction, or use bigger stock.`
|
||||
}
|
||||
const snapped = (modules * whole) / dpi.value
|
||||
const moduleMm = (whole / dpi.value) * 25.4
|
||||
if (Math.abs(snapped - codesize.value) < 0.002) {
|
||||
if (fit.even) {
|
||||
return `${modules} modules at exactly ${whole} dots each (${moduleMm.toFixed(3)} mm). Good.`
|
||||
}
|
||||
return `${modules} modules at ${dotsPerModule.toFixed(2)} dots each. Uneven - `
|
||||
@@ -361,30 +350,13 @@ const fitAdvice = computed(() => {
|
||||
//
|
||||
// margin 0 on the QR: the quiet zone is blank label supplied by --tool-quiet,
|
||||
// so none of the code box is spent on white we cannot then adjust.
|
||||
function svgDataUri(svg) {
|
||||
return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg)
|
||||
}
|
||||
|
||||
async function renderOne(row) {
|
||||
const text = row.content
|
||||
if (!text) return ''
|
||||
if (codetype.value === 'qr') {
|
||||
const svg = await QRCode.toString(text, {
|
||||
type: 'svg',
|
||||
errorCorrectionLevel: errorcorrection.value,
|
||||
margin: 0,
|
||||
})
|
||||
return svgDataUri(svg)
|
||||
return qrSvgDataUri(text, { errorCorrection: errorcorrection.value })
|
||||
}
|
||||
const element = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
||||
JsBarcode(element, text, {
|
||||
format: 'CODE128',
|
||||
displayValue: false,
|
||||
margin: 0,
|
||||
width: 2,
|
||||
height: 100,
|
||||
})
|
||||
return svgDataUri(new XMLSerializer().serializeToString(element))
|
||||
return barcodeSvgDataUri(text)
|
||||
}
|
||||
|
||||
let renderToken = 0
|
||||
|
||||
Reference in New Issue
Block a user