Tuesday, March 22, 2011

CSS3 Page Counter

When CSS documents are rendered to paged media, such as PDF pages, one of the very first requests is to add a page number to each page. In CSS3 this is recognized and included in the specification by means of page counters. This blog covers our experience with page counters and how we implemented them in WebToPDF.

CSS counters

In CSS2, counters are specified at element level. Here is a CSS example of such a counter specification:

H1 { counter-increment: chapter }

This means that a counter called "chapter" is created which is incremented for each H1 element in the document.

You can then use this counter using a content function like this:

H1:after { content: counter(chapter) }

This rule tells the layout engine to add a small piece of text after each H1 element containing the current value of the "chapter" counter.

CSS page counters

As of CSS3 you can specify counters in the @page rule. Here is an example:

@page { counter-increment: page }

The above rule instructs the layout engine to create a counter called "page" (it is called page by convention, it can be anything). This counter is incremented for each page. As with any counter, you can then use the current value of the counter anywhere in the document

For example with this CSS rule:

#pageNumber { content: counter(page) }

and this piece of HTML:

<span id="pageNumber"></span>

You can use the current page number counter as content in the HTML document.

You can even go further. Say you want to start your page number at 10. You can then use the @page:first rule to reset the counter for the first page to value 9.

@page { counter-increment: page }
@page:first { counter-reset: page 9 }

The combination of both rules will reset the counter for the first page to 9. Then for each page (including the first) it will increment the counter. This results in a counter value of 10 for the first page, 11 for the second and so on.

Implementation

Implementing support for counters in the @page rule in WebToPDF was far from trivial. Because so far all counters were calculated in the document tree. This is a tree of nodes which is build directly from the source document. No layout information (including page info) is available at this stage. 

Page number information is only known in the render tree at layout time. At this stage, all nodes from the document tree are converted to boxes (the render tree) which are used in the layout process.

So we had to bridge the gap between the counter implementation found in the document tree and the page information available in the layout process.

We solved this by keeping a bit of counter state from the document tree in the render process. When content is created from a counter (e.g. in this element <span id="pageNumber">) we attach an update function to that content. This update function is called just before that piece of content is processed by the layout engine. The update function then re-evaluates the counter. If the re-evaluation detects that the counter value was modified in an @page rule, the piece of content is updated with the latest value of the counter. In all other cases, the content is left unmodified. The last issue we had to solve is to update the page counters each time a new page is created. When these counters are updated, they are flagged as being modified by an @page rule, which results in updated content whenever the counters are used.

Testing

The entire WebToPDF layout engine is tested using (among others) the W3C CSS 2.1 test suite. Since page counters are part of CSS3, they are not covered by this test suite, so we had to write our own tests: pagenumber-001.html; pagenumber-002.html; pagenumber-003.html; pagenumber-004.html and pagenumber-005.html.

When we wrote those tests, we were curious how major browsers would perform. To our big surprise, Firefox 4, IE 9, Chrome 10 and Opera 10 were not able to process these tests correctly. This is in line with earlier experiences where we found that most major browsers are very weak when it comes to supporting paged media.

Author: Ewout Prangsma

5 comments:

  1. I'll definitely adapt this in my site, I love using CSS3 for my web development. Thank you very much.

    ReplyDelete
  2. Is there any way, in a document that may be of varying numbers of pages due to dynamic content, to have something like:

    element:after
    {
    counter(page) " of " counter(pages);
    }

    Headers and footers can be used for the dynamic page numbering but the "pages" counter is non functional in that context (the counter-increment: page occurs in the main body of the document).

    I may be missing something simple . . .

    ReplyDelete
  3. i also need to know how to display the total amount of pages in a document, is there a way?

    ReplyDelete
  4. @page {
    counter-increment: page;

    @bottom-right {
    content: "Page " counter(page) " of " counter(pages);
    }
    }

    The above helps to find the total number of pages

    ReplyDelete
  5. The above code does not help me. can u provide a clear code? Do you mean like this.

    CssStyleSheet styleSheet = new CssStyleSheet();
    styleSheet.Text = @"@page { counter-increment: page;
    @bottom-right {
    content: ""Page"" counter(page) ""of"" counter(pages);
    }
    }";

    ReplyDelete