Skip to content

Pad the PDF export to an even page count or a multiple of four - #282

Merged
zachhannum merged 5 commits into
mainfrom
feat/186-pad-page-count
Oct 11, 2026
Merged

zachhannum merged 5 commits into
mainfrom
feat/186-pad-page-count

Conversation

@zachhannum

Copy link
Copy Markdown
Owner

Closes #186

exportPdf takes a page count to pad to. The PDF writer adds blank pages after the last page of the book until the PDF has an even page count or a multiple of four.

What changed

  • fleuron::pdf has PadTo::{Even, MultipleOfFour}, write_padded, and page_count. write is unchanged and pads nothing.
  • Session::export_padded(pad) writes the padded PDF. Session::exported_pages(pad) returns the page count without writing the PDF.
  • The wasm session takes exportPdf(multipleOf?), where multipleOf is 2 or 4, and has pdfPages(multipleOf?). Any other number is an error.
  • In the npm package, Client.exportPdf(ops, { multipleOf }) and Preview.exportPdf({ multipleOf }) pad the PDF. Client.pdfPages and Preview.pdfPages return the page count. The reply to want: 'pdf' has pages, and want: 'pages' is the question.
  • A blank page uses the page settings of the last page of the book: the same sheet, trim box, and bleed box. Its content stream is empty.

Decisions for the reviewer

  • exportPdf still returns Uint8Array | null. The page count is on the worker reply as pages, and Client returns it through pdfPages and not through the return value of exportPdf. This keeps every existing caller working. If you prefer exportPdf to return { bytes, pages }, that is a breaking change that I did not make without asking.
  • A blank page has no crop marks. The marks are draw items of a page, and a blank page has no draw items.
  • A book with no pages is not padded, because there is no last page to take a size from.
  • The CLI has no flag for this. The issue names exportPdf and the session only.

Tests

  • pdf::tests::a_book_is_padded_to_an_even_count_or_a_multiple_of_four, and the_export_takes_a_count_and_says_how_many_pages_it_writes in the session, for the first checkbox.
  • pdf::tests::a_blank_leaf_is_the_size_of_the_last_page_and_carries_nothing for the second. The book has a running head, a page number, a bleed, and crop marks, and its two pages differ in size.
  • session::output::tests::padding_runs_no_stage_and_leaves_the_preview_as_it_was for the third.
  • pdf::tests::the_count_answered_is_the_count_written for the fourth.
  • crates/fleuron-wasm/tests/pdf.rs covers the binding, and the worker harness pads the fixture book (25 pages to 26 and to 28) and reads pages from the reply.

The CI mirror is green locally, and so is npm test in crates/fleuron-wasm/npm against a fresh module. I did not run the browser test, the package test, or the docs build locally.

@github-actions

github-actions Bot commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

The preview of this pull request is deleted.

@github-actions

github-actions Bot commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

CI — all green

job result time
perf ✅ success 5m39s
test ✅ success 5m59s
advisories ✅ success 0m27s
e2e ✅ success 1m32s
wasm / bindings ✅ success 4m01s

perf-gate — native

fleuron perf gate — native, best of 3

pride-and-prejudice — one column, no images, 307 pages, 9754 lines, 1357 KiB of PDF
  parse                1.6 ms
  style                4.7 ms
  line layout        221.8 ms
  fragment            13.6 ms
  layout             234.3 ms
  serialize           11.7 ms
  pdf                209.2 ms
  end to end         449.8 ms
  re-render           18.6 ms
  display structure      6244 KiB  digest 6a09955015c654f3
  layout              14.2 MiB peak
  session             38.8 MiB peak


  parse                1.6 ms   ceiling    20.0 ms      +92% headroom  ok
  end to end         449.8 ms   ceiling  1000.0 ms      +55% headroom  ok
  layout peak         14.2 MiB  ceiling    32.0 MiB     +56% headroom  ok
  re-render           18.6 ms   ceiling    20.0 ms       +7% headroom  ok
  session peak        38.8 MiB  ceiling    64.0 MiB     +39% headroom  ok

pride-and-prejudice — two columns, no images, 320 pages, 20215 lines, 1399 KiB of PDF
  parse                1.7 ms
  style                4.8 ms
  line layout        177.6 ms
  fragment            19.3 ms
  layout             193.8 ms
  serialize           12.9 ms
  pdf                218.0 ms
  end to end         418.2 ms
  re-render           25.5 ms
  display structure      6531 KiB  digest ddc2d3962a10a963
  layout              17.0 MiB peak
  session             51.1 MiB peak


  parse                1.7 ms   ceiling    20.0 ms      +92% headroom  ok
  end to end         418.2 ms   ceiling  1000.0 ms      +58% headroom  ok
  layout peak         17.0 MiB  ceiling    32.0 MiB     +47% headroom  ok
  re-render           25.5 ms   ceiling    20.0 ms      -28% headroom  OVER
  session peak        51.1 MiB  ceiling    64.0 MiB     +20% headroom  ok

pride-and-prejudice — one column, an image a chapter, 334 pages, 9754 lines, 1405 KiB of PDF
  parse                1.7 ms
  style                5.0 ms
  line layout        222.3 ms
  fragment            89.0 ms
  layout             534.7 ms
  serialize           11.8 ms
  pdf                208.0 ms
  end to end         749.4 ms
  re-render          326.8 ms
  display structure      6266 KiB  digest ec476e93ef01c2a8
  layout              14.5 MiB peak
  session             49.8 MiB peak


  parse                1.7 ms   ceiling    20.0 ms      +92% headroom  ok
  end to end         749.4 ms   ceiling  1000.0 ms      +25% headroom  ok
  layout peak         14.5 MiB  ceiling    32.0 MiB     +55% headroom  ok
  re-render          326.8 ms   ceiling   500.0 ms      +35% headroom  ok
  session peak        49.8 MiB  ceiling    64.0 MiB     +22% headroom  ok

pride-and-prejudice — two columns, an image a chapter, 342 pages, 20215 lines, 1439 KiB of PDF
  parse                1.6 ms
  style                4.9 ms
  line layout        178.1 ms
  fragment            92.5 ms
  layout             448.5 ms
  serialize           12.8 ms
  pdf                215.3 ms
  end to end         670.3 ms
  re-render          290.6 ms
  display structure      6538 KiB  digest 5affd7079ebe5775
  layout              17.0 MiB peak
  session             61.9 MiB peak


  parse                1.6 ms   ceiling    20.0 ms      +92% headroom  ok
  end to end         670.3 ms   ceiling  1000.0 ms      +33% headroom  ok
  layout peak         17.0 MiB  ceiling    32.0 MiB     +47% headroom  ok
  re-render          290.6 ms   ceiling   500.0 ms      +42% headroom  ok
  session peak        61.9 MiB  ceiling    64.0 MiB      +3% headroom  ok

monte-cristo — one column, no images, 1185 pages, 38917 lines, 5149 KiB of PDF
  parse                8.1 ms
  style               25.2 ms
  line layout        853.6 ms
  fragment            58.4 ms
  layout             900.7 ms
  serialize           49.1 ms
  pdf                791.5 ms
  end to end        1725.4 ms
  re-render           85.5 ms
  display structure     23903 KiB  digest 8f1298847615228a
  layout              54.6 MiB peak
  session            152.2 MiB peak


1 budget(s) over ceiling
warning only: pass --strict to fail on this

perf-gate — wasm

fleuron perf gate — wasm, best of 3

pride-and-prejudice — one column, no images, 307 pages, 9754 lines, 1357 KiB of PDF
  parse                3.9 ms
  style                7.4 ms
  line layout        330.2 ms
  fragment            17.2 ms
  layout             347.1 ms
  serialize           28.6 ms
  pdf                303.3 ms
  end to end         661.6 ms
  re-render           24.9 ms
  display structure      6244 KiB  digest 6a09955015c654f3
  layout              13.1 MiB peak
  session             34.7 MiB peak


  parse                3.9 ms   ceiling    20.0 ms      +81% headroom  ok
  layout + wire      375.7 ms   ceiling   500.0 ms      +25% headroom  ok
  layout peak         13.1 MiB  ceiling    32.0 MiB     +59% headroom  ok
  re-render           24.9 ms   ceiling    20.0 ms      -25% headroom  OVER
  session peak        34.7 MiB  ceiling    64.0 MiB     +46% headroom  ok

pride-and-prejudice — two columns, no images, 320 pages, 20215 lines, 1399 KiB of PDF
  parse                3.9 ms
  style                7.3 ms
  line layout        268.2 ms
  fragment            24.6 ms
  layout             292.2 ms
  serialize           30.0 ms
  pdf                321.1 ms
  end to end         624.5 ms
  re-render           32.5 ms
  display structure      6531 KiB  digest ddc2d3962a10a963
  layout              14.9 MiB peak
  session             43.0 MiB peak


  parse                3.9 ms   ceiling    20.0 ms      +81% headroom  ok
  layout + wire      322.2 ms   ceiling   500.0 ms      +36% headroom  ok
  layout peak         14.9 MiB  ceiling    32.0 MiB     +54% headroom  ok
  re-render           32.5 ms   ceiling    20.0 ms      -62% headroom  OVER
  session peak        43.0 MiB  ceiling    64.0 MiB     +33% headroom  ok

pride-and-prejudice — one column, an image a chapter, 334 pages, 9754 lines, 1405 KiB of PDF
  parse                3.9 ms
  style                7.6 ms
  line layout        328.9 ms
  fragment           147.4 ms
  layout             813.3 ms
  serialize           28.7 ms
  pdf                303.4 ms
  end to end        1128.2 ms
  re-render          494.0 ms
  display structure      6266 KiB  digest ec476e93ef01c2a8
  layout              13.3 MiB peak
  session             45.0 MiB peak


  parse                3.9 ms   ceiling    20.0 ms      +81% headroom  ok
  layout + wire      841.9 ms   ceiling   900.0 ms       +6% headroom  ok
  layout peak         13.3 MiB  ceiling    32.0 MiB     +58% headroom  ok
  re-render          494.0 ms   ceiling   500.0 ms       +1% headroom  ok
  session peak        45.0 MiB  ceiling    64.0 MiB     +30% headroom  ok

pride-and-prejudice — two columns, an image a chapter, 342 pages, 20215 lines, 1439 KiB of PDF
  parse                3.9 ms
  style                7.6 ms
  line layout        266.8 ms
  fragment           152.6 ms
  layout             695.1 ms
  serialize           29.8 ms
  pdf                317.8 ms
  end to end        1024.4 ms
  re-render          442.1 ms
  display structure      6538 KiB  digest 5affd7079ebe5775
  layout              14.9 MiB peak
  session             53.0 MiB peak


  parse                3.9 ms   ceiling    20.0 ms      +81% headroom  ok
  layout + wire      724.9 ms   ceiling   900.0 ms      +19% headroom  ok
  layout peak         14.9 MiB  ceiling    32.0 MiB     +53% headroom  ok
  re-render          442.1 ms   ceiling   500.0 ms      +12% headroom  ok
  session peak        53.0 MiB  ceiling    64.0 MiB     +17% headroom  ok

monte-cristo — one column, no images, 1185 pages, 38917 lines, 5149 KiB of PDF
  parse               17.3 ms
  style               39.4 ms
  line layout       1268.3 ms
  fragment            71.7 ms
  layout            1335.9 ms
  serialize          110.1 ms
  pdf               1147.7 ms
  end to end        2540.3 ms
  re-render          113.8 ms
  display structure     23903 KiB  digest 8f1298847615228a
  layout              50.3 MiB peak
  session            136.1 MiB peak


2 budget(s) over ceiling
warning only: pass --strict to fail on this

Full run · commit 11285b3c

@zachhannum
zachhannum merged commit e144a57 into main Oct 11, 2026
9 checks passed
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.

Export pads a book to an even page count or a multiple of four

1 participant