link

PrintCSS

Counter and Cross References

In this article, I want to explain how counters work and how to do cross-references in your document. Both counter and also cross-references were already a topic in the article about the table of contents.

There we used the page and pages counters, which are predefined in any of the PDF generation tools. To create the table of contents, we then used the ‘target-counter’ function on the headlines to figure out on which pages they are.

But let’s look at counters once again as you do not need to stick to the predefined ones.

#Counter

To use a counter you first need to initialize it by using the ‘counter-reset’ property. By default, the reset sets your counter to 0. But you can also set the counter to a specific number by passing it as a second parameter.

To display a counter in your document, you can use the counter function inside the content property.

div.content { 
    counter-reset: item; 
}

div.content > div { 
    counter-increment: item;
}

div.content > div:before { 
    content: counter(item) " - ";  
}

The HTML structure for the above CSS would be a div element with the class content, containing further div elements. The child div elements will get counted.

<div class="content">
    <h1>PrintCSS: Counter</h1>
    <div>first content div</div>
    <div>second content div</div>
    <div>third content div</div>
</div>
The result of the above HTML and CSS code
The result of the above HTML and CSS code

As I mentioned before, we can directly set the counter to a different number. For this, we need to adjust our CSS code.

div.content { 
    counter-reset: item 4; 
}

div.content > div { 
    counter-increment: item;
}

div.content > div:before { 
    content: counter(item) " - ";  
}

Now take a guess what number our ‘first content div’ would start with? The correct answer is 5. Why 5, you may ask, because we increment the counter by one for every div inside the content div.

Counter Reset with the value 4.
Counter Reset with the value 4.

As we saw until now, the counter increment is one by default, but also here; we can pass any other number. Let’s, for example, increment in steps of five and use the default reset of 0.

div.content { 
    counter-reset: item; 
}

div.content > div { 
    counter-increment: item 5;
}

div.content > div:before { 
    content: counter(item) " - ";
}

This will return us 5, 10, and 15 as the numbers in front of our content divs.

Counter Increment by steps of 5.
Counter Increment by steps of 5.

You can also pass a negative number to the counter increment to lower the current value. So let’s say we do the reset to 4 again and then increment by -1.

div.content { 
    counter-reset: item 4; 
}

div.content > div { 
    counter-increment: item -1;
}

div.content > div:before { 
    content: counter(item) " - ";  
}

This will give our content div’s the numbers 3, 2, and 1.

Counter Increment with a negative number
Counter Increment with a negative number

Also, the counter reset allows you to use negative numbers, and we can count up with the counter increment.

div.content { 
    counter-reset: item -1; 
}

div.content > div { 
    counter-increment: item;
}

div.content > div:before { 
    content: counter(item) " - ";  
}
Negative counter reset and counting up with the default increment
Negative counter reset and counting up with the default increment

#Nested Counter

You can nest counters to create something like 1.2.3 or 2.1. Let us have a look at how that is done. First, we will need to create a nested HTML structure.

<div class="content">
    <h1>PrintCSS: Nested Counter</h1>
    <ol>
        <li>item</li>
        <li>item
            <ol>
                <li>item</li>
                <li>item</li>
                <li>item
                    <ol>
                        <li>item</li>
                        <li>item</li>
                    </ol>
                </li>
                <li>item</li>
            </ol>
        </li>
        <li>item</li>
        <li>item</li>
    </ol>
    <ol>
        <li>item</li>
        <li>item</li>
    </ol>
</div>

To display our counter value now, we need to use the function counters instead of counter. This function requires a second parameter, which is the separator of the nested counters. The rest of the CSS is quite similar, only the selectors change.

ol {
    counter-reset: listnumber;
    list-style-type: none;
}

li {
    counter-increment: listnumber;
}

li::before {
    content: counters(listnumber, ".") " ";
}
A list with nested counters
A list with nested counters

Everything else works exactly like before, for example incrementing by 5 instead of 1.

ol {
    counter-reset: listnumber;
    list-style-type: none;
}

li {
    counter-increment: listnumber 5;
}

li::before {
    content: counters(listnumber, ".") " ";
}
Nested counter with increments of 5
Nested counter with increments of 5

#Cross References

Cross References are usually used to point to a chapter or page number. The most common example is the table of contents.

Looking back to our counter example with the content div’s. Let’s say we wanna reference to the second content div. For this to work, we first need to give the HTML element an ID.

The first counter example from this article
The first counter example from this article

Then we create a text anchor to this ID and make the marker IDs content bold and red.

<a href="#marker">The magic number is</a>
<div>first content div</div>
    <div id="marker">second content div</div>
    <div>third content div</div>
</div>
The new HTML and CSS
The new HTML and CSS

To get the magic number, or better said to cross-reference to this counter value, we need to use the target-counter function.

.content a::after {
    content: ": " target-counter(attr(href, url), item)
}

First, we say that we want the href attribute, and then we say which counter we want to get in our case item. And in this way, we will get our magic number 2!

Using target-counter to get the counter value on the second div
Using target-counter to get the counter value on the second div

Last let’s try the same on our nested counter sample from above. Of course, we need to change the counter name in the target-counter function to listnumber. We will put the marker ID on the item with the counter value 2.3.2.

Target-counter on the nested counters, we should get 2.3.2 but we get 2
Target-counter on the nested counters, we should get 2.3.2 but we get 2

As you see, we still get 2 as our magic number and not 2.3.2 cause we use target-counter. This only gives us the last counter, so as we want 2.3.2, the last number in this is 2. If we would have put the marker on the 2.3.1 element, we would have gotten 1 in return.

Here, we need to use the target-counters function and add another parameter at the end, the separator for the single counter values.

.content a::after {
    content: ": " target-counters(attr(href, url), listnumber, ".")
}
The correct result with target-counters
The correct result with target-counters

All the samples in this article are rendered with PDFreactor on the website printcss.live. The samples are working with Prince and Weasyprint too!

#Get the Code

View on GitHub

Get Result PDF

View on the PrintCSS Playground