fragmentainers
v0.0.7
Published
Javascript fragmentation engine
Readme
Fragmentainers
Javascript fragmentation engine to split content across bounded containers (fragmentainers). It implements the CSS Fragmentation model and CSS Paged Media breaks, inspired by browser layout engines internal block fragmentation architecture.
A part of Pagedjs, but this library can be used independently to fragment content across columns, regions, pages, or any bounded container.
NPM Module
npm install fragmentainersUsage
Fragmenter
import { Fragmenter } from "fragmentainers";
const flow = new Fragmenter(content, options);
for (const el of flow) {
/* ... */
}Content
DocumentFragment— fromtemplate.contentordocument.createDocumentFragment()Element— cloned into a new DocumentFragment
Options
styles—CSSStyleSheet[]applied viaadoptedStyleSheetsconstraintSpace—ConstraintSpacefor sizingresolver— APageResolverorRegionResolverinstancewidth/height— Resolves to a constraint space for fixed size fragmentationtype— Fragmentation type used withwidth/height(defaults to"column")devicePixelRatio— Target DPR for line-height roundingemulatePrintPixelRatio— Screen-only matching of print line-heightstyleSheet— Caller-owned sheet that receives the composite scoped rulescontinuation—{ fragmentainerIndex, blockOffset }resume point from another flow
Pagination with @page styles
import { Fragmenter } from "fragmentainers";
const sheet = new CSSStyleSheet();
sheet.replaceSync(`
@page { size: A4; margin: 2cm; }
`);
const template = document.createElement("template");
template.innerHTML = "<h1>Hello</h1><p>Content to paginate...</p>";
const flow = new Fragmenter(template.content, { styles: [sheet] });
for (const fragmentainer of flow) {
// Each element is a <fragment-container>
document.body.appendChild(fragmentainer);
}Preloading
Layout is synchronous. If fonts or images haven't loaded yet, measurements may be inaccurate. Use preload() to ensure resources are ready before iterating:
const flow = new Fragmenter(template.content, { styles: [sheet] });
await flow.preload();
for (const el of flow) {
document.body.appendChild(el);
}- Images — set
widthandheightattributes on<img>elements when possible. Images with explicit dimensions are automatically set toloading="lazy"and don't need preloading. Images that are unable to be loaded will be removed from the flow as their size can not be calculated. - Fonts — Ensure web fonts are loaded before layout.
preloadFonts()registers@font-facerules from the supplied content styles and waits for unloaded faces used by those styles. For best performance, use<link rel="preload" as="font">in your HTML. - Measurement — Top-level forced breaks and named-page changes divide long content into segments. One measurement element is rearranged as the break token advances, keeping only the active segment, persistent elements, and unfinished parallel-flow boxes live. Content without segment boundaries is measured as one segment.
The measurement element is released automatically when iteration or flow() completes, and when a for...of loop exits early. Call destroy() when the flow is no longer needed to release handlers, generated styles, preloaded fonts, and retained layout state.
Layout Handlers
Like pagedjs the engine supports handlers - self-contained extensions that hook into the layout and rendering pipeline. Much of the functionality outside of the core layout is implemented using these handlers.
.float-top {
--float-reference: page;
--float: top;
}import { LayoutHandler, Fragmenter } from "fragmentainers";
// Custom handlers extend the LayoutHandler base class
class MyHandler extends LayoutHandler {
claim(node) {
/* ... */
}
layout(rootNode, constraintSpace, breakToken, layoutChild) {
/* ... */
}
}
// Add it to the catalog once, at package load. Every flow constructed
// afterwards instantiates it.
Fragmenter.handlers.push(MyHandler);See Layout Handlers for the full handler interface and how handlers are added or overridden.
Emulate Print Pixel Ratio
Blink-based browsers round line-height: normal to the device pixel ratio — integer CSS pixels at DPR 1, half-pixels at DPR 2. This means the same content renders at different line heights screen vs print, causing fragmentation mismatches between layout and rendering.
The emulatePrintPixelRatio option attempts to normalize the two ratios to provide a more accurate preview of the print output. DPR 1 line-height values are emulated with a screen only the adopted stylesheet and will not be used in printing.
Documentation
- Architecture — How the engine works internally
- API Reference — Reference for all public exports
- Layout Algorithms — Reference for layout algorithms
- Layout Handlers — How to use and write layout handlers
- Browser Engine Reference — Mappings to Blink, Gecko, and WebKit equivalents
- Testing — Guide for writing tests and specs
Testing
npm test # unit/integration tests
npm run lint # eslint (separate from tests)Set FRAG_TEST_PORT to override the Playwright server port when running multiple checkouts concurrently.
Tests use Playwright running in a browser. Each test imports a shared browser fixture and evaluates code inside the browser via page.evaluate().
Specs (visual comparison tests)
npm run specsSpec tests are reftests based on the Web Platform Tests approach. Each test has a test HTML file and a reference HTML file — the runner screenshots both and compares them pixel-by-pixel.
Viewer
The fragment bin opens a spec in a headed browser with the engine live-injected, so you can refresh to re-run after edits:
fragment specs/at-page/awesome.html # interactive (Chromium)
fragment specs/at-page/awesome.html --inspect # per-page report to stdout
fragment specs/at-page/awesome.html --html out.html # dump fragmented HTML
fragment specs/at-page/awesome.html --pdf book.pdf # render to PDF
fragment --help # full flag list--inspect is the fastest way to see which page a break token landed on, which elements got split, and any zero-progress warnings — no screenshotting needed.
License
MIT
