From be7cfc85d08c545abfac08098b7b33b4bd71f37e Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sun, 27 Oct 2024 19:04:55 +0100 Subject: Split out four new crates (#5302) --- crates/typst-library/src/introspection/counter.rs | 856 +++++++++++++++++++++ crates/typst-library/src/introspection/here.rs | 52 ++ .../src/introspection/introspector.rs | 453 +++++++++++ crates/typst-library/src/introspection/locate.rs | 119 +++ crates/typst-library/src/introspection/location.rs | 113 +++ crates/typst-library/src/introspection/locator.rs | 350 +++++++++ crates/typst-library/src/introspection/metadata.rs | 38 + crates/typst-library/src/introspection/mod.rs | 53 ++ crates/typst-library/src/introspection/query.rs | 171 ++++ crates/typst-library/src/introspection/state.rs | 476 ++++++++++++ crates/typst-library/src/introspection/tag.rs | 73 ++ 11 files changed, 2754 insertions(+) create mode 100644 crates/typst-library/src/introspection/counter.rs create mode 100644 crates/typst-library/src/introspection/here.rs create mode 100644 crates/typst-library/src/introspection/introspector.rs create mode 100644 crates/typst-library/src/introspection/locate.rs create mode 100644 crates/typst-library/src/introspection/location.rs create mode 100644 crates/typst-library/src/introspection/locator.rs create mode 100644 crates/typst-library/src/introspection/metadata.rs create mode 100644 crates/typst-library/src/introspection/mod.rs create mode 100644 crates/typst-library/src/introspection/query.rs create mode 100644 crates/typst-library/src/introspection/state.rs create mode 100644 crates/typst-library/src/introspection/tag.rs (limited to 'crates/typst-library/src/introspection') diff --git a/crates/typst-library/src/introspection/counter.rs b/crates/typst-library/src/introspection/counter.rs new file mode 100644 index 00000000..2e7180c6 --- /dev/null +++ b/crates/typst-library/src/introspection/counter.rs @@ -0,0 +1,856 @@ +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, warning, 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 + /// `DisplayElem`, which will be deprecated. + 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?; + let CounterKey::Selector(Selector::Elem(func, _)) = self.0 else { + return None; + }; + + 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 + } + }) + .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) + } +} + +#[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 [`{