typst_library/model/
cite.rs

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