M10 — Barcodes
State: to do — Depends on: M08, M09
Goal
Paint the codes that invoices, labels, cover sheets and case-file stamps carry — QR, Data Matrix, Code 128 and GS1-128, PDF417, EAN and UPC, the EPC SEPA credit-transfer QR and the Swiss QR-bill — as deterministic vector form XObjects, from a satellite that depends on nothing but the core.
The need is ordinary and daily. A German, Austrian, Belgian or Dutch invoice often carries an EPC QR code so that a banking app fills the transfer; a Swiss invoice paid by transfer carries a QR-bill; a parcel label carries GS1-128; a case file wants each piece's number or hash in a form a scanner reads back. Today a caller rasterizes the code with another library — blurry in print, never byte-identical twice — or inlines an SVG from a tool whose output nobody here controls. Invariant 6 cannot survive either. This milestone makes the code our own output: the same payload and options give the same bytes, on any machine, in any culture.
Scope
In:
- the
AdCodicem.Pdf.Barcodessatellite, under the prefix reserved by ADR 24, depending onAdCodicem.Pdfalone (ADR 9), Native AOT and trimming compatible, with no reflection and no mutable static state; - encoders — QR Code, Data Matrix ECC 200, PDF417, Code 128, GS1-128, EAN-13, EAN-8, UPC-A and UPC-E — each a pure function from a payload and immutable options to a symbol, with its capacity checked first;
- GS1 element strings: Application Identifiers parsed and validated against a table generated at build time from GS1's Barcode Syntax Dictionary at a pinned commit;
- painting: a symbol as a form XObject in module units — merged rectangles in one path and one fill, its quiet zone inside the bounding box, an optional opaque background, human-readable text for linear codes drawn with an M08 font, bar-width reduction, a color checked against the document's output intent;
- payment payloads: the EPC069-12 SEPA credit-transfer QR (GiroCode), and the Swiss QR-bill — model, validation, payload written and parsed, and the payment part with its receipt laid out as the SIX implementation guidelines in force say, those guidelines kept as versioned data;
- hooks: a barcode as an element of M09's
PdfStamp— the form-XObject hook M09 leaves for it — with a payload that may be a per-page template ({bates},{piece},{page}), placed by M09'sPdfMarkPosition; thebarcode:specification that M12.5 resolves, parsed into options with an intrinsic size; the alternative text M13 will put on aFigure; - the tool:
barcode,qrbill, and a--barcodeoption on M09'sstamp.
Out, explicitly:
- reading barcodes, from vectors or from pixels — not in the roadmap, but among its open questions (barcode and patch-code recognition). M07's separator predicate stays the caller's;
- Micro QR, rMQR, Aztec, MaxiCode, Han Xin, DMRE (ISO/IEC 21471), Micro PDF417, Macro PDF417, structured append, Code 39, Code 93, Interleaved 2 of 5, Codabar, GS1 DataBar, postal codes, the EAN-2 and EAN-5 add-ons, and QR Kanji mode — none is in the roadmap; a symbology enters when a caller's document needs it;
- GS1 DataMatrix and GS1 QR — FNC1 in first position of the 2D encoders; one option away once asked for;
- French 2D-Doc — an open question of the roadmap, for an issuer approved by ANTS;
- print-quality grading (ISO/IEC 15415, 15416) — a verifier's job on paper; we guarantee geometry;
- tagging a code as a
Figurewith/Altin a generated document — M13; M10 carries the text; - the HTML element or
<img src="barcode:…">itself — M12.5; M10 gives it the parsed specification and the painter; - the invoice around a payment slip — M12's templates; M10 paints the slip, at the bottom of an A4 page or on a page of its own;
- the Swico billing-information syntax (
//S1/…) — carried as an opaque, length-checked string.
Dependencies. The roadmap's table gives M08 and M09. M08's registry and OFL set draw the human-readable text and
the slip, its PdfContentBuilder and PdfFormXObjectBuilder the symbols, its PdfDocumentBuilder a page of
its own. The stamp hook uses M09; its slice comes late, so the encoders never wait on it.
Design
Package layout
AdCodicem.Pdf.Barcodes
Encoding/ the encoders → BarcodeSymbol; Reed–Solomon over GF(256) and GF(929); no PDF type
Gs1/ element strings; the AI table generated from GS1's Barcode Syntax Dictionary
Painting/ BarcodeSymbol → content-stream operators → PdfBarcode, a form XObject
Payments/ EpcCreditTransfer, SwissQrBill: model, validation, payload, slip layout
Stamping/ BarcodeStampElement, a barcode in M09's PdfStamp; BarcodeSpecification, the `barcode:` grammar
| Type | Responsibility |
|---|---|
QrCode, DataMatrix, Pdf417, Code128, EanUpc | Static Encode(payload, options) → BarcodeSymbol |
QrCodeOptions, DataMatrixOptions, Pdf417Options, Code128Options, EanUpcOptions | Immutable records (ADR 8): error-correction level, version or size bounds, shape, columns, code set, ECI, quiet zone |
BarcodeSymbol | Immutable: the symbology, the modules (a bit matrix for 2D, bar and space widths for 1D), the quiet zone, the human-readable text, what the encoder chose (QR version, level and mask; Data Matrix size; PDF417 rows and columns; code sets), and the bytes encoded |
Gs1ElementString | Parsed Application Identifiers with their values, validated; the FNC1-separated data and the bracketed human-readable form |
PdfBarcode | A symbol painted into a document as a form XObject, with its size in points and its alternative text |
PdfBarcodeAppearance | Immutable: module size or target size, foreground color, background (none or opaque), human-readable text and its font, bar-width reduction, alternative text |
EpcCreditTransfer | The EPC069-12 data, validated; its payload; its QR symbol at level M |
SwissQrBill, SwissQrBillValidationResult, SwissQrBillLayout | The QR-bill data, its validation against the guidelines in force, its payload written and parsed, and the slip painted |
BarcodeStampElement | A barcode as an element of M09's PdfStamp, its payload fixed or a PdfStampText template |
BarcodeSpecification | The barcode: grammar parsed into a symbology, a payload and options |
BarcodeSymbol is public so that a caller can paint it elsewhere; PdfBarcode is the one path into a
PDF, so that every code in a document is drawn the same way.
Encoders
An encoder is a pure function of its payload and options: no clock, no culture, no randomness. Wherever the standard leaves a choice, the tie is broken by a fixed rule written beside the code and pinned by a test, so that determinism does not depend on the iteration order of a collection.
| Symbology | Standard | Covered | Quiet zone | Choices we fix |
|---|---|---|---|---|
| QR Code | ISO/IEC 18004:2024, model 2 | Versions 1 to 40; levels L, M, Q, H; numeric, alphanumeric and byte modes; ECI | 4 modules | The smallest version for the level unless a minimum is given; segmentation minimal in bits, ties to fewer segments; mask by the four penalty rules, ties to the lowest number |
| Data Matrix | ISO/IEC 16022:2024, ECC 200 | The 24 square and 6 rectangular sizes; ASCII, C40, Text, X12, EDIFACT and Base 256 encodations; ECI | 1 module | The smallest symbol of the requested shape; encodation minimal in codewords, ties in the standard's order |
| PDF417 | ISO/IEC 15438:2015 | Text, byte and numeric compaction; error-correction levels 0 to 8; 1 to 30 columns, 3 to 90 rows; compact PDF417 | 2 modules | The level the standard recommends for the data length unless given; rows 3 modules high; columns from a target aspect ratio |
| Code 128 | ISO/IEC 15417:2007 | Code sets A, B and C; FNC1 to FNC4 | 10 modules | The shortest symbol, ties to fewer shifts and code changes |
| GS1-128 | GS1 General Specifications | FNC1 first, element strings, at most 48 data characters and 165 mm | 10 modules | The caller's AI order kept; FNC1 only after a variable-length AI that is not last |
| EAN-13, EAN-8, UPC-A, UPC-E | ISO/IEC 15420:2009 | Check digit computed, or verified when given; UPC-E only for the UPC-A numbers it can express | 11 and 7 (EAN-13), 7 and 7 (EAN-8), 9 and 9 (UPC-A), 9 and 7 (UPC-E) | Magnification between 80 % and 200 % of the nominal size |
- Capacity first. A payload that cannot fit the options is refused before any encoding, by a
BarcodeCapacityException(anArgumentException) that names the symbology, the capacity and the length. A caller's impossible request is an exception, not a diagnostic (invariant 5). The bound on every input is the symbology's own capacity, and on Code 128 — which the standard does not bound — a documented maximum of symbol characters; nothing is sized by a value the encoder has not checked. - Text and bytes. A payload is bytes. A string is encoded under a rule stated once: ASCII goes into the
densest mode as it is; anything else goes into byte mode as UTF-8, with ECI 26 in QR Code and Data Matrix
when
EciisAuto, and without when it isNone. The payment builders fix the ECI their guideline specifies. A GS1 string never has an ECI. - Allocation. The module matrix is bit-packed in a buffer rented from
ArrayPool<ulong>; the finished symbol owns one exact-sized array; Reed–Solomon log and antilog tables, alignment-pattern positions, capacity tables and the Code 128 patterns areReadOnlySpan<byte>over constant data; segmentation's dynamic program works in a buffer sized by the checked capacity. Encoding a symbol allocates the symbol and nothing else, measured byBarcodeBenchmarks. - Correctness by construction. Each encoder reproduces its standard's worked example bit for bit —
ISO/IEC 18004's
01234567at 1-M, and the encodation examples of ISO/IEC 16022, 15438, 15417 and 15420 — and the capacity tables are checked cell by cell against the standards' own.
GS1 element strings
Gs1ElementString.Parsetakes the bracketed form,(01)09506000134352(17)201231(10)ABC123, or the unbracketed form with the group separator; each AI's value is checked for its length, its character set (GS1's sets 82, 39 and 64), its check digit (GTIN, SSCC, GLN, by modulo 10), its date (YYMMDD, with day00allowed where GS1 allows it), and the associations and exclusions between AIs.- The AI table is generated at build time from GS1's Barcode Syntax Dictionary (Apache-2.0) at a pinned
commit, its notice carried in
NOTICE, as ADR 44 does for the Arlington model; a test fails when the generated table and the pinned dictionary disagree. - The dictionary describes each component by a format and named linters. The linters are written in C#, each in linear time — never a regular expression built from data, which a crafted value could make backtrack.
- The human-readable text shows each AI in parentheses; the separators never print.
Painting
- Module units. The XObject's
/BBoxis[0 0 W H]in modules, quiet zone included, and its content is integers only; one/Matrix [s 0 0 s 0 0]scales a module to its size in points. Every coordinate is exact and the module size is the single number formatted with"0.####"— an error below a hundredth of a millimeter over the largest symbol, and never an uneven module from rounding each edge on its own. - 2D symbols. The dark runs of each row become rectangles; runs of equal position and length on
consecutive rows merge into taller ones, greedily from the top. Every rectangle goes into one path, filled
once (
f): viewers that anti-alias show hairline seams between separately filled neighbors, and a single non-zero fill has none. The merge is deterministic and measured against one square per module. - 1D symbols. One rectangle per bar, its width reduced by the bar-width reduction the caller gives for print gain; EAN and UPC guard bars extended as ISO/IEC 15420 draws them; PDF417 rows three modules high.
- Color. Foreground
DeviceGray0 by default; RGB, CMYK or a caller's color space when asked. Under a PDF/A claim a device color needs a matching output intent —DeviceGrayany intent,DeviceRGBan RGB one,DeviceCMYKa CMYK one — and a mismatch follows M09'sPdfConformancePolicy: refused withPdfConformanceExceptionby default, or written with the claim removed and reported underRemoveClaim. The core converts no color (M29). - Background. None, or opaque over the quiet zone. A code stamped on a received page may land on content: M10's stamps default to opaque; codes in generated documents to none.
- Human-readable text. EAN and UPC digits placed as ISO/IEC 15420 places them; Code 128 and GS1-128 text under the bars. Drawn with a font from M08's registry — OCR-B when the caller registers one, as GS1 recommends, the embedded sans otherwise — subsetted by M08. It is the only resource a symbol can need.
- Sizes. A module size in points or millimeters, or a target width from which the module size follows. Where a guideline fixes the size — the Swiss QR code at 46 mm, EAN and UPC within their magnification range — another size is refused; elsewhere any positive size is painted.
- One XObject per symbol. Within a document, the same symbol with the same appearance is painted once and reused, keyed by a SHA-256 of the payload, the options and the appearance.
- Alternative text.
PdfBarcode.AlternativeTextdefaults to the payload as text, control characters escaped; the payment codes default to a short description, since the guidelines already print their data in full beside the code. The caller may replace either. M13 puts it on aFigure; on a received document the code is an artifact (below). - Version. A symbol needs PDF 1.2 (form XObjects) and nothing later; it raises no document's version (ADR 40).
EPC SEPA credit transfer (EPC069-12)
EpcCreditTransfer: version002by default (BIC optional within the EEA) or001(BIC required); beneficiary name (at most 70 characters); IBAN (country length and modulo 97 checked); BIC (format checked); amount in euros from 0.01 to 999,999,999.99, optional; purpose code (four characters, optional); either a structured creditor reference (ISO 11649RF, its check digits verified, at most 35 characters) or an unstructured remittance text (at most 140), never both; beneficiary-to-originator information (at most 70).- The payload:
BCD, the version, character set1(UTF-8),SCT, then one field per line, separated by LF, trailing empty lines left out; at most 331 bytes, which keeps the symbol within version 13. The QR level is M, byte mode, no ECI. - A value the guideline forbids is refused, naming the field and the rule; nothing is truncated.
Swiss QR-bill
- The guidelines are data.
SwissQrBillGuidelinesholds, per version of SIX's implementation guidelines for the QR-bill, every rule with its section: character set, field lengths, reference rules, dimensions, fonts and sizes, headings in each language. The version in force when the slice is written is encoded — v2.3, structured addresses only since 22 November 2025 — and v2.4, published on 24 February 2026, is added with its date of entry into force once checked against SIX's page. Each version is verified against the published guide before it is encoded, as M18 does for court-portal presets, and the verification is recorded in the commit. - Model. Account (a Swiss or Liechtenstein IBAN or QR-IBAN); creditor with a structured address;
optional amount (0.01 to 999,999,999.99) and currency (CHF or EUR); optional ultimate debtor; reference type
and reference —
QRR(27 digits, modulo 10 recursive) with a QR-IBAN only,SCOR(ISO 11649) orNONwith an IBAN only; unstructured message and billing information (140 characters together); at most two alternative schemes of 100 characters; the language of the headings. - Validation.
SwissQrBillValidationResultlists errors, which refuse generation, and replacements — characters outside the permitted set replaced as the guideline allows, each one reported with its field. Every rule carries an identifier and its section, so that a caller can show the rule to a user. - Payload.
SPC,0200,1, the fields in the guideline's order,EPD, then billing information and alternative schemes; LF-separated on write, LF or CR LF accepted on parse; at most 997 characters; QR level M; the Swiss cross, at the size and with the border the guideline's annex gives, painted over the center of a 46 mm symbol. Parsing a payload yields the model with the same validation — the path that rebuilds the corpus bill from its own code. - Layout. The payment part, 148 × 105 mm, and the receipt, 62 × 105 mm, with sizes and positions from the
guideline's annex as data; Liberation Sans regular and bold — permitted by the guidelines, under the OFL, and
M08's candidates for its OFL set (#34) — or Arial, Frutiger or Helvetica when the caller registers one, the
other faces the guidelines permit. The two Liberation faces come from wherever M08's ADR packages the set: if
the core carries the regular face only,
.Barcodesreferences the data-only package that holds the bold one, and without it a slip whose registry has no bold face permitted by the guidelines is refused, naming the face — never drawn in synthetic bold; headings in the chosen language; corner marks where amount or debtor is left blank; the separation line with a scissors symbol drawn as a vector path, as SwissQRBill draws it, or no line for perforated paper; amounts and references printed in the groups the guideline gives. The slip is placed at the bottom of an A4 page, or on a 210 × 105 mm page, or alone on an A4 page; a standalone slip sets/PrintScaling /Nonethrough M06's typed viewer preferences, since a slip printed "fit to page" is no longer 46 mm. A field that does not fit at the smallest size the guideline allows is an error — never cut in silence. The slip's text is real, extractable text (M08'sToUnicode), as it is in the corpus file.
Stamping (M09)
BarcodeStampElementis one more element of M09'sPdfStamp, beside text, boxes and images: placed byPdfMarkPositionon the visible page, in the orientation/Rotategives it and at the sizeUserUnitgives it, so that the code reads upright; its opaque background keeps its quiet zone clear.- Every mark M09 writes is an
/Artifactof type/Pagination, and so is a code: a Bates code's subtype follows M09's rule by output version —HeaderorFooterby position in PDF 1.7,Batesin PDF 2.0. A PDF/UA input stays valid, and M09's recognition of its own marks updates or removes the code with the rest of the stamp. - A fixed payload is M09's static part: one XObject, shared by every page. A templated payload is M09's variable
part: each page's code drawn in that page's own suffix stream, the rectangles in module units under one
cm, a few hundred bytes a page for a Code 128, a few kilobytes before compression for a QR code — and no object per page. Two pages whose payloads are equal still draw it twice; the cost is measured. - Writing goes through M09's plan: its wrapper, M04's write guard (a stamp is an
Otherchange — a certification refuses it withPdfSignatureInvalidationException, approval signatures take it in an incremental update reported asstamp.after-signature), and M09's conformance policy, with the color rule above.
The barcode: specification (M12.5)
BarcodeSpecification.Parse("barcode:qr?data=…&ec=M&module=0.5mm"): a symbology, a percent-encoded UTF-8 payload, and options by name, the same names as the option records'. The grammar is published with the guide, so M12.5's<img src>and element attributes map onto it rather than inventing their own.- A template may carry data a user typed: the parser is total and bounded — any string yields a specification or a parse error naming the position, never another exception, and the payload's length is checked against the symbology's capacity before decoding the percent escapes.
- It gives the intrinsic size for layout. M12.5 decided the seam:
.Htmlreferences.Barcodesdirectly, rather than resolving the scheme through ADR 38's resolver, and records it in the architecture's package table (M12).
The command-line tool
barcode SYMBOLOGY --data TEXT [--gs1] [--ec L|M|Q|H] [--module 0.5mm] -o code.pdf
qrbill BILL.json [--language de|fr|it|en] [--append-to invoice.pdf | --page a4|slip] -o out.pdf
stamp … --barcode SPEC [--per-page bates|hash] (M09's verb gains the option)
qrbill --json prints the validation result; exit code 1 when it has errors. Names are settled in the slice,
as M06 settled the tool's own.
Slices
Each slice ends on a green commit, its acceptance rows passing, the referee harness it adds running in CI.
- Package, linear codes and the referee. Delivers the project (AOT and trimming analyzers on, #42's per-package
baseline in place first, the package's exported signatures added to M03's API baseline, #35),
Code128,EanUpc,BarcodeSymbol, the 1D painter andPdfBarcode. Proven by unit tests — the ISO/IEC 15417 and 15420 examples, every check digit, code-set switching against a brute-force search on short inputs (FsCheck), UPC-E's expansion rules — and by the newBarcodeRefereeTests: pages rasterized by poppler (pdftoppm) and MuPDF (mutool draw) at 300 and 150 dpi, read by zxing-cpp and by zbar, all in containers. Leaves 2D codes. - QR Code. Delivers the encoder, segmentation, masks, ECI, and the 2D painter with its run merging. Proven by the ISO/IEC 18004 example bit for bit, the capacity table cell by cell, FsCheck (for any payload within capacity, a test-only reader of the module matrix — no image, no detection — returns the payload; two encodings are identical), and zxing-cpp reporting the payload, the level and the version we chose. Leaves the other 2D codes.
- Data Matrix. Delivers ECC 200 with the six encodations and minimal switching, square and rectangular.
Proven by the ISO/IEC 16022 example, every size including 144 × 144, FsCheck through the test-only reader,
and zxing-cpp and libdmtx (
dmtxread) in containers. Leaves PDF417. - PDF417. Delivers the three compactions, levels 0 to 8, columns and rows, compact PDF417. Proven by the standard's example, FsCheck, and zxing-cpp at every level and at the extreme aspect ratios. Leaves GS1.
- GS1. Delivers the AI table generated from the pinned Barcode Syntax Dictionary,
Gs1ElementString, GS1-128 with FNC1 and its human-readable text, theNOTICEentry. Proven by unit tests per linter, a test that the generated table matches the pinned dictionary, and GS1's Barcode Syntax Engine (Apache-2.0) in a container accepting every element string we emit, zxing-cpp reporting the]C1identifier and the same string. Leaves the payment payloads. - EPC QR. Delivers
EpcCreditTransfer, its validation and payload. Proven by unit tests per rule (IBAN and RF check digits, lengths, the reference-or-text exclusion) and by segno (BSD), in a container, building the EPC QR for the same data: zxing-cpp reads both symbols, and the two payloads are equal byte for byte. Leaves the QR-bill. - Swiss QR-bill: data. Delivers the guidelines as data (the version verified against SIX's guide and recorded), the model, validation, payload writing and parsing. Proven by unit tests per rule, and by SwissQRBill (MIT), in a container, validating every bill we write and parsing every payload we write back to the same data; our parser reads the payload zxing-cpp decodes from the corpus bill. Leaves the slip.
- Swiss QR-bill: the slip. Delivers
SwissQrBillLayoutin every placement and language, the Swiss cross, the scissors path, corner marks. Proven by geometry tests (the symbol 46 mm, the cross as the annex draws it, the parts' boundaries, within 0.1 mm, read back from our own content stream), by pdftotext's text and-bboxpositions compared with the corpus bill's, and by SwissQRBill validating the payload zxing-cpp reads from our slip. Leaves placing codes on received pages. - Stamps. Delivers
BarcodeStampElementin M09'sPdfStamp: placement on rotated and cropped pages, artifact marking, fixed and templated payloads, the color rule under PDF/A. Proven by the corpus rows below (every committed document, the Bates set, the PDF/A and PDF/UA invoices, the 1000-page journal) andBarcodeBenchmarks. Leaves the specification and the tool. - Specification, alternative text and the tool. Delivers
BarcodeSpecificationand its published grammar, the alternative-text defaults, thebarcodeandqrbillverbs andstamp --barcode. Proven by FsCheck over arbitrary strings (the parser is total and bounded), by unit tests of each verb's handler, and byCorpusToolTestsrunning the AOT binary against the API's own output byte for byte. Leaves M12.5 its use of the specification, and M13 itsFigure.
Tests required
Unit — tests/AdCodicem.Pdf.Tests, under Barcodes/:
- Each encoder: its standard's worked example bit for bit; every capacity cell; each mode, encodation or code set on its own and at every switch; minimal segmentation checked against brute force on short inputs; masks and penalties on the standard's example; Reed–Solomon codewords against published vectors; the Data Matrix 144 × 144 interleaving; UPC-E's expansion rules; check digits computed and refused when wrong.
- Properties (FsCheck): for any payload within capacity, the test-only matrix reader returns it; encoding twice gives equal symbols; painting twice gives equal bytes; every coordinate written is an integer and the module size never uses exponent notation; the painted rectangles cover exactly the dark modules, no more.
- GS1: each linter on valid, boundary and invalid values; associations and exclusions; the bracketed and unbracketed forms agree; the generated table equals the pinned dictionary.
- Payments: every EPC and QR-bill rule on a value that passes, one at each boundary and one that fails; the QR-IBAN and reference-type matrix; replacements reported; payload parse and write round trip (FsCheck over valid bills); the guideline tables per version.
- Culture: every test above also runs under
fr-FR,de-CHandar-SAas the current culture, with identical bytes. - Hostile: payloads at capacity plus one, a million characters, unpaired surrogates, NUL bytes,
NaNand negative sizes, abarcode:string with deep percent-encoding or an absurd length — each ends in the typed exception or the parse error, inside a time and allocation budget. - The painter: the same symbol twice in one document is one XObject; the color rule under each output intent; human-readable text subsetted with only its glyphs.
Integration — tests/AdCodicem.Pdf.IntegrationTests, every referee in a container
(ADR 27):
- Rasterizers — poppler's
pdftoppmand MuPDF'smutool draw, at 300 and 150 dpi, anti-aliasing on. - zxing-cpp — the payload, the symbology identifier, and for QR Code the level and version; the referee the
roadmap names. zbar (
zbarimg) — a second reading of QR Code, Code 128, EAN and UPC. libdmtx (dmtxread) — a second reading of Data Matrix. - GS1 Barcode Syntax Engine — every GS1 element string we emit is valid.
- segno — the EPC QR built from the same data carries the same payload.
- SwissQRBill — every QR-bill payload we write validates and parses to our data.
- poppler (
pdftotext,-bbox) — the slip's text and positions; qpdf--checkon every output; veraPDF on the PDF/A and PDF/UA documents we stamp.
Acceptance conditions
"Every committed document" means every document under documents/ and vendor/ the reader opens, encrypted
ones excepted until M16, as in M06. "Reads back" means zxing-cpp returns the exact bytes from both rasterizers at
both resolutions.
| Documents | Behavior | Verified by |
|---|---|---|
The reference payload set, tests/AdCodicem.Pdf.TestSupport/Barcodes/reference-payloads.json: every symbology, every QR level and mode, Data Matrix in every encodation and both shapes, PDF417 at levels 0 to 8, numeric, text, binary and UTF-8 payloads, the capacity of each at its largest size | Each painted on a page reads back exactly; zbar and libdmtx agree where they read the symbology; zxing-cpp reports the QR level and version we chose | BarcodeRefereeTests.Every_reference_payload_reads_back_exactly |
| GS1 element strings from the GS1 General Specifications' examples (SSCC, GTIN with batch and expiry, a variable measure) | GS1-128 reads back with ]C1 and the same element string; the Syntax Engine accepts it; the text under the bars shows each AI in parentheses | BarcodeRefereeTests.Gs1_element_strings_read_back_and_validate |
vendor/swissqrbill/pdfbox-swiss-qr-bill-a4.pdf — SwissQRBill through PDFBox 3, SIX's specimen data, its 77-module code drawn as 1,265 rectangles at 1.69342 pt a module | The payload zxing-cpp reads from it parses into our model with no error; the bill we paint from that model carries the same payload, line separators aside; SwissQRBill validates ours; our code and cross measure what theirs measure, 46 mm and the annex's cross, within 0.1 mm; pdftotext finds the same headings and values in both, each within 1 mm of the corpus position | CorpusBarcodeTests.The_swiss_qr_bill_is_rebuilt_from_its_own_code |
| QR-bill variants — QRR with a QR-IBAN, SCOR, NON, EUR, no amount, no debtor, each language — not in the corpus (below) | Each rebuilt from its own code as the row above; the blank fields drawn with corner marks | CorpusBarcodeTests.Every_qr_bill_variant_is_rebuilt_from_its_own_code |
documents/invoice/chromium-invoice-fr.pdf, vendor/zugferd/mustang-zugferd2-en16931-invoice.pdf (PDF/A-3u), vendor/docentric/aspose-d365-facturx-extended-invoice.pdf (PDF/A-3b), vendor/pdf-association/pdflib-pps-kraxi-pdfa2a-pdfua1-invoice.pdf (PDF/A-2a and PDF/UA-1) | An EPC QR stamped on each reads back with the payload segno builds for the same data; veraPDF upholds every claim the input had; the embedded Factur-X XML is untouched; every object the stamp did not touch is byte-identical | CorpusBarcodeTests.An_epc_qr_stamped_on_an_invoice_reads_back_and_keeps_its_claims |
| Every committed document | A QR code carrying the document's SHA-256 stamped on its first page reads back from that page, upright as the viewer shows it — through the /Rotate 90 of vendor/opf-format-corpus/pdfmaker7-powerpoint-va-cancer-database-course.pdf, the /Rotate 270 of vendor/us-federal/docusign-pdfkit-gsa-sf30-contract-modification.pdf, the UserUnit of vendor/pdf-association/handwritten-compacted-syntax.pdf, and every crop box that differs from its media box | CorpusBarcodeTests.A_code_stamped_on_every_committed_document_reads_back |
The Bates set of M09's acceptance, and pdfmaker7-powerpoint-va-cancer-database-course.pdf (53 rotated pages) | A Code 128 of each page's Bates number reads back on every page and equals M09's document-to-range map; removing M09's stamps removes the codes with them | CorpusBarcodeTests.Bates_codes_read_back_on_every_page |
vendor/pdf-association/indesign13-pdfua1-german-book-chapter.pdf, vendor/pdf-association/indesign15-pdfua1-form.pdf | A code stamped on every page is an artifact; veraPDF's PDF/UA-1 profile finds no failure the input did not have | CorpusBarcodeTests.Stamped_codes_keep_tagged_documents_valid |
vendor/us-federal/itext-govinfo-us-code-certified.pdf (DocMDP P=1) | Stamping a code is refused with PdfSignatureInvalidationException; with AllowInvalidatingSignatures it is written as an update and the voided certification reported | CorpusBarcodeTests.Stamping_a_certified_document_is_refused_unless_insisted |
documents/stress/reportlab-journal-1000-pages.pdf | One code on every page, then a distinct code per page: memory flat as pages grow, one shared XObject in the first case and each code in its page's suffix stream in the second, the bytes per page measured and recorded in status.md | CorpusBarcodeTests.Stamping_a_thousand_pages_holds_its_budget, BarcodeBenchmarks |
| Every row above | Two runs give identical bytes, under the invariant culture and under fr-FR | CorpusBarcodeTests.Barcodes_are_deterministic |
| The same operations through the tool | The AOT binary produces what the API produces, byte for byte | CorpusToolTests.Barcode_and_qrbill_match_the_api |
The roadmap's two payment conditions, as they are made executable here. "Passes the reference validator of
the Swiss payment standards": SIX's validation portal is an upload service, not something a container runs in
CI, so SwissQRBill's validator stands in for it in CI, and the slips of the variant set are submitted to SIX's
portal by hand once per guideline version, the result recorded in status.md with its date. "Read by a
banking-app-grade decoder": two independent decoders, zxing-cpp and zbar, at 150 dpi as well as 300, with the
payload equal byte for byte to segno's.
Corpus
What the corpus holds
- One document carrying a barcode:
vendor/swissqrbill/pdfbox-swiss-qr-bill-a4.pdf— a Swiss QR-bill from the SwissQRBill Java library through PDFBox 3 with SIX's specimen data (vector-qr-code-rectangles,bezier-path-scissors-symbol,dashed-separator-lines,fractional-a4-mediabox), Liberation Sans in bold and regular, its alternative procedureUV;UltraPay005;12345printed as text. Its code is drawn in the page content rather than an XObject, but already as this design paints one: 77 modules (version 15) in module units under one scale, 1,265 rectangles and a single fill, then the Swiss cross as a filled square and a white cross polygon. Nothing else in the committed corpus carries one: no XFA form has a<barcode>field and no page uses a barcode font (checked on 2026-09-26). - Invoices to stamp: the generated invoices (
documents/invoice/*, Chromium, LibreOffice, ReportLab, Word, the print driver, PDF24); PDF/A-3 Factur-X and ZUGFeRD invoices (vendor/zugferd/mustang-zugferd2-en16931-invoice.pdf,itext-pdfbox-weclapp-facturx-en16931-invoice.pdf,gnuaccounting-mustang10-zugferd-rc-invoice.pdf,vendor/docentric/aspose-d365-facturx-extended-invoice.pdf,fop-factur-x-visualization.pdf); the PDF/A-2a and PDF/UA-1 Kraxi invoice. - Pages to place on:
/Rotate 90on every page ofpdfmaker7-powerpoint-va-cancer-database-course.pdfanddistiller7-pscript5-census-housing-units-2005.pdf, on one page ofdistiller952-pscript5-kb-pdf-risk-inventory.pdf;/Rotate 270in the DocuSign GSA contract modification;UserUnitinhandwritten-compacted-syntax.pdf; crop boxes that differ from media boxes in dozens. - Tagged and certified inputs: three PDF/UA-1 claims veraPDF upholds; the GPO's DocMDP P=1 certification.
- Scale: the 1000-page journal.
What it lacks
| Need | Why | Priority | Likely source |
|---|---|---|---|
The reference payload set itself, as test data in tests/AdCodicem.Pdf.TestSupport — decided on 2026-09-27: it is written from the standards, not received, so it is not a corpus document; a received document or image that carries a code joins the manifest through M07's format field | The roadmap's first acceptance condition is written against "a reference set of payloads" that does not exist yet | 1 | Written here from the standards' examples, GS1's examples and EPC069-12's |
| QR-bills in every variant: QRR with a QR-IBAN, SCOR, NON, EUR, no amount, no debtor with corner marks, each language, and one with billing information and two alternative schemes | One specimen cannot show the reference-type matrix, the blank fields or the headings; our slips must be compared with an independent producer's on each | 1 | Generated here: the qrbill package from pypi (MIT) drawing SVG, printed to PDF by Chromium, recorded in build_corpus.py; SIX's published example bills as a public source if their terms allow, the remote corpus otherwise |
| An invoice with an EPC QR code from a real invoicing system | Payload parity with segno proves the guideline, not what banks' customers receive; a real one shows the field choices and the placement an ERP makes | 2 | A contribution (W04, W07); a German or Austrian public administration's invoice template as a public source |
| Linear and 2D codes painted by other producers — ReportLab's barcode module (Code 128, EAN-13, QR, Data Matrix), LibreOffice's QR and barcode generator, a GS1-128 parcel label | A document that already carries a code is the case a stamp must not disturb, and the input any later recognition would start from | 3 | Generated here: ReportLab (already pinned in requirements.txt) and LibreOffice, recorded in build_corpus.py; a carrier's label as a contribution |
| A form whose pages carry a printed PDF417, such as the paper-forms barcode of a USCIS form | Government forms are the commonest received document with a 2D code; stamping beside it must leave it readable | 3 | A public source: a US federal form (public domain), screened for personal data as the third pass did |
Traps
- A payload is bytes. QR Code's byte mode declares no encoding without an ECI, and decoders guess — Latin-1, Shift JIS, UTF-8 — differently. The rule for text is stated once and tested; the payment codes follow their guidelines, not the rule.
- Separate fills show seams. Adjacent rectangles filled one by one leave hairlines in viewers that anti-alias; one path, one fill.
- Rounding each edge breaks modules. Coordinates in points rounded to four decimals make modules of unequal width at small sizes; module units and one scale do not.
- A decimal comma under
fr-FRorde-CHturns0.5into0,5and the content stream into garbage. Every number goes through the invariant formatter, and the tests change the current culture on purpose. - Data Matrix 144 × 144 is the odd one: its Reed–Solomon blocks are not all the same length. An encoder that assumes they are writes symbols no decoder reads, and only the largest size shows it.
- Mask penalty rule 3 has been read differently by implementations and editions. A decoder reads any mask, so the choice only has to be the standard's and stable: pinned by its worked example.
- PDF417 needs tall rows. Rows under three modules high, or an extreme column count, give symbols that decode in one reader and not another; the options refuse them.
- A wrong check digit is the caller's error. Correcting it in silence prints a code for another product.
- GS1 separators: FNC1 follows a variable-length AI only when another AI follows; the parentheses exist only
in the human-readable text; day
00is valid in some date AIs; an AI's format comes from the dictionary, not from memory. - The QR-bill's rules couple fields: a QR-IBAN requires a QRR reference and an IBAN forbids one; the reference prints in groups the payload does not have; the guideline version changes what is allowed — v2.3 removed combined addresses. The slip must also print at 100 %: a viewer's "fit to page" shrinks a 46 mm code.
- Device colors under PDF/A need a matching output intent; a red code on a CMYK-intent invoice is a conformance failure the caller did not see coming.
- A code on a received page lands on something. Without an opaque quiet zone it may not scan; with one it hides what was there. The stamp's defaults choose readability, and the placement is the caller's.
- The alternative text of a payment code read aloud is a machine layout of an IBAN. The printed slip already says it for a human.
- A
barcode:string comes from a template, and a template from data a user may have typed.
Documentation
docs/website/docs/guides/barcodes.md— the symbologies, their options, sizes and quiet zones, colors, human-readable text, placing a code on a page and in a stamp, determinism.docs/website/docs/guides/payment-codes.md— the EPC QR and the Swiss QR-bill: the data, the validation results, the slip's placements and languages, the guideline versions and how they are kept.docs/website/docs/reference/barcode-specification.md— thebarcode:grammar, shared with M12.5.docs/website/docs/reference/tool/—barcode,qrbill,stamp --barcode.docs/website/docs/introduction.mdanddocs/features/features.json— barcodes delivered, and the entry's group revisited: it sits under "HTML to PDF", while M10's codes serve stamps on received documents as much as generated ones.docs/architecture.md— the satellite's layers; the stamp hook; the seam M12.5 decided.docs/releasing.md— the new package and its API baseline (#42).NOTICE— GS1's Barcode Syntax Dictionary, Apache-2.0, at its pinned commit.docs/corpus.md— the reference payload set as test data inTestSupport, outside the manifest, and why.
Exit criteria
-
AdCodicem.Pdf.Barcodesships the five encoders, GS1-128, the painter, the EPC and QR-bill builders, the stamp element and the specification parser, with no dependency but the core and no AOT or trimming warning. - #42 is fixed before the package ships: an API baseline per package and one pack script for both release paths.
- Each encoder reproduces its standard's worked example and capacity table; the AI table is generated from the pinned dictionary and a test holds it to it.
- The Swiss guideline version encoded is verified against SIX's published guide, recorded in the commit,
and SIX's portal accepts the variant set once, recorded in
status.md. - The priority-1 gaps above are filled; each remaining gap is recorded in
docs/corpus-contributions.md. - The acceptance conditions above pass on the corpus, in CI, with no document skipped.
- Unit tests cover each behavior, its degenerate cases and its hostile ones, under several cultures.
- Integration tests confirm every code through zxing-cpp on two rasterizers, with zbar, libdmtx, the GS1 Syntax Engine, segno, SwissQRBill, veraPDF and qpdf, each in a container.
-
BarcodeBenchmarksmeasures encoding and painting each symbology, and stamping the 1000-page journal, withMemoryDiagnoser;status.mdrecords allocations per symbol and bytes per page. - The tool's verbs ship in the dotnet tool and the AOT binaries, documented.
- The documentation site publishes the pages listed above.
- Every page of Documentation is written in its Diátaxis section, one mode per page (ADR 47).