From 1a390deaea040191cf0e5937bd8e1427b49db71b Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sat, 11 Mar 2023 20:01:56 +0100 Subject: Figures --- library/src/layout/spacing.rs | 6 +++ library/src/layout/table.rs | 12 ++++- library/src/lib.rs | 1 + library/src/meta/figure.rs | 120 +++++++++++++++++++++++++++++++++++++++++ library/src/meta/heading.rs | 35 ++++++------ library/src/meta/mod.rs | 2 + library/src/meta/outline.rs | 18 ++++--- library/src/meta/reference.rs | 28 +++++----- library/src/visualize/image.rs | 12 +++-- 9 files changed, 192 insertions(+), 42 deletions(-) create mode 100644 library/src/meta/figure.rs (limited to 'library') diff --git a/library/src/layout/spacing.rs b/library/src/layout/spacing.rs index dbdf0c11..e67fec03 100644 --- a/library/src/layout/spacing.rs +++ b/library/src/layout/spacing.rs @@ -191,6 +191,12 @@ impl From for Spacing { } } +impl From for Spacing { + fn from(length: Length) -> Self { + Self::Rel(length.into()) + } +} + impl From for Spacing { fn from(fr: Fr) -> Self { Self::Fr(fr) diff --git a/library/src/layout/table.rs b/library/src/layout/table.rs index fabe8c33..012e63ac 100644 --- a/library/src/layout/table.rs +++ b/library/src/layout/table.rs @@ -1,4 +1,5 @@ use crate::layout::{AlignNode, GridLayouter, TrackSizings}; +use crate::meta::LocalName; use crate::prelude::*; /// A table of items. @@ -31,7 +32,7 @@ use crate::prelude::*; /// /// Display: Table /// Category: layout -#[node(Layout)] +#[node(Layout, LocalName)] pub struct TableNode { /// Defines the column sizes. See the [grid documentation]($func/grid) for /// more information on track sizing. @@ -264,3 +265,12 @@ impl> From> for Value { } } } + +impl LocalName for TableNode { + fn local_name(&self, lang: Lang) -> &'static str { + match lang { + Lang::GERMAN => "Tabelle", + Lang::ENGLISH | _ => "Table", + } + } +} diff --git a/library/src/lib.rs b/library/src/lib.rs index 36afdb2b..35cae526 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -88,6 +88,7 @@ fn global(math: Module, calc: Module) -> Module { global.define("link", meta::LinkNode::id()); global.define("outline", meta::OutlineNode::id()); global.define("heading", meta::HeadingNode::id()); + global.define("figure", meta::FigureNode::id()); global.define("numbering", meta::numbering); // Symbols. diff --git a/library/src/meta/figure.rs b/library/src/meta/figure.rs new file mode 100644 index 00000000..6c43fa68 --- /dev/null +++ b/library/src/meta/figure.rs @@ -0,0 +1,120 @@ +use std::str::FromStr; + +use super::{LocalName, Numbering, NumberingPattern}; +use crate::layout::{BlockNode, TableNode, VNode}; +use crate::prelude::*; +use crate::text::TextNode; + +/// A figure with an optional caption. +/// +/// ## Example +/// ```example +/// = Pipeline +/// @fig-lab shows the central step of +/// our molecular testing pipeline. +/// +/// #figure( +/// image("molecular.jpg", width: 80%), +/// caption: [ +/// The molecular testing pipeline. +/// ], +/// ) +/// ``` +/// +/// Display: Figure +/// Category: meta +#[node(Synthesize, Show, LocalName)] +pub struct FigureNode { + /// The content of the figure. Often, an [image]($func/image). + #[required] + pub body: Content, + + /// The figure's caption. + pub caption: Option, + + /// How to number the figure. Accepts a + /// [numbering pattern or function]($func/numbering). + #[default(Some(Numbering::Pattern(NumberingPattern::from_str("1").unwrap())))] + pub numbering: Option, + + /// The vertical gap between the body and caption. + #[default(Em::new(0.65).into())] + pub gap: Length, + + /// The figure's number. + #[synthesized] + pub number: Option, +} + +impl FigureNode { + fn element(&self) -> NodeId { + let mut id = self.body().id(); + if id != NodeId::of::() { + id = NodeId::of::(); + } + id + } +} + +impl Synthesize for FigureNode { + fn synthesize(&self, vt: &mut Vt, styles: StyleChain) -> Content { + let my_id = vt.identify(self); + let element = self.element(); + + let numbering = self.numbering(styles); + let mut number = None; + if numbering.is_some() { + number = NonZeroUsize::new( + 1 + vt + .locate(Selector::node::()) + .into_iter() + .take_while(|&(id, _)| id != my_id) + .filter(|(_, node)| node.to::().unwrap().element() == element) + .count(), + ); + } + + let node = self.clone().with_number(number).with_numbering(numbering).pack(); + let meta = Meta::Node(my_id, node.clone()); + node.styled(MetaNode::set_data(vec![meta])) + } +} + +impl Show for FigureNode { + fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult { + let mut realized = self.body(); + + if let Some(mut caption) = self.caption(styles) { + if let Some(numbering) = self.numbering(styles) { + let number = self.number().unwrap(); + let name = self.local_name(TextNode::lang_in(styles)); + caption = TextNode::packed(eco_format!("{name}\u{a0}")) + + numbering.apply(vt.world(), &[number])?.display() + + TextNode::packed(": ") + + caption; + } + + realized += VNode::weak(self.gap(styles).into()).pack(); + realized += caption; + } + + Ok(BlockNode::new() + .with_body(Some(realized)) + .pack() + .aligned(Axes::with_x(Some(Align::Center.into())))) + } +} + +impl LocalName for FigureNode { + fn local_name(&self, lang: Lang) -> &'static str { + let body = self.body(); + if body.is::() { + return body.with::().unwrap().local_name(lang); + } + + match lang { + Lang::GERMAN => "Abbildung", + Lang::ENGLISH | _ => "Figure", + } + } +} diff --git a/library/src/meta/heading.rs b/library/src/meta/heading.rs index 1bff3af4..e82f80c6 100644 --- a/library/src/meta/heading.rs +++ b/library/src/meta/heading.rs @@ -1,6 +1,6 @@ use typst::font::FontWeight; -use super::Numbering; +use super::{LocalName, Numbering}; use crate::layout::{BlockNode, HNode, VNode}; use crate::prelude::*; use crate::text::{TextNode, TextSize}; @@ -40,7 +40,7 @@ use crate::text::{TextNode, TextSize}; /// /// Display: Heading /// Category: meta -#[node(Synthesize, Show, Finalize)] +#[node(Synthesize, Show, Finalize, LocalName)] pub struct HeadingNode { /// The logical nesting depth of the heading, starting from one. #[default(NonZeroUsize::new(1).unwrap())] @@ -78,14 +78,6 @@ pub struct HeadingNode { pub body: Content, /// The heading's numbering numbers. - /// - /// ```example - /// #show heading: it => it.numbers - /// - /// = First - /// == Second - /// = Third - /// ``` #[synthesized] pub numbers: Option>, } @@ -93,17 +85,17 @@ pub struct HeadingNode { impl Synthesize for HeadingNode { fn synthesize(&self, vt: &mut Vt, styles: StyleChain) -> Content { let my_id = vt.identify(self); - let numbered = self.numbering(styles).is_some(); + let numbering = self.numbering(styles); let mut counter = HeadingCounter::new(); - if numbered { - // Advance passed existing headings. + if numbering.is_some() { + // Advance past existing headings. for (_, node) in vt - .locate(Selector::node::()) + .locate(Selector::node::()) .into_iter() .take_while(|&(id, _)| id != my_id) { - let heading = node.to::().unwrap(); + let heading = node.to::().unwrap(); if heading.numbering(StyleChain::default()).is_some() { counter.advance(heading); } @@ -116,8 +108,8 @@ impl Synthesize for HeadingNode { let node = self .clone() .with_outlined(self.outlined(styles)) - .with_numbering(self.numbering(styles)) - .with_numbers(numbered.then(|| counter.take())) + .with_numbers(numbering.is_some().then(|| counter.take())) + .with_numbering(numbering) .pack(); let meta = Meta::Node(my_id, node.clone()); @@ -196,3 +188,12 @@ cast_from_value! { HeadingNode, v: Content => v.to::().ok_or("expected heading")?.clone(), } + +impl LocalName for HeadingNode { + fn local_name(&self, lang: Lang) -> &'static str { + match lang { + Lang::GERMAN => "Abschnitt", + Lang::ENGLISH | _ => "Section", + } + } +} diff --git a/library/src/meta/mod.rs b/library/src/meta/mod.rs index 07f449a4..f5b9bf2f 100644 --- a/library/src/meta/mod.rs +++ b/library/src/meta/mod.rs @@ -1,6 +1,7 @@ //! Interaction between document parts. mod document; +mod figure; mod heading; mod link; mod numbering; @@ -8,6 +9,7 @@ mod outline; mod reference; pub use self::document::*; +pub use self::figure::*; pub use self::heading::*; pub use self::link::*; pub use self::numbering::*; diff --git a/library/src/meta/outline.rs b/library/src/meta/outline.rs index a2b12511..81785212 100644 --- a/library/src/meta/outline.rs +++ b/library/src/meta/outline.rs @@ -1,4 +1,4 @@ -use super::HeadingNode; +use super::{HeadingNode, LocalName}; use crate::layout::{BoxNode, HNode, HideNode, ParbreakNode, RepeatNode}; use crate::prelude::*; use crate::text::{LinebreakNode, SpaceNode, TextNode}; @@ -22,7 +22,7 @@ use crate::text::{LinebreakNode, SpaceNode, TextNode}; /// /// Display: Outline /// Category: meta -#[node(Synthesize, Show)] +#[node(Synthesize, Show, LocalName)] pub struct OutlineNode { /// The title of the outline. /// @@ -91,10 +91,7 @@ impl Show for OutlineNode { let mut seq = vec![ParbreakNode::new().pack()]; if let Some(title) = self.title(styles) { let title = title.clone().unwrap_or_else(|| { - TextNode::packed(match TextNode::lang_in(styles) { - Lang::GERMAN => "Inhaltsverzeichnis", - Lang::ENGLISH | _ => "Contents", - }) + TextNode::packed(self.local_name(TextNode::lang_in(styles))) }); seq.push( @@ -187,3 +184,12 @@ impl Show for OutlineNode { Ok(Content::sequence(seq)) } } + +impl LocalName for OutlineNode { + fn local_name(&self, lang: Lang) -> &'static str { + match lang { + Lang::GERMAN => "Inhaltsverzeichnis", + Lang::ENGLISH | _ => "Contents", + } + } +} diff --git a/library/src/meta/reference.rs b/library/src/meta/reference.rs index a46198bd..55f2fc6e 100644 --- a/library/src/meta/reference.rs +++ b/library/src/meta/reference.rs @@ -1,4 +1,4 @@ -use super::{HeadingNode, Numbering}; +use super::{FigureNode, HeadingNode, Numbering}; use crate::prelude::*; use crate::text::TextNode; @@ -92,7 +92,9 @@ impl Show for RefNode { }; let mut prefix = match self.prefix(styles) { - Smart::Auto => prefix(target, TextNode::lang_in(styles)) + Smart::Auto => target + .with::() + .map(|node| node.local_name(TextNode::lang_in(styles))) .map(TextNode::packed) .unwrap_or_default(), Smart::Custom(None) => Content::empty(), @@ -113,6 +115,13 @@ impl Show for RefNode { } else { bail!(self.span(), "cannot reference unnumbered heading"); } + } else if let Some(figure) = target.to::() { + if let Some(numbering) = figure.numbering(StyleChain::default()) { + let number = figure.number().unwrap(); + numbered(vt, prefix, &numbering, &[number])? + } else { + bail!(self.span(), "cannot reference unnumbered figure"); + } } else { bail!(self.span(), "cannot reference {}", target.id().name); }; @@ -138,15 +147,8 @@ fn numbered( }) } -/// The default prefix. -fn prefix(node: &Content, lang: Lang) -> Option<&str> { - if node.is::() { - match lang { - Lang::ENGLISH => Some("Section"), - Lang::GERMAN => Some("Abschnitt"), - _ => None, - } - } else { - None - } +/// The named with which an element is referenced. +pub trait LocalName { + /// Get the name in the given language. + fn local_name(&self, lang: Lang) -> &'static str; } diff --git a/library/src/visualize/image.rs b/library/src/visualize/image.rs index 78f477f6..29a04c96 100644 --- a/library/src/visualize/image.rs +++ b/library/src/visualize/image.rs @@ -11,11 +11,13 @@ use crate::prelude::*; /// /// ## Example /// ```example -/// #align(center)[ -/// #image("molecular.jpg", width: 80%) -/// *A step in the molecular testing -/// pipeline of our lab* -/// ] +/// #figure( +/// image("molecular.jpg", width: 80%), +/// caption: [ +/// A step in the molecular testing +/// pipeline of our lab. +/// ], +/// ) /// ``` /// /// Display: Image -- cgit v1.2.3