Repository navigation
fix: decode EAN-2 extensions from the located guard, bound the add-on gap - #111
Merged
Merged
Conversation
… gap UPCEANExtensionSupport::decodeRow gave UPCEANExtension2Support the guard pattern constant [1, 1, 2] instead of the range where the extension start guard was found, so every attached two-digit extension was dropped. ZXing passes extensionStartRange to both helpers. With two-digit extensions decoding, the unbounded guard search could read a neighbouring symbol as an add-on: in the zxing_bench images in tests/github_issues.rs an EAN-13 picked up "19" from the UPC-E about 40 modules to its right, and multi-barcode detection fell from 9 symbols to 2. An add-on starts 7 to 12 modules after the main symbol, so reject an extension start guard more than 4 guard widths (16 modules) away, as zxing-cpp bounds the gap. Adds an EAN-13 + EAN-2 image to the upcean-extension-1 black-box set. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hschimke
approved these changes
Oct 24, 2026
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.
Problem
EAN-13, UPC-A and UPC-E symbols with a two-digit (EAN-2) add-on decode without the add-on: the result carries no
UPC_EAN_EXTENSIONorISSUE_NUMBERmetadata. Five-digit add-ons are not affected.To reproduce with the image this PR adds (EAN-13
9771234567003with add-on05, the usual layout for a periodical's issue number):Cause
UPCEANExtensionSupport::decodeRow(src/oned/upc_ean_extension_support.rs) locates the extension start guard and passes that range to the five-digit helper. The two-digit helper gets the guard pattern instead:EXTENSION_START_PATTERNis[1, 1, 2], soUPCEANExtension2Supportstarts reading at bit 1 of the row rather than after the guard, and the two-digit decode never succeeds. ZXing passesextensionStartRangeto both helpers.The two helpers take differently shaped ranges:
&[u32; 3]for two digits,&[usize; 2]for five. That is probably how the constant slipped in, since it is the only nearby value whose type fits.Fix
Pass the located range to the two-digit helper, converted to its
[u32; 3]; only the first two slots are read. This keepsUPCEANExtension2Support::decodeRow's public signature. If you prefer, I can change it to&[usize; 2]to match the five-digit helper instead, which would be a breaking change.Bound the gap between the symbol and the add-on. Once two-digit decoding worked,
zxing_bench_issue_1and the twozxing_bench_grey_image_*tests intests/github_issues.rsfailed with "must detect 9 barcodes, found: 2". The extension guard search has no distance limit, so the EAN-13 in that image picked up a false add-on19from the UPC-E about 40 modules to its right. The extra result points then derailedGenericMultipleBarcodeReader.The five-digit path has the same exposure, but a false match there needs five valid characters and a matching check value, so it is rarer. ZXing Java has the same unbounded search. zxing-cpp limits the gap:
ODMultiUPCEANReader.cppaccepts at most 3.5 times the main start guard's width (about 10.5 modules).GS1 puts an add-on 7 to 12 modules after the main symbol. This PR rejects an extension start guard that begins more than 4 guard widths (16 modules) after the symbol's end. That covers the GS1 range with margin for print gain and rules out the neighbouring symbol. The check runs before both helpers, so it applies to five-digit add-ons too. With it, all 9 symbols are found again.
Test
This PR adds
3.pngtotest_resources/blackbox/upcean-extension-1, with its expected text and metadata (UPC_EAN_EXTENSION=05,ISSUE_NUMBER=5), and raises that black-box test's thresholds from 2 to 3. The image is a synthetic symbol made with an encoder I'm writing, at 3 px per module.Without the fix,
cargo test --release --test upc_ean_extension_black_box_1_testcasefails. The new image decodes but has noUPC_EAN_EXTENSIONorISSUE_NUMBERmetadata:With the fix, it passes. The existing
zxing_bench_*tests cover the gap bound: without the bound they fail as described above, and with it they pass.I also decoded 1,200 synthetic symbols: every add-on value from 00 to 99, on EAN-13, UPC-A and UPC-E, in four variants:
mainreads none of the 1,200 add-ons and this branch reads all of them, so the bound accepts the GS1 maximum gap even at 2 px per module.Both CI test steps,
cargo test --workspace --releaseand the same with--no-default-features, pass on this branch.I found this while using rxing to check a barcode encoder by decoding its output. The change and this description were prepared with AI assistance (Claude, via Claude Code). I reviewed the change and the test results before opening this PR. It is contributed under the Apache License 2.0.
🤖 Generated with Claude Code