Most of the posts in this series are about decisions I made deliberately. This one is a little different -- it's partly about a decision I made deliberately, and partly about a piece of dead code I left sitting in the Barcode Generator's script.js that I want to be honest about instead of pretending it never happened.
I Never Considered Writing My Own Encoder
Barcode formats like Code128, EAN-13, and UPC-A have exact, specification-defined bit patterns -- get the checksum digit or the start/stop pattern wrong and you produce something that looks like a barcode but a real scanner will reject. This isn't a case where "close enough" is acceptable, so from the very first line of code I reached for JsBarcode, a mature library that already implements a dozen formats correctly, rather than attempting my own encoder:
My actual code is almost entirely UI plumbing around that one function call: reading form values, handling errors when the input doesn't fit the selected format, and wiring up the download buttons. That's a deliberate scope decision -- the interesting, error-prone part of "generate a barcode" is exactly the part I outsourced, because getting it subtly wrong would produce something worse than a bug: a barcode that looks fine on screen but fails at a checkout counter.
SVG First, Everything Else Is a Conversion
JsBarcode renders directly into an <svg> element, and I kept that as the single source of truth. The on-screen preview is that SVG. Downloading as SVG is a straight serialize-and-save. But PNG and PDF downloads don't have any native representation of the barcode -- they have to be produced by rendering the SVG onto a canvas first:
Loading the SVG into an <img> via a base64 data URL, then drawing that image onto a canvas, is the standard browser trick for rasterizing vector content without a server round-trip. It means every download format ultimately traces back to the same SVG, so a color or size change made in the UI is guaranteed to be reflected identically in every export -- there's no separate PNG-generation code path that could drift out of sync with the SVG one.
The Function That's Never Called
Here's the part I want to be upfront about. Sitting in the middle of script.js is a function called generateSimplePDF() that hand-builds a raw PDF file byte-by-byte -- PDF header, catalog object, page object, an image XObject, a cross-reference table, the works:
It is never invoked anywhere in the file. I built it during an earlier attempt at PDF export, before switching to the jsPDF library that downloadAsPDF() actually uses today. Looking back at it now, it's also just wrong -- it declares the embedded image with /Filter /DCTDecode, which means "this data is a JPEG," but it's fed base64 PNG data, which isn't JPEG-encoded at all. That mismatch means even if I did call it, most PDF readers would either reject the file or render garbage. I'm leaving it in this post as a reminder to myself: it's harmless dead code today because nothing references it, but "harmless because unused" is exactly the kind of thing that becomes a real bug the moment someone -- possibly future me -- calls it without checking whether it still makes sense.
Regenerating on Every Keystroke, With a Debounce
Almost every input on the page -- the data field, the format dropdown, the size fields, both color pickers -- triggers an automatic regeneration so the preview always matches the current settings. For the text input specifically, that's debounced by 500ms:
Without the debounce, typing "123456789012" would re-run the full JsBarcode render on every single keystroke -- twelve renders for one number. The 500ms delay means the render only fires once typing pauses, which is invisible to the user (nobody notices a half-second delay while they're still typing) but avoids doing eleven unnecessary renders. The dropdowns and color pickers don't need the same treatment because those fire on change, not input -- they only trigger once per discrete selection, so there's no rapid-fire event to debounce in the first place.
Two Inputs, One Source of Truth
Each color has both a swatch (<input type="color">) and a text field for typing a hex code directly, and they have to stay in sync in both directions. The swatch always produces a valid color, so it just writes straight into the text field. The text field accepts arbitrary typing, so it's validated with a regex before it's allowed to update the swatch or trigger a redraw:
Without that guard, typing "#ff" while still midway through "#ff0000" would try to set an invalid color value and either throw or silently do nothing useful -- the regex just waits until the typed value is a complete, valid hex color before it does anything with it.