Takumi

Pagination

Control where pages break with CSS.

Content lays out once at unbounded height. It then splits into pages. Unsplittable atoms never straddle a cut. These atoms include text lines, images, and transformed subtrees. A cut inside an atom moves to its top. This matches browser print fragmentation.

Break properties

import {  } from "takumi-pdf";

const  = await (
  <>
    < ={{ : "page" }}>Chapter two</>
    < ={{ : "avoid" }}>Keep this together.</>
  </>,
);
PropertyEffect
break-before: pageStarts the element on a new page.
break-after: pageStarts the following content on a new page.
break-inside: avoidKeeps the element on one page when it fits.
box-decoration-break: cloneRepeats borders and backgrounds on every page fragment.

A break-inside: avoid box taller than the page window cannot fit on a page. It stops participating in cut avoidance. Browsers apply the same rule.

Widows and orphans

A cut through a paragraph keeps at least orphans lines at the bottom of the page and widows lines at the top of the next. Both default to 2, like Chromium. Set them to 1 to allow lone lines:

import {  } from "takumi-pdf";

const  = await (
  < ={{ : 3, : 3 }}>
    A long report body wraps into many lines across the page boundary.
  </>,
);

Both properties inherit. When a paragraph is too short to satisfy both minimums, the orphans win, as in Chromium. A break that cannot keep the orphans moves the whole paragraph to the next page. A minimum that cannot fit the current page at all is dropped for that page, like break-inside: avoid on an oversized box.

Split decorations

A box that crosses a page break slices its border and background by default. Set box-decoration-break: clone to give each fragment complete decorations:

import {  } from "takumi-pdf";

const  = await (
  <
    ={{
      : "1px solid #d1d5db",
      : 8,
      : "clone",
    }}
  >
    Long content that continues on the next page.
  </>,
);

Repeated table headers

A <thead> paints again at the top of every page its table continues onto, following css-tables-3 repeated headers. The band is monolithic: a page break never lands inside it, and the spacing to the first body row repeats with it.

import {  } from "takumi-pdf";

const  = await (
  <>
    <>
      <>
        <>Name</>
        <>Qty</>
      </>
    </>
    <>{}</>
  </>,
);

A header taller than a quarter of the page does not repeat, matching Chromium. A header cell whose rowspan reaches into the body suppresses repetition for that table. <tfoot> renders once, after the body.

Page ranges

pageRanges keeps only the listed pages, like a print dialog. Each entry is a 1-based page number or an inclusive span:

import {  } from "takumi-pdf";

const  = await (<>{}</>, {
  : [1, { : 4, : 8 }, { : 12 }],
});

An unset from starts at the first page. An unset to runs to the last. Ranges that keep no page reject the render.

Layout and page counters still run over the whole document. A kept page shows the numbers it would in full output, so page 4 of 12 still reads "Page 4 of 12". Links and outline entries pointing at a dropped page are dropped with it.

Watermarks

position: fixed paints a box on every page. The box lays out against the page area. It stays outside the content column.

import {  } from "takumi-pdf";

const  = await (
  <>
    {/* Centers the watermark inside each page area. */}
    <
      ={{
        : "fixed",
        : 0,
        : "flex",
        : "center",
        : "center",
      }}
    >
      < ={{ : 96, : "rgba(0,0,0,0.08)" }}>DRAFT</>
    </>
    <>Long content that continues on the next page.</>
  </>,
);

A transformed or filtered ancestor captures a fixed descendant. That descendant stays in flow and paginates with its ancestor.

A negative z-index paints the box under the content. A background on the root covers it, exactly as it does in a browser. Set the paper with backgroundColor instead, which paints under the box.

Last updated on

On this page