[csswg-drafts] [css-page] [css-gcpm] Page-based counters not quite working as expected (#3520)

faceless2 has just created a new issue for https://github.com/w3c/csswg-drafts:

== [css-page] [css-gcpm] Page-based counters not quite working as expected ==
We've just redone our counter implementation to match the current draft in css-page, and we've hit a few inconsistencies.

The language in question is [this bit](https://drafts.csswg.org/css-page-3/#page-based-counters) from css-page:

> A counter-increment within either a page or margin context causes the counter to increment with the generation of each page box.
> 
> If a counter is reset or incremented within the page context, it is in scope for all page-margin boxes and obscures all counters of the same name within the document.
> 
> If a counter is reset or incremented within a margin context, it is in scope for that page-margin box and obscures any counters of the same name in both the page context and the document.
> 
> If a counter that has not been reset or incremented within the margin context or the page context is used by counter() or counters() in the margin context, then the resultant value is exactly as if the page-margin box were an element within the document at the start of the page, inside the deepest element in the normal flow that spans the page break. Use of the counter in this way does not affect the calculation of the counter’s value.
> 

I have three issues with this.

### Scoping problem

_"If a counter is reset or incremented within the page context, it is in scope for all page-margin boxes and obscures all counters of the same name within the document."_

I don't believe this accurately conveys the intention from the original discussions, as (to me) it implies that the scope ends when the page-margin ends. The counter incremented in `@page { counter-increment: page 2 }` (from [example 20](https://drafts.csswg.org/css-page-3/#example-13051aa4)) is changing a different "page" counter to the the one used in the body of the document.

A qualifier was suggested when this was [first raised](https://lists.w3.org/Archives/Public/www-style/2009Apr/0368.html) but it seems to have eroded over time.

I think the intended meaning is if a counter is incremented **for the first time** - incrementing any counter without a previous reset will create a new counter. Should this instead read "_if a counter is created within the page context_"? Created is the term used in css-lists for when a counter is reset **or** set/incremented without a previous reset. With an implied rule of `:root { counter-reset: page 0 }` this solves the scope issue.

### 2. Unnecessary qualifier in last paragraph

The last paragraph starts by telling me it only applies _if the counter has not been reset or incremented in the page context_, but it is relevant if the counter is incremented too - how else would we know what value we're incrementing? It's really just telling us where in the document to take the initial counter values from.

To illustrate with an example, lets say I have the following HTML. I've used "section" as a counter to illustrate this could apply to any counter, not just the obvious choice of "page".
```html
<html>
 <style>
  @page {
   counter-increment: section 1;
   @top-center { content: counter(section); }
  }
  :root { counter-reset: section 5 }
  div::before { content: counter(section) }
  #div2 { break-before: page }
</style>
 <body>
  <div id="div1"></div>
  <div id="div2"></div>
</body>
</html>
``` 
so there is a page break between div1 and div2.

![output-001](https://user-images.githubusercontent.com/989243/51131545-f5e92800-1827-11e9-94bc-f962742d047a.png)

The page-content rules are effectively inserting some boxes into the tree at the position described above: _inside the deepest element in the normal flow that spans the page break_:

![output-002](https://user-images.githubusercontent.com/989243/51131978-06e66900-1829-11e9-87ef-359e334e082b.png)

and the counter values are as shown in the boxes: 5 on the first page, 6 on the second.

### 3. Counter-reset in body not acting as expected.

All of which leads me up to my final concern. If I modify the style to add `#div2 { counter-reset: section 10}`, then the expectation is that div2 will be on page 10, but it's not.

![output-003](https://user-images.githubusercontent.com/989243/51132497-45305800-182a-11e9-9bd3-cb077d33f132.png)

This expectation seems pretty widespread - some examples:
* https://github.com/web-platform-tests/wpt/blob/master/css/css-page/page-counters-000.xht
* http://css4.pub/2015/malthus/essay.html (Prince)
* https://www.oxygenxml.com/events/2015/DITA_OT_Day_2015/Slides/dita-css.pdf (Oxygen XML)
* https://github.com/Kozea/WeasyPrint/issues/93 - discussion.
and I'm pretty sure it's how AH works as well.

This was also suggested in @fantasai's [original text on this](https://lists.w3.org/Archives/Public/www-style/2009Apr/0227.html):
> Copy counters in scope at all break points, in document order, into the @page counter scope, obscuring any counters of the same name there. (In CSS3, this step is not required.)

Although this somewhat depends on whether "break points" is the point between elements, or includes the break-causing element. Either way, as it is now this step has disappeared, and "In CSS3 this step is not required" has me wondering if this was intentional.

I think this step is necessary. Why? Because otherwise, the process of resetting a page counter at the start of a chapter becomes very complex. 

```html
<style>
@page :nth(1 of chapter) {
    counter-reset: page 1;
    counter-increment: 0;
}
.chapter {
    break-before: page;
    page: chapter;
}
</style>
<section class="chapter">
```

You can't reset on page:first as suggested [here](https://www.tallcomponents.com/blog/css3-page-counter) as this [refers](https://lists.w3.org/Archives/Public/www-style/2013Oct/0567.html) to the first page in the document. The [:nth selector](https://drafts.csswg.org/css-gcpm/#document-sequence-selector) is very new and I don't think is supported by anyone, yet.

To sum up:
* Prince and AH both propagate a `counter-reset: page` on a break-causing element to the page margin.
* There is a long-held presumption that this is how it should work, and a clear requirement that it's possible.
* The alternative is complex and relies on a very new selector.

I've come to this very late and I'm sure there's a backstory to this I'm unaware of, and no doubt other implementations too. But to my mind this could be addressed by adding a paragraph:

> Copy all counters from any elements that have a forced break on the page, in document order, to the @page counter scope, obscuring any counters of the same name.

Limiting it to break-causing elements will cover the case where an element has `break-before: page; counter-reset: page 99` while removing a lot of the complexity over determining which element on the page has precedence. This approach is also the same one used in [css-gcpm](
https://drafts.csswg.org/css-gcpm/#document-sequence-selectors) to determine when to start a new page group.

Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/3520 using your GitHub account

Received on Tuesday, 15 January 2019 11:17:04 UTC