diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-09-11 14:40:22 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-09-11 14:40:22 +0200 |
| commit | b471ac7d590abd2398ce25193b4e4df373bf2e9c (patch) | |
| tree | b5f7a6fdc807ee3340a4f42b0ad3cc563fe45429 /crates/typst-library/src/meta | |
| parent | 8f36fca68447a5d42a3d54b5fac7e5546ee244be (diff) | |
First-class types
Makes types first-class values.
Diffstat (limited to 'crates/typst-library/src/meta')
| -rw-r--r-- | crates/typst-library/src/meta/bibliography.rs | 37 | ||||
| -rw-r--r-- | crates/typst-library/src/meta/context.rs | 83 | ||||
| -rw-r--r-- | crates/typst-library/src/meta/counter.rs | 373 | ||||
| -rw-r--r-- | crates/typst-library/src/meta/document.rs | 5 | ||||
| -rw-r--r-- | crates/typst-library/src/meta/figure.rs | 82 | ||||
| -rw-r--r-- | crates/typst-library/src/meta/footnote.rs | 46 | ||||
| -rw-r--r-- | crates/typst-library/src/meta/heading.rs | 37 | ||||
| -rw-r--r-- | crates/typst-library/src/meta/link.rs | 26 | ||||
| -rw-r--r-- | crates/typst-library/src/meta/metadata.rs | 15 | ||||
| -rw-r--r-- | crates/typst-library/src/meta/mod.rs | 49 | ||||
| -rw-r--r-- | crates/typst-library/src/meta/numbering.rs | 9 | ||||
| -rw-r--r-- | crates/typst-library/src/meta/outline.rs | 73 | ||||
| -rw-r--r-- | crates/typst-library/src/meta/query.rs | 49 | ||||
| -rw-r--r-- | crates/typst-library/src/meta/reference.rs | 30 | ||||
| -rw-r--r-- | crates/typst-library/src/meta/state.rs | 236 |
15 files changed, 490 insertions, 660 deletions
diff --git a/crates/typst-library/src/meta/bibliography.rs b/crates/typst-library/src/meta/bibliography.rs index d871db23..4f1cc32c 100644 --- a/crates/typst-library/src/meta/bibliography.rs +++ b/crates/typst-library/src/meta/bibliography.rs @@ -30,8 +30,8 @@ use crate::text::TextElem; /// /// As soon as you add a bibliography somewhere in your document, you can start /// citing things with reference syntax (`[@key]`) or explicit calls to the -/// [citation]($func/cite) function (`[#cite("key")]`). The bibliography will -/// only show entries for works that were referenced in the document. +/// [citation]($cite) function (`[#cite("key")]`). The bibliography will only +/// show entries for works that were referenced in the document. /// /// # Example /// ```example @@ -43,10 +43,7 @@ use crate::text::TextElem; /// /// #bibliography("works.bib") /// ``` -/// -/// Display: Bibliography -/// Category: meta -#[element(Locatable, Synthesize, Show, Finalize, LocalName)] +#[elem(Locatable, Synthesize, Show, Finalize, LocalName)] pub struct BibliographyElem { /// Path to a Hayagriva `.yml` or BibLaTeX `.bib` file. #[required] @@ -78,8 +75,8 @@ pub struct BibliographyElem { /// The title of the bibliography. /// - /// - When set to `{auto}`, an appropriate title for the [text - /// language]($func/text.lang) will be used. This is the default. + /// - When set to `{auto}`, an appropriate title for the + /// [text language]($text.lang) will be used. This is the default. /// - When set to `{none}`, the bibliography will not have a title. /// - A custom title can be set by passing content. /// @@ -109,7 +106,7 @@ cast! { impl BibliographyElem { /// Find the document's bibliography. pub fn find(introspector: Tracked<Introspector>) -> StrResult<Self> { - let mut iter = introspector.query(&Self::func().select()).into_iter(); + let mut iter = introspector.query(&Self::elem().select()).into_iter(); let Some(elem) = iter.next() else { bail!("the document does not contain a bibliography"); }; @@ -124,7 +121,7 @@ impl BibliographyElem { /// Whether the bibliography contains the given key. pub fn has(vt: &Vt, key: &str) -> bool { vt.introspector - .query(&Self::func().select()) + .query(&Self::elem().select()) .into_iter() .flat_map(|elem| { let elem = elem.to::<Self>().unwrap(); @@ -289,8 +286,8 @@ impl BibliographyStyle { /// Cite a work from the bibliography. /// -/// Before you starting citing, you need to add a -/// [bibliography]($func/bibliography) somewhere in your document. +/// Before you starting citing, you need to add a [bibliography]($bibliography) +/// somewhere in your document. /// /// # Example /// ```example @@ -304,13 +301,10 @@ impl BibliographyStyle { /// ``` /// /// # Syntax -/// This function indirectly has dedicated syntax. [References]($func/ref) -/// can be used to cite works from the bibliography. The label then -/// corresponds to the citation key. -/// -/// Display: Citation -/// Category: meta -#[element(Locatable, Synthesize, Show)] +/// This function indirectly has dedicated syntax. [References]($ref) can be +/// used to cite works from the bibliography. The label then corresponds to the +/// citation key. +#[elem(Locatable, Synthesize, Show)] pub struct CiteElem { /// The citation keys that identify the elements that shall be cited in /// the bibliography. @@ -329,7 +323,6 @@ pub struct CiteElem { /// /// #bibliography("works.bib") /// ``` - #[positional] pub supplement: Option<Content>, /// Whether the citation should include brackets. @@ -435,8 +428,8 @@ impl Works { let citations = vt .introspector .query(&Selector::Or(eco_vec![ - RefElem::func().select(), - CiteElem::func().select(), + RefElem::elem().select(), + CiteElem::elem().select(), ])) .into_iter() .map(|elem| match elem.to::<RefElem>() { diff --git a/crates/typst-library/src/meta/context.rs b/crates/typst-library/src/meta/context.rs index a42c6980..3a82a925 100644 --- a/crates/typst-library/src/meta/context.rs +++ b/crates/typst-library/src/meta/context.rs @@ -2,9 +2,9 @@ use crate::prelude::*; /// Provides access to the location of content. /// -/// This is useful in combination with [queries]($func/query), -/// [counters]($func/counter), [state]($func/state), and [links]($func/link). -/// See their documentation for more details. +/// This is useful in combination with [queries]($query), [counters]($counter), +/// [state]($state), and [links]($link). See their documentation for more +/// details. /// /// ```example /// #locate(loc => [ @@ -12,44 +12,10 @@ use crate::prelude::*; /// #loc.position()! /// ]) /// ``` -/// -/// ## Methods -/// ### page() -/// Returns the page number for this location. -/// -/// Note that this does not return the value of the [page counter]($func/counter) -/// at this location, but the true page number (starting from one). -/// -/// If you want to know the value of the page counter, use -/// `{counter(page).at(loc)}` instead. -/// -/// - returns: integer -/// -/// ### position() -/// Returns a dictionary with the page number and the x, y position for this -/// location. The page number starts at one and the coordinates are measured -/// from the top-left of the page. -/// -/// If you only need the page number, use `page()` instead as it allows Typst -/// to skip unnecessary work. -/// -/// - returns: dictionary -/// -/// ### page-numbering() -/// Returns the page numbering pattern of the page at this location. This can be -/// used when displaying the page counter in order to obtain the local numbering. -/// This is useful if you are building custom indices or outlines. -/// -/// If the page numbering is set to `none` at that location, this function returns `none`. -/// -/// - returns: string or function or none -/// -/// Display: Locate -/// Category: meta #[func] pub fn locate( - /// A function that receives a `location`. Its return value is displayed - /// in the document. + /// A function that receives a [`location`]($location). Its return value is + /// displayed in the document. /// /// This function is called once for each time the content returned by /// `locate` appears in the document. That makes it possible to generate @@ -60,10 +26,7 @@ pub fn locate( } /// Executes a `locate` call. -/// -/// Display: Locate -/// Category: special -#[element(Locatable, Show)] +#[elem(Locatable, Show)] struct LocateElem { /// The function to call with the location. #[required] @@ -83,9 +46,9 @@ impl Show for LocateElem { /// Provides access to active styles. /// /// The styles are currently opaque and only useful in combination with the -/// [`measure`]($func/measure) function. See its documentation for more details. -/// In the future, the provided styles might also be directly accessed to look -/// up styles defined by [set rules]($styling/#set-rules). +/// [`measure`]($measure) function. See its documentation for more details. In +/// the future, the provided styles might also be directly accessed to look up +/// styles defined by [set rules]($styling/#set-rules). /// /// ```example /// #let thing(body) = style(styles => { @@ -96,9 +59,6 @@ impl Show for LocateElem { /// #thing[Hey] \ /// #thing[Welcome] /// ``` -/// -/// Display: Style -/// Category: meta #[func] pub fn style( /// A function to call with the styles. Its return value is displayed @@ -113,10 +73,7 @@ pub fn style( } /// Executes a style access. -/// -/// Display: Style -/// Category: special -#[element(Show)] +#[elem(Show)] struct StyleElem { /// The function to call with the styles. #[required] @@ -134,10 +91,8 @@ impl Show for StyleElem { /// (width and height). /// /// The given function must accept a single parameter, `size`, which is a -/// dictionary with keys `width` and `height`, both of type -/// [`length`]($type/length). +/// dictionary with keys `width` and `height`, both of type [`length`]($length). /// - /// ```example /// #let text = lorem(30) /// #layout(size => style(styles => [ @@ -155,9 +110,9 @@ impl Show for StyleElem { /// and a height of `{400pt}`, then the specified function will be given the /// parameter `{(width: 800pt, height: 400pt)}`. If it placed directly into the /// page it receives the page's dimensions minus its margins. This is mostly -/// useful in combination with [measurement]($func/measure). +/// useful in combination with [measurement]($measure). /// -/// You can also use this function to resolve [`ratio`]($type/ratio) to fixed +/// You can also use this function to resolve [`ratio`]($ratio) to fixed /// lengths. This might come in handy if you're building your own layout /// abstractions. /// @@ -170,16 +125,13 @@ impl Show for StyleElem { /// /// Note that this function will provide an infinite width or height if one of /// the page width or height is `auto`, respectively. -/// -/// Display: Layout -/// Category: meta #[func] pub fn layout( /// A function to call with the outer container's size. Its return value is /// displayed in the document. /// - /// The container's size is given as a [dictionary]($type/dictionary) with - /// the keys `width` and `height`. + /// The container's size is given as a [dictionary]($dictionary) with the + /// keys `width` and `height`. /// /// This function is called once for each time the content returned by /// `layout` appears in the document. That makes it possible to generate @@ -190,10 +142,7 @@ pub fn layout( } /// Executes a `layout` call. -/// -/// Display: Layout -/// Category: special -#[element(Layout)] +#[elem(Layout)] struct LayoutElem { /// The function to call with the outer container's (or page's) size. #[required] diff --git a/crates/typst-library/src/meta/counter.rs b/crates/typst-library/src/meta/counter.rs index 6c437469..a2a63e81 100644 --- a/crates/typst-library/src/meta/counter.rs +++ b/crates/typst-library/src/meta/counter.rs @@ -17,15 +17,14 @@ use crate::prelude::*; /// headings, figures, and more. Moreover, you can define custom counters for /// other things you want to count. /// -/// ## Displaying a counter { #displaying } +/// # Displaying a counter { #displaying } /// To display the current value of the heading counter, you call the `counter` /// function with the `key` set to `heading` and then call the `display` method /// on the counter. To see any output, you also have to enable heading -/// [numbering]($func/heading.numbering). +/// [numbering]($heading.numbering). /// /// The `display` method optionally takes an argument telling it how to format -/// the counter. This can be a [numbering pattern or a -/// function]($func/numbering). +/// the counter. This can be a [numbering pattern or a function]($numbering). /// /// ```example /// #set heading(numbering: "1.") @@ -41,7 +40,7 @@ use crate::prelude::*; /// #counter(heading).display("I") /// ``` /// -/// ## Modifying a counter { #modifying } +/// # 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 @@ -76,7 +75,6 @@ use crate::prelude::*; /// Still at #counter(heading).display(). /// ``` /// -/// ## Custom counters { #custom-counters } /// To define your own counter, call the `counter` function with a string as a /// key. This key identifies the counter globally. /// @@ -89,7 +87,7 @@ use crate::prelude::*; /// #mine.display() \ /// ``` /// -/// ## How to step { #how-to-step } +/// # 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 @@ -118,7 +116,7 @@ use crate::prelude::*; /// they always start at zero. This way, they are at one for the first display /// (which happens after the first step). /// -/// ## Page counter { #page-counter } +/// # 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 @@ -145,7 +143,7 @@ use crate::prelude::*; /// Arabic numbers. /// ``` /// -/// ## Time travel { #time-travel } +/// # 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. @@ -177,16 +175,16 @@ use crate::prelude::*; /// /// Let's dissect what happens in the example above: /// -/// - We call [`locate`]($func/locate) to get access to the current location in -/// the document. We then pass this location to our counter's `at` method to -/// get its value at the current location. The `at` method always returns an -/// array because counters can have multiple levels. As the counter starts at -/// zero, the first value is thus `{(0,)}`. +/// - We call [`locate`]($locate) to get access to the current location in the +/// document. We then pass this location to our counter's `at` method to get +/// its value at the current location. The `at` method always returns an array +/// because counters can have multiple levels. As the counter starts at zero, +/// the first value is thus `{(0,)}`. /// -/// - We now [`query`]($func/query) the document for all elements with the +/// - We now [`query`]($query) the document for all elements with the /// `{<intro>}` label. The result is an array from which we extract the first -/// (and only) element's [location]($type/content.location). We then look up -/// the value of the counter at that location. The first update to the counter +/// (and only) element's [location]($content.location). We then look up the +/// value of the counter at that location. The first update to the counter /// sets it to `{0 + 3 = 3}`. At the introduction heading, the value is thus /// `{(3,)}`. /// @@ -196,180 +194,31 @@ use crate::prelude::*; /// which one doesn't matter. After the heading follow two calls to `step()`, /// so the final value is `{(5,)}`. /// -/// ## Other kinds of state { #other-state } -/// The `counter` function is closely related to [state]($func/state) function. -/// Read its documentation for more details on state management in Typst and -/// why it doesn't just use normal variables for counters. -/// -/// ## Methods -/// ### display() -/// Displays the value of the counter. -/// -/// - numbering: string or function (positional) -/// A [numbering pattern or a function]($func/numbering), which specifies how -/// to display the counter. If given a function, that function receives each -/// number of the counter as a separate argument. If the amount of numbers -/// varies, e.g. for the heading argument, you can use an -/// [argument sink]($type/arguments). -/// -/// If this is omitted, displays the counter with the numbering style for the -/// counted element or with the pattern `{"1.1"}` if no such style exists. -/// -/// - both: boolean (named) -/// If enabled, displays the current and final top-level count together. Both -/// can be styled through a single numbering pattern. This is used by the page -/// numbering property to display the current and total number of pages when a -/// pattern like `{"1 / 1"}` is given. -/// -/// - returns: content -/// -/// ### step() -/// Increases the value of the counter by one. -/// -/// The update will be in effect at the position where the returned content is -/// inserted into the document. If you don't put the output into the document, -/// nothing happens! This would be the case, for example, if you write -/// `{let _ = counter(page).step()}`. Counter updates are always applied in -/// layout order and in that case, Typst wouldn't know when to step the counter. -/// -/// - level: integer (named) -/// The depth at which to step the counter. Defaults to `{1}`. -/// -/// - returns: content -/// -/// ### update() -/// Updates the value of the counter. -/// -/// Just like with `step`, the update only occurs if you put the resulting -/// content into the document. -/// -/// - value: integer or array or function (positional, required) -/// If given an integer or array of integers, sets the counter to that value. -/// If given a function, that function receives the previous counter value -/// (with each number as a separate argument) and has to return the new -/// value (integer or array). -/// -/// - returns: content -/// -/// ### at() -/// Gets the value of the counter at the given location. Always returns an -/// array of integers, even if the counter has just one number. -/// -/// - location: location (positional, required) -/// The location at which the counter value should be retrieved. A suitable -/// location can be retrieved from [`locate`]($func/locate) or -/// [`query`]($func/query). -/// -/// - returns: array -/// -/// ### final() -/// Gets the value of the counter at the end of the document. Always returns an -/// array of integers, even if the counter has just one number. -/// -/// - location: location (positional, required) -/// Can be an arbitrary location, as its value is irrelevant for the method's -/// return value. Why is it required then? Typst has to evaluate parts of your -/// code multiple times to determine all counter values. By only allowing this -/// method within [`locate`]($func/locate) calls, the amount of code that can -/// depend on the method's result is reduced. If you could call `final` -/// directly at the top level of a module, the evaluation of the whole module -/// and its exports could depend on the counter's value. -/// -/// - returns: array -/// -/// Display: Counter -/// Category: meta -#[func] -pub fn counter( - /// The key that identifies this counter. - /// - /// - If it is a string, creates a custom counter that is only affected by - /// manual updates, - /// - If this is a `{<label>}`, counts through all elements with that label, - /// - If this is an element function or selector, counts through its elements, - /// - If this is the [`page`]($func/page) function, counts through pages. - key: CounterKey, -) -> Counter { - Counter::new(key) -} - -/// Counts through pages, elements, and more. +/// # Other kinds of state { #other-state } +/// The `counter` type is closely related to [state]($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(Clone, PartialEq, Hash)] pub struct Counter(CounterKey); impl Counter { - /// Create a new counter from a key. - pub fn new(key: CounterKey) -> Self { + /// 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: ElemFunc) -> Self { - Self::new(CounterKey::Selector(Selector::Elem(func, None))) - } - - /// Call a method on counter. - #[tracing::instrument(skip(vm))] - 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()?, args.named("both")?.unwrap_or(false)) - .into_value(), - "step" => self - .update(CounterUpdate::Step( - args.named("level")?.unwrap_or(NonZeroUsize::ONE), - )) - .into_value(), - "update" => self.update(args.expect("value or function")?).into_value(), - "at" => self.at(&mut vm.vt, args.expect("location")?)?.into_value(), - "final" => self.final_(&mut vm.vt, args.expect("location")?)?.into_value(), - _ => bail!(span, "type counter has no method `{}`", method), - }; - args.finish()?; - Ok(value) - } - - /// Display the current value of the counter. - pub fn display(self, numbering: Option<Numbering>, both: bool) -> Content { - DisplayElem::new(self, numbering, both).pack() - } - - /// Get the value of the state at the given location. - pub fn at(&self, vt: &mut Vt, location: Location) -> SourceResult<CounterState> { - let sequence = self.sequence(vt)?; - let offset = vt.introspector.query(&self.selector().before(location, true)).len(); - let (mut state, page) = sequence[offset].clone(); - if self.is_page() { - let delta = vt.introspector.page(location).get().saturating_sub(page.get()); - state.step(NonZeroUsize::ONE, delta); - } - - Ok(state) - } - - /// Get the value of the state at the final location. - pub fn final_(&self, vt: &mut Vt, _: Location) -> 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().saturating_sub(page.get()); - state.step(NonZeroUsize::ONE, delta); - } - Ok(state) + pub fn of(func: Element) -> Self { + Self::construct(CounterKey::Selector(Selector::Elem(func, None))) } - /// Get the current and final value of the state combined in one state. + /// Gets the current and final value of the state combined in one state. pub fn both(&self, vt: &mut Vt, location: Location) -> SourceResult<CounterState> { let sequence = self.sequence(vt)?; let offset = vt .introspector - .query(&Selector::before(self.selector(), location, true)) + .query(&self.selector().before(location.into(), true)) .len(); let (mut at_state, at_page) = sequence[offset].clone(); let (mut final_state, final_page) = sequence.last().unwrap().clone(); @@ -384,12 +233,7 @@ impl Counter { Ok(CounterState(smallvec![at_state.first(), final_state.first()])) } - /// Produce content that performs a state update. - pub fn update(self, update: CounterUpdate) -> Content { - UpdateElem::new(self.0, update).pack() - } - - /// Produce the whole sequence of counter states. + /// Produces 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. @@ -462,7 +306,7 @@ impl Counter { /// The selector relevant for this counter's updates. fn selector(&self) -> Selector { let mut selector = - Selector::Elem(UpdateElem::func(), Some(dict! { "key" => self.0.clone() })); + Selector::Elem(UpdateElem::elem(), Some(dict! { "key" => self.0.clone() })); if let CounterKey::Selector(key) = &self.0 { selector = Selector::Or(eco_vec![selector, key.clone()]); @@ -477,6 +321,140 @@ impl Counter { } } +#[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 this is a `{<label>}`, counts through all elements with that + /// label, + /// - If this is an element function or selector, counts through its + /// elements, + /// - If this is the [`page`]($page) function, counts through pages. + key: CounterKey, + ) -> Counter { + Self(key) + } + + /// Displays the current value of the counter. + #[func] + pub fn display( + self, + /// A [numbering pattern or a function]($numbering), which specifies how + /// to display the counter. If given a function, that function receives + /// each number of the counter as a separate argument. If the amount of + /// numbers varies, e.g. for the heading argument, you can use an + /// [argument sink]($arguments). + /// + /// If this is omitted, displays the counter with the numbering style + /// for the counted element or with the pattern `{"1.1"}` if no such + /// style exists. + #[default] + numbering: Option<Numbering>, + /// If enabled, displays the current and final top-level count together. + /// Both can be styled through a single numbering pattern. This is used + /// by the page numbering property to display the current and total + /// number of pages when a pattern like `{"1 / 1"}` is given. + #[named] + #[default(false)] + both: bool, + ) -> Content { + DisplayElem::new(self, numbering, both).pack() + } + + /// Increases the value of the counter by one. + /// + /// The update will be in effect at the position where the returned content + /// is inserted into the document. If you don't put the output into the + /// document, nothing happens! This would be the case, for example, if you + /// write `{let _ = counter(page).step()}`. Counter updates are always + /// applied in layout order and in that case, Typst wouldn't know when to + /// step the counter. + #[func] + pub fn step( + self, + /// The depth at which to step the counter. Defaults to `{1}`. + #[named] + #[default(NonZeroUsize::ONE)] + level: NonZeroUsize, + ) -> Content { + self.update(CounterUpdate::Step(level)) + } + + /// Updates the value of the counter. + /// + /// Just like with `step`, the update only occurs if you put the resulting + /// content into the document. + #[func] + pub fn update( + self, + /// If given an integer or array of integers, sets the counter to that + /// value. If given a function, that function receives the previous + /// counter value (with each number as a separate argument) and has to + /// return the new value (integer or array). + update: CounterUpdate, + ) -> Content { + UpdateElem::new(self.0, update).pack() + } + + /// Gets the value of the counter at the given location. Always returns an + /// array of integers, even if the counter has just one number. + #[func] + pub fn at( + &self, + /// The virtual typesetter. + vt: &mut Vt, + /// The location at which the counter value should be retrieved. A + /// suitable location can be retrieved from [`locate`]($locate) or + /// [`query`]($query). + location: Location, + ) -> SourceResult<CounterState> { + let sequence = self.sequence(vt)?; + let offset = vt + .introspector + .query(&self.selector().before(location.into(), true)) + .len(); + let (mut state, page) = sequence[offset].clone(); + if self.is_page() { + let delta = vt.introspector.page(location).get().saturating_sub(page.get()); + state.step(NonZeroUsize::ONE, delta); + } + + Ok(state) + } + + /// Gets the value of the counter at the end of the document. Always returns + /// an array of integers, even if the counter has just one number. + #[func] + pub fn final_( + &self, + /// The virtual typesetter. + vt: &mut Vt, + /// Can be an arbitrary location, as its value is irrelevant for the + /// method's return value. Why is it required then? Typst has to + /// evaluate parts of your code multiple times to determine all counter + /// values. By only allowing this method within [`locate`]($locate) + /// calls, the amount of code that can depend on the method's result is + /// reduced. If you could call `final` directly at the top level of a + /// module, the evaluation of the whole module and its exports could + /// depend on the counter's value. + location: Location, + ) -> SourceResult<CounterState> { + let _ = location; + let sequence = self.sequence(vt)?; + let (mut state, page) = sequence.last().unwrap().clone(); + if self.is_page() { + let delta = vt.introspector.pages().get().saturating_sub(page.get()); + state.step(NonZeroUsize::ONE, delta); + } + Ok(state) + } +} + impl Debug for Counter { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.write_str("counter(")?; @@ -486,7 +464,7 @@ impl Debug for Counter { } cast! { - type Counter: "counter", + type Counter, } /// Identifies a counter. @@ -504,14 +482,14 @@ pub enum CounterKey { cast! { CounterKey, self => match self { - Self::Page => PageElem::func().into_value(), + Self::Page => PageElem::elem().into_value(), Self::Selector(v) => v.into_value(), Self::Str(v) => v.into_value(), }, v: Str => Self::Str(v), v: Label => Self::Selector(Selector::Label(v)), - v: ElemFunc => { - if v == PageElem::func() { + v: Element => { + if v == PageElem::elem() { Self::Page } else { Self::Selector(LocatableSelector::from_value(v.into_value())?.0) @@ -531,6 +509,7 @@ impl Debug for CounterKey { } /// An update to perform on a counter. +#[ty] #[derive(Clone, PartialEq, Hash)] pub enum CounterUpdate { /// Set the counter to the specified state. @@ -548,7 +527,7 @@ impl Debug for CounterUpdate { } cast! { - type CounterUpdate: "counter update", + type CounterUpdate, v: CounterState => Self::Set(v), v: Func => Self::Func(v), } @@ -612,10 +591,7 @@ cast! { } /// Executes a display of a state. -/// -/// Display: State -/// Category: special -#[element(Locatable, Show)] +#[elem(Locatable, Show)] struct DisplayElem { /// The counter. #[required] @@ -643,11 +619,11 @@ impl Show for DisplayElem { return None; }; - if func == HeadingElem::func() { + if func == HeadingElem::elem() { HeadingElem::numbering_in(styles) - } else if func == FigureElem::func() { + } else if func == FigureElem::elem() { FigureElem::numbering_in(styles) - } else if func == EquationElem::func() { + } else if func == EquationElem::elem() { EquationElem::numbering_in(styles) } else { None @@ -667,10 +643,7 @@ impl Show for DisplayElem { } /// Executes a display of a state. -/// -/// Display: State -/// Category: special -#[element(Locatable, Show)] +#[elem(Locatable, Show)] struct UpdateElem { /// The key that identifies the counter. #[required] diff --git a/crates/typst-library/src/meta/document.rs b/crates/typst-library/src/meta/document.rs index db036e0a..66f8aeb5 100644 --- a/crates/typst-library/src/meta/document.rs +++ b/crates/typst-library/src/meta/document.rs @@ -17,10 +17,7 @@ use crate::prelude::*; /// /// Note that metadata set with this function is not rendered within the /// document. Instead, it is embedded in the compiled PDF file. -/// -/// Display: Document -/// Category: meta -#[element(Construct, LayoutRoot)] +#[elem(Construct, LayoutRoot)] pub struct DocumentElem { /// The document's title. This is often rendered as the title of the /// PDF viewer window. diff --git a/crates/typst-library/src/meta/figure.rs b/crates/typst-library/src/meta/figure.rs index d63ae3a8..6e95dce7 100644 --- a/crates/typst-library/src/meta/figure.rs +++ b/crates/typst-library/src/meta/figure.rs @@ -15,7 +15,7 @@ use crate::visualize::ImageElem; /// For example, figures containing images will be numbered separately from /// figures containing tables. /// -/// ## Examples { #examples } +/// # Examples /// The example below shows a basic figure with an image: /// ```example /// @glacier shows a glacier. Glaciers @@ -27,9 +27,8 @@ use crate::visualize::ImageElem; /// ) <glacier> /// ``` /// -/// You can also insert [tables]($func/table) into figures to give them a -/// caption. The figure will detect this and automatically use a separate -/// counter. +/// You can also insert [tables]($table) into figures to give them a caption. +/// The figure will detect this and automatically use a separate counter. /// /// ```example /// #figure( @@ -45,7 +44,7 @@ use crate::visualize::ImageElem; /// This behaviour can be overridden by explicitly specifying the figure's /// `kind`. All figures of the same kind share a common counter. /// -/// ## Modifying the appearance { #modifying-appearance } +/// # Modifying the appearance { #modifying-appearance } /// You can completely customize the look of your figures with a [show /// rule]($styling/#show-rules). In the example below, we show the figure's /// caption above its body and display its supplement and counter after the @@ -73,13 +72,10 @@ use crate::visualize::ImageElem; /// If your figure is too large and its contents are breakable across pages /// (e.g. if it contains a large table), then you can make the figure breakable /// across pages as well by using `[#show figure: set block(breakable: true)]` -/// (see the [block]($func/block) documentation for more information). -/// -/// Display: Figure -/// Category: meta -#[element(Locatable, Synthesize, Count, Show, Finalize, Refable, Outlinable)] +/// (see the [block]($block) documentation for more information). +#[elem(Locatable, Synthesize, Count, Show, Finalize, Refable, Outlinable)] pub struct FigureElem { - /// The content of the figure. Often, an [image]($func/image). + /// The content of the figure. Often, an [image]($image). #[required] pub body: Content, @@ -103,7 +99,7 @@ pub struct FigureElem { /// ) /// #lorem(60) /// ``` - pub placement: Option<Smart<VerticalAlign>>, + pub placement: Option<Smart<VAlign>>, /// The figure's caption. pub caption: Option<Content>, @@ -122,8 +118,17 @@ pub struct FigureElem { /// caption: [I'm down here], /// ) /// ``` - #[default(VerticalAlign(GenAlign::Specific(Align::Bottom)))] - pub caption_pos: VerticalAlign, + #[default(VAlign::Bottom)] + #[parse({ + let option: Option<Spanned<VAlign>> = args.named("caption-pos")?; + if let Some(Spanned { v: align, span }) = option { + if align == VAlign::Horizon { + bail!(span, "expected `top` or `bottom`"); + } + } + option.map(|spanned| spanned.v) + })] + pub caption_pos: VAlign, /// The kind of figure this is. /// @@ -133,7 +138,7 @@ pub struct FigureElem { /// Setting this to something other than `{auto}` will override the /// automatic detection. This can be useful if /// - you wish to create a custom figure type that is not an - /// [image]($func/image), a [table]($func/table) or [code]($func/raw), + /// [image]($image), a [table]($table) or [code]($raw), /// - you want to force the figure to use a specific counter regardless of /// its content. /// @@ -155,8 +160,8 @@ pub struct FigureElem { /// The figure's supplement. /// /// If set to `{auto}`, the figure will try to automatically determine the - /// correct supplement based on the `kind` and the active [text - /// language]($func/text.lang). If you are using a custom figure type, you + /// correct supplement based on the `kind` and the active + /// [text language]($text.lang). If you are using a custom figure type, you /// will need to manually specify the supplement. /// /// If a function is specified, it is passed the first descendant of the @@ -174,7 +179,7 @@ pub struct FigureElem { pub supplement: Smart<Option<Supplement>>, /// How to number the figure. Accepts a - /// [numbering pattern or function]($func/numbering). + /// [numbering pattern or function]($numbering). #[default(Some(NumberingPattern::from_str("1").unwrap().into()))] pub numbering: Option<Numbering>, @@ -182,16 +187,15 @@ pub struct FigureElem { #[default(Em::new(0.65).into())] pub gap: Length, - /// Whether the figure should appear in an [`outline`]($func/outline) - /// of figures. + /// Whether the figure should appear in an [`outline`]($outline) of figures. #[default(true)] pub outlined: bool, /// Convenience field to get access to the counter for this figure. /// /// The counter only depends on the `kind`: - /// - For (tables)[$func/table]: `{counter(figure.where(kind: table))}` - /// - For (images)[$func/image]: `{counter(figure.where(kind: image))}` + /// - For (tables)[@table]: `{counter(figure.where(kind: table))}` + /// - For (images)[@image]: `{counter(figure.where(kind: image))}` /// - For a custom kind: `{counter(figure.where(kind: kind))}` /// /// These are the counters you'll need to modify if you want to skip a @@ -210,16 +214,9 @@ impl Synthesize for FigureElem { .query_first(Selector::can::<dyn Figurable>()) .cloned() .map(|elem| FigureKind::Elem(elem.func())) - .unwrap_or_else(|| FigureKind::Elem(ImageElem::func())) + .unwrap_or_else(|| FigureKind::Elem(ImageElem::elem())) }); - let caption_pos = - VerticalAlign(GenAlign::Specific(match self.caption_pos(styles) { - VerticalAlign(GenAlign::Specific(Align::Top)) => Align::Top, - VerticalAlign(GenAlign::Specific(Align::Bottom)) => Align::Bottom, - _ => bail!(self.span(), "caption-pos can only be top or bottom"), - })); - // Resolve the supplement. let supplement = match self.supplement(styles) { Smart::Auto => { @@ -261,14 +258,14 @@ impl Synthesize for FigureElem { // Construct the figure's counter. let counter = Counter::new(CounterKey::Selector(Selector::Elem( - Self::func(), + Self::elem(), Some(dict! { "kind" => kind.clone(), }), ))); self.push_placement(self.placement(styles)); - self.push_caption_pos(caption_pos); + self.push_caption_pos(self.caption_pos(styles)); self.push_caption(self.caption(styles)); self.push_kind(Smart::Custom(kind)); self.push_supplement(Smart::Custom(Some(Supplement::Content(supplement)))); @@ -288,10 +285,7 @@ impl Show for FigureElem { // Build the caption, if any. if let Some(caption) = self.full_caption(vt)? { let v = VElem::weak(self.gap(styles).into()).pack(); - realized = if matches!( - self.caption_pos(styles), - VerticalAlign(GenAlign::Specific(Align::Bottom)) - ) { + realized = if self.caption_pos(styles) == VAlign::Bottom { realized + v + caption } else { caption + v + realized @@ -302,15 +296,13 @@ impl Show for FigureElem { realized = BlockElem::new() .with_body(Some(realized)) .pack() - .aligned(Axes::with_x(Some(Align::Center.into()))); + .aligned(Align::CENTER); // Wrap in a float. if let Some(align) = self.placement(styles) { realized = PlaceElem::new(realized) .with_float(true) - .with_alignment(align.map(|VerticalAlign(align)| { - Axes::new(Some(Align::Center.into()), Some(align)) - })) + .with_alignment(align.map(|align| HAlign::Center + align)) .pack(); } @@ -345,7 +337,7 @@ impl Refable for FigureElem { } fn counter(&self) -> Counter { - self.counter().unwrap_or_else(|| Counter::of(Self::func())) + self.counter().unwrap_or_else(|| Counter::of(Self::elem())) } fn numbering(&self) -> Option<Numbering> { @@ -379,8 +371,8 @@ impl FigureElem { self.counter(), self.numbering(StyleChain::default()), ) { - let loc = self.0.location().unwrap(); - let numbers = counter.at(vt, loc)?.display(vt, &numbering)?; + let location = self.0.location().unwrap(); + let numbers = counter.at(vt, location)?.display(vt, &numbering)?; if !supplement.is_empty() { supplement += TextElem::packed("\u{a0}"); @@ -397,7 +389,7 @@ impl FigureElem { #[derive(Debug, Clone)] pub enum FigureKind { /// The kind is an element function. - Elem(ElemFunc), + Elem(Element), /// The kind is a name. Name(EcoString), } @@ -408,7 +400,7 @@ cast! { Self::Elem(v) => v.into_value(), Self::Name(v) => v.into_value(), }, - v: ElemFunc => Self::Elem(v), + v: Element => Self::Elem(v), v: EcoString => Self::Name(v), } diff --git a/crates/typst-library/src/meta/footnote.rs b/crates/typst-library/src/meta/footnote.rs index 848c0b7c..ed7242bb 100644 --- a/crates/typst-library/src/meta/footnote.rs +++ b/crates/typst-library/src/meta/footnote.rs @@ -34,11 +34,11 @@ cast! { /// and can break across multiple pages. /// /// To customize the appearance of the entry in the footnote listing, see -/// [`footnote.entry`]($func/footnote.entry). The footnote itself is realized as -/// a normal superscript, so you can use a set rule on the -/// [`super`]($func/super) function to customize it. +/// [`footnote.entry`]($footnote.entry). The footnote itself is realized as a +/// normal superscript, so you can use a set rule on the [`super`]($super) +/// function to customize it. /// -/// ## Example { #example } +/// # Example /// ```example /// Check the docs for more details. /// #footnote[https://typst.app/docs] @@ -46,7 +46,7 @@ cast! { /// /// The footnote automatically attaches itself to the preceding word, even if /// there is a space before it in the markup. To force space, you can use the -/// string `[#" "]` or explicit [horizontal spacing]($func/h). +/// string `[#" "]` or explicit [horizontal spacing]($h). /// /// By giving a label to a footnote, you can have multiple references to it. /// @@ -61,21 +61,14 @@ cast! { /// apply to the footnote's content. See [here][issue] for more information. /// /// [issue]: https://github.com/typst/typst/issues/1467#issuecomment-1588799440 -/// -/// Display: Footnote -/// Category: meta -#[element(Locatable, Synthesize, Show, Count)] -#[scope( - scope.define("entry", FootnoteEntry::func()); - scope -)] +#[elem(scope, Locatable, Synthesize, Show, Count)] pub struct FootnoteElem { /// How to number footnotes. /// /// By default, the footnote numbering continues throughout your document. /// If you prefer per-page footnote numbering, you can reset the footnote - /// [counter]($func/counter) in the page [header]($func/page.header). In the - /// future, there might be a simpler way to achieve this. + /// [counter]($counter) in the page [header]($page.header). In the future, + /// there might be a simpler way to achieve this. /// /// ```example /// #set footnote(numbering: "*") @@ -93,6 +86,12 @@ pub struct FootnoteElem { pub body: FootnoteBody, } +#[scope] +impl FootnoteElem { + #[elem] + type FootnoteEntry; +} + impl FootnoteElem { /// Creates a new footnote that the passed content as its body. pub fn with_content(content: Content) -> Self { @@ -145,7 +144,7 @@ impl Show for FootnoteElem { Ok(vt.delayed(|vt| { let loc = self.declaration_location(vt).at(self.span())?; let numbering = self.numbering(styles); - let counter = Counter::of(Self::func()); + let counter = Counter::of(Self::elem()); let num = counter.at(vt, loc)?.display(vt, &numbering)?; let sup = SuperElem::new(num).pack(); let hole = HElem::new(Abs::zero().into()).with_weak(true).pack(); @@ -168,11 +167,9 @@ impl Count for FootnoteElem { /// /// _Note:_ Set and show rules for `footnote.entry` must be defined at the /// beginning of the document in order to work correctly. -/// See [here][issue] for more information. -/// -/// [issue]: https://github.com/typst/typst/issues/1348#issuecomment-1566316463 +/// See [here](https://github.com/typst/typst/issues/1348#issuecomment-1566316463) +/// for more information. /// -/// ## Example { #example } /// ```example /// #show footnote.entry: set text(red) /// @@ -180,10 +177,7 @@ impl Count for FootnoteElem { /// #footnote[It's down here] /// has red text! /// ``` -/// -/// Display: Footnote Entry -/// Category: meta -#[element(Show, Finalize)] +#[elem(name = "entry", title = "Footnote Entry", Show, Finalize)] pub struct FootnoteEntry { /// The footnote for this entry. It's location can be used to determine /// the footnote counter state. @@ -220,7 +214,7 @@ pub struct FootnoteEntry { #[default( LineElem::new() .with_length(Ratio::new(0.3).into()) - .with_stroke(PartialStroke { + .with_stroke(Stroke { thickness: Smart::Custom(Abs::pt(0.5).into()), ..Default::default() }) @@ -273,7 +267,7 @@ impl Show for FootnoteEntry { let note = self.note(); let number_gap = Em::new(0.05); let numbering = note.numbering(StyleChain::default()); - let counter = Counter::of(FootnoteElem::func()); + let counter = Counter::of(FootnoteElem::elem()); let loc = note.0.location().unwrap(); let num = counter.at(vt, loc)?.display(vt, &numbering)?; let sup = SuperElem::new(num) diff --git a/crates/typst-library/src/meta/heading.rs b/crates/typst-library/src/meta/heading.rs index 84da7c03..edd2d0f0 100644 --- a/crates/typst-library/src/meta/heading.rs +++ b/crates/typst-library/src/meta/heading.rs @@ -17,14 +17,13 @@ use crate::text::{SpaceElem, TextElem, TextSize}; /// /// Typst can automatically number your headings for you. To enable numbering, /// specify how you want your headings to be numbered with a -/// [numbering pattern or function]($func/numbering). +/// [numbering pattern or function]($numbering). /// /// Independently from the numbering, Typst can also automatically generate an -/// [outline]($func/outline) of all headings for you. To exclude one or more -/// headings from this outline, you can set the `outlined` parameter to -/// `{false}`. +/// [outline]($outline) of all headings for you. To exclude one or more headings +/// from this outline, you can set the `outlined` parameter to `{false}`. /// -/// ## Example { #example } +/// # Example /// ```example /// #set heading(numbering: "1.a)") /// @@ -35,21 +34,18 @@ use crate::text::{SpaceElem, TextElem, TextSize}; /// To start, ... /// ``` /// -/// ## Syntax { #syntax } +/// # Syntax /// Headings have dedicated syntax: They can be created by starting a line with /// one or multiple equals signs, followed by a space. The number of equals /// signs determines the heading's logical nesting depth. -/// -/// Display: Heading -/// Category: meta -#[element(Locatable, Synthesize, Count, Show, Finalize, LocalName, Refable, Outlinable)] +#[elem(Locatable, Synthesize, Count, Show, Finalize, LocalName, Refable, Outlinable)] pub struct HeadingElem { /// The logical nesting depth of the heading, starting from one. #[default(NonZeroUsize::ONE)] pub level: NonZeroUsize, /// How to number the heading. Accepts a - /// [numbering pattern or function]($func/numbering). + /// [numbering pattern or function]($numbering). /// /// ```example /// #set heading(numbering: "1.a.") @@ -78,11 +74,11 @@ pub struct HeadingElem { /// ``` pub supplement: Smart<Option<Supplement>>, - /// Whether the heading should appear in the [outline]($func/outline). + /// Whether the heading should appear in the [outline]($outline). /// - /// Note that this property, if set to `{true}`, ensures the heading is - /// also shown as a bookmark in the exported PDF's outline (when exporting - /// to PDF). To change that behavior, use the `bookmarked` property. + /// Note that this property, if set to `{true}`, ensures the heading is also + /// shown as a bookmark in the exported PDF's outline (when exporting to + /// PDF). To change that behavior, use the `bookmarked` property. /// /// ```example /// #outline() @@ -103,9 +99,8 @@ pub struct HeadingElem { /// The default value of `{auto}` indicates that the heading will only /// appear in the exported PDF's outline if its `outlined` property is set /// to `{true}`, that is, if it would also be listed in Typst's - /// [outline]($func/outline). Setting this property to either - /// `{true}` (bookmark) or `{false}` (don't bookmark) bypasses that - /// behavior. + /// [outline]($outline). Setting this property to either `{true}` (bookmark) + /// or `{false}` (don't bookmark) bypasses that behavior. /// /// ```example /// #heading[Normal heading] @@ -149,7 +144,7 @@ impl Show for HeadingElem { fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> { let mut realized = self.body(); if let Some(numbering) = self.numbering(styles) { - realized = Counter::of(Self::func()) + realized = Counter::of(Self::elem()) .display(Some(numbering), false) .spanned(self.span()) + HElem::new(Em::new(0.3).into()).with_weak(true).pack() @@ -205,7 +200,7 @@ impl Refable for HeadingElem { } fn counter(&self) -> Counter { - Counter::of(Self::func()) + Counter::of(Self::elem()) } fn numbering(&self) -> Option<Numbering> { @@ -221,7 +216,7 @@ impl Outlinable for HeadingElem { let mut content = self.body(); if let Some(numbering) = self.numbering(StyleChain::default()) { - let numbers = Counter::of(Self::func()) + let numbers = Counter::of(Self::elem()) .at(vt, self.0.location().unwrap())? .display(vt, &numbering)?; content = numbers + SpaceElem::new().pack() + content; diff --git a/crates/typst-library/src/meta/link.rs b/crates/typst-library/src/meta/link.rs index 2a53b84f..7b68b186 100644 --- a/crates/typst-library/src/meta/link.rs +++ b/crates/typst-library/src/meta/link.rs @@ -6,7 +6,7 @@ use crate::text::{Hyphenate, TextElem}; /// By default, links are not styled any different from normal text. However, /// you can easily apply a style of your choice with a show rule. /// -/// ## Example { #example } +/// # Example /// ```example /// #show link: underline /// @@ -18,13 +18,10 @@ use crate::text::{Hyphenate, TextElem}; /// ] /// ``` /// -/// ## Syntax { #syntax } +/// # Syntax /// This function also has dedicated syntax: Text that starts with `http://` or /// `https://` is automatically turned into a link. -/// -/// Display: Link -/// Category: meta -#[element(Show)] +#[elem(Show)] pub struct LinkElem { /// The destination the link points to. /// @@ -35,17 +32,16 @@ pub struct LinkElem { /// /// - To link to another part of the document, `dest` can take one of three /// forms: - /// - A [label]($func/label) attached to an element. If you also want - /// automatic text for the link based on the element, consider using - /// a [reference]($func/ref) instead. + /// - A [label]($label) attached to an element. If you also want automatic + /// text for the link based on the element, consider using a + /// [reference]($ref) instead. /// - /// - A [location]($func/locate) resulting from a [`locate`]($func/locate) - /// call or [`query`]($func/query). + /// - A [location]($locate) resulting from a [`locate`]($locate) call or + /// [`query`]($query). /// - /// - A dictionary with a `page` key of type [integer]($type/integer) and - /// `x` and `y` coordinates of type [length]($type/length). Pages are - /// counted from one, and the coordinates are relative to the page's top - /// left corner. + /// - A dictionary with a `page` key of type [integer]($int) and `x` and + /// `y` coordinates of type [length]($length). Pages are counted from + /// one, and the coordinates are relative to the page's top left corner. /// /// ```example /// = Introduction <intro> diff --git a/crates/typst-library/src/meta/metadata.rs b/crates/typst-library/src/meta/metadata.rs index 3e08a9f7..b4ae64cb 100644 --- a/crates/typst-library/src/meta/metadata.rs +++ b/crates/typst-library/src/meta/metadata.rs @@ -2,11 +2,11 @@ use crate::prelude::*; /// Exposes a value to the query system without producing visible content. /// -/// This element can be retrieved with the [`query`]($func/query) function and -/// from the command with [`typst query`]($reference/meta/query/#cli-queries). -/// Its purpose is to expose an arbitrary value to the introspection system. To -/// identify a metadata value among others, you can attach a -/// [`label`]($func/label) to it and query for that label. +/// This element can be retrieved with the [`query`]($query) function and from +/// the command with [`typst query`]($reference/meta/query/#cli-queries). Its +/// purpose is to expose an arbitrary value to the introspection system. To +/// identify a metadata value among others, you can attach a [`label`]($label) +/// to it and query for that label. /// /// The `metadata` element is especially useful for command line queries because /// it allows you to expose arbitrary values to the outside world. @@ -20,10 +20,7 @@ use crate::prelude::*; /// query(<note>, loc).first().value /// }) /// ``` -/// -/// Display: Metadata -/// Category: meta -#[element(Behave, Show, Locatable)] +#[elem(Behave, Show, Locatable)] pub struct MetadataElem { /// The value to embed into the document. #[required] diff --git a/crates/typst-library/src/meta/mod.rs b/crates/typst-library/src/meta/mod.rs index bb6ac3d3..659cb5a3 100644 --- a/crates/typst-library/src/meta/mod.rs +++ b/crates/typst-library/src/meta/mod.rs @@ -9,9 +9,11 @@ mod footnote; mod heading; mod link; mod metadata; -mod numbering; +#[path = "numbering.rs"] +mod numbering_; mod outline; -mod query; +#[path = "query.rs"] +mod query_; mod reference; mod state; @@ -24,9 +26,9 @@ pub use self::footnote::*; pub use self::heading::*; pub use self::link::*; pub use self::metadata::*; -pub use self::numbering::*; +pub use self::numbering_::*; pub use self::outline::*; -pub use self::query::*; +pub use self::query_::*; pub use self::reference::*; pub use self::state::*; @@ -35,24 +37,27 @@ use crate::text::TextElem; /// Hook up all meta definitions. pub(super) fn define(global: &mut Scope) { - global.define("document", DocumentElem::func()); - global.define("ref", RefElem::func()); - global.define("link", LinkElem::func()); - global.define("outline", OutlineElem::func()); - global.define("heading", HeadingElem::func()); - global.define("figure", FigureElem::func()); - global.define("footnote", FootnoteElem::func()); - global.define("cite", CiteElem::func()); - global.define("bibliography", BibliographyElem::func()); - global.define("locate", locate_func()); - global.define("style", style_func()); - global.define("layout", layout_func()); - global.define("counter", counter_func()); - global.define("numbering", numbering_func()); - global.define("state", state_func()); - global.define("query", query_func()); - global.define("selector", selector_func()); - global.define("metadata", MetadataElem::func()); + global.category("meta"); + global.define_type::<Label>(); + global.define_type::<Selector>(); + global.define_type::<Location>(); + global.define_type::<Counter>(); + global.define_type::<State>(); + global.define_elem::<DocumentElem>(); + global.define_elem::<RefElem>(); + global.define_elem::<LinkElem>(); + global.define_elem::<OutlineElem>(); + global.define_elem::<HeadingElem>(); + global.define_elem::<FigureElem>(); + global.define_elem::<FootnoteElem>(); + global.define_elem::<CiteElem>(); + global.define_elem::<BibliographyElem>(); + global.define_elem::<MetadataElem>(); + global.define_func::<locate>(); + global.define_func::<style>(); + global.define_func::<layout>(); + global.define_func::<numbering>(); + global.define_func::<query>(); } /// The named with which an element is referenced. diff --git a/crates/typst-library/src/meta/numbering.rs b/crates/typst-library/src/meta/numbering.rs index 8698f7b9..40308af0 100644 --- a/crates/typst-library/src/meta/numbering.rs +++ b/crates/typst-library/src/meta/numbering.rs @@ -16,7 +16,7 @@ use crate::text::Case; /// number is substituted, their prefixes, and one suffix. The prefixes and the /// suffix are repeated as-is. /// -/// ## Example { #example } +/// # Example /// ```example /// #numbering("1.1)", 1, 2, 3) \ /// #numbering("1.a.i", 1, 2) \ @@ -29,11 +29,10 @@ use crate::text::Case; /// 1, 2, 3, /// ) /// ``` -/// -/// Display: Numbering -/// Category: meta #[func] pub fn numbering( + /// The virtual machine. + vm: &mut Vm, /// Defines how the numbering works. /// /// **Counting symbols** are `1`, `a`, `A`, `i`, `I`, `い`, `イ`, `א`, `가`, @@ -64,8 +63,6 @@ pub fn numbering( /// given, the last counting symbol with its prefix is repeated. #[variadic] numbers: Vec<usize>, - /// The virtual machine. - vm: &mut Vm, ) -> SourceResult<Value> { numbering.apply_vm(vm, &numbers) } diff --git a/crates/typst-library/src/meta/outline.rs b/crates/typst-library/src/meta/outline.rs index 6fd4ebd0..8583d96f 100644 --- a/crates/typst-library/src/meta/outline.rs +++ b/crates/typst-library/src/meta/outline.rs @@ -16,7 +16,7 @@ use crate::text::{LinebreakElem, SpaceElem, TextElem}; /// be displayed in the outline alongside its title or caption. By default this /// generates a table of contents. /// -/// ## Example { #example } +/// # Example /// ```example /// #outline() /// @@ -27,13 +27,13 @@ use crate::text::{LinebreakElem, SpaceElem, TextElem}; /// #lorem(10) /// ``` /// -/// ## Alternative outlines { #alternative-outlines } +/// # Alternative outlines /// By setting the `target` parameter, the outline can be used to generate a /// list of other kinds of elements than headings. In the example below, we list /// all figures containing images by setting `target` to `{figure.where(kind: /// image)}`. We could have also set it to just `figure`, but then the list /// would also include figures containing tables or other material. For more -/// details on the `where` selector, [see here]($type/content.where). +/// details on the `where` selector, [see here]($function.where). /// /// ```example /// #outline( @@ -47,25 +47,17 @@ use crate::text::{LinebreakElem, SpaceElem, TextElem}; /// ) /// ``` /// -/// ## Styling the outline { #styling-the-outline } +/// # Styling the outline /// The outline element has several options for customization, such as its -/// `title` and `indent` parameters. If desired, however, it is possible to -/// have more control over the outline's look and style through the -/// [`outline.entry`]($func/outline.entry) element. -/// -/// Display: Outline -/// Category: meta -/// Keywords: Table of Contents -#[element(Show, Finalize, LocalName)] -#[scope( - scope.define("entry", OutlineEntry::func()); - scope -)] +/// `title` and `indent` parameters. If desired, however, it is possible to have +/// more control over the outline's look and style through the +/// [`outline.entry`]($outline.entry) element. +#[elem(scope, keywords = ["Table of Contents"], Show, Finalize, LocalName)] pub struct OutlineElem { /// The title of the outline. /// /// - When set to `{auto}`, an appropriate title for the - /// [text language]($func/text.lang) will be used. This is the default. + /// [text language]($text.lang) will be used. This is the default. /// - When set to `{none}`, the outline will not have a title. /// - A custom title can be set by passing content. /// @@ -97,7 +89,7 @@ pub struct OutlineElem { /// ) /// ``` #[default(LocatableSelector(Selector::Elem( - HeadingElem::func(), + HeadingElem::elem(), Some(dict! { "outlined" => true }) )))] pub target: LocatableSelector, @@ -125,19 +117,18 @@ pub struct OutlineElem { /// - `{none}`: No indent /// - `{auto}`: Indents the numbering of the nested entry with the title of /// its parent entry. This only has an effect if the entries are numbered - /// (e.g., via [heading numbering]($func/heading.numbering)). - /// - [Relative length]($type/relative-length): Indents the item by this length + /// (e.g., via [heading numbering]($heading.numbering)). + /// - [Relative length]($relative): Indents the item by this length /// multiplied by its nesting level. Specifying `{2em}`, for instance, /// would indent top-level headings (not nested) by `{0em}`, second level /// headings by `{2em}` (nested once), third-level headings by `{4em}` /// (nested twice) and so on. - /// - [Function]($type/function): You can completely customize this setting - /// with a function. That function receives the nesting level as a - /// parameter (starting at 0 for top-level headings/elements) and can - /// return a relative length or content making up the indent. For example, - /// `{n => n * 2em}` would be equivalent to just specifying `{2em}`, - /// while `{n => [→ ] * n}` would indent with one arrow per nesting - /// level. + /// - [Function]($function): You can completely customize this setting with + /// a function. That function receives the nesting level as a parameter + /// (starting at 0 for top-level headings/elements) and can return a + /// relative length or content making up the indent. For example, + /// `{n => n * 2em}` would be equivalent to just specifying `{2em}`, while + /// `{n => [→ ] * n}` would indent with one arrow per nesting level. /// /// *Migration hints:* Specifying `{true}` (equivalent to `{auto}`) or /// `{false}` (equivalent to `{none}`) for this option is deprecated and @@ -184,6 +175,12 @@ pub struct OutlineElem { pub fill: Option<Content>, } +#[scope] +impl OutlineElem { + #[elem] + type OutlineEntry; +} + impl Show for OutlineElem { #[tracing::instrument(name = "OutlineElem::show", skip_all)] fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> { @@ -391,11 +388,9 @@ cast! { /// outlined element, its page number, and the filler content between both. /// /// This element is intended for use with show rules to control the appearance -/// of outlines. -/// -/// ## Example { #example } -/// The example below shows how to style entries for top-level sections to make -/// them stand out. +/// of outlines. To customize an entry's line, you can build it from scratch by +/// accessing the `level`, `element`, `body`, `fill` and `page` fields on the +/// entry. /// /// ```example /// #set heading(numbering: "1.") @@ -416,13 +411,7 @@ cast! { /// = Analysis /// == Setup /// ``` -/// -/// To completely customize an entry's line, you can also build it from scratch -/// by accessing the `level`, `element`, `body`, `fill` and `page` fields on the entry. -/// -/// Display: Outline Entry -/// Category: meta -#[element(Show)] +#[elem(name = "entry", title = "Outline Entry", Show)] pub struct OutlineEntry { /// The nesting level of this outline entry. Starts at `{1}` for top-level /// entries. @@ -430,8 +419,8 @@ pub struct OutlineEntry { pub level: NonZeroUsize, /// The element this entry refers to. Its location will be available - /// through the [`location`]($type/content.location) method on content - /// and can be [linked]($func/link) to. + /// through the [`location`]($content.location) method on content + /// and can be [linked]($link) to. #[required] pub element: Content, @@ -446,7 +435,7 @@ pub struct OutlineEntry { /// located in. When `{none}`, empty space is inserted in that gap instead. /// /// Note that, when using show rules to override outline entries, it is - /// recommended to wrap the filling content in a [`box`]($func/box) with + /// recommended to wrap the filling content in a [`box`]($box) with /// fractional width. For example, `{box(width: 1fr, repeat[-])}` would show /// precisely as many `-` characters as necessary to fill a particular gap. #[required] diff --git a/crates/typst-library/src/meta/query.rs b/crates/typst-library/src/meta/query.rs index eb520896..d6c600d7 100644 --- a/crates/typst-library/src/meta/query.rs +++ b/crates/typst-library/src/meta/query.rs @@ -4,10 +4,10 @@ use crate::prelude::*; /// /// The `query` functions lets you search your document for elements of a /// particular type or with a particular label. To use it, you first need to -/// retrieve the current document location with the [`locate`]($func/locate) +/// retrieve the current document location with the [`locate`]($locate) /// function. /// -/// ## Finding elements { #finding-elements } +/// # Finding elements /// In the example below, we create a custom page header that displays the text /// "Typst Academy" in small capitals and the current section title. On the /// first page, the section title is omitted because the header is before the @@ -59,7 +59,7 @@ use crate::prelude::*; /// #lorem(15) /// ``` /// -/// ## A word of caution { #caution } +/// # A word of caution { #caution } /// To resolve all your queries, Typst evaluates and layouts parts of the /// document multiple times. However, there is no guarantee that your queries /// can actually be completely resolved. If you aren't careful a query can @@ -73,9 +73,9 @@ use crate::prelude::*; /// on. As we can see, the output has five headings. This is because Typst /// simply gives up after five attempts. /// -/// In general, you should try not to write queries that affect themselves. -/// The same words of caution also apply to other introspection features like -/// [counters]($func/counter) and [state]($func/state). +/// In general, you should try not to write queries that affect themselves. The +/// same words of caution also apply to other introspection features like +/// [counters]($counter) and [state]($state). /// /// ```example /// = Real @@ -86,11 +86,11 @@ use crate::prelude::*; /// }) /// ``` /// -/// ## Command line queries { #command-line-queries } +/// # Command line queries /// You can also perform queries from the command line with the `typst query` /// command. This command executes an arbitrary query on the document and /// returns the resulting elements in serialized form. Consider the following -/// `example.typ` file which contains some invisible [metadata]($func/metadata): +/// `example.typ` file which contains some invisible [metadata]($metadata): /// /// ```typ /// #metadata("This is a note") <note> @@ -125,32 +125,29 @@ use crate::prelude::*; /// $ typst query example.typ "<note>" --field value --one /// "This is a note" /// ``` -/// -/// Display: Query -/// Category: meta #[func] pub fn query( + /// The virtual machine. + vm: &mut Vm, /// Can be an element function like a `heading` or `figure`, a `{<label>}` /// or a more complex selector like `{heading.where(level: 1)}`. /// /// Currently, only a subset of element functions is supported. Aside from /// headings and figures, this includes equations, references and all /// elements with an explicit label. As a result, you _can_ query for e.g. - /// [`strong`]($func/strong) elements, but you will find only those that - /// have an explicit label attached to them. This limitation will be - /// resolved in the future. + /// [`strong`]($strong) elements, but you will find only those that have an + /// explicit label attached to them. This limitation will be resolved in the + /// future. target: LocatableSelector, /// Can be an arbitrary location, as its value is irrelevant for the /// function's return value. Why is it required then? As noted before, Typst /// has to evaluate parts of your code multiple times to determine the /// values of all state. By only allowing this function within - /// [`locate`]($func/locate) calls, the amount of code that can depend on - /// the query's result is reduced. If you could call it directly at the top + /// [`locate`]($locate) calls, the amount of code that can depend on the + /// query's result is reduced. If you could call it directly at the top /// level of a module, the evaluation of the whole module and its exports /// could depend on the query's result. location: Location, - /// The virtual machine. - vm: &mut Vm, ) -> Array { let _ = location; let vec = vm.vt.introspector.query(&target.0); @@ -158,19 +155,3 @@ pub fn query( .map(|elem| Value::Content(elem.into_inner())) .collect() } - -/// Turns a value into a selector. The following values are accepted: -/// - An element function like a `heading` or `figure`. -/// - A `{<label>}`. -/// - A more complex selector like `{heading.where(level: 1)}`. -/// -/// Display: Selector -/// Category: meta -#[func] -pub fn selector( - /// Can be an element function like a `heading` or `figure`, a `{<label>}` - /// or a more complex selector like `{heading.where(level: 1)}`. - target: Selector, -) -> Selector { - target -} diff --git a/crates/typst-library/src/meta/reference.rs b/crates/typst-library/src/meta/reference.rs index 015157a8..7f05bfcc 100644 --- a/crates/typst-library/src/meta/reference.rs +++ b/crates/typst-library/src/meta/reference.rs @@ -9,20 +9,19 @@ use crate::text::TextElem; /// Produces a textual reference to a label. For example, a reference to a /// heading will yield an appropriate string such as "Section 1" for a reference /// to the first heading. The references are also links to the respective -/// element. Reference syntax can also be used to [cite]($func/cite) from a +/// element. Reference syntax can also be used to [cite]($cite) from a /// bibliography. /// -/// Referenceable elements include [headings]($func/heading), -/// [figures]($func/figure), [equations]($func/math.equation), and -/// [footnotes]($func/footnote). To create a custom referenceable element like a -/// theorem, you can create a figure of a custom [`kind`]($func/figure.kind) and -/// write a show rule for it. In the future, there might be a more direct way to -/// define a custom referenceable element. +/// Referenceable elements include [headings]($heading), [figures]($figure), +/// [equations]($math.equation), and [footnotes]($footnote). To create a custom +/// referenceable element like a theorem, you can create a figure of a custom +/// [`kind`]($figure.kind) and write a show rule for it. In the future, there +/// might be a more direct way to define a custom referenceable element. /// /// If you just want to link to a labelled element and not get an automatic -/// textual reference, consider using the [`link`]($func/link) function instead. +/// textual reference, consider using the [`link`]($link) function instead. /// -/// ## Example { #example } +/// # Example /// ```example /// #set heading(numbering: "1.") /// #set math.equation(numbering: "(1)") @@ -46,7 +45,7 @@ use crate::text::TextElem; /// #bibliography("works.bib") /// ``` /// -/// ## Syntax { #syntax } +/// # Syntax /// This function also has dedicated syntax: A reference to a label can be /// created by typing an `@` followed by the name of the label (e.g. /// `[= Introduction <intro>]` can be referenced by typing `[@intro]`). @@ -54,7 +53,7 @@ use crate::text::TextElem; /// To customize the supplement, add content in square brackets after the /// reference: `[@intro[Chapter]]`. /// -/// ## Customization { #customization } +/// # Customization /// If you write a show rule for references, you can access the referenced /// element through the `element` field of the reference. The `element` may /// be `{none}` even if it exists if Typst hasn't discovered it yet, so you @@ -83,10 +82,7 @@ use crate::text::TextElem; /// In @beginning we prove @pythagoras. /// $ a^2 + b^2 = c^2 $ <pythagoras> /// ``` -/// -/// Display: Reference -/// Category: meta -#[element(Synthesize, Locatable, Show)] +#[elem(title = "Reference", Synthesize, Locatable, Show)] pub struct RefElem { /// The target label that should be referenced. #[required] @@ -163,7 +159,7 @@ impl Show for RefElem { let elem = elem.at(span)?; - if elem.func() == FootnoteElem::func() { + if elem.func() == FootnoteElem::elem() { return Ok(FootnoteElem::with_label(target).pack().spanned(span)); } @@ -192,7 +188,7 @@ impl Show for RefElem { .hint(eco_format!( "you can enable {} numbering with `#set {}(numbering: \"1.\")`", elem.func().name(), - if elem.func() == EquationElem::func() { + if elem.func() == EquationElem::elem() { "math.equation" } else { elem.func().name() diff --git a/crates/typst-library/src/meta/state.rs b/crates/typst-library/src/meta/state.rs index ee2a6e32..f7b5eb77 100644 --- a/crates/typst-library/src/meta/state.rs +++ b/crates/typst-library/src/meta/state.rs @@ -30,7 +30,7 @@ use crate::prelude::*; /// #compute("x - 5") /// ``` /// -/// ## State and document markup { #state-and-markup } +/// # State and document markup { #state-and-markup } /// Why does it do that? Because, in general, this kind of computation with side /// effects is problematic in document markup and Typst is upfront about that. /// For the results to make sense, the computation must proceed in the same @@ -61,7 +61,7 @@ use crate::prelude::*; /// `template` function and only then sees the `Outline`. Just counting up would /// number the `Introduction` with `1` and the `Outline` with `2`. /// -/// ## Managing state in Typst { #state-in-typst } +/// # Managing state in Typst { #state-in-typst } /// So what do we do instead? We use Typst's state management system. Calling /// the `state` function with an identifying string key and an optional initial /// value gives you a state value which exposes a few methods. The two most @@ -122,14 +122,14 @@ use crate::prelude::*; /// /// This example is of course a bit silly, but in practice this is often exactly /// what you want! A good example are heading counters, which is why Typst's -/// [counting system]($func/counter) is very similar to its state system. +/// [counting system]($counter) is very similar to its state system. /// -/// ## Time Travel { #time-travel } +/// # Time Travel /// By using Typst's state management system you also get time travel -/// capabilities! By combining the state system with [`locate`]($func/locate) -/// and [`query`]($func/query), we can find out what the value of the state will -/// be at any position in the document from anywhere else. In particular, the -/// `at` method gives us the value of the state at any location and the `final` +/// capabilities! By combining the state system with [`locate`]($locate) and +/// [`query`]($query), we can find out what the value of the state will be at +/// any position in the document from anywhere else. In particular, the `at` +/// method gives us the value of the state at any location and the `final` /// methods gives us the value of the state at the end of the document. /// /// ```example @@ -156,7 +156,7 @@ use crate::prelude::*; /// #compute("x - 5") /// ``` /// -/// ## A word of caution { #caution } +/// # A word of caution { #caution } /// To resolve the values of all states, Typst evaluates parts of your code /// multiple times. However, there is no guarantee that your state manipulation /// can actually be completely resolved. @@ -180,73 +180,7 @@ use crate::prelude::*; /// `locate` calls or `display` calls of state or counters. Instead, pass a /// function to `update` that determines the value of the state based on its /// previous value. -/// -/// ## Methods -/// ### display() -/// Displays the value of the state. -/// -/// - format: function (positional) -/// A function which receives the value of the state and can return arbitrary -/// content which is then displayed. If this is omitted, the value is directly -/// displayed. -/// -/// - returns: content -/// -/// ### update() -/// Updates the value of the state. -/// -/// The update will be in effect at the position where the returned content is -/// inserted into the document. If you don't put the output into the document, -/// nothing happens! This would be the case, for example, if you write -/// `{let _ = state("key").update(7)}`. State updates are always applied in -/// layout order and in that case, Typst wouldn't know when to update the state. -/// -/// - value: any or function (positional, required) -/// If given a non function-value, sets the state to that value. If given a -/// function, that function receives the previous state and has to return the -/// new state. -/// -/// - returns: content -/// -/// ### at() -/// Gets the value of the state at the given location. -/// -/// - location: location (positional, required) -/// The location at which the state's value should be retrieved. A suitable -/// location can be retrieved from [`locate`]($func/locate) or -/// [`query`]($func/query). -/// -/// - returns: any -/// -/// ### final() -/// Gets the value of the state at the end of the document. -/// -/// - location: location (positional, required) -/// Can be an arbitrary location, as its value is irrelevant for the method's -/// return value. Why is it required then? As noted before, Typst has to -/// evaluate parts of your code multiple times to determine the values of all -/// state. By only allowing this method within [`locate`]($func/locate) calls, -/// the amount of code that can depend on the method's result is reduced. If -/// you could call `final` directly at the top level of a module, the -/// evaluation of the whole module and its exports could depend on the state's -/// value. -/// -/// - returns: any -/// -/// Display: State -/// Category: meta -#[func] -pub fn state( - /// The key that identifies this state. - key: Str, - /// The initial value of the state. - #[default] - init: Value, -) -> State { - State { key, init } -} - -/// A state. +#[ty(scope)] #[derive(Clone, PartialEq, Hash)] pub struct State { /// The key that identifies the state. @@ -256,49 +190,9 @@ pub struct State { } impl State { - /// Call a method on a state. - #[tracing::instrument(skip(vm))] - 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_value(), - "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_value(), - _ => bail!(span, "type state has no method `{}`", method), - }; - args.finish()?; - Ok(value) - } - - /// Display the current value of the state. - pub fn display(self, func: Option<Func>) -> Content { - DisplayElem::new(self, func).pack() - } - - /// Get the value of the state at the given location. - #[tracing::instrument(skip(self, vt))] - pub fn at(self, vt: &mut Vt, location: Location) -> SourceResult<Value> { - let sequence = self.sequence(vt)?; - let offset = vt.introspector.query(&self.selector().before(location, true)).len(); - Ok(sequence[offset].clone()) - } - - /// Get the value of the state at the final location. - #[tracing::instrument(skip(self, vt))] - pub fn final_(self, vt: &mut Vt, _: Location) -> 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 { - UpdateElem::new(self.key, update).pack() + /// Create a new state identified by a key. + pub fn new(key: Str, init: Value) -> State { + Self { key, init } } /// Produce the whole sequence of states. @@ -350,7 +244,94 @@ impl State { /// The selector for this state's updates. fn selector(&self) -> Selector { - Selector::Elem(UpdateElem::func(), Some(dict! { "key" => self.key.clone() })) + Selector::Elem(UpdateElem::elem(), Some(dict! { "key" => self.key.clone() })) + } +} + +#[scope] +impl State { + /// Create a new state identified by a key. + #[func(constructor)] + pub fn construct( + /// The key that identifies this state. + key: Str, + /// The initial value of the state. + #[default] + init: Value, + ) -> State { + Self::new(key, init) + } + + /// Displays the current value of the state. + #[func] + pub fn display( + self, + /// A function which receives the value of the state and can return + /// arbitrary content which is then displayed. If this is omitted, the + /// value is directly displayed. + #[default] + func: Option<Func>, + ) -> Content { + DisplayElem::new(self, func).pack() + } + + /// Update the value of the state. + /// + /// The update will be in effect at the position where the returned content + /// is inserted into the document. If you don't put the output into the + /// document, nothing happens! This would be the case, for example, if you + /// write `{let _ = state("key").update(7)}`. State updates are always + /// applied in layout order and in that case, Typst wouldn't know when to + /// update the state. + #[func] + pub fn update( + self, + /// If given a non function-value, sets the state to that value. If + /// given a function, that function receives the previous state and has + /// to return the new state. + update: StateUpdate, + ) -> Content { + UpdateElem::new(self.key, update).pack() + } + + /// Get the value of the state at the given location. + #[func] + pub fn at( + &self, + /// The virtual typesetter. + vt: &mut Vt, + /// The location at which the state's value should be retrieved. A + /// suitable location can be retrieved from [`locate`]($locate) or + /// [`query`]($query). + location: Location, + ) -> SourceResult<Value> { + let sequence = self.sequence(vt)?; + let offset = vt + .introspector + .query(&self.selector().before(location.into(), true)) + .len(); + Ok(sequence[offset].clone()) + } + + /// Get the value of the state at the end of the document. + #[func] + pub fn final_( + &self, + /// The virtual typesetter. + vt: &mut Vt, + /// Can be an arbitrary location, as its value is irrelevant for the + /// method's return value. Why is it required then? As noted before, + /// Typst has to evaluate parts of your code multiple times to determine + /// the values of all state. By only allowing this method within + /// [`locate`]($locate) calls, the amount of code that can depend on the + /// method's result is reduced. If you could call `final` directly at + /// the top level of a module, the evaluation of the whole module and + /// its exports could depend on the state's value. + location: Location, + ) -> SourceResult<Value> { + let _ = location; + let sequence = self.sequence(vt)?; + Ok(sequence.last().unwrap().clone()) } } @@ -365,10 +346,11 @@ impl Debug for State { } cast! { - type State: "state", + type State, } /// An update to perform on a state. +#[ty] #[derive(Clone, PartialEq, Hash)] pub enum StateUpdate { /// Set the state to the specified value. @@ -384,16 +366,13 @@ impl Debug for StateUpdate { } cast! { - type StateUpdate: "state update", + type StateUpdate, v: Func => Self::Func(v), v: Value => Self::Set(v), } /// Executes a display of a state. -/// -/// Display: State -/// Category: special -#[element(Locatable, Show)] +#[elem(Locatable, Show)] struct DisplayElem { /// The state. #[required] @@ -419,10 +398,7 @@ impl Show for DisplayElem { } /// Executes a display of a state. -/// -/// Display: State -/// Category: special -#[element(Locatable, Show)] +#[elem(Locatable, Show)] struct UpdateElem { /// The key that identifies the state. #[required] |
