Skip to content

feat(pdf): draw panels as filled Bézier paths instead of rasterizing - #16

Merged
Harsh-2002 merged 1 commit into
mainfrom
feat/pdf-vector-panels
Aug 31, 2026
Merged

feat(pdf): draw panels as filled Bézier paths instead of rasterizing#16
Harsh-2002 merged 1 commit into
mainfrom
feat/pdf-vector-panels

Conversation

@Harsh-2002

Copy link
Copy Markdown
Owner

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.

before after
README export 200,810 B 192,456 B
image XObjects in it 6 0
code/table heavy fixture 144,520 B 126,725 B
its image XObjects 11 0
temp files written one per panel none

Why this needed vendoring genpdfi

Area exposed draw_line — a stroked polyline — and nothing that fills a path. Area.layer is private with no accessor, and Context carries only the font cache, so an Element had no route to printpdf's drawing API, which does support filled Bézier paths.

Added Area::fill_polygon and the Layer::add_filled_shape it calls, both modelled directly on the existing draw_line/add_line_shape pair and going through the same private position()/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:

0.9529412 0.95686275 0.972549 rg
53.858273 742.1512 m
541.41736 742.1512 l
547.6795 742.1512 552.756 737.07477 552.756 730.8126 c
552.756 723.7378 l
552.756 717.47565 547.6795 712.3992 541.41736 712.3992 c

printpdf reads a curve as four consecutive points, taking the pair marked true as its start and first control — that convention is the fiddly part and is commented at the call site.

What this fixes beyond size

Unaffected

Real markdown images still embed (verified: ![](pic.png) → 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_tables asserted a code block produces an image, which is now exactly the wrong contract. It is now test_export_pdf_panels_are_not_rasterized and asserts the opposite. 324 tests pass.

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.
@Harsh-2002
Harsh-2002 merged commit f362ad1 into main Aug 31, 2026
8 of 10 checks passed
@Harsh-2002
Harsh-2002 deleted the feat/pdf-vector-panels branch August 31, 2026 13:01
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.

1 participant