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 {
fn from(fr: Fr) -> Self {
Self::Fr(fr)

View File

@ -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<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("outline", meta::OutlineNode::id());
global.define("heading", meta::HeadingNode::id());
global.define("figure", meta::FigureNode::id());
global.define("numbering", meta::numbering);
// 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 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<Vec<NonZeroUsize>>,
}
@ -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::<HeadingNode>())
.locate(Selector::node::<Self>())
.into_iter()
.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() {
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::<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.
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::*;

View File

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

View File

@ -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::<dyn LocalName>()
.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::<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 {
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::<HeadingNode>() {
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;
}

View File

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

View File

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

View File

@ -10,7 +10,9 @@ use once_cell::sync::Lazy;
use super::{node, Guard, Recipe, Style, StyleMap};
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::util::pretty_array_like;
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.
pub struct NodeMeta {
/// 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>