Skip to main content

typst_library/model/
cite.rs

1use typst_syntax::Spanned;
2
3use crate::diag::SourceResult;
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[bibliography]
16/// somewhere in your document.
17///
18/// = Example <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/// ```
39///
40/// = Syntax <syntax>
41/// This function indirectly has dedicated syntax. @ref[References] can be used
42/// to cite works from the bibliography. The label then corresponds to the
43/// citation key.
44#[elem(Locatable, Synthesize)]
45pub struct CiteElem {
46    /// The citation key that identifies the entry in the bibliography that
47    /// shall be cited, as a label.
48    ///
49    /// ```example
50    /// // All the same
51    /// @netwok \
52    /// #cite(<netwok>) \
53    /// #cite(label("netwok"))
54    /// >>> #set text(0pt)
55    /// >>> #bibliography("works.bib", style: "apa")
56    /// ```
57    #[required]
58    pub key: Label,
59
60    /// A supplement for the citation such as page or chapter number.
61    ///
62    /// In reference syntax, the supplement can be added in square brackets:
63    ///
64    /// ```example
65    /// This has been proven. @distress[p.~7]
66    ///
67    /// #bibliography("works.bib")
68    /// ```
69    pub supplement: Option<Content>,
70
71    /// The kind of citation to produce. Different forms are useful in different
72    /// scenarios: A normal citation is useful as a source at the end of a
73    /// sentence, while a "prose" citation is more suitable for inclusion in the
74    /// flow of text.
75    ///
76    /// If set to `{none}`, the cited work is included in the bibliography, but
77    /// nothing will be displayed.
78    ///
79    /// ```example
80    /// #cite(<netwok>, form: "prose")
81    /// show the outsized effects of
82    /// pirate life on the human psyche.
83    /// >>> #set text(0pt)
84    /// >>> #bibliography("works.bib", style: "apa")
85    /// ```
86    #[default(Some(CitationForm::Normal))]
87    pub form: Option<CitationForm>,
88
89    /// The citation style.
90    ///
91    /// This can be:
92    /// - `{auto}` to automatically use the
93    ///   @bibliography.style[bibliography's style] for citations.
94    /// - A string with the name of one of the built-in styles (see below). Some
95    ///   of the styles listed below appear twice, once with their full name and
96    ///   once with a short alias.
97    /// - A path string or @path to a
98    ///   #link("https://citationstyles.org/")[CSL file].
99    /// - Raw bytes from which a CSL style should be decoded.
100    #[parse(match args.named::<Spanned<Smart<CslSource>>>("style")? {
101        Some(Spanned { v: Smart::Custom(source), span }) => Some(Smart::Custom(
102            CslStyle::load(engine, Spanned::new(source, span))?
103        )),
104        Some(Spanned { v: Smart::Auto, .. }) => Some(Smart::Auto),
105        None => None,
106    })]
107    pub style: Smart<Derived<CslSource, CslStyle>>,
108
109    /// The text language setting where the citation is.
110    #[internal]
111    #[synthesized]
112    pub lang: Lang,
113
114    /// The text region setting where the citation is.
115    #[internal]
116    #[synthesized]
117    pub region: Option<Region>,
118}
119
120impl Synthesize for Packed<CiteElem> {
121    fn synthesize(&mut self, _: &mut Engine, styles: StyleChain) -> SourceResult<()> {
122        let elem = self.as_mut();
123        elem.lang = Some(styles.get(TextElem::lang));
124        elem.region = Some(styles.get(TextElem::region));
125        Ok(())
126    }
127}
128
129cast! {
130    CiteElem,
131    v: Content => v.unpack::<Self>().map_err(|_| "expected citation")?,
132}
133
134/// The form of the citation.
135#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Cast)]
136pub enum CitationForm {
137    /// Display in the standard way for the active style.
138    #[default]
139    Normal,
140    /// Produces a citation that is suitable for inclusion in a sentence.
141    Prose,
142    /// Mimics a bibliography entry, with full information about the cited work.
143    Full,
144    /// Shows only the cited work's author(s).
145    Author,
146    /// Shows only the cited work's year.
147    Year,
148}
149
150/// A group of consecutive citations.
151///
152/// This element is automatically created from adjacent citations during
153/// realization. Citations are grouped without regard to which bibliography they
154/// end up being assigned to. They are only split into subgroups during
155/// bibliography assignment within the call tree of [`Works::generate`].
156///
157/// Each subgroup created there may consist of one or multiple citations that
158/// are processed as a union by hayagriva. If hayagriva were to support a single
159/// citation group for multiple bibliographies, the subgroup concept could be
160/// removed, but it's unclear whether that can be reasonably supported.
161///
162/// Another alternative would have been to already segment by assigned
163/// bibliography when grouping consecutive citations into [`CiteGroup`]s during
164/// realization. This would be quite clean, but unfortunately it would incur one
165/// additional document iteration, which is too high a price to pay for the
166/// conceptual cleanliness.
167///
168/// The citation group element is purposefully kept internal to retain
169/// flexibility in how it is collected.
170#[elem(Locatable)]
171pub struct CiteGroup {
172    /// Holds citations and potentially spaces in between them. The spaces are
173    /// retained so that they can be correctly rendered between subgroups
174    /// assigned to different bibliographies.
175    #[required]
176    pub children: Vec<Content>,
177}
178
179impl Packed<CiteGroup> {
180    pub fn realize(&self, engine: &mut Engine) -> SourceResult<Content> {
181        let loc = self.location().unwrap();
182        let span = self.span();
183        Works::generate(engine, span)?.citation(loc, span)
184    }
185}