From 090acc955955854ae628feb1d556589bd918dff8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Sat, 12 Sep 2026 00:12:47 +0200 Subject: [PATCH] fix footer pdf --- static/js/pdf-export.js | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/static/js/pdf-export.js b/static/js/pdf-export.js index 164a7b9..1982511 100644 --- a/static/js/pdf-export.js +++ b/static/js/pdf-export.js @@ -101,10 +101,13 @@ function alignFooterToLastPage(doc, pageHeightMm, marginMm) { if (!documentRoot || !spacer || !footer) return; spacer.style.height = "0px"; + footer.style.removeProperty("margin-top"); const pxPerMm = mmToPx(doc); const pageContentHeight = (Number(pageHeightMm) - (2 * Number(marginMm))) * pxPerMm; const footerHeight = footer.getBoundingClientRect().height; + const footerMarginTop = Number.parseFloat(doc.defaultView?.getComputedStyle(footer).marginTop || "0") || 0; + const bottomGuard = 1.5 * pxPerMm; if (!Number.isFinite(pageContentHeight) || pageContentHeight <= footerHeight || footerHeight <= 0) return; const rootTop = documentRoot.getBoundingClientRect().top; @@ -115,12 +118,31 @@ function alignFooterToLastPage(doc, pageHeightMm, marginMm) { ? lastPageBreak.getBoundingClientRect().bottom - rootTop : 0; - // A forced break starts a fresh page, so only the final segment determines - // where the one-time footer lands on the last printed page. - const finalSegmentHeight = Math.max(0, footerTop - finalSegmentStart); - const positionOnPage = ((finalSegmentHeight % pageContentHeight) + pageContentHeight) % pageContentHeight; - let spacerHeight = pageContentHeight - positionOnPage - footerHeight; - if (spacerHeight < -0.25) spacerHeight += pageContentHeight; + // Measure the end of document content, not the footer box. The footer's + // top margin may be reduced on a tight last page so it does not create an + // otherwise unnecessary extra page. + const contentEnd = Math.max(0, footerTop - footerMarginTop - finalSegmentStart); + const positionOnPage = ((contentEnd % pageContentHeight) + pageContentHeight) % pageContentHeight; + const available = pageContentHeight - positionOnPage; + + let actualMarginTop = footerMarginTop; + let spacerHeight; + + if (available >= footerHeight + bottomGuard) { + // Keep the normal footer gap when possible, but shrink it before moving + // the footer to another page. Leave a small guard against print rounding. + actualMarginTop = Math.min( + footerMarginTop, + Math.max(0, available - footerHeight - bottomGuard), + ); + spacerHeight = available - footerHeight - actualMarginTop - bottomGuard; + } else { + // The footer itself cannot fit on the current page. Align it near the + // bottom of the next page while preserving its normal top gap. + spacerHeight = available + pageContentHeight - footerHeight - actualMarginTop - bottomGuard; + } + + footer.style.marginTop = `${Math.max(0, actualMarginTop)}px`; spacer.style.height = `${Math.max(0, spacerHeight)}px`; } @@ -175,6 +197,8 @@ function printStyles(cssSize, pageWidthMm, marginMm, preserveColors) { margin: 0; padding: 0; border: 0; + break-after: avoid-page; + page-break-after: avoid; } .pdf-generated-footer { box-sizing: border-box; @@ -185,6 +209,8 @@ function printStyles(cssSize, pageWidthMm, marginMm, preserveColors) { font: 500 7.5pt/1.2 Inter, ui-sans-serif, system-ui, sans-serif; letter-spacing: .01em; text-align: center; + break-before: avoid-page; + page-break-before: avoid; break-inside: avoid; page-break-inside: avoid; }