This commit is contained in:
Mateusz Gruszczyński committed 2026-10-29 15:20:35 +01:00
1 parent d36a4b2451
commit 99a5700caa
8 files changed
+176 -56

No files matched your search

+40 -29
View File
@@ -317,11 +317,18 @@ function groupHeadingsWithLead(root) {
});
}
function markAtomicTextBlocks(root) {
root.querySelectorAll("h1, h2, h3, h4, h5, h6, p, blockquote, dt, dd").forEach(node => {
if (!node.closest(".pdf-keep-unit")) node.classList.add("pdf-atomic-text");
});
}
function preparePrintPagination(root) {
collapseHeadingGaps(root);
markShortKeepBlocks(root);
groupAtomicVisuals(root);
groupHeadingsWithLead(root);
markAtomicTextBlocks(root);
}
async function ensurePrintableMermaid(root, darkMode = false) {
@@ -446,8 +453,17 @@ function alignFooterToLastPage(doc, pageHeightMm, marginMm) {
const minGap = 4 * pxPerMm;
if (!Number.isFinite(pageContentHeight) || pageContentHeight <= footerHeight || footerHeight <= 0) return;
const measuredFill = measureLastPageFill(doc, documentRoot, pageContentHeight);
const positionOnPage = measuredFill ?? fallbackLastPageFill(documentRoot, spacer, pageContentHeight);
// The multicolumn pagination probe is only needed when the document contains
// an explicit PDF page break. For ordinary flowing documents Chromium's
// column layout can report the sentinel at the end of a full column, which
// incorrectly makes the footer look as if it no longer fits and creates an
// otherwise empty extra page containing only the footer.
const fallbackFill = fallbackLastPageFill(documentRoot, spacer, pageContentHeight);
const hasExplicitPageBreak = Boolean(doc.querySelector(".pdf-content .pdf-page-break"));
const measuredFill = hasExplicitPageBreak
? measureLastPageFill(doc, documentRoot, pageContentHeight)
: null;
const positionOnPage = measuredFill ?? fallbackFill;
const available = Math.max(0, pageContentHeight - positionOnPage);
const spacerHeight = available >= footerHeight + minGap + bottomGuard
@@ -464,7 +480,7 @@ function fitOversizedKeepUnits(doc, pageHeightMm, marginMm) {
const pageContentHeight = (Number(pageHeightMm) - (2 * Number(marginMm))) * pxPerMm;
const maxHeight = Math.max(40, pageContentHeight - Math.max(2, pxPerMm));
root.querySelectorAll(".pdf-keep-unit").forEach(unit => {
root.querySelectorAll(".pdf-keep-unit, .pdf-keep-short, .pdf-atomic-text").forEach(unit => {
unit.classList.remove("pdf-keep-unit--oversized");
let rect = unit.getBoundingClientRect();
if (rect.height <= maxHeight) return;
@@ -659,25 +675,13 @@ function printStyles(cssSize, pageWidthMm, pageHeightMm, marginMm, darkMode) {
break-after: avoid-page;
page-break-after: avoid;
}
.pdf-content h1,
.pdf-content h2,
.pdf-content h3,
.pdf-content h4,
.pdf-content h5,
.pdf-content h6,
.pdf-content blockquote,
.pdf-content dd,
.pdf-content .footnotes li {
break-inside: avoid-page;
page-break-inside: avoid;
break-inside: auto;
page-break-inside: auto;
}
.pdf-content p,
.pdf-content li,
.pdf-content dd { orphans: 3; widows: 3; }
.pdf-content p {
break-inside: avoid-page;
page-break-inside: avoid;
}
.pdf-content ul,
.pdf-content ol,
@@ -693,8 +697,8 @@ function printStyles(cssSize, pageWidthMm, pageHeightMm, marginMm, darkMode) {
page-break-inside: auto;
}
.pdf-content li {
break-inside: avoid-page;
page-break-inside: avoid;
break-inside: auto;
page-break-inside: auto;
}
.pdf-content li:has(> ul),
.pdf-content li:has(> ol) {
@@ -736,31 +740,38 @@ function printStyles(cssSize, pageWidthMm, pageHeightMm, marginMm, darkMode) {
-webkit-box-decoration-break: clone;
}
.pdf-content .pdf-keep-unit {
.pdf-content .pdf-keep-unit,
.pdf-content .pdf-atomic-text,
.pdf-content .pdf-keep-short {
/*
* Chromium can leave a 1-2 px fragment of an avoided block on the
* previous page before moving the real block to the next page.
* Making the keep unit an atomic inline-block prevents that ghost
* fragment while preserving book-like pagination.
* Chromium can paint a thin fragment of a block at the bottom of the
* previous page before moving the actual block to the next page.
* Atomic inline-blocks prevent that paint fragment instead of relying
* only on break-inside: avoid, which triggers the Chromium artifact.
*/
display: inline-block;
width: 100%;
max-width: 100%;
vertical-align: top;
box-sizing: border-box;
break-inside: avoid-page;
page-break-inside: avoid;
}
.pdf-content dd.pdf-atomic-text {
width: calc(100% - 1.5em);
max-width: calc(100% - 1.5em);
}
.pdf-content .pdf-keep-unit > :first-child { margin-top: 0 !important; }
.pdf-content .pdf-keep-unit > :last-child { margin-bottom: 0 !important; }
.pdf-content .pdf-keep-unit.pdf-keep-unit--oversized {
.pdf-content .pdf-keep-unit.pdf-keep-unit--oversized,
.pdf-content .pdf-atomic-text.pdf-keep-unit--oversized,
.pdf-content .pdf-keep-short.pdf-keep-unit--oversized {
display: block;
width: auto;
max-width: none;
break-inside: auto;
page-break-inside: auto;
}
.pdf-content .pdf-keep-short {
break-inside: avoid-page !important;
page-break-inside: avoid !important;
}
.pdf-content .pdf-heading-lead > h1,
.pdf-content .pdf-heading-lead > h2,
.pdf-content .pdf-heading-lead > h3 { margin-bottom: .18em; }