diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index c4c18b871..f0e42e68c 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -42,8 +42,10 @@ use self::fragment::*; use self::row::*; use self::spacing::*; use crate::layout::{HElem, ParElem, Spacing}; -use crate::meta::Refable; -use crate::meta::{Count, Counter, CounterUpdate, LocalName, Numbering}; +use crate::meta::Supplement; +use crate::meta::{ + Count, Counter, CounterUpdate, LocalName, Numbering, Outlinable, Refable, +}; use crate::prelude::*; use crate::text::{ families, variant, FontFamily, FontList, LinebreakElem, SpaceElem, TextElem, TextSize, @@ -140,7 +142,8 @@ pub fn module() -> Module { /// Display: Equation /// Category: math #[element( - Locatable, Synthesize, Show, Finalize, Layout, LayoutMath, Count, LocalName, Refable + Locatable, Synthesize, Show, Finalize, Layout, LayoutMath, Count, LocalName, Refable, + Outlinable )] pub struct EquationElem { /// Whether the equation is displayed as a separate block. @@ -160,15 +163,44 @@ pub struct EquationElem { /// ``` pub numbering: Option, + /// A supplement for the equation. + /// + /// For references to equations, this is added before the referenced number. + /// + /// If a function is specified, it is passed the referenced equation and + /// should return content. + /// + /// ```example + /// #set math.equation(numbering: "(1)", supplement: [Eq.]) + /// + /// We define: + /// $ phi.alt := (1 + sqrt(5)) / 2 $ + /// + /// With @ratio, we get: + /// $ F_n = floor(1 / sqrt(5) phi.alt^n) $ + /// ``` + pub supplement: Smart>, + /// The contents of the equation. #[required] pub body: Content, } impl Synthesize for EquationElem { - fn synthesize(&mut self, _vt: &mut Vt, styles: StyleChain) -> SourceResult<()> { + fn synthesize(&mut self, vt: &mut Vt, styles: StyleChain) -> SourceResult<()> { + // Resolve the supplement. + let supplement = match self.supplement(styles) { + Smart::Auto => TextElem::packed(self.local_name_in(styles)), + Smart::Custom(None) => Content::empty(), + Smart::Custom(Some(supplement)) => { + supplement.resolve(vt, [self.clone().into()])? + } + }; + self.push_block(self.block(styles)); self.push_numbering(self.numbering(styles)); + self.push_supplement(Smart::Custom(Some(Supplement::Content(supplement)))); + Ok(()) } } @@ -302,41 +334,45 @@ impl LocalName for EquationElem { } impl Refable for EquationElem { - fn reference( - &self, - vt: &mut Vt, - supplement: Option, - lang: Lang, - region: Option, - ) -> SourceResult { - // first we create the supplement of the heading - let mut supplement = - supplement.unwrap_or_else(|| TextElem::packed(self.local_name(lang, region))); + fn supplement(&self) -> Content { + // After synthesis, this should always be custom content. + match self.supplement(StyleChain::default()) { + Smart::Custom(Some(Supplement::Content(content))) => content, + _ => Content::empty(), + } + } - // we append a space if the supplement is not empty - if !supplement.is_empty() { - supplement += TextElem::packed('\u{a0}') - }; - - // we check for a numbering - let Some(numbering) = self.numbering(StyleChain::default()) else { - bail!(self.span(), "only numbered equations can be referenced"); - }; - - // we get the counter and display it - let numbers = Counter::of(Self::func()) - .at(vt, self.0.location().expect("missing location"))? - .display(vt, &numbering.trimmed())?; - - Ok(supplement + numbers) + fn counter(&self) -> Counter { + Counter::of(Self::func()) } fn numbering(&self) -> Option { self.numbering(StyleChain::default()) } +} - fn counter(&self) -> Counter { - Counter::of(Self::func()) +impl Outlinable for EquationElem { + fn outline(&self, vt: &mut Vt) -> SourceResult> { + let Some(numbering) = self.numbering(StyleChain::default()) else { + return Ok(None); + }; + + // After synthesis, this should always be custom content. + let mut supplement = match self.supplement(StyleChain::default()) { + Smart::Custom(Some(Supplement::Content(content))) => content, + _ => Content::empty(), + }; + + if !supplement.is_empty() { + supplement += TextElem::packed("\u{a0}"); + } + + let numbers = self + .counter() + .at(vt, self.0.location().unwrap())? + .display(vt, &numbering)?; + + Ok(Some(supplement + numbers)) } } diff --git a/library/src/meta/figure.rs b/library/src/meta/figure.rs index 945a812af..670878321 100644 --- a/library/src/meta/figure.rs +++ b/library/src/meta/figure.rs @@ -4,7 +4,7 @@ use super::{ Count, Counter, CounterKey, CounterUpdate, LocalName, Numbering, NumberingPattern, }; use crate::layout::{BlockElem, VElem}; -use crate::meta::{Refable, Supplement}; +use crate::meta::{Outlinable, Refable, Supplement}; use crate::prelude::*; use crate::text::TextElem; use crate::visualize::ImageElem; @@ -77,7 +77,7 @@ use crate::visualize::ImageElem; /// /// Display: Figure /// Category: meta -#[element(Locatable, Synthesize, Count, Show, Finalize, Refable)] +#[element(Locatable, Synthesize, Count, Show, Finalize, Refable, Outlinable)] pub struct FigureElem { /// The content of the figure. Often, an [image]($func/image). #[required] @@ -120,8 +120,9 @@ pub struct FigureElem { /// language]($func/text.lang). If you are using a custom figure type, you /// will need to manually specify the supplement. /// - /// This can also be set to a function that receives the figure's body to - /// select the supplement based on the figure's contents. + /// If a function is specified, it is passed the first descendant of the + /// specified `kind` (typically, the figure's body) and should return + /// content. /// /// ```example /// #figure( @@ -131,8 +132,7 @@ pub struct FigureElem { /// kind: "foo", /// ) /// ``` - #[default(Smart::Auto)] - pub supplement: Smart, + pub supplement: Smart>, /// How to number the figure. Accepts a /// [numbering pattern or function]($func/numbering). @@ -163,52 +163,54 @@ pub struct FigureElem { impl Synthesize for FigureElem { fn synthesize(&mut self, vt: &mut Vt, styles: StyleChain) -> SourceResult<()> { - // Determine the figure's kind. - let kind = match self.kind(styles) { - Smart::Auto => self - .find_figurable() - .map(|elem| FigureKind::Elem(elem.func())) - .unwrap_or_else(|| FigureKind::Elem(ImageElem::func())), - Smart::Custom(kind) => kind, - }; - - let content = match &kind { - FigureKind::Elem(func) => self.find_of_elem(*func), - FigureKind::Name(_) => None, - } - .unwrap_or_else(|| self.body()); - let numbering = self.numbering(styles); - // We get the supplement or `None`. The supplement must either be set - // manually or the content identification must have succeeded. - let supplement = match self.supplement(styles) { - Smart::Auto => match &kind { - FigureKind::Elem(func) => { - let elem = Content::new(*func).with::().map(|c| { - TextElem::packed(c.local_name( - TextElem::lang_in(styles), - TextElem::region_in(styles), - )) - }); + // Determine the figure's kind. + let kind = self.kind(styles).unwrap_or_else(|| { + self.body() + .query_first(Selector::can::()) + .cloned() + .map(|elem| FigureKind::Elem(elem.func())) + .unwrap_or_else(|| FigureKind::Elem(ImageElem::func())) + }); - if numbering.is_some() { - Some(elem - .ok_or("unable to determine the figure's `supplement`, please specify it manually") - .at(self.span())?) - } else { - elem + // Resolve the supplement. + let supplement = match self.supplement(styles) { + Smart::Auto => { + // Default to the local name for the kind, if available. + let name = match &kind { + FigureKind::Elem(func) => { + let empty = Content::new(*func); + empty.with::().map(|c| { + TextElem::packed(c.local_name( + TextElem::lang_in(styles), + TextElem::region_in(styles), + )) + }) } + FigureKind::Name(_) => None, + }; + + if numbering.is_some() && name.is_none() { + bail!(self.span(), "please specify the figure's supplement") } - FigureKind::Name(_) => { - if numbering.is_some() { - bail!(self.span(), "please specify the figure's supplement") - } else { - None + + name.unwrap_or_default() + } + Smart::Custom(None) => Content::empty(), + Smart::Custom(Some(supplement)) => { + // Resolve the supplement with the first descendant of the kind or + // just the body, if none was found. + let descendant = match kind { + FigureKind::Elem(func) => { + self.body().query_first(Selector::Elem(func, None)).cloned() } - } - }, - Smart::Custom(supp) => Some(supp.resolve(vt, [content.into()])?), + FigureKind::Name(_) => None, + }; + + let target = descendant.unwrap_or_else(|| self.body()); + supplement.resolve(vt, [target.into()])? + } }; // Construct the figure's counter. @@ -221,9 +223,7 @@ impl Synthesize for FigureElem { self.push_caption(self.caption(styles)); self.push_kind(Smart::Custom(kind)); - self.push_supplement(Smart::Custom(Supplement::Content( - supplement.unwrap_or_default(), - ))); + self.push_supplement(Smart::Custom(Some(Supplement::Content(supplement)))); self.push_numbering(numbering); self.push_outlined(self.outlined(styles)); self.push_counter(Some(counter)); @@ -235,16 +235,15 @@ impl Synthesize for FigureElem { impl Show for FigureElem { #[tracing::instrument(name = "FigureElem::show", skip_all)] fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult { - // We build the body of the figure. let mut realized = self.body(); - // We build the caption, if any. - if self.caption(styles).is_some() { + // Build the caption, if any. + if let Some(caption) = self.full_caption(vt)? { realized += VElem::weak(self.gap(styles).into()).pack(); - realized += self.show_caption(vt)?; + realized += caption; } - // We wrap the contents in a block. + // Wrap the contents in a block. Ok(BlockElem::new() .with_body(Some(realized)) .pack() @@ -270,100 +269,60 @@ impl Count for FigureElem { } impl Refable for FigureElem { - fn reference( - &self, - vt: &mut Vt, - supplement: Option, - _: Lang, - _: Option, - ) -> SourceResult { - // If the figure is not numbered, we cannot reference it. - // Otherwise we build the supplement and numbering scheme. - let Some(desc) = self.show_supplement_and_numbering(vt, supplement)? else { - bail!(self.span(), "cannot reference unnumbered figure") - }; - - Ok(desc) - } - - fn outline( - &self, - vt: &mut Vt, - _: Lang, - _: Option, - ) -> SourceResult> { - // If the figure is not outlined, it is not referenced. - if !self.outlined(StyleChain::default()) { - return Ok(None); + fn supplement(&self) -> Content { + // After synthesis, this should always be custom content. + match self.supplement(StyleChain::default()) { + Smart::Custom(Some(Supplement::Content(content))) => content, + _ => Content::empty(), } - - self.show_caption(vt).map(Some) - } - - fn numbering(&self) -> Option { - self.numbering(StyleChain::default()) } fn counter(&self) -> Counter { self.counter().unwrap_or_else(|| Counter::of(Self::func())) } + + fn numbering(&self) -> Option { + self.numbering(StyleChain::default()) + } +} + +impl Outlinable for FigureElem { + fn outline(&self, vt: &mut Vt) -> SourceResult> { + if !self.outlined(StyleChain::default()) { + return Ok(None); + } + + self.full_caption(vt) + } } impl FigureElem { - /// Determines the type of the figure by looking at the content, finding all - /// [`Figurable`] elements and sorting them by priority then returning the highest. - pub fn find_figurable(&self) -> Option { - self.body().query_first(Selector::can::()).cloned() - } - - /// Finds the element with the given function in the figure's content. - /// Returns `None` if no element with the given function is found. - pub fn find_of_elem(&self, func: ElemFunc) -> Option { - self.body().query_first(Selector::Elem(func, None)).cloned() - } - - /// Builds the supplement and numbering of the figure. Returns [`None`] if - /// there is no numbering. - pub fn show_supplement_and_numbering( - &self, - vt: &mut Vt, - external_supplement: Option, - ) -> SourceResult> { - if let (Some(numbering), Some(supplement), Some(counter)) = ( - self.numbering(StyleChain::default()), - self.supplement(StyleChain::default()) - .as_custom() - .and_then(|s| s.as_content()), - self.counter(), - ) { - let mut name = external_supplement.unwrap_or(supplement); - if !name.is_empty() { - name += TextElem::packed("\u{a0}"); - } - - let number = counter - .at(vt, self.0.location().unwrap())? - .display(vt, &numbering)? - .spanned(self.span()); - - Ok(Some(name + number)) - } else { - Ok(None) - } - } - - /// Builds the caption for the figure. If there is a numbering, will also - /// try to show the supplement and the numbering. - pub fn show_caption(&self, vt: &mut Vt) -> SourceResult { + /// Builds the full caption for the figure (with supplement and numbering). + pub fn full_caption(&self, vt: &mut Vt) -> SourceResult> { let Some(mut caption) = self.caption(StyleChain::default()) else { - return Ok(Content::empty()); + return Ok(None); }; - if let Some(sup_and_num) = self.show_supplement_and_numbering(vt, None)? { - caption = sup_and_num + TextElem::packed(": ") + caption; + if let ( + Smart::Custom(Some(Supplement::Content(mut supplement))), + Some(counter), + Some(numbering), + ) = ( + self.supplement(StyleChain::default()), + self.counter(), + self.numbering(StyleChain::default()), + ) { + let numbers = + counter.at(vt, self.0.location().unwrap())?.display(vt, &numbering)?; + + if !supplement.is_empty() { + supplement += TextElem::packed("\u{a0}"); + } + + caption = supplement + numbers + TextElem::packed(": ") + caption; } - Ok(caption) + Ok(Some(caption)) } } diff --git a/library/src/meta/heading.rs b/library/src/meta/heading.rs index a5fa74410..494807d5e 100644 --- a/library/src/meta/heading.rs +++ b/library/src/meta/heading.rs @@ -1,7 +1,7 @@ use typst::font::FontWeight; use typst::util::option_eq; -use super::{Counter, CounterUpdate, LocalName, Numbering, Refable}; +use super::{Counter, CounterUpdate, LocalName, Numbering, Outlinable, Refable}; use crate::layout::{BlockElem, HElem, VElem}; use crate::meta::{Count, Supplement}; use crate::prelude::*; @@ -42,7 +42,7 @@ use crate::text::{SpaceElem, TextElem, TextSize}; /// /// Display: Heading /// Category: meta -#[element(Locatable, Synthesize, Count, Show, Finalize, LocalName, Refable)] +#[element(Locatable, Synthesize, Count, Show, Finalize, LocalName, Refable, Outlinable)] pub struct HeadingElem { /// The logical nesting depth of the heading, starting from one. #[default(NonZeroUsize::ONE)] @@ -62,11 +62,13 @@ pub struct HeadingElem { /// A supplement for the heading. /// - /// For references to headings, this is added before the - /// referenced number. + /// For references to headings, this is added before the referenced number. + /// + /// If a function is specified, it is passed the referenced heading and + /// should return content. /// /// ```example - /// #set heading(numbering: "1.", supplement: "Chapter") + /// #set heading(numbering: "1.", supplement: [Chapter]) /// /// = Introduction /// In @intro, we see how to turn @@ -74,7 +76,6 @@ pub struct HeadingElem { /// in @intro[Part], it is done /// manually. /// ``` - #[default(Smart::Auto)] pub supplement: Smart>, /// Whether the heading should appear in the outline. @@ -98,12 +99,21 @@ pub struct HeadingElem { } impl Synthesize for HeadingElem { - fn synthesize(&mut self, _vt: &mut Vt, styles: StyleChain) -> SourceResult<()> { + fn synthesize(&mut self, vt: &mut Vt, styles: StyleChain) -> SourceResult<()> { + // Resolve the supplement. + let supplement = match self.supplement(styles) { + Smart::Auto => TextElem::packed(self.local_name_in(styles)), + Smart::Custom(None) => Content::empty(), + Smart::Custom(Some(supplement)) => { + supplement.resolve(vt, [self.clone().into()])? + } + }; + self.push_level(self.level(styles)); self.push_numbering(self.numbering(styles)); - self.push_supplement(self.supplement(styles)); + self.push_supplement(Smart::Custom(Some(Supplement::Content(supplement)))); self.push_outlined(self.outlined(styles)); - self.push_supplement(self.supplement(styles)); + Ok(()) } } @@ -160,77 +170,42 @@ cast_from_value! { } impl Refable for HeadingElem { - fn reference( - &self, - vt: &mut Vt, - supplement: Option, - lang: Lang, - region: Option, - ) -> SourceResult { - // Create the supplement of the heading. - let mut supplement = if let Some(supplement) = supplement { - supplement - } else { - match self.supplement(StyleChain::default()) { - Smart::Auto => TextElem::packed(self.local_name(lang, region)), - Smart::Custom(None) => Content::empty(), - Smart::Custom(Some(supplement)) => { - supplement.resolve(vt, std::iter::once(Value::from(self.clone())))? - } - } - }; - - // Append a non-breaking space if the supplement is not empty. - if !supplement.is_empty() { - supplement += TextElem::packed('\u{a0}') - }; - - // Check for a numbering. - let Some(numbering) = self.numbering(StyleChain::default()) else { - bail!(self.span(), "only numbered headings can be referenced"); - }; - - // Get the counter and display it. - let numbers = Counter::of(Self::func()) - .at(vt, self.0.location().unwrap())? - .display(vt, &numbering.trimmed())?; - - Ok(supplement + numbers) - } - - fn level(&self) -> usize { - self.level(StyleChain::default()).get() - } - - fn numbering(&self) -> Option { - self.numbering(StyleChain::default()) + fn supplement(&self) -> Content { + // After synthesis, this should always be custom content. + match self.supplement(StyleChain::default()) { + Smart::Custom(Some(Supplement::Content(content))) => content, + _ => Content::empty(), + } } fn counter(&self) -> Counter { Counter::of(Self::func()) } - fn outline( - &self, - vt: &mut Vt, - _: Lang, - _: Option, - ) -> SourceResult> { - // Check whether the heading is outlined. + fn numbering(&self) -> Option { + self.numbering(StyleChain::default()) + } +} + +impl Outlinable for HeadingElem { + fn outline(&self, vt: &mut Vt) -> SourceResult> { if !self.outlined(StyleChain::default()) { return Ok(None); } - // Build the numbering followed by the title. - let mut start = self.body(); + let mut content = self.body(); if let Some(numbering) = self.numbering(StyleChain::default()) { - let numbers = Counter::of(HeadingElem::func()) + let numbers = Counter::of(Self::func()) .at(vt, self.0.location().unwrap())? .display(vt, &numbering)?; - start = numbers + SpaceElem::new().pack() + start; + content = numbers + SpaceElem::new().pack() + content; }; - Ok(Some(start)) + Ok(Some(content)) + } + + fn level(&self) -> NonZeroUsize { + self.level(StyleChain::default()) } } diff --git a/library/src/meta/mod.rs b/library/src/meta/mod.rs index 0cbbafffb..724e5d20d 100644 --- a/library/src/meta/mod.rs +++ b/library/src/meta/mod.rs @@ -29,6 +29,7 @@ pub use self::reference::*; pub use self::state::*; use crate::prelude::*; +use crate::text::TextElem; /// Hook up all meta definitions. pub(super) fn define(global: &mut Scope) { @@ -55,4 +56,9 @@ pub(super) fn define(global: &mut Scope) { pub trait LocalName { /// Get the name in the given language and (optionally) region. fn local_name(&self, lang: Lang, region: Option) -> &'static str; + + /// Resolve the local name with a style chain. + fn local_name_in(&self, styles: StyleChain) -> &'static str { + self.local_name(TextElem::lang_in(styles), TextElem::region_in(styles)) + } } diff --git a/library/src/meta/outline.rs b/library/src/meta/outline.rs index 3114aa380..c309e7429 100644 --- a/library/src/meta/outline.rs +++ b/library/src/meta/outline.rs @@ -86,8 +86,11 @@ pub struct OutlineElem { /// caption: [Experiment results], /// ) /// ``` - #[default(Selector::Elem(HeadingElem::func(), Some(dict! { "outlined" => true })))] - pub target: Selector, + #[default(LocatableSelector(Selector::Elem( + HeadingElem::func(), + Some(dict! { "outlined" => true }) + )))] + pub target: LocatableSelector, /// The maximum level up to which elements are included in the outline. When /// this argument is `{none}`, all elements are included. @@ -157,23 +160,21 @@ impl Show for OutlineElem { } let indent = self.indent(styles); - let depth = self.depth(styles).map_or(usize::MAX, NonZeroUsize::get); - let lang = TextElem::lang_in(styles); - let region = TextElem::region_in(styles); + let depth = self.depth(styles).unwrap_or(NonZeroUsize::new(usize::MAX).unwrap()); let mut ancestors: Vec<&Content> = vec![]; - let elems = vt.introspector.query(&self.target(styles)); + let elems = vt.introspector.query(&self.target(styles).0); for elem in &elems { - let Some(refable) = elem.with::() else { - bail!(elem.span(), "outlined elements must be referenceable"); + let Some(outlinable) = elem.with::() else { + bail!(self.span(), "cannot outline {}", elem.func().name()); }; - if depth < refable.level() { + if depth < outlinable.level() { continue; } - let Some(outline) = refable.outline(vt, lang, region)? else { + let Some(outline) = outlinable.outline(vt)? else { continue; }; @@ -183,8 +184,8 @@ impl Show for OutlineElem { // This is only applicable for elements with a hierarchy/level. while ancestors .last() - .and_then(|ancestor| ancestor.with::()) - .map_or(false, |last| last.level() >= refable.level()) + .and_then(|ancestor| ancestor.with::()) + .map_or(false, |last| last.level() >= outlinable.level()) { ancestors.pop(); } @@ -193,10 +194,10 @@ impl Show for OutlineElem { if indent { let mut hidden = Content::empty(); for ancestor in &ancestors { - let ancestor_refable = ancestor.with::().unwrap(); + let ancestor_outlinable = ancestor.with::().unwrap(); - if let Some(numbering) = ancestor_refable.numbering() { - let numbers = ancestor_refable + if let Some(numbering) = ancestor_outlinable.numbering() { + let numbers = ancestor_outlinable .counter() .at(vt, ancestor.location().unwrap())? .display(vt, &numbering)?; @@ -285,3 +286,15 @@ impl LocalName for OutlineElem { } } } + +/// Marks an element as being able to be outlined. This is used to implement the +/// `#outline()` element. +pub trait Outlinable: Refable { + /// Produce an outline item for this element. + fn outline(&self, vt: &mut Vt) -> SourceResult>; + + /// Returns the nesting level of this element. + fn level(&self) -> NonZeroUsize { + NonZeroUsize::ONE + } +} diff --git a/library/src/meta/reference.rs b/library/src/meta/reference.rs index 328e6098a..c538b6965 100644 --- a/library/src/meta/reference.rs +++ b/library/src/meta/reference.rs @@ -95,6 +95,9 @@ pub struct RefElem { /// For references to headings or figures, this is added before the /// referenced number. For citations, this can be used to add a page number. /// + /// If a function is specified, it is passed the referenced element and + /// should return content. + /// /// ```example /// #set heading(numbering: "1.") /// #set ref(supplement: it => { @@ -149,43 +152,57 @@ impl Show for RefElem { let target = self.target(); let elem = vt.introspector.query_label(&self.target()); + let span = self.span(); if BibliographyElem::has(vt, &target.0) { if elem.is_ok() { - bail!(self.span(), "label occurs in the document and its bibliography"); + bail!(span, "label occurs in the document and its bibliography"); } - return Ok(self.to_citation(vt, styles)?.pack().spanned(self.span())); + return Ok(self.to_citation(vt, styles)?.pack().spanned(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 elem = elem.at(span)?; + let refable = elem + .with::() + .ok_or_else(|| { + if elem.can::() { + eco_format!( + "cannot reference {} directly, try putting it into a figure", + elem.func().name() + ) + } else { + eco_format!("cannot reference {}", elem.func().name()) + } + }) + .at(span)?; + + let numbering = refable + .numbering() + .ok_or_else(|| { + eco_format!("cannot reference {} without numbering", elem.func().name()) + }) + .at(span)?; + + let numbers = refable + .counter() + .at(vt, elem.location().unwrap())? + .display(vt, &numbering.trimmed())?; let supplement = match self.supplement(styles) { - Smart::Auto | Smart::Custom(None) => None, + Smart::Auto => refable.supplement(), + Smart::Custom(None) => Content::empty(), Smart::Custom(Some(supplement)) => { - Some(supplement.resolve(vt, [(*elem).clone().into()])?) + 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)?; + let mut content = numbers; + if !supplement.is_empty() { + content = supplement + TextElem::packed("\u{a0}") + content; + } - Ok(reference.linked(Destination::Location(elem.location().unwrap()))) + Ok(content.linked(Destination::Location(elem.location().unwrap()))) } } @@ -217,19 +234,10 @@ impl Supplement { 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, - } + Ok(match self { + Supplement::Content(content) => content.clone(), + Supplement::Func(func) => func.call_vt(vt, args)?.display(), + }) } } @@ -247,46 +255,14 @@ cast_to_value! { } /// Marks an element as being able to be referenced. This is used to implement -/// the `@ref` element. It is expected to build the [`Content`] that gets linked -/// by the [`RefElem`]. +/// the `@ref` element. 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; + /// The supplement, if not overriden by the reference. + fn supplement(&self) -> Content; /// Returns the counter of this element. fn counter(&self) -> Counter; + + /// Returns the numbering of this element. + fn numbering(&self) -> Option; } diff --git a/src/model/styles.rs b/src/model/styles.rs index 2f33ef929..7ebb766cc 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -463,6 +463,12 @@ impl Cast for LocatableSelector { } } +impl From for Value { + fn from(value: LocatableSelector) -> Self { + value.0.into() + } +} + /// A selector that can be used with show rules. /// /// Hopefully, this is made obsolete by a more powerful showing mechanism in the @@ -518,6 +524,12 @@ impl Cast for ShowableSelector { } } +impl From for Value { + fn from(value: ShowableSelector) -> Self { + value.0.into() + } +} + /// A show rule transformation that can be applied to a match. #[derive(Clone, PartialEq, Hash)] pub enum Transform { diff --git a/tests/ref/meta/ref.png b/tests/ref/meta/ref.png index c904fc998..d38969487 100644 Binary files a/tests/ref/meta/ref.png and b/tests/ref/meta/ref.png differ diff --git a/tests/typ/meta/ref.typ b/tests/typ/meta/ref.typ index 2bed2125b..a62e6ccec 100644 --- a/tests/typ/meta/ref.typ +++ b/tests/typ/meta/ref.typ @@ -21,102 +21,28 @@ As seen in @intro, we proceed. @foo --- +#set heading(numbering: "1.", supplement: [Chapter]) +#set math.equation(numbering: "(1)", supplement: [Eq.]) -#show ref: it => { - if it.element != none and it.element.func() == figure { - let element = it.element - "[" - element.supplement - "-" - str(element.counter.at(element.location()).at(0)) - "]" - // it - } else { - it - } -} - += Intro #figure( - image("/cylinder.svg", height: 3cm), - caption: [A sylinder.], + image("/cylinder.svg", height: 1cm), + caption: [A cylinder.], supplement: "Fig", ) #figure( - image("/tiger.jpg", height: 3cm), + image("/tiger.jpg", height: 1cm), caption: [A tiger.], - supplement: "Figg", + supplement: "Tig", ) -#figure( - $ A = 1 $, - kind: "equation", - supplement: "Equa", +$ A = 1 $ -) -@fig1 +#set math.equation(supplement: none) +$ A = 1 $ -@fig2 +@fig1, @fig2, @eq1, (@eq2) -@eq1 - ---- -#set heading(numbering: (..nums) => { - nums.pos().map(str).join(".") - }, supplement: [Chapt]) - -#show ref: it => { - if it.element != none and it.element.func() == heading { - let element = it.element - "[" - emph(element.supplement) - "-" - numbering(element.numbering, ..counter(heading).at(element.location())) - "]" - } else { - it - } -} - -= Introduction - -= Summary - -== Subsection - -@intro - -@sum - -@sub - ---- - -#show ref: it => { - if it.element != none { - if it.element.func() == text { - let element = it.element - "[" - element - "]" - } else if it.element.func() == underline { - let element = it.element - "{" - element - "}" - } else { - it - } - } else { - it - } -} - -@txt - -Ref something unreferable - -@under -#underline[ -Some underline text. -] +#set ref(supplement: none) +@fig1, @fig2, @eq1, @eq2