Tuesday , April 6 2021

Paged.js: Paginating content (and making books) in browser, Hacker News

Paged.js is an open-source library to paginate content in the browser. Based on the W3C specifications, it’s a sort of polyfill forPaged MediaandGenerated Content for Paged MediaCSS modules. The development was launch as an open-source community driven initiative and it’s still experimental. The core team behind paged.js includes Adam Hyde, Julie Blanc, Fred Chasen & Julien Taquet.


Until we have formal accessible documentation for paged.js, here is a list of links for those who would like to start using paged.js:

You can also find below the features we are supporting right now. This text is an extract from theEditoria book.

Page rules

The page rules must be set up in the@ printmedia query.

@ mediaprint{      }


The size of the pages in a book can be defined by either width and height (in inches or millimeters) or a paper size such as A5 or Letter. It must be the same for all the pages in the book and will be inferred only from the root@ page.

@page{    size: A5;  }    #or  @page{    size:(mm)200 mm;  }


The margin command defines the top, bottom, left, and right areas around the page’s content.

@page{    Margin:1in  2in********************************************* (5in)2in;  }


Single pages or groups can be named, for instance as “cover” or “backmatter.” Named pages can have their own, more specific, styles and margins, and even different styles from the main rule.

@pagebackmatter{          Margin:(mm)30 mm;        background: yellow;  }

In HTML, these page groups are defined by adding the page name to a CSS selector.

section.backmatter {        page: backmatter;  }

Page selectors

Blank pages

The blank selector styles pages that have no content, e.g., pages automatically added to make sure a new chapter begins on the desired left or right page.

@ page: blank {        @ top-left {content: none; }  }

First page and nth page

There are selectors for styling the first page or a specific page, targeted by its number (namednin the specification).

@ page: first {        background: yellow;  }    @page: nth (5) {        Margin:2in;  }

Left and right or recto and verso

Typically, pages across a spread (a pair of pages) have symmetrical margins and are centered on the gutter. If, however, the inner margin needs to be larger or smaller, the selector to style left and right pages can make that change.

@page: left{        margin-right:2in;  }    @page: right{        margin-left:2in;  }

Margin boxes

The margins of a page are divided into sixteen named boxes, each with its own border, padding, and content area. They’re set within the@ pagequery. A box is named based on its position: for example,@ top-left,@ bottom-right-corner, or@ left-middle(see all rules). By default, the size is determined by the page area. Margin boxes are typically used to display running headers, running footers, page numbers, and other content more likely to be found in a book than on a website. The content of the box is governed by CSS properties.

To select these margin boxes and add content to them, use the following example:

@ page {    @ top-center {          content: "Moby-Dick";    }  }

Generated content

CSS counters

(css-counter) is a CSS property that lets you count elements within your content. For example, you might want to add a number before each figure caption. To do so, you would reset the counter for the, increment it any time a caption appears in the content, and display that number in a:: beforepseudo-element.

body{    counter-reset: figureNumber;  }  figcaption{    counter-increment: figureNumber;  }  figcaption:: before{    content:counter(figureNumber)  }

Page-based counters

To define page numbers, paged.js uses a CSS counter that gets incremented for each new page.

To insert a page number on a page or retrieve the total number of pages in a document, the W3C proposes a specific counter namedpage. The counters declaration must be used within acontentproperty in the margin-boxes declaration. The following example declares the page number in the bottom-left box:

@ page {    @ bottom-left {          content: counter (page);    }  }

You can also add a bit of text before the page number:

@ page {    @ bottom-left {          content:"page"counter (page);    }  }

To tally the total number of pages in your document, write this:

@ page {    @ bottom-left {          content: counter (pages);    }  }

Repeated elements on different pages

Named string

Named strings are used to create running headers and footers: they copy text for reuse in margin boxes.

First, the text content of the element is cloned into a named string using (string-setwith a custom identifier (in the code below we call it “title,” but you can name it whatever makes sense as a variable). In the following example, each time a new

appears in the HTML, the content of the named string gets updated with the text of that


h1 {string-set: title content (text)}

Next, thestring ()function copies the value of a named string to the document, via thecontentproperty.

@ page {    @ bottom-left {          content: string (title)    }  }

Running elements

Running elements are another way to create running headers and footers. Here the content, complete with style and structure, is copied from the text, assigned a custom identifier, and placed inside a margin box. This is useful for formatted text such as a word in italics.

The element’spositionis set:

. title {    position: running (title);  }

Then it is placed into a margin box with theelement ()value via thecontentproperty:

@ page {  @ top-center {  content: element (title)  }  }

Controlling text fragmentation with page breaks

Sometimes there is a need to define how content gets divided into pages based on markup. To do so, paged media specifications includebreak-before, (beak-inside, andbreak-afterproperties.

break-beforeadds a page break before the element;break-afteradds a page break after the element.

Here is the list of options:

  • break-before: pagepushes the element (and the following content) to the next available page
  • break-before: rightpushes the element to the next right page
  • break-before: leftpushes the element to the next left page
  • break-before: rectopushes the element to the next recto page
  • break-before: versopushes the element to the next verso page
  • break-before: avoidensures that no page break appears between two specified elements

For example, this sequence will create a page break before eachh1element:

H1{        break-before: page;  }

This code, in contrast, will push the (H1) to the next right page, creating a blank page if needed:

H1{      break-before: right;  }

This snippet will keep any HTML element that comes after anH1on the same page as theH1, moving them both to the next page if necessary.

H1{        break-after: avoid;  }

The last option is thebreak-insideproperty, which ensures that the element won’t be separated across multiple pages. If you want to be sure that your block quotes will never be divided, write this:

blockquote{      break-inside: avoid;  }


To build items such as an index or a table of contents, the export function has to find the pages on which the relevant elements appear inside the book. To do so, paged media specifications include a (target-counter) property.

For cross-references, links are used that target anchors in the book:

see theTitle of the chapter

Later in the book, the chapter title will appear with the anchor, set using anIDproperty.

title of the chapter

Thetarget-counterproperty is used in:: beforeand:: afterpseudo-elements and set into thecontentproperty. As a page counter, it can include some text:

a:: after{    content:", page"target-counter(attr (href), page);  }

In the PDF, this code will be rendered as “see title of the chapter, page 12 ”.

There are several ways to extend the rendering of Paged.js. Selecting the best method will depend on how the code will be called and what it needs to access.

When creating a script or library that is specifically aimed at extending the functionality of paged.js, it is best to use hooks and a handler class.

Paged.js has various points in the parsing of content, transforming of CSS, rendering, and layout of HTML that you can hook into and make changes to before further code is run.

A handler is a JavaScript class that defines functions that are called when a hook in Paged.js is ready to defer to your code. All of the core modules for support of paged media specifications and generated content are implemented as handlers. To create your own handler, you extend this same handler class.

classMyHandler(extends) ***********************  (Paged) ***********************.  Handler{      constructor(chunker, polisher, caller) {          super(chunker, polisher, caller);      }  }

The handler also exposes the underlying tools for fragmenting text (Chunker) and transforming CSS (Polisher) – see below.

Within this class, you can define methods for each of the hooks, and specify when they will be run in the code. Areturnthat is asynchronous will delay the next code usingawait.

classMyHandler(extends) ***********************  (Paged) ***********************.  Handler{      constructor(chunker, polisher, caller) {          super(chunker, polisher, caller);      }        afterPageLayout (pageFragment, page, breakToken) {          Console.log (pageFragment, page, breakToken);      }  }

Paged.js contains the following asynchronous hooks:


  • beforeParsed (contentruns on content before it is parsed and given IDs
  • afterParsed (parsed)runs after the content has been parsed but before rendering has started
  • beforePageLayout (page)runs when a new page has been created
  • afterPageLayout (pageElement, page, breakToken)runs after a single page has gone through layout, and allows adjusting the breakToken
  • afterRendered (pages)runs after all pages have finished rendering


  • beforeTreeParse (text, sheet)runs on the text of the style sheet
  • onUrl (urlNode)runs any time a CSS URL is parsed .
  • onAtPage (atPageNode)runs any time a CSS @page is parsed
  • onRule (ruleNode) runsany time a CSS rule is parsed
  • onDeclaration (declarationNode, ruleNode)runs any time a CSS declaration is parsed
  • onContent (contentNode, declarationNode, ruleNode)runs any time a CSS content declaration is parsed

Finally, the new handler needs to be registed in order to be used.

Paged.registerHandlers (MyHandler);

This can be registered anytime before the preview has started and will persist through any instances ofPaged.Previewerthat are created.

If a JavaScript library, such as MathJax, needs access to the content before it is paginated, you can delay pagination until that script has completed its work. This will give the library full access to the content of the book but has the disadvantage of needing to render the entire book before rendering each page, which can cause a significant delay.

Given that the polyfill will remove the page contents as soon as possible, adding awindow.PagedConfigwill allow you to pass aPromisethat will delay until it is resolved.

letpromise=newPromise((resolve, reject) {    someLongTask (resolve);  });  window.PagedConfig={    before:()=>{        returnpromise;    }  };

It is also possible to delay rendering of the polyfill until called by passingauto: false.

window.PagedConfig={      auto:false};  window.PagedPolyfill.preview ();

When thePreviewerclass is used directly, thepreview ()method can be called at any point that is appropriate.

Brave Browser
Read More

About admin

Leave a Reply

Your email address will not be published. Required fields are marked *