summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--library/src/math/mod.rs100
-rw-r--r--library/src/meta/figure.rs215
-rw-r--r--library/src/meta/heading.rs101
-rw-r--r--library/src/meta/mod.rs6
-rw-r--r--library/src/meta/outline.rs43
-rw-r--r--library/src/meta/reference.rs122
-rw-r--r--src/model/styles.rs12
-rw-r--r--tests/ref/meta/ref.pngbin137626 -> 39979 bytes
-rw-r--r--tests/typ/meta/ref.typ100
9 files changed, 301 insertions, 398 deletions
diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs
index c4c18b87..f0e42e68 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<Numbering>,
+ /// 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 $ <ratio>
+ ///
+ /// With @ratio, we get:
+ /// $ F_n = floor(1 / sqrt(5) phi.alt^n) $
+ /// ```
+ pub supplement: Smart<Option<Supplement>>,
+
/// 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<Content>,
- lang: Lang,
- region: Option<Region>,
- ) -> SourceResult<Content> {
- // first we create the supplement of the heading
- let mut supplement =
- supplement.unwrap_or_else(|| TextElem::packed(self.local_name(lang, region)));
-
- // 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())?;
+ 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(),
+ }
+ }
- Ok(supplement + numbers)
+ fn counter(&self) -> Counter {
+ Counter::of(Self::func())
}
fn numbering(&self) -> Option<Numbering> {
self.numbering(StyleChain::default())
}
+}
- fn counter(&self) -> Counter {
- Counter::of(Self::func())
+impl Outlinable for EquationElem {
+ fn outline(&self, vt: &mut Vt) -> SourceResult<Option<Content>> {
+ 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 945a812a..67087832 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<Supplement>,
+ pub supplement: Smart<Option<Supplement>>,
/// 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<()> {
+ let numbering = self.numbering(styles);
+
// Determine the figure's kind.
- let kind = match self.kind(styles) {
- Smart::Auto => self
- .find_figurable()
+ let kind = self.kind(styles).unwrap_or_else(|| {
+ self.body()
+ .query_first(Selector::can::<dyn Figurable>())
+ .cloned()
.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());
+ .unwrap_or_else(|| FigureKind::Elem(ImageElem::func()))
+ });
- 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.
+ // Resolve the supplement.
let supplement = match self.supplement(styles) {
- Smart::Auto => match &kind {
- FigureKind::Elem(func) => {
- let elem = Content::new(*func).with::<dyn LocalName>().map(|c| {
- TextElem::packed(c.local_name(
- TextElem::lang_in(styles),
- TextElem::region_in(styles),
- ))
- });
-
- if numbering.is_some() {
- Some(elem
- .ok_or("unable to determine the figure's `supplement`, please specify it manually")
- .at(self.span())?)
- } else {
- elem
+ 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::<dyn LocalName>().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<Content> {
- // 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<Content>,
- _: Lang,
- _: Option<Region>,
- ) -> SourceResult<Content> {
- // 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<Region>,
- ) -> SourceResult<Option<Content>> {
- // 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 counter(&self) -> Counter {
+ self.counter().unwrap_or_else(|| Counter::of(Self::func()))
}
fn numbering(&self) -> Option<Numbering> {
self.numbering(StyleChain::default())
}
+}
- fn counter(&self) -> Counter {
- self.counter().unwrap_or_else(|| Counter::of(Self::func()))
+impl Outlinable for FigureElem {
+ fn outline(&self, vt: &mut Vt) -> SourceResult<Option<Content>> {
+ 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<Content> {
- self.body().query_first(Selector::can::<dyn Figurable>()).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<Content> {
- self.body().query_first(Selector::Elem(func, None)).cloned()
- }
+ /// Builds the full caption for the figure (with supplement and numbering).
+ pub fn full_caption(&self, vt: &mut Vt) -> SourceResult<Option<Content>> {
+ let Some(mut caption) = self.caption(StyleChain::default()) else {
+ return Ok(None);
+ };
- /// 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<Content>,
- ) -> SourceResult<Option<Content>> {
- if let (Some(numbering), Some(supplement), Some(counter)) = (
- self.numbering(StyleChain::default()),
- self.supplement(StyleChain::default())
- .as_custom()
- .and_then(|s| s.as_content()),
+ if let (
+ Smart::Custom(Some(Supplement::Content(mut supplement))),
+ Some(counter),
+ Some(numbering),
+ ) = (
+ self.supplement(StyleChain::default()),
self.counter(),
+ self.numbering(StyleChain::default()),
) {
- 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());
+ let numbers =
+ counter.at(vt, self.0.location().unwrap())?.display(vt, &numbering)?;
- 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<Content> {
- let Some(mut caption) = self.caption(StyleChain::default()) else {
- return Ok(Content::empty());
- };
+ if !supplement.is_empty() {
+ supplement += TextElem::packed("\u{a0}");
+ }
- if let Some(sup_and_num) = self.show_supplement_and_numbering(vt, None)? {
- caption = sup_and_num + TextElem::packed(": ") + caption;
+ 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 a5fa7441..494807d5 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 <intro>
/// 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<Option<Supplement>>,
/// 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<Content>,
- lang: Lang,
- region: Option<Region>,
- ) -> SourceResult<Content> {
- // 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 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 level(&self) -> usize {
- self.level(StyleChain::default()).get()
+ fn counter(&self) -> Counter {
+ Counter::of(Self::func())
}
fn numbering(&self) -> Option<Numbering> {
self.numbering(StyleChain::default())
}
+}
- fn counter(&self) -> Counter {
- Counter::of(Self::func())
- }
-
- fn outline(
- &self,
- vt: &mut Vt,
- _: Lang,
- _: Option<Region>,
- ) -> SourceResult<Option<Content>> {
- // Check whether the heading is outlined.
+impl Outlinable for HeadingElem {
+ fn outline(&self, vt: &mut Vt) -> SourceResult<Option<Content>> {
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 0cbbafff..724e5d20 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<Region>) -> &'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 3114aa38..c309e742 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::<dyn Refable>() else {
- bail!(elem.span(), "outlined elements must be referenceable");
+ let Some(outlinable) = elem.with::<dyn Outlinable>() 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::<dyn Refable>())
- .map_or(false, |last| last.level() >= refable.level())
+ .and_then(|ancestor| ancestor.with::<dyn Outlinable>())
+ .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::<dyn Refable>().unwrap();
+ let ancestor_outlinable = ancestor.with::<dyn Outlinable>().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<Option<Content>>;
+
+ /// 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 328e6098..c538b696 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::<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 elem = elem.at(span)?;
+ let refable = elem
+ .with::<dyn Refable>()
+ .ok_or_else(|| {
+ if elem.can::<dyn Figurable>() {
+ 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::<dyn Refable>()
- .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<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,
- }
+ 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<Content>,
- lang: Lang,
- region: Option<Region>,
- ) -> 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,
- region: Option<Region>,
- ) -> SourceResult<Option<Content>> {
- self.reference(vt, None, lang, region).map(Some)
- }
+ /// The supplement, if not overriden by the reference.
+ fn supplement(&self) -> Content;
- /// 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 counter of this element.
+ fn counter(&self) -> Counter;
/// Returns the numbering of this element.
fn numbering(&self) -> Option<Numbering>;
-
- /// Returns the counter of this element.
- fn counter(&self) -> Counter;
}
diff --git a/src/model/styles.rs b/src/model/styles.rs
index 2f33ef92..7ebb766c 100644
--- a/src/model/styles.rs
+++ b/src/model/styles.rs
@@ -463,6 +463,12 @@ impl Cast for LocatableSelector {
}
}
+impl From<LocatableSelector> 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<ShowableSelector> 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 c904fc99..d3896948 100644
--- a/tests/ref/meta/ref.png
+++ b/tests/ref/meta/ref.png
Binary files differ
diff --git a/tests/typ/meta/ref.typ b/tests/typ/meta/ref.typ
index 2bed2125..a62e6cce 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",
) <fig1>
#figure(
- image("/tiger.jpg", height: 3cm),
+ image("/tiger.jpg", height: 1cm),
caption: [A tiger.],
- supplement: "Figg",
+ supplement: "Tig",
) <fig2>
-#figure(
- $ A = 1 $,
- kind: "equation",
- supplement: "Equa",
-
-) <eq1>
-@fig1
-
-@fig2
-
-@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 <intro>
-
-= Summary <sum>
-
-== Subsection <sub>
-
-@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
- }
-}
+$ A = 1 $ <eq1>
-@txt
+#set math.equation(supplement: none)
+$ A = 1 $ <eq2>
-Ref something unreferable <txt>
+@fig1, @fig2, @eq1, (@eq2)
-@under
-#underline[
-Some underline text.
-] <under>
+#set ref(supplement: none)
+@fig1, @fig2, @eq1, @eq2