mirror of https://github.com/stelzo/typst.git
291 lines
8.7 KiB
Rust
291 lines
8.7 KiB
Rust
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 <intro>
|
|
/// Recent developments in
|
|
/// typesetting software have
|
|
/// rekindled hope in previously
|
|
/// frustrated researchers. @distress
|
|
/// As shown in @results, we ...
|
|
///
|
|
/// = Results <results>
|
|
/// We discuss our approach in
|
|
/// comparison with others.
|
|
///
|
|
/// == Performance <perf>
|
|
/// @slow demonstrates what slow
|
|
/// software looks like.
|
|
/// $ O(n) = 2^n $ <slow>
|
|
///
|
|
/// #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 <intro>]` 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 <intro>
|
|
/// In @intro, we see how to turn
|
|
/// Sections into Chapters. And
|
|
/// in @intro[Part], it is done
|
|
/// manually.
|
|
/// ```
|
|
pub supplement: Smart<Option<Supplement>>,
|
|
|
|
/// A synthesized citation.
|
|
#[synthesized]
|
|
pub citation: Option<CiteElem>,
|
|
|
|
/// 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 <intro>
|
|
/// = Summary <sum>
|
|
/// == Subsection <sub>
|
|
/// @intro
|
|
///
|
|
/// @sum
|
|
///
|
|
/// @sub
|
|
/// ```
|
|
#[synthesized]
|
|
pub element: Option<Content>,
|
|
}
|
|
|
|
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 {
|
|
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
|
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::<dyn Refable>() {
|
|
if elem.can::<dyn Figurable>() {
|
|
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 reference = elem
|
|
.with::<dyn Refable>()
|
|
.expect("element should be refable")
|
|
.reference(vt, supplement, lang)?;
|
|
|
|
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<CiteElem> {
|
|
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<Item = Value>,
|
|
) -> SourceResult<Content> {
|
|
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<Content> {
|
|
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.
|
|
/// - `styles` - The styles of the reference.
|
|
/// - `location` - The location where the reference is being created.
|
|
/// - `supplement` - The supplement of the reference.
|
|
fn reference(
|
|
&self,
|
|
vt: &mut Vt,
|
|
supplement: Option<Content>,
|
|
lang: Lang,
|
|
) -> SourceResult<Content>;
|
|
|
|
/// 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) -> SourceResult<Option<Content>> {
|
|
self.reference(vt, None, lang).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<Numbering>;
|
|
|
|
/// Returns the counter of this element.
|
|
fn counter(&self) -> Counter;
|
|
}
|