This commit is contained in:
Laurenz 2023-03-11 20:01:56 +01:00
parent 529d3e10c6
commit 1a390deaea
13 changed files with 243 additions and 55 deletions

View File

@ -191,6 +191,12 @@ impl From<Em> for Spacing {
} }
} }
impl From<Length> for Spacing {
fn from(length: Length) -> Self {
Self::Rel(length.into())
}
}
impl From<Fr> for Spacing { impl From<Fr> for Spacing {
fn from(fr: Fr) -> Self { fn from(fr: Fr) -> Self {
Self::Fr(fr) Self::Fr(fr)

View File

@ -1,4 +1,5 @@
use crate::layout::{AlignNode, GridLayouter, TrackSizings}; use crate::layout::{AlignNode, GridLayouter, TrackSizings};
use crate::meta::LocalName;
use crate::prelude::*; use crate::prelude::*;
/// A table of items. /// A table of items.
@ -31,7 +32,7 @@ use crate::prelude::*;
/// ///
/// Display: Table /// Display: Table
/// Category: layout /// Category: layout
#[node(Layout)] #[node(Layout, LocalName)]
pub struct TableNode { pub struct TableNode {
/// 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.
@ -264,3 +265,12 @@ impl<T: Into<Value>> From<Celled<T>> for Value {
} }
} }
} }
impl LocalName for TableNode {
fn local_name(&self, lang: Lang) -> &'static str {
match lang {
Lang::GERMAN => "Tabelle",
Lang::ENGLISH | _ => "Table",
}
}
}

View File

@ -88,6 +88,7 @@ fn global(math: Module, calc: Module) -> Module {
global.define("link", meta::LinkNode::id()); global.define("link", meta::LinkNode::id());
global.define("outline", meta::OutlineNode::id()); global.define("outline", meta::OutlineNode::id());
global.define("heading", meta::HeadingNode::id()); global.define("heading", meta::HeadingNode::id());
global.define("figure", meta::FigureNode::id());
global.define("numbering", meta::numbering); global.define("numbering", meta::numbering);
// Symbols. // Symbols.

120
library/src/meta/figure.rs Normal file
View File

@ -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.
/// ],
/// ) <fig-lab>
/// ```
///
/// 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<Content>,
/// 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<Numbering>,
/// 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<NonZeroUsize>,
}
impl FigureNode {
fn element(&self) -> NodeId {
let mut id = self.body().id();
if id != NodeId::of::<TableNode>() {
id = NodeId::of::<Self>();
}
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::<Self>())
.into_iter()
.take_while(|&(id, _)| id != my_id)
.filter(|(_, node)| node.to::<Self>().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<Content> {
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::<TableNode>() {
return body.with::<dyn LocalName>().unwrap().local_name(lang);
}
match lang {
Lang::GERMAN => "Abbildung",
Lang::ENGLISH | _ => "Figure",
}
}
}

View File

@ -1,6 +1,6 @@
use typst::font::FontWeight; use typst::font::FontWeight;
use super::Numbering; use super::{LocalName, Numbering};
use crate::layout::{BlockNode, HNode, VNode}; use crate::layout::{BlockNode, HNode, VNode};
use crate::prelude::*; use crate::prelude::*;
use crate::text::{TextNode, TextSize}; use crate::text::{TextNode, TextSize};
@ -40,7 +40,7 @@ use crate::text::{TextNode, TextSize};
/// ///
/// Display: Heading /// Display: Heading
/// Category: meta /// Category: meta
#[node(Synthesize, Show, Finalize)] #[node(Synthesize, Show, Finalize, LocalName)]
pub struct HeadingNode { pub struct HeadingNode {
/// The logical nesting depth of the heading, starting from one. /// The logical nesting depth of the heading, starting from one.
#[default(NonZeroUsize::new(1).unwrap())] #[default(NonZeroUsize::new(1).unwrap())]
@ -78,14 +78,6 @@ pub struct HeadingNode {
pub body: Content, pub body: Content,
/// The heading's numbering numbers. /// The heading's numbering numbers.
///
/// ```example
/// #show heading: it => it.numbers
///
/// = First
/// == Second
/// = Third
/// ```
#[synthesized] #[synthesized]
pub numbers: Option<Vec<NonZeroUsize>>, pub numbers: Option<Vec<NonZeroUsize>>,
} }
@ -93,17 +85,17 @@ pub struct HeadingNode {
impl Synthesize for HeadingNode { impl Synthesize for HeadingNode {
fn synthesize(&self, vt: &mut Vt, styles: StyleChain) -> Content { fn synthesize(&self, vt: &mut Vt, styles: StyleChain) -> Content {
let my_id = vt.identify(self); let my_id = vt.identify(self);
let numbered = self.numbering(styles).is_some(); let numbering = self.numbering(styles);
let mut counter = HeadingCounter::new(); let mut counter = HeadingCounter::new();
if numbered { if numbering.is_some() {
// Advance passed existing headings. // Advance past existing headings.
for (_, node) in vt for (_, node) in vt
.locate(Selector::node::<HeadingNode>()) .locate(Selector::node::<Self>())
.into_iter() .into_iter()
.take_while(|&(id, _)| id != my_id) .take_while(|&(id, _)| id != my_id)
{ {
let heading = node.to::<HeadingNode>().unwrap(); let heading = node.to::<Self>().unwrap();
if heading.numbering(StyleChain::default()).is_some() { if heading.numbering(StyleChain::default()).is_some() {
counter.advance(heading); counter.advance(heading);
} }
@ -116,8 +108,8 @@ impl Synthesize for HeadingNode {
let node = self let node = self
.clone() .clone()
.with_outlined(self.outlined(styles)) .with_outlined(self.outlined(styles))
.with_numbering(self.numbering(styles)) .with_numbers(numbering.is_some().then(|| counter.take()))
.with_numbers(numbered.then(|| counter.take())) .with_numbering(numbering)
.pack(); .pack();
let meta = Meta::Node(my_id, node.clone()); let meta = Meta::Node(my_id, node.clone());
@ -196,3 +188,12 @@ cast_from_value! {
HeadingNode, HeadingNode,
v: Content => v.to::<Self>().ok_or("expected heading")?.clone(), v: Content => v.to::<Self>().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",
}
}
}

View File

@ -1,6 +1,7 @@
//! Interaction between document parts. //! Interaction between document parts.
mod document; mod document;
mod figure;
mod heading; mod heading;
mod link; mod link;
mod numbering; mod numbering;
@ -8,6 +9,7 @@ mod outline;
mod reference; mod reference;
pub use self::document::*; pub use self::document::*;
pub use self::figure::*;
pub use self::heading::*; pub use self::heading::*;
pub use self::link::*; pub use self::link::*;
pub use self::numbering::*; pub use self::numbering::*;

View File

@ -1,4 +1,4 @@
use super::HeadingNode; use super::{HeadingNode, LocalName};
use crate::layout::{BoxNode, HNode, HideNode, ParbreakNode, RepeatNode}; use crate::layout::{BoxNode, HNode, HideNode, ParbreakNode, RepeatNode};
use crate::prelude::*; use crate::prelude::*;
use crate::text::{LinebreakNode, SpaceNode, TextNode}; use crate::text::{LinebreakNode, SpaceNode, TextNode};
@ -22,7 +22,7 @@ use crate::text::{LinebreakNode, SpaceNode, TextNode};
/// ///
/// Display: Outline /// Display: Outline
/// Category: meta /// Category: meta
#[node(Synthesize, Show)] #[node(Synthesize, Show, LocalName)]
pub struct OutlineNode { pub struct OutlineNode {
/// The title of the outline. /// The title of the outline.
/// ///
@ -91,10 +91,7 @@ impl Show for OutlineNode {
let mut seq = vec![ParbreakNode::new().pack()]; let mut seq = vec![ParbreakNode::new().pack()];
if let Some(title) = self.title(styles) { if let Some(title) = self.title(styles) {
let title = title.clone().unwrap_or_else(|| { let title = title.clone().unwrap_or_else(|| {
TextNode::packed(match TextNode::lang_in(styles) { TextNode::packed(self.local_name(TextNode::lang_in(styles)))
Lang::GERMAN => "Inhaltsverzeichnis",
Lang::ENGLISH | _ => "Contents",
})
}); });
seq.push( seq.push(
@ -187,3 +184,12 @@ impl Show for OutlineNode {
Ok(Content::sequence(seq)) Ok(Content::sequence(seq))
} }
} }
impl LocalName for OutlineNode {
fn local_name(&self, lang: Lang) -> &'static str {
match lang {
Lang::GERMAN => "Inhaltsverzeichnis",
Lang::ENGLISH | _ => "Contents",
}
}
}

View File

@ -1,4 +1,4 @@
use super::{HeadingNode, Numbering}; use super::{FigureNode, HeadingNode, Numbering};
use crate::prelude::*; use crate::prelude::*;
use crate::text::TextNode; use crate::text::TextNode;
@ -92,7 +92,9 @@ impl Show for RefNode {
}; };
let mut prefix = match self.prefix(styles) { let mut prefix = match self.prefix(styles) {
Smart::Auto => prefix(target, TextNode::lang_in(styles)) Smart::Auto => target
.with::<dyn LocalName>()
.map(|node| node.local_name(TextNode::lang_in(styles)))
.map(TextNode::packed) .map(TextNode::packed)
.unwrap_or_default(), .unwrap_or_default(),
Smart::Custom(None) => Content::empty(), Smart::Custom(None) => Content::empty(),
@ -113,6 +115,13 @@ impl Show for RefNode {
} else { } else {
bail!(self.span(), "cannot reference unnumbered heading"); bail!(self.span(), "cannot reference unnumbered heading");
} }
} else if let Some(figure) = target.to::<FigureNode>() {
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 { } else {
bail!(self.span(), "cannot reference {}", target.id().name); bail!(self.span(), "cannot reference {}", target.id().name);
}; };
@ -138,15 +147,8 @@ fn numbered(
}) })
} }
/// The default prefix. /// The named with which an element is referenced.
fn prefix(node: &Content, lang: Lang) -> Option<&str> { pub trait LocalName {
if node.is::<HeadingNode>() { /// Get the name in the given language.
match lang { fn local_name(&self, lang: Lang) -> &'static str;
Lang::ENGLISH => Some("Section"),
Lang::GERMAN => Some("Abschnitt"),
_ => None,
}
} else {
None
}
} }

View File

@ -11,11 +11,13 @@ use crate::prelude::*;
/// ///
/// ## Example /// ## Example
/// ```example /// ```example
/// #align(center)[ /// #figure(
/// #image("molecular.jpg", width: 80%) /// image("molecular.jpg", width: 80%),
/// *A step in the molecular testing /// caption: [
/// pipeline of our lab* /// A step in the molecular testing
/// ] /// pipeline of our lab.
/// ],
/// )
/// ``` /// ```
/// ///
/// Display: Image /// Display: Image

View File

@ -142,6 +142,14 @@ impl Func {
self.select(Some(fields)) self.select(Some(fields))
} }
/// The node id of this function if it is an element function.
pub fn id(&self) -> Option<NodeId> {
match **self.0 {
Repr::Node(id) => Some(id),
_ => None,
}
}
/// Execute the function's set rule and return the resulting style map. /// Execute the function's set rule and return the resulting style map.
pub fn set(&self, mut args: Args) -> SourceResult<StyleMap> { pub fn set(&self, mut args: Args) -> SourceResult<StyleMap> {
Ok(match &**self.0 { Ok(match &**self.0 {
@ -156,17 +164,16 @@ impl Func {
/// Create a selector for this function's node type. /// Create a selector for this function's node type.
pub fn select(&self, fields: Option<Dict>) -> StrResult<Selector> { pub fn select(&self, fields: Option<Dict>) -> StrResult<Selector> {
match **self.0 { let Some(id) = self.id() else {
Repr::Node(id) => { return Err("this function is not selectable".into());
};
if id == item!(text_id) { if id == item!(text_id) {
Err("to select text, please use a string or regex instead")?; Err("to select text, please use a string or regex instead")?;
} }
Ok(Selector::Node(id, fields)) Ok(Selector::Node(id, fields))
} }
_ => Err("this function is not selectable")?,
}
}
} }
impl Debug for Func { impl Debug for Func {
@ -196,10 +203,6 @@ impl From<NodeId> for Func {
} }
} }
cast_to_value! {
v: NodeId => Value::Func(v.into())
}
/// A native Rust function. /// A native Rust function.
pub struct NativeFunc { pub struct NativeFunc {
/// The function's implementation. /// The function's implementation.

View File

@ -10,7 +10,9 @@ use once_cell::sync::Lazy;
use super::{node, Guard, Recipe, Style, StyleMap}; use super::{node, Guard, Recipe, Style, StyleMap};
use crate::diag::{SourceResult, StrResult}; use crate::diag::{SourceResult, StrResult};
use crate::eval::{cast_from_value, Args, Cast, FuncInfo, Str, Value, Vm}; use crate::eval::{
cast_from_value, cast_to_value, Args, Cast, Func, FuncInfo, Str, Value, Vm,
};
use crate::syntax::Span; use crate::syntax::Span;
use crate::util::pretty_array_like; use crate::util::pretty_array_like;
use crate::World; use crate::World;
@ -382,6 +384,15 @@ impl Deref for NodeId {
} }
} }
cast_from_value! {
NodeId,
v: Func => v.id().ok_or("this function is not an element")?
}
cast_to_value! {
v: NodeId => Value::Func(v.into())
}
/// Static node for a node. /// Static node for a node.
pub struct NodeMeta { pub struct NodeMeta {
/// The node's name. /// The node's name.

BIN
tests/ref/meta/figure.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

24
tests/typ/meta/figure.typ Normal file
View File

@ -0,0 +1,24 @@
// Test figures.
---
#set page(width: 150pt)
#set figure(numbering: "I")
We can clearly see that @fig-cylinder and
@tab-complex are relevant in this context.
#figure(
table(columns: 2)[a][b],
caption: [The basic table.],
) <tab-basic>
#figure(
pad(y: -11pt, image("/cylinder.svg", height: 3cm)),
caption: [The basic shapes.],
numbering: "I",
) <fig-cylinder>
#figure(
table(columns: 3)[a][b][c][d][e][f],
caption: [The complex table.],
) <tab-complex>