Skip to content

print: a built-in <qrcode> element renders a QR code from a line-preserving payload (#8608) - #8611

Merged
delchev merged 1 commit into
masterfrom
issue-8608-print-qrcode
Nov 13, 2026
Merged

delchev merged 1 commit into
masterfrom
issue-8608-print-qrcode

Conversation

@delchev

@delchev delchev commented Nov 13, 2026

Copy link
Copy Markdown
Contributor

Cause

A .print template could not print a QR code. sdk.utils.QrCode / QRCodeFacade generate an image and TagRegistry documented registry.register("qrcode") as its extension example, but nothing connected them: <qrcode> in a shipped template was Unknown tag <qrcode>, and the parser collapses every element's text to one line, so a line-oriented payload (the EPC069-12 "SEPA credit transfer" payment code) could not even be written.

Change

<qrcode> is a built-in tag:

<if source="document.BankAccount.Iban">
    <qrcode size="90" level="M" align="right">
        BCD
        002
        1
        SCT
        {{document.BankAccount.Bic}}
        {{document.Company.LegalName}}
        {{document.BankAccount.Iban}}
        EUR{{document.Balance:0.00}}


        Invoice {{document.Number}}
    </qrcode>
</if>

<qrcode value="https://example.com/invoices/{{document.Number}}"/>
  • Payload = the element's bound text (placeholders, alternatives and format specifiers as in <text>) with its line breaks kept, or the single-line value="..." attribute (wins when not blank).
  • Whitespace rule (applied at parse time, before data is merged): line endings become \n; every line is trimmed of leading/trailing spaces and tabs (the template's indentation); the first line is dropped when blank and so is the last (exactly one each - the formatting around the content); every other line stays, empty ones included. A line holding only a placeholder stays a line whatever it resolves to.
  • size - side of the printed square in points, quiet zone included (default 90); level - L/M/Q/H, default M; align. A wrong size/level is a layout error at the attribute.
  • Fail-soft like <image>: a payload blank after binding, or too long for a QR code at the level, renders nothing. Works in the flow, row, stack, header/footer, and under <if>.

Where the code lives:

  • modules/parsers/document stays dependency-free: QrCodeNode, TagRegistry.registerLinePreserving / preservesLines (the parser keeps lines for such tags), renderer.QrCodeEncoder + QrCodeLevel, and XslFoRenderer(imageResolver, qrCodeEncoder) emitting an fo:external-graphic of size x size. Default encoder NONE renders no QR code.
  • components/engine/engine-document: PrintQrCodeEncoder (ZXing core 3.5.4, the version api-utils already ships) draws a 1-bit PNG - 8 px per module plus the 4-module quiet zone - inlined as a data: URI, wired in PrintRenderer, so the Print button, attach: print and function: Snapshot all get it. The payload is written as UTF-8 bytes without an ECI header. QRCodeFacade is not reused: it encodes ISO-8859-1 (a Cyrillic name becomes ?) and takes no level.
  • Docs: parser README ("QR codes" section; the extension example now uses stamp), engine-document/CLAUDE.md, .claude/docs/document-templates.md.

There is no HTML preview of a .print in the platform (the Settings preview renders the PDF), so the PDF renderer is the only one touched. No generator or template changed, so no sample-intent-* fixture is affected.

Verification

Run locally in a worktree:

  • mvn test -pl modules/parsers/document,components/engine/engine-document - parsers-document 169 tests, engine-document 94 tests, 0 failures.
    • QrCodeTest (20): parse (lines kept, indentation trimmed, CRLF/CR, only one formatting line dropped at each end, entities), bind (11-line EPC payload exact, interior empty lines kept, empty placeholder line stays, value short form), render (payload + level reach the encoder, defaults, blank/declined payload renders nothing, row/stack/footer/<if>, invalid level/size).
    • PrintQrCodeEncoderTest (9): the PNG is scanned back with ZXing to the exact 12-line Cyrillic payload at every level; trailing empty line kept; geometry; an oversized payload is declined softly.
    • PrintRendererTest.aPaymentQrCodeScansBackFromTheRenderedPdf: a real PDF through PrintRenderer.renderPdf (parse, bind, XSL-FO, FOP), page rasterized at 200 DPI with PDFBox (new test-scope dependency, 3.0.3 - the FOP line's fontbox version) and the code scanned off the page: the exact 12-line EPC payload, Cyrillic beneficiary and empty lines included.
  • mvn -T 1C formatter:validate with the formatter cache wiped - green.
  • mvn -P release ... install -pl modules/parsers/document,components/engine/engine-document (javadoc) - green.

Not verified: no scan with a real banking app (the decoder in the tests is ZXing, without a character-set hint); PrintRenderIT was not run locally.

Fixes #8608

🤖 Generated with Claude Code

…erving payload (#8608)

A .print template could not print a QR code - the payment code (EPC069-12,
"SEPA credit transfer") mainstream invoicing tools put on an invoice.

<qrcode> is now a built-in tag. Its payload is the element's bound text with
the line breaks kept (per-line trim of the template's indentation, the blank
first/last formatting line dropped, interior empty lines kept), or the
single-line value="..." attribute; size (points, default 90), align and level
(L/M/Q/H, default M). A blank payload, or one too long for a QR code, renders
nothing - like an unreadable <image>.

The parser library stays dependency-free: it models the node
(QrCodeNode, TagRegistry.registerLinePreserving) and hands the payload to a
QrCodeEncoder; engine-document's PrintQrCodeEncoder encodes it with ZXing as
UTF-8 bytes, so Cyrillic names scan back.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@delchev
delchev merged commit 45ee28e into master Nov 13, 2026
26 of 28 checks passed
@delchev
delchev deleted the issue-8608-print-qrcode branch November 13, 2026 19:52
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.

print: a .print template cannot render a QR code - built-in <qrcode> element (payment QR on invoices, EPC069-12)

1 participant