--- title: "text-crossrefs: cross-references to arbitrary portions of text in Pandoc" author: "Bastien Dumont (`bastien.dumont [at] posteo.net`)" date: 2025/12/21 --- This filter extends Pandoc's cross-referencing abilities with references to any portion of text by its page number, its note number (when applicable) or an arbitrary reference type (with ConTeXt or LaTeX output). It currently supports the following output formats: * context * docx * latex * odt * opendocument * typst # Format-specific preliminary notices ## DOCX and ODT/Opendocument When opening for the first time a file produced by Pandoc with _text-crossrefs_, you should have to refresh the fields in order to get the correct values. In LibreOffice, press `F9`; in Word, a dialog box should appear when the file opens. ## ConTeXt specifically Your template should include the following directive before `\starttext` (as in the default template for ConTeXt): ``` $for(header-includes)$ $header-includes$ $endfor$ ``` ## All TeX-based formats All references are wrapped in a macro named `\crossrefenum`. It has two optional arguments: the first one is the reference type, the second indicates whether the prefix (e.g. “p. ”) should be printed or not (can be set to `withprefix`, `noprefix`, `yes` or `no`). The default values for these arguments should match those of `tcrf-default-reftype` and `tcrf-default-prefixref` (resp. `page` and `yes`, i.e. `withprefix`). The mandatory argument of `\crossrefenum` is a list of references (possibly ranges). Here are some valid invocations: * `\crossrefenum[note][withprefix]{lblone, lbltwo, lblthree}` * `\crossrefenum[page][noprefix]{lblone, lbltwo, lblthree}` * `\crossrefenum[noprefix]{lblone, lbltwo, lblthree}` (the first argument defaults to `page`) * `\crossrefenum{lblone, lbltwo, lblthree}` (the second argument defaults to `withprefix`) * `\crossrefenum{only-one}` * `\crossrefenum{lblone to lbltwo, lblthree}` (the first reference points to a range) > **For users from before December 2025**: the formatting > of the main argument of `\crossrefenum` [changed](#breaking-comma-crfnm). It is up to you to define `\crossrefenum` in your preamble. If your target format is LaTeX, it should be possible to define it as a wrapper for the `\zcref` macro provided by [the zref-clever package](https://ctan.org/pkg/zref-clever). Alternatively, you can use [my implementation](https://ctan.org/pkg/crossrefenum), which currently supports ConTeXt and LaTeX. Here are some hints about the implementation of the `\crossrefenum` macro: * [The `\crossrefenum` macro is supposed to output non only the numbers, but also the prefixes and delimiters (e.g. “p. ” and “–”)]{#prefixes-tex}; * In ConTeXt, there is no way to retrieve the note number from a `\reference` or a `\pagereference` contained in the note as one would from a `\label` in LaTeX. To work around this, footnotes are labelled automatically with `note:` followed by the first identifier attached to a span in the note. Contrary to the ConTeXt syntax, this label is placed _after_ the footnote content, so the `\footnote` macro has to be redefined. If your template includes the `header-includes` metadata variable like the default one does, this redefinition will happen automatically. Otherwise, you can copy-paste the following code in your preamble: ``` {=tex} \catcode`\@=11 \let\origfootnote\footnote \def\footnote#1#2{ \def\crfnm@secondArg{#2}% \ifx\crfnm@secondArg\crfnm@bracket \def\crfnm@todo{\crfnm@footnote@withlabel{#1}#2} % \else \def\crfnm@todo{\origfootnote{#1}#2}% \fi \crfnm@todo } \def\crfnm@bracket{[} \def\crfnm@footnote@withlabel#1[#2]{\origfootnote[#2]{#1}} \catcode`\@=13 ``` ## Typst {#typst} You can either get an equivalent of `\crossrefenum` in Typst, the implementation of which is to be provided in your document, or simply output consecutive `#ref` commands. The advantage of the first approach is that a `#crossrefenum` command could handle a list of references intelligently (e.g. printing “pp. 1–3 and 5” instead of “pp. 1, 2, 3, 5 and 5”; see [my implementation in TeX](https://ctan.org/pkg/crossrefenum) for more examples). The main drawback is that is has still to be implemented in Typst to date (14 December 2025), which is why `text-crossrefs` uses the second approach by default. The `#crossrefenum` command has the following parameters: > ``` > crossrefenum( > form: str, > prefixed: bool, > array dictionary label, > ) -> content > ``` > > **form**: `"normal"`, `"page"`, `"pagenote"`, or an [accepted type](#tex-options) > > **prefixed**: whether to print the prefix (e.g. “p. ”) > > **main argument**: a label, a range, or an array of labels and ranges; > ranges are dictionaries of the form `(beg: , end: )` > > To see examples, please convert `sample.md` to Typst using this filter. See the [Typst-specific options](#typst-options) to know how to switch to `#crossrefenum`. If you keep the defaults (outputting `#ref` commands), you may wish to set some [formatting options](#docx-odt-options) and to add a show rule for `#ref` to prevent it from typesetting prefixes (e.g. “p. ”) in addition to those that `text-crossrefs` already inserts. In the default mode, you can also (ab)use the `note` reference type to refer to a heading, figure or equation by its number, for the `note` reference type is mapped to the `normal` form in Typst, which is common to all these elements: for instance, `[my-lbl]{.tcrf reftype=note}` will be translated as `#ref(, form: "normal")`, which may be printed as “Section 2” if `my-lbl` is a label attached to a section heading. # Usage ## Basics Mark the span of text you want to refer to later with an identifier composed of alphanumeric characters, periods, colons, underscores or hyphens: ``` markdown Émile Gaboriau published [_L'Affaire Lerouge_ in 1866]{#publication}.[^1] [^1]: It is a very [fine piece of literature]{#my-evaluation}. [It was very popular.]{#reception} ``` To refer to it, write another span with class `tcrf` containing the target's identifier. If the span you are referring to is part of a footnote, you can refer to it either by page or note number according to the value of the `reftype` attribute (defaults to `page`). For instance, this: ``` markdown See [publication]{.tcrf} for the publication date. I gave my opinion in [my-evaluation]{.tcrf reftype=note}, [my-evaluation]{.tcrf}. ``` will be rendered in ConTeXt or LaTeX output as: ``` tex See \crossrefenum{{publication}} for the publication date. I expressed my thoughts about it in \crossrefenum[note]{my-evaluation}, \crossrefenum{my-evaluation}. ``` If you want to give a reference by note _and_ page number like in the example above, you can also use the following shorthand: ``` markdown [my-evaluation]{.tcrf reftype=pagenote} ``` In fact, you can use any identifier, including those automatically set by Pandoc. To suppress the prefixes (e.g. “p. ”), you can set the `prefixref` attribute to `no` (defaults to `yes`). It can be useful, for instance, for small manually formatted indexes[^comma-syntax]: ``` markdown Gaboriau: [publication, my-evaluation, reception]{.tcrf prefixref=no} ``` [^comma-syntax]: About the comma-delimited syntax used in this example, see [the section on enumerations below](#enums). ## Controlling where the label is placed By default, most of Pandoc's writers place the labels at the beginning of the span they identify (the Typst writer is an exception). You can control the placement via the attribute `refanchor`, which can be set to: * `beg`; * `end`; * `both`. When the value is `both`, _text-crossrefs_ creates two labels by suffixing `-beg` and `-end` to the identifier. A typical use case is: ``` [A portion of text that may cross a page break.]{#mylbl refanchor=both} See [mylbl-beg>mylbl-end]{.tcrf}. ``` ## Page ranges You can refer to a page range like this: ``` markdown If you want to know more about _L'Affaire Lerouge_, see [publication>reception]{.tcrf}. ``` The separator (here `>`) can be set to any string of characters other than alphanumeric, period, colon, underscore, hyphen and space (see the [customization options](#common-opts)). In LaTeX and ConTeXt output, the above-mentionned `\crossrefenum` macro should be defined so that the range is printed as a simple page reference if the page numbers are identical. The syntax of a range is[^customize-range-tex]: ``` tex \crossrefenum{{publication to reception}} ``` [^customize-range-tex]: Note that [it can be customized](#tex-options). In DOCX and ODT/Opendocument output and in the default Typst output, a range will be printed as a range even if the numbers are identical. ## Enumerations {#enums} You can enumerate several references as a comma-delimited list, for instance: ``` markdown [ref-one, ref-two>ref-three, ref-four]{.tcrf} ``` In DOCX and ODT/Opendocument output, all these references will be printed, potentially resulting in unnecessary repetitions. In TeX-based output formats, they will be wrapped in `\crossrefenum` like this and should be collapsed by the macro when it is desirable: ``` tex \crossrefenum{ref-one, ref-two to ref-three, ref-four} ``` # Customization Most of the customization variables expect a string. In this case, they must not contain Markdown markup and all breakable spaces must be replaced with the HTML entity ` `: otherwise, only the first word is taken into account and leading and trailing spaces are ignored. See `sample-with-options.md` for examples. ## Common options {#common-opts} The following metadata fields can be set: * `tcrf-references-enum-separator`: * the string between two references in an enumeration in a reference span; * defaults to a comma followed by a space. * `tcrf-references-range-separator`: * the string used to separate two references in a reference span; * defaults to `>`. * `tcrf-only-explicit-labels`: * set it to `true` if you want _crossrefenum_ to refer to spans with class `label` only; * defaults to `false`, can be set to `true` or `yes` (without quotes). * `tcrf-default-prefixref`: * default value for the `prefixref` attribute; * defaults to `yes`, can be set to `no` or `false` (without quotes). * `tcrf-default-reftype`: * default value for the `reftype` attribute; * defaults to `page`. ## Options specific to DOCX, ODT/Opendocument and (by default) Typst {#docx-odt-options} Here are some metadata fields for the `docx`, `odt` and `opendocument` formats. They are also used for `typst` unless [`tcrf-typst-crossrefenum` is set to `true`](#typst-options) (see [above](#prefixes-tex) why they are ignored in ConTeXt and LaTeX output): * `tcrf-page-prefix`: * “page” prefix; * defaults to `p. `. * `tcrf-pages-prefix`: * “pages” prefix; * defaults to `pp. `. * `tcrf-note-prefix`: * “note” prefix; * defaults to `n. `. * `tcrf-notes-prefix`: * “notes” prefix; * defaults to `nn. `. * `tcrf-pagenote-separator`: * the separator between the enumerated references in the output file when `reftype` is set to `pagenote`; * defaults to a comma followed by a space. * `tcrf-pagenote-at-end`: * the string printed at the end of a `pagenote` reference in the output file; * defaults to the empty string, but it can be used to achieve something like *n. 3 (p. 5)* (see the sample file). * `tcrf-pagenote-factorize-first-prefix-in-enum`: * defines if the prefixes of the type printed first in a reference to page and note should be repeated (like in “p. 6, n. 1 and p. 9, n. 3”) or set globally at the beginning of the enumeration (like in “pp. 6, n. 1 and 9, n. 3”); * defaults to `no`, can be set to `yes` or `true` (without quotes). * `tcrf-pagenote-first-type`: * the type of the reference number that is printed first in references to page and note; * defaults to `page`, can be set to `note`. * `tcrf-range-separator`: * the string separating the page numbers in a range; * defaults to `–`. * `tcrf-references-enum-separator`: * the string separating the elements of an enumeration in a reference span; * defaults to a comma followed by a space. * `tcrf-multiple-delimiter`: * the string between two elements (but the two last ones) in an enumeration; * defaults to a comma followed by a space. * `tcrf-multiple-before-last`: * the string inserted between the two last elements in an enumeration; * defaults to `and` surrounded with spaces. ## Options specific to the formats based on TeX {#tex-options} Since TeX is extensible, you may wish to support types other than `page`, `note` and `pagenote` for ConTeXt and LaTeX output. `tcrf-additional-types` can be provided with a list of supplementary accepted types, e.g.: ``` yaml tcrf-additional-types: - line - figure ``` Once declared, these types can be used as the value of `reftype` in the same way as `page`, `note` and `pagenote`. In addition, the following metadata field can be used to control the rendering of ranges of labels in `\crossrefenum`: * `tcrf-range-delim-crossrefenum`: * the delimiter between the labels of a range in the TeX output file; * defaults to `to` surrounded with spaces. ## Options specific to Typst {#typst-options} If the metadata field `tcrf-typst-crossrefenum` is set to `true` (defaults to `false`), `text-crossrefs` will output `#crossrefenum` commands modeled after the `\crossrefenum` macro for TeX-based formats (see [above](#typst)). The implementation of this command must be provided by the user. In this case, the metadata fields [specific to TeX](#tex-options) will be taken into account. Otherwise, `text-crossrefs` will only output native `#ref` commands. In this case, the metadata fields [specific to DOCX and ODT](#docx-odt-options) will be used. # Compatibility with other filters As _text-crossrefs_ and _pandoc-crossref_ do not manage the same kind of cross-references, they can perfectly be used together. _text-crossrefs_ must be run after all filters that may create, delete or move footnotes, such as _citeproc_. In a citation inside square brackets, the span bearing an identifier should not include a citation key, a locator or a `;` delimiter. When it follows immediatly the locator, you should protect the locator with curly brackets. For example, this should work: ``` markdown [@Jones1973, p. 5-70; @Doe2004[]{#jones-doe}] [@Jones1973, p. 5-70; [it was elaborated upon]{#further-elaboration} by @Doe2004] [@Jones1973, {p. 5-70}[]{#ref-to-jones}; @Doe2004] ``` not that: ``` markdown [[@Jones1973, p. 5-70]{#ref-to-jones}; @Doe2004] [[@Jones1973, p. 5-70; @Doe2004]{#jones-doe}] [@Jones1973, p. 5-70[]{#ref-to-jones}; @Doe2004] ``` # Breaking changes Until December 2025, `yes`, `no`, `true` and `false` as metadata values had to be put within double quotes. Now, they must not be quoted at all, which is a more standard way to handle boolean values in YAML. From December 2025, `text-crossrefs` formats [the main argument of `\crossrefenum`]{#breaking-comma-crfnm} as a comma-delimited list, not as a list of groups. This is in line the new, more readable formatting implemented in the version 1.2 of [the _crossrefenum_ TeX package](https://ctan.org/pkg/crossrefenum). # License Copyright 2024–2025 Bastien Dumont (bastien.dumont [at] posteo.net). The program and all related files, including the present documentation, are under the MIT License: see LICENSE for more details.