{
+ // Try to use bitcode or exception representations.
+ if let Ok(value) = PicoStr::try_constant(string) {
+ return Some(value);
+ }
+
+ // Try to find an existing entry that we can reuse.
+ let interner = INTERNER.read().unwrap();
+ interner.seen.get(string).copied()
+ }
+
/// Creates a compile-time constant `PicoStr`.
///
/// Should only be used in const contexts because it can panic.
diff --git a/tests/ref/html/link-html-frame-ref.html b/tests/ref/html/link-html-frame-ref.html
new file mode 100644
index 000000000..32f2cf4d4
--- /dev/null
+++ b/tests/ref/html/link-html-frame-ref.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+ 1 Introduction
+
+
diff --git a/tests/ref/html/link-html-frame.html b/tests/ref/html/link-html-frame.html
new file mode 100644
index 000000000..60cca836d
--- /dev/null
+++ b/tests/ref/html/link-html-frame.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+ Frame 1
+
+ Text
+ Go to teal square
+ Frame 2
+
+
+
diff --git a/tests/ref/html/link-html-here.html b/tests/ref/html/link-html-here.html
new file mode 100644
index 000000000..783c050bd
--- /dev/null
+++ b/tests/ref/html/link-html-here.html
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+ Go
+
+
diff --git a/tests/ref/html/link-html-id-attach.html b/tests/ref/html/link-html-id-attach.html
new file mode 100644
index 000000000..d1e7cbf9b
--- /dev/null
+++ b/tests/ref/html/link-html-id-attach.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+ Hi
+ Hi there
+ See it
+ See it here
+ See a b
+ See a b
+ See
+
+ See
+
+
+
diff --git a/tests/ref/html/link-html-id-existing.html b/tests/ref/html/link-html-id-existing.html
new file mode 100644
index 000000000..7686fc9ac
--- /dev/null
+++ b/tests/ref/html/link-html-id-existing.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+ This
+ Go
+
+
diff --git a/tests/ref/html/link-html-label-disambiguation.html b/tests/ref/html/link-html-label-disambiguation.html
new file mode 100644
index 000000000..41f1d33a9
--- /dev/null
+++ b/tests/ref/html/link-html-label-disambiguation.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+ A
+ B
+ C
+ D
+ E
+ F
+ G
+ H
+ I
+ J
+
+
+
diff --git a/tests/ref/html/link-html-nested-empty.html b/tests/ref/html/link-html-nested-empty.html
new file mode 100644
index 000000000..e197d54e1
--- /dev/null
+++ b/tests/ref/html/link-html-nested-empty.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+ Hi
+ A B C
+
+
diff --git a/tests/ref/html/ref-basic.html b/tests/ref/html/ref-basic.html
new file mode 100644
index 000000000..4e0e01b7d
--- /dev/null
+++ b/tests/ref/html/ref-basic.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+ 1. Introduction
+ See Section 1.1.
+ 1.1. Setup
+ As seen in Section 1, we proceed.
+
+
diff --git a/tests/suite/model/link.typ b/tests/suite/model/link.typ
index bd6c8a307..1394638f5 100644
--- a/tests/suite/model/link.typ
+++ b/tests/suite/model/link.typ
@@ -66,6 +66,117 @@ My cool #box(move(dx: 0.7cm, dy: 0.7cm, rotate(10deg, scale(200%, mylink))))
Text
#link()[Go to text.]
+--- link-html-id-attach html ---
+// Tests how IDs and, if necessary, spans, are added to the DOM to support
+// links.
+
+#for i in range(1, 9) {
+ list.item(link(label("t" + str(i)), [Go]))
+}
+
+// Text at start of paragraph
+Hi
+
+// Text at start of paragraph + more text
+Hi there
+
+// Text in the middle of paragraph
+See #[it ]
+
+// Text in the middle of paragraph + more text
+See #[it ] here
+
+// Text + more elements
+See #[a *b*]
+
+// Element
+See *a _b_*
+
+// Nothing
+See #[]
+
+// Nothing 2
+See #metadata(none)
+
+--- link-html-label-disambiguation html ---
+// Tests automatic ID generation for labelled elements.
+
+#[= A] #label("%") // not reusable => loc-1
+= B <1> // not reusable => loc-3 (loc-2 exists)
+= C // reusable, unique => loc
+= D // reusable, unique => loc-2
+= E // reusable, not unique => lib-1
+= F // reusable, not unique => lib-3 (lib-2 exists)
+= G // reusable, unique => lib-2
+= H // reusable, unique => hi
+= I // reusable, not unique => hi-2-1
+= J // reusable, not unique => hi-2-2
+
+#context for it in query(heading) {
+ list.item(link(it.location(), it.body))
+}
+
+--- link-html-id-existing html ---
+// Test that linking reuses the existing ID, if any.
+#html.div[
+ #html.span(id: "this")[This]
+]
+
+#link()[Go]
+
+--- link-html-here html ---
+#context link(here())[Go]
+
+--- link-html-nested-empty html ---
+#[#metadata(none) #metadata(none) Hi]
+
+#link()[A] // creates empty span
+#link()[B] // creates second empty span
+#link()[C] // links to #a because the generated span is contained in it
+
+--- link-html-frame html ---
+= Frame 1
+#html.frame(block(
+ stroke: 1pt,
+ width: 200pt,
+ height: 500pt,
+)[
+ #place(center, dy: 100pt, stack(
+ dir: ltr,
+ spacing: 10pt,
+ link(, square(size: 10pt, fill: teal)),
+ link(, square(size: 10pt, fill: black)),
+ link(, square(size: 10pt, fill: yellow)),
+ ))
+ #place(center, dy: 200pt)[
+ #square(size: 10pt, fill: teal)
+ ]
+])
+
+= Text
+#link()[Go to teal square]
+
+= Frame 2
+#html.frame(block(
+ stroke: 1pt,
+ width: 200pt,
+ height: 500pt,
+)[
+ #place(center, dy: 100pt)[
+ #square(size: 10pt, fill: yellow)
+ ]
+])
+
+--- link-html-frame-ref html ---
+// Test that reference links work in `html.frame`. Currently, references (and a
+// few other elements) do not internally use `LinkElem`s, so they trigger a
+// slightly different code path; see `typst-html/src/link.rs`. The text show
+// rule is only there to keep the output small.
+#set heading(numbering: "1")
+#show "Section" + sym.space.nobreak + "1": rect()
+#html.frame[@intro]
+= Introduction
+
--- link-to-label-missing ---
// Error: 2-20 label `` does not exist in the document
#link()[Nope.]
diff --git a/tests/suite/model/ref.typ b/tests/suite/model/ref.typ
index d48072edb..1e07a3e9b 100644
--- a/tests/suite/model/ref.typ
+++ b/tests/suite/model/ref.typ
@@ -1,6 +1,6 @@
// Test references.
---- ref-basic ---
+--- ref-basic render html ---
#set heading(numbering: "1.")
= Introduction