From 23715e813e1888c8c317ec2f9649d0f1ad46bd8c Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 4 Apr 2023 23:15:03 +0200 Subject: [PATCH] Refactor and document figures --- library/src/layout/table.rs | 3 + library/src/math/mod.rs | 3 +- library/src/meta/bibliography.rs | 2 - library/src/meta/counter.rs | 4 +- library/src/meta/figure.rs | 501 ++++++++++++++----------------- library/src/meta/heading.rs | 19 +- library/src/meta/outline.rs | 91 ++++-- library/src/meta/reference.rs | 21 +- library/src/text/raw.rs | 1 - src/model/content.rs | 29 +- 10 files changed, 335 insertions(+), 339 deletions(-) diff --git a/library/src/layout/table.rs b/library/src/layout/table.rs index c9a67a1b2..4e21cb452 100644 --- a/library/src/layout/table.rs +++ b/library/src/layout/table.rs @@ -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( diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index b07fc78f9..db066522f 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -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()) } } diff --git a/library/src/meta/bibliography.rs b/library/src/meta/bibliography.rs index 7bc2ff7bd..864aa1ff4 100644 --- a/library/src/meta/bibliography.rs +++ b/library/src/meta/bibliography.rs @@ -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(()) } } diff --git a/library/src/meta/counter.rs b/library/src/meta/counter.rs index 3868040e2..5ad9b48d4 100644 --- a/library/src/meta/counter.rs +++ b/library/src/meta/counter.rs @@ -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 { diff --git a/library/src/meta/figure.rs b/library/src/meta/figure.rs index aae13738e..2cea230e5 100644 --- a/library/src/meta/figure.rs +++ b/library/src/meta/figure.rs @@ -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. -/// -/// 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. +/// Automatically detects its contents to select the correct counting track. +/// For example, figures containing images will be numbered separately from +/// figures containing tables. /// +/// ## Examples +/// The example below shows a basic figure with an image: /// ```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 ], -/// ) -/// ] +/// @glacier shows a glacier. Glaciers +/// are complex systems. +/// +/// #figure( +/// image("glacier.jpg", width: 80%), +/// caption: [A curious figure.], +/// ) /// ``` /// -/// 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. +/// 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. /// -/// ## 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. -/// -/// The overriding of these values is done as follows: /// ```example -/// #figure(caption: [ Hello, world! ], kind: "hello", supplement: "Molecule")[ -/// #image("molecular.jpg", width: 32pt) -/// ] +/// #figure( +/// table( +/// columns: 4, +/// [t], [1], [2], [3], +/// [y], [0.3s], [0.4s], [0.8s], +/// ), +/// caption: [Timing results], +/// ) /// ``` /// -/// 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))` +/// This behaviour can be overridden by explictly specifying the figure's +/// `kind`. All figures of the same kind share a common counter. /// -/// These are the counters you need to use if you want to change the -/// counting behaviour of figures. +/// ## 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. /// -/// ## 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. +/// #show figure: it => align(center)[ +/// #it.caption | +/// #emph[ +/// #it.supplement +/// #it.counter.display(it.numbering) +/// ] +/// #v(10pt, weak: true) +/// #it.body +/// ] /// /// #figure( /// image("molecular.jpg", width: 80%), /// caption: [ /// The molecular testing pipeline. /// ], -/// ) +/// ) /// ``` /// /// Display: Figure @@ -104,224 +80,139 @@ pub struct FigureElem { /// The figure's caption. pub caption: Option, - /// 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>, + pub kind: Smart, - /// 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, /// 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, - /// 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, - /// 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, } -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 { - let potential_elems = self.body().query(if require_supplement { - Selector::All(eco_vec![ - Selector::can::(), - Selector::can::() - ]) - } else { - Selector::can::() - }); - - potential_elems.into_iter().max_by_key(|elem| { - elem.with::() - .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 { - 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, - ) -> SourceResult> { - 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 { - 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)); + // 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, + }; - // We get the numbering or `None`. - let numbering = self.numbering(styles); - let supplement = self.supplement(styles); + let content = match &kind { + FigureKind::Elem(func) => self.find_of_elem(*func), + FigureKind::Name(_) => None, + } + .unwrap_or_else(|| self.body()); - // 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" - ), + // 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::() + .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(ContentParam::Elem(ty)) => self.find_elem(ty), - Smart::Custom(ContentParam::Name(_)) => None, + Smart::Custom(supp) => supp.resolve(vt, [content.into()])?, }; - if self.kind(styles).is_auto() { - if let Some(content) = &content { - self.push_kind(Smart::Custom(ContentParam::Elem(content.func()))); - } - } + // Construct the figure's counter. + let counter = Counter::new(CounterKey::Selector(Selector::Elem( + Self::func(), + Some(dict! { + "kind" => kind.clone(), + }), + ))); - // 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( - Self::func(), - Some(dict! { - "kind" => content, - }), - ))) - }); - - // 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::()).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 { + self.body() + .query(Selector::can::()) + .into_iter() + .max_by_key(|elem| elem.with::().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 { + 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, + ) -> SourceResult> { + 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 { + 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; } diff --git a/library/src/meta/heading.rs b/library/src/meta/heading.rs index e4339dc8d..dbf585736 100644 --- a/library/src/meta/heading.rs +++ b/library/src/meta/heading.rs @@ -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, ) -> SourceResult { - // 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> { - // 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; }; diff --git a/library/src/meta/outline.rs b/library/src/meta/outline.rs index f24df6539..21305b092 100644 --- a/library/src/meta/outline.rs +++ b/library/src/meta/outline.rs @@ -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>, - /// The maximum depth up to which headings are included in the outline. When - /// this argument is `{none}`, all headings are included. - pub depth: Option, - /// 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, + + /// 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)?; diff --git a/library/src/meta/reference.rs b/library/src/meta/reference.rs index 81fd88b9a..03c02eb48 100644 --- a/library/src/meta/reference.rs +++ b/library/src/meta/reference.rs @@ -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,7 +113,15 @@ impl Show for RefElem { let elem = elem.at(self.span())?; if !elem.can::() { - bail!(self.span(), "cannot reference {}", elem.func().name()); + if elem.can::() { + bail!( + self.span(), + "cannot reference {} directly, try putting it into a figure", + elem.func().name() + ); + } else { + bail!(self.span(), "cannot reference {}", elem.func().name()); + } } let supplement = match self.supplement(styles) { diff --git a/library/src/text/raw.rs b/library/src/text/raw.rs index 2324eb212..594fd1c68 100644 --- a/library/src/text/raw.rs +++ b/library/src/text/raw.rs @@ -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(()) } } diff --git a/src/model/content.rs b/src/model/content.rs index db8677157..da3b8b750 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -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 { - 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 { - 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 { + 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) { + 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) { + 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) => {