typst_library/foundations/label.rs
1use ecow::{EcoString, eco_format};
2use typst_utils::{PicoStr, ResolvedPicoStr};
3
4use crate::diag::StrResult;
5use crate::foundations::{Repr, Str, bail, func, scope, ty};
6
7/// A label for an element.
8///
9/// Inserting a label into content attaches it to the closest preceding element
10/// that is not a space. The preceding element must be in the same scope as the
11/// label, which means that `[Hello #[<label>]]`, for instance, wouldn't work.
12///
13/// A labelled element can be [referenced]($ref), [queried]($query) for, and
14/// [styled]($styling) through its label.
15///
16/// Once constructed, you can get the name of a label using
17/// [`str`]($str/#constructor).
18///
19/// # Example
20/// ```example
21/// #show <a>: set text(blue)
22/// #show label("b"): set text(red)
23///
24/// = Heading <a>
25/// *Strong* #label("b")
26/// ```
27///
28/// # Syntax
29/// This function also has dedicated syntax: You can create a label by enclosing
30/// its name in angle brackets. This works both in markup and code. A label's
31/// name can contain letters, numbers, `_`, `-`, `:`, and `.`. A label cannot
32/// be empty.
33///
34/// Note that there is a syntactical difference when using the dedicated syntax
35/// for this function. In the code below, the `[<a>]` terminates the heading and
36/// thus attaches to the heading itself, whereas the `[#label("b")]` is part of
37/// the heading and thus attaches to the heading's text.
38///
39/// ```typ
40/// // Equivalent to `#heading[Introduction] <a>`.
41/// = Introduction <a>
42///
43/// // Equivalent to `#heading[Conclusion #label("b")]`.
44/// = Conclusion #label("b")
45/// ```
46///
47/// Currently, labels can only be attached to elements in markup mode, not in
48/// code mode. This might change in the future.
49#[ty(scope, cast)]
50#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
51pub struct Label(PicoStr);
52
53impl Label {
54 /// Creates a label from an interned string.
55 ///
56 /// Returns `None` if the given string is empty.
57 pub fn new(name: PicoStr) -> Option<Self> {
58 const EMPTY: PicoStr = PicoStr::constant("");
59 (name != EMPTY).then_some(Self(name))
60 }
61
62 /// Resolves the label to a string.
63 pub fn resolve(self) -> ResolvedPicoStr {
64 self.0.resolve()
65 }
66
67 /// Turns this label into its inner interned string.
68 pub fn into_inner(self) -> PicoStr {
69 self.0
70 }
71}
72
73#[scope]
74impl Label {
75 /// Creates a label from a string.
76 #[func(constructor)]
77 pub fn construct(
78 /// The name of the label.
79 ///
80 /// Unlike the [dedicated syntax](#syntax), this constructor accepts
81 /// any non-empty string, including names with special characters.
82 name: Str,
83 ) -> StrResult<Label> {
84 if name.is_empty() {
85 bail!("label name must not be empty");
86 }
87
88 Ok(Self(PicoStr::intern(name.as_str())))
89 }
90}
91
92impl Repr for Label {
93 fn repr(&self) -> EcoString {
94 let resolved = self.resolve();
95 if typst_syntax::is_valid_label_literal_id(&resolved) {
96 eco_format!("<{resolved}>")
97 } else {
98 eco_format!("label({})", resolved.repr())
99 }
100 }
101}
102
103impl From<Label> for PicoStr {
104 fn from(value: Label) -> Self {
105 value.into_inner()
106 }
107}
108
109/// Indicates that an element cannot be labelled.
110pub trait Unlabellable {}