use super::{BibliographyElem, CiteElem, Counter, Figurable, Numbering}; use crate::prelude::*; use crate::text::TextElem; /// A reference to a label or bibliography. /// /// The reference function produces a textual reference to a label. For example, /// a reference to a heading will yield an appropriate string such as "Section /// 1" for a reference to the first heading. The references are also links to /// the respective element. Reference syntax can also be used to /// [cite]($func/cite) from a bibliography. /// /// Referenceable elements include [headings]($func/heading), /// [figures]($func/figure), and [equations]($func/equation). To create a custom /// referenceable element like a theorem, you can create a figure of a custom /// [`kind`]($func/figure.kind) and write a show rule for it. In the future, /// there might be a more direct way to define a custom referenceable element. /// /// If you just want to link to a labelled element and not get an automatic /// textual reference, consider using the [`link`]($func/link) function instead. /// /// # Example /// ```example /// #set heading(numbering: "1.") /// #set math.equation(numbering: "(1)") /// /// = Introduction /// Recent developments in /// typesetting software have /// rekindled hope in previously /// frustrated researchers. @distress /// As shown in @results, we ... /// /// = Results /// We discuss our approach in /// comparison with others. /// /// == Performance /// @slow demonstrates what slow /// software looks like. /// $ O(n) = 2^n $ /// /// #bibliography("works.bib") /// ``` /// /// ## Syntax /// This function also has dedicated syntax: A reference to a label can be /// created by typing an `@` followed by the name of the label (e.g. /// `[= Introduction ]` can be referenced by typing `[@intro]`). /// /// To customize the supplement, add content in square brackets after the /// reference: `[@intro[Chapter]]`. /// /// Display: Reference /// Category: meta #[element(Synthesize, Locatable, Show)] pub struct RefElem { /// The target label that should be referenced. #[required] pub target: Label, /// A supplement for the reference. /// /// For references to headings or figures, this is added before the /// referenced number. For citations, this can be used to add a page number. /// /// ```example /// #set heading(numbering: "1.") /// #set ref(supplement: it => { /// if it.func() == heading { /// "Chapter" /// } else { /// "Thing" /// } /// }) /// /// = Introduction /// In @intro, we see how to turn /// Sections into Chapters. And /// in @intro[Part], it is done /// manually. /// ``` pub supplement: Smart>, /// A synthesized citation. #[synthesized] pub citation: Option, /// Content of the element, it should be referable. /// /// ```example /// #set heading(numbering: (..nums) => { /// nums.pos().map(str).join(".") /// }, supplement: [Chapt]) /// /// #show ref: it => { /// if it.has("element") and it.element.func() == heading { /// let element = it.element /// "[" /// element.supplement /// "-" /// numbering(element.numbering, ..counter(heading).at(element.location())) /// "]" /// } else { /// it /// } /// } /// /// = Introduction /// = Summary /// == Subsection /// @intro /// /// @sum /// /// @sub /// ``` #[synthesized] pub element: Option, } impl Synthesize for RefElem { fn synthesize(&mut self, vt: &mut Vt, styles: StyleChain) -> SourceResult<()> { let citation = self.to_citation(vt, styles)?; self.push_citation(Some(citation)); if !vt.introspector.init() { self.push_element(None); return Ok(()); } // find the element content let target = self.target(); let elem = vt.introspector.query_label(&self.target()); // not in bibliography, but in document, then push the element if let (false, Ok(elem)) = (BibliographyElem::has(vt, &target.0), elem.at(self.span())) { self.push_element(Some(elem)); } else { self.push_element(None); } Ok(()) } } impl Show for RefElem { #[tracing::instrument(name = "RefElem::show", skip_all)] fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult { if !vt.introspector.init() { return Ok(Content::empty()); } let target = self.target(); let elem = vt.introspector.query_label(&self.target()); if BibliographyElem::has(vt, &target.0) { if elem.is_ok() { bail!(self.span(), "label occurs in the document and its bibliography"); } return Ok(self.to_citation(vt, styles)?.pack().spanned(self.span())); } let elem = elem.at(self.span())?; if !elem.can::() { if elem.can::() { bail!( self.span(), "cannot reference {} directly, try putting it into a figure", elem.func().name() ); } else { bail!(self.span(), "cannot reference {}", elem.func().name()); } } let supplement = match self.supplement(styles) { Smart::Auto | Smart::Custom(None) => None, Smart::Custom(Some(supplement)) => { Some(supplement.resolve(vt, [elem.clone().into()])?) } }; let lang = TextElem::lang_in(styles); let region = TextElem::region_in(styles); let reference = elem .with::() .expect("element should be refable") .reference(vt, supplement, lang, region)?; Ok(reference.linked(Destination::Location(elem.location().unwrap()))) } } impl RefElem { /// Turn the reference into a citation. pub fn to_citation(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult { let mut elem = CiteElem::new(vec![self.target().0]); elem.0.set_location(self.0.location().unwrap()); elem.synthesize(vt, styles)?; elem.push_supplement(match self.supplement(styles) { Smart::Custom(Some(Supplement::Content(content))) => Some(content), _ => None, }); Ok(elem) } } /// Additional content for a reference. pub enum Supplement { Content(Content), Func(Func), } impl Supplement { /// Tries to resolve the supplement into its content. pub fn resolve( &self, vt: &mut Vt, args: impl IntoIterator, ) -> SourceResult { match self { Supplement::Content(content) => Ok(content.clone()), Supplement::Func(func) => func.call_vt(vt, args).map(|v| v.display()), } } /// Tries to get the content of the supplement. /// Returns `None` if the supplement is a function. pub fn as_content(self) -> Option { match self { Supplement::Content(content) => Some(content), _ => None, } } } cast_from_value! { Supplement, v: Content => Self::Content(v), v: Func => Self::Func(v), } cast_to_value! { v: Supplement => match v { Supplement::Content(v) => v.into(), Supplement::Func(v) => v.into(), } } /// Marks an element as being able to be referenced. /// This is used to implement the `@ref` macro. /// It is expected to build the [`Content`] that gets linked /// by the [`RefElement`]. pub trait Refable { /// Tries to build a reference content for this element. /// /// # Arguments /// - `vt` - The virtual typesetter. /// - `supplement` - The supplement of the reference. /// - `lang`: The language of the reference. /// - `region`: The region of the reference. fn reference( &self, vt: &mut Vt, supplement: Option, lang: Lang, region: Option, ) -> SourceResult; /// Tries to build an outline element for this element. /// If this returns `None`, the outline will not include this element. /// By default this just calls [`Refable::reference`]. fn outline( &self, vt: &mut Vt, lang: Lang, region: Option, ) -> SourceResult> { self.reference(vt, None, lang, region).map(Some) } /// Returns the level of this element. /// This is used to determine the level of the outline. /// By default this returns `0`. fn level(&self) -> usize { 0 } /// Returns the numbering of this element. fn numbering(&self) -> Option; /// Returns the counter of this element. fn counter(&self) -> Counter; }