diff --git a/README.md b/README.md index 22c3508d6..fd4081597 100644 --- a/README.md +++ b/README.md @@ -118,8 +118,9 @@ compilation. typst --watch file.typ ``` -If you prefer an integrated IDE-like experience, you can also check out the -[Typst web app][app], which is currently in public beta. +If you prefer an integrated IDE-like experience with autocompletion and instant +preview, you can also check out the [Typst web app][app], which is currently in +public beta. ## Build from source To build Typst yourself, you need to have the [latest stable Rust][rust] diff --git a/docs/src/reference/styling.md b/docs/src/reference/styling.md index 2a50019ef..4997bf197 100644 --- a/docs/src/reference/styling.md +++ b/docs/src/reference/styling.md @@ -88,7 +88,7 @@ fantasy encyclopedia. #set text(font: "Inria Serif") \~ #emph(it.body) #(counter(heading) - .get(it.numbering)) \~ + .display(it.numbering)) \~ ] = Dragon diff --git a/library/src/layout/measure.rs b/library/src/layout/measure.rs new file mode 100644 index 000000000..b116cbf8d --- /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 0ce292aae..b6ecce51f 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 eeb74a557..93ee08cee 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 14fea66df..2f951b922 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::().cloned() { + counter.call_method(vm, method, args, span) + } else if let Some(state) = dynamic.downcast::().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 9e692f571..7fb1aadfa 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 000000000..9e5428476 --- /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 { + 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 { + 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 27fd548fb..f033a04f2 100644 --- a/library/src/meta/counter.rs +++ b/library/src/meta/counter.rs @@ -22,6 +22,192 @@ pub fn counter( Value::dynamic(Counter::new(key)) } +/// Counts through pages, elements, and more. +#[derive(Clone, PartialEq, Hash)] +pub struct Counter(CounterKey); + +impl Counter { + /// Create a new counter from a key. + pub fn new(key: CounterKey) -> Self { + Self(key) + } + + /// 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 call_method( + self, + vm: &mut Vm, + method: &str, + mut args: Args, + span: Span, + ) -> SourceResult { + 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) + } + + /// Display the current value of the counter. + pub fn display(self, numbering: Numbering, both: bool) -> Content { + DisplayNode::new(self, numbering, both).pack() + } + + /// Get the value of the state at the given location. + pub fn at(&self, vt: &mut Vt, id: StableId) -> SourceResult { + 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) + } + + /// Get the value of the state at the final location. + pub fn final_(&self, vt: &mut Vt, _: StableId) -> SourceResult { + 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) + } + + /// Get the current and final value of the state combined in one state. + pub fn both(&self, vt: &mut Vt, id: StableId) -> SourceResult { + 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()])) + } + + /// Produce content that performs a state update. + pub fn update(self, update: CounterUpdate) -> Content { + UpdateNode::new(self, update).pack() + } + + /// 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, + ) -> SourceResult> { + 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_impl( + &self, + world: Tracked, + tracer: TrackedMut, + provider: TrackedMut, + introspector: Tracked, + ) -> SourceResult> { + let mut vt = Vt { world, tracer, provider, introspector }; + 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)]; + + for node in introspector.query(self.selector()) { + if self.is_page() { + let id = node.stable_id().unwrap(); + let prev = page; + page = introspector.page(id); + + let delta = page.get() - prev.get(); + if delta > 0 { + state.step(NonZeroUsize::ONE, delta); + } + } + + if let Some(update) = match node.to::() { + Some(node) => Some(node.update()), + None => match node.with::() { + Some(countable) => countable.update(), + None => Some(CounterUpdate::Step(NonZeroUsize::ONE)), + }, + } { + state.update(&mut vt, update)?; + } + + 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::(), + Some(dict! { "counter" => self.clone() }), + ); + + if let CounterKey::Selector(key) = &self.0 { + selector = Selector::Any(eco_vec![selector, key.clone()]); + } + + 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.0.fmt(f)?; + f.write_char(')') + } +} + +cast_from_value! { + Counter: "counter", +} + /// Identifies a counter. #[derive(Clone, PartialEq, Hash)] pub enum CounterKey { @@ -65,118 +251,8 @@ impl Debug for CounterKey { } } -/// Call a method on counter. -pub fn counter_method( - counter: Counter, - method: &str, - mut args: Args, - span: Span, -) -> SourceResult { - 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, -} - -impl Show for CounterNode { - fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult { - 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); - } - - 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()), - } - } -} - -/// 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(..)"), - } - } -} - /// An update to perform on a counter. -#[derive(Debug, Clone, PartialEq, Hash)] +#[derive(Clone, PartialEq, Hash)] pub enum CounterUpdate { /// Set the counter to the specified state. Set(CounterState), @@ -186,8 +262,14 @@ pub enum CounterUpdate { Func(Func), } +impl Debug for CounterUpdate { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad("..") + } +} + cast_from_value! { - CounterUpdate, + CounterUpdate: "counter update", v: CounterState => Self::Set(v), v: Func => Self::Func(v), } @@ -198,153 +280,6 @@ pub trait Count { fn update(&self) -> Option; } -/// 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 } - } - - /// The counter for the given node. - pub fn of(id: NodeId) -> Self { - Self::new(CounterKey::Selector(Selector::Node(id, None))) - } - - /// Display the value of the counter at the postition of the given stable - /// id. - pub fn resolve( - &self, - vt: &mut Vt, - stop: Option, - numbering: &Numbering, - ) -> SourceResult { - if !vt.introspector.init() { - return Ok(Content::empty()); - } - - 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(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. - #[comemo::memoize] - fn sequence( - &self, - world: Tracked, - tracer: TrackedMut, - provider: TrackedMut, - introspector: Tracked, - ) -> SourceResult { - let mut vt = Vt { world, tracer, provider, introspector }; - let mut search = Selector::Node( - NodeId::of::(), - 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 { - CounterKey::Selector(_) => smallvec![], - _ => smallvec![NonZeroUsize::ONE], - }); - - let is_page = self.key == CounterKey::Page; - let mut prev_page = NonZeroUsize::ONE; - - 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(); - if delta > 0 { - state.step(NonZeroUsize::ONE, delta); - } - prev_page = page; - } - - if let Some(update) = match node.to::() { - Some(counter) => match counter.action() { - CounterAction::Update(update) => Some(update), - _ => None, - }, - None => match node.with::() { - Some(countable) => countable.update(), - None => Some(CounterUpdate::Step(NonZeroUsize::ONE)), - }, - } { - state.update(&mut vt, update)?; - } - - stops.push((id, state.clone())); - } - - Ok(CounterSequence { stops, is_page }) - } -} - -impl Debug for Counter { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_str("counter(")?; - self.key.fmt(f)?; - f.write_char(')') - } -} - -cast_from_value! { - Counter: "counter", -} - -/// A sequence of counter values. -#[derive(Debug, Clone)] -struct CounterSequence { - stops: EcoVec<(StableId, CounterState)>, - is_page: bool, -} - -impl CounterSequence { - fn at(&self, stop: Option) -> Option { - let entry = match stop { - Some(stop) => self.stops.iter().find(|&&(id, _)| id == stop), - None => self.stops.last(), - }; - - if let Some((_, state)) = entry { - return Some(state.clone()); - } - - if self.is_page { - return Some(CounterState(smallvec![NonZeroUsize::ONE])); - } - - None - } - - fn single(&self, stop: Option) -> Option { - Some(*self.at(stop)?.0.first()?) - } -} - /// 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 { + Ok(numbering.apply_vt(vt, &self.0)?.display()) + } } cast_from_value! { @@ -388,3 +333,57 @@ cast_from_value! { .map(Value::cast) .collect::>()?), } + +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 { + 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 { + Ok(Content::empty()) + } +} diff --git a/library/src/meta/figure.rs b/library/src/meta/figure.rs index a92ff5e71..6f9011b8a 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 833421557..61605e67d 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 50b8627e2..5ec40e421 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 7cd26ebaa..a0a238973 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 86419be0e..23e310fe3 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 { - 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 0603ee4ec..000080d87 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 6b521301f..b19a26716 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 { - 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 { - 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), - /// Displays the final state. - Final(Option), - /// 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, - func: Option, - ) -> SourceResult { - 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 { + 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, - )?; + /// Display the current value of the state. + pub fn display(self, func: Option) -> Content { + DisplayNode::new(self, func).pack() + } - Ok(match sequence.at(stop) { - Some(value) => { - if let Some(func) = func { - func.call_vt(vt, [value])?.display() - } else { - value.display() - } - } - None => Content::empty(), - }) + /// Get the value of the state at the given location. + pub fn at(self, vt: &mut Vt, id: StableId) -> SourceResult { + 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 { + 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> { + 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, tracer: TrackedMut, provider: TrackedMut, introspector: Tracked, - ) -> SourceResult { + ) -> SourceResult> { let mut vt = Vt { world, tracer, provider, introspector }; - let search = Selector::Node( - NodeId::of::(), - 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::().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::().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::(), + 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) -> Option { - let entry = match stop { - Some(stop) => self.0.iter().find(|&&(id, _)| id == stop), - None => self.0.last(), - }; - - entry.map(|(_, value)| value.clone()) +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, +} + +impl Show for DisplayNode { + fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult { + 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, +} + +impl Show for UpdateNode { + fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult { + Ok(Content::empty()) } } diff --git a/src/eval/library.rs b/src/eval/library.rs index 485a766b6..eae342c28 100644 --- a/src/eval/library.rs +++ b/src/eval/library.rs @@ -6,7 +6,7 @@ use comemo::Tracked; use ecow::EcoString; use once_cell::sync::OnceCell; -use super::{Args, Dynamic, Module, Value}; +use super::{Args, Dynamic, Module, Value, Vm}; use crate::diag::SourceResult; use crate::doc::Document; use crate::geom::{Abs, Dir}; @@ -92,6 +92,7 @@ pub struct LangItems { pub math_frac: fn(num: Content, denom: Content) -> Content, /// Dispatch a method on a library value. pub library_method: fn( + vm: &mut Vm, dynamic: &Dynamic, method: &str, args: Args, diff --git a/src/eval/methods.rs b/src/eval/methods.rs index eef825ff8..324191ab3 100644 --- a/src/eval/methods.rs +++ b/src/eval/methods.rs @@ -4,6 +4,7 @@ use ecow::EcoString; use super::{Args, Str, Value, Vm}; use crate::diag::{At, SourceResult}; +use crate::model::StableId; use crate::syntax::Span; /// Call a method on a value. @@ -73,8 +74,11 @@ pub fn call( "func" => Value::Func(content.id().into()), "has" => Value::Bool(content.has(&args.expect::("field")?)), "at" => content.at(&args.expect::("field")?).at(span)?.clone(), - "page" => content.page(&vm.vt).at(span)?.into(), - "location" => content.location(&vm.vt).at(span)?.into(), + "id" => content + .stable_id() + .ok_or("this method can only be called on content returned by query()") + .at(span)? + .into(), _ => return missing(), }, @@ -137,7 +141,15 @@ pub fn call( }, Value::Dyn(dynamic) => { - return (vm.items.library_method)(&dynamic, method, args, span); + if let Some(&id) = dynamic.downcast::() { + match method { + "page" => vm.vt.introspector.page(id).into(), + "location" => vm.vt.introspector.location(id).into(), + _ => return missing(), + } + } else { + return (vm.items.library_method)(vm, &dynamic, method, args, span); + } } _ => return missing(), @@ -251,13 +263,7 @@ pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] { ("starts-with", true), ("trim", true), ], - "content" => &[ - ("func", false), - ("has", true), - ("at", true), - ("page", false), - ("location", false), - ], + "content" => &[("func", false), ("has", true), ("at", true), ("id", false)], "array" => &[ ("all", true), ("any", true), @@ -293,14 +299,15 @@ pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] { ], "function" => &[("where", true), ("with", true)], "arguments" => &[("named", false), ("pos", false)], + "stable id" => &[("page", false), ("location", false)], "counter" => &[ - ("get", true), + ("display", true), + ("at", true), ("final", true), - ("both", true), ("step", true), ("update", true), ], - "state" => &[("get", true), ("final", true), ("update", true)], + "state" => &[("display", true), ("at", true), ("final", true), ("update", true)], _ => &[], } } diff --git a/src/model/content.rs b/src/model/content.rs index 3c6619292..5317236e6 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -2,7 +2,6 @@ use std::any::TypeId; use std::fmt::{self, Debug, Formatter, Write}; use std::hash::{Hash, Hasher}; use std::iter::{self, Sum}; -use std::num::NonZeroUsize; use std::ops::{Add, AddAssign, Deref}; use ecow::{eco_format, EcoString, EcoVec}; @@ -10,10 +9,10 @@ use once_cell::sync::Lazy; use super::{ node, Behave, Behaviour, Fold, Guard, Locatable, Recipe, StableId, Style, StyleMap, - Synthesize, Vt, + Synthesize, }; use crate::diag::{SourceResult, StrResult}; -use crate::doc::{Location, Meta}; +use crate::doc::Meta; use crate::eval::{ cast_from_value, cast_to_value, Args, Cast, Func, FuncInfo, Str, Value, Vm, }; @@ -186,22 +185,6 @@ impl Content { self.field(field).ok_or_else(|| missing_field(field)) } - /// Determine the page of this content. - pub fn page(&self, vt: &Vt) -> StrResult { - match self.stable_id() { - Some(id) => Ok(vt.introspector.page(id)), - None => Err("this method can only be called on queried content".into()), - } - } - - /// Determine the location of this content. - pub fn location(&self, vt: &Vt) -> StrResult { - match self.stable_id() { - Some(id) => Ok(vt.introspector.location(id)), - None => Err("this method can only be called on queried content".into()), - } - } - /// The content's label. pub fn label(&self) -> Option<&Label> { match self.field("label")? { diff --git a/src/model/styles.rs b/src/model/styles.rs index 7b725af9a..b7d09774e 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -78,13 +78,7 @@ impl PartialEq for StyleMap { impl Debug for StyleMap { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if let [style] = self.0.as_slice() { - return style.fmt(f); - } - - let pieces: Vec<_> = - self.0.iter().map(|value| eco_format!("{value:?}")).collect(); - f.write_str(&pretty_array_like(&pieces, false)) + f.pad("..") } } diff --git a/src/model/typeset.rs b/src/model/typeset.rs index 162a2a677..8216d7a82 100644 --- a/src/model/typeset.rs +++ b/src/model/typeset.rs @@ -1,3 +1,4 @@ +use std::fmt::{self, Debug, Formatter}; use std::hash::Hash; use std::num::NonZeroUsize; @@ -6,7 +7,7 @@ use comemo::{Constraint, Track, Tracked, TrackedMut}; use super::{Content, Selector, StyleChain}; use crate::diag::SourceResult; use crate::doc::{Document, Element, Frame, Location, Meta}; -use crate::eval::Tracer; +use crate::eval::{cast_from_value, Tracer}; use crate::geom::{Point, Transform}; use crate::util::NonZeroExt; use crate::World; @@ -116,7 +117,7 @@ impl StabilityProvider { /// Stably identifies a call site across multiple layout passes. /// /// This struct is created by [`StabilityProvider::identify`]. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Copy, Clone, Eq, PartialEq, Hash)] pub struct StableId(u128, usize, usize); impl StableId { @@ -126,16 +127,27 @@ impl StableId { } } +impl Debug for StableId { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad("..") + } +} + +cast_from_value! { + StableId: "stable id", +} + /// Provides access to information about the document. pub struct Introspector { init: bool, + pages: usize, nodes: Vec<(Content, Location)>, } impl Introspector { /// Create a new introspector. pub fn new(frames: &[Frame]) -> Self { - let mut introspector = Self { init: false, nodes: vec![] }; + let mut introspector = Self { init: false, pages: frames.len(), nodes: vec![] }; for (i, frame) in frames.iter().enumerate() { let page = NonZeroUsize::new(1 + i).unwrap(); introspector.extract(frame, page, Transform::identity()); @@ -180,26 +192,37 @@ impl Introspector { self.init } - /// Query for all metadata matches for the given selector. + /// Query for all nodes for the given selector. pub fn query(&self, selector: Selector) -> Vec { self.all().filter(|node| selector.matches(node)).cloned().collect() } - /// Query for all metadata matches before the given id. - pub fn query_split( - &self, - selector: Selector, - id: StableId, - ) -> (Vec, Vec) { - let mut iter = self.all(); - let before = iter - .by_ref() - .take_while(|node| node.stable_id() != Some(id)) + /// Query for all nodes up to the given id. + pub fn query_before(&self, selector: Selector, id: StableId) -> Vec { + let mut matches = vec![]; + for node in self.all() { + if selector.matches(node) { + matches.push(node.clone()); + } + if node.stable_id() == Some(id) { + break; + } + } + matches + } + + /// Query for all nodes starting from the given id. + pub fn query_after(&self, selector: Selector, id: StableId) -> Vec { + self.all() + .skip_while(|node| node.stable_id() != Some(id)) .filter(|node| selector.matches(node)) .cloned() - .collect(); - let after = iter.filter(|node| selector.matches(node)).cloned().collect(); - (before, after) + .collect() + } + + /// The total number pages. + pub fn pages(&self) -> NonZeroUsize { + NonZeroUsize::new(self.pages).unwrap_or(NonZeroUsize::ONE) } /// Find the page number for the given stable id. diff --git a/tests/ref/meta/counter.png b/tests/ref/meta/counter.png index 679e748fb..7e9ea69db 100644 Binary files a/tests/ref/meta/counter.png and b/tests/ref/meta/counter.png differ diff --git a/tests/ref/meta/query.png b/tests/ref/meta/query.png index 809812020..facf2b89d 100644 Binary files a/tests/ref/meta/query.png and b/tests/ref/meta/query.png differ diff --git a/tests/ref/meta/state.png b/tests/ref/meta/state.png index d48e1dc3c..86d9da4a9 100644 Binary files a/tests/ref/meta/state.png and b/tests/ref/meta/state.png differ diff --git a/tests/typ/layout/page-marginals.typ b/tests/typ/layout/page-marginals.typ index d8ce9b790..d65591e6c 100644 --- a/tests/typ/layout/page-marginals.typ +++ b/tests/typ/layout/page-marginals.typ @@ -6,8 +6,8 @@ h(1fr) text(0.8em)[_Chapter 1_] }, - footer: align(center)[\~ #counter(page).get() \~], - background: counter(page).get(n => if n <= 2 { + footer: align(center)[\~ #counter(page).display() \~], + background: counter(page).display(n => if n <= 2 { place(center + horizon, circle(radius: 1cm, fill: luma(90%))) }) ) diff --git a/tests/typ/meta/counter.typ b/tests/typ/meta/counter.typ index 539af6b90..c3b976295 100644 --- a/tests/typ/meta/counter.typ +++ b/tests/typ/meta/counter.typ @@ -4,21 +4,21 @@ // Count with string key. #let mine = counter("mine!") -Final: #mine.final() \ +Final: #locate(loc => mine.final(loc).at(0)) \ #mine.step() -First: #mine.get() \ +First: #mine.display() \ #mine.update(7) -#mine.both("1 of 1") \ +#mine.display("1 of 1", both: true) \ #mine.step() #mine.step() -Second: #mine.get("I") +Second: #mine.display("I") #mine.update(n => n * 2) #mine.step() --- // Count labels. #let label = -#let count = counter(label).get() +#let count = counter(label).display() #let elem(it) = [#box(it) #label] #elem[hey, there!] #count \ @@ -31,13 +31,19 @@ Second: #mine.get("I") #counter(heading).step() = Alpha +In #counter(heading).display(). + == Beta -In #counter(heading).get(). #set heading(numbering: none) = Gamma #heading(numbering: "I.")[Delta] +At Beta, it was #locate(loc => { + let it = query(heading, loc).find(it => it.body == [Beta]) + numbering(it.numbering, ..counter(heading).at(it.id())) +}) + --- // Count figures. #figure(numbering: "A", caption: [Four 'A's])[_AAAA!_] diff --git a/tests/typ/meta/query.typ b/tests/typ/meta/query.typ index 85608e8e8..38eadfab5 100644 --- a/tests/typ/meta/query.typ +++ b/tests/typ/meta/query.typ @@ -7,7 +7,9 @@ header: { smallcaps[Typst Academy] h(1fr) - query(heading, (before, after) => { + locate(it => { + let after = query(heading, after: it) + let before = query(heading, before: it) let elem = if before.len() != 0 { before.last() } else if after.len() != 0 { @@ -28,3 +30,41 @@ = Approach #lorem(60) + +--- +#set page( + paper: "a7", + numbering: "1 / 1", + margin: (bottom: 1cm, rest: 0.5cm), +) + +#set figure(numbering: "I") +#show figure: set image(width: 80%) + += List of Figures +#locate(it => { + let elements = query(figure, after: it) + for it in elements [ + Figure + #numbering(it.numbering, + ..counter(figure).at(it.id())): + #it.caption + #box(width: 1fr, repeat[.]) + #counter(page).at(it.id()).first() \ + ] +}) + +#figure( + image("/glacier.jpg"), + caption: [Glacier melting], +) + +#figure( + rect[Just some stand-in text], + caption: [Stand-in text], +) + +#figure( + image("/tiger.jpg"), + caption: [Tiger world], +) diff --git a/tests/typ/meta/state.typ b/tests/typ/meta/state.typ index dd34deac2..8dfab05b6 100644 --- a/tests/typ/meta/state.typ +++ b/tests/typ/meta/state.typ @@ -1,18 +1,33 @@ // Test state. +--- +#let s = state("hey", "a") +#let double(it) = 2 * it + +#s.update(double) +#s.update(double) +$ 2 + 3 $ +#s.update(double) + +Is: #s.display(), +Was: #locate(id => { + let it = query(math.equation, id).first() + s.at(it.id()) +}). + --- #set page(width: 200pt) #set text(8pt) #let ls = state("lorem", lorem(1000).split(".")) #let loremum(count) = { - ls.get(list => list.slice(0, count).join(".").trim() + ".") + ls.display(list => list.slice(0, count).join(".").trim() + ".") ls.update(list => list.slice(count)) } #let fs = state("fader", red) #let trait(title) = block[ - #fs.get(color => text(fill: color)[ + #fs.display(color => text(fill: color)[ *#title:* #loremum(1) ]) #fs.update(color => color.lighten(30%))