summaryrefslogtreecommitdiff
path: root/crates/typst-library/src/model/cite.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-library/src/model/cite.rs')
-rw-r--r--crates/typst-library/src/model/cite.rs175
1 files changed, 175 insertions, 0 deletions
diff --git a/crates/typst-library/src/model/cite.rs b/crates/typst-library/src/model/cite.rs
new file mode 100644
index 00000000..ac0cfa79
--- /dev/null
+++ b/crates/typst-library/src/model/cite.rs
@@ -0,0 +1,175 @@
+use crate::diag::{error, At, HintedString, SourceResult};
+use crate::engine::Engine;
+use crate::foundations::{
+ cast, elem, Cast, Content, Label, Packed, Show, Smart, StyleChain, Synthesize,
+};
+use crate::introspection::Locatable;
+use crate::model::bibliography::Works;
+use crate::model::CslStyle;
+use crate::text::{Lang, Region, TextElem};
+
+/// Cite a work from the bibliography.
+///
+/// Before you starting citing, you need to add a [bibliography] somewhere in
+/// your document.
+///
+/// # Example
+/// ```example
+/// This was already noted by
+/// pirates long ago. @arrgh
+///
+/// Multiple sources say ...
+/// @arrgh @netwok.
+///
+/// You can also call `cite`
+/// explicitly. #cite(<arrgh>)
+///
+/// #bibliography("works.bib")
+/// ```
+///
+/// If your source name contains certain characters such as slashes, which are
+/// not recognized by the `<>` syntax, you can explicitly call `label` instead.
+///
+/// ```typ
+/// Computer Modern is an example of a modernist serif typeface.
+/// #cite(label("DBLP:books/lib/Knuth86a")).
+/// >>> #bibliography("works.bib")
+/// ```
+///
+/// # Syntax
+/// This function indirectly has dedicated syntax. [References]($ref) can be
+/// used to cite works from the bibliography. The label then corresponds to the
+/// citation key.
+#[elem(Synthesize)]
+pub struct CiteElem {
+ /// The citation key that identifies the entry in the bibliography that
+ /// shall be cited, as a label.
+ ///
+ /// ```example
+ /// // All the same
+ /// @netwok \
+ /// #cite(<netwok>) \
+ /// #cite(label("netwok"))
+ /// >>> #set text(0pt)
+ /// >>> #bibliography("works.bib", style: "apa")
+ /// ```
+ #[required]
+ pub key: Label,
+
+ /// A supplement for the citation such as page or chapter number.
+ ///
+ /// In reference syntax, the supplement can be added in square brackets:
+ ///
+ /// ```example
+ /// This has been proven. @distress[p.~7]
+ ///
+ /// #bibliography("works.bib")
+ /// ```
+ pub supplement: Option<Content>,
+
+ /// The kind of citation to produce. Different forms are useful in different
+ /// scenarios: A normal citation is useful as a source at the end of a
+ /// sentence, while a "prose" citation is more suitable for inclusion in the
+ /// flow of text.
+ ///
+ /// If set to `{none}`, the cited work is included in the bibliography, but
+ /// nothing will be displayed.
+ ///
+ /// ```example
+ /// #cite(<netwok>, form: "prose")
+ /// show the outsized effects of
+ /// pirate life on the human psyche.
+ /// >>> #set text(0pt)
+ /// >>> #bibliography("works.bib", style: "apa")
+ /// ```
+ #[default(Some(CitationForm::Normal))]
+ pub form: Option<CitationForm>,
+
+ /// The citation style.
+ ///
+ /// Should be either `{auto}`, one of the built-in styles (see below) or a
+ /// path to a [CSL file](https://citationstyles.org/). Some of the styles
+ /// listed below appear twice, once with their full name and once with a
+ /// short alias.
+ ///
+ /// When set to `{auto}`, automatically use the
+ /// [bibliography's style]($bibliography.style) for the citations.
+ #[parse(CslStyle::parse_smart(engine, args)?)]
+ pub style: Smart<CslStyle>,
+
+ /// The text language setting where the citation is.
+ #[internal]
+ #[synthesized]
+ pub lang: Lang,
+
+ /// The text region setting where the citation is.
+ #[internal]
+ #[synthesized]
+ pub region: Option<Region>,
+}
+
+impl Synthesize for Packed<CiteElem> {
+ fn synthesize(&mut self, _: &mut Engine, styles: StyleChain) -> SourceResult<()> {
+ let elem = self.as_mut();
+ elem.push_lang(TextElem::lang_in(styles));
+ elem.push_region(TextElem::region_in(styles));
+ Ok(())
+ }
+}
+
+cast! {
+ CiteElem,
+ v: Content => v.unpack::<Self>().map_err(|_| "expected citation")?,
+}
+
+/// The form of the citation.
+#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Cast)]
+pub enum CitationForm {
+ /// Display in the standard way for the active style.
+ #[default]
+ Normal,
+ /// Produces a citation that is suitable for inclusion in a sentence.
+ Prose,
+ /// Mimics a bibliography entry, with full information about the cited work.
+ Full,
+ /// Shows only the cited work's author(s).
+ Author,
+ /// Shows only the cited work's year.
+ Year,
+}
+
+/// A group of citations.
+///
+/// This is automatically created from adjacent citations during show rule
+/// application.
+#[elem(Locatable, Show)]
+pub struct CiteGroup {
+ /// The citations.
+ #[required]
+ pub children: Vec<Packed<CiteElem>>,
+}
+
+impl Show for Packed<CiteGroup> {
+ #[typst_macros::time(name = "cite", span = self.span())]
+ fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ let location = self.location().unwrap();
+ let span = self.span();
+ Works::generate(engine)
+ .at(span)?
+ .citations
+ .get(&location)
+ .cloned()
+ .ok_or_else(failed_to_format_citation)
+ .at(span)?
+ }
+}
+
+/// The error message when a citation wasn't found in the pre-formatted list.
+#[cold]
+fn failed_to_format_citation() -> HintedString {
+ error!(
+ "cannot format citation in isolation";
+ hint: "check whether this citation is measured \
+ without being inserted into the document"
+ )
+}