UNPKG

vfrag

Version:

Paginate a document by breaking one or more containers vertically into multiple fragments.

194 lines (161 loc) 3.9 kB
/* Base breaking styles */ /* The unbreakables */ h1, h2, h3, h4, h5, h6, summary, legend, caption, figcaption, figure, img, video, audio, iframe, embed, object, dt, dd { page-break-inside: avoid; } /* Breaking inside these should be possible, but only when really worth it */ pre, code, article, blockquote, aside, details, table { orphans: 3; widows: 4; } h1, h2, h3, h4, h5, h6, [role="heading"][aria-level], summary, legend, caption, figcaption:where(:first-child) { /* Avoid breaking after headings and heading-like elements */ page-break-after: avoid; } figcaption:where(:last-child) { page-break-before: avoid; } /* Utility classes to be used in post-production */ .force-break-before { page-break-before: always; } .force-break-after { page-break-after: always; } /* To be used when printing from the paginated view. We have already fragmented the content, so there should naturally be enough space, so this prevents issues where a tiny amount of overflow causes the UA to create a new page. */ @page paginated { margin-block: 0; } :where(.pagination-root) { page: paginated; --pages-total: calc(var(--pages, 1) + var(--pages-left, 0)); counter-reset: page var(--first-page, 1) pages var(--pages-total, 1); } .page, .vfrag-page { position: relative; counter-increment: page; counter-reset: page var(--page-number); & + & { page-break-before: always; } &:has(+ &) { page-break-after: always; } > .running-header, > .running-footer { position: absolute; line-height: 1; width: max-content; max-width: calc(100% - var(--running-margin, 2lh)); &.start { inset-inline-start: 0; } &.end { inset-inline-end: 0; } &:not(.start, .end) { inset-inline-start: 50%; transform: translateX(-50%); } &:where([data-page="1"] > *) { /* No running headers and footers on the very first page */ display: none; } a&, a { text-decoration: none; color: inherit; } } > .running-header { inset-block-start: var(--running-header-offset, 3lh); &:where([data-fragment="1"] > *) { /* No running header on a chapter first page */ display: none; } } > .running-footer { inset-block-end: var(--running-footer-offset, 1.2lh); } > .page-number { font-variant-numeric: diagonal-fractions; &::before { counter-reset: page var(--page-number); content: counter(page); font-size: 125%; font-weight: bold; letter-spacing: .1em; } &::after { content: "/" counter(pages); opacity: .6; font-weight: 300; } } &:is(.debug-pagination, .debug-pagination *) { @media print { outline: 1px solid red; } @media screen { /* Highlight areas that exceed page dimensions */ background: var(--page-aspect-ratio-image,) no-repeat hsl(0 50% 95%); &:where(.empty-space-m, .empty-space-l) { /* Highlight empty space */ &::after { content: var(--empty-lines-text) " empty lines"; display: block; height: calc(var(--empty-lines) * 1lh); flex: 1; box-sizing: border-box; padding: .5em; background: linear-gradient(to top, hsl(0 50% 50% / 50%) 1px, transparent 0) 0 0 / 100% 1lh, hsl(0 90% 50% / 10%); } } } } &:not(.debug-pagination, .debug-pagination *) { &[data-page="1"] { > .page-first { margin-block-start: auto; } } &.fragment-last { /* Last page */ > .page-last { margin-block-end: auto; } } &:not([data-page="1"], .fragment-last) { &:not(h1, h2, h3, h4, h5, h6, header):where(:has(+ :is(h1, h2))) { /* Distribute empty space between before headings and after last child */ margin-bottom: auto; } .footnote:nth-child(1 of .footnote) { margin-block: auto 0; } } } } /* Fragment styles */ .fragment:not([data-fragment="1"]) { &:is(li) { list-style: none; } &:is(details) { > summary { opacity: .5; } } }