Cross-references
htmlbag resolves CSS Generated Content for Paged Media (GCPM) cross-reference functions. They let generated content pull values from a different element — typically the page number or the text of a referenced anchor — so you can build tables of contents, “see page N” references, and figure / chapter pointers without manual page tracking.
Functions
| Function | Returns |
|---|---|
target-counter(url, counter) |
The named counter’s value at the referenced anchor. The only counter currently supported is page (the page on which the anchor sits). |
target-counters(url, counter, sep) |
Joined counter stack (for hierarchical numbering). Reserved; resolves to ? until a counter snapshot is available per anchor. |
target-text(url) |
The textual content of the referenced anchor element (captured up to 200 characters). |
url is typically attr(href) so the function pulls the target from
the element’s own href attribute, but a literal url(#id) works too.
Anchors
Any element with an id attribute is registered as an anchor:
- Block-level anchors (headings, divs, list items, table rows, …) are picked up on the page they finally land on, after the line breaker finishes paginating.
- Inline anchors (
<span id>,<a id>,<em id>,<strong id>, …) are tracked alongside the enclosing paragraph and resolve to the page where their line of text settles.
Two-pass resolution
Resolution needs two render passes because page numbers depend on the final layout:
- Pass 1 — every cross-reference renders as
?. While the engine paginates, anchor positions and captured texts are collected. - Aux file — the collected
{id → page, text}map is written to<output>-aux.jsonalongside the per-document_auxtable. - Pass 2 — the aux file is read back; cross-references now resolve to real page numbers and texts.
The Markdown frontend handles passes automatically: it re-runs the
pipeline (up to --max-passes, default 3) until the aux file stops
changing. Library callers see this through the regular htmlbag
CSSBuilder API and feed the aux content back via
SetAnchorPages / SetAnchorTexts.
Tables of contents
A common shape is “a list of links to headings, each with a dotted leader filling the gap to a right-aligned page number”:
<ol class="toc">
<li><a href="#intro">Introduction</a></li>
<li><a href="#methods">Methods</a></li>
<li><a href="#results">Results</a></li>
</ol>.toc li {
width: 9cm;
}
.toc a::after {
content: leader(".") target-counter(attr(href), page);
}leader(".") becomes a stretchy glue that expands to fill the
available width, repeating the . pattern as needed. Stretch only
takes effect inside a container with a definite width — set width
(or rely on a parent’s fixed width) on the <li>. The example folder
toc-target-counter
shows the full setup.
Inline cross-references with captured text
target-text captures the rendered text of the referenced anchor and
inlines it. Combined with target-counter, this gives “see Chapter 3
on page 17” style references:
<p>See <a href="#methods"></a>.</p>
<h2 id="methods">Methodology</h2>a[href^="#"]::after {
content: " ‘" target-text(attr(href)) "’ on page "
target-counter(attr(href), page);
}The cross-reference-inline
example
demonstrates inline anchors (<span id>) plus target-text.
Current limitations
- Only the
pagecounter is resolvable.target-counter(..., section)or any other named counter returns?. target-counters(...)(the plural-stack form) returns?— there is no per-anchor counter snapshot yet.target-text(..., before|after|first-letter)(the pseudo-element selector form) returns?— only the element’s textual content is captured.- Block-level
::afteris not implemented; the renderer evaluates the inline branch only.::beforeworks on both inline and block.