use std::num::NonZeroUsize; use std::str::FromStr; use comemo::{Track, Tracked, TrackedMut}; use ecow::{eco_format, eco_vec, EcoString, EcoVec}; use smallvec::{smallvec, SmallVec}; use typst_syntax::Span; use typst_utils::NonZeroExt; use crate::diag::{bail, At, HintedStrResult, SourceResult}; use crate::engine::{Engine, Route, Sink, Traced}; use crate::foundations::{ cast, elem, func, scope, select_where, ty, Args, Array, Construct, Content, Context, Element, Func, IntoValue, Label, LocatableSelector, NativeElement, Packed, Repr, Selector, Show, Smart, Str, StyleChain, Value, }; use crate::introspection::{Introspector, Locatable, Location, Tag}; use crate::layout::{Frame, FrameItem, PageElem}; use crate::math::EquationElem; use crate::model::{FigureElem, FootnoteElem, HeadingElem, Numbering, NumberingPattern}; use crate::routines::Routines; use crate::World; /// Counts through pages, elements, and more. /// /// With the counter function, you can access and modify counters for pages, /// headings, figures, and more. Moreover, you can define custom counters for /// other things you want to count. /// /// Since counters change throughout the course of the document, their current /// value is _contextual._ It is recommended to read the chapter on [context] /// before continuing here. /// /// # Accessing a counter { #accessing } /// To access the raw value of a counter, we can use the [`get`]($counter.get) /// function. This function returns an [array]: Counters can have multiple /// levels (in the case of headings for sections, subsections, and so on), and /// each item in the array corresponds to one level. /// /// ```example /// #set heading(numbering: "1.") /// /// = Introduction /// Raw value of heading counter is /// #context counter(heading).get() /// ``` /// /// # Displaying a counter { #displaying } /// Often, we want to display the value of a counter in a more human-readable /// way. To do that, we can call the [`display`]($counter.display) function on /// the counter. This function retrieves the current counter value and formats /// it either with a provided or with an automatically inferred [numbering]. /// /// ```example /// #set heading(numbering: "1.") /// /// = Introduction /// Some text here. /// /// = Background /// The current value is: #context { /// counter(heading).display() /// } /// /// Or in roman numerals: #context { /// counter(heading).display("I") /// } /// ``` /// /// # Modifying a counter { #modifying } /// To modify a counter, you can use the `step` and `update` methods: /// /// - The `step` method increases the value of the counter by one. Because /// counters can have multiple levels , it optionally takes a `level` /// argument. If given, the counter steps at the given depth. /// /// - The `update` method allows you to arbitrarily modify the counter. In its /// basic form, you give it an integer (or an array for multiple levels). For /// more flexibility, you can instead also give it a function that receives /// the current value and returns a new value. /// /// The heading counter is stepped before the heading is displayed, so /// `Analysis` gets the number seven even though the counter is at six after the /// second update. /// /// ```example /// #set heading(numbering: "1.") /// /// = Introduction /// #counter(heading).step() /// /// = Background /// #counter(heading).update(3) /// #counter(heading).update(n => n * 2) /// /// = Analysis /// Let's skip 7.1. /// #counter(heading).step(level: 2) /// /// == Analysis /// Still at #context { /// counter(heading).display() /// } /// ``` /// /// # Page counter /// The page counter is special. It is automatically stepped at each pagebreak. /// But like other counters, you can also step it manually. For example, you /// could have Roman page numbers for your preface, then switch to Arabic page /// numbers for your main content and reset the page counter to one. /// /// ```example /// >>> #set page( /// >>> height: 100pt, /// >>> margin: (bottom: 24pt, rest: 16pt), /// >>> ) /// #set page(numbering: "(i)") /// /// = Preface /// The preface is numbered with /// roman numerals. /// /// #set page(numbering: "1 / 1") /// #counter(page).update(1) /// /// = Main text /// Here, the counter is reset to one. /// We also display both the current /// page and total number of pages in /// Arabic numbers. /// ``` /// /// # Custom counters /// To define your own counter, call the `counter` function with a string as a /// key. This key identifies the counter globally. /// /// ```example /// #let mine = counter("mycounter") /// #context mine.display() \ /// #mine.step() /// #context mine.display() \ /// #mine.update(c => c * 3) /// #context mine.display() /// ``` /// /// # How to step /// When you define and use a custom counter, in general, you should first step /// the counter and then display it. This way, the stepping behaviour of a /// counter can depend on the element it is stepped for. If you were writing a /// counter for, let's say, theorems, your theorem's definition would thus first /// include the counter step and only then display the counter and the theorem's /// contents. /// /// ```example /// #let c = counter("theorem") /// #let theorem(it) = block[ /// #c.step() /// *Theorem #context c.display():* /// #it /// ] /// /// #theorem[$1 = 1$] /// #theorem[$2 < 3$] /// ``` /// /// The rationale behind this is best explained on the example of the heading /// counter: An update to the heading counter depends on the heading's level. By /// stepping directly before the heading, we can correctly step from `1` to /// `1.1` when encountering a level 2 heading. If we were to step after the /// heading, we wouldn't know what to step to. /// /// Because counters should always be stepped before the elements they count, /// they always start at zero. This way, they are at one for the first display /// (which happens after the first step). /// /// # Time travel /// Counters can travel through time! You can find out the final value of the /// counter before it is reached and even determine what the value was at any /// particular location in the document. /// /// ```example /// #let mine = counter("mycounter") /// /// = Values /// #context [ /// Value here: #mine.get() \ /// At intro: #mine.at() \ /// Final value: #mine.final() /// ] /// /// #mine.update(n => n + 3) /// /// = Introduction /// #lorem(10) /// /// #mine.step() /// #mine.step() /// ``` /// /// # Other kinds of state { #other-state } /// The `counter` type is closely related to [state] type. Read its /// documentation for more details on state management in Typst and why it /// doesn't just use normal variables for counters. #[ty(scope)] #[derive(Debug, Clone, PartialEq, Hash)] pub struct Counter(CounterKey); impl Counter { /// Create a new counter identified by a key. pub fn new(key: CounterKey) -> Counter { Self(key) } /// The counter for the given element. pub fn of(func: Element) -> Self { Self::new(CounterKey::Selector(Selector::Elem(func, None))) } /// Gets the current and final value of the state combined in one state. pub fn both( &self, engine: &mut Engine, location: Location, ) -> SourceResult { let sequence = self.sequence(engine)?; let offset = engine.introspector.query_count_before(&self.selector(), location); 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 = engine.introspector.page(location).get().saturating_sub(at_page.get()); at_state.step(NonZeroUsize::ONE, at_delta); let final_delta = engine.introspector.pages().get().saturating_sub(final_page.get()); final_state.step(NonZeroUsize::ONE, final_delta); } Ok(CounterState(smallvec![at_state.first(), final_state.first()])) } /// Gets the value of the counter at the given location. Always returns an /// array of integers, even if the counter has just one number. pub fn at_loc( &self, engine: &mut Engine, location: Location, ) -> SourceResult { let sequence = self.sequence(engine)?; let offset = engine.introspector.query_count_before(&self.selector(), location); let (mut state, page) = sequence[offset].clone(); if self.is_page() { let delta = engine.introspector.page(location).get().saturating_sub(page.get()); state.step(NonZeroUsize::ONE, delta); } Ok(state) } /// Displays the value of the counter at the given location. pub fn display_at_loc( &self, engine: &mut Engine, loc: Location, styles: StyleChain, numbering: &Numbering, ) -> SourceResult { let context = Context::new(Some(loc), Some(styles)); Ok(self .at_loc(engine, loc)? .display(engine, context.track(), numbering)? .display()) } /// 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, engine: &mut Engine, ) -> SourceResult> { self.sequence_impl( engine.routines, engine.world, engine.introspector, engine.traced, TrackedMut::reborrow_mut(&mut engine.sink), engine.route.track(), ) } /// Memoized implementation of `sequence`. #[comemo::memoize] fn sequence_impl( &self, routines: &Routines, world: Tracked, introspector: Tracked, traced: Tracked, sink: TrackedMut, route: Tracked, ) -> SourceResult> { let mut engine = Engine { routines, world, introspector, traced, sink, route: Route::extend(route).unnested(), }; let mut state = CounterState::init(matches!(self.0, CounterKey::Page)); let mut page = NonZeroUsize::ONE; let mut stops = eco_vec![(state.clone(), page)]; for elem in introspector.query(&self.selector()) { if self.is_page() { let prev = page; page = introspector.page(elem.location().unwrap()); let delta = page.get() - prev.get(); if delta > 0 { state.step(NonZeroUsize::ONE, delta); } } if let Some(update) = match elem.with::() { Some(countable) => countable.update(), None => Some(CounterUpdate::Step(NonZeroUsize::ONE)), } { state.update(&mut engine, update)?; } stops.push((state.clone(), page)); } Ok(stops) } /// The selector relevant for this counter's updates. fn selector(&self) -> Selector { let mut selector = select_where!(CounterUpdateElem, Key => self.0.clone()); if let CounterKey::Selector(key) = &self.0 { selector = Selector::Or(eco_vec![selector, key.clone()]); } selector } /// Whether this is the page counter. fn is_page(&self) -> bool { self.0 == CounterKey::Page } /// Shared implementation of displaying between `counter.display` and /// `CounterDisplayElem`. fn display_impl( &self, engine: &mut Engine, location: Location, numbering: Smart, both: bool, styles: Option, ) -> SourceResult { let numbering = numbering .custom() .or_else(|| { let styles = styles?; match self.0 { CounterKey::Page => PageElem::numbering_in(styles).clone(), CounterKey::Selector(Selector::Elem(func, _)) => { if func == HeadingElem::elem() { HeadingElem::numbering_in(styles).clone() } else if func == FigureElem::elem() { FigureElem::numbering_in(styles).clone() } else if func == EquationElem::elem() { EquationElem::numbering_in(styles).clone() } else if func == FootnoteElem::elem() { Some(FootnoteElem::numbering_in(styles).clone()) } else { None } } _ => None, } }) .unwrap_or_else(|| NumberingPattern::from_str("1.1").unwrap().into()); let state = if both { self.both(engine, location)? } else { self.at_loc(engine, location)? }; let context = Context::new(Some(location), styles); state.display(engine, context.track(), &numbering) } /// Selects all state updates. pub fn select_any() -> Selector { CounterUpdateElem::elem().select() } } #[scope] impl Counter { /// Create a new counter identified by a key. #[func(constructor)] pub fn construct( /// The key that identifies this counter. /// /// - If it is a string, creates a custom counter that is only affected /// by manual updates, /// - If it is the [`page`] function, counts through pages, /// - If it is a [selector], counts through elements that matches with the /// selector. For example, /// - provide an element function: counts elements of that type, /// - provide a [`{