feat(pdf): draw panels as filled Bézier paths instead of rasterizing - #16
Merged
Conversation
Code block and table backgrounds were flat-colour PNGs: one per panel, rasterized at 144 DPI, written to a temp file, decoded and embedded as an image XObject. Corners were antialiased by hand and had to be painted opaque, because genpdfi rejects images carrying alpha, so a rounded panel could not be transparent outside its arc. genpdfi exposed only draw_line, a stroked polyline, and walls off the printpdf layer -- Area.layer is private and Context carries only the font cache -- so there was no way to fill a path from inside an Element. Vendor it and add Area::fill_polygon, modelled on the existing draw_line and going through the same private position mapping. Panels are now a single filled path with true quarter-arc Béziers: exact at any zoom, no temp file, no image XObject, and no opaque corners to fake transparency against the page. README export 200,810 -> 192,456 bytes with 6 image XObjects down to 0; a code-and-table heavy fixture 144,520 -> 126,725 and 11 down to 0. Real markdown images and mermaid diagrams are untouched.
This was referenced Aug 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the last item left open from the PDF work. Code block and table backgrounds were flat-colour PNGs — one rasterized per panel at 144 DPI, written to a temp file, decoded, and embedded as an image XObject.
Why this needed vendoring genpdfi
Areaexposeddraw_line— a stroked polyline — and nothing that fills a path.Area.layeris private with no accessor, andContextcarries only the font cache, so anElementhad no route to printpdf's drawing API, which does support filled Bézier paths.Added
Area::fill_polygonand theLayer::add_filled_shapeit calls, both modelled directly on the existingdraw_line/add_line_shapepair and going through the same privateposition()/transform_position()mapping. genpdfi's repository is archived, so there is no upstream to send this to; it is vendored beside printpdf under[patch.crates-io], Apache-2.0 OR MIT, 248 KB.The output
Real quarter-arc Béziers,
k = r × 0.5522847:printpdf reads a curve as four consecutive points, taking the pair marked
trueas its start and first control — that convention is the fiddly part and is commented at the call site.What this fixes beyond size
interpolate: true, so viewers smooth-scaled it — soft and jagged at once.Unaffected
Real markdown images still embed (verified:
→ 1 image XObject) and mermaid still degrades to a labelled source block without uploading. Output stays byte-identical across runs.One test updated rather than added:
test_export_pdf_renders_code_and_tablesasserted a code block produces an image, which is now exactly the wrong contract. It is nowtest_export_pdf_panels_are_not_rasterizedand asserts the opposite. 324 tests pass.