typst/library/src/meta/counter.rs

391 lines
11 KiB
Rust

use std::fmt::{self, Debug, Formatter, Write};
use std::str::FromStr;
use ecow::{eco_vec, EcoVec};
use smallvec::{smallvec, SmallVec};
use typst::eval::Tracer;
use super::{Numbering, NumberingPattern};
use crate::layout::PageNode;
use crate::prelude::*;
/// Count through pages, elements, and more.
///
/// Display: Counter
/// Category: meta
/// Returns: counter
#[func]
pub fn counter(
/// The key that identifies this counter.
key: CounterKey,
) -> Value {
Value::dynamic(Counter::new(key))
}
/// 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),
}
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))?;
}
Self::Selector(Selector::Node(id, None))
}
}
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),
}
}
}
/// 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,
}
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);
}
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)]
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 }
}
/// 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<StableId>,
numbering: &Numbering,
) -> SourceResult<Content> {
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<dyn World>,
tracer: TrackedMut<Tracer>,
provider: TrackedMut<StabilityProvider>,
introspector: Tracked<Introspector>,
) -> SourceResult<CounterSequence> {
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 {
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 let Some(delta) = NonZeroUsize::new(delta) {
state.step(delta);
}
prev_page = page;
}
if let Some(update) = match node.to::<CounterNode>() {
Some(counter) => match counter.action() {
CounterAction::Update(update) => Some(update),
_ => None,
},
None => match node.with::<dyn Count>() {
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<StableId>) -> Option<CounterState> {
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<StableId>) -> Option<NonZeroUsize> {
Some(*self.at(stop)?.0.first()?)
}
}
/// Counts through elements with different levels.
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct CounterState(pub SmallVec<[NonZeroUsize; 3]>);
impl CounterState {
/// Advance the counter and return the numbers for the given heading.
pub fn update(&mut self, vt: &mut Vt, update: CounterUpdate) -> SourceResult<()> {
match update {
CounterUpdate::Set(state) => *self = state,
CounterUpdate::Step(level) => self.step(level),
CounterUpdate::Func(func) => {
*self = func
.call_vt(vt, self.0.iter().copied().map(Into::into))?
.cast()
.at(func.span())?
}
}
Ok(())
}
/// Advance the top level number by the specified amount.
pub fn step(&mut self, level: NonZeroUsize) {
let level = level.get();
if self.0.len() >= level {
self.0[level - 1] = self.0[level - 1].saturating_add(1);
self.0.truncate(level);
}
while self.0.len() < level {
self.0.push(NonZeroUsize::ONE);
}
}
}
cast_from_value! {
CounterState,
num: NonZeroUsize => Self(smallvec![num]),
array: Array => Self(array
.into_iter()
.map(Value::cast)
.collect::<StrResult<_>>()?),
}