Skip to main content

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.Barcodes satellite, under the prefix reserved by ADR 24, depending on AdCodicem.Pdf alone (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's PdfMarkPosition; the barcode: specification that M12.5 resolves, parsed into options with an intrinsic size; the alternative text M13 will put on a Figure;
  • the tool: barcode, qrbill, and a --barcode option on M09's stamp.

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 Figure with /Alt in 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
TypeResponsibility
QrCode, DataMatrix, Pdf417, Code128, EanUpcStatic Encode(payload, options) → BarcodeSymbol
QrCodeOptions, DataMatrixOptions, Pdf417Options, Code128Options, EanUpcOptionsImmutable records (ADR 8): error-correction level, version or size bounds, shape, columns, code set, ECI, quiet zone
BarcodeSymbolImmutable: 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
Gs1ElementStringParsed Application Identifiers with their values, validated; the FNC1-separated data and the bracketed human-readable form
PdfBarcodeA symbol painted into a document as a form XObject, with its size in points and its alternative text
PdfBarcodeAppearanceImmutable: module size or target size, foreground color, background (none or opaque), human-readable text and its font, bar-width reduction, alternative text
EpcCreditTransferThe EPC069-12 data, validated; its payload; its QR symbol at level M
SwissQrBill, SwissQrBillValidationResult, SwissQrBillLayoutThe QR-bill data, its validation against the guidelines in force, its payload written and parsed, and the slip painted
BarcodeStampElementA barcode as an element of M09's PdfStamp, its payload fixed or a PdfStampText template
BarcodeSpecificationThe 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.

SymbologyStandardCoveredQuiet zoneChoices we fix
QR CodeISO/IEC 18004:2024, model 2Versions 1 to 40; levels L, M, Q, H; numeric, alphanumeric and byte modes; ECI4 modulesThe 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 MatrixISO/IEC 16022:2024, ECC 200The 24 square and 6 rectangular sizes; ASCII, C40, Text, X12, EDIFACT and Base 256 encodations; ECI1 moduleThe smallest symbol of the requested shape; encodation minimal in codewords, ties in the standard's order
PDF417ISO/IEC 15438:2015Text, byte and numeric compaction; error-correction levels 0 to 8; 1 to 30 columns, 3 to 90 rows; compact PDF4172 modulesThe level the standard recommends for the data length unless given; rows 3 modules high; columns from a target aspect ratio
Code 128ISO/IEC 15417:2007Code sets A, B and C; FNC1 to FNC410 modulesThe shortest symbol, ties to fewer shifts and code changes
GS1-128GS1 General SpecificationsFNC1 first, element strings, at most 48 data characters and 165 mm10 modulesThe caller's AI order kept; FNC1 only after a variable-length AI that is not last
EAN-13, EAN-8, UPC-A, UPC-EISO/IEC 15420:2009Check digit computed, or verified when given; UPC-E only for the UPC-A numbers it can express11 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 (an ArgumentException) 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 Eci is Auto, and without when it is None. 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 are ReadOnlySpan<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 by BarcodeBenchmarks.
  • Correctness by construction. Each encoder reproduces its standard's worked example bit for bit — ISO/IEC 18004's 01234567 at 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.Parse takes 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 day 00 allowed 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 /BBox is [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 DeviceGray 0 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 — DeviceGray any intent, DeviceRGB an RGB one, DeviceCMYK a CMYK one — and a mismatch follows M09's PdfConformancePolicy: refused with PdfConformanceException by default, or written with the claim removed and reported under RemoveClaim. 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.AlternativeText defaults 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 a Figure; 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: version 002 by default (BIC optional within the EEA) or 001 (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 11649 RF, 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 set 1 (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. SwissQrBillGuidelines holds, 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) or NON with 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. SwissQrBillValidationResult lists 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, .Barcodes references 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 /None through 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's ToUnicode), as it is in the corpus file.

Stamping (M09)​

  • BarcodeStampElement is one more element of M09's PdfStamp, beside text, boxes and images: placed by PdfMarkPosition on the visible page, in the orientation /Rotate gives it and at the size UserUnit gives it, so that the code reads upright; its opaque background keeps its quiet zone clear.
  • Every mark M09 writes is an /Artifact of type /Pagination, and so is a code: a Bates code's subtype follows M09's rule by output version — Header or Footer by position in PDF 1.7, Bates in 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 Other change — a certification refuses it with PdfSignatureInvalidationException, approval signatures take it in an incremental update reported as stamp.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: .Html references .Barcodes directly, 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.

  1. 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 and PdfBarcode. 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 new BarcodeRefereeTests: 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.
  2. 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.
  3. 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.
  4. 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.
  5. GS1. Delivers the AI table generated from the pinned Barcode Syntax Dictionary, Gs1ElementString, GS1-128 with FNC1 and its human-readable text, the NOTICE entry. 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 ]C1 identifier and the same string. Leaves the payment payloads.
  6. 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.
  7. 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.
  8. Swiss QR-bill: the slip. Delivers SwissQrBillLayout in 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 -bbox positions compared with the corpus bill's, and by SwissQRBill validating the payload zxing-cpp reads from our slip. Leaves placing codes on received pages.
  9. Stamps. Delivers BarcodeStampElement in M09's PdfStamp: 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) and BarcodeBenchmarks. Leaves the specification and the tool.
  10. Specification, alternative text and the tool. Delivers BarcodeSpecification and its published grammar, the alternative-text defaults, the barcode and qrbill verbs and stamp --barcode. Proven by FsCheck over arbitrary strings (the parser is total and bounded), by unit tests of each verb's handler, and by CorpusToolTests running the AOT binary against the API's own output byte for byte. Leaves M12.5 its use of the specification, and M13 its Figure.

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-CH and ar-SA as the current culture, with identical bytes.
  • Hostile: payloads at capacity plus one, a million characters, unpaired surrogates, NUL bytes, NaN and negative sizes, a barcode: 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 pdftoppm and MuPDF's mutool 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 --check on 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.

DocumentsBehaviorVerified 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 sizeEach 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 choseBarcodeRefereeTests.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 parenthesesBarcodeRefereeTests.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 moduleThe 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 positionCorpusBarcodeTests.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 marksCorpusBarcodeTests.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-identicalCorpusBarcodeTests.An_epc_qr_stamped_on_an_invoice_reads_back_and_keeps_its_claims
Every committed documentA 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 boxCorpusBarcodeTests.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 themCorpusBarcodeTests.Bates_codes_read_back_on_every_page
vendor/pdf-association/indesign13-pdfua1-german-book-chapter.pdf, vendor/pdf-association/indesign15-pdfua1-form.pdfA code stamped on every page is an artifact; veraPDF's PDF/UA-1 profile finds no failure the input did not haveCorpusBarcodeTests.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 reportedCorpusBarcodeTests.Stamping_a_certified_document_is_refused_unless_insisted
documents/stress/reportlab-journal-1000-pages.pdfOne 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.mdCorpusBarcodeTests.Stamping_a_thousand_pages_holds_its_budget, BarcodeBenchmarks
Every row aboveTwo runs give identical bytes, under the invariant culture and under fr-FRCorpusBarcodeTests.Barcodes_are_deterministic
The same operations through the toolThe AOT binary produces what the API produces, byte for byteCorpusToolTests.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 procedure UV;UltraPay005;12345 printed 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 90 on every page of pdfmaker7-powerpoint-va-cancer-database-course.pdf and distiller7-pscript5-census-housing-units-2005.pdf, on one page of distiller952-pscript5-kb-pdf-risk-inventory.pdf; /Rotate 270 in the DocuSign GSA contract modification; UserUnit in handwritten-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​

NeedWhyPriorityLikely 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 fieldThe roadmap's first acceptance condition is written against "a reference set of payloads" that does not exist yet1Written 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 schemesOne specimen cannot show the reference-type matrix, the blank fields or the headings; our slips must be compared with an independent producer's on each1Generated 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 systemPayload parity with segno proves the guideline, not what banks' customers receive; a real one shows the field choices and the placement an ERP makes2A 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 labelA document that already carries a code is the case a stamp must not disturb, and the input any later recognition would start from3Generated 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 formGovernment forms are the commonest received document with a 2D code; stamping beside it must leave it readable3A 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-FR or de-CH turns 0.5 into 0,5 and 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 00 is 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 — the barcode: grammar, shared with M12.5.
  • docs/website/docs/reference/tool/ — barcode, qrbill, stamp --barcode.
  • docs/website/docs/introduction.md and docs/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 in TestSupport, outside the manifest, and why.

Exit criteria​

  • AdCodicem.Pdf.Barcodes ships 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.
  • BarcodeBenchmarks measures encoding and painting each symbology, and stamping the 1000-page journal, with MemoryDiagnoser; status.md records 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).