Add an HTML image factory - #432
Open
vsengar26 wants to merge 1 commit into
Open
Conversation
Adds an `html` image factory that renders the code as an HTML fragment, for embedding in a document rather than referencing an image file. This covers contexts where an <img> is not an option: email clients block external images by default and support neither inline SVG nor data: URIs, and some sanitizers and CSP policies strip image sources. The markup is a table of cells. Runs of same-coloured modules within a row are merged with colspan, and identical adjacent rows are merged into a single taller row, which keeps the output to about a third of the cells of a naive one-td-per-module grid. Only inline styles and the legacy table attributes are used, as that is the subset of HTML that email clients render reliably. Colours, the aria-label and extra table attributes can be set through make_image(); box_size and border behave as they do for the other factories. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Adds
qrcode.image.html.HtmlImage, which renders a QR code as an HTML fragment, plus a--factory=htmlshortcut for theqrscript.Why
Every current factory produces an image, which then has to reach the page as a file or an image source. There are a few common situations where that isn't possible, or isn't wanted:
There is no image pipeline. Inlining markup into a template means no PNG written to disk, no file hosted and served, no attachment referenced by Content-ID, and no separate
/qr.pngview to build, route and secure. It also needs no imaging dependency at all — the output is a string built with the standard library, which matters in slim containers and serverless builds where Pillow or a native PNG library is unwelcome. For short-lived codes such as TOTP enrolment or single-use tickets, it also avoids the code existing as a fetchable URL that can end up in logs or caches.Sanitisers and content policies. Plenty of pipelines strip
<img>, refusedata:URIs, or restrict image hosts through CSP. Markup survives in places an image source does not — the most familiar example being email, where external images are blocked by default in most clients until the recipient chooses to load them, so a legitimate QR code shows as a placeholder on first read.Styling and theming. As part of the DOM the code inherits the surrounding CSS: recolour it, resize it, give it a dark-mode variant, and it stays crisp at any zoom, all without re-rendering.
The SVG factory is the closest fit, but inline SVG is not rendered by many email clients and is commonly stripped by HTML sanitisers, so it does not cover these cases.
How
The code is drawn as a table of cells. Two things keep that from being enormous:
<td>withcolspan<tr>— which mostly collapses the quiet zoneFor a default version-4 code that is 589 cells across 35 rows, rather than 1681 cells across 41 rows.
Only inline styles and the legacy table attributes are used, since that is the subset of HTML that renders consistently in the constrained environments above. Trimmed sample output:
box_sizeandborderbehave as they do for the other factories, setting the module size in pixels and the width of the quiet zone.make_image()additionally accepts:fill_color#000000back_color#ffffffaltQR Codearia-labelattrib{}<table>elementto_string()returns the markup.save()accepts a path, a binary stream or a text stream. Attribute values are escaped.Testing
test_qrcode_html.pyparses the generated markup back into a matrix and asserts it matchesQRCode.get_matrix(), overborder0/1/4 andbox_size1/7, which covers the two merging steps. The rest coversto_string(), the threesave()targets,check_kind, colours,alt/attriband escaping;test_script.pycovers the CLI shortcut.Beyond the suite, I rendered eight variants in headless Chromium — default,
box_size2 and 4,border0 and 8, custom colours, error-correction H, and a version-12 code — screenshotted each and decoded the screenshots with zxing. All scanned back to the original data, as did the fragment written by theqrscript.Notes
box_sizechanges the rendered size but not the markup size.