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. page resolves to the page the anchor sits on; any other counter resolves to its innermost value at the anchor’s position (snapshotted during layout). |
target-counters(url, counter, sep) |
The full nesting chain of the named counter at the anchor, joined with sep — the target-side analogue of counters() for hierarchical numbering like 2.1.1. |
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, captured texts and counter snapshots are collected. - Aux file — the collected
{id → page, text, counters}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, texts and counter values.
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 / SetAnchorCounters.
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.
Section numbers
Named counters resolve against a snapshot taken at the anchor’s
position, so “see section 2.2 on page 7” references work by combining
target-counter calls (or target-counters for counters that nest):
body { counter-reset: h1cnt h2cnt; }
h1 { counter-increment: h1cnt; counter-reset: h2cnt; }
h2 { counter-increment: h2cnt; }
a.secref::before {
content: "section " target-counter(attr(href), h1cnt)
"." target-counter(attr(href), h2cnt);
}
a.secref::after {
content: " on page " target-counter(attr(href), page);
}<p>See <a class="secref" href="#deep"></a>.</p>
...
<h2 id="deep">Details</h2>A counter-increment on the anchor element itself is included in the
snapshot: the h2 above carries counter-increment: h2cnt, and the
reference shows the incremented value, as CSS GCPM requires.
Current limitations
target-text(..., before|after|first-letter)(the pseudo-element selector form) returns?— only the element’s textual content is captured.target-counter()ignores the optional third argument (a counter style such asupper-roman); values always render as decimal numbers.- Generated content (
::before/::after) renders on inline and block-level elements (a block’s::beforebecomes its first inline content,::afterits last). On<li>the::beforecontent feeds the list marker instead, and table-structural elements (table,tr,tbody, …) do not render generated content — cells (td,th) and captions do.