Skip to content

v0.3.0 — Code Set C packing for Code 128

Latest

Choose a tag to compare

@lhopkins lhopkins released this 28 Aug 18:32
· 1 commit to master since this release

Code 128 now chooses code sets to minimise the symbol count. Digit-heavy barcodes were being drawn up to 1.76x denser than callers sized them for.

The bug

Code Set C packs two digits per symbol; sets A and B take one character each. rubar always used Code B, so a digit run cost twice the symbols it needed to. Callers compute physical width from the symbol count, so bars came out proportionally finer than intended — silent, still decodable in software, and visible only on a scanner or under a caliper.

payload correct drew before
ABC123 101 101
009312345678901234 134 233
12345678901234567890 145 255
(01)20197344223371 134 222
(01)20197344223371(21)00000001 189 332

Changes

  • Minimal code-set planning. encode_code128 runs a backward DP over the token stream to pick the start code set and latch points. Planning spans the whole symbol list, so a digit run split across several Data entries still packs as one run.
  • gs1::to_symbols no longer hardcodes StartB. That pin defeated planning on exactly the digit-heavy payloads GS1-128 carries. Its output now begins with the FNC1 designator and the encoder picks the start set. Callers passing its output straight to encode_code128 need no change.
  • Start symbols now pin the initial code set and plan onward, rather than forcing one set for the whole payload. Never produces a worse barcode, and it turns the old StartC-plus-odd-digit-count error into a valid encoding.
  • Data is validated as ASCII. Accented text already failed inside barcoders, so the real change is narrow: the seven in-band switch and function characters (Ć, Ź, À, ...) previously smuggled a code-set switch through a Data payload and silently rendered a barcode that didn't contain the requested character. That is now InvalidCharacter.

Compatibility

Source-compatible. total_modules keeps its meaning exactly — drawn modules, no quiet zone; only the value changes. Rust consumers on rubar-core = "0.2.0" need a real version edit, since pre-1.0 caret semantics won't resolve 0.3.0 on a cargo update.

Verification

Checked against an independent exact minimal encoder (sklib's min_symbol_count): 108 payloads assert total_modules == count * 11 + 35. Confirmed end-to-end through rupdf's PDF path, where every row of the density harness now reads drawn == counted at ratio 1.00.

One deliberate gap: the planner never emits SHIFT, because barcoders cannot express it — its parse() does not model shift state, so the character after a shift marker is looked up in the wrong set. SHIFT beats latch-there-and-back by exactly one symbol, and only for payloads alternating between control characters (ASCII < 32) and Code-B-only characters (ASCII >= 96). Verified zero divergence across 21,613 printable, GS1 and real-world payloads.

Wheels

Binary wheels for macOS arm64, Linux x86_64 and Linux aarch64 on Python 3.11, 3.12 and 3.13 — a wider matrix than 0.2.0, which shipped 3.11 + 3.13 on two platforms. Other platforms build from sdist.

Also published to crates.io as rubar-core 0.3.0.