Cross-references

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:

  1. Pass 1 — every cross-reference renders as ?. While the engine paginates, anchor positions, captured texts and counter snapshots are collected.
  2. Aux file — the collected {id → page, text, counters} map is written to <output>-aux.json alongside the per-document _aux table.
  3. 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 as upper-roman); values always render as decimal numbers.
  • Generated content (::before / ::after) renders on inline and block-level elements (a block’s ::before becomes its first inline content, ::after its last). On <li> the ::before content feeds the list marker instead, and table-structural elements (table, tr, tbody, …) do not render generated content — cells (td, th) and captions do.