diff options
Diffstat (limited to 'library/src')
| -rw-r--r-- | library/src/layout/measure.rs | 20 | ||||
| -rw-r--r-- | library/src/layout/mod.rs | 2 | ||||
| -rw-r--r-- | library/src/layout/page.rs | 21 | ||||
| -rw-r--r-- | library/src/lib.rs | 13 | ||||
| -rw-r--r-- | library/src/math/mod.rs | 24 | ||||
| -rw-r--r-- | library/src/meta/context.rs | 66 | ||||
| -rw-r--r-- | library/src/meta/counter.rs | 485 | ||||
| -rw-r--r-- | library/src/meta/figure.rs | 14 | ||||
| -rw-r--r-- | library/src/meta/heading.rs | 6 | ||||
| -rw-r--r-- | library/src/meta/mod.rs | 2 | ||||
| -rw-r--r-- | library/src/meta/outline.rs | 16 | ||||
| -rw-r--r-- | library/src/meta/query.rs | 53 | ||||
| -rw-r--r-- | library/src/meta/reference.rs | 5 | ||||
| -rw-r--r-- | library/src/meta/state.rs | 274 |
14 files changed, 531 insertions, 470 deletions
diff --git a/library/src/layout/measure.rs b/library/src/layout/measure.rs new file mode 100644 index 00000000..b116cbf8 --- /dev/null +++ b/library/src/layout/measure.rs @@ -0,0 +1,20 @@ +use crate::prelude::*; + +/// Measure the size of content. +/// +/// Display: Measure +/// Category: layout +/// Returns: array +#[func] +pub fn measure( + /// The content whose size to measure. + content: Content, + /// The styles with which to layout the content. + styles: StyleMap, +) -> Value { + let pod = Regions::one(Axes::splat(Abs::inf()), Axes::splat(false)); + let styles = StyleChain::new(&styles); + let frame = content.measure(&mut vm.vt, styles, pod)?.into_frame(); + let Size { x, y } = frame.size(); + Value::Array(array![x, y]) +} diff --git a/library/src/layout/mod.rs b/library/src/layout/mod.rs index 0ce292aa..b6ecce51 100644 --- a/library/src/layout/mod.rs +++ b/library/src/layout/mod.rs @@ -10,6 +10,7 @@ mod fragment; mod grid; mod hide; mod list; +mod measure; mod pad; mod page; mod par; @@ -31,6 +32,7 @@ pub use self::fragment::*; pub use self::grid::*; pub use self::hide::*; pub use self::list::*; +pub use self::measure::*; pub use self::pad::*; pub use self::page::*; pub use self::par::*; diff --git a/library/src/layout/page.rs b/library/src/layout/page.rs index eeb74a55..93ee08ce 100644 --- a/library/src/layout/page.rs +++ b/library/src/layout/page.rs @@ -2,7 +2,7 @@ use std::ptr; use std::str::FromStr; use super::{AlignNode, ColumnsNode}; -use crate::meta::{Counter, CounterAction, CounterKey, CounterNode, Numbering}; +use crate::meta::{Counter, CounterKey, Numbering}; use crate::prelude::*; /// Layouts its child onto one or multiple pages. @@ -214,8 +214,10 @@ pub struct PageNode { /// footer: [ /// #set align(right) /// #set text(8pt) - /// #counter(page).get("1") of - /// #counter(page).final("I") + /// #counter(page).display( + /// "1 of I", + /// both: true, + /// ) /// ] /// ) /// @@ -311,12 +313,13 @@ impl PageNode { let header_ascent = self.header_ascent(styles); let footer = self.footer(styles).or_else(|| { self.numbering(styles).map(|numbering| { - CounterNode::new( - Counter::new(CounterKey::Page), - CounterAction::Both(numbering), - ) - .pack() - .aligned(self.number_align(styles)) + let both = match &numbering { + Numbering::Pattern(pattern) => pattern.pieces() >= 2, + Numbering::Func(_) => true, + }; + Counter::new(CounterKey::Page) + .display(numbering, both) + .aligned(self.number_align(styles)) }) }); let footer_descent = self.footer_descent(styles); diff --git a/library/src/lib.rs b/library/src/lib.rs index 14fea66d..2f951b92 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -74,6 +74,7 @@ fn global(math: Module, calc: Module) -> Module { global.define("scale", layout::ScaleNode::id()); global.define("rotate", layout::RotateNode::id()); global.define("hide", layout::HideNode::id()); + global.define("measure", layout::measure); // Visualize. global.define("image", visualize::ImageNode::id()); @@ -92,6 +93,8 @@ fn global(math: Module, calc: Module) -> Module { global.define("figure", meta::FigureNode::id()); global.define("cite", meta::CiteNode::id()); global.define("bibliography", meta::BibliographyNode::id()); + global.define("locate", meta::locate); + global.define("style", meta::style); global.define("counter", meta::counter); global.define("numbering", meta::numbering); global.define("state", meta::state); @@ -228,11 +231,11 @@ fn items() -> LangItems { math::AccentNode::new(base, math::Accent::new(accent)).pack() }, math_frac: |num, denom| math::FracNode::new(num, denom).pack(), - library_method: |dynamic, method, args, span| { - if let Some(counter) = dynamic.downcast().cloned() { - meta::counter_method(counter, method, args, span) - } else if let Some(state) = dynamic.downcast().cloned() { - meta::state_method(state, method, args, span) + library_method: |vm, dynamic, method, args, span| { + if let Some(counter) = dynamic.downcast::<meta::Counter>().cloned() { + counter.call_method(vm, method, args, span) + } else if let Some(state) = dynamic.downcast::<meta::State>().cloned() { + state.call_method(vm, method, args, span) } else { Err(format!("type {} has no method `{method}`", dynamic.type_name())) .at(span) diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index 9e692f57..7fb1aadf 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -39,9 +39,7 @@ use self::fragment::*; use self::row::*; use self::spacing::*; use crate::layout::{HNode, ParNode, Spacing}; -use crate::meta::{ - Count, Counter, CounterAction, CounterNode, CounterUpdate, LocalName, Numbering, -}; +use crate::meta::{Count, Counter, CounterUpdate, LocalName, Numbering}; use crate::prelude::*; use crate::text::{ families, variant, FontFamily, FontList, LinebreakNode, SpaceNode, TextNode, TextSize, @@ -217,29 +215,29 @@ impl Layout for EquationNode { if block { if let Some(numbering) = self.numbering(styles) { let pod = Regions::one(regions.base(), Axes::splat(false)); - let counter = CounterNode::new( - Counter::of(Self::id()), - CounterAction::Get(numbering), - ); + let counter = Counter::of(Self::id()) + .display(numbering, false) + .layout(vt, styles, pod)? + .into_frame(); - let sub = counter.pack().layout(vt, styles, pod)?.into_frame(); let width = if regions.size.x.is_finite() { regions.size.x } else { - frame.width() + 2.0 * (sub.width() + NUMBER_GUTTER.resolve(styles)) + frame.width() + + 2.0 * (counter.width() + NUMBER_GUTTER.resolve(styles)) }; - let height = frame.height().max(sub.height()); + let height = frame.height().max(counter.height()); frame.resize(Size::new(width, height), Align::CENTER_HORIZON); let x = if TextNode::dir_in(styles).is_positive() { - frame.width() - sub.width() + frame.width() - counter.width() } else { Abs::zero() }; - let y = (frame.height() - sub.height()) / 2.0; + let y = (frame.height() - counter.height()) / 2.0; - frame.push_frame(Point::new(x, y), sub) + frame.push_frame(Point::new(x, y), counter) } } else { let slack = ParNode::leading_in(styles) * 0.7; diff --git a/library/src/meta/context.rs b/library/src/meta/context.rs new file mode 100644 index 00000000..9e542847 --- /dev/null +++ b/library/src/meta/context.rs @@ -0,0 +1,66 @@ +use crate::prelude::*; + +/// Provide access to the location of content. +/// +/// Display: Locate +/// Category: meta +/// Returns: content +#[func] +pub fn locate( + /// The function to call with the location. + func: Func, +) -> Value { + LocateNode::new(func).pack().into() +} + +/// Executes a `locate` call. +/// +/// Display: Styled +/// Category: special +#[node(Locatable, Show)] +struct LocateNode { + /// The function to call with the location. + #[required] + func: Func, +} + +impl Show for LocateNode { + fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> { + if !vt.introspector.init() { + return Ok(Content::empty()); + } + + let id = self.0.stable_id().unwrap(); + Ok(self.func().call_vt(vt, [id.into()])?.display()) + } +} + +/// Provide access to active styles. +/// +/// Display: Styled +/// Category: layout +/// Returns: content +#[func] +pub fn style( + /// The function to call with the styles. + func: Func, +) -> Value { + StyleNode::new(func).pack().into() +} + +/// Executes a style access. +/// +/// Display: Style +/// Category: special +#[node(Show)] +struct StyleNode { + /// The function to call with the styles. + #[required] + func: Func, +} + +impl Show for StyleNode { + fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> { + Ok(self.func().call_vt(vt, [styles.to_map().into()])?.display()) + } +} diff --git a/library/src/meta/counter.rs b/library/src/meta/counter.rs index 27fd548f..f033a04f 100644 --- a/library/src/meta/counter.rs +++ b/library/src/meta/counter.rs @@ -22,272 +22,146 @@ pub fn counter( Value::dynamic(Counter::new(key)) } -/// Identifies a counter. +/// Counts through pages, elements, and more. #[derive(Clone, PartialEq, Hash)] -pub enum CounterKey { - /// The page counter. - Page, - /// Counts elements matching the given selectors. Only works for locatable - /// elements or labels. - Selector(Selector), - /// Counts through manual counters with the same key. - Str(Str), -} - -cast_from_value! { - CounterKey, - v: Str => Self::Str(v), - label: Label => Self::Selector(Selector::Label(label)), - func: Func => { - let Some(id) = func.id() else { - return Err("this function is not selectable".into()); - }; - - if id == NodeId::of::<PageNode>() { - return Ok(Self::Page); - } - - if !Content::new(id).can::<dyn Locatable>() { - Err(eco_format!("cannot count through {}s", id.name))?; - } +pub struct Counter(CounterKey); - Self::Selector(Selector::Node(id, None)) +impl Counter { + /// Create a new counter from a key. + pub fn new(key: CounterKey) -> Self { + Self(key) } -} -impl Debug for CounterKey { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Page => f.pad("page"), - Self::Selector(selector) => selector.fmt(f), - Self::Str(str) => str.fmt(f), - } + /// The counter for the given node. + pub fn of(id: NodeId) -> Self { + Self::new(CounterKey::Selector(Selector::Node(id, None))) } -} - -/// Call a method on counter. -pub fn counter_method( - counter: Counter, - method: &str, - mut args: Args, - span: Span, -) -> SourceResult<Value> { - let pattern = |s| NumberingPattern::from_str(s).unwrap().into(); - let action = match method { - "get" => CounterAction::Get(args.eat()?.unwrap_or_else(|| pattern("1.1"))), - "final" => CounterAction::Final(args.eat()?.unwrap_or_else(|| pattern("1.1"))), - "both" => CounterAction::Both(args.eat()?.unwrap_or_else(|| pattern("1/1"))), - "step" => CounterAction::Update(CounterUpdate::Step( - args.named("level")?.unwrap_or(NonZeroUsize::ONE), - )), - "update" => CounterAction::Update(args.expect("value or function")?), - _ => bail!(span, "type counter has no method `{}`", method), - }; - - args.finish()?; - - let content = CounterNode::new(counter, action).pack(); - Ok(Value::Content(content)) -} - -/// Executes an action on a counter. -/// -/// Display: Counter -/// Category: special -#[node(Locatable, Show)] -pub struct CounterNode { - /// The counter key. - #[required] - pub counter: Counter, - /// The action. - #[required] - pub action: CounterAction, -} + /// Call a method on counter. + pub fn call_method( + self, + vm: &mut Vm, + method: &str, + mut args: Args, + span: Span, + ) -> SourceResult<Value> { + let pattern = |s| NumberingPattern::from_str(s).unwrap().into(); + let value = match method { + "display" => self + .display( + args.eat()?.unwrap_or_else(|| pattern("1.1")), + args.named("both")?.unwrap_or(false), + ) + .into(), + "at" => self.at(&mut vm.vt, args.expect("location")?)?.into(), + "final" => self.final_(&mut vm.vt, args.expect("location")?)?.into(), + "update" => self.update(args.expect("value or function")?).into(), + "step" => self + .update(CounterUpdate::Step( + args.named("level")?.unwrap_or(NonZeroUsize::ONE), + )) + .into(), + _ => bail!(span, "type counter has no method `{}`", method), + }; + args.finish()?; + Ok(value) + } -impl Show for CounterNode { - fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> { - match self.action() { - CounterAction::Get(numbering) => { - self.counter().resolve(vt, self.0.stable_id(), &numbering) - } - CounterAction::Final(numbering) => { - self.counter().resolve(vt, None, &numbering) - } - CounterAction::Both(numbering) => { - let both = match &numbering { - Numbering::Pattern(pattern) => pattern.pieces() >= 2, - _ => false, - }; - - let counter = self.counter(); - let id = self.0.stable_id(); - if !both { - return counter.resolve(vt, id, &numbering); - } + /// Display the current value of the counter. + pub fn display(self, numbering: Numbering, both: bool) -> Content { + DisplayNode::new(self, numbering, both).pack() + } - let sequence = counter.sequence( - vt.world, - TrackedMut::reborrow_mut(&mut vt.tracer), - TrackedMut::reborrow_mut(&mut vt.provider), - vt.introspector, - )?; - - Ok(match (sequence.single(id), sequence.single(None)) { - (Some(current), Some(total)) => { - numbering.apply_vt(vt, &[current, total])?.display() - } - _ => Content::empty(), - }) - } - CounterAction::Update(_) => Ok(Content::empty()), + /// Get the value of the state at the given location. + pub fn at(&self, vt: &mut Vt, id: StableId) -> SourceResult<CounterState> { + let sequence = self.sequence(vt)?; + let offset = vt.introspector.query_before(self.selector(), id).len(); + let (mut state, page) = sequence[offset].clone(); + if self.is_page() { + let delta = vt.introspector.page(id).get() - page.get(); + state.step(NonZeroUsize::ONE, delta); } + Ok(state) } -} - -/// The action to perform on a counter. -#[derive(Clone, PartialEq, Hash)] -pub enum CounterAction { - /// Displays the current value. - Get(Numbering), - /// Displays the final value. - Final(Numbering), - /// If given a pattern with at least two parts, displays the current value - /// together with the final value. Otherwise, displays just the current - /// value. - Both(Numbering), - /// Updates the value, possibly based on the previous one. - Update(CounterUpdate), -} - -cast_from_value! { - CounterAction: "counter action", -} -impl Debug for CounterAction { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Get(_) => f.pad("get(..)"), - Self::Final(_) => f.pad("final(..)"), - Self::Both(_) => f.pad("both(..)"), - Self::Update(_) => f.pad("update(..)"), + /// Get the value of the state at the final location. + pub fn final_(&self, vt: &mut Vt, _: StableId) -> SourceResult<CounterState> { + let sequence = self.sequence(vt)?; + let (mut state, page) = sequence.last().unwrap().clone(); + if self.is_page() { + let delta = vt.introspector.pages().get() - page.get(); + state.step(NonZeroUsize::ONE, delta); } + Ok(state) } -} - -/// An update to perform on a counter. -#[derive(Debug, Clone, PartialEq, Hash)] -pub enum CounterUpdate { - /// Set the counter to the specified state. - Set(CounterState), - /// Increase the number for the given level by one. - Step(NonZeroUsize), - /// Apply the given function to the counter's state. - Func(Func), -} -cast_from_value! { - CounterUpdate, - v: CounterState => Self::Set(v), - v: Func => Self::Func(v), -} - -/// Nodes that have special counting behaviour. -pub trait Count { - /// Get the counter update for this node. - fn update(&self) -> Option<CounterUpdate>; -} - -/// Counts through pages, elements, and more. -#[derive(Clone, PartialEq, Hash)] -pub struct Counter { - /// The key that identifies the counter. - pub key: CounterKey, -} - -impl Counter { - /// Create a new counter from a key. - pub fn new(key: CounterKey) -> Self { - Self { key } + /// Get the current and final value of the state combined in one state. + pub fn both(&self, vt: &mut Vt, id: StableId) -> SourceResult<CounterState> { + let sequence = self.sequence(vt)?; + let offset = vt.introspector.query_before(self.selector(), id).len(); + let (mut at_state, at_page) = sequence[offset].clone(); + let (mut final_state, final_page) = sequence.last().unwrap().clone(); + if self.is_page() { + let at_delta = vt.introspector.page(id).get() - at_page.get(); + at_state.step(NonZeroUsize::ONE, at_delta); + let final_delta = vt.introspector.pages().get() - final_page.get(); + final_state.step(NonZeroUsize::ONE, final_delta); + } + Ok(CounterState(smallvec![at_state.first(), final_state.first()])) } - /// The counter for the given node. - pub fn of(id: NodeId) -> Self { - Self::new(CounterKey::Selector(Selector::Node(id, None))) + /// Produce content that performs a state update. + pub fn update(self, update: CounterUpdate) -> Content { + UpdateNode::new(self, update).pack() } - /// Display the value of the counter at the postition of the given stable - /// id. - pub fn resolve( + /// Produce the whole sequence of counter states. + /// + /// This has to happen just once for all counters, cutting down the number + /// of counter updates from quadratic to linear. + fn sequence( &self, vt: &mut Vt, - stop: Option<StableId>, - numbering: &Numbering, - ) -> SourceResult<Content> { - if !vt.introspector.init() { - return Ok(Content::empty()); - } - - let sequence = self.sequence( + ) -> SourceResult<EcoVec<(CounterState, NonZeroUsize)>> { + self.sequence_impl( vt.world, TrackedMut::reborrow_mut(&mut vt.tracer), TrackedMut::reborrow_mut(&mut vt.provider), vt.introspector, - )?; - - Ok(match sequence.at(stop) { - Some(state) => numbering.apply_vt(vt, &state.0)?.display(), - None => Content::empty(), - }) + ) } - /// Produce the whole sequence of counter states. - /// - /// This has to happen just once for all counters, cutting down the number - /// of counter updates from quadratic to linear. + /// Memoized implementation of `sequence`. #[comemo::memoize] - fn sequence( + fn sequence_impl( &self, world: Tracked<dyn World>, tracer: TrackedMut<Tracer>, provider: TrackedMut<StabilityProvider>, introspector: Tracked<Introspector>, - ) -> SourceResult<CounterSequence> { + ) -> SourceResult<EcoVec<(CounterState, NonZeroUsize)>> { let mut vt = Vt { world, tracer, provider, introspector }; - let mut search = Selector::Node( - NodeId::of::<CounterNode>(), - Some(dict! { "counter" => self.clone() }), - ); - - if let CounterKey::Selector(selector) = &self.key { - search = Selector::Any(eco_vec![search, selector.clone()]); - } - - let mut stops = EcoVec::new(); - let mut state = CounterState(match &self.key { + let mut state = CounterState(match &self.0 { CounterKey::Selector(_) => smallvec![], _ => smallvec![NonZeroUsize::ONE], }); + let mut page = NonZeroUsize::ONE; + let mut stops = eco_vec![(state.clone(), page)]; - let is_page = self.key == CounterKey::Page; - let mut prev_page = NonZeroUsize::ONE; + for node in introspector.query(self.selector()) { + if self.is_page() { + let id = node.stable_id().unwrap(); + let prev = page; + page = introspector.page(id); - for node in introspector.query(search) { - let id = node.stable_id().unwrap(); - if is_page { - let page = introspector.page(id); - let delta = page.get() - prev_page.get(); + let delta = page.get() - prev.get(); if delta > 0 { state.step(NonZeroUsize::ONE, delta); } - prev_page = page; } - if let Some(update) = match node.to::<CounterNode>() { - Some(counter) => match counter.action() { - CounterAction::Update(update) => Some(update), - _ => None, - }, + if let Some(update) = match node.to::<UpdateNode>() { + Some(node) => Some(node.update()), None => match node.with::<dyn Count>() { Some(countable) => countable.update(), None => Some(CounterUpdate::Step(NonZeroUsize::ONE)), @@ -296,17 +170,36 @@ impl Counter { state.update(&mut vt, update)?; } - stops.push((id, state.clone())); + stops.push((state.clone(), page)); + } + + Ok(stops) + } + + /// The selector relevant for this counter's updates. + fn selector(&self) -> Selector { + let mut selector = Selector::Node( + NodeId::of::<UpdateNode>(), + Some(dict! { "counter" => self.clone() }), + ); + + if let CounterKey::Selector(key) = &self.0 { + selector = Selector::Any(eco_vec![selector, key.clone()]); } - Ok(CounterSequence { stops, is_page }) + selector + } + + /// Whether this is the page counter. + fn is_page(&self) -> bool { + self.0 == CounterKey::Page } } impl Debug for Counter { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.write_str("counter(")?; - self.key.fmt(f)?; + self.0.fmt(f)?; f.write_char(')') } } @@ -315,36 +208,78 @@ cast_from_value! { Counter: "counter", } -/// A sequence of counter values. -#[derive(Debug, Clone)] -struct CounterSequence { - stops: EcoVec<(StableId, CounterState)>, - is_page: bool, +/// Identifies a counter. +#[derive(Clone, PartialEq, Hash)] +pub enum CounterKey { + /// The page counter. + Page, + /// Counts elements matching the given selectors. Only works for locatable + /// elements or labels. + Selector(Selector), + /// Counts through manual counters with the same key. + Str(Str), } -impl CounterSequence { - fn at(&self, stop: Option<StableId>) -> Option<CounterState> { - let entry = match stop { - Some(stop) => self.stops.iter().find(|&&(id, _)| id == stop), - None => self.stops.last(), +cast_from_value! { + CounterKey, + v: Str => Self::Str(v), + label: Label => Self::Selector(Selector::Label(label)), + func: Func => { + let Some(id) = func.id() else { + return Err("this function is not selectable".into()); }; - if let Some((_, state)) = entry { - return Some(state.clone()); + if id == NodeId::of::<PageNode>() { + return Ok(Self::Page); } - if self.is_page { - return Some(CounterState(smallvec![NonZeroUsize::ONE])); + if !Content::new(id).can::<dyn Locatable>() { + Err(eco_format!("cannot count through {}s", id.name))?; } - None + Self::Selector(Selector::Node(id, None)) } +} - fn single(&self, stop: Option<StableId>) -> Option<NonZeroUsize> { - Some(*self.at(stop)?.0.first()?) +impl Debug for CounterKey { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Self::Page => f.pad("page"), + Self::Selector(selector) => selector.fmt(f), + Self::Str(str) => str.fmt(f), + } } } +/// An update to perform on a counter. +#[derive(Clone, PartialEq, Hash)] +pub enum CounterUpdate { + /// Set the counter to the specified state. + Set(CounterState), + /// Increase the number for the given level by one. + Step(NonZeroUsize), + /// Apply the given function to the counter's state. + Func(Func), +} + +impl Debug for CounterUpdate { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad("..") + } +} + +cast_from_value! { + CounterUpdate: "counter update", + v: CounterState => Self::Set(v), + v: Func => Self::Func(v), +} + +/// Nodes that have special counting behaviour. +pub trait Count { + /// Get the counter update for this node. + fn update(&self) -> Option<CounterUpdate>; +} + /// Counts through elements with different levels. #[derive(Debug, Clone, PartialEq, Hash)] pub struct CounterState(pub SmallVec<[NonZeroUsize; 3]>); @@ -378,6 +313,16 @@ impl CounterState { self.0.push(NonZeroUsize::ONE); } } + + /// Get the first number of the state. + pub fn first(&self) -> NonZeroUsize { + self.0.first().copied().unwrap_or(NonZeroUsize::ONE) + } + + /// Display the counter state with a numbering. + pub fn display(&self, vt: &mut Vt, numbering: &Numbering) -> SourceResult<Content> { + Ok(numbering.apply_vt(vt, &self.0)?.display()) + } } cast_from_value! { @@ -388,3 +333,57 @@ cast_from_value! { .map(Value::cast) .collect::<StrResult<_>>()?), } + +cast_to_value! { + v: CounterState => Value::Array(v.0.into_iter().map(Into::into).collect()) +} + +/// Executes a display of a state. +/// +/// Display: State +/// Category: special +#[node(Locatable, Show)] +struct DisplayNode { + /// The counter. + #[required] + counter: Counter, + + /// The numbering to display the counter with. + #[required] + numbering: Numbering, + + /// Whether to display both the current and final value. + #[required] + both: bool, +} + +impl Show for DisplayNode { + fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> { + let id = self.0.stable_id().unwrap(); + let counter = self.counter(); + let numbering = self.numbering(); + let state = if self.both() { counter.both(vt, id) } else { counter.at(vt, id) }?; + state.display(vt, &numbering) + } +} + +/// Executes a display of a state. +/// +/// Display: State +/// Category: special +#[node(Locatable, Show)] +struct UpdateNode { + /// The counter. + #[required] + counter: Counter, + + /// The update to perform on the counter. + #[required] + update: CounterUpdate, +} + +impl Show for UpdateNode { + fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> { + Ok(Content::empty()) + } +} diff --git a/library/src/meta/figure.rs b/library/src/meta/figure.rs index a92ff5e7..6f9011b8 100644 --- a/library/src/meta/figure.rs +++ b/library/src/meta/figure.rs @@ -1,9 +1,6 @@ use std::str::FromStr; -use super::{ - Count, Counter, CounterAction, CounterNode, CounterUpdate, LocalName, Numbering, - NumberingPattern, -}; +use super::{Count, Counter, CounterUpdate, LocalName, Numbering, NumberingPattern}; use crate::layout::{BlockNode, VNode}; use crate::prelude::*; use crate::text::TextNode; @@ -59,12 +56,9 @@ impl Show for FigureNode { if let Some(numbering) = self.numbering(styles) { let name = self.local_name(TextNode::lang_in(styles)); caption = TextNode::packed(eco_format!("{name}\u{a0}")) - + CounterNode::new( - Counter::of(Self::id()), - CounterAction::Get(numbering), - ) - .pack() - .spanned(self.span()) + + Counter::of(Self::id()) + .display(numbering, false) + .spanned(self.span()) + TextNode::packed(": ") + caption; } diff --git a/library/src/meta/heading.rs b/library/src/meta/heading.rs index 83342155..61605e67 100644 --- a/library/src/meta/heading.rs +++ b/library/src/meta/heading.rs @@ -1,6 +1,6 @@ use typst::font::FontWeight; -use super::{Counter, CounterAction, CounterNode, CounterUpdate, LocalName, Numbering}; +use super::{Counter, CounterUpdate, LocalName, Numbering}; use crate::layout::{BlockNode, HNode, VNode}; use crate::meta::Count; use crate::prelude::*; @@ -92,9 +92,7 @@ impl Show for HeadingNode { let mut realized = self.body(); if let Some(numbering) = self.numbering(styles) { realized = - CounterNode::new(Counter::of(Self::id()), CounterAction::Get(numbering)) - .pack() - .spanned(self.span()) + Counter::of(Self::id()).display(numbering, false).spanned(self.span()) + HNode::new(Em::new(0.3).into()).with_weak(true).pack() + realized; } diff --git a/library/src/meta/mod.rs b/library/src/meta/mod.rs index 50b8627e..5ec40e42 100644 --- a/library/src/meta/mod.rs +++ b/library/src/meta/mod.rs @@ -1,6 +1,7 @@ //! Interaction between document parts. mod bibliography; +mod context; mod counter; mod document; mod figure; @@ -13,6 +14,7 @@ mod reference; mod state; pub use self::bibliography::*; +pub use self::context::*; pub use self::counter::*; pub use self::document::*; pub use self::figure::*; diff --git a/library/src/meta/outline.rs b/library/src/meta/outline.rs index 7cd26eba..a0a23897 100644 --- a/library/src/meta/outline.rs +++ b/library/src/meta/outline.rs @@ -120,11 +120,9 @@ impl Show for OutlineNode { let mut hidden = Content::empty(); for ancestor in &ancestors { if let Some(numbering) = ancestor.numbering(StyleChain::default()) { - let numbers = Counter::of(HeadingNode::id()).resolve( - vt, - ancestor.0.stable_id(), - &numbering, - )?; + let numbers = Counter::of(HeadingNode::id()) + .at(vt, ancestor.0.stable_id().unwrap())? + .display(vt, &numbering)?; hidden += numbers + SpaceNode::new().pack(); }; } @@ -138,11 +136,9 @@ impl Show for OutlineNode { // Format the numbering. let mut start = heading.body(); if let Some(numbering) = heading.numbering(StyleChain::default()) { - let numbers = Counter::of(HeadingNode::id()).resolve( - vt, - Some(stable_id), - &numbering, - )?; + let numbers = Counter::of(HeadingNode::id()) + .at(vt, stable_id)? + .display(vt, &numbering)?; start = numbers + SpaceNode::new().pack() + start; }; diff --git a/library/src/meta/query.rs b/library/src/meta/query.rs index 86419be0..23e310fe 100644 --- a/library/src/meta/query.rs +++ b/library/src/meta/query.rs @@ -9,10 +9,29 @@ use crate::prelude::*; pub fn query( /// The thing to search for. target: Target, - /// A function to format the results with. - format: Func, + /// The location. + #[external] + location: StableId, + /// The location before which to query. + #[named] + #[external] + before: StableId, + /// The location after which to query. + #[named] + #[external] + after: StableId, ) -> Value { - QueryNode::new(target.0, format).pack().into() + let selector = target.0; + let introspector = vm.vt.introspector; + let elements = if let Some(id) = args.named("before")? { + introspector.query_before(selector, id) + } else if let Some(id) = args.named("after")? { + introspector.query_after(selector, id) + } else { + let _: StableId = args.expect("id")?; + introspector.query(selector) + }; + elements.into() } /// A query target. @@ -33,31 +52,3 @@ cast_from_value! { Self(Selector::Node(id, None)) } } - -/// Executes a query. -/// -/// Display: Query -/// Category: special -#[node(Locatable, Show)] -struct QueryNode { - /// The thing to search for. - #[required] - target: Selector, - - /// The function to format the results with. - #[required] - format: Func, -} - -impl Show for QueryNode { - fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> { - if !vt.introspector.init() { - return Ok(Content::empty()); - } - - let id = self.0.stable_id().unwrap(); - let target = self.target(); - let (before, after) = vt.introspector.query_split(target, id); - Ok(self.format().call_vt(vt, [before.into(), after.into()])?.display()) - } -} diff --git a/library/src/meta/reference.rs b/library/src/meta/reference.rs index 0603ee4e..000080d8 100644 --- a/library/src/meta/reference.rs +++ b/library/src/meta/reference.rs @@ -114,8 +114,9 @@ impl Show for RefNode { bail!(self.span(), "only numbered elements can be referenced"); }; - let numbers = - Counter::of(node.id()).resolve(vt, node.stable_id(), &numbering.trimmed())?; + let numbers = Counter::of(node.id()) + .at(vt, node.stable_id().unwrap())? + .display(vt, &numbering.trimmed())?; Ok((supplement + numbers).linked(Link::Node(node.stable_id().unwrap()))) } diff --git a/library/src/meta/state.rs b/library/src/meta/state.rs index 6b521301..b19a2671 100644 --- a/library/src/meta/state.rs +++ b/library/src/meta/state.rs @@ -1,6 +1,6 @@ use std::fmt::{self, Debug, Formatter, Write}; -use ecow::EcoVec; +use ecow::{eco_vec, EcoVec}; use typst::eval::Tracer; use crate::prelude::*; @@ -21,91 +21,6 @@ pub fn state( Value::dynamic(State { key, init }) } -/// Call a method on a state. -pub fn state_method( - state: State, - method: &str, - mut args: Args, - span: Span, -) -> SourceResult<Value> { - let action = match method { - "get" => StateAction::Get(args.eat()?), - "final" => StateAction::Final(args.eat()?), - "update" => StateAction::Update(args.expect("value or function")?), - _ => bail!(span, "type state has no method `{}`", method), - }; - - args.finish()?; - - let content = StateNode::new(state, action).pack(); - Ok(Value::Content(content)) -} - -/// Executes an action on a state. -/// -/// Display: State -/// Category: special -#[node(Locatable, Show)] -pub struct StateNode { - /// The state. - #[required] - pub state: State, - - /// The action. - #[required] - pub action: StateAction, -} - -impl Show for StateNode { - fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> { - match self.action() { - StateAction::Get(func) => self.state().resolve(vt, self.0.stable_id(), func), - StateAction::Final(func) => self.state().resolve(vt, None, func), - StateAction::Update(_) => Ok(Content::empty()), - } - } -} - -/// The action to perform on the state. -#[derive(Clone, PartialEq, Hash)] -pub enum StateAction { - /// Displays the current state. - Get(Option<Func>), - /// Displays the final state. - Final(Option<Func>), - /// Updates the state, possibly based on the previous one. - Update(StateUpdate), -} - -cast_from_value! { - StateAction: "state action", -} - -impl Debug for StateAction { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Get(_) => f.pad("get(..)"), - Self::Final(_) => f.pad("final(..)"), - Self::Update(_) => f.pad("update(..)"), - } - } -} - -/// An update to perform on a state. -#[derive(Debug, Clone, PartialEq, Hash)] -pub enum StateUpdate { - /// Set the state to the specified value. - Set(Value), - /// Apply the given function to the state. - Func(Func), -} - -cast_from_value! { - StateUpdate, - v: Func => Self::Func(v), - v: Value => Self::Set(v), -} - /// A state. #[derive(Clone, PartialEq, Hash)] pub struct State { @@ -116,72 +31,92 @@ pub struct State { } impl State { - /// Display the state at the postition of the given stable id. - fn resolve( - &self, - vt: &mut Vt, - stop: Option<StableId>, - func: Option<Func>, - ) -> SourceResult<Content> { - if !vt.introspector.init() { - return Ok(Content::empty()); - } + /// Call a method on a state. + pub fn call_method( + self, + vm: &mut Vm, + method: &str, + mut args: Args, + span: Span, + ) -> SourceResult<Value> { + let value = match method { + "display" => self.display(args.eat()?).into(), + "at" => self.at(&mut vm.vt, args.expect("location")?)?, + "final" => self.final_(&mut vm.vt, args.expect("location")?)?, + "update" => self.update(args.expect("value or function")?).into(), + _ => bail!(span, "type state has no method `{}`", method), + }; + args.finish()?; + Ok(value) + } - let sequence = self.sequence( - vt.world, - TrackedMut::reborrow_mut(&mut vt.tracer), - TrackedMut::reborrow_mut(&mut vt.provider), - vt.introspector, - )?; - - Ok(match sequence.at(stop) { - Some(value) => { - if let Some(func) = func { - func.call_vt(vt, [value])?.display() - } else { - value.display() - } - } - None => Content::empty(), - }) + /// Display the current value of the state. + pub fn display(self, func: Option<Func>) -> Content { + DisplayNode::new(self, func).pack() + } + + /// Get the value of the state at the given location. + pub fn at(self, vt: &mut Vt, id: StableId) -> SourceResult<Value> { + let sequence = self.sequence(vt)?; + let offset = vt.introspector.query_before(self.selector(), id).len(); + Ok(sequence[offset].clone()) + } + + /// Get the value of the state at the final location. + pub fn final_(self, vt: &mut Vt, _: StableId) -> SourceResult<Value> { + let sequence = self.sequence(vt)?; + Ok(sequence.last().unwrap().clone()) + } + + /// Produce content that performs a state update. + pub fn update(self, update: StateUpdate) -> Content { + UpdateNode::new(self, update).pack() } /// Produce the whole sequence of states. /// /// This has to happen just once for all states, cutting down the number /// of state updates from quadratic to linear. + fn sequence(&self, vt: &mut Vt) -> SourceResult<EcoVec<Value>> { + self.sequence_impl( + vt.world, + TrackedMut::reborrow_mut(&mut vt.tracer), + TrackedMut::reborrow_mut(&mut vt.provider), + vt.introspector, + ) + } + + /// Memoized implementation of `sequence`. #[comemo::memoize] - fn sequence( + fn sequence_impl( &self, world: Tracked<dyn World>, tracer: TrackedMut<Tracer>, provider: TrackedMut<StabilityProvider>, introspector: Tracked<Introspector>, - ) -> SourceResult<StateSequence> { + ) -> SourceResult<EcoVec<Value>> { let mut vt = Vt { world, tracer, provider, introspector }; - let search = Selector::Node( - NodeId::of::<StateNode>(), - Some(dict! { "state" => self.clone() }), - ); - - let mut stops = EcoVec::new(); let mut state = self.init.clone(); + let mut stops = eco_vec![state.clone()]; - for node in introspector.query(search) { - let id = node.stable_id().unwrap(); - let node = node.to::<StateNode>().unwrap(); - - if let StateAction::Update(update) = node.action() { - match update { - StateUpdate::Set(value) => state = value, - StateUpdate::Func(func) => state = func.call_vt(&mut vt, [state])?, - } + for node in introspector.query(self.selector()) { + let node = node.to::<UpdateNode>().unwrap(); + match node.update() { + StateUpdate::Set(value) => state = value, + StateUpdate::Func(func) => state = func.call_vt(&mut vt, [state])?, } - - stops.push((id, state.clone())); + stops.push(state.clone()); } - Ok(StateSequence(stops)) + Ok(stops) + } + + /// The selector for this state's updates. + fn selector(&self) -> Selector { + Selector::Node( + NodeId::of::<UpdateNode>(), + Some(dict! { "state" => self.clone() }), + ) } } @@ -199,17 +134,70 @@ cast_from_value! { State: "state", } -/// A sequence of state values. -#[derive(Debug, Clone)] -struct StateSequence(EcoVec<(StableId, Value)>); +/// An update to perform on a state. +#[derive(Clone, PartialEq, Hash)] +pub enum StateUpdate { + /// Set the state to the specified value. + Set(Value), + /// Apply the given function to the state. + Func(Func), +} -impl StateSequence { - fn at(&self, stop: Option<StableId>) -> Option<Value> { - let entry = match stop { - Some(stop) => self.0.iter().find(|&&(id, _)| id == stop), - None => self.0.last(), - }; +impl Debug for StateUpdate { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad("..") + } +} + +cast_from_value! { + StateUpdate: "state update", + v: Func => Self::Func(v), + v: Value => Self::Set(v), +} + +/// Executes a display of a state. +/// +/// Display: State +/// Category: special +#[node(Locatable, Show)] +struct DisplayNode { + /// The state. + #[required] + state: State, + + /// The function to display the state with. + #[required] + func: Option<Func>, +} + +impl Show for DisplayNode { + fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> { + let id = self.0.stable_id().unwrap(); + let value = self.state().at(vt, id)?; + Ok(match self.func() { + Some(func) => func.call_vt(vt, [value])?.display(), + None => value.display(), + }) + } +} + +/// Executes a display of a state. +/// +/// Display: State +/// Category: special +#[node(Locatable, Show)] +struct UpdateNode { + /// The state. + #[required] + state: State, + + /// The update to perform on the state. + #[required] + update: StateUpdate, +} - entry.map(|(_, value)| value.clone()) +impl Show for UpdateNode { + fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> { + Ok(Content::empty()) } } |
