Skip to content

rMQR: thirty-two rectangles and one mask - #237

Merged
s2x merged 2 commits into
v2from
v2-rmqr
Sep 3, 2026
Merged

rMQR: thirty-two rectangles and one mask#237
s2x merged 2 commits into
v2from
v2-rmqr

Conversation

@s2x

@s2x s2x commented Sep 3, 2026

Copy link
Copy Markdown
Member

QR for a space that is long rather than square. A QR symbol is square because it has three finders in three corners, and that shape is wrong for most of the things barcodes go on: the side of a cable, the barrel of a syringe, the edge of a board. rMQR keeps QR's alphabet, its Reed–Solomon and its zigzag and rearranges the geometry into thirty-two rectangles, from 7×43 up to 17×139.

What is new in the geometry

One finder in the top-left corner and a five-module sub-finder in the bottom-right; timing patterns running the whole way along all four edges rather than QR's two internal lines; an alignment column every twenty-odd modules of width, a 3×3 ring top and bottom joined by a vertical timing line; and three modules in each of the remaining two corners that the alternating edges would otherwise get wrong — which are the only thing distinguishing the two ends of a seven-module-tall rectangle.

Two options, and the missing third is the point

RmqrOptions takes a shape and an error correction level. There is no mask option, because rMQR defines one mask pattern and no field to say which was used: the bits QR spends on a mask number are spent here saying which of the thirty-two shapes the symbol is. Eighteen bits of BCH over six, written twice and masked with different constants in the two copies — one constant for both draws a symbol whose first copy is right, which is exactly the kind of mistake a small fixture misses.

There are two levels, M and H, and no others. A pinned shape is kept rather than grown: a caller who names Version::R7x99 names it because that is the space the label has.

Verification: one opinion, not two

zint draws every symbol in the fixture. zxing-cpp 3.1.1 lists RMQRCode among its formats and decodes neither our symbols nor zint's own, so there is no round trip. DecoderRoundTripTest exempts the symbology and gates the exemption on that staying true, so the day a reader appears the suite fails and says to delete it.

What replaces the missing reader is coherence. The fixture is exhaustive rather than representative — all sixty-four cells in all three modes, at one character, at the longest payload and in between — and RmqrTest asserts the tables against each other: the codeword total is the free-module count to within a byte, every block count divides its own check codewords, the single mask is QR's pattern 4 written out from QR's formula, and the format code's minimum Hamming distance is the eight BCH(18,6) is supposed to buy. A wrong generator that still made sixty-four distinct values passes a uniqueness test and fails that one.

Three things measured rather than assumed

  • The count indicator widths are not the minimum that fits. R7x43 holds seven alphanumeric characters and spends four bits saying so; R11x27 holds fourteen digits and spends five. Ninety-six numbers, and the obvious guess gets two wrong — which shifts every bit after it.
  • The block counts are not derivable from the codeword counts. R15x99-H splits forty-eight data codewords into four blocks; R13x139-M splits a hundred and six into three. Interleaving is invisible in a symbol that leaves any block short, so every interleaved cell is drawn at its longest payload.
  • The two corner patterns. Nothing in the timing rule explains them, and without them a seven-module-tall rectangle has no way to say which end is which.

One extraction

The mode-splitting shortest path Micro QR introduced is now Encoding\Segmentation, shared by both symbologies, with each contributing only what a segment header costs it — which is the whole of what differs between them. Here the split agrees with zint on every payload in the fixture, so unlike Micro QR's the module comparison is unconditional.

Checks

  • composer lint clean (cs 0/309, phpstan no errors, rector done, kb-lint 0 warnings)
  • full suite 18695 tests, 284000 assertions (21 pre-existing PHPUnit deprecations, 1780 skipped)
  • SCANME_REQUIRE_DECODER=1 round trip 405 tests, 1561 assertions
  • tests/RmqrTest.php + tests/RmqrReferenceTest.php: 1882 tests, 10912 assertions
  • composer reference:rmqr regenerates deterministically (782 symbols, 64/64 cells)

s2x added 2 commits September 4, 2026 00:05
QR for a space that is long rather than square: one finder top-left, a
five-module sub-finder bottom-right, timing along all four edges, and an
alignment column every twenty-odd modules of width. Thirty-two shapes from
7x43 to 17x139, two error correction levels, no mask option -- rMQR defines
one mask and spends the bits QR gives a mask number on saying which shape
the symbol is, in eighteen bits of BCH written twice under two different
masks.

Verified with one opinion rather than two, and the difference is stated
rather than glossed: module for module against zint at all sixty-four cells
in all three modes, but no round trip, because zxing-cpp 3.1.1 lists
RMQRCode and decodes neither our symbols nor zint's own. The exemption in
DecoderRoundTripTest is gated on that staying true. In place of the missing
reader, RmqrTest asserts the tables against each other: the codeword total
is the free-module count, every block count divides its own check
codewords, and the format code's minimum Hamming distance is the eight
BCH(18,6) buys.

The mode-splitting search Micro QR introduced is now Encoding\Segmentation,
shared by both, with each symbology contributing only what a segment header
costs it.
@s2x
s2x merged commit 63f73f7 into v2 Sep 3, 2026
5 checks passed
@s2x
s2x deleted the v2-rmqr branch September 3, 2026 22:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant