[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

Rather than writing and maintaining a ToC manually, a script can do most 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 which elements that should copied into the ToC, and which element will hold the ToC. This is done by providing two CSS selectors in a function call:

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

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', '#toc')">
<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', '#toc')">
<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> ...

Several ToCs

Sometimes there is a need for several ToCs. We can add more ToCs by extending the list of arguments to the toc() function. Here is an example with a simple one-level ToC first, thereafter a multi-level ToC:

html<body onload="toc('h2', '#toc1', 'h2, h3', '#toc2')">
<ol id=toc1></ol>
<ol id=toc2></ol>

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', '#toc')">

<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> ...

Starting from a given page number

If your document will be part of a larger publication, you may need to start it on a given page number. This can be achieved with the counter-reset property, and the ToC will automatically pick up the new numbers:

htmlbody { counter-reset: page 86 }

Multipass

The automatically generated indexes presented in this guide reliy on the Prince 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.

Note that the script will modify the id attribute of elements that end up in the ToC. Therefore, you cannot use this attribute for other purposes.

2024-02-16 HÃ¥kon Wium Lie