Skip to content

Table of Contents

Press can automatically generate a table of contents by collecting elements marked as outline entries, then rendering them on a page marked as deferred.

Creating Outline Entries

Add the outline attribute to any element to register it in a named collection:

xml
<h1 outline="sections">Introduction</h1>

This adds the element to a collection called "sections". You can create multiple collections for different purposes.

Outline Levels

Use the level attribute for hierarchical outlines:

xml
<h1 outline="sections" level="1">Data Sources</h1>
<h2 outline="sections" level="2">Satellite Data</h2>
<h2 outline="sections" level="2">Ground Stations</h2>
<h1 outline="sections" level="1">Analysis</h1>

level and outline can also be set in <styles>:

xml
<styles>
  <h1>
    <level>1</level>
    <outline>custom-headings</outline>
  </h1>
</styles>

Default Headings Outline

When you use the default <h1> through <h6> tags, they are automatically added to a default collection called "headings" with appropriate levels. You don't need to add outline manually for standard headings in flows.

Deferred Pages

A table of contents needs to know the page number of every entry. Papermill achieves this through deferred pages and flows.

xml
<page deferred="true">
  <h1>Contents</h1>
  <!-- This content is rendered after all other pages -->
</page>

Tables of Contents Page

Here's a page definition called <toc> which renders a custom contents page:

xml
<pages>
  <toc deferred="true" flow="contents" repeat="true">
    <flow deferred="true" name="contents">
      <h1>Contents</h1>
      <table>
        <repeat outline="sections" item="entry">
          <tr>
            <td width="40pt">{{ entry.page-number }}</td>
            <td width="fill">{{ entry.text-content }}</td>
          </tr>
        </repeat>
      </table>
    </flow>
  </toc>
</pages>

Include this in <documents> at the place where you want the table of contents to render:

xml
<documents>
  <toc />
</documents>

Outline Entry Properties

When iterating over an outline collection, each entry has these properties:

PropertyDescription
page-numberThe page number where the entry appears
text-contentThe text content of the element
indexThe position in the collection (1-based)
levelThe hierarchy level (if set)

Building a Contents Page

Here's a complete example of a report with an auto-generated table of contents:

xml
<<press>
  <document format="A4" page-margin="2cm">
    <coverpage />
    <toc />
    <repeat flow="body">
      <body-page />
    </repeat>
  </document>

  <pages>
    <coverpage>
      <frame height="fill" v-align="center">
        <h1 font-size="36pt">{{ data.title }}</h1>
        <p font-size="14pt" font-color="#6b7280">{{ data.subtitle }}</p>
      </frame>
    </coverpage>

    <toc deferred="true" flow="contents" repeat="true">
      <flow deferred="true" name="contents">
        <frame font-style="bold" font-size="18pt" margin-bottom="20pt">
          Contents
        </frame>
        <table>
          <repeat outline="sections" item="entry">
            <show-if condition="entry.page-number">
              <tr>
                <td font-size="14pt">{{ entry.page-number }}</td>
                <td width="fill">{{ entry.text-content }}</td>
              </tr>
            </show-if>
          </repeat>
        </table>
      </flow>
    </toc>

    <body-page>
      <frame padding-bottom="40pt" height="fill">
        <flow name="body" />
      </frame>
      <frame text-align="center" font-size="9pt">
        Page <page-number />
      </frame>
    </body-page>
  </pages>

  <flows>
    <body type="markdown">
      <h1 outline="sections">Executive Summary</h1>

      The organisation achieved record growth in 2025, driven by
      expansion into three new markets and a 40% increase in
      recurring revenue.
      <frame-break />
      <h1 outline="sections">Financial Overview</h1>

      Total revenue reached £12.4M, a 28% increase year-on-year.
      Operating margins improved to 18%, up from 14% in the
      previous fiscal year.
      <frame-break />

      <h1 outline="sections">Strategic Outlook</h1>

      The board has approved investment in two new product lines
      for 2026, targeting the healthcare and education sectors.
    </body>
  </flows>

  <styles>
    <table>
      <child name="td">
        <border-weight>0</border-weight>
        <border-weight-bottom>0.5pt</border-weight-bottom>
        <border-color>#e5e7eb</border-color>
        <padding-top>6pt</padding-top>
        <padding-bottom>6pt</padding-bottom>
      </child>
    </table>
  </styles>

  <data type="json">
  {
    "title": "Annual Report 2025",
    "subtitle": "Meridian Holdings Group"
  }
  </data>
</press>

Auto-Numbered Headings

Use <counter-define> to create named counters that increment automatically:

xml
<components>
  <h1 outline="sections">
    <counter-define name="h1" />
    <counter-define name="h2" value="0" />
    <frame font-size="18pt" font-style="bold" space-after-desired="16pt">
      <counter-value name="h1" /> <slot />
    </frame>
  </h1>

  <h2 outline="sections" level="2">
    <counter-define name="h2" />
    <frame font-size="14pt" font-style="bold" space-after-desired="12pt">
      <counter-value name="h1" />.<counter-value name="h2" /> <slot />
    </frame>
  </h2>
</components>

This produces numbered headings like "1 Introduction", "1.1 Background", "2 Methodology", etc. The value="0" on the counter named "h2" in the "h1" definition resets the sub-counter each time a new h1 is encountered.