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 {}