typst_library/model/
reference.rs

1use comemo::Track;
2use ecow::eco_format;
3
4use crate::diag::{bail, At, Hint, SourceResult};
5use crate::engine::Engine;
6use crate::foundations::{
7    cast, elem, Cast, Content, Context, Func, IntoValue, Label, NativeElement, Packed,
8    Show, Smart, StyleChain, Synthesize,
9};
10use crate::introspection::{Counter, CounterKey, Locatable};
11use crate::math::EquationElem;
12use crate::model::{
13    BibliographyElem, CiteElem, Destination, Figurable, FootnoteElem, Numbering,
14};
15use crate::text::TextElem;
16
17/// A reference to a label or bibliography.
18///
19/// Takes a label and cross-references it. There are two kind of references,
20/// determined by its [`form`]($ref.form): `{"normal"}` and `{"page"}`.
21///
22/// The default, a `{"normal"}` reference, produces a textual reference to a
23/// label. For example, a reference to a heading will yield an appropriate
24/// string such as "Section 1" for a reference to the first heading. The
25/// references are also links to the respective element. Reference syntax can
26/// also be used to [cite] from a bibliography.
27///
28/// As the default form requires a supplement and numbering, the label must be
29/// attached to a _referenceable element_. Referenceable elements include
30/// [headings]($heading), [figures]($figure), [equations]($math.equation), and
31/// [footnotes]($footnote). To create a custom referenceable element like a
32/// theorem, you can create a figure of a custom [`kind`]($figure.kind) and
33/// write a show rule for it. In the future, there might be a more direct way
34/// to define a custom referenceable element.
35///
36/// If you just want to link to a labelled element and not get an automatic
37/// textual reference, consider using the [`link`] function instead.
38///
39/// A `{"page"}` reference produces a page reference to a label, displaying the
40/// page number at its location. You can use the
41/// [page's supplement]($page.supplement) to modify the text before the page
42/// number. Unlike a `{"normal"}` reference, the label can be attached to any
43/// element.
44///
45/// # Example
46/// ```example
47/// #set page(numbering: "1")
48/// #set heading(numbering: "1.")
49/// #set math.equation(numbering: "(1)")
50///
51/// = Introduction <intro>
52/// Recent developments in
53/// typesetting software have
54/// rekindled hope in previously
55/// frustrated researchers. @distress
56/// As shown in @results (see
57/// #ref(<results>, form: "page")),
58/// we ...
59///
60/// = Results <results>
61/// We discuss our approach in
62/// comparison with others.
63///
64/// == Performance <perf>
65/// @slow demonstrates what slow
66/// software looks like.
67/// $ T(n) = O(2^n) $ <slow>
68///
69/// #bibliography("works.bib")
70/// ```
71///
72/// # Syntax
73/// This function also has dedicated syntax: A `{"normal"}` reference to a
74/// label can be created by typing an `@` followed by the name of the label
75/// (e.g. `[= Introduction <intro>]` can be referenced by typing `[@intro]`).
76///
77/// To customize the supplement, add content in square brackets after the
78/// reference: `[@intro[Chapter]]`.
79///
80/// # Customization
81/// If you write a show rule for references, you can access the referenced
82/// element through the `element` field of the reference. The `element` may
83/// be `{none}` even if it exists if Typst hasn't discovered it yet, so you
84/// always need to handle that case in your code.
85///
86/// ```example
87/// #set heading(numbering: "1.")
88/// #set math.equation(numbering: "(1)")
89///
90/// #show ref: it => {
91///   let eq = math.equation
92///   let el = it.element
93///   if el != none and el.func() == eq {
94///     // Override equation references.
95///     link(el.location(),numbering(
96///       el.numbering,
97///       ..counter(eq).at(el.location())
98///     ))
99///   } else {
100///     // Other references as usual.
101///     it
102///   }
103/// }
104///
105/// = Beginnings <beginning>
106/// In @beginning we prove @pythagoras.
107/// $ a^2 + b^2 = c^2 $ <pythagoras>
108/// ```
109#[elem(title = "Reference", Synthesize, Locatable, Show)]
110pub struct RefElem {
111    /// The target label that should be referenced.
112    ///
113    /// Can be a label that is defined in the document or, if the
114    /// [`form`]($ref.form) is set to `["normal"]`, an entry from the
115    /// [`bibliography`].
116    #[required]
117    pub target: Label,
118
119    /// A supplement for the reference.
120    ///
121    /// If the [`form`]($ref.form) is set to `{"normal"}`:
122    /// - For references to headings or figures, this is added before the
123    ///   referenced number.
124    /// - For citations, this can be used to add a page number.
125    ///
126    /// If the [`form`]($ref.form) is set to `{"page"}`, then this is added
127    /// before the page number of the label referenced.
128    ///
129    /// If a function is specified, it is passed the referenced element and
130    /// should return content.
131    ///
132    /// ```example
133    /// #set heading(numbering: "1.")
134    /// #show ref.where(
135    ///   form: "normal"
136    /// ): set ref(supplement: it => {
137    ///   if it.func() == heading {
138    ///     "Chapter"
139    ///   } else {
140    ///     "Thing"
141    ///   }
142    /// })
143    ///
144    /// = Introduction <intro>
145    /// In @intro, we see how to turn
146    /// Sections into Chapters. And
147    /// in @intro[Part], it is done
148    /// manually.
149    /// ```
150    #[borrowed]
151    pub supplement: Smart<Option<Supplement>>,
152
153    /// The kind of reference to produce.
154    ///
155    /// ```example
156    /// #set page(numbering: "1")
157    ///
158    /// Here <here> we are on
159    /// #ref(<here>, form: "page").
160    /// ```
161    #[default(RefForm::Normal)]
162    pub form: RefForm,
163
164    /// A synthesized citation.
165    #[synthesized]
166    pub citation: Option<Packed<CiteElem>>,
167
168    /// The referenced element.
169    #[synthesized]
170    pub element: Option<Content>,
171}
172
173impl Synthesize for Packed<RefElem> {
174    fn synthesize(
175        &mut self,
176        engine: &mut Engine,
177        styles: StyleChain,
178    ) -> SourceResult<()> {
179        let citation = to_citation(self, engine, styles)?;
180
181        let elem = self.as_mut();
182        elem.push_citation(Some(citation));
183        elem.push_element(None);
184
185        if !BibliographyElem::has(engine, elem.target) {
186            if let Ok(found) = engine.introspector.query_label(elem.target).cloned() {
187                elem.push_element(Some(found));
188                return Ok(());
189            }
190        }
191
192        Ok(())
193    }
194}
195
196impl Show for Packed<RefElem> {
197    #[typst_macros::time(name = "ref", span = self.span())]
198    fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
199        let elem = engine.introspector.query_label(self.target);
200        let span = self.span();
201
202        let form = self.form(styles);
203        if form == RefForm::Page {
204            let elem = elem.at(span)?;
205            let elem = elem.clone();
206
207            let loc = elem.location().unwrap();
208            let numbering = engine
209                .introspector
210                .page_numbering(loc)
211                .ok_or_else(|| eco_format!("cannot reference without page numbering"))
212                .hint(eco_format!(
213                    "you can enable page numbering with `#set page(numbering: \"1\")`"
214                ))
215                .at(span)?;
216            let supplement = engine.introspector.page_supplement(loc);
217
218            return show_reference(
219                self,
220                engine,
221                styles,
222                Counter::new(CounterKey::Page),
223                numbering.clone(),
224                supplement,
225                elem,
226            );
227        }
228        // RefForm::Normal
229
230        if BibliographyElem::has(engine, self.target) {
231            if elem.is_ok() {
232                bail!(span, "label occurs in the document and its bibliography");
233            }
234
235            return Ok(to_citation(self, engine, styles)?.pack().spanned(span));
236        }
237
238        let elem = elem.at(span)?;
239
240        if let Some(footnote) = elem.to_packed::<FootnoteElem>() {
241            return Ok(footnote.into_ref(self.target).pack().spanned(span));
242        }
243
244        let elem = elem.clone();
245        let refable = elem
246            .with::<dyn Refable>()
247            .ok_or_else(|| {
248                if elem.can::<dyn Figurable>() {
249                    eco_format!(
250                        "cannot reference {} directly, try putting it into a figure",
251                        elem.func().name()
252                    )
253                } else {
254                    eco_format!("cannot reference {}", elem.func().name())
255                }
256            })
257            .at(span)?;
258
259        let numbering = refable
260            .numbering()
261            .ok_or_else(|| {
262                eco_format!("cannot reference {} without numbering", elem.func().name())
263            })
264            .hint(eco_format!(
265                "you can enable {} numbering with `#set {}(numbering: \"1.\")`",
266                elem.func().name(),
267                if elem.func() == EquationElem::elem() {
268                    "math.equation"
269                } else {
270                    elem.func().name()
271                }
272            ))
273            .at(span)?;
274
275        show_reference(
276            self,
277            engine,
278            styles,
279            refable.counter(),
280            numbering.clone(),
281            refable.supplement(),
282            elem,
283        )
284    }
285}
286
287/// Show a reference.
288fn show_reference(
289    reference: &Packed<RefElem>,
290    engine: &mut Engine,
291    styles: StyleChain,
292    counter: Counter,
293    numbering: Numbering,
294    supplement: Content,
295    elem: Content,
296) -> SourceResult<Content> {
297    let loc = elem.location().unwrap();
298    let numbers = counter.display_at_loc(engine, loc, styles, &numbering.trimmed())?;
299
300    let supplement = match reference.supplement(styles).as_ref() {
301        Smart::Auto => supplement,
302        Smart::Custom(None) => Content::empty(),
303        Smart::Custom(Some(supplement)) => supplement.resolve(engine, styles, [elem])?,
304    };
305
306    let mut content = numbers;
307    if !supplement.is_empty() {
308        content = supplement + TextElem::packed("\u{a0}") + content;
309    }
310
311    Ok(content.linked(Destination::Location(loc)))
312}
313
314/// Turn a reference into a citation.
315fn to_citation(
316    reference: &Packed<RefElem>,
317    engine: &mut Engine,
318    styles: StyleChain,
319) -> SourceResult<Packed<CiteElem>> {
320    let mut elem = Packed::new(CiteElem::new(reference.target).with_supplement(
321        match reference.supplement(styles).clone() {
322            Smart::Custom(Some(Supplement::Content(content))) => Some(content),
323            _ => None,
324        },
325    ));
326
327    if let Some(loc) = reference.location() {
328        elem.set_location(loc);
329    }
330
331    elem.synthesize(engine, styles)?;
332
333    Ok(elem)
334}
335
336/// Additional content for a reference.
337#[derive(Debug, Clone, PartialEq, Hash)]
338pub enum Supplement {
339    Content(Content),
340    Func(Func),
341}
342
343impl Supplement {
344    /// Tries to resolve the supplement into its content.
345    pub fn resolve<T: IntoValue>(
346        &self,
347        engine: &mut Engine,
348        styles: StyleChain,
349        args: impl IntoIterator<Item = T>,
350    ) -> SourceResult<Content> {
351        Ok(match self {
352            Supplement::Content(content) => content.clone(),
353            Supplement::Func(func) => func
354                .call(engine, Context::new(None, Some(styles)).track(), args)?
355                .display(),
356        })
357    }
358}
359
360cast! {
361    Supplement,
362    self => match self {
363        Self::Content(v) => v.into_value(),
364        Self::Func(v) => v.into_value(),
365    },
366    v: Content => Self::Content(v),
367    v: Func => Self::Func(v),
368}
369
370/// The form of the reference.
371#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Cast)]
372pub enum RefForm {
373    /// Produces a textual reference to a label.
374    #[default]
375    Normal,
376    /// Produces a page reference to a label.
377    Page,
378}
379
380/// Marks an element as being able to be referenced. This is used to implement
381/// the `@ref` element.
382pub trait Refable {
383    /// The supplement, if not overridden by the reference.
384    fn supplement(&self) -> Content;
385
386    /// Returns the counter of this element.
387    fn counter(&self) -> Counter;
388
389    /// Returns the numbering of this element.
390    fn numbering(&self) -> Option<&Numbering>;
391}