npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sythos/js_barcode_universal

v1.6.5

Published

Open-source MIT-licensed JavaScript/TypeScript barcode generator and barcode reader SDK with zero runtime dependencies for 1D and 2D barcodes in browsers, Web Workers and Node.js: QR, Data Matrix, Aztec, PDF417, GS1, EAN and more.

Readme

JS Barcode Universal SDK

Sythos Barcode Universal — MIT 1D/2D Barcode SDK

Open-source MIT-licensed JavaScript/TypeScript barcode generator and barcode reader SDK for encoding and decoding 1D linear and 2D matrix barcodes. It has zero runtime dependencies and runs in browsers, Web Workers and Node.js, or Bun as an alternative.

npm License: MIT Runtime dependencies: 0 ESM TypeScript

npm downloads Node Bun

Original Sythos implementation, zero runtime dependencies, MIT. It runs unmodified in Node, in Bun as an alternative runtime, in browsers (including Safari on iOS) and in web workers. The core requires no canvas, no filesystem and no DOM — images go in and come out as plain { data, width, height } RGBA objects, which is exactly what an ImageData is.

Bun is a lighter, faster alternative to Node.js for running this SDK: it executes the TypeScript sources under src/ts/ natively, with no separate compile step, and its JavaScriptCore-based runtime generally uses less memory and starts faster than Node's V8. See the Node.js or Bun guide.

The code is complete and entirely human-readable. The full source ships. There is no WebAssembly, no native addon, no compiled artefact, no binary blob and no minified file anywhere in this repository — every tracked file is text you can open and read.

That includes the prebuilt bundles. They are generated, concatenated and wrapped from src/ by the project's own bundler, but nothing is stripped in the process: bundle/sythos-barcode.js runs to roughly 7,900 lines, about a third of them comments, averaging a little over 30 characters a line. Open it anywhere and you are reading the same annotated code as the source, in the same order — a convenience, not a black box.

Don't take that on trust either; it takes one command:

awk '{ n += length($0) } END { print "lines:", NR, " avg length:", int(n/NR) }' bundle/sythos-barcode.js

A minified bundle gives you a handful of lines averaging thousands of characters. This one does not, and that is the whole point.

This is deliberate. A barcode library decides what a scanner believes a label says, so it belongs in the category of code you can audit rather than have to trust. Every constant table, every check digit and every error-correction step is here in full, with the reasoning next to it.

import { encode, decode, toSVG, toImageData } from './src/index.js';

const matrix = encode('https://example.com', { format: 'qr', ecc: 'M' });
const svg    = toSVG(matrix, { scale: 8 });

const found = decode(toImageData(matrix, { scale: 4 }), { formats: ['qr'] });
console.log(found[0].text);   // 'https://example.com'

Quick start

There are four ways in, and none of them needs a build step.

Full documentation lives at sythos.github.io/JS_Barcode_Universal.

1. npm

npm install @sythos/js_barcode_universal

yarn add @sythos/js_barcode_universal and pnpm add @sythos/js_barcode_universal do the same thing. Nothing is installed alongside it — there are no runtime dependencies, no postinstall script and no native build. The package is plain ESM ("type": "module") and asks for Node 24 or newer, or Bun as an alternative.

import { encode, decode, toSVG, toImageData } from '@sythos/js_barcode_universal';

const code = encode('SYT-2026-0042', { format: 'code128' });

const svg = toSVG(code, { scale: 2, margin: 10, barHeight: 60 });
// '<svg xmlns="http://www.w3.org/2000/svg" width="374" height="100" …'

const found = decode(toImageData(code, { scale: 4, margin: 10 }), { formats: ['code128'] });
console.log(found[0].text);   // 'SYT-2026-0042'

Subpath exports hand you one layer instead of the whole surface, which is what lets a tree-shaking bundler drop everything you did not ask for. Importing only the QR writer and only the SVG renderer never pulls in the 1D formats, the PNG encoder or the read pipeline:

import { encodeQR } from '@sythos/js_barcode_universal/qr';
import { toSVG }    from '@sythos/js_barcode_universal/render/svg';

const svg = toSVG(encodeQR('https://example.com', { ecc: 'M' }), { scale: 8 });
// a 25×25 module symbol — 264×264 px at scale 8 with the default 4-module quiet zone

| Subpath | What it exports | |---|---| | @sythos/js_barcode_universal | The whole surface: encode, decode, every renderer, every error type | | @sythos/js_barcode_universal/core | BitMatrix, GaloisField, Reed–Solomon, the error classes | | @sythos/js_barcode_universal/image | LuminanceSource, the binarizers, grid sampling, PerspectiveTransform | | @sythos/js_barcode_universal/oned | The per-format 1D writers (encodeEAN13, encodeCode128, encodeCode32, encodePZN, encodeTelepen, encodePostnet, encodePlanet, encodeRM4SCC, encodeKIX, encodeAustraliaPost, encodeJapanPost, encodeIMB, …), Code 25 variants and decodeOneD | | @sythos/js_barcode_universal/qr | encodeQR, decodeQR, detectQR, detectAndDecodeQR | | @sythos/js_barcode_universal/datamatrix | encodeDataMatrix, decodeDataMatrix, detectDataMatrix, detectAndDecodeDataMatrix | | @sythos/js_barcode_universal/aztec | encodeAztec, decodeAztec, detectAztec, detectAndDecodeAztec | | @sythos/js_barcode_universal/aztecrune | encodeAztecRune, decodeAztecRune, detectAztecRune, detectAndDecodeAztecRune | | @sythos/js_barcode_universal/pdf417 | encodePDF417, decodePDF417, detectPDF417, detectAndDecodePDF417 | | @sythos/js_barcode_universal/compactpdf417 | encodeCompactPDF417, decodeCompactPDF417, detectCompactPDF417, detectAndDecodeCompactPDF417 | | @sythos/js_barcode_universal/databar | GS1 DataBar GTIN/AI codecs plus Omnidirectional/Truncated, Limited, Stacked, Stacked Omnidirectional and Expanded physical helpers | | @sythos/js_barcode_universal/micropdf417 | encodeMicroPDF417, decodeMicroPDF417, detectMicroPDF417, detectAndDecodeMicroPDF417 | | @sythos/js_barcode_universal/microqr | encodeMicroQR, decodeMicroQR, detectMicroQR, detectAndDecodeMicroQR | | @sythos/js_barcode_universal/rmqr | encodeRMQR, decodeRMQR, detectRMQR, detectAndDecodeRMQR | | @sythos/js_barcode_universal/frameqr | encodeFrameQR, decodeFrameQR, detectFrameQR, detectAndDecodeFrameQR | | @sythos/js_barcode_universal/maxicode | encodeMaxiCode, decodeMaxiCode, detectMaxiCode, detectAndDecodeMaxiCode | | @sythos/js_barcode_universal/dotcode | encodeDotCode, decodeDotCode, detectDotCode, detectAndDecodeDotCode | | @sythos/js_barcode_universal/hanxin | encodeHanXin, encodeHanXinBytes, decodeHanXin, detectHanXin, detectAndDecodeHanXin | | @sythos/js_barcode_universal/composite | encodeGS1Composite, decodeGS1Composite, detectGS1Composite, detectAndDecodeGS1Composite | | @sythos/js_barcode_universal/kartrak | encodeKarTrak, decodeKarTrak, decodeKarTrakMatrix, detectKarTrak — experimental, colour-coded, not part of encode()/decode() | | @sythos/js_barcode_universal/jabcode | encodeJABCode, decodeJABCode, decodeJABCodeMatrix — experimental, colour-coded, not part of encode()/decode() | | @sythos/js_barcode_universal/color | PolychromeMatrix, toColorImageData, classifyGrid — the experimental colour primitives KarTrak and JAB Code are built on; not registered in listFormats(), no stability guarantee yet | | @sythos/js_barcode_universal/payloads | encodeVCard, encodeVIN, encodeSPARQCode, encodeSwissQR, encodeSEPAQR, encodeAAMVA (plus each build* counterpart) — structured payload conventions on top of qr, code39 and pdf417, not separate symbologies, not part of encode()/decode() | | @sythos/js_barcode_universal/render | Every renderer plus isWebGL2Available / isWebGPUAvailable | | @sythos/js_barcode_universal/render/svg | toSVG, toSVGDataURI | | @sythos/js_barcode_universal/render/png | toPNG, toPNGDataURI | | @sythos/js_barcode_universal/render/image-data | toImageData, toCanvas | | @sythos/js_barcode_universal/bundle | The prebuilt ESM bundle, as one file | | @sythos/js_barcode_universal/bundle/iife | The prebuilt IIFE bundle, for a <script> tag |

The unpkg and jsdelivr fields point at the IIFE bundle, so a CDN needs no install at all:

<script src="https://unpkg.com/@sythos/js_barcode_universal"></script>
<script src="https://unpkg.com/@sythos/[email protected]"></script>
<script src="https://cdn.jsdelivr.net/npm/@sythos/[email protected]"></script>

Pin the version for anything you ship; the unpinned form resolves to latest and will move under you.

Both CDNs serve the same file the repository ships in bundle/sythos-barcode.js — byte for byte, since that is exactly what npm publishes.

2. A <script> tag

bundle/sythos-barcode.js is a self-contained IIFE that exposes a single global, SythosBarcode. It works straight from file:// — open an HTML file off your disk and it runs.

<script src="bundle/sythos-barcode.js"></script>
<script>
  var encode = SythosBarcode.encode;
  var toSVGDataURI = SythosBarcode.toSVGDataURI;

  var img = new Image();
  img.src = toSVGDataURI(encode('https://example.com', { format: 'qr' }), { scale: 8 });
  document.body.appendChild(img);
</script>

3. ESM bundle

bundle/sythos-barcode.esm.js is the same code as a single ES module, for <script type="module">, a bundler, or Node.js or Bun as alternatives.

import { encode, toSVG, toPNG } from './bundle/sythos-barcode.esm.js';

const ean = encode('5901234123457', { format: 'ean13' });

const svg = toSVG(ean, { scale: 3, margin: 10, barHeight: 80 });
// '<svg xmlns="http://www.w3.org/2000/svg" width="345" height="141" …'

toPNG(ean, { scale: 3, barHeight: 80 }).then((bytes) => {
  // Uint8Array — a 1-bit palette PNG
});

4. The source directly

src/index.js is the generated ESM facade emitted from the TypeScript source tree. The source layout is deliberately explicit:

  • src/ts/ contains the TypeScript runtime sources and their adjacent machine-readable .d.ts declarations, including src/ts/index.ts and src/ts/index.d.ts.
  • src/js/ contains the compiled JavaScript runtime modules used by Node.js or Bun as alternatives, browsers and the CDN bundles.
  • src/index.js and src/index.d.ts are the stable package-root facades.

From the development workspace, npm run build:ts compiles src/ts/ into src/js/ and the root JavaScript facade; npm run build then regenerates both bundles. The published package keeps both source languages visible while retaining zero runtime dependencies.

The package subpath exports therefore pair compiled runtime modules under src/js/ with their TypeScript sources and declarations under src/ts/; direct source imports should continue to use the JavaScript facade above.

import { encode, decode, toImageData, listFormats } from './src/index.js';

const matrix = encode('https://example.com', { format: 'qr', ecc: 'M' });
const image  = toImageData(matrix, { scale: 4, margin: 4 });

const found = decode(image, { formats: ['qr'] });
// [ { text: 'https://example.com', format: 'qr', version: 2, ecc: 'M', … } ]

Decoding takes anything ImageData-shaped, so a canvas, an OffscreenCanvas, createImageBitmap, or an image library's raw buffer all satisfy it without an adapter:

const ctx = canvas.getContext('2d');
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);

for (const hit of decode(ctx.getImageData(0, 0, canvas.width, canvas.height))) {
  console.log(hit.format, hit.text);
}

decode returns an array, empty when nothing is found. A frame with no barcode is an ordinary outcome for a camera loop, not an error, so the common case needs no try/catch. Use decodeStrict when absence really is a failure.

Strict camera profile

For a live camera loop, opt into the stricter 1D policy:

decode(frame, { formats, profile: 'camera', tryHarder: true })

The profile requires a compatible quiet zone and the same complete 1D symbol on at least two scan samples. It evaluates the eight fixed in-plane orientations 0°, 45°, 90°, 135°, 180°, 225°, 270° and 315° when the native orientation has no validated read. The same orientation set is available to the supported 2D detector passes in the strict camera profile. Code 11 and MSI require a verified check digit in this profile; other formats retain their own structural and checksum validation. A frame without a validated barcode still returns []. No partial, structurally inconsistent or low-confidence value is emitted to the caller. This is a finite in-plane retry policy, not a guarantee for arbitrary perspective, curved media, severe occlusion or multi-symbol scenes.

Camera-profile 1D results add confidence (0–1), bounds, rotation, and quality: { quietZone, checksum, rows, consistency }. bounds is reported in the raster orientation that was scanned; unavailable quality data is represented by null where applicable.


Supported barcode formats

Generated from listFormats(), which reports writing and reading as separate capabilities. Writing a symbology is a table lookup; reading one needs a detector that finds it in a photograph. The two lists legitimately differ, and saying so here is better than failing at call time.

Kind is the literal '1D' | '2D' value format.kind returns from the API — it never changes. Structure is a descriptive grouping only, not part of the API: Linear (a single row), Stacked (multiple linear-derived rows, including PDF417-style and stacked GS1 DataBar variants), or Matrix (a true two-dimensional grid, e.g. QR or Data Matrix). A few formats are 2D by Kind but row-stacked rather than gridded (Codablock-F, Code 16K), and two are 1D by Kind but physically stacked (GS1 DataBar Stacked and Stacked Omnidirectional) — Structure exists to make that distinction visible.

| Format | id | Kind | Structure | Write | Read | |---|---|:---:|:---:|:---:|:---:| | AAMVA DL/ID data (payload convention on PDF417) | variant of pdf417 | 2D | Stacked | ✅ | ✅ [^9] | | Australia Post 4-State | auspost | 1D | Linear | ✅ | ✅ | | Aztec Code | aztec | 2D | Matrix | ✅ | ✅ | | Aztec Rune | aztecrune | 2D | Matrix | ✅ | ✅ | | Codabar | codabar | 1D | Linear | ✅ | ✅ | | Codablock-F | codablockf | 2D | Stacked | ✅ | ✅ | | Code 11 | code11 | 1D | Linear | ✅ | ✅ | | Code 128 | code128 | 1D | Linear | ✅ | ✅ | | Code 16K | code16k | 2D | Stacked | ✅ | ✅ | | Code 2 of 5 Data Logic (China Post) | datalogic2of5 | 1D | Linear | ✅ | ✅ | | Code 25 / Standard 2 of 5 | standard2of5 | 1D | Linear | ✅ | ✅ [^4] | | Code 32 (Italian Pharmacode) | code32 | 1D | Linear | ✅ | ✅ | | Code 39 | code39 | 1D | Linear | ✅ | ✅ | | Code 93 | code93 | 1D | Linear | ✅ | ✅ | | Compact PDF417 | compactpdf417 | 2D | Stacked | ✅ | ✅ | | Data Matrix ECC 200 | datamatrix | 2D | Matrix | ✅ | ✅ | | DotCode | dotcode | 2D | Matrix | ✅ | ✅ | | DX Film Edge Barcode | dxfilmedge | 1D | Linear | ✅ | ✅ | | EAN-13 | ean13 | 1D | Linear | ✅ | ✅ | | EAN-2 supplement | ean2 | 1D | Linear | ✅ | ✅ [^2] | | EAN-5 supplement | ean5 | 1D | Linear | ✅ | ✅ [^2] | | EAN-8 | ean8 | 1D | Linear | ✅ | ✅ | | Facing Identification Mark (FIM) | fim | 1D | Linear | ✅ | ✅ | | GS1 DataBar Composite (bounded Sythos profile) | gs1composite | 2D | Stacked | ✅ | ✅ | | GS1 DataBar Expanded | gs1databar-expanded | 1D | Linear | ✅ | ✅ | | GS1 DataBar Limited | gs1databar-limited | 1D | Linear | ✅ | ✅ | | GS1 DataBar Omnidirectional / Truncated | gs1databar14 | 1D | Linear | ✅ | ✅ | | GS1 DataBar Stacked | gs1databar-stacked | 1D | Stacked | ✅ | ✅ | | GS1 DataBar Stacked Omnidirectional | gs1databar-stacked-omnidirectional | 1D | Stacked | ✅ | ✅ | | GS1-128 | gs1128 | 1D | Linear | ✅ | ✅ [^1] | | Han Xin Code | hanxin | 2D | Matrix | ✅ | ✅ | | IATA 2 of 5 | iata2of5 | 1D | Linear | ✅ | ✅ [^4] | | Industrial 2 of 5 | industrial2of5 | 1D | Linear | ✅ | ✅ [^4] | | ISBN (Bookland) | isbn | 1D | Linear | ✅ | ✅ [^1] | | ITF (Interleaved 2 of 5) | itf | 1D | Linear | ✅ | ✅ | | ITF-14 | itf14 | 1D | Linear | ✅ | ✅ [^1] | | ITF-6 | itf6 | 1D | Linear | ✅ | ✅ [^5] | | JAB Code (colour, experimental — default mode profile) | jabcode | 2D | Matrix | ✅ | ✅ [^8] | | JAN (Japanese Article Number) | jan | 1D | Linear | ✅ | ✅ [^1] | | Japan Post 4-State | japanpost | 1D | Linear | ✅ | ✅ | | KarTrak ACI (colour, experimental — bounded Sythos profile) | kartrak | 1D | Linear | ✅ | ✅ [^6] | | KIX postal code | kix | 1D | Linear | ✅ | ✅ | | Matrix 2 of 5 | matrix2of5 | 1D | Linear | ✅ | ✅ | | MaxiCode | maxicode | 2D | Matrix | ✅ | ✅ | | Micro QR Code | microqr | 2D | Matrix | ✅ | ✅ | | MicroPDF417 | micropdf417 | 2D | Stacked | ✅ | ✅ | | MSI Plessey | msi | 1D | Linear | ✅ | ✅ | | PDF417 | pdf417 | 2D | Stacked | ✅ | ✅ | | Pharmacode | pharmacode | 1D | Linear | ✅ | — | | Plessey Code | plessey | 1D | Linear | ✅ | ✅ | | PostBar.C10 (Canada Post, internal) | postbarc10 | 1D | Linear | ✅ | ✅ [^7] | | PostBar.D22 (Canada Post, domestic) | postbard22 | 1D | Linear | ✅ | ✅ [^7] | | PostBar.G12 (Canada Post, international) | postbarg12 | 1D | Linear | ✅ | ✅ [^7] | | PZN-7 / PZN-8 | pzn | 1D | Linear | ✅ | ✅ | | QR Code | qr | 2D | Matrix | ✅ | ✅ | | rMQR Code | rmqr | 2D | Matrix | ✅ | ✅ | | Royal Mail 4-State (RM4SCC) | rm4scc | 1D | Linear | ✅ | ✅ | | SEPA / EPC QR Code (payload convention on QR) | variant of qr | 2D | Matrix | ✅ | ✅ [^9] | | SPARQCode (payload convention on QR) | variant of qr | 2D | Matrix | ✅ | ✅ [^9] | | Swiss QR-bill (payload convention on QR) | variant of qr | 2D | Matrix | ✅ | ✅ [^9] | | Sythos Canvas QR profile — not DENSO FrameQR® compatible | frameqr | 2D | Matrix | ✅ | ✅ | | Telepen (ASCII and Numeric) | telepen | 1D | Linear | ✅ | ✅ [^3] | | UPC-A | upca | 1D | Linear | ✅ | ✅ | | UPC-E | upce | 1D | Linear | ✅ | ✅ | | USPS Intelligent Mail (IMb / OneCode) | imb | 1D | Linear | ✅ | ✅ | | USPS PLANET | planet | 1D | Linear | ✅ | ✅ | | USPS POSTNET | postnet | 1D | Linear | ✅ | ✅ | | vCard (payload convention on QR) | variant of qr | 2D | Matrix | ✅ | ✅ [^9] | | VIN (Vehicle Identification Number, payload convention on Code 39) | variant of code39 | 1D | Linear | ✅ | ✅ [^9] |

This table lists sixty-nine formats and variants: sixty-nine are writable and sixty-eight are readable. Sixty-one of those go through the counted listFormats() registry and the top-level encode()/decode() dispatcher — sixty-one writable, sixty readable there specifically (EAN-2 and EAN-5 are parent-bound supplements). The remaining eight rows reach that same total two different ways. KarTrak ACI and JAB Code are colour-coded, not black/white, so neither can go through BitMatrix-based encode()/decode() at all — each ships only as its own subpath (@sythos/js_barcode_universal/kartrak, @sythos/js_barcode_universal/jabcode). The six rows marked "variant of qr/code39/pdf417" (AAMVA, SEPA/EPC QR, SPARQCode, Swiss QR-bill, vCard and VIN) are not symbologies at all, so none has an id of its own to register — each is a payload-convention helper that builds a structured payload string and hands it to the named parent format's own encoder, from @sythos/js_barcode_universal/payloads, see footnote 9. Neither group goes through listFormats(), which is why the registry's own count stays sixty-one/sixty even as this table grows. Pharmacode remains intentionally write-only in the generic image pipeline. Code 11 and MSI Plessey use the scanline reader. Telepen supports both its full seven-bit ASCII mode and explicit Numeric pair mode; Numeric reads must request formats: ['telepennumeric'] so digit pairs are never guessed as ASCII control characters. The Code 25 family shares one numeric digit grammar while exposing explicit Standard/Industrial and IATA guard profiles; Code 2 of 5 Data Logic (China Post) and Matrix 2 of 5 share a different, width-modulated digit grammar and both reject the 2:1 wide:narrow ratio, which collides with a different valid reading once the symbol is mirrored — they differ only in their guard frame. Code 32 and PZN validate their pharmaceutical check digits before a read is returned. Plessey Code (plessey), the format MSI Plessey descends from, encodes hexadecimal payloads and always validates a mandatory two-nibble CRC-8 check — unlike MSI's optional check digit, an unchecked Plessey read does not exist. Facing Identification Mark (fim) is not a general data carrier: it selects one of five fixed USPS-defined nine-position patterns (A-E), each a palindrome, so there is no reversed-read ambiguity between them. Postal formats use operator-specific four-state alphabets with strict framing and checksum validation; KIX deliberately has no check character, while Australia Post supports explicit character or numeric customer-data groups and IMb accepts its four legal payload lengths. The GS1 DataBar physical variants use strict clean-raster readers and variant-specific detectors over the verified GTIN/GS1 element-string decoder. Expanded also accepts the common compressed GTIN-14 method on the read path. MaxiCode uses a fixed 30×33 matrix and a clean binary-raster detector for one prominent symbol. PDF417 exposes direct matrix decoding, automatic camera localization and an assisted quadrilateral sampler through its subpath. Its detector is validated on degraded synthetic photographs and real Pixel 10/Chrome and iPhone 17/Safari camera tests; external black-box vectors from ZXing 3.5.3 and bwip-js also pass in both directions. Text and Numeric vectors are covered bidirectionally; binary byte-for-byte interop remains explicitly unclaimed until a dedicated external byte corpus is added.

Codablock-F uses stacked Code 128 rows with per-row modulo-103 checks and two overall modulo-86 checks. The writer chooses a compact 2–44 row layout (or accepts explicit rows and columns), while the detector returns a result only when every expected row and check agrees:

import { encodeCodablockF, detectAndDecodeCodablockF } from '@sythos/js_barcode_universal/codablockf';

const matrix = encodeCodablockF('STACKED ORDER 12345', { rows: 3, columns: 12 });
const hit = detectAndDecodeCodablockF(matrix);
console.log(hit?.text, hit?.rows, hit?.columns);

The clean integer-scale detector is designed to reject incomplete or damaged rows. It does not promise arbitrary perspective, severe occlusion or multi-symbol camera scenes. See docs/formats/codablockf.md and licenses/codablockf.license.

Code 16K keeps the Code 128 A/B/C data sets in a compact stacked symbol. The writer supports 2–16 rows, five Code 128 symbols per row, optional GS1 modes and explicit row or separator heights. The reader validates every row, both modulo-107 check characters and the complete geometry before returning text:

import { encodeCode16K, detectAndDecodeCode16K } from '@sythos/js_barcode_universal/code16k';

const matrix = encodeCode16K('INVENTORY 123456', { mode: 'B', rows: 3 });
const hit = detectAndDecodeCode16K(matrix);
console.log(hit?.text, hit?.rows, hit?.mode);

The detector accepts clean integer-scale rasters and orthogonal rotations. It rejects missing rows, altered modules, invalid checks and ambiguous geometry; arbitrary perspective and multi-symbol camera scenes remain outside the validated envelope. See docs/formats/code16k.md and licenses/code16k.license.

DotCode uses an alternating dot grid rather than a solid finder pattern. The writer and reader cover the bounded five-of-nine pattern set, four masks, prime-field Reed–Solomon correction, UTF-8 and byte payloads, with optional GS1/FNC1 handling:

import { encodeDotCode, detectAndDecodeDotCode } from '@sythos/js_barcode_universal/dotcode';

const matrix = encodeDotCode('DOTCODE ORDER 123', { width: 29, height: 30, mask: 1 });
const hits = detectAndDecodeDotCode(matrix.withMargin(3).scale(2), { moduleSize: 2 });
console.log(hits[0]?.text, hits[0]?.moduleSize);

The detector is intentionally strict: it accepts complete clean binary rasters at integer scale, quarter-turn orientations and either polarity, then returns only a checksum-validated symbol. Perspective, curved media, severe blur and multi-symbol scenes remain outside the supported profile. See docs/formats/dotcode.md and licenses/dotcode.license.

Han Xin Code is available as a bounded compact profile covering versions 1–3, the four L1–L4 error-correction levels, numeric/text/byte modes, four masks and strict Reed–Solomon validation. The root dispatcher accepts both format: 'hanxin' and the compatibility alias format: 'han-xin'; the focused subpath is useful when byte payloads or format metadata are needed:

import {
  encodeHanXin,
  detectAndDecodeHanXin,
} from '@sythos/js_barcode_universal/hanxin';

const matrix = encodeHanXin('HAN XIN 2026', { mode: 'text', ecc: 'L2' });
const hit = detectAndDecodeHanXin(matrix.withMargin(3).scale(2));
console.log(hit?.text, hit?.version, hit?.moduleSize);

The Han Xin detector accepts one complete, axis-aligned symbol at an integer module scale and either polarity. Versions 4–84, Chinese/GB18030 compaction, ECI, perspective correction and multi-symbol camera scenes remain outside this profile. See docs/formats/hanxin.md and licenses/hanxin.license.

GS1 DataBar Composite is available as the bounded gs1composite profile. It links one validated DataBar host to one strict CC-A or CC-B component and requires the complete geometry, linkage flag, private profile marker and shared integer module scale to validate before returning data:

import {
  encodeGS1Composite,
  detectAndDecodeGS1Composite,
} from '@sythos/js_barcode_universal/composite';

const matrix = encodeGS1Composite({
  linear: { format: 'databar14', value: '00012345678905' },
  data: '(01)09506000134352(17)260101',
});
const hit = detectAndDecodeGS1Composite(matrix.withMargin(3).scale(2));
console.log(hit?.text, hit?.linearFormat, hit?.component);

This is an original Sythos engineering profile, not a claim of complete ISO/IEC 24723 certification or universal scanner interoperability. Its full limits and legal boundary are in docs/formats/gs1-composite.md and licenses/gs1-composite.license.

[^1]: itf14, isbn and jan share a decoder with their base format, so an ITF-14 comes back as itf, an ISBN as ean13, and a JAN as ean13. GS1-128 is classified separately as gs1128 when its leading FNC1 is present and exposes gs1, symbologyIdentifier and parsed elements metadata. The payload is intact either way — an ITF-14 is an ITF fixed at fourteen digits, an ISBN barcode is an EAN-13 with a 978/979 prefix, and a JAN barcode is an EAN-13 with a 45/49 prefix.

[^2]: EAN-2 and EAN-5 are recognized only when attached to a validated EAN-13, EAN-8, UPC-A or UPC-E parent; they are not independent generic retail-symbol readers.

[^3]: Telepen Numeric is an explicit mode because its compact digit-pair glyphs share the same guards as Telepen Alpha. Use format: 'telepennumeric' when encoding or formats: ['telepennumeric'] when reading.

[^4]: Code 25/Standard 2 of 5 and Industrial 2 of 5 use the canonical Industrial frame in this SDK, so — as with footnote 1 — a Standard 2 of 5 read comes back reported as industrial2of5; the payload is intact, only the reported id differs. IATA 2 of 5 uses its shorter guard frame and is reported under its own iata2of5 id. Check digits are optional for ordinary reads and are required by the strict camera profile.

[^5]: itf6 is not a separate symbology — it is the same ITF grammar validated as exactly six digits with a mandatory check digit. Unlike footnote 1's formats, it keeps its own itf6 id rather than folding into itf: requesting formats: ['itf', 'itf6'] (or leaving formats unset) on a valid ITF-6 symbol legitimately returns both an itf result and a validated itf6 result for the same payload, the same way a Code 32 symbol returns both code32 and its Code 39 carrier. Requesting itf6 alone returns nothing for a six-digit ITF fragment whose check digit does not validate.

[^6]: KarTrak ACI (AAR Automatic Car Identification, 1967-1977) encodes each of 13 stacked label lines as which of four colours (blue, checkerboard/white, red, black) appears in its two stripes, not by bar width — so it needs PolychromeMatrix, not BitMatrix, and is not reachable through encode()/decode(), listFormats() or the formats: allow-list. Use encodeKarTrak/decodeKarTrak/detectKarTrak from @sythos/js_barcode_universal/kartrak directly. Reading is scoped honestly: detectKarTrak locates one axis-aligned plate against a roughly uniform background — no rotation or perspective search, the same "clean single-symbol" boundary already documented for MaxiCode. See docs/formats/kartrak.md and docs/COLOR_PIPELINE_NOTES.md for the full picture, including what real-world validation is still outstanding.

[^7]: Canada Post's own PostBar engineering specification is not published; this SDK implements the C10 (internal), D22 (customer-applied domestic) and G12 (international) profiles from the technical disclosure in US Patent 5,602,382A (expired), verified against the patent's own fully worked examples. postbard07/postbard12/postbarg22/postbars06/postbars11/postbars21 are documented by the same patent but not implemented — see docs/formats/postbar.md. Reading corrects errors via the format's own Reed-Solomon check (over GF(64)) before returning a result.

[^8]: JAB Code (ISO/IEC 23634:2022, Fraunhofer SIT) encodes several bits per module as one of several module colours rather than bar width, so — like KarTrak — it needs PolychromeMatrix, not BitMatrix, and is not reachable through encode()/decode(), listFormats() or the formats: allow-list. Use encodeJABCode/decodeJABCode/decodeJABCodeMatrix from @sythos/js_barcode_universal/jabcode directly. This is the reference encoder's own "default mode" profile (8 colours, ECC level 3, mask type 7, no metadata Part I/II, byte-mode-only data encoding, single symbol) — not the full ISO specification, and reading requires the symbol's corners to already be known, the same "known geometry" boundary as KarTrak (no detector exists for this format yet). See docs/formats/jabcode.md and docs/JABCODE_NOTES.md for the full scope, including the PRNG bit-exactness limitation and what real-world validation is still outstanding.

[^9]: None of these six rows is a barcode symbology of its own, and none has a registry id or goes through encode()/decode()/listFormats() — each is a structured text/data convention written as the payload of an already-listed symbology (qr, code39 or pdf417) and read back through that symbology's own decoder. Use them from @sythos/js_barcode_universal/payloads: encodeVCard/encodeSwissQR/encodeSEPAQR/encodeSPARQCode build a QR payload (with decodeVCard/decodeSwissQR/decodeSEPAQR/decodeSPARQCode reading each back into the same structured fields), encodeVIN a Code 39 payload (read back with validateVIN plus the existing Code 39 decoder — there is no separate decodeVIN), encodeAAMVA a PDF417 payload (with decodeAAMVA reading it back). "SPARQCode" (a MSKYNET/Yahoo-era product name) named a curated set of already-public conventions (structured URLs, phone numbers, WiFi config, vCard-style contacts, and similar), not a bit-level format of its own — encodeSPARQCode implements those same public conventions directly. See docs/formats/qr-family.md, docs/formats/oned.md and docs/formats/pdf417-family.md for each convention's own documentation, and licenses/payload-conventions.license for provenance and what was verified for each (including a real worked-example cross-check for the Swiss QR-bill and AAMVA check-digit/offset algorithms, whose reference tables are undocumented as text in their own source PDFs). AADHAAR was investigated for the same treatment and deliberately left out — see docs/guides/legal-exclusions.md.

Code 11 and MSI Plessey image reading

The generic image pipeline now recognizes Code 11 and MSI Plessey through the existing scanline reader. Check-digit validation is opt-in with decode(image, { checkDigit: true }); without that option the physical grammar is still required, while the literal check character is preserved for MSI and reliably stripped for Code 11 when its C/K grammar is unambiguous. The reader keeps Pharmacode write-only because its unframed narrow/wide grammar is not safe for unrestricted image autodetection.

Telepen

Telepen is available from the oned subpath and the root dispatcher. The default telepen format carries full seven-bit ASCII with an even parity bit and a modulo-127 check value. The explicit telepennumeric format compacts digit pairs and allows X only as the second character of a pair. Both writers enforce the format grammar, a 500-character input limit and complete start/check/stop structure.

import { encodeTelepen, encodeTelepenNumeric } from '@sythos/js_barcode_universal/oned';
import { decode, toImageData } from '@sythos/js_barcode_universal';

const alpha = encodeTelepen('TELEPEN-ASCII');
const numeric = encodeTelepenNumeric('00112738999X');
const image = toImageData(alpha, { scale: 3, margin: 30, barHeight: 64 });
const [hit] = decode(image, { formats: ['telepen'] });
console.log(hit?.text); // TELEPEN-ASCII

For Numeric symbols, select the numeric identifier explicitly:

const numericImage = toImageData(numeric, { scale: 3, margin: 30, barHeight: 64 });
const [numericHit] = decode(numericImage, { formats: ['telepennumeric'] });
console.log(numericHit?.text); // 00112738999X

The scanline reader measures the complete symbol at scale-independent run widths, rejects ambiguous candidates, verifies parity and the modulo-127 check value, and returns no partial result. A camera-profile read additionally requires a coherent quiet-zone-qualified symbol across repeated scan samples.

Code 25, Industrial 2 of 5, IATA 2 of 5, Data Logic 2 of 5 and Matrix 2 of 5

The Code 25 family is available from both the root dispatcher and the oned subpath. standard2of5 (also code2of5) and industrial2of5 use the same canonical two-wide-bar frame here; iata2of5 uses the shorter IATA guard. The optional modulo-10 check digit is appended by the writer and can be required by the reader or by the strict camera profile:

import { decode, encode, toImageData } from '@sythos/js_barcode_universal';

const matrix = encode('01234567', {
  format: 'industrial2of5',
  checkDigit: true,
  wideRatio: 3,
});
const [hit] = decode(toImageData(matrix, {
  scale: 3,
  margin: 30,
  barHeight: 64,
}), {
  formats: ['industrial2of5'],
  checkDigit: true,
});
console.log(hit?.text); // 01234567

Use format: 'iata2of5' for IATA framing. The reader validates the complete start/data/stop structure and rejects clipped or ambiguous candidates; a camera read additionally requires a quiet zone and a valid check digit.

format: 'datalogic2of5' (aliases: data-logic-2-of-5, chinapost, china-post) uses a different, width-modulated digit grammar and the shorter IATA-style guard. Its wideRatio accepts 3..8, not 2..8: a 2:1 wide:narrow ratio makes this specific digit table's mirrored reading collide with a different valid full-length reading, so both the writer and reader reject it. Reads shorter than five digits without a check digit are rejected as not distinctive enough to trust.

format: 'matrix2of5' (alias: matrix-2-of-5) shares that exact width-modulated digit grammar with Data Logic 2 of 5 but uses its own, longer guard frame — the same wideRatio: 3..8 restriction and five-digit minimum apply, for the same reason (the digit table, not the guard, is what creates the mirrored-reading collision at 2:1).

Code 32 and PZN

Code 32 (the Italian pharmaceutical code) and PZN-7/PZN-8 are explicit, check-digit-validated pharmaceutical formats. Code 32 accepts an eight-digit body (or the same body with its validated check digit) and renders the compact base-32 payload through the Code 39 carrier. PZN accepts six digits for PZN-7 or seven digits with { pzn8: true } for PZN-8; the decoder exposes pznVariant.

import { decode, encode, toImageData } from '@sythos/js_barcode_universal';

const code32 = encode('01234567', { format: 'code32' });
const pzn8 = encode('1234567', { format: 'pzn8' });
const image = toImageData(pzn8, { scale: 3, margin: 30, barHeight: 64 });
const [result] = decode(image, { formats: ['pzn8'] });
console.log(result?.format, result?.pznVariant, result?.text);
// pzn pzn8 1234567

Both readers return no value when the carrier or pharmaceutical check digit is not valid. The format-specific engineering notes and the independent black-box validation boundary are recorded in licenses/.

Facing Identification Mark (FIM)

FIM is not a general-purpose data carrier. It selects one of five fixed, USPS-defined nine-position patterns (A through E) printed near the upper edge of a mailpiece to tell automated facing equipment the mail class. Every pattern is a palindrome and always starts and ends with a bar, so a mirrored read never resolves to a different type:

import { decode, encode, toImageData } from '@sythos/js_barcode_universal';

const matrix = encode('C', { format: 'fim' });
const [result] = decode(toImageData(matrix, {
  scale: 3,
  margin: 20,
  barHeight: 40,
}), {
  formats: ['fim'],
});
console.log(result?.text); // C

encodeFIM/decodeFIM are also exported from the oned subpath. The reader requires a substantial leading and trailing quiet zone before promoting a match; this was tuned against an adversarial sweep of random noise and repeating textures during implementation, since a nine-module pattern is short enough that a naive scale-invariant matcher would otherwise false-match unrelated camera content.

ITF-6

ITF-6 is not a separate symbology: it is the existing ITF grammar constrained to exactly six digits (five significant digits plus a mandatory modulo-10 check digit), the JIS X 0502 add-on printed alongside ITF-14/ITF-16 for item quantity or container weight. It reuses the same check digit routine already used for ITF-14 and EAN:

import { decode, encode, toImageData } from '@sythos/js_barcode_universal';

const matrix = encode('12345', { format: 'itf6' }); // check digit appended
const [result] = decode(toImageData(matrix, {
  scale: 3,
  margin: 20,
  barHeight: 40,
}), {
  formats: ['itf6'],
});
console.log(result?.text); // 123457

Because ITF-6 shares its grammar with plain ITF, an unrestricted or ['itf', 'itf6'] read of a valid ITF-6 symbol legitimately returns both an itf result and a validated itf6 result — see footnote 5. Requesting itf6 alone returns nothing unless the check digit is valid, which is the only thing distinguishing a genuine ITF-6 read from an ordinary six-digit ITF fragment.

Plessey Code

Plessey Code (1971, Plessey Company) is the original format that Modified Plessey/MSI Plessey (already shipped here as msi) is a variant of. It encodes hexadecimal payloads (0-9, A-F) and always appends a mandatory two-nibble CRC-8 check — there is no unchecked mode, unlike MSI's optional check digit:

import { decode, encode, plesseyCheckDigits, toImageData } from '@sythos/js_barcode_universal';

console.log(plesseyCheckDigits([1, 2, 3, 4, 5])); // [6, 14] -> "6E"

const matrix = encode('12345', { format: 'plessey' });
const [result] = decode(toImageData(matrix, {
  scale: 3,
  margin: 20,
  barHeight: 40,
}), {
  formats: ['plessey'],
});
console.log(result?.text); // 12345

The reader validates the CRC before returning a value; a damaged symbol or one with an incorrect check simply does not decode as Plessey.

Postal 4-state formats

The postal family shares a strict height-coded raster classifier while keeping each operator's alphabet and checksum separate. The root dispatcher and the oned subpath expose USPS POSTNET and PLANET, Royal Mail RM4SCC, Dutch KIX, Australia Post, Japan Post and USPS Intelligent Mail (IMb/OneCode):

import { decode, encode, toImageData } from '@sythos/js_barcode_universal';

const matrix = encode('5956439111ABC', {
  format: 'auspost',
  customerEncoding: 'character',
});
const image = toImageData(matrix, { scale: 3, margin: 24, barHeight: 72 });
const [result] = decode(image, {
  formats: ['auspost'],
  customerEncoding: 'character',
});
console.log(result?.format, result?.text, result?.checkDigit);
// auspost 5956439111ABC true

POSTNET accepts 5/9/11 body digits and PLANET 11/13; both append and verify a Mod-10 check digit. RM4SCC generates and verifies its row/column check character, while KIX has no check character. Japan Post expands its public alphabet into fixed groups and verifies a Mod-19 check. Australia Post starts with an FCC and eight-digit DPID, supports customerEncoding: 'character' or 'numeric' (also custinfoenc) and verifies GF(64) parity. IMb accepts exactly 20, 25, 29 or 31 digits and verifies its frame check sequence. Convenience aliases such as onecode, usps-postnet and royal-mail resolve to the canonical ids shown in the format table.

The generic image reader returns an empty array for a clipped, ambiguous or checksum-invalid symbol. profile: 'camera' additionally requires a measurable quiet zone on both sides of the bars; it is intentionally conservative for blurred, curved, heavily occluded or multi-symbol photographs. See the postal format guide for payload limits, direct subpath functions and the complete provenance note.

Data Matrix ECC 200

datamatrix writes and reads the 30 classic ECC 200 square and rectangular symbol sizes. The encoder supports ASCII compaction (including numeric pairs), Base256 binary payloads, automatic or forced square/rectangular shape, Reed–Solomon error correction and GS1 FNC1 in the first position. DMRE is not included.

import { encodeDataMatrix, decodeDataMatrix } from '@sythos/js_barcode_universal/datamatrix';

const symbol = encodeDataMatrix('0101234567890128', { gs1: true, shape: 'square' });
const result = decodeDataMatrix(symbol);
console.log(result.text, result.gs1); // 0101234567890128 true

Binary content is accepted as a Uint8Array with encoding: 'base256'. Base256 string input uses UTF-8 with ECI 26; the returned text preserves accented characters and emoji. Uint8Array input remains binary: use returned bytes for its exact payload, while text is a one-character-per-byte Latin-1 view without charset guessing. The current high-level decoder handles ASCII and Base256 codewords; C40, Text, X12 and EDIFACT input symbols are not yet decoded. The current detector accepts axis-aligned square or rectangular symbols; with profile: 'camera', the decode pipeline evaluates the eight fixed in-plane orientations at 45° steps. Arbitrary perspective and perspective-skewed Data Matrix photographs are not yet guaranteed.

Aztec Code

aztec writes Compact layers 1–4 and Full layers 1–32, selecting a fitting symbol automatically unless layers and compact are forced. It supports the five Aztec text tables and UTF-8 byte payloads through Binary Shift, with eccPercent (default 23) controlling the requested error-correction level. ECI is not yet a configurable public option.

import { encodeAztec, decodeAztec } from '@sythos/js_barcode_universal/aztec';

const symbol = encodeAztec('Greetings My Lord Sythos  👋', { eccPercent: 23 });
const result = decodeAztec(symbol);
console.log(result.text); // Greetings My Lord Sythos  👋

The image detector handles the eight fixed camera-profile orientations, inverted polarity and quadrilateral sampling around the central bull’s-eye. Severe photographic perspective remains an interoperability and robustness gate rather than a guaranteed capability.

PDF417 (writer, matrix decoder and camera reader)

pdf417 supports PDF417 Text, Byte and Numeric compaction, ECI 3 (ISO-8859-1) and ECI 26 (UTF-8), ECC levels 0–8, row-height inference and Reed–Solomon erasure correction. The direct matrix decoder is available from @sythos/js_barcode_universal/pdf417.

The image helper handles clean module-aligned raster symbols, integer scale, fixed 45°-step camera orientations, automatic perspective estimation, mild blur/noise and an application-supplied quadrilateral. Results expose bytes and ordered segments for byte-preserving payloads. Real device validation covers Pixel 10/Chrome and iPhone 17/Safari with printed symbols and continuous camera capture. Extreme glare, severe occlusion, curved media and multi-symbol scenes remain outside the validated robustness envelope.

import { encodePDF417, decodePDF417 } from '@sythos/js_barcode_universal/pdf417';

const symbol = encodePDF417('AAMVA SAMPLE', { eccLevel: 3 });
console.log(decodePDF417(symbol).text);

MicroPDF417

micropdf417 writes and reads the 34 fixed MicroPDF417 variants. It supports Text, Byte and Numeric compaction, plus Byte-compaction ECI 3 (ISO-8859-1) and 26 (UTF-8). columns, rowHeight and aspectRatio let callers constrain automatic variant selection.

import { encodeMicroPDF417, decodeMicroPDF417 } from '@sythos/js_barcode_universal/micropdf417';

const symbol = encodeMicroPDF417('MICRO PDF417', { compaction: 'text' });
console.log(decodeMicroPDF417(symbol).text);

The detector accepts clean, integer-scaled raster symbols and the eight fixed camera-profile orientations. Arbitrary perspective, severe photographic degradation and multi-symbol scenes are not yet claimed as robust capabilities.

Micro QR Code

microqr implements the M1–M4 family with Numeric, Alphanumeric, ISO-8859-1 Byte and Kanji payloads, BCH format protection, the four Micro QR masks and Reed–Solomon correction. M1 is detection-only. ECI, FNC1/GS1 and Structured Append are intentionally outside the current API. The detector accepts clean scaled rasters, the eight fixed camera-profile orientations, inverted polarity and mild projective sampling, and rejects normal QR Model 2 symbols. Arbitrary perspective and curved-media robustness are not claimed.

import { encodeMicroQR, decodeMicroQR } from '@sythos/js_barcode_universal/microqr';

const symbol = encodeMicroQR('12345', { version: 'M2', ecc: 'L' });
console.log(decodeMicroQR(symbol).text);

rMQR Code

rmqr implements all 32 standard rectangular geometries, M/H ECC, Numeric, Alphanumeric, Byte, Kanji and ECI payloads. The detector accepts clean integer-scaled rasters, quiet zones and the eight fixed camera-profile orientations; arbitrary photographic perspective and multi-symbol scenes are not claimed.

import { encodeRMQR, decodeRMQR } from '@sythos/js_barcode_universal/rmqr';

const symbol = encodeRMQR('rMQR SAMPLE', { ecc: 'M' });
console.log(decodeRMQR(symbol).text);

Sythos Canvas QR profile

frameqr is an explicitly scoped, non-certified Sythos Canvas QR profile — not DENSO FrameQR® compatible. It reserves a bounded square, circle or diamond artwork canvas inside an ECC-H QR Model 2 symbol. It is not a native DENSO FrameQR encoder or decoder, and the package makes no DENSO interoperability claim. The profile can be read from clean rendered rasters and is exposed through the normal encode/decode API and the frameqr subpath.

import { encodeFrameQR, decodeFrameQR } from '@sythos/js_barcode_universal/frameqr';

const symbol = encodeFrameQR('https://www.sythos.net/', {
  canvas: { shape: 'square', size: 5 },
});
console.log(decodeFrameQR(symbol).text);

The canonical examples/create.html preview loads https://www.sythos.net/favicon.ico and falls back to https://www.sythos.net/apple-touch-icon.png. The image is never copied into the repository; if browser CORS prevents safe compositing, the page keeps a preview overlay and exports the QR symbol without embedding the remote artwork.

Aztec Rune, Compact PDF417 and EAN supplements

aztecrune implements the fixed 11×11 Rune values 0–255 with clean raster detection, inversion and the eight fixed camera-profile orientations. Its matrices were compared exhaustively with ZXing-C++ as an independent black-box runtime; no ZXing source or table is shipped.

compactpdf417 implements the truncated PDF417 geometry with Text, Byte and Numeric compaction. It has a clean raster detector and direct matrix decoder.

EAN-2 and EAN-5 are writable supplements exposed by the oned subpath and by the generic ean2 and ean5 format IDs. The image reader recognizes them only when attached to a validated EAN/UPC parent; use the composition helpers with an EAN/UPC base symbol.

EAN-2 and EAN-5 are parent-bound supplements, never standalone image results. They may be listed with EAN-13, EAN-8, UPC-A, UPC-E or Bookland ISBN in formats: the parent remains valid without a supplement, and a valid requested supplement is exposed only through result.addon. If only ean2 or ean5 is requested, a validated EAN/UPC parent is still required and remains the returned format; an absent, malformed or unrequested supplement never rejects the parent.

GS1 DataBar

The databar subpath exposes original GS1 GTIN/AI codecs and five physical variants: Omnidirectional and Truncated through encodeDataBar14, Limited through encodeDataBarLimited, Stacked through encodeDataBar14Stacked, Stacked Omnidirectional through encodeDataBarStackedOmnidirectional, and linear Expanded through encodeDataBarExpanded. The physical paths encode the fixed GS1 (01) GTIN element and can carry the standard composite linkage flag. The complete bounded composition is exposed separately through the composite subpath.

The clean readers require a complete dark-on-light or inverted binary raster, integer module scaling and valid checksum/guard structure. The Limited and Stacked readers accept quarter turns; the Stacked Omnidirectional reader accepts the same clean integer-scaled geometry. Their detectors intentionally reject partial symbols, arbitrary perspective and grayscale input. Use a binarized image or call the matrix decoder after an application-owned perspective sample.

The Stacked variant uses a 50-module row with a five-module top row, one-module separator and seven-module bottom row. Stacked Omnidirectional uses two 50-module rows, a three-module separator and a minimum 33-module row height. Limited uses a 79-module row and a minimum 10-module output height; its accepted GTIN indicator is 0 or 1. Expanded uses the linear finder sequence and constrained 17-module data characters defined by GS1 DataBar; its writer emits the general- purpose method and its reader also accepts the common compressed GTIN-14 method. These geometry constraints are part of the format API, not an interoperability claim for arbitrary photographs.

import {
  encodeDataBarLimited,
  encodeDataBar14Stacked,
  encodeDataBarStackedOmnidirectional,
} from '@sythos/js_barcode_universal/databar';

const limited = encodeDataBarLimited('01234567890128', { moduleScale: 2 });
const stacked = encodeDataBar14Stacked('01234567890128');
const stackedOmni = encodeDataBarStackedOmnidirectional('01234567890128');

For a GS1 element string, use the Expanded helper. It returns a clean linear symbol without a quiet zone, just like the other DataBar writers:

import {
  decodeDataBarExpanded,
  encodeDataBarExpanded,
} from '@sythos/js_barcode_universal/databar';

const expanded = encodeDataBarExpanded('(01)09506000134352(10)ABC-123');
const decoded = decodeDataBarExpanded(expanded);
console.log(decoded.elements, decoded.linkage);

Expanded reading accepts a complete dark-on-light or inverted binary raster, integer module scaling and a valid finder/checksum structure. The detector is deliberately conservative: it handles a single clean linear symbol (including quarter turns) and rejects partial, ambiguous, grayscale or arbitrary-perspective input instead of returning a guessed GS1 payload.

MaxiCode

The maxicode subpath exposes encodeMaxiCode, decodeMaxiCode, detectMaxiCode and detectAndDecodeMaxiCode. The implementation uses the fixed 30×33-module MaxiCode geometry and supports modes 2–5. Modes 2 and 3 require structured primary data (postalCode, countryCode and serviceClass); modes 4 and 5 carry an unstructured secondary message. Text and byte input are restricted to ISO-8859-1 (charset: 'latin1').

import { encodeMaxiCode, decodeMaxiCode } from '@sythos/js_barcode_universal/maxicode';

const symbol = encodeMaxiCode('HELLO FROM SYTHOS', { mode: 4 });
const result = decodeMaxiCode(symbol);
console.log(result.text, result.mode);

decodeMaxiCode works on a canonical 30×33 matrix and can validate its 180-degree and inverted forms. detectMaxiCode is deliberately a clean binary detector for one prominent symbol at integer or near-integer scale. It does not promise arbitrary perspective, grayscale thresholding, severe occlusion or multi-symbol scene handling.

Remaining out of scope

Data Matrix ECC 200 is implemented for its classic square and rectangular symbols; DMRE remains outside the current scope. See PLAN.md for the remaining symbologies.


Live examples

Two self-contained pages, each loading the IIFE bundle with a plain <script> tag. Both open directly from disk — double-click the file, no server and no build. This examples/ directory is the single canonical source; the development workspace references these files instead of keeping a second copy.

examples/create.html

Pick any writable format, type a payload, and watch the symbol redraw as you type; download it as PNG or SVG. For QR it adds a content-type builder that assembles the payload for you across URL, email, phone, SMS, Wi-Fi network, contact card (both vCard and MeCard), geo location and calendar event — with correct escaping for each — and shows you the exact string it produced, so you can see what a Wi-Fi or vCard QR actually contains. ECC level, version, scale, margin and both colours are exposed.

examples/read.html

Decode from an image: drop one onto the page, or click to choose a file. It then offers a live camera loop that decodes continuously from the video stream.

The file and drag-drop path works anywhere, file:// included. The camera needs http(s), because getUserMedia requires a secure context and refuses to run from file://. Serve the folder over localhost for that half; the page detects the situation and says so rather than failing silently.


Documentation

The full, searchable documentation lives on GitHub Pages and is built from the checked-in docs/ tree with MkDocs Material. It includes the API reference, format catalogue, camera and image guides, practical recipes, FAQ and troubleshooting.

Every documentation change is checked for local links, navigation coverage and registry drift in CI before the Pages build is published. The compact README remains the versioned project overview; the Pages site is the place for the longer explanations and copy-ready examples.


API summary

Two functions carry the whole surface. Everything else is a renderer or a format-specific escape hatch.

Encoding and decoding

QR byte text uses ECI 3 for non-ASCII ISO-8859-1 and ECI 26 for UTF-8. For older symbols without ECI, decodeQR(matrix) keeps UTF-8-first compatibility. Use decodeQR(matrix, { charset: 'iso-8859-1' }) or { charset: 'utf-8' } when the source encoding is known. ECI takes precedence; raw bytes stay unchanged. See QR byte text and ECI for ambiguity, capacity and fallback rules.

The QR reader honors FNC1 mode indicators: in Alphanumeric segments, % becomes the group separator (\u001D) and %% becomes a literal percent. Byte segments and ordinary QR text stay unchanged. The second-position application indicator is returned before the payload as two digits (00–99) or one letter (A–Z or a–z). Invalid indicator values produce FormatError. Raw bytes stay unchanged; no metadata fields are added. This does not add a GS1 QR writer option or GS1 Application Identifier validation. FNC1 must occur once, after optional headers and immediately before the first data segment. Repeated, late or separated FNC1 headers produce FormatError.

encode(text, options?) → BitMatrix

options: format (default 'qr'), ecc ('L'|'M'|'Q'|'H'), version (QR 1–40, auto if omitted), checkDigit, fullAscii (Code 39 extended), gs1 (emit a leading FNC1). Data Matrix ECC 200 accepts shape: 'any' | 'square' | 'rectangular' and encoding: 'ascii' | 'base256'. Aztec accepts layers, compact and eccPercent; it transports UTF-8 byte payloads through Binary Shift, and it does not expose configurable ECI yet. MicroPDF417 accepts compaction: 'auto' | 'text' | 'byte' | 'numeric', ECI 3 or 26 for Byte compaction, and optional columns, rowHeight and aspectRatio constraints. Micro QR accepts version: 'M1' | 'M2' | 'M3' | 'M4', its legal ECC level and mask; its unsupported ECI, FNC1/GS1 and Structured Append features are rejected explicitly. rMQR accepts ecc: 'M' | 'H', optional geometry/version constraints and ECI for byte payloads. The FrameQR Code profile accepts canvas: { shape: 'square' | 'circle' | 'diamond', size, width, height, centerX, centerY, angle }; it is non-certified and separate from DENSO FrameQR.

encode('5901234123457', { format: 'ean13' })
encode('ABC-123', { format: 'code39', fullAscii: true, checkDigit: true })
encode('https://example.com', { format: 'qr', ecc: 'H', version: 7 })
encode('0101234567890128', { format: 'datamatrix', gs1: true })
encode('Greetings My Lord Sythos  👋', { format: 'aztec', eccPercent: 23 })
encode('MICRO PDF417', { format: 'micropdf417', compaction: 'text' })
encode('12345', { format: 'microqr', version: 'M2', ecc: 'L' })
encode('rMQR SAMPLE', { format: 'rmqr', ecc: 'M' })
encode('https://www.sythos.net/', {
  format: 'frameqr',
  canvas: { shape: 'square', size: 5 },
})
decode(image, options?) → Result[]
decodeStrict(image, options?) → Result        // throws NotFoundError instead of returning []

image is { data, width, height } with RGBA bytes. options: formats (restrict the search, and go faster), tryHarder (retry inverted, default true), binarizer ('global' | 'hybrid' | 'auto'). A Result carries at least text and format; QR results also carry bytes, version and ecc.

For larger clean QR Code, PDF417 and MaxiCode rasters, auto and hybrid retain their primary local-threshold pass and retry once with the global threshold only when that pass finds no result. An explicit binarizer: 'global' request remains single-pass.

listFormats() → { id, label, canWrite, canRead, kind }[]

The table above is this function's output. Read it at runtime rather than hard-coding a format list — that is how the demo pages build their dropdowns.

Renderers

toSVG(matrix, options?) → string                       // one merged <path>, not a rect per module
toSVGDataURI(matrix, options?) → string                // data: URI for an <img src>
toImageData(matrix, options?) → { data, width, height }
toPNG(matrix, options?) → Promise<Uint8Array>          // 1-bit palette PNG
toPNGDataURI(matrix, options?) → Promise<string>
toCanvas(matrix, canvas, options?) → boolean           // 2D context
renderToCanvasAuto(matrix, canvas, options?) → { backend: 'webgl2' | '2d' | 'none' }
renderToCanvasAutoAsync(matrix, canvas, options?) → Promise<{ backend: 'webgpu' | 'webgl2' | '2d' | 'none' }>

The two PNG functions are async because they use the platform's deflate — node:zlib or CompressionStream — and fall back to stored blocks where neither exists.

renderToCanvasAuto is synchronous and therefore cannot reach WebGPU: acquiring an adapter is asynchronous, and a synchronous function can never wait for one. Use renderToCanvasAutoAsync when you want WebGPU in the chain. Both fall through to the 2D context, which always exists.

All renderers share the same options:

| Option | Default | Meaning | |---|---|---| | scale | 8 | Pixels per module | | margin | 4 | Quiet-zone modules on every side | | dark | '#000000' | Colour of set modules | | light | '#ffffff' | Colour of clear modules; 'none' for transparent | | barHeight | auto | 1D only: total bar height in pixels |

Also exported: BitMatrix, the error types (BarcodeError, EncodeError, NotFoundError, FormatError, ChecksumError), the per-format writers (encodeEAN13, encodeCode128, …), the QR entry points (encodeQR, decodeQR, detectQR, detectAndDecodeQR), the image primitives (LuminanceSource, binarize, binarizeGlobal, binarizeHybrid), and the capability probes isWebGL2Available / isWebGPUAvailable.


How it works

BitMatrix is the interchange type. Every writer produces one, every reader consumes one, every renderer draws one. That single currency is what keeps symbologies and output targets independent of each other — adding a format touches no renderer, and adding a renderer touches no format. Storage is row-packed into a Uint32Array: one allocation, cache-friendly row scans, and cheap whole-row operations for the 1D readers.

A set bit is a dark module. This matches how every specification describes its symbols; renderers invert where their medium needs it.

encode returns no quiet zone. The margin is a rendering decision, not an encoding one — how much white space a symbol needs depends on where it is going — so the renderers add it and the matrix stays the pure symbol. For the same reason, linear symbols come back exactly one module tall: height carries no information in a 1D barcode, so encoding one would be inventing data. The renderer stretches the single row to barHeight before applying the quiet zone, so the margin ends up uniform on all four sides.

The read pipeline is a straight line, each stage a separate module:

RGBA bytes → luminance → binarize → detect → sample → error-correct → decode

Luminance conversion flattens the image to greyscale. Binarization turns that into a BitMatrix, either globally or with a hybrid local threshold that survives uneven lighting. Detection locates a symbol and its corners in that bit plane. Detectors that recover four perspective-aware corners (currently QR) sample the symbol back through a perspective transform. Data Matrix currently uses an axis-aligned bounding box; the strict camera profile evaluates the eight fixed in-plane orientations before error correction repairs what the camera lost. Only then is the pa