Conversation
The second four-state postal code, and the smallest symbology in the library: no start bar, no stop bar, no check character. A KIX symbol is its characters concatenated and nothing else, which is asserted directly because every mistake available here is a mistake of addition. The thirty-six characters turned out to be identical to RM4SCC's -- measured, by drawing both fixtures independently through zint -- so they moved into Generator\FourState\Alphabet and RM4SCC now keeps only what is its own. Two copies of a base-six derivation are two chances for one of them to drift, and a drifted copy still draws thirty-six legal characters. What KIX does not do is written down rather than left to be discovered: there is no error detection over the symbol, so a dropped character produces another legal symbol saying something else, and the quiet zone is load-bearing because nothing else marks where the first character starts. Eighteen characters is the specification's ceiling, not zint's. The substituted round trip is now one test over the family rather than one per symbology, so the next four-state code adds a fixture row instead of deciding for itself whether it wanted the gate.
s2x
added a commit
that referenced
this pull request
Sep 3, 2026
feat(v2): KIX, the second four-state postal code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
KIX — the Dutch four-state code,
kix, aliaseskix-code,klantindex,postnl. Pure PHP, no options.What it is
RM4SCC's alphabet with the envelope taken off. The whole symbol:
No start bar, no stop bar, no check character. So
KixTestasserts the symbology in one line — a payload's bars are its characters' bars concatenated — because every mistake available here is a mistake of addition.The shared alphabet
The thirty-six characters are identical to RM4SCC's. That was measured, not assumed: the two fixtures were drawn independently through zint and agree character for character. So the derivation moved into
Generator\FourState\AlphabetandRm4scc\Charactersnow holds only what is RM4SCC's — start bar, stop bar, check character. Two copies of a base-six derivation are two chances for one to drift, and a drifted copy still draws thirty-six legal characters; a test asserts they still agree.What KIX does not do
Worth stating, because a postal code sounds like something that checks itself:
Eighteen characters is the ceiling, and this time it is the specification's rather than the reference encoder's.
Verification
Same arrangement as RM4SCC, same caveat: no free decoder reads a four-state postal code, so this is one independent opinion where the rest of the library has two.
tools/kix_reference.py→tests/fixtures/kix_reference.csv, 188 symbols, 36/36 characters, lengths 1 to 18. Every character is drawn alone, at the front, at the back and mid-symbol, since an encoder that had grown an RM4SCC-shaped start bar would still get every one-character symbol right. The tool asserts zint's own output is a concatenation before writing anything.fourStateProvider()instead of deciding for itself whether it wanted the gate. Same for the expiry test that fails the day zxing-cpp learns to read one.The encoder matched zint on
2500GG30250on the first run and on all 188 fixture rows.Checks
composer lintclean; full suite green (15390 tests, 264323 assertions, the 21 pre-existing PHPUnit deprecations);SCANME_REQUIRE_DECODER=1 composer test:roundtripgreen (356 tests, 1389 assertions).