Fix and simplify reference supplements

- Fixes #873 by properly handling `none` supplement for`ref`.
- Fixes #523 by adding a `supplement` parameter to `math.equation`
- In the future, we can remove supplement functions in favor of show-set rules with fine-grained selectors. Currently, this is not possible because show-set + synthesis doesn't play well together
This commit is contained in:
Laurenz 2023-05-22 20:50:15 +02:00
parent 183997d5fe
commit f4fd6855e7
9 changed files with 310 additions and 407 deletions

View File

@ -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)));
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<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))
}
}

View File

@ -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<()> {
// 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::<dyn LocalName>().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::<dyn Figurable>())
.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::<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 numbering(&self) -> Option<Numbering> {
self.numbering(StyleChain::default())
}
fn counter(&self) -> Counter {
self.counter().unwrap_or_else(|| Counter::of(Self::func()))
}
fn numbering(&self) -> Option<Numbering> {
self.numbering(StyleChain::default())
}
}
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 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()),
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<Content> {
/// 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(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))
}
}

View File

@ -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 level(&self) -> usize {
self.level(StyleChain::default()).get()
}
fn numbering(&self) -> Option<Numbering> {
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<Region>,
) -> SourceResult<Option<Content>> {
// Check whether the heading is outlined.
fn numbering(&self) -> Option<Numbering> {
self.numbering(StyleChain::default())
}
}
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())
}
}

View File

@ -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))
}
}

View File

@ -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
}
}

View File

@ -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)
}
/// 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>;
/// 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<Numbering>;
}

View File

@ -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 {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -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",
$ A = 1 $ <eq1>
) <eq1>
@fig1
#set math.equation(supplement: none)
$ A = 1 $ <eq2>
@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 <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
}
}
@txt
Ref something unreferable <txt>
@under
#underline[
Some underline text.
] <under>
#set ref(supplement: none)
@fig1, @fig2, @eq1, @eq2