Export Full Book as PDF with automatic chapter page breaks

Print the Full Book window via webContents.printToPDF (A4, 1in margins),
reusing the existing print stylesheet so every chapter after the first
starts on its own page and all app chrome is stripped from the output.
Expose it as an Export PDF button in the Full Book header and a File menu
item, save via a native save dialog (self-test writes to FOLIO_TEST_PDF),
and force light-on-white colors in print so dark-mode exports stay readable.
Add e2e coverage that exports through the real renderer->IPC->print chain
and verifies a valid non-empty PDF is written.
This commit is contained in:
avi 2026-08-18 10:11:06 -05:00
commit 778b480ea2
8 changed files with 137 additions and 0 deletions

View file

@ -9,5 +9,6 @@ BOOK2=$(node -e 'const fs=require("fs"),path=require("path"),os=require("os");co
export FOLIO_SELF_TEST=1
export FOLIO_TEST_BOOK="$BOOK"
export FOLIO_TEST_BOOK2="$BOOK2"
export FOLIO_TEST_PDF="$BOOK/export-test.pdf"
trap 'rm -rf "$BOOK" "$BOOK2"' EXIT
xvfb-run -a electron .

View file

@ -407,6 +407,34 @@ const BOOK2_DIR = __BOOKDIR2_VALUE__;
out.fbWithEmpty = assembledWithEmpty;
out.fbEmptyOk = assembledWithEmpty.includes("# Empty") && assembledWithEmpty.includes("Beta Renamed");
// --- PDF export of the Full Book ---
// The e2e harness sets FOLIO_TEST_PDF, so the main process skips the
// native save dialog and writes to that path, returning the byte count
// and whether the buffer starts with the PDF magic number. The Full Book
// view is on screen here, so what gets printed is exactly the assembled
// manuscript (the print stylesheet hides all app chrome).
const pdfBtn = document.getElementById("exportPdfBtn");
out.pdfBtnPresent = !!pdfBtn && !pdfBtn.disabled;
out.pdfExportPdfVisible = !document.getElementById("fullBookView").classList.contains("hidden");
pdfBtn.click();
await new Promise((r) => setTimeout(r, 2500));
out.pdfStatusShown = document
.getElementById("fullBookStatus")
.textContent.startsWith("PDF saved to");
const pdfRes = await window.folio.exportPdf();
out.pdfExport =
pdfRes && "filePath" in pdfRes
? { filePath: pdfRes.filePath, bytes: pdfRes.bytes, pdfOk: pdfRes.pdfOk }
: pdfRes;
out.pdfExportOk =
!!pdfRes &&
!("error" in pdfRes) &&
"filePath" in pdfRes &&
pdfRes.filePath.endsWith(".pdf") &&
typeof pdfRes.bytes === "number" &&
pdfRes.bytes > 500 &&
pdfRes.pdfOk === true;
const okFlags = [
"dndMoved",
"kbMoved",
@ -452,6 +480,10 @@ const BOOK2_DIR = __BOOKDIR2_VALUE__;
"fbAfterDeleteOk",
"fbAfterRenameOk",
"fbEmptyOk",
"pdfBtnPresent",
"pdfExportPdfVisible",
"pdfStatusShown",
"pdfExportOk",
];
out.all = okFlags.every((f) => out[f] === true);
} catch (e) {