summaryrefslogtreecommitdiff
path: root/library/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-03-19 10:19:24 +0100
committerLaurenz <laurmaedje@gmail.com>2023-03-19 10:19:34 +0100
commit0ba99ab8aa523645e2f0a0d9f6333ad4e48f5daa (patch)
tree2de7ce344b6d579dca8ae346c68b1931a6e79b25 /library/src
parentc7f4d6b12ee3138c752402889eeaa603ac69351d (diff)
Measurement and introspection rework
Diffstat (limited to 'library/src')
-rw-r--r--library/src/layout/measure.rs20
-rw-r--r--library/src/layout/mod.rs2
-rw-r--r--library/src/layout/page.rs21
-rw-r--r--library/src/lib.rs13
-rw-r--r--library/src/math/mod.rs24
-rw-r--r--library/src/meta/context.rs66
-rw-r--r--library/src/meta/counter.rs485
-rw-r--r--library/src/meta/figure.rs14
-rw-r--r--library/src/meta/heading.rs6
-rw-r--r--library/src/meta/mod.rs2
-rw-r--r--library/src/meta/outline.rs16
-rw-r--r--library/src/meta/query.rs53
-rw-r--r--library/src/meta/reference.rs5
-rw-r--r--library/src/meta/state.rs274
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())
}
}