typst_library/introspection/counter.rs
1use std::num::NonZeroUsize;
2use std::str::FromStr;
3
4use comemo::{Track, Tracked, TrackedMut};
5use ecow::{EcoString, EcoVec, eco_format, eco_vec};
6use smallvec::{SmallVec, smallvec};
7use typst_syntax::Span;
8use typst_utils::NonZeroExt;
9
10use crate::World;
11use crate::diag::{At, HintedStrResult, SourceResult, bail};
12use crate::engine::{Engine, Route, Sink, Traced};
13use crate::foundations::{
14 Args, Array, Construct, Content, Context, Element, Func, IntoValue, Label,
15 LocatableSelector, NativeElement, Packed, Repr, Selector, ShowFn, Smart, Str,
16 StyleChain, Value, cast, elem, func, scope, select_where, ty,
17};
18use crate::introspection::{Introspector, Locatable, Location, Tag, Unqueriable};
19use crate::layout::{Frame, FrameItem, PageElem};
20use crate::math::EquationElem;
21use crate::model::{FigureElem, FootnoteElem, HeadingElem, Numbering, NumberingPattern};
22use crate::routines::Routines;
23
24/// Counts through pages, elements, and more.
25///
26/// With the counter function, you can access and modify counters for pages,
27/// headings, figures, and more. Moreover, you can define custom counters for
28/// other things you want to count.
29///
30/// Since counters change throughout the course of the document, their current
31/// value is _contextual._ It is recommended to read the chapter on [context]
32/// before continuing here.
33///
34/// # Accessing a counter { #accessing }
35/// To access the raw value of a counter, we can use the [`get`]($counter.get)
36/// function. This function returns an [array]: Counters can have multiple
37/// levels (in the case of headings for sections, subsections, and so on), and
38/// each item in the array corresponds to one level.
39///
40/// ```example
41/// #set heading(numbering: "1.")
42///
43/// = Introduction
44/// Raw value of heading counter is
45/// #context counter(heading).get()
46/// ```
47///
48/// # Displaying a counter { #displaying }
49/// Often, we want to display the value of a counter in a more human-readable
50/// way. To do that, we can call the [`display`]($counter.display) function on
51/// the counter. This function retrieves the current counter value and formats
52/// it either with a provided or with an automatically inferred [numbering].
53///
54/// ```example
55/// #set heading(numbering: "1.")
56///
57/// = Introduction
58/// Some text here.
59///
60/// = Background
61/// The current value is: #context {
62/// counter(heading).display()
63/// }
64///
65/// Or in roman numerals: #context {
66/// counter(heading).display("I")
67/// }
68/// ```
69///
70/// # Modifying a counter { #modifying }
71/// To modify a counter, you can use the `step` and `update` methods:
72///
73/// - The `step` method increases the value of the counter by one. Because
74/// counters can have multiple levels , it optionally takes a `level`
75/// argument. If given, the counter steps at the given depth.
76///
77/// - The `update` method allows you to arbitrarily modify the counter. In its
78/// basic form, you give it an integer (or an array for multiple levels). For
79/// more flexibility, you can instead also give it a function that receives
80/// the current value and returns a new value.
81///
82/// The heading counter is stepped before the heading is displayed, so
83/// `Analysis` gets the number seven even though the counter is at six after the
84/// second update.
85///
86/// ```example
87/// #set heading(numbering: "1.")
88///
89/// = Introduction
90/// #counter(heading).step()
91///
92/// = Background
93/// #counter(heading).update(3)
94/// #counter(heading).update(n => n * 2)
95///
96/// = Analysis
97/// Let's skip 7.1.
98/// #counter(heading).step(level: 2)
99///
100/// == Analysis
101/// Still at #context {
102/// counter(heading).display()
103/// }
104/// ```
105///
106/// # Page counter
107/// The page counter is special. It is automatically stepped at each pagebreak.
108/// But like other counters, you can also step it manually. For example, you
109/// could have Roman page numbers for your preface, then switch to Arabic page
110/// numbers for your main content and reset the page counter to one.
111///
112/// ```example
113/// >>> #set page(
114/// >>> height: 100pt,
115/// >>> margin: (bottom: 24pt, rest: 16pt),
116/// >>> )
117/// #set page(numbering: "(i)")
118///
119/// = Preface
120/// The preface is numbered with
121/// roman numerals.
122///
123/// #set page(numbering: "1 / 1")
124/// #counter(page).update(1)
125///
126/// = Main text
127/// Here, the counter is reset to one.
128/// We also display both the current
129/// page and total number of pages in
130/// Arabic numbers.
131/// ```
132///
133/// # Custom counters
134/// To define your own counter, call the `counter` function with a string as a
135/// key. This key identifies the counter globally.
136///
137/// ```example
138/// #let mine = counter("mycounter")
139/// #context mine.display() \
140/// #mine.step()
141/// #context mine.display() \
142/// #mine.update(c => c * 3)
143/// #context mine.display()
144/// ```
145///
146/// # How to step
147/// When you define and use a custom counter, in general, you should first step
148/// the counter and then display it. This way, the stepping behaviour of a
149/// counter can depend on the element it is stepped for. If you were writing a
150/// counter for, let's say, theorems, your theorem's definition would thus first
151/// include the counter step and only then display the counter and the theorem's
152/// contents.
153///
154/// ```example
155/// #let c = counter("theorem")
156/// #let theorem(it) = block[
157/// #c.step()
158/// *Theorem #context c.display():*
159/// #it
160/// ]
161///
162/// #theorem[$1 = 1$]
163/// #theorem[$2 < 3$]
164/// ```
165///
166/// The rationale behind this is best explained on the example of the heading
167/// counter: An update to the heading counter depends on the heading's level. By
168/// stepping directly before the heading, we can correctly step from `1` to
169/// `1.1` when encountering a level 2 heading. If we were to step after the
170/// heading, we wouldn't know what to step to.
171///
172/// Because counters should always be stepped before the elements they count,
173/// they always start at zero. This way, they are at one for the first display
174/// (which happens after the first step).
175///
176/// # Time travel
177/// Counters can travel through time! You can find out the final value of the
178/// counter before it is reached and even determine what the value was at any
179/// particular location in the document.
180///
181/// ```example
182/// #let mine = counter("mycounter")
183///
184/// = Values
185/// #context [
186/// Value here: #mine.get() \
187/// At intro: #mine.at(<intro>) \
188/// Final value: #mine.final()
189/// ]
190///
191/// #mine.update(n => n + 3)
192///
193/// = Introduction <intro>
194/// #lorem(10)
195///
196/// #mine.step()
197/// #mine.step()
198/// ```
199///
200/// # Other kinds of state { #other-state }
201/// The `counter` type is closely related to [state] type. Read its
202/// documentation for more details on state management in Typst and why it
203/// doesn't just use normal variables for counters.
204#[ty(scope)]
205#[derive(Debug, Clone, PartialEq, Hash)]
206pub struct Counter(CounterKey);
207
208impl Counter {
209 /// Create a new counter identified by a key.
210 pub fn new(key: CounterKey) -> Counter {
211 Self(key)
212 }
213
214 /// The counter for the given element.
215 pub fn of(func: Element) -> Self {
216 Self::new(CounterKey::Selector(Selector::Elem(func, None)))
217 }
218
219 /// Gets the current and final value of the state combined in one state.
220 pub fn both(
221 &self,
222 engine: &mut Engine,
223 location: Location,
224 ) -> SourceResult<CounterState> {
225 let sequence = self.sequence(engine)?;
226 let offset = engine.introspector.query_count_before(&self.selector(), location);
227 let (mut at_state, at_page) = sequence[offset].clone();
228 let (mut final_state, final_page) = sequence.last().unwrap().clone();
229 if self.is_page() {
230 let at_delta =
231 engine.introspector.page(location).get().saturating_sub(at_page.get());
232 at_state.step(NonZeroUsize::ONE, at_delta as u64);
233 let final_delta =
234 engine.introspector.pages().get().saturating_sub(final_page.get());
235 final_state.step(NonZeroUsize::ONE, final_delta as u64);
236 }
237 Ok(CounterState(smallvec![at_state.first(), final_state.first()]))
238 }
239
240 /// Gets the value of the counter at the given location. Always returns an
241 /// array of integers, even if the counter has just one number.
242 pub fn at_loc(
243 &self,
244 engine: &mut Engine,
245 location: Location,
246 ) -> SourceResult<CounterState> {
247 let sequence = self.sequence(engine)?;
248 let offset = engine.introspector.query_count_before(&self.selector(), location);
249 let (mut state, page) = sequence[offset].clone();
250 if self.is_page() {
251 let delta =
252 engine.introspector.page(location).get().saturating_sub(page.get());
253 state.step(NonZeroUsize::ONE, delta as u64);
254 }
255 Ok(state)
256 }
257
258 /// Displays the value of the counter at the given location.
259 pub fn display_at_loc(
260 &self,
261 engine: &mut Engine,
262 loc: Location,
263 styles: StyleChain,
264 numbering: &Numbering,
265 ) -> SourceResult<Content> {
266 let context = Context::new(Some(loc), Some(styles));
267 Ok(self
268 .at_loc(engine, loc)?
269 .display(engine, context.track(), numbering)?
270 .display())
271 }
272
273 /// Produce the whole sequence of counter states.
274 ///
275 /// This has to happen just once for all counters, cutting down the number
276 /// of counter updates from quadratic to linear.
277 fn sequence(
278 &self,
279 engine: &mut Engine,
280 ) -> SourceResult<EcoVec<(CounterState, NonZeroUsize)>> {
281 self.sequence_impl(
282 engine.routines,
283 engine.world,
284 engine.introspector,
285 engine.traced,
286 TrackedMut::reborrow_mut(&mut engine.sink),
287 engine.route.track(),
288 )
289 }
290
291 /// Memoized implementation of `sequence`.
292 #[comemo::memoize]
293 fn sequence_impl(
294 &self,
295 routines: &Routines,
296 world: Tracked<dyn World + '_>,
297 introspector: Tracked<Introspector>,
298 traced: Tracked<Traced>,
299 sink: TrackedMut<Sink>,
300 route: Tracked<Route>,
301 ) -> SourceResult<EcoVec<(CounterState, NonZeroUsize)>> {
302 let mut engine = Engine {
303 routines,
304 world,
305 introspector,
306 traced,
307 sink,
308 route: Route::extend(route).unnested(),
309 };
310
311 let mut state = CounterState::init(matches!(self.0, CounterKey::Page));
312 let mut page = NonZeroUsize::ONE;
313 let mut stops = eco_vec![(state.clone(), page)];
314
315 for elem in introspector.query(&self.selector()) {
316 if self.is_page() {
317 let prev = page;
318 page = introspector.page(elem.location().unwrap());
319
320 let delta = page.get() - prev.get();
321 if delta > 0 {
322 state.step(NonZeroUsize::ONE, delta as u64);
323 }
324 }
325
326 if let Some(update) = match elem.with::<dyn Count>() {
327 Some(countable) => countable.update(),
328 None => Some(CounterUpdate::Step(NonZeroUsize::ONE)),
329 } {
330 state.update(&mut engine, update)?;
331 }
332
333 stops.push((state.clone(), page));
334 }
335
336 Ok(stops)
337 }
338
339 /// The selector relevant for this counter's updates.
340 fn selector(&self) -> Selector {
341 let mut selector = select_where!(CounterUpdateElem, key => self.0.clone());
342
343 if let CounterKey::Selector(key) = &self.0 {
344 selector = Selector::Or(eco_vec![selector, key.clone()]);
345 }
346
347 selector
348 }
349
350 /// Whether this is the page counter.
351 fn is_page(&self) -> bool {
352 self.0 == CounterKey::Page
353 }
354
355 /// Shared implementation of displaying between `counter.display` and
356 /// `CounterDisplayElem`.
357 fn display_impl(
358 &self,
359 engine: &mut Engine,
360 location: Location,
361 numbering: Smart<Numbering>,
362 both: bool,
363 styles: Option<StyleChain>,
364 ) -> SourceResult<Value> {
365 let numbering = numbering
366 .custom()
367 .or_else(|| {
368 let styles = styles?;
369 match self.0 {
370 CounterKey::Page => styles.get_cloned(PageElem::numbering),
371 CounterKey::Selector(Selector::Elem(func, _)) => {
372 if func == HeadingElem::ELEM {
373 styles.get_cloned(HeadingElem::numbering)
374 } else if func == FigureElem::ELEM {
375 styles.get_cloned(FigureElem::numbering)
376 } else if func == EquationElem::ELEM {
377 styles.get_cloned(EquationElem::numbering)
378 } else if func == FootnoteElem::ELEM {
379 Some(styles.get_cloned(FootnoteElem::numbering))
380 } else {
381 None
382 }
383 }
384 _ => None,
385 }
386 })
387 .unwrap_or_else(|| NumberingPattern::from_str("1.1").unwrap().into());
388
389 let state = if both {
390 self.both(engine, location)?
391 } else {
392 self.at_loc(engine, location)?
393 };
394
395 let context = Context::new(Some(location), styles);
396 state.display(engine, context.track(), &numbering)
397 }
398
399 /// Selects all state updates.
400 pub fn select_any() -> Selector {
401 CounterUpdateElem::ELEM.select()
402 }
403}
404
405#[scope]
406impl Counter {
407 /// Create a new counter identified by a key.
408 #[func(constructor)]
409 pub fn construct(
410 /// The key that identifies this counter globally.
411 ///
412 /// - If it is a string, creates a custom counter that is only affected
413 /// by manual updates,
414 /// - If it is the [`page`] function, counts through pages,
415 /// - If it is a [selector], counts through elements that match the
416 /// selector. For example,
417 /// - provide an element function: counts elements of that type,
418 /// - provide a [`where`]($function.where) selector:
419 /// counts a type of element with specific fields,
420 /// - provide a [`{<label>}`]($label): counts elements with that label.
421 key: CounterKey,
422 ) -> Counter {
423 Self::new(key)
424 }
425
426 /// Retrieves the value of the counter at the current location. Always
427 /// returns an array of integers, even if the counter has just one number.
428 ///
429 /// This is equivalent to `{counter.at(here())}`.
430 #[func(contextual)]
431 pub fn get(
432 &self,
433 engine: &mut Engine,
434 context: Tracked<Context>,
435 span: Span,
436 ) -> SourceResult<CounterState> {
437 let loc = context.location().at(span)?;
438 self.at_loc(engine, loc)
439 }
440
441 /// Displays the current value of the counter with a numbering and returns
442 /// the formatted output.
443 #[func(contextual)]
444 pub fn display(
445 self,
446 engine: &mut Engine,
447 context: Tracked<Context>,
448 span: Span,
449 /// A [numbering pattern or a function]($numbering), which specifies how
450 /// to display the counter. If given a function, that function receives
451 /// each number of the counter as a separate argument. If the amount of
452 /// numbers varies, e.g. for the heading argument, you can use an
453 /// [argument sink]($arguments).
454 ///
455 /// If this is omitted or set to `{auto}`, displays the counter with the
456 /// numbering style for the counted element or with the pattern
457 /// `{"1.1"}` if no such style exists.
458 #[default]
459 numbering: Smart<Numbering>,
460 /// If enabled, displays the current and final top-level count together.
461 /// Both can be styled through a single numbering pattern. This is used
462 /// by the page numbering property to display the current and total
463 /// number of pages when a pattern like `{"1 / 1"}` is given.
464 #[named]
465 #[default(false)]
466 both: bool,
467 ) -> SourceResult<Value> {
468 let loc = context.location().at(span)?;
469 self.display_impl(engine, loc, numbering, both, context.styles().ok())
470 }
471
472 /// Retrieves the value of the counter at the given location. Always returns
473 /// an array of integers, even if the counter has just one number.
474 ///
475 /// The `selector` must match exactly one element in the document. The most
476 /// useful kinds of selectors for this are [labels]($label) and
477 /// [locations]($location).
478 #[func(contextual)]
479 pub fn at(
480 &self,
481 engine: &mut Engine,
482 context: Tracked<Context>,
483 span: Span,
484 /// The place at which the counter's value should be retrieved.
485 selector: LocatableSelector,
486 ) -> SourceResult<CounterState> {
487 let loc = selector.resolve_unique(engine.introspector, context).at(span)?;
488 self.at_loc(engine, loc)
489 }
490
491 /// Retrieves the value of the counter at the end of the document. Always
492 /// returns an array of integers, even if the counter has just one number.
493 #[func(contextual)]
494 pub fn final_(
495 &self,
496 engine: &mut Engine,
497 context: Tracked<Context>,
498 span: Span,
499 ) -> SourceResult<CounterState> {
500 context.introspect().at(span)?;
501 let sequence = self.sequence(engine)?;
502 let (mut state, page) = sequence.last().unwrap().clone();
503 if self.is_page() {
504 let delta = engine.introspector.pages().get().saturating_sub(page.get());
505 state.step(NonZeroUsize::ONE, delta as u64);
506 }
507 Ok(state)
508 }
509
510 /// Increases the value of the counter by one.
511 ///
512 /// The update will be in effect at the position where the returned content
513 /// is inserted into the document. If you don't put the output into the
514 /// document, nothing happens! This would be the case, for example, if you
515 /// write `{let _ = counter(page).step()}`. Counter updates are always
516 /// applied in layout order and in that case, Typst wouldn't know when to
517 /// step the counter.
518 #[func]
519 pub fn step(
520 self,
521 span: Span,
522 /// The depth at which to step the counter. Defaults to `{1}`.
523 #[named]
524 #[default(NonZeroUsize::ONE)]
525 level: NonZeroUsize,
526 ) -> Content {
527 self.update(span, CounterUpdate::Step(level))
528 }
529
530 /// Updates the value of the counter.
531 ///
532 /// Just like with `step`, the update only occurs if you put the resulting
533 /// content into the document.
534 #[func]
535 pub fn update(
536 self,
537 span: Span,
538 /// If given an integer or array of integers, sets the counter to that
539 /// value. If given a function, that function receives the previous
540 /// counter value (with each number as a separate argument) and has to
541 /// return the new value (integer or array).
542 update: CounterUpdate,
543 ) -> Content {
544 CounterUpdateElem::new(self.0, update).pack().spanned(span)
545 }
546}
547
548impl Repr for Counter {
549 fn repr(&self) -> EcoString {
550 eco_format!("counter({})", self.0.repr())
551 }
552}
553
554/// Identifies a counter.
555#[derive(Debug, Clone, PartialEq, Hash)]
556pub enum CounterKey {
557 /// The page counter.
558 Page,
559 /// Counts elements matching the given selectors. Only works for
560 /// [locatable]($location/#locatable)
561 /// elements or labels.
562 Selector(Selector),
563 /// Counts through manual counters with the same key.
564 Str(Str),
565}
566
567cast! {
568 CounterKey,
569 self => match self {
570 Self::Page => PageElem::ELEM.into_value(),
571 Self::Selector(v) => v.into_value(),
572 Self::Str(v) => v.into_value(),
573 },
574 v: Str => Self::Str(v),
575 v: Label => Self::Selector(Selector::Label(v)),
576 v: Element => {
577 if v == PageElem::ELEM {
578 Self::Page
579 } else {
580 Self::Selector(LocatableSelector::from_value(v.into_value())?.0)
581 }
582 },
583 v: LocatableSelector => Self::Selector(v.0),
584}
585
586impl Repr for CounterKey {
587 fn repr(&self) -> EcoString {
588 match self {
589 Self::Page => "page".into(),
590 Self::Selector(selector) => selector.repr(),
591 Self::Str(str) => str.repr(),
592 }
593 }
594}
595
596/// An update to perform on a counter.
597#[derive(Debug, Clone, PartialEq, Hash)]
598pub enum CounterUpdate {
599 /// Set the counter to the specified state.
600 Set(CounterState),
601 /// Increase the number for the given level by one.
602 Step(NonZeroUsize),
603 /// Apply the given function to the counter's state.
604 Func(Func),
605}
606
607cast! {
608 CounterUpdate,
609 v: CounterState => Self::Set(v),
610 v: Func => Self::Func(v),
611}
612
613/// Elements that have special counting behaviour.
614pub trait Count {
615 /// Get the counter update for this element.
616 fn update(&self) -> Option<CounterUpdate>;
617}
618
619/// Counts through elements with different levels.
620#[derive(Debug, Clone, PartialEq, Hash)]
621pub struct CounterState(pub SmallVec<[u64; 3]>);
622
623impl CounterState {
624 /// Get the initial counter state for the key.
625 pub fn init(page: bool) -> Self {
626 // Special case, because pages always start at one.
627 Self(smallvec![u64::from(page)])
628 }
629
630 /// Advance the counter and return the numbers for the given heading.
631 pub fn update(
632 &mut self,
633 engine: &mut Engine,
634 update: CounterUpdate,
635 ) -> SourceResult<()> {
636 match update {
637 CounterUpdate::Set(state) => *self = state,
638 CounterUpdate::Step(level) => self.step(level, 1),
639 CounterUpdate::Func(func) => {
640 *self = func
641 .call(engine, Context::none().track(), self.0.iter().copied())?
642 .cast()
643 .at(func.span())?
644 }
645 }
646 Ok(())
647 }
648
649 /// Advance the number of the given level by the specified amount.
650 pub fn step(&mut self, level: NonZeroUsize, by: u64) {
651 let level = level.get();
652
653 while self.0.len() < level {
654 self.0.push(0);
655 }
656
657 self.0[level - 1] = self.0[level - 1].saturating_add(by);
658 self.0.truncate(level);
659 }
660
661 /// Get the first number of the state.
662 pub fn first(&self) -> u64 {
663 self.0.first().copied().unwrap_or(1)
664 }
665
666 /// Display the counter state with a numbering.
667 pub fn display(
668 &self,
669 engine: &mut Engine,
670 context: Tracked<Context>,
671 numbering: &Numbering,
672 ) -> SourceResult<Value> {
673 numbering.apply(engine, context, &self.0)
674 }
675}
676
677cast! {
678 CounterState,
679 self => Value::Array(self.0.into_iter().map(IntoValue::into_value).collect()),
680 num: u64 => Self(smallvec![num]),
681 array: Array => Self(array
682 .into_iter()
683 .map(Value::cast)
684 .collect::<HintedStrResult<_>>()?),
685}
686
687/// Executes an update of a counter.
688#[elem(Construct, Locatable, Count)]
689pub struct CounterUpdateElem {
690 /// The key that identifies the counter.
691 #[required]
692 key: CounterKey,
693
694 /// The update to perform on the counter.
695 #[required]
696 #[internal]
697 update: CounterUpdate,
698}
699
700impl Construct for CounterUpdateElem {
701 fn construct(_: &mut Engine, args: &mut Args) -> SourceResult<Content> {
702 bail!(args.span, "cannot be constructed manually");
703 }
704}
705
706impl Count for Packed<CounterUpdateElem> {
707 fn update(&self) -> Option<CounterUpdate> {
708 Some(self.update.clone())
709 }
710}
711
712/// Executes a display of a counter.
713#[elem(Construct, Unqueriable, Locatable)]
714pub struct CounterDisplayElem {
715 /// The counter.
716 #[required]
717 #[internal]
718 counter: Counter,
719
720 /// The numbering to display the counter with.
721 #[required]
722 #[internal]
723 numbering: Smart<Numbering>,
724
725 /// Whether to display both the current and final value.
726 #[required]
727 #[internal]
728 both: bool,
729}
730
731impl Construct for CounterDisplayElem {
732 fn construct(_: &mut Engine, args: &mut Args) -> SourceResult<Content> {
733 bail!(args.span, "cannot be constructed manually");
734 }
735}
736
737pub const COUNTER_DISPLAY_RULE: ShowFn<CounterDisplayElem> = |elem, engine, styles| {
738 Ok(elem
739 .counter
740 .display_impl(
741 engine,
742 elem.location().unwrap(),
743 elem.numbering.clone(),
744 elem.both,
745 Some(styles),
746 )?
747 .display())
748};
749
750/// An specialized handler of the page counter that tracks both the physical
751/// and the logical page counter.
752#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
753pub struct ManualPageCounter {
754 physical: NonZeroUsize,
755 logical: u64,
756}
757
758impl ManualPageCounter {
759 /// Create a new fast page counter, starting at 1.
760 pub fn new() -> Self {
761 Self { physical: NonZeroUsize::ONE, logical: 1 }
762 }
763
764 /// Get the current physical page counter state.
765 pub fn physical(&self) -> NonZeroUsize {
766 self.physical
767 }
768
769 /// Get the current logical page counter state.
770 pub fn logical(&self) -> u64 {
771 self.logical
772 }
773
774 /// Advance past a page.
775 pub fn visit(&mut self, engine: &mut Engine, page: &Frame) -> SourceResult<()> {
776 for (_, item) in page.items() {
777 match item {
778 FrameItem::Group(group) => self.visit(engine, &group.frame)?,
779 FrameItem::Tag(Tag::Start(elem, _)) => {
780 let Some(elem) = elem.to_packed::<CounterUpdateElem>() else {
781 continue;
782 };
783 if elem.key == CounterKey::Page {
784 let mut state = CounterState(smallvec![self.logical]);
785 state.update(engine, elem.update.clone())?;
786 self.logical = state.first();
787 }
788 }
789 _ => {}
790 }
791 }
792
793 Ok(())
794 }
795
796 /// Step past a page _boundary._
797 pub fn step(&mut self) {
798 self.physical = self.physical.saturating_add(1);
799 self.logical += 1;
800 }
801}
802
803impl Default for ManualPageCounter {
804 fn default() -> Self {
805 Self::new()
806 }
807}