The methods below are implemented for headings, but remain valid for figures. The only difference are the fields accessed. In the case of a figure, one may want to use the caption to generate the label text.
Approach 1: naive show rule (error)
#show heading: it => {
let key = lower(it.body.text.replace(" ", "-"))
[#it #label(key)]
}
= Long Heading with long title
See @long-heading-with-long-title.,
cf. Programmatically attaching labels to locatable elements #2926
As mentionned in the issue above, this simple approach results in an error: label <...> does not exist in the document
.
As of 2024-09-17T00:00:00Z, it is still the case.
Approach 2: invisible figure workaround
Starting from the final result, you may obtain the following, where content is addressable even though no label is explictly defined.
= Test
@test
== Test 2 ah $x y z$ #rect(fill: black) test
@test-2-ah-x-y-z--test
Output
Complete code
You may simply copy the entire code below for your purposes.
Code
#let heading-numbering = "1.1"
#set heading(numbering: heading-numbering)
#let to-string(content) = {
if content.has("text") and type(content.text) == "string" {
content.text
} else if content.has("children") {
content.children.map(to-string).join("")
} else if content.has("body") {
to-string(content.body)
} else if content == [ ] {
" "
}
}
#show heading: it => {
let key = lower(to-string(it).replace(" ", "-"))
return [
#it
#v(-1em)
#figure(
kind: "heading",
numbering: (..numbers) => numbering(heading-numbering, ..(counter(heading).get())),
supplement: "Section",
)[]
#label(key)
]
}
Technical details
This approach relies on:
show heading
rule adding an invisible, referenceable figure
- a
to-string
conversion function for the heading.body
numbering
In order to reference heading, you need to define the numbering scheme first. As an example, "1.1"
is enough.
#let heading-numbering = "1.1"
#set heading(numbering: heading-numbering)
to-string
The content to string function comes from GitHub issue #3876.
#let to-string(content) = {
if content.has("text") and type(content.text) == "string" {
content.text
} else if content.has("children") {
content.children.map(to-string).join("")
} else if content.has("body") {
to-string(content.body)
} else if content == [ ] {
" "
}
}
show heading
rule
In order:
- We generate the label key, replacing spaces by
-
- Return the heading content by juxtaposing
a. the original heading
b. negative vertical space to remove the linebreak
c. the figure used for labelling
Notice that the figure must folllow the heading-numbering
scheme and the counter(heading)
.
From this point, it is fairly simple to customize the generation rule for your own purposes, e.g., only generate level 1 heading labels, determine the heading supplement according to the level, etc.
#show heading: it => {
let key = lower(to-string(it).replace(" ", "-"))
return [
#it
#v(-1em)
#figure(
kind: "heading",
numbering: (..numbers) => numbering(heading-numbering, ..(counter(heading).get())),
supplement: "Section",
)[]
#label(key)
]
}