[More quick guides]

A quick guide to
creating Table of Contents
in Prince

A script brings something to the table

Prince is a HTML-to-PDF-via-CSS converter which is often for creating books from HTML and CSS. Most books have a Table of Contents (ToC) to help readers navigate the content. In the ToC, headings appear alongside page numbers. Page numbers, however, must be calculated by the formatter, and not a human author. This guide will show you how to automatically generate a Table of Contents in your book.

Starting point

We start with a simple HTML document, which has a title page and two short chapters:

htmlh1, h2 { break-before: page }

<h1>Waterfowl</h1>
<h2>Ducks</h2> ...
<h2>Geese</h2> ...
...

There is no ToC in the above document. Let's add one!

Adding a ToC manually

Adding a ToC manually is easy. First, you must create a list of ToC entries, and hyperlink these entries to their respective headlines. Second, you must style the hyperlink so that dots (called the leader) and page numbers are added in the formatting process.

html#toc a:after { content: leader('. ') target-counter(attr(href), page) }

<h2>Table of Contents</h2>
<ol id=toc>
<li><a href=#ducks>Ducks</a>
<li><a href=#geese>Geese</a>
</ol>

While this first example is simple, the CSS code is actually quite complex. In prose it says: After all a elements inside the ToC, there should be a lader made up of dots, and then the page number fetched from the target of the href attribute.

Manually adding HTML elements to the ToC is simple, but it has one major drawback: When you edit the document (e.g., by adding a new section) you must also edit the ToC manually. To avoid this manual labour, you can use a simple script. This is what we do next.

Adding a ToC automatically

Adding a ToC is easy – a script does most of the work. First, we add an empty element into which the ToC will be poured. A ToC is an ordered list, so we use the <ol> element for this:

<ol id=toc></ol>

Second, we must tell the script which elements should copied into the ToC. This is done by providing a CSS selector in a function call:

<script type="text/javascript" src="https://princexml.com/howcome/2021/guides/charms.js"></script>
<body onload="toc('h2')">

Third, we must add some lines of CSS to style the elements generated by the script:

#toc li { list-style-type: none }
#toc a:after { content: leader('. ') target-counter(attr(href), page) }

Adding these code snippets into our barebone example, we get:

htmlh1, h2 { break-before: page }
#toc li { list-style-type: none }
#toc a:after { content: leader('. ') target-counter(attr(href), page) }
...
<script type="text/javascript" src="https://princexml.com/howcome/2020/guides/charms.js">
<body onload="toc('h2')">
<h1>Waterfowl</h1>
<h2>Table of Contents</h2>
<ol id=toc></ol>
<h2>Ducks</h2> ...
<h2>Geese</h2> ...

The resulting ToC can be seen on page 2 above. Try clicking on the blue links in the PDF document!

Styling the ToC

The ToC can be styled. For example, we can change the order so that page numbers appear first:

htmlh1, h2 { break-before: page }
#toc li { list-style-type: none }
#toc a:before { content: target-counter(attr(href), page) leader('. ') }
...
<script type="text/javascript" src="https://princexml.com/howcome/2020/guides/charms.js">
<body onload="toc('h2')">
<h1>Waterfowl</h1>
<h2>Table of Contents</h2>
<ol id=toc></ol>
<h2>Ducks</h2> ...
<h2>Geese</h2> ...

The resulting ToC can be seen on page 2 above. Try clicking on the blue links in the PDF document!

Multi-level ToC

Textbooks and other complex documents often have multi-level chapter structures where CSS pseudo-elements are used to number chapters and sections. Here is an example which uses CSS counters to add numbering. Not all headlines should be numbered, though, therefore fancy :not selectors are used:

htmlh2:not(h2.toc) { 
  counter-increment: section; counter-reset: subsection 0 }
h2:not(h2.toc):before { 
  content: "Chapter " counter(section) ": " }

h3 { counter-increment: subsection }
h3::before { 
  content: counter(section) "." counter(subsection) ": " }
...
<script type="text/javascript" src="../charms.js">
<body onload="toc('h2, h3')">
<h2>Table of Contents</h2>
<ol id=toc></ol>

<h1>Waterfowl</h1>
  <h2>Ducks</h2> ...
    <h3>Feet and feathers</h3> ...
    <h3>Habitat</h3> ...
  <h2>Geese</h2> ...
    <h3>Neck length</h3> ...
    <h3>Instincts</h3> ...

Browser-friendly ToC

The previous examples use a script which is custom-made for Prince. A simplified version of the script produces clickable links that work both in browsers and in PDF documents. The only downside to using this solution is that CSS pseudo-elements (which were used for counting sections above) are not supported:

htmlh1, h2 { break-before: page }
#toc li { list-style-type: none }
#toc li.h3 { margin-left: 3em; font-style: italic }
#toc a:after { content: leader('. ') target-counter(attr(href), page) }
...
<script type="text/javascript" src="../charms.js">
<body onload="simpleToc('h2, h3')">

<h2>Table of Contents</h2>
<ol id=toc></ol>

<h1>Waterfowl</h1>
  <h2>Ducks</h2> ...
    <h3>Feet and feathers</h3> ...
    <h3>Habitat</h3> ...
  <h2>Geese</h2> ...
    <h3>Neck length</h3> ...
    <h3>Instincts</h3> ...

Multipass

Creating indexes in Prince 14 relies on the multipass feature, which formats the document several times. Between each run, a script can modify the document to improve layout. These scripts do amazing things in Prince, but will not work in browsers.

2021-01-06 HÃ¥kon Wium Lie