Refactor and document figures
This commit is contained in:
parent
f347ed4314
commit
23715e813e
@ -10,6 +10,9 @@ use crate::prelude::*;
|
||||
/// the [grid documentation]($func/grid) for more information on how to size the
|
||||
/// table tracks.
|
||||
///
|
||||
/// To give a table a caption and make it [referenceable]($func/ref), put it
|
||||
/// into a [figure]($func/figure).
|
||||
///
|
||||
/// ## Example
|
||||
/// ```example
|
||||
/// #table(
|
||||
|
@ -165,7 +165,6 @@ impl Synthesize for EquationElem {
|
||||
fn synthesize(&mut self, _vt: &mut Vt, styles: StyleChain) -> SourceResult<()> {
|
||||
self.push_block(self.block(styles));
|
||||
self.push_numbering(self.numbering(styles));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -317,7 +316,7 @@ impl Refable for EquationElem {
|
||||
self.numbering(styles)
|
||||
}
|
||||
|
||||
fn counter(&self, _styles: StyleChain) -> Counter {
|
||||
fn counter(&self, _: StyleChain) -> Counter {
|
||||
Counter::of(Self::func())
|
||||
}
|
||||
}
|
||||
|
@ -135,7 +135,6 @@ impl BibliographyElem {
|
||||
impl Synthesize for BibliographyElem {
|
||||
fn synthesize(&mut self, _vt: &mut Vt, styles: StyleChain) -> SourceResult<()> {
|
||||
self.push_style(self.style(styles));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -322,7 +321,6 @@ impl Synthesize for CiteElem {
|
||||
self.push_supplement(self.supplement(styles));
|
||||
self.push_brackets(self.brackets(styles));
|
||||
self.push_style(self.style(styles));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -496,9 +496,7 @@ cast_from_value! {
|
||||
|
||||
Self::Selector(Selector::Elem(element, None))
|
||||
},
|
||||
selector: Selector => {
|
||||
Self::Selector(selector)
|
||||
}
|
||||
selector: Selector => Self::Selector(selector),
|
||||
}
|
||||
|
||||
impl Debug for CounterKey {
|
||||
|
@ -1,7 +1,5 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use ecow::eco_vec;
|
||||
|
||||
use super::{
|
||||
Count, Counter, CounterKey, CounterUpdate, LocalName, Numbering, NumberingPattern,
|
||||
};
|
||||
@ -12,85 +10,63 @@ use crate::text::TextElem;
|
||||
|
||||
/// A figure with an optional caption.
|
||||
///
|
||||
/// ## Content detection
|
||||
/// By default, the figure will attempt to automatically detect the content
|
||||
/// and use a priority list to detect which content is likely
|
||||
/// to be the most important. The priority list is as follows:
|
||||
/// - [image]($func/image) are the most important
|
||||
/// - [equations]($func/equation) are the second most important
|
||||
/// - [code]($func/raw) are the third most important
|
||||
/// - [table]($func/table) are the fourth most important.
|
||||
/// Automatically detects its contents to select the correct counting track.
|
||||
/// For example, figures containing images will be numbered separately from
|
||||
/// figures containing tables.
|
||||
///
|
||||
/// There can be a variety of content within a figure and only the first element
|
||||
/// of the most important category will be used. For example, if a figure contains
|
||||
/// an image and a table, the image will be used. This behaviour can be overridden
|
||||
/// using the `kind` parameter. By setting it, you can force the figure to use a
|
||||
/// specific type of content. Note however that if the figure does not contain said
|
||||
/// element, or the `kind` is set to a string, you will need to manually specify
|
||||
/// the supplement to be able to make an outline or reference it.
|
||||
/// ## Examples
|
||||
/// The example below shows a basic figure with an image:
|
||||
/// ```example
|
||||
/// @glacier shows a glacier. Glaciers
|
||||
/// are complex systems.
|
||||
///
|
||||
/// #figure(
|
||||
/// image("glacier.jpg", width: 80%),
|
||||
/// caption: [A curious figure.],
|
||||
/// ) <glacier>
|
||||
/// ```
|
||||
///
|
||||
/// You can also insert [tables]($func/table) into figures to give them a
|
||||
/// caption. The figure will detect this and automatically use a separate
|
||||
/// counter.
|
||||
///
|
||||
/// ```example
|
||||
/// #figure(caption: [ Hello, world! ], kind: table)[
|
||||
/// #table(
|
||||
/// columns: (auto, 1fr),
|
||||
/// image("molecular.jpg", width: 32pt),
|
||||
/// [ A first picture ],
|
||||
/// image("molecular.jpg", width: 32pt),
|
||||
/// [ A second picture ],
|
||||
/// #figure(
|
||||
/// table(
|
||||
/// columns: 4,
|
||||
/// [t], [1], [2], [3],
|
||||
/// [y], [0.3s], [0.4s], [0.8s],
|
||||
/// ),
|
||||
/// caption: [Timing results],
|
||||
/// )
|
||||
/// ]
|
||||
/// ```
|
||||
///
|
||||
/// If you use an element that is not supported by the figure, and set it as its `content` parameter,
|
||||
/// to be able to make an outline or reference it, you will need to manually specify the supplement
|
||||
/// and counter. Otherwise the figure will produce an error.
|
||||
/// This behaviour can be overridden by explictly specifying the figure's
|
||||
/// `kind`. All figures of the same kind share a common counter.
|
||||
///
|
||||
/// ## Counting and supplement
|
||||
/// Based on the `kind` parameter or the detected content, the figure will chose
|
||||
/// the appropriate counter and supplement. These can be overridden by using the
|
||||
/// `kind` and `supplement` parameters respectively.
|
||||
/// ## Modifying the appearance
|
||||
/// You can completely customize the look of your figures with a [show
|
||||
/// rule]($styling/#show-rules). In the example below, we show the figure's
|
||||
/// caption above its body and display its supplement and counter after the
|
||||
/// caption.
|
||||
///
|
||||
/// The overriding of these values is done as follows:
|
||||
/// ```example
|
||||
/// #figure(caption: [ Hello, world! ], kind: "hello", supplement: "Molecule")[
|
||||
/// #image("molecular.jpg", width: 32pt)
|
||||
/// #show figure: it => align(center)[
|
||||
/// #it.caption |
|
||||
/// #emph[
|
||||
/// #it.supplement
|
||||
/// #it.counter.display(it.numbering)
|
||||
/// ]
|
||||
/// #v(10pt, weak: true)
|
||||
/// #it.body
|
||||
/// ]
|
||||
/// ```
|
||||
///
|
||||
/// The default counters are defined as follows:
|
||||
/// - for (tables)[$func/table]: `counter(figure.where(kind: table))`
|
||||
/// - for (equations)[$func/equation]: `counter(figure.where(kind: math.equation))`
|
||||
/// - for (raw text)[$func/raw]: `counter(figure.where(kind: raw))`
|
||||
/// - for (images)[$func/image]: `counter(figure.where(kind: image))`
|
||||
/// - for a custom kind: `counter(figure.where(kind: kind))`
|
||||
///
|
||||
/// These are the counters you need to use if you want to change the
|
||||
/// counting behaviour of figures.
|
||||
///
|
||||
/// ## Numbering
|
||||
/// By default, the figure will be numbered using the `1` [numbering pattern]($func/numbering).
|
||||
/// This can be overridden by using the `numbering` parameter.
|
||||
///
|
||||
/// ## Outline
|
||||
/// By default, the figure will be outlined in the list of figures/tables/code. This can be disabled by
|
||||
/// setting the `outlined` parameter to `false`.
|
||||
///
|
||||
/// ## Global figure counter
|
||||
/// There is a global figure counter which can be accessed which counts all numbered figures in the document
|
||||
/// regardless of its type. This counter can be accessed using the `counter(figure)` function.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```example
|
||||
/// = Pipeline
|
||||
/// @lab shows the central step of
|
||||
/// our molecular testing pipeline.
|
||||
///
|
||||
/// #figure(
|
||||
/// image("molecular.jpg", width: 80%),
|
||||
/// caption: [
|
||||
/// The molecular testing pipeline.
|
||||
/// ],
|
||||
/// ) <lab>
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// Display: Figure
|
||||
@ -104,224 +80,139 @@ pub struct FigureElem {
|
||||
/// The figure's caption.
|
||||
pub caption: Option<Content>,
|
||||
|
||||
/// The figure's supplement, if not provided, the figure will attempt to
|
||||
/// automatically detect the counter from the content.
|
||||
/// The kind of the figure this is.
|
||||
///
|
||||
/// ## Custom figure type
|
||||
/// If you are using a custom figure type and would like to figure to be
|
||||
/// referenced, you will need to manually specify the supplement, using either
|
||||
/// a function or a string.
|
||||
/// If set to `{auto}`, the figure will try to automatically determine its
|
||||
/// kind. All figures of the same kind share a common counter.
|
||||
///
|
||||
/// Setting this to something other than `{auto}` will override the
|
||||
/// automatic detection. This can be useful if
|
||||
/// - you wish to create a custom figure type that is not an
|
||||
/// [image]($func/image), a [table]($func/table) or [code]($func/raw),
|
||||
/// - you want to force the figure to use a counter regardless of its
|
||||
/// content.
|
||||
///
|
||||
/// You can set the kind to be an element function or a string. If you set
|
||||
/// it to an element function that is not supported by the figure, you will
|
||||
/// need to manually specify the figure's supplement.
|
||||
///
|
||||
/// The figure's automatic detection is based on a priority list to select
|
||||
/// the element that is likely to be the most important one. If the figure's
|
||||
/// body contains multiple valid elements, the one with the highest priority
|
||||
/// is selected. The priority list is as follows:
|
||||
/// - [image]($func/image) is the most important,
|
||||
/// - [code]($func/raw) is the second most important,
|
||||
/// - [table]($func/table) is the least important one.
|
||||
///
|
||||
/// ```example
|
||||
/// #figure(caption: "My custom figure", kind: "foo", supplement: "Bar")[
|
||||
/// #block[ The inside of my custom figure! ]
|
||||
/// ]
|
||||
/// #figure(
|
||||
/// circle(radius: 10pt),
|
||||
/// caption: [A curious atom.],
|
||||
/// kind: "atom",
|
||||
/// supplement: [Atom],
|
||||
/// )
|
||||
/// ```
|
||||
#[default(Smart::Auto)]
|
||||
pub supplement: Smart<Option<Supplement>>,
|
||||
pub kind: Smart<FigureKind>,
|
||||
|
||||
/// Whether the figure should appear in the list of figures/tables/code.
|
||||
/// Defaults to `true`.
|
||||
#[default(true)]
|
||||
pub outlined: bool,
|
||||
/// The figure's supplement.
|
||||
///
|
||||
/// If set to `{auto}`, the figure will try to automatically determine the
|
||||
/// correct supplement based on the `kind` and the active [text
|
||||
/// 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.
|
||||
///
|
||||
/// ```example
|
||||
/// #figure(
|
||||
/// [The contents of my figure!],
|
||||
/// caption: [My custom figure],
|
||||
/// supplement: [Bar],
|
||||
/// kind: "foo",
|
||||
/// )
|
||||
/// ```
|
||||
#[default(Smart::Auto)]
|
||||
pub supplement: Smart<Supplement>,
|
||||
|
||||
/// How to number the figure. Accepts a
|
||||
/// [numbering pattern or function]($func/numbering).
|
||||
///
|
||||
/// Defaults to `{"1"}`.
|
||||
#[default(Some(NumberingPattern::from_str("1").unwrap().into()))]
|
||||
pub numbering: Option<Numbering>,
|
||||
|
||||
/// The type of the figure. Setting this will override the automatic detection.
|
||||
///
|
||||
/// This can be useful if you wish to create a custom figure type that is not
|
||||
/// an [image]($func/image), a [table]($func/table) or a [code]($func/raw). Or if
|
||||
/// you want to force the figure to use a specific type regardless of its content.
|
||||
///
|
||||
/// You can set the kind to be an element, or a string. If you set it to be
|
||||
/// a string or an element that is not supported by the figure, you will need to
|
||||
/// manually specify the supplement if you wish to number the figure.
|
||||
#[default(Smart::Auto)]
|
||||
pub kind: Smart<ContentParam>,
|
||||
|
||||
/// The vertical gap between the body and caption.
|
||||
#[default(Em::new(0.65).into())]
|
||||
pub gap: Length,
|
||||
|
||||
/// Convenience field to get access to the figures counter, if any.
|
||||
/// If the figure is not numbered, this will be `none`.
|
||||
/// Otherwise it will be set to the counter being used by this figure.
|
||||
/// Whether the figure should appear in an [`outline`]($func/outline)
|
||||
/// of figures.
|
||||
///
|
||||
/// Defaults to `{true}`.
|
||||
#[default(true)]
|
||||
pub outlined: bool,
|
||||
|
||||
/// Convenience field to get access to the counter for this figure.
|
||||
///
|
||||
/// The counter only depends on the `kind`:
|
||||
/// - For (tables)[$func/table]: `{counter(figure.where(kind: table))}`
|
||||
/// - For (images)[$func/image]: `{counter(figure.where(kind: image))}`
|
||||
/// - For a custom kind: `{counter(figure.where(kind: kind))}`
|
||||
///
|
||||
/// These are the counters you'll need to modify if you want to skip a
|
||||
/// number or reset the counter.
|
||||
#[synthesized]
|
||||
#[internal]
|
||||
pub counter: Option<Counter>,
|
||||
}
|
||||
|
||||
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 determine_type(
|
||||
&self,
|
||||
styles: StyleChain,
|
||||
require_supplement: bool,
|
||||
) -> Option<Content> {
|
||||
let potential_elems = self.body().query(if require_supplement {
|
||||
Selector::All(eco_vec![
|
||||
Selector::can::<dyn Figurable>(),
|
||||
Selector::can::<dyn LocalName>()
|
||||
])
|
||||
} else {
|
||||
Selector::can::<dyn Figurable>()
|
||||
});
|
||||
|
||||
potential_elems.into_iter().max_by_key(|elem| {
|
||||
elem.with::<dyn Figurable>()
|
||||
.expect("should be figurable")
|
||||
.priority(styles)
|
||||
})
|
||||
}
|
||||
|
||||
/// 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_elem(&self, func: ElemFunc) -> Option<Content> {
|
||||
self.body().query(Selector::Elem(func, None)).first().cloned()
|
||||
}
|
||||
|
||||
/// Builds the supplement and numbering of the figure.
|
||||
/// If there is no numbering, returns [`None`].
|
||||
///
|
||||
/// # Errors
|
||||
/// If a numbering is specified but the [`Self::data()`] is `None`.
|
||||
pub fn show_supplement_and_numbering(
|
||||
&self,
|
||||
vt: &mut Vt,
|
||||
styles: StyleChain,
|
||||
external_supp: Option<Content>,
|
||||
) -> SourceResult<Option<Content>> {
|
||||
if let (Some(numbering), Some(supplement), Some(counter)) = (
|
||||
self.numbering(styles),
|
||||
self.supplement(styles)
|
||||
.as_custom()
|
||||
.and_then(|s| s.and_then(Supplement::as_content)),
|
||||
self.counter(),
|
||||
) {
|
||||
let mut name = external_supp.unwrap_or(supplement);
|
||||
|
||||
if !name.is_empty() {
|
||||
name += TextElem::packed("\u{a0}");
|
||||
}
|
||||
|
||||
let number = counter
|
||||
.at(vt, self.0.location().expect("missing location"))?
|
||||
.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.
|
||||
///
|
||||
/// # Errors
|
||||
/// If a numbering is specified but the [`Self::element`] is `None`.
|
||||
pub fn show_caption(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||
let Some(mut caption) = self.caption(styles) else {
|
||||
return Ok(Content::empty());
|
||||
};
|
||||
|
||||
if let Some(sup_and_num) = self.show_supplement_and_numbering(vt, styles, None)? {
|
||||
caption = sup_and_num + TextElem::packed(": ") + caption;
|
||||
}
|
||||
|
||||
Ok(caption)
|
||||
}
|
||||
}
|
||||
|
||||
impl Synthesize for FigureElem {
|
||||
fn synthesize(&mut self, vt: &mut Vt, styles: StyleChain) -> SourceResult<()> {
|
||||
self.push_numbering(self.numbering(styles));
|
||||
|
||||
// We get the numbering or `None`.
|
||||
let numbering = self.numbering(styles);
|
||||
let supplement = self.supplement(styles);
|
||||
|
||||
// We get the content or `None`.
|
||||
let content = match self.kind(styles) {
|
||||
Smart::Auto => match self.determine_type(styles, supplement.is_auto()) {
|
||||
Some(ty) => Some(ty),
|
||||
None => bail!(
|
||||
self.span(),
|
||||
"unable to determine figure type, use `kind` to manually specify it"
|
||||
),
|
||||
},
|
||||
Smart::Custom(ContentParam::Elem(ty)) => self.find_elem(ty),
|
||||
Smart::Custom(ContentParam::Name(_)) => None,
|
||||
// Determine the figure's kind.
|
||||
let kind = match self.kind(styles) {
|
||||
Smart::Auto => self
|
||||
.find_figurable(styles)
|
||||
.map(|elem| FigureKind::Elem(elem.func()))
|
||||
.ok_or(
|
||||
"unable to determine the figure's `kind`, please specify it manually",
|
||||
)
|
||||
.at(self.span())?,
|
||||
Smart::Custom(kind) => kind,
|
||||
};
|
||||
|
||||
if self.kind(styles).is_auto() {
|
||||
if let Some(content) = &content {
|
||||
self.push_kind(Smart::Custom(ContentParam::Elem(content.func())));
|
||||
}
|
||||
let content = match &kind {
|
||||
FigureKind::Elem(func) => self.find_of_elem(*func),
|
||||
FigureKind::Name(_) => None,
|
||||
}
|
||||
.unwrap_or_else(|| self.body());
|
||||
|
||||
// The list of choices is the following:
|
||||
// 1. If there is a detected content, we use the counter `counter(figure.where(kind: detected_content))`
|
||||
// 2. If there is a name/elem, we use the counter `counter(figure.where(kind: name/elem))`
|
||||
// 4. We return None.
|
||||
let counter = content
|
||||
.as_ref()
|
||||
.map(Content::func)
|
||||
.map(Value::from)
|
||||
.or_else(|| self.kind(styles).as_custom().map(Value::from))
|
||||
.map(|content| {
|
||||
Counter::new(CounterKey::Selector(Selector::Elem(
|
||||
// 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) => Content::new(*func)
|
||||
.with::<dyn LocalName>()
|
||||
.map(|c| TextElem::packed(c.local_name(TextElem::lang_in(styles))))
|
||||
.ok_or("unable to determine the figure's `supplement`, please specify it manually")
|
||||
.at(self.span())?,
|
||||
FigureKind::Name(_) => bail!(self.span(), "please specify the figure's supplement"),
|
||||
},
|
||||
Smart::Custom(supp) => supp.resolve(vt, [content.into()])?,
|
||||
};
|
||||
|
||||
// Construct the figure's counter.
|
||||
let counter = Counter::new(CounterKey::Selector(Selector::Elem(
|
||||
Self::func(),
|
||||
Some(dict! {
|
||||
"kind" => content,
|
||||
"kind" => kind.clone(),
|
||||
}),
|
||||
)))
|
||||
});
|
||||
)));
|
||||
|
||||
// We get the supplement or `None`.
|
||||
// The supplement must either be set manually or the content identification
|
||||
// must have succeeded.
|
||||
let supplement = match supplement {
|
||||
Smart::Auto => {
|
||||
content.as_ref().and_then(|c| c.with::<dyn LocalName>()).map(|c| {
|
||||
Supplement::Content(TextElem::packed(
|
||||
c.local_name(TextElem::lang_in(styles)),
|
||||
))
|
||||
})
|
||||
}
|
||||
Smart::Custom(supp) => supp,
|
||||
};
|
||||
|
||||
// When the user wishes to number their figure, we check whether there is a
|
||||
// counter and a supplement. If so, we push the element, which is just a
|
||||
// summary of the caption properties. We also push all of the components
|
||||
// of the summary for convenient access by the user for `show` rules.
|
||||
if let Some(numbering) = numbering {
|
||||
let Some(counter) = counter else {
|
||||
bail!(self.span(), "numbering a figure requires that is has a kind");
|
||||
};
|
||||
|
||||
let Some(supplement) = supplement else {
|
||||
bail!(self.span(), "numbering a figure requires that is has a supplement");
|
||||
};
|
||||
|
||||
let supplement = supplement
|
||||
.resolve(vt, [content.unwrap_or_else(|| self.body()).into()])?;
|
||||
|
||||
self.push_supplement(Smart::Custom(Some(Supplement::Content(
|
||||
supplement.clone(),
|
||||
))));
|
||||
self.push_counter(Some(counter.clone()));
|
||||
self.push_numbering(Some(numbering.clone()));
|
||||
} else {
|
||||
self.push_supplement(Smart::Custom(None));
|
||||
self.push_counter(None);
|
||||
self.push_numbering(None);
|
||||
}
|
||||
self.push_kind(Smart::Custom(kind));
|
||||
self.push_supplement(Smart::Custom(Supplement::Content(supplement)));
|
||||
self.push_numbering(self.numbering(styles));
|
||||
self.push_counter(Some(counter));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -386,39 +277,109 @@ impl Refable for FigureElem {
|
||||
self.numbering(styles)
|
||||
}
|
||||
|
||||
fn counter(&self, _styles: StyleChain) -> Counter {
|
||||
fn counter(&self, _: StyleChain) -> Counter {
|
||||
self.counter().unwrap_or_else(|| Counter::of(Self::func()))
|
||||
}
|
||||
}
|
||||
|
||||
/// The `kind` parameter of [`FigureElem`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ContentParam {
|
||||
/// The content is an element function.
|
||||
Elem(ElemFunc),
|
||||
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, styles: StyleChain) -> Option<Content> {
|
||||
self.body()
|
||||
.query(Selector::can::<dyn Figurable>())
|
||||
.into_iter()
|
||||
.max_by_key(|elem| elem.with::<dyn Figurable>().unwrap().priority(styles))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
/// The content is a name.
|
||||
/// 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(Selector::Elem(func, None))
|
||||
.into_iter()
|
||||
.next()
|
||||
.cloned()
|
||||
}
|
||||
|
||||
/// Builds the supplement and numbering of the figure.
|
||||
/// If there is no numbering, returns [`None`].
|
||||
///
|
||||
/// # Errors
|
||||
/// If a numbering is specified but the [`Self::data()`] is `None`.
|
||||
pub fn show_supplement_and_numbering(
|
||||
&self,
|
||||
vt: &mut Vt,
|
||||
styles: StyleChain,
|
||||
external_supplement: Option<Content>,
|
||||
) -> SourceResult<Option<Content>> {
|
||||
if let (Some(numbering), Some(supplement), Some(counter)) = (
|
||||
self.numbering(styles),
|
||||
self.supplement(styles).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.
|
||||
///
|
||||
/// # Errors
|
||||
/// If a numbering is specified but the [`Self::element`] is `None`.
|
||||
pub fn show_caption(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||
let Some(mut caption) = self.caption(styles) else {
|
||||
return Ok(Content::empty());
|
||||
};
|
||||
|
||||
if let Some(sup_and_num) = self.show_supplement_and_numbering(vt, styles, None)? {
|
||||
caption = sup_and_num + TextElem::packed(": ") + caption;
|
||||
}
|
||||
|
||||
Ok(caption)
|
||||
}
|
||||
}
|
||||
|
||||
/// The `kind` parameter of a [`FigureElem`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum FigureKind {
|
||||
/// The kind is an element function.
|
||||
Elem(ElemFunc),
|
||||
/// The kind is a name.
|
||||
Name(EcoString),
|
||||
}
|
||||
|
||||
cast_from_value! {
|
||||
ContentParam,
|
||||
FigureKind,
|
||||
v: ElemFunc => Self::Elem(v),
|
||||
v: EcoString => Self::Name(v),
|
||||
}
|
||||
|
||||
cast_to_value! {
|
||||
v: ContentParam => match v {
|
||||
ContentParam::Elem(v) => v.into(),
|
||||
ContentParam::Name(v) => v.into(),
|
||||
v: FigureKind => match v {
|
||||
FigureKind::Elem(v) => v.into(),
|
||||
FigureKind::Name(v) => v.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// An element that can be autodetected in a figure.
|
||||
/// This trait is used to determine the type of a figure, its counter, its numbering pattern
|
||||
/// and the supplement to use for referencing it and creating the caption.
|
||||
/// The element chosen as the figure's content is the one with the highest priority.
|
||||
pub trait Figurable {
|
||||
/// An element that can be auto-detected in a figure.
|
||||
///
|
||||
/// This trait is used to determine the type of a figure. The element chosen as
|
||||
/// the figure's content is the figurable descendant with the highest priority.
|
||||
pub trait Figurable: LocalName {
|
||||
/// The priority of this element.
|
||||
fn priority(&self, styles: StyleChain) -> isize;
|
||||
}
|
||||
|
@ -101,7 +101,6 @@ impl Synthesize for HeadingElem {
|
||||
self.push_level(self.level(styles));
|
||||
self.push_numbering(self.numbering(styles));
|
||||
self.push_outlined(self.outlined(styles));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -163,7 +162,7 @@ impl Refable for HeadingElem {
|
||||
styles: StyleChain,
|
||||
supplement: Option<Content>,
|
||||
) -> SourceResult<Content> {
|
||||
// first we create the supplement of the heading
|
||||
// Create the supplement of the heading.
|
||||
let mut supplement = if let Some(supplement) = supplement {
|
||||
supplement
|
||||
} else {
|
||||
@ -178,19 +177,19 @@ impl Refable for HeadingElem {
|
||||
}
|
||||
};
|
||||
|
||||
// we append a space if the supplement is not empty
|
||||
// Append a non-breaking space if the supplement is not empty.
|
||||
if !supplement.is_empty() {
|
||||
supplement += TextElem::packed('\u{a0}')
|
||||
};
|
||||
|
||||
// we check for a numbering
|
||||
// Check for a numbering.
|
||||
let Some(numbering) = self.numbering(styles) else {
|
||||
bail!(self.span(), "only numbered headings can be referenced");
|
||||
};
|
||||
|
||||
// we get the counter and display it
|
||||
// Get the counter and display it.
|
||||
let numbers = Counter::of(Self::func())
|
||||
.at(vt, self.0.location().expect("missing location"))?
|
||||
.at(vt, self.0.location().unwrap())?
|
||||
.display(vt, &numbering.trimmed())?;
|
||||
|
||||
Ok(supplement + numbers)
|
||||
@ -204,21 +203,21 @@ impl Refable for HeadingElem {
|
||||
self.numbering(styles)
|
||||
}
|
||||
|
||||
fn counter(&self, _styles: StyleChain) -> Counter {
|
||||
fn counter(&self, _: StyleChain) -> Counter {
|
||||
Counter::of(Self::func())
|
||||
}
|
||||
|
||||
fn outline(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Option<Content>> {
|
||||
// we check if the heading is outlined
|
||||
// Check whether the heading is outlined.
|
||||
if !self.outlined(styles) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// We build the numbering followed by the title
|
||||
// Build the numbering followed by the title.
|
||||
let mut start = self.body();
|
||||
if let Some(numbering) = self.numbering(StyleChain::default()) {
|
||||
let numbers = Counter::of(HeadingElem::func())
|
||||
.at(vt, self.0.location().expect("missing location"))?
|
||||
.at(vt, self.0.location().unwrap())?
|
||||
.display(vt, &numbering)?;
|
||||
start = numbers + SpaceElem::new().pack() + start;
|
||||
};
|
||||
|
@ -3,15 +3,12 @@ use crate::layout::{BoxElem, HElem, HideElem, ParbreakElem, RepeatElem};
|
||||
use crate::prelude::*;
|
||||
use crate::text::{LinebreakElem, SpaceElem, TextElem};
|
||||
|
||||
/// A section outline / table of contents / table of figures / table of tables / etc.
|
||||
/// A table of contents, figures, or other elements.
|
||||
///
|
||||
/// This function generates a list of all headings in the document, up to a
|
||||
/// given depth. The [heading]($func/heading) numbering will be reproduced
|
||||
/// within the outline.
|
||||
///
|
||||
/// Alternatively, by setting the `target` parameter, the outline can be used to
|
||||
/// generate a list of all figures, tables, code blocks, etc. When the `target` parameter
|
||||
/// is set, the `depth` parameter is ignored unless it is set to `heading`.
|
||||
/// This function generates a list of all occurances of an element in the
|
||||
/// document, up to a given depth. The element's numbering and page number will
|
||||
/// be displayed in the outline alongside its title or caption. By default this
|
||||
/// generates a table of contents.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```example
|
||||
@ -24,13 +21,24 @@ use crate::text::{LinebreakElem, SpaceElem, TextElem};
|
||||
/// #lorem(10)
|
||||
/// ```
|
||||
///
|
||||
/// ## Example: List of figures
|
||||
/// ```example
|
||||
/// #outline(target: figure.where(kind: image), title: "Table of Figures")
|
||||
/// ## Alternative outlines
|
||||
/// By setting the `target` parameter, the outline can be used to generate a
|
||||
/// list of other kinds of elements than headings. In the example below, we list
|
||||
/// all figures containing images by setting `target` to `{figure.where(kind:
|
||||
/// image)}`. We could have also set it to just `figure`, but then the list
|
||||
/// would also include figures containing tables or other material. For more
|
||||
/// details on the `where` selector, [see here]($type/content.where).
|
||||
///
|
||||
/// #figure(caption: "A nice figure!")[
|
||||
/// #image("/tiger.jpg")
|
||||
/// ]
|
||||
/// ```example
|
||||
/// #outline(
|
||||
/// title: [List of Figures],
|
||||
/// target: figure.where(kind: image),
|
||||
/// )
|
||||
///
|
||||
/// #figure(
|
||||
/// image("tiger.jpg"),
|
||||
/// caption: [A nice figure!],
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// Display: Outline
|
||||
@ -39,28 +47,60 @@ use crate::text::{LinebreakElem, SpaceElem, TextElem};
|
||||
pub struct OutlineElem {
|
||||
/// The title of the outline.
|
||||
///
|
||||
/// - When set to `{auto}`, an appropriate title for the [text
|
||||
/// language]($func/text.lang) will be used. This is the default.
|
||||
/// - When set to `{auto}`, an appropriate title for the
|
||||
/// [text language]($func/text.lang) will be used. This is the default.
|
||||
/// - When set to `{none}`, the outline will not have a title.
|
||||
/// - A custom title can be set by passing content.
|
||||
#[default(Some(Smart::Auto))]
|
||||
pub title: Option<Smart<Content>>,
|
||||
|
||||
/// The maximum depth up to which headings are included in the outline. When
|
||||
/// this argument is `{none}`, all headings are included.
|
||||
pub depth: Option<NonZeroUsize>,
|
||||
|
||||
/// The type of element to include in the outline.
|
||||
///
|
||||
/// To list figures containing a specific kind of element, like a table, you
|
||||
/// can write `{figure.where(kind: table)}`.
|
||||
///
|
||||
/// ```example
|
||||
/// #outline(
|
||||
/// title: [List of Tables],
|
||||
/// target: figure.where(kind: table),
|
||||
/// )
|
||||
///
|
||||
/// #figure(
|
||||
/// table(
|
||||
/// columns: 4,
|
||||
/// [t], [1], [2], [3],
|
||||
/// [y], [0.3], [0.7], [0.5],
|
||||
/// ),
|
||||
/// caption: [Experiment results],
|
||||
/// )
|
||||
/// ```
|
||||
#[default(Selector::Elem(HeadingElem::func(), Some(dict! { "outlined" => true })))]
|
||||
pub target: Selector,
|
||||
|
||||
/// Whether to indent the subheadings to align the start of their numbering
|
||||
/// The maximum level up to which elements are included in the outline. When
|
||||
/// this argument is `{none}`, all elements are included.
|
||||
///
|
||||
/// ```example
|
||||
/// #set heading(numbering: "1.")
|
||||
/// #outline(depth: 2)
|
||||
///
|
||||
/// = Yes
|
||||
/// Top-level section.
|
||||
///
|
||||
/// == Still
|
||||
/// Subsection.
|
||||
///
|
||||
/// === Nope
|
||||
/// Not included.
|
||||
/// ```
|
||||
pub depth: Option<NonZeroUsize>,
|
||||
|
||||
/// Whether to indent the sub-elements to align the start of their numbering
|
||||
/// with the title of their parents. This will only have an effect if a
|
||||
/// [heading numbering]($func/heading.numbering) is set.
|
||||
///
|
||||
/// ```example
|
||||
/// #set heading(numbering: "1.a.")
|
||||
///
|
||||
/// #outline(indent: true)
|
||||
///
|
||||
/// = About ACME Corp.
|
||||
@ -117,12 +157,11 @@ impl Show for OutlineElem {
|
||||
};
|
||||
|
||||
let location = elem.location().expect("missing location");
|
||||
|
||||
if depth < refable.level(styles) {
|
||||
if depth < refable.level(StyleChain::default()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(outline) = refable.outline(vt, styles)? else {
|
||||
let Some(outline) = refable.outline(vt, StyleChain::default())? else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@ -149,7 +188,7 @@ impl Show for OutlineElem {
|
||||
ancestor_refable.numbering(StyleChain::default())
|
||||
{
|
||||
let numbers = ancestor_refable
|
||||
.counter(styles)
|
||||
.counter(StyleChain::default())
|
||||
.at(vt, ancestor.location().unwrap())?
|
||||
.display(vt, &numbering)?;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use super::{BibliographyElem, CiteElem, Counter, Numbering};
|
||||
use super::{BibliographyElem, CiteElem, Counter, Figurable, Numbering};
|
||||
use crate::prelude::*;
|
||||
|
||||
/// A reference to a label or bibliography.
|
||||
@ -6,9 +6,14 @@ use crate::prelude::*;
|
||||
/// 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.
|
||||
/// the respective element. Reference syntax can also be used to
|
||||
/// [cite]($func/cite) from a bibliography.
|
||||
///
|
||||
/// 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.
|
||||
@ -108,8 +113,16 @@ impl Show for RefElem {
|
||||
|
||||
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,
|
||||
|
@ -124,7 +124,6 @@ impl RawElem {
|
||||
impl Synthesize for RawElem {
|
||||
fn synthesize(&mut self, _vt: &mut Vt, styles: StyleChain) -> SourceResult<()> {
|
||||
self.push_lang(self.lang(styles));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -354,28 +354,11 @@ impl Content {
|
||||
self.attrs.push(Attr::Location(location));
|
||||
}
|
||||
|
||||
/// Gives an iterator over the children of this content
|
||||
pub fn children(&self) -> impl Iterator<Item = &Content> {
|
||||
self.attrs.iter().filter_map(Attr::child)
|
||||
}
|
||||
|
||||
/// Gives an iterator over the children of this content that are contained
|
||||
/// within the arguments of the content.
|
||||
pub fn children_in_args(&self) -> impl Iterator<Item = &Content> {
|
||||
self.attrs
|
||||
.iter()
|
||||
.filter_map(Attr::value)
|
||||
.filter_map(|value| match value {
|
||||
Value::Content(content) => Some(content),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Queries the content tree for all elements that match the given selector.
|
||||
///
|
||||
/// # Show rules
|
||||
/// Elements produced in `show` rules will not be included in the results.
|
||||
pub fn query(&self, selector: Selector) -> Vec<Content> {
|
||||
pub fn query(&self, selector: Selector) -> Vec<&Content> {
|
||||
let mut results = Vec::new();
|
||||
self.query_into(&selector, &mut results);
|
||||
results
|
||||
@ -383,9 +366,9 @@ impl Content {
|
||||
|
||||
/// Queries the content tree for all elements that match the given selector
|
||||
/// and stores the results inside of the `results` vec.
|
||||
fn query_into(&self, selector: &Selector, results: &mut Vec<Content>) {
|
||||
fn query_into<'a>(&'a self, selector: &Selector, results: &mut Vec<&'a Content>) {
|
||||
if selector.matches(self) {
|
||||
results.push(self.clone());
|
||||
results.push(self);
|
||||
}
|
||||
|
||||
for attr in &self.attrs {
|
||||
@ -397,7 +380,11 @@ impl Content {
|
||||
}
|
||||
|
||||
/// Walks a given value to find any content that matches the selector.
|
||||
fn walk_value(value: &Value, selector: &Selector, results: &mut Vec<Content>) {
|
||||
fn walk_value<'a>(
|
||||
value: &'a Value,
|
||||
selector: &Selector,
|
||||
results: &mut Vec<&'a Content>,
|
||||
) {
|
||||
match value {
|
||||
Value::Content(content) => content.query_into(selector, results),
|
||||
Value::Array(array) => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user