typst_library/model/
cite.rs

1use typst_syntax::Spanned;
2
3use crate::diag::{error, At, HintedString, SourceResult};
4use crate::engine::Engine;
5use crate::foundations::{
6    cast, elem, Cast, Content, Derived, Label, Packed, Show, Smart, StyleChain,
7    Synthesize,
8};
9use crate::introspection::Locatable;
10use crate::model::bibliography::Works;
11use crate::model::{CslSource, CslStyle};
12use crate::text::{Lang, Region, TextElem};
13
14/// Cite a work from the bibliography.
15///
16/// Before you starting citing, you need to add a [bibliography] somewhere in
17/// your document.
18///
19/// # Example
20/// ```example
21/// This was already noted by
22/// pirates long ago. @arrgh
23///
24/// Multiple sources say ...
25/// @arrgh @netwok.
26///
27/// You can also call `cite`
28/// explicitly. #cite(<arrgh>)
29///
30/// #bibliography("works.bib")
31/// ```
32///
33/// If your source name contains certain characters such as slashes, which are
34/// not recognized by the `<>` syntax, you can explicitly call `label` instead.
35///
36/// ```typ
37/// Computer Modern is an example of a modernist serif typeface.
38/// #cite(label("DBLP:books/lib/Knuth86a")).
39/// >>> #bibliography("works.bib")
40/// ```
41///
42/// # Syntax
43/// This function indirectly has dedicated syntax. [References]($ref) can be
44/// used to cite works from the bibliography. The label then corresponds to the
45/// citation key.
46#[elem(Synthesize)]
47pub struct CiteElem {
48    /// The citation key that identifies the entry in the bibliography that
49    /// shall be cited, as a label.
50    ///
51    /// ```example
52    /// // All the same
53    /// @netwok \
54    /// #cite(<netwok>) \
55    /// #cite(label("netwok"))
56    /// >>> #set text(0pt)
57    /// >>> #bibliography("works.bib", style: "apa")
58    /// ```
59    #[required]
60    pub key: Label,
61
62    /// A supplement for the citation such as page or chapter number.
63    ///
64    /// In reference syntax, the supplement can be added in square brackets:
65    ///
66    /// ```example
67    /// This has been proven. @distress[p.~7]
68    ///
69    /// #bibliography("works.bib")
70    /// ```
71    pub supplement: Option<Content>,
72
73    /// The kind of citation to produce. Different forms are useful in different
74    /// scenarios: A normal citation is useful as a source at the end of a
75    /// sentence, while a "prose" citation is more suitable for inclusion in the
76    /// flow of text.
77    ///
78    /// If set to `{none}`, the cited work is included in the bibliography, but
79    /// nothing will be displayed.
80    ///
81    /// ```example
82    /// #cite(<netwok>, form: "prose")
83    /// show the outsized effects of
84    /// pirate life on the human psyche.
85    /// >>> #set text(0pt)
86    /// >>> #bibliography("works.bib", style: "apa")
87    /// ```
88    #[default(Some(CitationForm::Normal))]
89    pub form: Option<CitationForm>,
90
91    /// The citation style.
92    ///
93    /// This can be:
94    /// - `{auto}` to automatically use the
95    ///   [bibliography's style]($bibliography.style) for citations.
96    /// - A string with the name of one of the built-in styles (see below). Some
97    ///   of the styles listed below appear twice, once with their full name and
98    ///   once with a short alias.
99    /// - A path string to a [CSL file](https://citationstyles.org/). For more
100    ///   details about paths, see the [Paths section]($syntax/#paths).
101    /// - Raw bytes from which a CSL style should be decoded.
102    #[parse(match args.named::<Spanned<Smart<CslSource>>>("style")? {
103        Some(Spanned { v: Smart::Custom(source), span }) => Some(Smart::Custom(
104            CslStyle::load(engine.world, Spanned::new(source, span))?
105        )),
106        Some(Spanned { v: Smart::Auto, .. }) => Some(Smart::Auto),
107        None => None,
108    })]
109    #[borrowed]
110    pub style: Smart<Derived<CslSource, CslStyle>>,
111
112    /// The text language setting where the citation is.
113    #[internal]
114    #[synthesized]
115    pub lang: Lang,
116
117    /// The text region setting where the citation is.
118    #[internal]
119    #[synthesized]
120    pub region: Option<Region>,
121}
122
123impl Synthesize for Packed<CiteElem> {
124    fn synthesize(&mut self, _: &mut Engine, styles: StyleChain) -> SourceResult<()> {
125        let elem = self.as_mut();
126        elem.push_lang(TextElem::lang_in(styles));
127        elem.push_region(TextElem::region_in(styles));
128        Ok(())
129    }
130}
131
132cast! {
133    CiteElem,
134    v: Content => v.unpack::<Self>().map_err(|_| "expected citation")?,
135}
136
137/// The form of the citation.
138#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Cast)]
139pub enum CitationForm {
140    /// Display in the standard way for the active style.
141    #[default]
142    Normal,
143    /// Produces a citation that is suitable for inclusion in a sentence.
144    Prose,
145    /// Mimics a bibliography entry, with full information about the cited work.
146    Full,
147    /// Shows only the cited work's author(s).
148    Author,
149    /// Shows only the cited work's year.
150    Year,
151}
152
153/// A group of citations.
154///
155/// This is automatically created from adjacent citations during show rule
156/// application.
157#[elem(Locatable, Show)]
158pub struct CiteGroup {
159    /// The citations.
160    #[required]
161    pub children: Vec<Packed<CiteElem>>,
162}
163
164impl Show for Packed<CiteGroup> {
165    #[typst_macros::time(name = "cite", span = self.span())]
166    fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
167        let location = self.location().unwrap();
168        let span = self.span();
169        Works::generate(engine)
170            .at(span)?
171            .citations
172            .get(&location)
173            .cloned()
174            .ok_or_else(failed_to_format_citation)
175            .at(span)?
176    }
177}
178
179/// The error message when a citation wasn't found in the pre-formatted list.
180#[cold]
181fn failed_to_format_citation() -> HintedString {
182    error!(
183        "cannot format citation in isolation";
184        hint: "check whether this citation is measured \
185               without being inserted into the document"
186    )
187}