mirror of
https://github.com/typst/typst
synced 2025-05-19 11:35:27 +08:00
Improved figure numbering, labelling and referencing (#491)
This commit is contained in:
parent
5b0297464e
commit
f347ed4314
@ -1,5 +1,5 @@
|
|||||||
use crate::layout::{AlignElem, GridLayouter, TrackSizings};
|
use crate::layout::{AlignElem, GridLayouter, TrackSizings};
|
||||||
use crate::meta::LocalName;
|
use crate::meta::{Figurable, LocalName};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// A table of items.
|
/// A table of items.
|
||||||
@ -32,7 +32,7 @@ use crate::prelude::*;
|
|||||||
///
|
///
|
||||||
/// Display: Table
|
/// Display: Table
|
||||||
/// Category: layout
|
/// Category: layout
|
||||||
#[element(Layout, LocalName)]
|
#[element(Layout, LocalName, Figurable)]
|
||||||
pub struct TableElem {
|
pub struct TableElem {
|
||||||
/// Defines the column sizes. See the [grid documentation]($func/grid) for
|
/// Defines the column sizes. See the [grid documentation]($func/grid) for
|
||||||
/// more information on track sizing.
|
/// more information on track sizing.
|
||||||
@ -293,3 +293,9 @@ impl LocalName for TableElem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Figurable for TableElem {
|
||||||
|
fn priority(&self, _styles: StyleChain) -> isize {
|
||||||
|
-1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -39,6 +39,7 @@ use self::fragment::*;
|
|||||||
use self::row::*;
|
use self::row::*;
|
||||||
use self::spacing::*;
|
use self::spacing::*;
|
||||||
use crate::layout::{HElem, ParElem, Spacing};
|
use crate::layout::{HElem, ParElem, Spacing};
|
||||||
|
use crate::meta::Refable;
|
||||||
use crate::meta::{Count, Counter, CounterUpdate, LocalName, Numbering};
|
use crate::meta::{Count, Counter, CounterUpdate, LocalName, Numbering};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::text::{
|
use crate::text::{
|
||||||
@ -134,7 +135,9 @@ pub fn module() -> Module {
|
|||||||
///
|
///
|
||||||
/// Display: Equation
|
/// Display: Equation
|
||||||
/// Category: math
|
/// Category: math
|
||||||
#[element(Locatable, Synthesize, Show, Finalize, Layout, LayoutMath, Count, LocalName)]
|
#[element(
|
||||||
|
Locatable, Synthesize, Show, Finalize, Layout, LayoutMath, Count, LocalName, Refable
|
||||||
|
)]
|
||||||
pub struct EquationElem {
|
pub struct EquationElem {
|
||||||
/// Whether the equation is displayed as a separate block.
|
/// Whether the equation is displayed as a separate block.
|
||||||
#[default(false)]
|
#[default(false)]
|
||||||
@ -159,9 +162,11 @@ pub struct EquationElem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Synthesize for EquationElem {
|
impl Synthesize for EquationElem {
|
||||||
fn synthesize(&mut self, styles: StyleChain) {
|
fn synthesize(&mut self, _vt: &mut Vt, styles: StyleChain) -> SourceResult<()> {
|
||||||
self.push_block(self.block(styles));
|
self.push_block(self.block(styles));
|
||||||
self.push_numbering(self.numbering(styles));
|
self.push_numbering(self.numbering(styles));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,6 +283,45 @@ impl LocalName for EquationElem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Refable for EquationElem {
|
||||||
|
fn reference(
|
||||||
|
&self,
|
||||||
|
vt: &mut Vt,
|
||||||
|
styles: StyleChain,
|
||||||
|
supplement: Option<Content>,
|
||||||
|
) -> SourceResult<Content> {
|
||||||
|
// first we create the supplement of the heading
|
||||||
|
let mut supplement = supplement.unwrap_or_else(|| {
|
||||||
|
TextElem::packed(self.local_name(TextElem::lang_in(styles)))
|
||||||
|
});
|
||||||
|
|
||||||
|
// we append a space if the supplement is not empty
|
||||||
|
if !supplement.is_empty() {
|
||||||
|
supplement += TextElem::packed('\u{a0}')
|
||||||
|
};
|
||||||
|
|
||||||
|
// we check for a numbering
|
||||||
|
let Some(numbering) = self.numbering(styles) else {
|
||||||
|
bail!(self.span(), "only numbered equations can be referenced");
|
||||||
|
};
|
||||||
|
|
||||||
|
// we get the counter and display it
|
||||||
|
let numbers = Counter::of(Self::func())
|
||||||
|
.at(vt, self.0.location().expect("missing location"))?
|
||||||
|
.display(vt, &numbering.trimmed())?;
|
||||||
|
|
||||||
|
Ok(supplement + numbers)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn numbering(&self, styles: StyleChain) -> Option<Numbering> {
|
||||||
|
self.numbering(styles)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn counter(&self, _styles: StyleChain) -> Counter {
|
||||||
|
Counter::of(Self::func())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait LayoutMath {
|
pub trait LayoutMath {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()>;
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()>;
|
||||||
}
|
}
|
||||||
|
@ -133,8 +133,10 @@ impl BibliographyElem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Synthesize for BibliographyElem {
|
impl Synthesize for BibliographyElem {
|
||||||
fn synthesize(&mut self, styles: StyleChain) {
|
fn synthesize(&mut self, _vt: &mut Vt, styles: StyleChain) -> SourceResult<()> {
|
||||||
self.push_style(self.style(styles));
|
self.push_style(self.style(styles));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,10 +318,12 @@ pub struct CiteElem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Synthesize for CiteElem {
|
impl Synthesize for CiteElem {
|
||||||
fn synthesize(&mut self, styles: StyleChain) {
|
fn synthesize(&mut self, _vt: &mut Vt, styles: StyleChain) -> SourceResult<()> {
|
||||||
self.push_supplement(self.supplement(styles));
|
self.push_supplement(self.supplement(styles));
|
||||||
self.push_brackets(self.brackets(styles));
|
self.push_brackets(self.brackets(styles));
|
||||||
self.push_style(self.style(styles));
|
self.push_style(self.style(styles));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -495,6 +495,9 @@ cast_from_value! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Self::Selector(Selector::Elem(element, None))
|
Self::Selector(Selector::Elem(element, None))
|
||||||
|
},
|
||||||
|
selector: Selector => {
|
||||||
|
Self::Selector(selector)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,84 @@
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use super::{Count, Counter, CounterUpdate, LocalName, Numbering, NumberingPattern};
|
use ecow::eco_vec;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
Count, Counter, CounterKey, CounterUpdate, LocalName, Numbering, NumberingPattern,
|
||||||
|
};
|
||||||
use crate::layout::{BlockElem, VElem};
|
use crate::layout::{BlockElem, VElem};
|
||||||
|
use crate::meta::{Refable, Supplement};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::text::TextElem;
|
use crate::text::TextElem;
|
||||||
|
|
||||||
/// A figure with an optional caption.
|
/// 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.
|
||||||
|
///
|
||||||
|
/// ```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 ],
|
||||||
|
/// )
|
||||||
|
/// ]
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// ## 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)
|
||||||
|
/// ]
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// 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
|
||||||
/// ```example
|
/// ```example
|
||||||
/// = Pipeline
|
/// = Pipeline
|
||||||
@ -23,7 +95,7 @@ use crate::text::TextElem;
|
|||||||
///
|
///
|
||||||
/// Display: Figure
|
/// Display: Figure
|
||||||
/// Category: meta
|
/// Category: meta
|
||||||
#[element(Locatable, Synthesize, Count, Show, LocalName)]
|
#[element(Locatable, Synthesize, Count, Show, Refable)]
|
||||||
pub struct FigureElem {
|
pub struct FigureElem {
|
||||||
/// The content of the figure. Often, an [image]($func/image).
|
/// The content of the figure. Often, an [image]($func/image).
|
||||||
#[required]
|
#[required]
|
||||||
@ -32,41 +104,241 @@ pub struct FigureElem {
|
|||||||
/// The figure's caption.
|
/// The figure's caption.
|
||||||
pub caption: Option<Content>,
|
pub caption: Option<Content>,
|
||||||
|
|
||||||
|
/// The figure's supplement, if not provided, the figure will attempt to
|
||||||
|
/// automatically detect the counter from the content.
|
||||||
|
///
|
||||||
|
/// ## 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.
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// #figure(caption: "My custom figure", kind: "foo", supplement: "Bar")[
|
||||||
|
/// #block[ The inside of my custom figure! ]
|
||||||
|
/// ]
|
||||||
|
/// ```
|
||||||
|
#[default(Smart::Auto)]
|
||||||
|
pub supplement: Smart<Option<Supplement>>,
|
||||||
|
|
||||||
|
/// Whether the figure should appear in the list of figures/tables/code.
|
||||||
|
/// Defaults to `true`.
|
||||||
|
#[default(true)]
|
||||||
|
pub outlined: bool,
|
||||||
|
|
||||||
/// How to number the figure. Accepts a
|
/// How to number the figure. Accepts a
|
||||||
/// [numbering pattern or function]($func/numbering).
|
/// [numbering pattern or function]($func/numbering).
|
||||||
#[default(Some(NumberingPattern::from_str("1").unwrap().into()))]
|
#[default(Some(NumberingPattern::from_str("1").unwrap().into()))]
|
||||||
pub numbering: Option<Numbering>,
|
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.
|
/// The vertical gap between the body and caption.
|
||||||
#[default(Em::new(0.65).into())]
|
#[default(Em::new(0.65).into())]
|
||||||
pub gap: Length,
|
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.
|
||||||
|
#[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 {
|
impl Synthesize for FigureElem {
|
||||||
fn synthesize(&mut self, styles: StyleChain) {
|
fn synthesize(&mut self, vt: &mut Vt, styles: StyleChain) -> SourceResult<()> {
|
||||||
self.push_numbering(self.numbering(styles));
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.kind(styles).is_auto() {
|
||||||
|
if let Some(content) = &content {
|
||||||
|
self.push_kind(Smart::Custom(ContentParam::Elem(content.func())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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::<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);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for FigureElem {
|
impl Show for FigureElem {
|
||||||
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||||
|
// We build the body of the figure.
|
||||||
let mut realized = self.body();
|
let mut realized = self.body();
|
||||||
|
|
||||||
if let Some(mut caption) = self.caption(styles) {
|
// We build the caption, if any.
|
||||||
if let Some(numbering) = self.numbering(styles) {
|
if self.caption(styles).is_some() {
|
||||||
let name = self.local_name(TextElem::lang_in(styles));
|
|
||||||
caption = TextElem::packed(eco_format!("{name}\u{a0}"))
|
|
||||||
+ Counter::of(Self::func())
|
|
||||||
.display(Some(numbering), false)
|
|
||||||
.spanned(self.span())
|
|
||||||
+ TextElem::packed(": ")
|
|
||||||
+ caption;
|
|
||||||
}
|
|
||||||
|
|
||||||
realized += VElem::weak(self.gap(styles).into()).pack();
|
realized += VElem::weak(self.gap(styles).into()).pack();
|
||||||
realized += caption;
|
realized += self.show_caption(vt, styles)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We wrap the contents in a block.
|
||||||
Ok(BlockElem::new()
|
Ok(BlockElem::new()
|
||||||
.with_body(Some(realized))
|
.with_body(Some(realized))
|
||||||
.with_breakable(false)
|
.with_breakable(false)
|
||||||
@ -77,21 +349,76 @@ impl Show for FigureElem {
|
|||||||
|
|
||||||
impl Count for FigureElem {
|
impl Count for FigureElem {
|
||||||
fn update(&self) -> Option<CounterUpdate> {
|
fn update(&self) -> Option<CounterUpdate> {
|
||||||
|
// If the figure is numbered, step the counter by one.
|
||||||
|
// This steps the `counter(figure)` which is global to all numbered figures.
|
||||||
self.numbering(StyleChain::default())
|
self.numbering(StyleChain::default())
|
||||||
.is_some()
|
.is_some()
|
||||||
.then(|| CounterUpdate::Step(NonZeroUsize::ONE))
|
.then(|| CounterUpdate::Step(NonZeroUsize::ONE))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LocalName for FigureElem {
|
impl Refable for FigureElem {
|
||||||
fn local_name(&self, lang: Lang) -> &'static str {
|
fn reference(
|
||||||
match lang {
|
&self,
|
||||||
Lang::CHINESE => "图",
|
vt: &mut Vt,
|
||||||
Lang::GERMAN => "Abbildung",
|
styles: StyleChain,
|
||||||
Lang::ITALIAN => "Figura",
|
supplement: Option<Content>,
|
||||||
Lang::PORTUGUESE => "Figura",
|
) -> SourceResult<Content> {
|
||||||
Lang::RUSSIAN => "Рисунок",
|
// If the figure is not numbered, we cannot reference it.
|
||||||
Lang::ENGLISH | Lang::FRENCH | _ => "Figure",
|
// Otherwise we build the supplement and numbering scheme.
|
||||||
|
let Some(desc) = self.show_supplement_and_numbering(vt, styles, supplement)? else {
|
||||||
|
bail!(self.span(), "cannot reference unnumbered figure")
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(desc)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn outline(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Option<Content>> {
|
||||||
|
// If the figure is not outlined, it is not referenced.
|
||||||
|
if !self.outlined(styles) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.show_caption(vt, styles).map(Some)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn numbering(&self, styles: StyleChain) -> Option<Numbering> {
|
||||||
|
self.numbering(styles)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn counter(&self, _styles: 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),
|
||||||
|
|
||||||
|
/// The content is a name.
|
||||||
|
Name(EcoString),
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
ContentParam,
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
||||||
|
/// The priority of this element.
|
||||||
|
fn priority(&self, styles: StyleChain) -> isize;
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use typst::font::FontWeight;
|
use typst::font::FontWeight;
|
||||||
|
|
||||||
use super::{Counter, CounterUpdate, LocalName, Numbering};
|
use super::{Counter, CounterUpdate, LocalName, Numbering, Refable};
|
||||||
use crate::layout::{BlockElem, HElem, VElem};
|
use crate::layout::{BlockElem, HElem, VElem};
|
||||||
use crate::meta::Count;
|
use crate::meta::{Count, Supplement};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::text::{TextElem, TextSize};
|
use crate::text::{SpaceElem, TextElem, TextSize};
|
||||||
|
|
||||||
/// A section heading.
|
/// A section heading.
|
||||||
///
|
///
|
||||||
@ -41,7 +41,7 @@ use crate::text::{TextElem, TextSize};
|
|||||||
///
|
///
|
||||||
/// Display: Heading
|
/// Display: Heading
|
||||||
/// Category: meta
|
/// Category: meta
|
||||||
#[element(Locatable, Synthesize, Count, Show, Finalize, LocalName)]
|
#[element(Locatable, Synthesize, Count, Show, Finalize, LocalName, Refable)]
|
||||||
pub struct HeadingElem {
|
pub struct HeadingElem {
|
||||||
/// The logical nesting depth of the heading, starting from one.
|
/// The logical nesting depth of the heading, starting from one.
|
||||||
#[default(NonZeroUsize::ONE)]
|
#[default(NonZeroUsize::ONE)]
|
||||||
@ -74,16 +74,35 @@ pub struct HeadingElem {
|
|||||||
#[default(true)]
|
#[default(true)]
|
||||||
pub outlined: bool,
|
pub outlined: bool,
|
||||||
|
|
||||||
|
/// A supplement for the heading.
|
||||||
|
///
|
||||||
|
/// For references to headings, this is added before the
|
||||||
|
/// referenced number.
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// #set heading(numbering: "1.", supplement: "Chapter")
|
||||||
|
///
|
||||||
|
/// = Introduction <intro>
|
||||||
|
/// In @intro, we see how to turn
|
||||||
|
/// Sections into Chapters. And
|
||||||
|
/// in @intro[Part], it is done
|
||||||
|
/// manually.
|
||||||
|
/// ```
|
||||||
|
#[default(Smart::Auto)]
|
||||||
|
pub supplement: Smart<Option<Supplement>>,
|
||||||
|
|
||||||
/// The heading's title.
|
/// The heading's title.
|
||||||
#[required]
|
#[required]
|
||||||
pub body: Content,
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Synthesize for HeadingElem {
|
impl Synthesize for HeadingElem {
|
||||||
fn synthesize(&mut self, styles: StyleChain) {
|
fn synthesize(&mut self, _vt: &mut Vt, styles: StyleChain) -> SourceResult<()> {
|
||||||
self.push_level(self.level(styles));
|
self.push_level(self.level(styles));
|
||||||
self.push_numbering(self.numbering(styles));
|
self.push_numbering(self.numbering(styles));
|
||||||
self.push_outlined(self.outlined(styles));
|
self.push_outlined(self.outlined(styles));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,6 +156,77 @@ cast_from_value! {
|
|||||||
v: Content => v.to::<Self>().ok_or("expected heading")?.clone(),
|
v: Content => v.to::<Self>().ok_or("expected heading")?.clone(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Refable for HeadingElem {
|
||||||
|
fn reference(
|
||||||
|
&self,
|
||||||
|
vt: &mut Vt,
|
||||||
|
styles: StyleChain,
|
||||||
|
supplement: Option<Content>,
|
||||||
|
) -> SourceResult<Content> {
|
||||||
|
// first we create the supplement of the heading
|
||||||
|
let mut supplement = if let Some(supplement) = supplement {
|
||||||
|
supplement
|
||||||
|
} else {
|
||||||
|
match self.supplement(styles) {
|
||||||
|
Smart::Auto => {
|
||||||
|
TextElem::packed(self.local_name(TextElem::lang_in(styles)))
|
||||||
|
}
|
||||||
|
Smart::Custom(None) => Content::empty(),
|
||||||
|
Smart::Custom(Some(supplement)) => {
|
||||||
|
supplement.resolve(vt, std::iter::once(Value::from(self.clone())))?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// we append a space if the supplement is not empty
|
||||||
|
if !supplement.is_empty() {
|
||||||
|
supplement += TextElem::packed('\u{a0}')
|
||||||
|
};
|
||||||
|
|
||||||
|
// we check for a numbering
|
||||||
|
let Some(numbering) = self.numbering(styles) else {
|
||||||
|
bail!(self.span(), "only numbered headings can be referenced");
|
||||||
|
};
|
||||||
|
|
||||||
|
// we get the counter and display it
|
||||||
|
let numbers = Counter::of(Self::func())
|
||||||
|
.at(vt, self.0.location().expect("missing location"))?
|
||||||
|
.display(vt, &numbering.trimmed())?;
|
||||||
|
|
||||||
|
Ok(supplement + numbers)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn level(&self, styles: StyleChain) -> usize {
|
||||||
|
self.level(styles).get()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn numbering(&self, styles: StyleChain) -> Option<Numbering> {
|
||||||
|
self.numbering(styles)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn counter(&self, _styles: StyleChain) -> Counter {
|
||||||
|
Counter::of(Self::func())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn outline(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Option<Content>> {
|
||||||
|
// we check if the heading is outlined
|
||||||
|
if !self.outlined(styles) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We 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"))?
|
||||||
|
.display(vt, &numbering)?;
|
||||||
|
start = numbers + SpaceElem::new().pack() + start;
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Some(start))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl LocalName for HeadingElem {
|
impl LocalName for HeadingElem {
|
||||||
fn local_name(&self, lang: Lang) -> &'static str {
|
fn local_name(&self, lang: Lang) -> &'static str {
|
||||||
match lang {
|
match lang {
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
use super::{Counter, CounterKey, HeadingElem, LocalName};
|
use super::{Counter, CounterKey, HeadingElem, LocalName, Refable};
|
||||||
use crate::layout::{BoxElem, HElem, HideElem, ParbreakElem, RepeatElem};
|
use crate::layout::{BoxElem, HElem, HideElem, ParbreakElem, RepeatElem};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::text::{LinebreakElem, SpaceElem, TextElem};
|
use crate::text::{LinebreakElem, SpaceElem, TextElem};
|
||||||
|
|
||||||
/// A section outline / table of contents.
|
/// A section outline / table of contents / table of figures / table of tables / etc.
|
||||||
///
|
///
|
||||||
/// This function generates a list of all headings in the document, up to a
|
/// This function generates a list of all headings in the document, up to a
|
||||||
/// given depth. The [heading]($func/heading) numbering will be reproduced
|
/// given depth. The [heading]($func/heading) numbering will be reproduced
|
||||||
/// within the outline.
|
/// 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`.
|
||||||
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```example
|
/// ```example
|
||||||
/// #outline()
|
/// #outline()
|
||||||
@ -20,6 +24,15 @@ use crate::text::{LinebreakElem, SpaceElem, TextElem};
|
|||||||
/// #lorem(10)
|
/// #lorem(10)
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
|
/// ## Example: List of figures
|
||||||
|
/// ```example
|
||||||
|
/// #outline(target: figure.where(kind: image), title: "Table of Figures")
|
||||||
|
///
|
||||||
|
/// #figure(caption: "A nice figure!")[
|
||||||
|
/// #image("/tiger.jpg")
|
||||||
|
/// ]
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
/// Display: Outline
|
/// Display: Outline
|
||||||
/// Category: meta
|
/// Category: meta
|
||||||
#[element(Show, LocalName)]
|
#[element(Show, LocalName)]
|
||||||
@ -37,6 +50,10 @@ pub struct OutlineElem {
|
|||||||
/// this argument is `{none}`, all headings are included.
|
/// this argument is `{none}`, all headings are included.
|
||||||
pub depth: Option<NonZeroUsize>,
|
pub depth: Option<NonZeroUsize>,
|
||||||
|
|
||||||
|
/// The type of element to include in the outline.
|
||||||
|
#[default(Selector::Elem(HeadingElem::func(), Some(dict! { "outlined" => true })))]
|
||||||
|
pub target: Selector,
|
||||||
|
|
||||||
/// Whether to indent the subheadings to align the start of their numbering
|
/// Whether to indent the subheadings to align the start of their numbering
|
||||||
/// with the title of their parents. This will only have an effect if a
|
/// with the title of their parents. This will only have an effect if a
|
||||||
/// [heading numbering]($func/heading.numbering) is set.
|
/// [heading numbering]($func/heading.numbering) is set.
|
||||||
@ -72,6 +89,7 @@ pub struct OutlineElem {
|
|||||||
impl Show for OutlineElem {
|
impl Show for OutlineElem {
|
||||||
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||||
let mut seq = vec![ParbreakElem::new().pack()];
|
let mut seq = vec![ParbreakElem::new().pack()];
|
||||||
|
// Build the outline title.
|
||||||
if let Some(title) = self.title(styles) {
|
if let Some(title) = self.title(styles) {
|
||||||
let title = title.unwrap_or_else(|| {
|
let title = title.unwrap_or_else(|| {
|
||||||
TextElem::packed(self.local_name(TextElem::lang_in(styles)))
|
TextElem::packed(self.local_name(TextElem::lang_in(styles)))
|
||||||
@ -88,30 +106,36 @@ impl Show for OutlineElem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let indent = self.indent(styles);
|
let indent = self.indent(styles);
|
||||||
let depth = self.depth(styles);
|
let depth = self.depth(styles).map_or(usize::MAX, NonZeroUsize::get);
|
||||||
|
|
||||||
let mut ancestors: Vec<&HeadingElem> = vec![];
|
let mut ancestors: Vec<&Content> = vec![];
|
||||||
let elems = vt.introspector.query(Selector::Elem(
|
let elems = vt.introspector.query(self.target(styles));
|
||||||
HeadingElem::func(),
|
|
||||||
Some(dict! { "outlined" => true }),
|
|
||||||
));
|
|
||||||
|
|
||||||
for elem in &elems {
|
for elem in &elems {
|
||||||
let heading = elem.to::<HeadingElem>().unwrap();
|
let Some(refable) = elem.with::<dyn Refable>() else {
|
||||||
let location = heading.0.location().unwrap();
|
bail!(elem.span(), "outlined elements must be referenceable");
|
||||||
if !heading.outlined(StyleChain::default()) {
|
};
|
||||||
|
|
||||||
|
let location = elem.location().expect("missing location");
|
||||||
|
|
||||||
|
if depth < refable.level(styles) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(depth) = depth {
|
let Some(outline) = refable.outline(vt, styles)? else {
|
||||||
if depth < heading.level(StyleChain::default()) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
while ancestors.last().map_or(false, |last| {
|
// Deals with the ancestors of the current element.
|
||||||
last.level(StyleChain::default()) >= heading.level(StyleChain::default())
|
// This is only applicable for elements with a hierarchy/level.
|
||||||
}) {
|
while ancestors
|
||||||
|
.last()
|
||||||
|
.and_then(|ancestor| ancestor.with::<dyn Refable>())
|
||||||
|
.map_or(false, |last| {
|
||||||
|
last.level(StyleChain::default())
|
||||||
|
>= refable.level(StyleChain::default())
|
||||||
|
})
|
||||||
|
{
|
||||||
ancestors.pop();
|
ancestors.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,10 +143,16 @@ impl Show for OutlineElem {
|
|||||||
if indent {
|
if indent {
|
||||||
let mut hidden = Content::empty();
|
let mut hidden = Content::empty();
|
||||||
for ancestor in &ancestors {
|
for ancestor in &ancestors {
|
||||||
if let Some(numbering) = ancestor.numbering(StyleChain::default()) {
|
let ancestor_refable = ancestor.with::<dyn Refable>().unwrap();
|
||||||
let numbers = Counter::of(HeadingElem::func())
|
|
||||||
.at(vt, ancestor.0.location().unwrap())?
|
if let Some(numbering) =
|
||||||
|
ancestor_refable.numbering(StyleChain::default())
|
||||||
|
{
|
||||||
|
let numbers = ancestor_refable
|
||||||
|
.counter(styles)
|
||||||
|
.at(vt, ancestor.location().unwrap())?
|
||||||
.display(vt, &numbering)?;
|
.display(vt, &numbering)?;
|
||||||
|
|
||||||
hidden += numbers + SpaceElem::new().pack();
|
hidden += numbers + SpaceElem::new().pack();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -133,17 +163,8 @@ impl Show for OutlineElem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format the numbering.
|
// Add the outline of the element.
|
||||||
let mut start = heading.body();
|
seq.push(outline.linked(Destination::Location(location)));
|
||||||
if let Some(numbering) = heading.numbering(StyleChain::default()) {
|
|
||||||
let numbers = Counter::of(HeadingElem::func())
|
|
||||||
.at(vt, location)?
|
|
||||||
.display(vt, &numbering)?;
|
|
||||||
start = numbers + SpaceElem::new().pack() + start;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add the numbering and section name.
|
|
||||||
seq.push(start.linked(Destination::Location(location)));
|
|
||||||
|
|
||||||
// Add filler symbols between the section name and page number.
|
// Add filler symbols between the section name and page number.
|
||||||
if let Some(filler) = self.fill(styles) {
|
if let Some(filler) = self.fill(styles) {
|
||||||
@ -167,7 +188,8 @@ impl Show for OutlineElem {
|
|||||||
let end = TextElem::packed(eco_format!("{page}"));
|
let end = TextElem::packed(eco_format!("{page}"));
|
||||||
seq.push(end.linked(Destination::Location(location)));
|
seq.push(end.linked(Destination::Location(location)));
|
||||||
seq.push(LinebreakElem::new().pack());
|
seq.push(LinebreakElem::new().pack());
|
||||||
ancestors.push(heading);
|
|
||||||
|
ancestors.push(elem);
|
||||||
}
|
}
|
||||||
|
|
||||||
seq.push(ParbreakElem::new().pack());
|
seq.push(ParbreakElem::new().pack());
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use super::{BibliographyElem, CiteElem, Counter, LocalName, Numbering};
|
use super::{BibliographyElem, CiteElem, Counter, Numbering};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::text::TextElem;
|
|
||||||
|
|
||||||
/// A reference to a label or bibliography.
|
/// A reference to a label or bibliography.
|
||||||
///
|
///
|
||||||
@ -83,9 +82,10 @@ pub struct RefElem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Synthesize for RefElem {
|
impl Synthesize for RefElem {
|
||||||
fn synthesize(&mut self, styles: StyleChain) {
|
fn synthesize(&mut self, vt: &mut Vt, styles: StyleChain) -> SourceResult<()> {
|
||||||
let citation = self.to_citation(styles);
|
let citation = self.to_citation(vt, styles)?;
|
||||||
self.push_citation(Some(citation));
|
self.push_citation(Some(citation));
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,55 +103,42 @@ impl Show for RefElem {
|
|||||||
bail!(self.span(), "label occurs in the document and its bibliography");
|
bail!(self.span(), "label occurs in the document and its bibliography");
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(self.to_citation(styles).pack());
|
return Ok(self.to_citation(vt, styles)?.pack());
|
||||||
}
|
}
|
||||||
|
|
||||||
let elem = elem.at(self.span())?;
|
let elem = elem.at(self.span())?;
|
||||||
if !elem.can::<dyn Locatable>() {
|
if !elem.can::<dyn Refable>() {
|
||||||
bail!(self.span(), "cannot reference {}", elem.func().name());
|
bail!(self.span(), "cannot reference {}", elem.func().name());
|
||||||
}
|
}
|
||||||
|
|
||||||
let supplement = self.supplement(styles);
|
let supplement = match self.supplement(styles) {
|
||||||
let mut supplement = match supplement {
|
Smart::Auto | Smart::Custom(None) => None,
|
||||||
Smart::Auto => elem
|
Smart::Custom(Some(supplement)) => {
|
||||||
.with::<dyn LocalName>()
|
Some(supplement.resolve(vt, [elem.clone().into()])?)
|
||||||
.map(|elem| elem.local_name(TextElem::lang_in(styles)))
|
|
||||||
.map(TextElem::packed)
|
|
||||||
.unwrap_or_default(),
|
|
||||||
Smart::Custom(None) => Content::empty(),
|
|
||||||
Smart::Custom(Some(Supplement::Content(content))) => content,
|
|
||||||
Smart::Custom(Some(Supplement::Func(func))) => {
|
|
||||||
func.call_vt(vt, [elem.clone().into()])?.display()
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if !supplement.is_empty() {
|
let reference = elem
|
||||||
supplement += TextElem::packed('\u{a0}');
|
.with::<dyn Refable>()
|
||||||
}
|
.expect("element should be refable")
|
||||||
|
.reference(vt, styles, supplement)?;
|
||||||
|
|
||||||
let Some(numbering) = elem.cast_field::<Numbering>("numbering") else {
|
Ok(reference.linked(Destination::Location(elem.location().unwrap())))
|
||||||
bail!(self.span(), "only numbered elements can be referenced");
|
|
||||||
};
|
|
||||||
|
|
||||||
let numbers = Counter::of(elem.func())
|
|
||||||
.at(vt, elem.location().unwrap())?
|
|
||||||
.display(vt, &numbering.trimmed())?;
|
|
||||||
|
|
||||||
Ok((supplement + numbers).linked(Destination::Location(elem.location().unwrap())))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RefElem {
|
impl RefElem {
|
||||||
/// Turn the reference into a citation.
|
/// Turn the reference into a citation.
|
||||||
pub fn to_citation(&self, styles: StyleChain) -> CiteElem {
|
pub fn to_citation(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<CiteElem> {
|
||||||
let mut elem = CiteElem::new(vec![self.target().0]);
|
let mut elem = CiteElem::new(vec![self.target().0]);
|
||||||
elem.0.set_location(self.0.location().unwrap());
|
elem.0.set_location(self.0.location().unwrap());
|
||||||
elem.synthesize(styles);
|
elem.synthesize(vt, styles)?;
|
||||||
elem.push_supplement(match self.supplement(styles) {
|
elem.push_supplement(match self.supplement(styles) {
|
||||||
Smart::Custom(Some(Supplement::Content(content))) => Some(content),
|
Smart::Custom(Some(Supplement::Content(content))) => Some(content),
|
||||||
_ => None,
|
_ => None,
|
||||||
});
|
});
|
||||||
elem
|
|
||||||
|
Ok(elem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,6 +148,29 @@ pub enum Supplement {
|
|||||||
Func(Func),
|
Func(Func),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Supplement {
|
||||||
|
/// Tries to resolve the supplement into its content.
|
||||||
|
pub fn resolve(
|
||||||
|
&self,
|
||||||
|
vt: &mut Vt,
|
||||||
|
args: impl IntoIterator<Item = Value>,
|
||||||
|
) -> SourceResult<Content> {
|
||||||
|
match self {
|
||||||
|
Supplement::Content(content) => Ok(content.clone()),
|
||||||
|
Supplement::Func(func) => func.call_vt(vt, args).map(|v| v.display()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to get the content of the supplement.
|
||||||
|
/// Returns `None` if the supplement is a function.
|
||||||
|
pub fn as_content(self) -> Option<Content> {
|
||||||
|
match self {
|
||||||
|
Supplement::Content(content) => Some(content),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cast_from_value! {
|
cast_from_value! {
|
||||||
Supplement,
|
Supplement,
|
||||||
v: Content => Self::Content(v),
|
v: Content => Self::Content(v),
|
||||||
@ -173,3 +183,43 @@ cast_to_value! {
|
|||||||
Supplement::Func(v) => v.into(),
|
Supplement::Func(v) => v.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Marks an element as being able to be referenced.
|
||||||
|
/// This is used to implement the `@ref` macro.
|
||||||
|
/// It is expected to build the [`Content`] that gets linked
|
||||||
|
/// by the [`RefElement`].
|
||||||
|
pub trait Refable {
|
||||||
|
/// Tries to build a reference content for this element.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// - `vt` - The virtual typesetter.
|
||||||
|
/// - `styles` - The styles of the reference.
|
||||||
|
/// - `location` - The location where the reference is being created.
|
||||||
|
/// - `supplement` - The supplement of the reference.
|
||||||
|
fn reference(
|
||||||
|
&self,
|
||||||
|
vt: &mut Vt,
|
||||||
|
styles: StyleChain,
|
||||||
|
supplement: Option<Content>,
|
||||||
|
) -> SourceResult<Content>;
|
||||||
|
|
||||||
|
/// Tries to build an outline element for this element.
|
||||||
|
/// If this returns `None`, the outline will not include this element.
|
||||||
|
/// By default this just calls [`Refable::reference`].
|
||||||
|
fn outline(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Option<Content>> {
|
||||||
|
self.reference(vt, styles, None).map(Some)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the level of this element.
|
||||||
|
/// This is used to determine the level of the outline.
|
||||||
|
/// By default this returns `0`.
|
||||||
|
fn level(&self, _styles: StyleChain) -> usize {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the numbering of this element.
|
||||||
|
fn numbering(&self, styles: StyleChain) -> Option<Numbering>;
|
||||||
|
|
||||||
|
/// Returns the counter of this element.
|
||||||
|
fn counter(&self, styles: StyleChain) -> Counter;
|
||||||
|
}
|
||||||
|
@ -6,6 +6,7 @@ use super::{
|
|||||||
FontFamily, FontList, Hyphenate, LinebreakElem, SmartQuoteElem, TextElem, TextSize,
|
FontFamily, FontList, Hyphenate, LinebreakElem, SmartQuoteElem, TextElem, TextSize,
|
||||||
};
|
};
|
||||||
use crate::layout::BlockElem;
|
use crate::layout::BlockElem;
|
||||||
|
use crate::meta::{Figurable, LocalName};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// Raw text with optional syntax highlighting.
|
/// Raw text with optional syntax highlighting.
|
||||||
@ -35,7 +36,7 @@ use crate::prelude::*;
|
|||||||
///
|
///
|
||||||
/// Display: Raw Text / Code
|
/// Display: Raw Text / Code
|
||||||
/// Category: text
|
/// Category: text
|
||||||
#[element(Synthesize, Show, Finalize)]
|
#[element(Synthesize, Show, Finalize, LocalName, Figurable)]
|
||||||
pub struct RawElem {
|
pub struct RawElem {
|
||||||
/// The raw text.
|
/// The raw text.
|
||||||
///
|
///
|
||||||
@ -121,8 +122,10 @@ impl RawElem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Synthesize for RawElem {
|
impl Synthesize for RawElem {
|
||||||
fn synthesize(&mut self, styles: StyleChain) {
|
fn synthesize(&mut self, _vt: &mut Vt, styles: StyleChain) -> SourceResult<()> {
|
||||||
self.push_lang(self.lang(styles));
|
self.push_lang(self.lang(styles));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,6 +200,24 @@ impl Finalize for RawElem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LocalName for RawElem {
|
||||||
|
fn local_name(&self, lang: Lang) -> &'static str {
|
||||||
|
match lang {
|
||||||
|
Lang::CHINESE => "代码",
|
||||||
|
Lang::ITALIAN => "Codice",
|
||||||
|
Lang::RUSSIAN => "код",
|
||||||
|
Lang::FRENCH => "Liste",
|
||||||
|
Lang::ENGLISH | Lang::GERMAN | _ => "Listing",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Figurable for RawElem {
|
||||||
|
fn priority(&self, _styles: StyleChain) -> isize {
|
||||||
|
500
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Highlight a syntax node in a theme by calling `f` with ranges and their
|
/// Highlight a syntax node in a theme by calling `f` with ranges and their
|
||||||
/// styles.
|
/// styles.
|
||||||
fn highlight_themed<F>(
|
fn highlight_themed<F>(
|
||||||
|
@ -3,7 +3,10 @@ use std::path::Path;
|
|||||||
|
|
||||||
use typst::image::{Image, ImageFormat, RasterFormat, VectorFormat};
|
use typst::image::{Image, ImageFormat, RasterFormat, VectorFormat};
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::{
|
||||||
|
meta::{Figurable, LocalName},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
|
||||||
/// A raster or vector graphic.
|
/// A raster or vector graphic.
|
||||||
///
|
///
|
||||||
@ -22,7 +25,7 @@ use crate::prelude::*;
|
|||||||
///
|
///
|
||||||
/// Display: Image
|
/// Display: Image
|
||||||
/// Category: visualize
|
/// Category: visualize
|
||||||
#[element(Layout)]
|
#[element(Layout, LocalName, Figurable)]
|
||||||
pub struct ImageElem {
|
pub struct ImageElem {
|
||||||
/// Path to an image file.
|
/// Path to an image file.
|
||||||
#[required]
|
#[required]
|
||||||
@ -112,6 +115,24 @@ impl Layout for ImageElem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LocalName for ImageElem {
|
||||||
|
fn local_name(&self, lang: Lang) -> &'static str {
|
||||||
|
match lang {
|
||||||
|
Lang::CHINESE => "图",
|
||||||
|
Lang::GERMAN => "Abbildung",
|
||||||
|
Lang::ITALIAN | Lang::PORTUGUESE => "Figura",
|
||||||
|
Lang::RUSSIAN => "Рисунок",
|
||||||
|
Lang::ENGLISH | Lang::FRENCH | _ => "Figure",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Figurable for ImageElem {
|
||||||
|
fn priority(&self, _styles: StyleChain) -> isize {
|
||||||
|
1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// How an image should adjust itself to a given area.
|
/// How an image should adjust itself to a given area.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
|
||||||
pub enum ImageFit {
|
pub enum ImageFit {
|
||||||
|
@ -20,6 +20,15 @@ impl<T> Smart<T> {
|
|||||||
matches!(self, Self::Custom(_))
|
matches!(self, Self::Custom(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a reference the contained custom value.
|
||||||
|
/// If the value is [`Smart::Auto`], `None` is returned.
|
||||||
|
pub fn as_custom(self) -> Option<T> {
|
||||||
|
match self {
|
||||||
|
Self::Auto => None,
|
||||||
|
Self::Custom(x) => Some(x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Map the contained custom value with `f`.
|
/// Map the contained custom value with `f`.
|
||||||
pub fn map<F, U>(self, f: F) -> Smart<U>
|
pub fn map<F, U>(self, f: F) -> Smart<U>
|
||||||
where
|
where
|
||||||
|
@ -7,7 +7,7 @@ use ecow::{eco_format, EcoString, EcoVec};
|
|||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
element, Behave, Behaviour, ElemFunc, Element, Fold, Guard, Label, Locatable,
|
element, Behave, Behaviour, ElemFunc, Element, Fold, Guard, Label, Locatable,
|
||||||
Location, Recipe, Style, Styles, Synthesize,
|
Location, Recipe, Selector, Style, Styles, Synthesize,
|
||||||
};
|
};
|
||||||
use crate::diag::{SourceResult, StrResult};
|
use crate::diag::{SourceResult, StrResult};
|
||||||
use crate::doc::Meta;
|
use crate::doc::Meta;
|
||||||
@ -105,6 +105,12 @@ impl Content {
|
|||||||
(self.func.0.vtable)(TypeId::of::<C>()).is_some()
|
(self.func.0.vtable)(TypeId::of::<C>()).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the contained element has the given capability.
|
||||||
|
/// Where the capability is given by a `TypeId`.
|
||||||
|
pub fn can_type_id(&self, type_id: TypeId) -> bool {
|
||||||
|
(self.func.0.vtable)(type_id).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
/// Cast to a trait object if the contained element has the given
|
/// Cast to a trait object if the contained element has the given
|
||||||
/// capability.
|
/// capability.
|
||||||
pub fn with<C>(&self) -> Option<&C>
|
pub fn with<C>(&self) -> Option<&C>
|
||||||
@ -347,6 +353,62 @@ impl Content {
|
|||||||
pub fn set_location(&mut self, location: Location) {
|
pub fn set_location(&mut self, location: Location) {
|
||||||
self.attrs.push(Attr::Location(location));
|
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> {
|
||||||
|
let mut results = Vec::new();
|
||||||
|
self.query_into(&selector, &mut results);
|
||||||
|
results
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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>) {
|
||||||
|
if selector.matches(self) {
|
||||||
|
results.push(self.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
for attr in &self.attrs {
|
||||||
|
match attr {
|
||||||
|
Attr::Child(child) => child.query_into(selector, results),
|
||||||
|
Attr::Value(value) => walk_value(&value, selector, results),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Walks a given value to find any content that matches the selector.
|
||||||
|
fn walk_value(value: &Value, selector: &Selector, results: &mut Vec<Content>) {
|
||||||
|
match value {
|
||||||
|
Value::Content(content) => content.query_into(selector, results),
|
||||||
|
Value::Array(array) => {
|
||||||
|
for value in array {
|
||||||
|
walk_value(value, selector, results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Content {
|
impl Debug for Content {
|
||||||
|
@ -63,6 +63,14 @@ impl ElemFunc {
|
|||||||
(self.0.construct)(vm, args)
|
(self.0.construct)(vm, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the contained element has the given capability.
|
||||||
|
pub fn can<C>(&self) -> bool
|
||||||
|
where
|
||||||
|
C: ?Sized + 'static,
|
||||||
|
{
|
||||||
|
(self.0.vtable)(TypeId::of::<C>()).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a selector for elements of this function.
|
/// Create a selector for elements of this function.
|
||||||
pub fn select(self) -> Selector {
|
pub fn select(self) -> Selector {
|
||||||
Selector::Elem(self, None)
|
Selector::Elem(self, None)
|
||||||
|
@ -42,7 +42,7 @@ pub fn realize(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(elem) = elem.with_mut::<dyn Synthesize>() {
|
if let Some(elem) = elem.with_mut::<dyn Synthesize>() {
|
||||||
elem.synthesize(styles);
|
elem.synthesize(vt, styles)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
elem.mark_prepared();
|
elem.mark_prepared();
|
||||||
@ -152,7 +152,7 @@ fn try_apply(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Not supported here.
|
// Not supported here.
|
||||||
Some(Selector::Any(_)) => Ok(None),
|
Some(Selector::Any(_) | Selector::All(_) | Selector::Can(_)) => Ok(None),
|
||||||
|
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
}
|
}
|
||||||
@ -165,7 +165,7 @@ pub trait Locatable {}
|
|||||||
/// rule.
|
/// rule.
|
||||||
pub trait Synthesize {
|
pub trait Synthesize {
|
||||||
/// Prepare the element for show rule application.
|
/// Prepare the element for show rule application.
|
||||||
fn synthesize(&mut self, styles: StyleChain);
|
fn synthesize(&mut self, vt: &mut Vt, styles: StyleChain) -> SourceResult<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The base recipe for an element.
|
/// The base recipe for an element.
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use std::any::{Any, TypeId};
|
||||||
use std::fmt::{self, Debug, Formatter, Write};
|
use std::fmt::{self, Debug, Formatter, Write};
|
||||||
use std::iter;
|
use std::iter;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
@ -260,8 +261,12 @@ pub enum Selector {
|
|||||||
Label(Label),
|
Label(Label),
|
||||||
/// Matches text elements through a regular expression.
|
/// Matches text elements through a regular expression.
|
||||||
Regex(Regex),
|
Regex(Regex),
|
||||||
|
/// Matches elements with a specific capability.
|
||||||
|
Can(TypeId),
|
||||||
/// Matches if any of the subselectors match.
|
/// Matches if any of the subselectors match.
|
||||||
Any(EcoVec<Self>),
|
Any(EcoVec<Self>),
|
||||||
|
/// Matches if all of the subselectors match.
|
||||||
|
All(EcoVec<Self>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Selector {
|
impl Selector {
|
||||||
@ -270,6 +275,11 @@ impl Selector {
|
|||||||
Self::Regex(Regex::new(®ex::escape(text)).unwrap())
|
Self::Regex(Regex::new(®ex::escape(text)).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Define a simple [`Selector::Can`] selector.
|
||||||
|
pub fn can<T: ?Sized + Any>() -> Self {
|
||||||
|
Self::Can(TypeId::of::<T>())
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether the selector matches for the target.
|
/// Whether the selector matches for the target.
|
||||||
pub fn matches(&self, target: &Content) -> bool {
|
pub fn matches(&self, target: &Content) -> bool {
|
||||||
match self {
|
match self {
|
||||||
@ -285,7 +295,9 @@ impl Selector {
|
|||||||
target.func() == item!(text_func)
|
target.func() == item!(text_func)
|
||||||
&& item!(text_str)(target).map_or(false, |text| regex.is_match(&text))
|
&& item!(text_str)(target).map_or(false, |text| regex.is_match(&text))
|
||||||
}
|
}
|
||||||
|
Self::Can(cap) => target.can_type_id(*cap),
|
||||||
Self::Any(selectors) => selectors.iter().any(|sel| sel.matches(target)),
|
Self::Any(selectors) => selectors.iter().any(|sel| sel.matches(target)),
|
||||||
|
Self::All(selectors) => selectors.iter().all(|sel| sel.matches(target)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -303,8 +315,9 @@ impl Debug for Selector {
|
|||||||
}
|
}
|
||||||
Self::Label(label) => label.fmt(f),
|
Self::Label(label) => label.fmt(f),
|
||||||
Self::Regex(regex) => regex.fmt(f),
|
Self::Regex(regex) => regex.fmt(f),
|
||||||
Self::Any(selectors) => {
|
Self::Can(cap) => cap.fmt(f),
|
||||||
f.write_str("any")?;
|
Self::Any(selectors) | Self::All(selectors) => {
|
||||||
|
f.write_str(if matches!(self, Self::Any(_)) { "any" } else { "all" })?;
|
||||||
let pieces: Vec<_> =
|
let pieces: Vec<_> =
|
||||||
selectors.iter().map(|sel| eco_format!("{sel:?}")).collect();
|
selectors.iter().map(|sel| eco_format!("{sel:?}")).collect();
|
||||||
f.write_str(&pretty_array_like(&pieces, false))
|
f.write_str(&pretty_array_like(&pieces, false))
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 101 KiB |
@ -45,8 +45,8 @@ At Beta, it was #locate(loc => {
|
|||||||
|
|
||||||
---
|
---
|
||||||
// Count figures.
|
// Count figures.
|
||||||
#figure(numbering: "A", caption: [Four 'A's])[_AAAA!_]
|
#figure(numbering: "A", caption: [Four 'A's], kind: image, supplement: "Figure")[_AAAA!_]
|
||||||
#figure(numbering: none, caption: [Four 'B's])[_BBBB!_]
|
#figure(numbering: none, caption: [Four 'B's], kind: image, supplement: "Figure")[_BBBB!_]
|
||||||
#figure(caption: [Four 'C's])[_CCCC!_]
|
#figure(caption: [Four 'C's], kind: image, supplement: "Figure")[_CCCC!_]
|
||||||
#counter(figure).update(n => n + 3)
|
#counter(figure.where(kind: image)).update(n => n + 3)
|
||||||
#figure(caption: [Four 'D's])[_DDDD!_]
|
#figure(caption: [Four 'D's], kind: image, supplement: "Figure")[_DDDD!_]
|
||||||
|
@ -22,3 +22,83 @@ We can clearly see that @fig-cylinder and
|
|||||||
table(columns: 3)[a][b][c][d][e][f],
|
table(columns: 3)[a][b][c][d][e][f],
|
||||||
caption: [The complex table.],
|
caption: [The complex table.],
|
||||||
) <tab-complex>
|
) <tab-complex>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
// Testing figures with and without caption
|
||||||
|
#figure(
|
||||||
|
table(
|
||||||
|
columns: 2,
|
||||||
|
[First cylinder],
|
||||||
|
image("/cylinder.svg", height: 3cm),
|
||||||
|
)
|
||||||
|
) <fig-image-in-table-no-caption>
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
table(
|
||||||
|
columns: 2,
|
||||||
|
[Second cylinder],
|
||||||
|
image("/cylinder.svg", height: 3cm),
|
||||||
|
),
|
||||||
|
caption: "A table containing images."
|
||||||
|
) <fig-image-in-table>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
// Testing show rules with figures with a simple theorem display
|
||||||
|
#show figure.where(kind: "theorem"): it => {
|
||||||
|
let name = none
|
||||||
|
if not it.caption == none {
|
||||||
|
name = [ #emph(it.caption)]
|
||||||
|
} else {
|
||||||
|
name = []
|
||||||
|
}
|
||||||
|
|
||||||
|
let title = none
|
||||||
|
if not it.numbering == none {
|
||||||
|
title = it.supplement
|
||||||
|
if not it.numbering == none {
|
||||||
|
title += " " + it.counter.display(it.numbering)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
title = strong(title)
|
||||||
|
pad(
|
||||||
|
top: 0em, bottom: 0em,
|
||||||
|
block(
|
||||||
|
fill: green.lighten(90%),
|
||||||
|
stroke: 1pt + green,
|
||||||
|
inset: 10pt,
|
||||||
|
width: 100%,
|
||||||
|
radius: 5pt,
|
||||||
|
breakable: false,
|
||||||
|
[#title#name#h(0.1em):#h(0.2em)#it.body#v(0.5em)]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
$a^2 + b^2 = c^2$,
|
||||||
|
supplement: "Theorem",
|
||||||
|
kind: "theorem",
|
||||||
|
caption: "Pythagoras' theorem.",
|
||||||
|
numbering: "1",
|
||||||
|
) <fig-formula>
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
$a^2 + b^2 = c^2$,
|
||||||
|
supplement: "Theorem",
|
||||||
|
kind: "theorem",
|
||||||
|
caption: "Another Pythagoras' theorem.",
|
||||||
|
numbering: none,
|
||||||
|
) <fig-formula>
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
caption: [Hello world in #emph[rust].],
|
||||||
|
)[
|
||||||
|
#show raw: set align(left)
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
println!("Hello, world!");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
]
|
@ -61,6 +61,8 @@
|
|||||||
|
|
||||||
#figure(
|
#figure(
|
||||||
rect[Just some stand-in text],
|
rect[Just some stand-in text],
|
||||||
|
kind: image,
|
||||||
|
supplement: "Figure",
|
||||||
caption: [Stand-in text],
|
caption: [Stand-in text],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user