Refactor and document figures

This commit is contained in:
Laurenz 2023-04-04 23:15:03 +02:00
parent f347ed4314
commit 23715e813e
10 changed files with 335 additions and 339 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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