feat(issued-orders-pdf): true per-page footer (Vystavil + Strana X z Y), sections flow inline

The PDF now carries a real repeating footer on EVERY page via Puppeteer's footerTemplate (Chromium has no CSS margin-box support, so this is the only true footer): 'Vystavil: <logged-in user>' on the left, 'Strana X z Y' / 'Page X of Y' centered, localized by the document language. htmlToPdf gained an optional footerTemplate option (empty header suppresses Chromium's default; bottom margin grows to 18mm). The old body footer pinned by flex min-height is gone. The 'Obsah' sections no longer start a new page with a repeated header - they flow inline right after the items/totals block and paginate naturally (break-inside: avoid per section). Applied at all three render sites (PDF route, ?save=1 archive, /file fallback).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-06-10 17:44:11 +02:00
parent 75dc97516e
commit 268a947c85
4 changed files with 129 additions and 79 deletions

View File

@@ -63,7 +63,23 @@ async function getBrowser(): Promise<Browser> {
}
}
export async function htmlToPdf(html: string): Promise<Buffer> {
export interface HtmlToPdfOptions {
/**
* Chromium footerTemplate HTML rendered in the bottom margin of EVERY page
* — the only true repeating footer Puppeteer supports (CSS @page margin
* boxes are not implemented in Chromium). Styles must be inline and the
* font-size explicit (Chromium defaults it to 0); `.pageNumber` /
* `.totalPages` spans are substituted by Chromium. When set, the bottom
* margin grows to make room and an empty headerTemplate suppresses
* Chromium's default date/title header.
*/
footerTemplate?: string;
}
export async function htmlToPdf(
html: string,
options: HtmlToPdfOptions = {},
): Promise<Buffer> {
const b = await getBrowser();
const page = await b.newPage();
// Per-request timeouts so one stuck render (e.g. image tag pointing at a
@@ -78,7 +94,19 @@ export async function htmlToPdf(html: string): Promise<Buffer> {
const pdf = await page.pdf({
format: "A4",
printBackground: true,
margin: { top: "10mm", bottom: "10mm", left: "10mm", right: "10mm" },
margin: {
top: "10mm",
bottom: options.footerTemplate ? "18mm" : "10mm",
left: "10mm",
right: "10mm",
},
...(options.footerTemplate
? {
displayHeaderFooter: true,
headerTemplate: "<span></span>",
footerTemplate: options.footerTemplate,
}
: {}),
timeout: 15_000,
});
return Buffer.from(pdf);