From ab43bd802eafe33977a91893907e67553e099569 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sun, 19 Mar 2023 22:28:49 +0100 Subject: [PATCH] Renaming and refactoring --- ARCHITECTURE.md | 14 +- docs/src/lib.rs | 14 +- docs/src/reference/scripting.md | 2 +- docs/src/reference/types.md | 58 +-- docs/src/tutorial/4-template.md | 7 +- library/src/compute/construct.rs | 11 +- library/src/compute/foundations.rs | 2 +- library/src/layout/align.rs | 6 +- library/src/layout/columns.rs | 16 +- library/src/layout/container.rs | 30 +- library/src/layout/enum.rs | 18 +- library/src/layout/flow.rs | 86 ++--- library/src/layout/grid.rs | 10 +- library/src/layout/hide.rs | 8 +- library/src/layout/list.rs | 20 +- library/src/layout/measure.rs | 2 +- library/src/layout/mod.rs | 183 ++++----- library/src/layout/pad.rs | 6 +- library/src/layout/page.rs | 18 +- library/src/layout/par.rs | 194 +++++----- library/src/layout/place.rs | 14 +- library/src/layout/regions.rs | 4 +- library/src/layout/repeat.rs | 10 +- library/src/layout/spacing.rs | 24 +- library/src/layout/stack.rs | 23 +- library/src/layout/table.rs | 18 +- library/src/layout/terms.rs | 28 +- library/src/layout/transform.rs | 18 +- library/src/lib.rs | 170 ++++---- library/src/math/accent.rs | 14 +- library/src/math/align.rs | 6 +- library/src/math/attach.rs | 26 +- library/src/math/ctx.rs | 50 +-- library/src/math/delimited.rs | 29 +- library/src/math/frac.rs | 16 +- library/src/math/fragment.rs | 8 +- library/src/math/matrix.rs | 22 +- library/src/math/mod.rs | 137 ++++--- library/src/math/op.rs | 14 +- library/src/math/root.rs | 40 +- library/src/math/row.rs | 6 +- library/src/math/spacing.rs | 8 +- library/src/math/style.rs | 213 +++++----- library/src/math/underover.rs | 36 +- library/src/meta/bibliography.rs | 106 ++--- library/src/meta/context.rs | 20 +- library/src/meta/counter.rs | 82 ++-- library/src/meta/document.rs | 24 +- library/src/meta/figure.rs | 28 +- library/src/meta/heading.rs | 45 +-- library/src/meta/link.rs | 40 +- library/src/meta/outline.rs | 70 ++-- library/src/meta/query.rs | 28 +- library/src/meta/reference.rs | 48 +-- library/src/meta/state.rs | 37 +- library/src/prelude.rs | 8 +- library/src/shared/behave.rs | 33 +- library/src/shared/ext.rs | 32 +- library/src/symbols/emoji.rs | 2 +- library/src/text/deco.rs | 30 +- library/src/text/misc.rs | 36 +- library/src/text/mod.rs | 34 +- library/src/text/quotes.rs | 4 +- library/src/text/raw.rs | 41 +- library/src/text/shaping.rs | 72 ++-- library/src/text/shift.rs | 42 +- library/src/visualize/image.rs | 8 +- library/src/visualize/line.rs | 8 +- library/src/visualize/shape.rs | 26 +- macros/src/{node.rs => element.rs} | 140 +++---- macros/src/lib.rs | 8 +- src/doc.rs | 242 ++++++------ src/eval/array.rs | 14 +- src/eval/func.rs | 69 +--- src/eval/library.rs | 26 +- src/eval/methods.rs | 31 +- src/eval/mod.rs | 42 +- src/eval/scope.rs | 4 +- src/eval/symbol.rs | 126 +++--- src/eval/value.rs | 9 +- src/export/pdf/page.rs | 71 ++-- src/export/render.rs | 40 +- src/geom/paint.rs | 14 +- src/geom/path.rs | 22 +- src/ide/analyze.rs | 15 +- src/ide/complete.rs | 4 +- src/ide/jump.rs | 54 ++- src/lib.rs | 4 +- src/model/content.rs | 554 +++++++++++++-------------- src/model/element.rs | 145 +++++++ src/model/introspect.rs | 170 ++++++++ src/model/mod.rs | 83 +++- src/model/realize.rs | 96 +++-- src/model/styles.rs | 271 +++++++------ src/model/typeset.rs | 241 ------------ src/syntax/kind.rs | 6 +- src/syntax/lexer.rs | 8 +- src/syntax/mod.rs | 1 - src/syntax/source.rs | 3 +- src/util/mod.rs | 25 +- tests/ref/compiler/show-node.png | Bin 24387 -> 24545 bytes tests/ref/layout/place.png | Bin 44401 -> 44724 bytes tests/src/tests.rs | 22 +- tests/typ/compiler/construct.typ | 2 +- tests/typ/compiler/content-field.typ | 16 +- tests/typ/compiler/field.typ | 10 +- tests/typ/compiler/show-node.typ | 12 +- tests/typ/compute/construct.typ | 4 +- tests/typ/layout/list-marker.typ | 2 +- tests/typ/layout/pad.typ | 2 +- tests/typ/layout/par-bidi.typ | 2 +- tests/typ/layout/place.typ | 4 +- tests/typ/meta/counter.typ | 2 +- tests/typ/meta/document.typ | 8 +- tests/typ/meta/query.typ | 4 +- tests/typ/meta/state.typ | 6 +- tests/typ/text/features.typ | 2 +- tests/typ/text/symbol.typ | 2 +- 118 files changed, 2643 insertions(+), 2538 deletions(-) rename macros/src/{node.rs => element.rs} (80%) create mode 100644 src/model/element.rs create mode 100644 src/model/introspect.rs delete mode 100644 src/model/typeset.rs diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 60254ba61..af48401d1 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -94,10 +94,10 @@ items into a list that we want to layout, we don't realize the content within the list items just yet. This only happens lazily once the list items are layouted. -When we a have realized the content into a layoutable -node, we can then layout it into _regions,_ which describe the space into which -the content shall be layouted. Within these, a node is free to layout itself -as it sees fit, returning one `Frame` per region it wants to occupy. +When we a have realized the content into a layoutable element, we can then +layout it into _regions,_ which describe the space into which the content shall +be layouted. Within these, an element is free to layout itself as it sees fit, +returning one `Frame` per region it wants to occupy. **Introspection:** How content layouts (and realizes) may depend on how _it itself_ is layouted @@ -108,9 +108,9 @@ introspections stabilize after one or two iterations. However, some may never stabilize, so we give up after five attempts. **Incremental:** -Layout caching happens at the granularity of a node. This is important because -overall layout is the most expensive compilation phase, so we want to reuse as -much as possible. +Layout caching happens at the granularity of the element. This is important +because overall layout is the most expensive compilation phase, so we want to +reuse as much as possible. ## Export diff --git a/docs/src/lib.rs b/docs/src/lib.rs index 563d565c2..97535b1a2 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -18,7 +18,7 @@ use typst::doc::Frame; use typst::eval::{CastInfo, Func, FuncInfo, Library, Module, ParamInfo, Value}; use typst::font::{Font, FontBook}; use typst::geom::{Abs, Sides, Smart}; -use typst_library::layout::PageNode; +use typst_library::layout::PageElem; use unscanny::Scanner; static SRC: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/src"); @@ -40,9 +40,9 @@ static FONTS: Lazy<(Prehashed, Vec)> = Lazy::new(|| { static LIBRARY: Lazy> = Lazy::new(|| { let mut lib = typst_library::build(); lib.styles - .set(PageNode::set_width(Smart::Custom(Abs::pt(240.0).into()))); - lib.styles.set(PageNode::set_height(Smart::Auto)); - lib.styles.set(PageNode::set_margin(Sides::splat(Some(Smart::Custom( + .set(PageElem::set_width(Smart::Custom(Abs::pt(240.0).into()))); + lib.styles.set(PageElem::set_height(Smart::Auto)); + lib.styles.set(PageElem::set_margin(Sides::splat(Some(Smart::Custom( Abs::pt(15.0).into(), ))))); typst::eval::set_lang_items(lib.items.clone()); @@ -299,8 +299,8 @@ pub struct FuncModel { pub name: &'static str, pub display: &'static str, pub oneliner: &'static str, - pub details: Html, pub showable: bool, + pub details: Html, pub params: Vec, pub returns: Vec<&'static str>, } @@ -336,8 +336,8 @@ fn func_model(resolver: &dyn Resolver, func: &Func, info: &FuncInfo) -> FuncMode name: info.name.into(), display: info.display, oneliner: oneliner(info.docs), + showable: func.element().is_some(), details: Html::markdown(resolver, info.docs), - showable: func.select(None).is_ok() && info.category != "math", params: info.params.iter().map(|param| param_model(resolver, param)).collect(), returns: info.returns.clone(), } @@ -632,7 +632,7 @@ fn symbol_page(resolver: &dyn Resolver, parent: &str, name: &str) -> PageModel { .find(|&(_, x)| x == c) .map(|(s, _)| s), codepoint: c as u32, - accent: typst::eval::combining_accent(c).is_some(), + accent: typst::eval::Symbol::combining_accent(c).is_some(), unicode_name: unicode_names2::name(c) .map(|s| s.to_string().to_title_case()), alternates: symbol diff --git a/docs/src/reference/scripting.md b/docs/src/reference/scripting.md index 68e73c62e..1aa399d1a 100644 --- a/docs/src/reference/scripting.md +++ b/docs/src/reference/scripting.md @@ -138,7 +138,7 @@ For loops can iterate over a variety of collections: - `{for value in array {..}}` \ `{for index, value in array {..}}`\ Iterates over the items in the [array]($type/array). Can also provide the - index of each element. + index of each item. - `{for value in dict {..}}` \ `{for key, value in dict {..}}` \ diff --git a/docs/src/reference/types.md b/docs/src/reference/types.md index 355027f58..a64f4eb39 100644 --- a/docs/src/reference/types.md +++ b/docs/src/reference/types.md @@ -420,7 +420,7 @@ A sequence of values. You can construct an array by enclosing a comma-separated sequence of values in parentheses. The values do not have to be of the same type. -You can access and update array elements with the `.at()` method. Indices are +You can access and update array items with the `.at()` method. Indices are zero-based and negative indices wrap around to the end of the array. You can iterate over an array using a [for loop]($scripting/#loops). Arrays can be added together with the `+` operator, @@ -453,26 +453,26 @@ The number of values in the array. - returns: integer ### first() -Returns the first element in the array. +Returns the first item in the array. May be used on the left-hand side of an assignment. Fails with an error if the array is empty. - returns: any ### last() -Returns the last element in the array. +Returns the last item in the array. May be used on the left-hand side of an assignment. Fails with an error if the array is empty. - returns: any ### at() -Returns the element at the specified index in the array. +Returns the item at the specified index in the array. May be used on the left-hand side of an assignment. Fails with an error if the index is out of bounds. - index: integer (positional, required) - The index at which to retrieve the element. + The index at which to retrieve the item. - returns: any ### push() @@ -482,7 +482,7 @@ Add a value to the end of the array. The value to insert at the end of the array. ### pop() -Remove the last element from the array and return it. +Remove the last item from the array and return it. Fails with an error if the array is empty. - returns: any @@ -493,7 +493,7 @@ Insert a value into the array at the specified index. Fails with an error if the index is out of bounds. - index: integer (positional, required) - The index at which to insert the element. + The index at which to insert the item. - value: any (positional, required) The value to insert into the array. @@ -501,7 +501,7 @@ Fails with an error if the index is out of bounds. Remove the value at the specified index from the array and return it. - index: integer (positional, required) - The index at which to remove the element. + The index at which to remove the item. - returns: any ### slice() @@ -514,7 +514,7 @@ Fails with an error if the start or index is out of bounds. The end index (exclusive). If omitted, the whole slice until the end of the array is extracted. - count: integer (named) - The number of elements to extract. This is equivalent to passing `start + + The number of items to extract. This is equivalent to passing `start + count` as the `end` position. Mutually exclusive with `end`. - returns: array @@ -529,59 +529,59 @@ of `{(1, 2, 3).contains(2)}`. - returns: boolean ### find() -Searches for an element for which the given function returns `{true}` and +Searches for an item for which the given function returns `{true}` and returns the first match or `{none}` if there is no match. - searcher: function (positional, required) - The function to apply to each element. Must return a boolean. + The function to apply to each item. Must return a boolean. - returns: any or none ### position() -Searches for an element for which the given function returns `{true}` and +Searches for an item for which the given function returns `{true}` and returns the index of the first match or `{none}` if there is no match. - searcher: function (positional, required) - The function to apply to each element. Must return a boolean. + The function to apply to each item. Must return a boolean. - returns: integer or none ### filter() -Produces a new array with only the elements from the original one for which the +Produces a new array with only the items from the original one for which the given function returns true. - test: function (positional, required) - The function to apply to each element. Must return a boolean. + The function to apply to each item. Must return a boolean. - returns: array ### map() -Produces a new array in which all elements from the original one were +Produces a new array in which all items from the original one were transformed with the given function. - mapper: function (positional, required) - The function to apply to each element. + The function to apply to each item. - returns: array ### fold() -Folds all elements into a single value using an accumulator function. +Folds all items into a single value using an accumulator function. - init: any (positional, required) The initial value to start with. - folder: function (positional, required) The folding function. Must have two parameters: One for the accumulated value - and one for an element. + and one for an item. - returns: any ### any() -Whether the given function returns `{true}` for any element in the array. +Whether the given function returns `{true}` for any item in the array. - test: function (positional, required) - The function to apply to each element. Must return a boolean. + The function to apply to each item. Must return a boolean. - returns: boolean ### all() -Whether the given function returns `{true}` for all elements in the array. +Whether the given function returns `{true}` for all items in the array. - test: function (positional, required) - The function to apply to each element. Must return a boolean. + The function to apply to each item. Must return a boolean. - returns: boolean ### flatten() @@ -590,21 +590,21 @@ Combine all nested arrays into a single flat one. - returns: array ### rev() -Return a new array with the same elements, but in reverse order. +Return a new array with the same items, but in reverse order. - returns: array ### join() -Combine all elements in the array into one. +Combine all items in the array into one. - separator: any (positional) - A value to insert between each element of the array. + A value to insert between each item of the array. - last: any (named) - An alternative separator between the last two elements + An alternative separator between the last two items - returns: any ### sorted() -Return a new array with the same elements, but sorted. +Return a new array with the same items, but sorted. - returns: array @@ -658,7 +658,7 @@ present in the dictionary. Fails with an error if the key is not part of the dictionary. - index: integer (positional, required) - The index at which to retrieve the element. + The index at which to retrieve the item. - returns: any ### insert() diff --git a/docs/src/tutorial/4-template.md b/docs/src/tutorial/4-template.md index d49ee6a19..3208f0125 100644 --- a/docs/src/tutorial/4-template.md +++ b/docs/src/tutorial/4-template.md @@ -206,11 +206,12 @@ from the dictionary, we use the [field access syntax]($scripting/#fields). We still have to provide an argument to the grid for each author: Here is where the array's [`map` method]($type/array.map) comes in handy. It takes a function -as an argument that gets called with each element of the array. We pass it a +as an argument that gets called with each item of the array. We pass it a function that formats the details for each author and returns a new array containing content values. We've now got one array of values that we'd like to -use as multiple arguments for the grid. We can do that by using the -[`spread` operator]($type/arguments). It takes an array and applies each of its elements as a separate argument to the function. +use as multiple arguments for the grid. We can do that by using the [`spread` +operator]($type/arguments). It takes an array and applies each of its items as a +separate argument to the function. The resulting template function looks like this: diff --git a/library/src/compute/construct.rs b/library/src/compute/construct.rs index 4d6068a11..a30faf2ea 100644 --- a/library/src/compute/construct.rs +++ b/library/src/compute/construct.rs @@ -1,7 +1,6 @@ use std::num::NonZeroI64; use std::str::FromStr; -use ecow::EcoVec; use typst::eval::Regex; use crate::prelude::*; @@ -173,12 +172,12 @@ cast_from_value! { Component, v: i64 => match v { 0 ..= 255 => Self(v as u8), - _ => Err("must be between 0 and 255")?, + _ => Err("number must be between 0 and 255")?, }, v: Ratio => if (0.0 ..= 1.0).contains(&v.get()) { Self((v.get() * 255.0).round() as u8) } else { - Err("must be between 0% and 100%")? + Err("ratio must be between 0% and 100%")? }, } @@ -220,7 +219,7 @@ cast_from_value! { v: Ratio => if (0.0 ..= 1.0).contains(&v.get()) { Self((v.get() * 255.0).round() as u8) } else { - Err("must be between 0% and 100%")? + Err("ratio must be between 0% and 100%")? }, } @@ -258,14 +257,14 @@ pub fn symbol( #[variadic] variants: Vec>, ) -> Value { - let mut list = EcoVec::new(); + let mut list = Vec::new(); for Spanned { v, span } in variants { if list.iter().any(|(prev, _)| &v.0 == prev) { bail!(span, "duplicate variant"); } list.push((v.0, v.1)); } - Value::Symbol(Symbol::runtime(list)) + Value::Symbol(Symbol::runtime(list.into_boxed_slice())) } /// A value that can be cast to a symbol. diff --git a/library/src/compute/foundations.rs b/library/src/compute/foundations.rs index 8b148c856..d5397e601 100644 --- a/library/src/compute/foundations.rs +++ b/library/src/compute/foundations.rs @@ -134,5 +134,5 @@ pub fn eval( source: Spanned, ) -> Value { let Spanned { v: text, span } = source; - typst::eval::eval_code_str(vm.world(), &text, span)? + typst::eval::eval_string(vm.world(), &text, span)? } diff --git a/library/src/layout/align.rs b/library/src/layout/align.rs index 2a3998bfc..c2f8262ea 100644 --- a/library/src/layout/align.rs +++ b/library/src/layout/align.rs @@ -14,8 +14,8 @@ use crate::prelude::*; /// /// Display: Align /// Category: layout -#[node(Show)] -pub struct AlignNode { +#[element(Show)] +pub struct AlignElem { /// The alignment along both axes. /// /// Possible values for horizontal alignments are: @@ -57,7 +57,7 @@ pub struct AlignNode { pub body: Content, } -impl Show for AlignNode { +impl Show for AlignElem { fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult { Ok(self .body() diff --git a/library/src/layout/columns.rs b/library/src/layout/columns.rs index 7704e9c41..3a1b012aa 100644 --- a/library/src/layout/columns.rs +++ b/library/src/layout/columns.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use crate::text::TextNode; +use crate::text::TextElem; /// Separate a region into multiple equally sized columns. /// @@ -32,8 +32,8 @@ use crate::text::TextNode; /// /// Display: Columns /// Category: layout -#[node(Layout)] -pub struct ColumnsNode { +#[element(Layout)] +pub struct ColumnsElem { /// The number of columns. #[positional] #[default(NonZeroUsize::new(2).unwrap())] @@ -49,7 +49,7 @@ pub struct ColumnsNode { pub body: Content, } -impl Layout for ColumnsNode { +impl Layout for ColumnsElem { fn layout( &self, vt: &mut Vt, @@ -88,7 +88,7 @@ impl Layout for ColumnsNode { let mut frames = body.layout(vt, styles, pod)?.into_iter(); let mut finished = vec![]; - let dir = TextNode::dir_in(styles); + let dir = TextElem::dir_in(styles); let total_regions = (frames.len() as f32 / columns as f32).ceil() as usize; // Stitch together the columns for each region. @@ -151,15 +151,15 @@ impl Layout for ColumnsNode { /// /// Display: Column Break /// Category: layout -#[node(Behave)] -pub struct ColbreakNode { +#[element(Behave)] +pub struct ColbreakElem { /// If `{true}`, the column break is skipped if the current column is /// already empty. #[default(false)] pub weak: bool, } -impl Behave for ColbreakNode { +impl Behave for ColbreakElem { fn behaviour(&self) -> Behaviour { if self.weak(StyleChain::default()) { Behaviour::Weak(1) diff --git a/library/src/layout/container.rs b/library/src/layout/container.rs index bdc147c37..ef7def7ad 100644 --- a/library/src/layout/container.rs +++ b/library/src/layout/container.rs @@ -1,4 +1,4 @@ -use super::VNode; +use super::VElem; use crate::layout::Spacing; use crate::prelude::*; @@ -21,8 +21,8 @@ use crate::prelude::*; /// /// Display: Box /// Category: layout -#[node(Layout)] -pub struct BoxNode { +#[element(Layout)] +pub struct BoxElem { /// The width of the box. /// /// Boxes can have [fractional]($type/fraction) widths, as the example @@ -93,7 +93,7 @@ pub struct BoxNode { pub body: Option, } -impl Layout for BoxNode { +impl Layout for BoxElem { fn layout( &self, vt: &mut Vt, @@ -183,8 +183,8 @@ impl Layout for BoxNode { /// /// Display: Block /// Category: layout -#[node(Layout)] -pub struct BlockNode { +#[element(Layout)] +pub struct BlockElem { /// The block's width. /// /// ```example @@ -278,11 +278,11 @@ pub struct BlockNode { #[parse( let spacing = args.named("spacing")?; args.named("above")? - .map(VNode::block_around) - .or_else(|| spacing.map(VNode::block_spacing)) + .map(VElem::block_around) + .or_else(|| spacing.map(VElem::block_spacing)) )] - #[default(VNode::block_spacing(Em::new(1.2).into()))] - pub above: VNode, + #[default(VElem::block_spacing(Em::new(1.2).into()))] + pub above: VElem, /// The spacing between this block and its successor. Takes precedence /// over `spacing`. @@ -290,11 +290,11 @@ pub struct BlockNode { /// The default value is `{1.2em}`. #[parse( args.named("below")? - .map(VNode::block_around) - .or_else(|| spacing.map(VNode::block_spacing)) + .map(VElem::block_around) + .or_else(|| spacing.map(VElem::block_spacing)) )] - #[default(VNode::block_spacing(Em::new(1.2).into()))] - pub below: VNode, + #[default(VElem::block_spacing(Em::new(1.2).into()))] + pub below: VElem, /// The contents of the block. #[positional] @@ -308,7 +308,7 @@ pub struct BlockNode { pub sticky: bool, } -impl Layout for BlockNode { +impl Layout for BlockElem { fn layout( &self, vt: &mut Vt, diff --git a/library/src/layout/enum.rs b/library/src/layout/enum.rs index 05b42bd82..1be57d4ce 100644 --- a/library/src/layout/enum.rs +++ b/library/src/layout/enum.rs @@ -1,9 +1,9 @@ use std::str::FromStr; -use crate::layout::{BlockNode, ParNode, Sizing, Spacing}; +use crate::layout::{BlockElem, ParElem, Sizing, Spacing}; use crate::meta::{Numbering, NumberingPattern}; use crate::prelude::*; -use crate::text::TextNode; +use crate::text::TextElem; use super::GridLayouter; @@ -50,8 +50,8 @@ use super::GridLayouter; /// /// Display: Numbered List /// Category: layout -#[node(Layout)] -pub struct EnumNode { +#[element(Layout)] +pub struct EnumElem { /// If this is `{false}`, the items are spaced apart with /// [enum spacing]($func/enum.spacing). If it is `{true}`, they use normal /// [leading]($func/par.leading) instead. This makes the enumeration more @@ -153,7 +153,7 @@ pub struct EnumNode { parents: Parent, } -impl Layout for EnumNode { +impl Layout for EnumElem { fn layout( &self, vt: &mut Vt, @@ -164,10 +164,10 @@ impl Layout for EnumNode { let indent = self.indent(styles); let body_indent = self.body_indent(styles); let gutter = if self.tight(styles) { - ParNode::leading_in(styles).into() + ParElem::leading_in(styles).into() } else { self.spacing(styles) - .unwrap_or_else(|| BlockNode::below_in(styles).amount()) + .unwrap_or_else(|| BlockElem::below_in(styles).amount()) }; let mut cells = vec![]; @@ -186,7 +186,7 @@ impl Layout for EnumNode { } else { match &numbering { Numbering::Pattern(pattern) => { - TextNode::packed(pattern.apply_kth(parents.len(), number)) + TextElem::packed(pattern.apply_kth(parents.len(), number)) } other => other.apply_vt(vt, &[number])?.display(), } @@ -221,7 +221,7 @@ impl Layout for EnumNode { /// /// Display: Numbered List Item /// Category: layout -#[node] +#[element] pub struct EnumItem { /// The item's number. #[positional] diff --git a/library/src/layout/flow.rs b/library/src/layout/flow.rs index 096c575e6..b64768169 100644 --- a/library/src/layout/flow.rs +++ b/library/src/layout/flow.rs @@ -1,24 +1,22 @@ -use typst::model::StyledNode; - -use super::{AlignNode, BlockNode, ColbreakNode, ParNode, PlaceNode, Spacing, VNode}; +use super::{AlignElem, BlockElem, ColbreakElem, ParElem, PlaceElem, Spacing, VElem}; use crate::prelude::*; -use crate::visualize::{CircleNode, EllipseNode, ImageNode, RectNode, SquareNode}; +use crate::visualize::{CircleElem, EllipseElem, ImageElem, RectElem, SquareElem}; -/// Arrange spacing, paragraphs and block-level nodes into a flow. +/// Arrange spacing, paragraphs and block-level elements into a flow. /// -/// This node is responsible for layouting both the top-level content flow and +/// This element is responsible for layouting both the top-level content flow and /// the contents of boxes. /// /// Display: Flow /// Category: layout -#[node(Layout)] -pub struct FlowNode { +#[element(Layout)] +pub struct FlowElem { /// The children that will be arranges into a flow. #[variadic] pub children: Vec, } -impl Layout for FlowNode { +impl Layout for FlowElem { fn layout( &self, vt: &mut Vt, @@ -27,29 +25,27 @@ impl Layout for FlowNode { ) -> SourceResult { let mut layouter = FlowLayouter::new(regions); - for mut child in self.children() { - let map; + for mut child in &self.children() { let outer = styles; - let mut styles = outer; - if let Some(node) = child.to::() { - map = node.styles(); + let mut styles = styles; + if let Some((elem, map)) = child.to_styled() { + child = elem; styles = outer.chain(&map); - child = node.body(); } - if let Some(node) = child.to::() { - layouter.layout_spacing(node, styles); - } else if let Some(node) = child.to::() { - layouter.layout_par(vt, node, styles)?; - } else if child.is::() - || child.is::() - || child.is::() - || child.is::() - || child.is::() + if let Some(elem) = child.to::() { + layouter.layout_spacing(elem, styles); + } else if let Some(elem) = child.to::() { + layouter.layout_par(vt, elem, styles)?; + } else if child.is::() + || child.is::() + || child.is::() + || child.is::() + || child.is::() { let layoutable = child.with::().unwrap(); layouter.layout_single(vt, layoutable, styles)?; - } else if child.is::() { + } else if child.is::() { let mut frame = Frame::new(Size::zero()); frame.meta(styles, true); layouter.items.push(FlowItem::Frame( @@ -59,7 +55,7 @@ impl Layout for FlowNode { )); } else if child.can::() { layouter.layout_multiple(vt, &child, styles)?; - } else if child.is::() { + } else if child.is::() { if !layouter.regions.backlog.is_empty() || layouter.regions.last.is_some() { layouter.finish_region(); @@ -122,13 +118,13 @@ impl<'a> FlowLayouter<'a> { } /// Layout vertical spacing. - fn layout_spacing(&mut self, node: &VNode, styles: StyleChain) { - self.layout_item(match node.amount() { - Spacing::Rel(v) => FlowItem::Absolute( - v.resolve(styles).relative_to(self.initial.y), - node.weakness(styles) > 0, + fn layout_spacing(&mut self, v: &VElem, styles: StyleChain) { + self.layout_item(match v.amount() { + Spacing::Rel(rel) => FlowItem::Absolute( + rel.resolve(styles).relative_to(self.initial.y), + v.weakness(styles) > 0, ), - Spacing::Fr(v) => FlowItem::Fractional(v), + Spacing::Fr(fr) => FlowItem::Fractional(fr), }); } @@ -136,11 +132,11 @@ impl<'a> FlowLayouter<'a> { fn layout_par( &mut self, vt: &mut Vt, - par: &ParNode, + par: &ParElem, styles: StyleChain, ) -> SourceResult<()> { - let aligns = AlignNode::alignment_in(styles).resolve(styles); - let leading = ParNode::leading_in(styles); + let aligns = AlignElem::alignment_in(styles).resolve(styles); + let leading = ParElem::leading_in(styles); let consecutive = self.last_was_par; let frames = par .layout(vt, styles, consecutive, self.regions.base(), self.regions.expand.x)? @@ -185,8 +181,8 @@ impl<'a> FlowLayouter<'a> { content: &dyn Layout, styles: StyleChain, ) -> SourceResult<()> { - let aligns = AlignNode::alignment_in(styles).resolve(styles); - let sticky = BlockNode::sticky_in(styles); + let aligns = AlignElem::alignment_in(styles).resolve(styles); + let sticky = BlockElem::sticky_in(styles); let pod = Regions::one(self.regions.base(), Axes::splat(false)); let frame = content.layout(vt, styles, pod)?.into_frame(); self.layout_item(FlowItem::Frame(frame, aligns, sticky)); @@ -201,9 +197,9 @@ impl<'a> FlowLayouter<'a> { block: &Content, styles: StyleChain, ) -> SourceResult<()> { - // Placed nodes that are out of flow produce placed items which aren't - // aligned later. - if let Some(placed) = block.to::() { + // Placed elements that are out of flow produce placed items which + // aren't aligned later. + if let Some(placed) = block.to::() { if placed.out_of_flow(styles) { let frame = block.layout(vt, styles, self.regions)?.into_frame(); self.layout_item(FlowItem::Placed(frame)); @@ -212,17 +208,17 @@ impl<'a> FlowLayouter<'a> { } // How to align the block. - let aligns = if let Some(align) = block.to::() { + let aligns = if let Some(align) = block.to::() { align.alignment(styles) - } else if let Some(styled) = block.to::() { - AlignNode::alignment_in(styles.chain(&styled.styles())) + } else if let Some((_, local)) = block.to_styled() { + AlignElem::alignment_in(styles.chain(local)) } else { - AlignNode::alignment_in(styles) + AlignElem::alignment_in(styles) } .resolve(styles); // Layout the block itself. - let sticky = BlockNode::sticky_in(styles); + let sticky = BlockElem::sticky_in(styles); let fragment = block.layout(vt, styles, self.regions)?; for (i, frame) in fragment.into_iter().enumerate() { if i > 0 { diff --git a/library/src/layout/grid.rs b/library/src/layout/grid.rs index 47d3ab86f..5c3d132ef 100644 --- a/library/src/layout/grid.rs +++ b/library/src/layout/grid.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use crate::text::TextNode; +use crate::text::TextElem; use super::Sizing; @@ -61,8 +61,8 @@ use super::Sizing; /// /// Display: Grid /// Category: layout -#[node(Layout)] -pub struct GridNode { +#[element(Layout)] +pub struct GridElem { /// Defines the column sizes. /// /// Either specify a track size array or provide an integer to create a grid @@ -101,7 +101,7 @@ pub struct GridNode { pub children: Vec, } -impl Layout for GridNode { +impl Layout for GridElem { fn layout( &self, vt: &mut Vt, @@ -257,7 +257,7 @@ impl<'a, 'v> GridLayouter<'a, 'v> { } // Reverse for RTL. - let is_rtl = TextNode::dir_in(styles) == Dir::RTL; + let is_rtl = TextElem::dir_in(styles) == Dir::RTL; if is_rtl { cols.reverse(); } diff --git a/library/src/layout/hide.rs b/library/src/layout/hide.rs index 1d87d3e8b..d9bee317b 100644 --- a/library/src/layout/hide.rs +++ b/library/src/layout/hide.rs @@ -15,15 +15,15 @@ use crate::prelude::*; /// /// Display: Hide /// Category: layout -#[node(Show)] -pub struct HideNode { +#[element(Show)] +pub struct HideElem { /// The content to hide. #[required] pub body: Content, } -impl Show for HideNode { +impl Show for HideElem { fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult { - Ok(self.body().styled(MetaNode::set_data(vec![Meta::Hide]))) + Ok(self.body().styled(MetaElem::set_data(vec![Meta::Hide]))) } } diff --git a/library/src/layout/list.rs b/library/src/layout/list.rs index fe78131d5..179c93eb9 100644 --- a/library/src/layout/list.rs +++ b/library/src/layout/list.rs @@ -1,6 +1,6 @@ -use crate::layout::{BlockNode, ParNode, Sizing, Spacing}; +use crate::layout::{BlockElem, ParElem, Sizing, Spacing}; use crate::prelude::*; -use crate::text::TextNode; +use crate::text::TextElem; use super::GridLayouter; @@ -36,8 +36,8 @@ use super::GridLayouter; /// /// Display: Bullet List /// Category: layout -#[node(Layout)] -pub struct ListNode { +#[element(Layout)] +pub struct ListElem { /// If this is `{false}`, the items are spaced apart with [list /// spacing]($func/list.spacing). If it is `{true}`, they use normal /// [leading]($func/par.leading) instead. This makes the list more compact, @@ -111,7 +111,7 @@ pub struct ListNode { depth: Depth, } -impl Layout for ListNode { +impl Layout for ListElem { fn layout( &self, vt: &mut Vt, @@ -121,10 +121,10 @@ impl Layout for ListNode { let indent = self.indent(styles); let body_indent = self.body_indent(styles); let gutter = if self.tight(styles) { - ParNode::leading_in(styles).into() + ParElem::leading_in(styles).into() } else { self.spacing(styles) - .unwrap_or_else(|| BlockNode::below_in(styles).amount()) + .unwrap_or_else(|| BlockElem::below_in(styles).amount()) }; let depth = self.depth(styles); @@ -160,7 +160,7 @@ impl Layout for ListNode { /// /// Display: Bullet List Item /// Category: layout -#[node] +#[element] pub struct ListItem { /// The item's body. #[required] @@ -187,7 +187,7 @@ impl ListMarker { .get(depth) .or(list.last()) .cloned() - .unwrap_or_else(|| TextNode::packed('•')), + .unwrap_or_else(|| TextElem::packed('•')), Self::Func(func) => func.call_vt(vt, [Value::Int(depth as i64)])?.display(), }) } @@ -198,7 +198,7 @@ cast_from_value! { v: Content => Self::Content(vec![v]), array: Array => { if array.len() == 0 { - Err("must contain at least one marker")?; + Err("array must contain at least one marker")?; } Self::Content(array.into_iter().map(Value::display).collect()) }, diff --git a/library/src/layout/measure.rs b/library/src/layout/measure.rs index b116cbf8d..df66d67f3 100644 --- a/library/src/layout/measure.rs +++ b/library/src/layout/measure.rs @@ -10,7 +10,7 @@ pub fn measure( /// The content whose size to measure. content: Content, /// The styles with which to layout the content. - styles: StyleMap, + styles: Styles, ) -> Value { let pod = Regions::one(Axes::splat(Abs::inf()), Axes::splat(false)); let styles = StyleChain::new(&styles); diff --git a/library/src/layout/mod.rs b/library/src/layout/mod.rs index b6ecce51f..02d3bca5f 100644 --- a/library/src/layout/mod.rs +++ b/library/src/layout/mod.rs @@ -50,14 +50,14 @@ use std::mem; use typed_arena::Arena; use typst::diag::SourceResult; use typst::eval::Tracer; -use typst::model::{applicable, realize, SequenceNode, StyleVecBuilder, StyledNode}; +use typst::model::{applicable, realize, StyleVecBuilder}; -use crate::math::{EquationNode, LayoutMath}; -use crate::meta::DocumentNode; +use crate::math::{EquationElem, LayoutMath}; +use crate::meta::DocumentElem; use crate::prelude::*; use crate::shared::BehavedBuilder; -use crate::text::{LinebreakNode, SmartQuoteNode, SpaceNode, TextNode}; -use crate::visualize::{CircleNode, EllipseNode, ImageNode, RectNode, SquareNode}; +use crate::text::{LinebreakElem, SmartQuoteElem, SpaceElem, TextElem}; +use crate::visualize::{CircleElem, EllipseElem, ImageElem, RectElem, SquareElem}; /// Root-level layout. pub trait LayoutRoot { @@ -69,7 +69,7 @@ impl LayoutRoot for Content { fn layout_root(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult { #[comemo::memoize] fn cached( - node: &Content, + content: &Content, world: Tracked, tracer: TrackedMut, provider: TrackedMut, @@ -78,7 +78,7 @@ impl LayoutRoot for Content { ) -> SourceResult { let mut vt = Vt { world, tracer, provider, introspector }; let scratch = Scratch::default(); - let (realized, styles) = realize_root(&mut vt, &scratch, node, styles)?; + let (realized, styles) = realize_root(&mut vt, &scratch, content, styles)?; realized .with::() .unwrap() @@ -108,8 +108,8 @@ pub trait Layout { /// Layout without side effects. /// - /// This node must be layouted again in the same order for the results to be - /// valid. + /// This element must be layouted again in the same order for the results to + /// be valid. fn measure( &self, vt: &mut Vt, @@ -132,7 +132,7 @@ impl Layout for Content { ) -> SourceResult { #[comemo::memoize] fn cached( - node: &Content, + content: &Content, world: Tracked, tracer: TrackedMut, provider: TrackedMut, @@ -142,7 +142,7 @@ impl Layout for Content { ) -> SourceResult { let mut vt = Vt { world, tracer, provider, introspector }; let scratch = Scratch::default(); - let (realized, styles) = realize_block(&mut vt, &scratch, node, styles)?; + let (realized, styles) = realize_block(&mut vt, &scratch, content, styles)?; realized .with::() .unwrap() @@ -161,7 +161,7 @@ impl Layout for Content { } } -/// Realize into a node that is capable of root-level layout. +/// Realize into an element that is capable of root-level layout. fn realize_root<'a>( vt: &mut Vt, scratch: &'a Scratch<'a>, @@ -176,10 +176,10 @@ fn realize_root<'a>( builder.accept(content, styles)?; builder.interrupt_page(Some(styles))?; let (pages, shared) = builder.doc.unwrap().pages.finish(); - Ok((DocumentNode::new(pages.to_vec()).pack(), shared)) + Ok((DocumentElem::new(pages.to_vec()).pack(), shared)) } -/// Realize into a node that is capable of block-level layout. +/// Realize into an element that is capable of block-level layout. fn realize_block<'a>( vt: &mut Vt, scratch: &'a Scratch<'a>, @@ -187,11 +187,11 @@ fn realize_block<'a>( styles: StyleChain<'a>, ) -> SourceResult<(Content, StyleChain<'a>)> { if content.can::() - && !content.is::() - && !content.is::() - && !content.is::() - && !content.is::() - && !content.is::() + && !content.is::() + && !content.is::() + && !content.is::() + && !content.is::() + && !content.is::() && !applicable(content, styles) { return Ok((content.clone(), styles)); @@ -201,10 +201,10 @@ fn realize_block<'a>( builder.accept(content, styles)?; builder.interrupt_par()?; let (children, shared) = builder.flow.0.finish(); - Ok((FlowNode::new(children.to_vec()).pack(), shared)) + Ok((FlowElem::new(children.to_vec()).pack(), shared)) } -/// Builds a document or a flow node from content. +/// Builds a document or a flow element from content. struct Builder<'a, 'v, 't> { /// The virtual typesetter. vt: &'v mut Vt<'t>, @@ -227,7 +227,6 @@ struct Scratch<'a> { styles: Arena>, /// An arena where intermediate content resulting from show rules is stored. content: Arena, - maps: Arena, } impl<'a, 'v, 't> Builder<'a, 'v, 't> { @@ -247,19 +246,18 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> { mut content: &'a Content, styles: StyleChain<'a>, ) -> SourceResult<()> { - if content.can::() && !content.is::() { + if content.can::() && !content.is::() { content = - self.scratch.content.alloc(EquationNode::new(content.clone()).pack()); + self.scratch.content.alloc(EquationElem::new(content.clone()).pack()); } - if let Some(styled) = content.to::() { - return self.styled(styled, styles); + if let Some((elem, local)) = content.to_styled() { + return self.styled(elem, local, styles); } - if let Some(seq) = content.to::() { - for sub in seq.children() { - let stored = self.scratch.content.alloc(sub); - self.accept(stored, styles)?; + if let Some(children) = content.to_sequence() { + for elem in children { + self.accept(elem, styles)?; } return Ok(()); } @@ -290,7 +288,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> { } let keep = content - .to::() + .to::() .map_or(false, |pagebreak| !pagebreak.weak(styles)); self.interrupt_page(keep.then(|| styles))?; @@ -301,52 +299,55 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> { } } - bail!(content.span(), "not allowed here"); + if content.is::() { + bail!(content.span(), "pagebreaks are not allowed inside of containers"); + } else { + bail!(content.span(), "{} is not allowed here", content.func().name()); + } } fn styled( &mut self, - styled: &'a StyledNode, + elem: &'a Content, + map: &'a Styles, styles: StyleChain<'a>, ) -> SourceResult<()> { - let map = self.scratch.maps.alloc(styled.styles()); let stored = self.scratch.styles.alloc(styles); - let content = self.scratch.content.alloc(styled.body()); let styles = stored.chain(map); self.interrupt_style(&map, None)?; - self.accept(content, styles)?; + self.accept(elem, styles)?; self.interrupt_style(map, Some(styles))?; Ok(()) } fn interrupt_style( &mut self, - map: &StyleMap, - styles: Option>, + local: &Styles, + outer: Option>, ) -> SourceResult<()> { - if let Some(Some(span)) = map.interruption::() { + if let Some(Some(span)) = local.interruption::() { if self.doc.is_none() { - bail!(span, "not allowed here"); + bail!(span, "document set rules are not allowed inside of containers"); } - if styles.is_none() + if outer.is_none() && (!self.flow.0.is_empty() || !self.par.0.is_empty() || !self.list.items.is_empty()) { - bail!(span, "must appear before any content"); + bail!(span, "document set rules must appear before any content"); } - } else if let Some(Some(span)) = map.interruption::() { + } else if let Some(Some(span)) = local.interruption::() { if self.doc.is_none() { - bail!(span, "not allowed here"); + bail!(span, "page configuration is not allowed inside of containers"); } - self.interrupt_page(styles)?; - } else if map.interruption::().is_some() - || map.interruption::().is_some() + self.interrupt_page(outer)?; + } else if local.interruption::().is_some() + || local.interruption::().is_some() { self.interrupt_par()?; - } else if map.interruption::().is_some() - || map.interruption::().is_some() - || map.interruption::().is_some() + } else if local.interruption::().is_some() + || local.interruption::().is_some() + || local.interruption::().is_some() { self.interrupt_list()?; } @@ -387,7 +388,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> { } else { shared }; - let page = PageNode::new(FlowNode::new(flow.to_vec()).pack()).pack(); + let page = PageElem::new(FlowElem::new(flow.to_vec()).pack()).pack(); let stored = self.scratch.content.alloc(page); self.accept(stored, styles)?; } @@ -405,12 +406,12 @@ struct DocBuilder<'a> { impl<'a> DocBuilder<'a> { fn accept(&mut self, content: &Content, styles: StyleChain<'a>) -> bool { - if let Some(pagebreak) = content.to::() { + if let Some(pagebreak) = content.to::() { self.keep_next = !pagebreak.weak(styles); return true; } - if content.is::() { + if content.is::() { self.pages.push(content.clone(), styles); self.keep_next = false; return true; @@ -432,7 +433,7 @@ struct FlowBuilder<'a>(BehavedBuilder<'a>, bool); impl<'a> FlowBuilder<'a> { fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { - if content.is::() { + if content.is::() { self.1 = true; return true; } @@ -440,33 +441,33 @@ impl<'a> FlowBuilder<'a> { let last_was_parbreak = self.1; self.1 = false; - if content.is::() - || content.is::() - || content.is::() + if content.is::() + || content.is::() + || content.is::() { self.0.push(content.clone(), styles); return true; } - if content.can::() || content.is::() { - let is_tight_list = if let Some(node) = content.to::() { - node.tight(styles) - } else if let Some(node) = content.to::() { - node.tight(styles) - } else if let Some(node) = content.to::() { - node.tight(styles) + if content.can::() || content.is::() { + let is_tight_list = if let Some(elem) = content.to::() { + elem.tight(styles) + } else if let Some(elem) = content.to::() { + elem.tight(styles) + } else if let Some(elem) = content.to::() { + elem.tight(styles) } else { false }; if !last_was_parbreak && is_tight_list { - let leading = ParNode::leading_in(styles); - let spacing = VNode::list_attach(leading.into()); + let leading = ParElem::leading_in(styles); + let spacing = VElem::list_attach(leading.into()); self.0.push(spacing.pack(), styles); } - let above = BlockNode::above_in(styles); - let below = BlockNode::below_in(styles); + let above = BlockElem::above_in(styles); + let below = BlockElem::below_in(styles); self.0.push(above.clone().pack(), styles); self.0.push(content.clone(), styles); self.0.push(below.clone().pack(), styles); @@ -483,18 +484,18 @@ struct ParBuilder<'a>(BehavedBuilder<'a>); impl<'a> ParBuilder<'a> { fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { - if content.is::() { + if content.is::() { if !self.0.is_basically_empty() { self.0.push(content.clone(), styles); return true; } - } else if content.is::() - || content.is::() - || content.is::() - || content.is::() - || content.is::() - || content.to::().map_or(false, |node| !node.block(styles)) - || content.is::() + } else if content.is::() + || content.is::() + || content.is::() + || content.is::() + || content.is::() + || content.to::().map_or(false, |elem| !elem.block(styles)) + || content.is::() { self.0.push(content.clone(), styles); return true; @@ -505,7 +506,7 @@ impl<'a> ParBuilder<'a> { fn finish(self) -> (Content, StyleChain<'a>) { let (children, shared) = self.0.finish(); - (ParNode::new(children.to_vec()).pack(), shared) + (ParElem::new(children.to_vec()).pack(), shared) } } @@ -522,7 +523,7 @@ struct ListBuilder<'a> { impl<'a> ListBuilder<'a> { fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { if !self.items.is_empty() - && (content.is::() || content.is::()) + && (content.is::() || content.is::()) { self.staged.push((content, styles)); return true; @@ -533,12 +534,12 @@ impl<'a> ListBuilder<'a> { || content.is::()) && self .items - .items() + .elems() .next() - .map_or(true, |first| first.id() == content.id()) + .map_or(true, |first| first.func() == content.func()) { self.items.push(content.clone(), styles); - self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::()); + self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::()); return true; } @@ -549,39 +550,39 @@ impl<'a> ListBuilder<'a> { let (items, shared) = self.items.finish(); let item = items.items().next().unwrap(); let output = if item.is::() { - ListNode::new( + ListElem::new( items .iter() - .map(|(item, map)| { + .map(|(item, local)| { let item = item.to::().unwrap(); - item.clone().with_body(item.body().styled_with_map(map.clone())) + item.clone().with_body(item.body().styled_with_map(local.clone())) }) .collect::>(), ) .with_tight(self.tight) .pack() } else if item.is::() { - EnumNode::new( + EnumElem::new( items .iter() - .map(|(item, map)| { + .map(|(item, local)| { let item = item.to::().unwrap(); - item.clone().with_body(item.body().styled_with_map(map.clone())) + item.clone().with_body(item.body().styled_with_map(local.clone())) }) .collect::>(), ) .with_tight(self.tight) .pack() } else if item.is::() { - TermsNode::new( + TermsElem::new( items .iter() - .map(|(item, map)| { + .map(|(item, local)| { let item = item.to::().unwrap(); item.clone() - .with_term(item.term().styled_with_map(map.clone())) + .with_term(item.term().styled_with_map(local.clone())) .with_description( - item.description().styled_with_map(map.clone()), + item.description().styled_with_map(local.clone()), ) }) .collect::>(), diff --git a/library/src/layout/pad.rs b/library/src/layout/pad.rs index e81715602..441aa2115 100644 --- a/library/src/layout/pad.rs +++ b/library/src/layout/pad.rs @@ -17,8 +17,8 @@ use crate::prelude::*; /// /// Display: Padding /// Category: layout -#[node(Layout)] -pub struct PadNode { +#[element(Layout)] +pub struct PadElem { /// The padding at the left side. #[parse( let all = args.named("rest")?.or(args.find()?); @@ -59,7 +59,7 @@ pub struct PadNode { pub body: Content, } -impl Layout for PadNode { +impl Layout for PadElem { fn layout( &self, vt: &mut Vt, diff --git a/library/src/layout/page.rs b/library/src/layout/page.rs index 93ee08cee..a8a806ad6 100644 --- a/library/src/layout/page.rs +++ b/library/src/layout/page.rs @@ -1,7 +1,7 @@ use std::ptr; use std::str::FromStr; -use super::{AlignNode, ColumnsNode}; +use super::{AlignElem, ColumnsElem}; use crate::meta::{Counter, CounterKey, Numbering}; use crate::prelude::*; @@ -24,8 +24,8 @@ use crate::prelude::*; /// /// Display: Page /// Category: layout -#[node] -pub struct PageNode { +#[element] +pub struct PageElem { /// A standard paper size to set width and height. When this is not /// specified, Typst defaults to `{"a4"}` paper. #[external] @@ -270,7 +270,7 @@ pub struct PageNode { pub body: Content, } -impl PageNode { +impl PageElem { /// Layout the page run into a sequence of frames, one per page. pub fn layout(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult { // When one of the lengths is infinite the page fits its content along @@ -296,7 +296,7 @@ impl PageNode { // Realize columns. let columns = self.columns(styles); if columns.get() > 1 { - child = ColumnsNode::new(child).with_count(columns).pack(); + child = ColumnsElem::new(child).with_count(columns).pack(); } // Realize margins. @@ -356,7 +356,7 @@ impl PageNode { let pod = Regions::one(area, Axes::splat(true)); let sub = content .clone() - .styled(AlignNode::set_alignment(align)) + .styled(AlignElem::set_alignment(align)) .layout(vt, styles, pod)? .into_frame(); if ptr::eq(marginal, &header) || ptr::eq(marginal, &background) { @@ -387,8 +387,8 @@ impl PageNode { /// /// Display: Page Break /// Category: layout -#[node] -pub struct PagebreakNode { +#[element] +pub struct PagebreakElem { /// If `{true}`, the page break is skipped if the current page is already /// empty. #[default(false)] @@ -467,7 +467,7 @@ macro_rules! papers { fn from_str(name: &str) -> Result { match name.to_lowercase().as_str() { $($pat => Ok(Self::$var),)* - _ => Err("invalid paper name"), + _ => Err("unknown paper size"), } } } diff --git a/library/src/layout/par.rs b/library/src/layout/par.rs index cef0d11c9..db65b1259 100644 --- a/library/src/layout/par.rs +++ b/library/src/layout/par.rs @@ -3,17 +3,15 @@ use unicode_bidi::{BidiInfo, Level as BidiLevel}; use unicode_script::{Script, UnicodeScript}; use xi_unicode::LineBreakIterator; -use typst::model::StyledNode; - -use super::{BoxNode, HNode, Sizing, Spacing}; -use crate::layout::AlignNode; -use crate::math::EquationNode; +use super::{BoxElem, HElem, Sizing, Spacing}; +use crate::layout::AlignElem; +use crate::math::EquationElem; use crate::prelude::*; use crate::text::{ - shape, LinebreakNode, Quoter, Quotes, ShapedText, SmartQuoteNode, SpaceNode, TextNode, + shape, LinebreakElem, Quoter, Quotes, ShapedText, SmartQuoteElem, SpaceElem, TextElem, }; -/// Arrange text, spacing and inline-level nodes into a paragraph. +/// Arrange text, spacing and inline-level elements into a paragraph. /// /// Although this function is primarily used in set rules to affect paragraph /// properties, it can also be used to explicitly render its argument onto a @@ -38,8 +36,8 @@ use crate::text::{ /// /// Display: Paragraph /// Category: layout -#[node(Construct)] -pub struct ParNode { +#[element(Construct)] +pub struct ParElem { /// The spacing between lines. /// /// The default value is `{0.65em}`. @@ -110,22 +108,22 @@ pub struct ParNode { pub children: Vec, } -impl Construct for ParNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult { +impl Construct for ParElem { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { // The paragraph constructor is special: It doesn't create a paragraph - // node. Instead, it just ensures that the passed content lives in a + // element. Instead, it just ensures that the passed content lives in a // separate paragraph and styles it. let styles = Self::set(args)?; let body = args.expect::("body")?; - Ok(Content::sequence(vec![ - ParbreakNode::new().pack(), + Ok(Content::sequence([ + ParbreakElem::new().pack(), body.styled_with_map(styles), - ParbreakNode::new().pack(), + ParbreakElem::new().pack(), ])) } } -impl ParNode { +impl ParElem { /// Layout the paragraph into a collection of lines. pub fn layout( &self, @@ -137,7 +135,7 @@ impl ParNode { ) -> SourceResult { #[comemo::memoize] fn cached( - par: &ParNode, + par: &ParElem, world: Tracked, tracer: TrackedMut, provider: TrackedMut, @@ -179,26 +177,6 @@ impl ParNode { } } -/// A horizontal alignment. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct HorizontalAlign(pub GenAlign); - -cast_from_value! { - HorizontalAlign, - align: GenAlign => match align.axis() { - Axis::X => Self(align), - Axis::Y => Err("must be horizontal")?, - }, -} - -impl Resolve for HorizontalAlign { - type Output = Align; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.0.resolve(styles) - } -} - /// How to determine line breaks in a paragraph. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)] pub enum Linebreaks { @@ -232,10 +210,10 @@ pub enum Linebreaks { /// /// Display: Paragraph Break /// Category: layout -#[node(Unlabellable)] -pub struct ParbreakNode {} +#[element(Unlabellable)] +pub struct ParbreakElem {} -impl Unlabellable for ParbreakNode {} +impl Unlabellable for ParbreakElem {} /// Range of a substring of text. type Range = std::ops::Range; @@ -243,7 +221,7 @@ type Range = std::ops::Range; // The characters by which spacing, inline content and pins are replaced in the // paragraph's full text. const SPACING_REPLACE: char = ' '; // Space -const NODE_REPLACE: char = '\u{FFFC}'; // Object Replacement Character +const OBJ_REPLACE: char = '\u{FFFC}'; // Object Replacement Character /// A paragraph representation in which children are already layouted and text /// is already preshaped. @@ -254,7 +232,7 @@ const NODE_REPLACE: char = '\u{FFFC}'; // Object Replacement Character struct Preparation<'a> { /// Bidirectional text embedding levels for the paragraph. bidi: BidiInfo<'a>, - /// Text runs, spacing and layouted nodes. + /// Text runs, spacing and layouted elements. items: Vec>, /// The span mapper. spans: SpanMapper, @@ -325,9 +303,9 @@ enum Segment<'a> { /// Horizontal spacing between other segments. Spacing(Spacing), /// A mathematical equation. - Equation(&'a EquationNode), + Equation(&'a EquationElem), /// A box with arbitrary content. - Box(&'a BoxNode, bool), + Box(&'a BoxElem, bool), /// Metadata. Meta, } @@ -339,7 +317,7 @@ impl Segment<'_> { Self::Text(len) => len, Self::Spacing(_) => SPACING_REPLACE.len_utf8(), Self::Box(_, true) => SPACING_REPLACE.len_utf8(), - Self::Equation(_) | Self::Box(_, _) | Self::Meta => NODE_REPLACE.len_utf8(), + Self::Equation(_) | Self::Box(_, _) | Self::Meta => OBJ_REPLACE.len_utf8(), } } } @@ -352,7 +330,7 @@ enum Item<'a> { /// Absolute spacing between other items. Absolute(Abs), /// Fractional spacing between other items. - Fractional(Fr, Option<(&'a BoxNode, StyleChain<'a>)>), + Fractional(Fr, Option<(&'a BoxElem, StyleChain<'a>)>), /// Layouted inline-level content. Frame(Frame), } @@ -371,7 +349,7 @@ impl<'a> Item<'a> { match self { Self::Text(shaped) => shaped.text.len(), Self::Absolute(_) | Self::Fractional(_, _) => SPACING_REPLACE.len_utf8(), - Self::Frame(_) => NODE_REPLACE.len_utf8(), + Self::Frame(_) => OBJ_REPLACE.len_utf8(), } } @@ -520,7 +498,7 @@ fn collect<'a>( let mut iter = children.iter().peekable(); if consecutive { - let first_line_indent = ParNode::first_line_indent_in(*styles); + let first_line_indent = ParElem::first_line_indent_in(*styles); if !first_line_indent.is_zero() && children .iter() @@ -529,7 +507,7 @@ fn collect<'a>( behaved.behaviour() == Behaviour::Ignorant }) { None - } else if child.is::() || child.is::() { + } else if child.is::() || child.is::() { Some(true) } else { Some(false) @@ -542,7 +520,7 @@ fn collect<'a>( } } - let hang = ParNode::hanging_indent_in(*styles); + let hang = ParElem::hanging_indent_in(*styles); if !hang.is_zero() { full.push(SPACING_REPLACE); segments.push((Segment::Spacing((-hang).into()), *styles)); @@ -551,61 +529,61 @@ fn collect<'a>( while let Some(mut child) = iter.next() { let outer = styles; let mut styles = *styles; - if let Some(node) = child.to::() { - child = Box::leak(Box::new(node.body())); - styles = outer.chain(Box::leak(Box::new(node.styles()))); + if let Some((elem, local)) = child.to_styled() { + child = elem; + styles = outer.chain(local); } - let segment = if child.is::() { + let segment = if child.is::() { full.push(' '); Segment::Text(1) - } else if let Some(node) = child.to::() { + } else if let Some(elem) = child.to::() { let prev = full.len(); - if let Some(case) = TextNode::case_in(styles) { - full.push_str(&case.apply(&node.text())); + if let Some(case) = TextElem::case_in(styles) { + full.push_str(&case.apply(&elem.text())); } else { - full.push_str(&node.text()); + full.push_str(&elem.text()); } Segment::Text(full.len() - prev) - } else if let Some(node) = child.to::() { + } else if let Some(elem) = child.to::() { full.push(SPACING_REPLACE); - Segment::Spacing(node.amount()) - } else if let Some(node) = child.to::() { - let c = if node.justify(styles) { '\u{2028}' } else { '\n' }; + Segment::Spacing(elem.amount()) + } else if let Some(elem) = child.to::() { + let c = if elem.justify(styles) { '\u{2028}' } else { '\n' }; full.push(c); Segment::Text(c.len_utf8()) - } else if let Some(node) = child.to::() { + } else if let Some(elem) = child.to::() { let prev = full.len(); - if SmartQuoteNode::enabled_in(styles) { - let lang = TextNode::lang_in(styles); - let region = TextNode::region_in(styles); + if SmartQuoteElem::enabled_in(styles) { + let lang = TextElem::lang_in(styles); + let region = TextElem::region_in(styles); let quotes = Quotes::from_lang(lang, region); let peeked = iter.peek().and_then(|child| { - if let Some(node) = child.to::() { - node.text().chars().next() - } else if child.is::() { + if let Some(elem) = child.to::() { + elem.text().chars().next() + } else if child.is::() { Some('"') - } else if child.is::() || child.is::() { + } else if child.is::() || child.is::() { Some(SPACING_REPLACE) } else { - Some(NODE_REPLACE) + Some(OBJ_REPLACE) } }); - full.push_str(quoter.quote("es, node.double(styles), peeked)); + full.push_str(quoter.quote("es, elem.double(styles), peeked)); } else { - full.push(if node.double(styles) { '"' } else { '\'' }); + full.push(if elem.double(styles) { '"' } else { '\'' }); } Segment::Text(full.len() - prev) - } else if let Some(node) = child.to::() { - full.push(NODE_REPLACE); - Segment::Equation(node) - } else if let Some(node) = child.to::() { - let frac = node.width(styles).is_fractional(); - full.push(if frac { SPACING_REPLACE } else { NODE_REPLACE }); - Segment::Box(node, frac) - } else if child.is::() { - full.push(NODE_REPLACE); + } else if let Some(elem) = child.to::() { + full.push(OBJ_REPLACE); + Segment::Equation(elem) + } else if let Some(elem) = child.to::() { + let frac = elem.width(styles).is_fractional(); + full.push(if frac { SPACING_REPLACE } else { OBJ_REPLACE }); + Segment::Box(elem, frac) + } else if child.is::() { + full.push(OBJ_REPLACE); Segment::Meta } else { bail!(child.span(), "unexpected paragraph child"); @@ -645,7 +623,7 @@ fn prepare<'a>( ) -> SourceResult> { let bidi = BidiInfo::new( text, - match TextNode::dir_in(styles) { + match TextElem::dir_in(styles) { Dir::LTR => Some(BidiLevel::ltr()), Dir::RTL => Some(BidiLevel::rtl()), _ => None, @@ -674,16 +652,16 @@ fn prepare<'a>( Segment::Equation(equation) => { let pod = Regions::one(region, Axes::splat(false)); let mut frame = equation.layout(vt, styles, pod)?.into_frame(); - frame.translate(Point::with_y(TextNode::baseline_in(styles))); + frame.translate(Point::with_y(TextElem::baseline_in(styles))); items.push(Item::Frame(frame)); } - Segment::Box(node, _) => { - if let Sizing::Fr(v) = node.width(styles) { - items.push(Item::Fractional(v, Some((node, styles)))); + Segment::Box(elem, _) => { + if let Sizing::Fr(v) = elem.width(styles) { + items.push(Item::Fractional(v, Some((elem, styles)))); } else { let pod = Regions::one(region, Axes::splat(false)); - let mut frame = node.layout(vt, styles, pod)?.into_frame(); - frame.translate(Point::with_y(TextNode::baseline_in(styles))); + let mut frame = elem.layout(vt, styles, pod)?.into_frame(); + frame.translate(Point::with_y(TextElem::baseline_in(styles))); items.push(Item::Frame(frame)); } } @@ -702,11 +680,11 @@ fn prepare<'a>( items, spans, styles, - hyphenate: shared_get(styles, children, TextNode::hyphenate_in), - lang: shared_get(styles, children, TextNode::lang_in), - align: AlignNode::alignment_in(styles).x.resolve(styles), - justify: ParNode::justify_in(styles), - hang: ParNode::hanging_indent_in(styles), + hyphenate: shared_get(styles, children, TextElem::hyphenate_in), + lang: shared_get(styles, children, TextElem::lang_in), + align: AlignElem::alignment_in(styles).x.resolve(styles), + justify: ParElem::justify_in(styles), + hang: ParElem::hanging_indent_in(styles), }) } @@ -775,15 +753,15 @@ fn shared_get<'a, T: PartialEq>( let value = getter(styles); children .iter() - .filter_map(|child| child.to::()) - .all(|node| getter(styles.chain(&node.styles())) == value) + .filter_map(|child| child.to_styled()) + .all(|(_, local)| getter(styles.chain(&local)) == value) .then(|| value) } /// Find suitable linebreaks. fn linebreak<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec> { - let linebreaks = ParNode::linebreaks_in(p.styles).unwrap_or_else(|| { - if ParNode::justify_in(p.styles) { + let linebreaks = ParElem::linebreaks_in(p.styles).unwrap_or_else(|| { + if ParElem::justify_in(p.styles) { Linebreaks::Optimized } else { Linebreaks::Simple @@ -881,7 +859,7 @@ fn linebreak_optimized<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec { .hyphenate .or_else(|| { let shaped = self.p.find(offset)?.text()?; - Some(TextNode::hyphenate_in(shaped.styles)) + Some(TextElem::hyphenate_in(shaped.styles)) }) .unwrap_or(false) } @@ -1055,7 +1033,7 @@ impl Breakpoints<'_> { fn lang(&self, offset: usize) -> Option { let lang = self.p.lang.or_else(|| { let shaped = self.p.find(offset)?.text()?; - Some(TextNode::lang_in(shaped.styles)) + Some(TextElem::lang_in(shaped.styles)) })?; let bytes = lang.as_str().as_bytes().try_into().ok()?; @@ -1196,7 +1174,7 @@ fn finalize( .collect::>()?; // Prevent orphans. - let leading = ParNode::leading_in(p.styles); + let leading = ParElem::leading_in(p.styles); if frames.len() >= 2 && !frames[1].is_empty() { let second = frames.remove(1); let first = &mut frames[0]; @@ -1243,7 +1221,7 @@ fn commit( if let Some(Item::Text(text)) = reordered.first() { if let Some(glyph) = text.glyphs.first() { if !text.dir.is_positive() - && TextNode::overhang_in(text.styles) + && TextElem::overhang_in(text.styles) && (reordered.len() > 1 || text.glyphs.len() > 1) { let amount = overhang(glyph.c) * glyph.x_advance.at(text.size); @@ -1257,7 +1235,7 @@ fn commit( if let Some(Item::Text(text)) = reordered.last() { if let Some(glyph) = text.glyphs.last() { if text.dir.is_positive() - && TextNode::overhang_in(text.styles) + && TextElem::overhang_in(text.styles) && (reordered.len() > 1 || text.glyphs.len() > 1) { let amount = overhang(glyph.c) * glyph.x_advance.at(text.size); @@ -1295,13 +1273,13 @@ fn commit( Item::Absolute(v) => { offset += *v; } - Item::Fractional(v, node) => { + Item::Fractional(v, elem) => { let amount = v.share(fr, remaining); - if let Some((node, styles)) = node { + if let Some((elem, styles)) = elem { let region = Size::new(amount, full); let pod = Regions::one(region, Axes::new(true, false)); - let mut frame = node.layout(vt, *styles, pod)?.into_frame(); - frame.translate(Point::with_y(TextNode::baseline_in(*styles))); + let mut frame = elem.layout(vt, *styles, pod)?.into_frame(); + frame.translate(Point::with_y(TextElem::baseline_in(*styles))); push(&mut offset, frame); } else { offset += amount; diff --git a/library/src/layout/place.rs b/library/src/layout/place.rs index bfabd0f3d..057278dff 100644 --- a/library/src/layout/place.rs +++ b/library/src/layout/place.rs @@ -23,8 +23,8 @@ use crate::prelude::*; /// /// Display: Place /// Category: layout -#[node(Layout, Behave)] -pub struct PlaceNode { +#[element(Layout, Behave)] +pub struct PlaceElem { /// Relative to which position in the parent container to place the content. /// /// When an axis of the page is `{auto}` sized, all alignments relative to that @@ -53,7 +53,7 @@ pub struct PlaceNode { pub body: Content, } -impl Layout for PlaceNode { +impl Layout for PlaceElem { fn layout( &self, vt: &mut Vt, @@ -86,16 +86,16 @@ impl Layout for PlaceNode { } } -impl PlaceNode { - /// Whether this node wants to be placed relative to its its parent's base - /// origin. Instead of relative to the parent's current flow/cursor +impl PlaceElem { + /// Whether this element wants to be placed relative to its its parent's + /// base origin. Instead of relative to the parent's current flow/cursor /// position. pub fn out_of_flow(&self, styles: StyleChain) -> bool { self.alignment(styles).y.is_some() } } -impl Behave for PlaceNode { +impl Behave for PlaceElem { fn behaviour(&self) -> Behaviour { Behaviour::Ignorant } diff --git a/library/src/layout/regions.rs b/library/src/layout/regions.rs index 94c817043..5a4db1782 100644 --- a/library/src/layout/regions.rs +++ b/library/src/layout/regions.rs @@ -14,8 +14,8 @@ pub struct Regions<'a> { /// The height of the final region that is repeated once the backlog is /// drained. The width is the same for all regions. pub last: Option, - /// Whether nodes should expand to fill the regions instead of shrinking to - /// fit the content. + /// Whether elements should expand to fill the regions instead of shrinking + /// to fit the content. pub expand: Axes, } diff --git a/library/src/layout/repeat.rs b/library/src/layout/repeat.rs index c8f63ac33..a44bd0759 100644 --- a/library/src/layout/repeat.rs +++ b/library/src/layout/repeat.rs @@ -1,6 +1,6 @@ use crate::prelude::*; -use super::AlignNode; +use super::AlignElem; /// Repeats content to the available space. /// @@ -23,14 +23,14 @@ use super::AlignNode; /// /// Display: Repeat /// Category: layout -#[node(Layout)] -pub struct RepeatNode { +#[element(Layout)] +pub struct RepeatElem { /// The content to repeat. #[required] pub body: Content, } -impl Layout for RepeatNode { +impl Layout for RepeatElem { fn layout( &self, vt: &mut Vt, @@ -39,7 +39,7 @@ impl Layout for RepeatNode { ) -> SourceResult { let pod = Regions::one(regions.size, Axes::new(false, false)); let piece = self.body().layout(vt, styles, pod)?.into_frame(); - let align = AlignNode::alignment_in(styles).x.resolve(styles); + let align = AlignElem::alignment_in(styles).x.resolve(styles); let fill = regions.size.x; let width = piece.width(); diff --git a/library/src/layout/spacing.rs b/library/src/layout/spacing.rs index e67fec03d..9253c4978 100644 --- a/library/src/layout/spacing.rs +++ b/library/src/layout/spacing.rs @@ -21,8 +21,8 @@ use crate::prelude::*; /// /// Display: Spacing (H) /// Category: layout -#[node(Behave)] -pub struct HNode { +#[element(Behave)] +pub struct HElem { /// How much spacing to insert. #[required] pub amount: Spacing, @@ -45,7 +45,7 @@ pub struct HNode { pub weak: bool, } -impl Behave for HNode { +impl Behave for HElem { fn behaviour(&self) -> Behaviour { if self.amount().is_fractional() { Behaviour::Destructive @@ -85,8 +85,8 @@ impl Behave for HNode { /// /// Display: Spacing (V) /// Category: layout -#[node(Behave)] -pub struct VNode { +#[element(Behave)] +pub struct VElem { /// How much spacing to insert. #[required] pub amount: Spacing, @@ -107,13 +107,13 @@ pub struct VNode { #[external] pub weak: bool, - /// The node's weakness level, see also [`Behaviour`]. + /// The elements's weakness level, see also [`Behaviour`]. #[internal] #[parse(args.named("weak")?.map(|v: bool| v as usize))] pub weakness: usize, } -impl VNode { +impl VElem { /// Normal strong spacing. pub fn strong(amount: Spacing) -> Self { Self::new(amount).with_weakness(0) @@ -129,18 +129,18 @@ impl VNode { Self::new(amount).with_weakness(2) } - /// Weak spacing with BlockNode::ABOVE/BELOW weakness. + /// Weak spacing with BlockElem::ABOVE/BELOW weakness. pub fn block_around(amount: Spacing) -> Self { Self::new(amount).with_weakness(3) } - /// Weak spacing with BlockNode::SPACING weakness. + /// Weak spacing with BlockElem::SPACING weakness. pub fn block_spacing(amount: Spacing) -> Self { Self::new(amount).with_weakness(4) } } -impl Behave for VNode { +impl Behave for VElem { fn behaviour(&self) -> Behaviour { if self.amount().is_fractional() { Behaviour::Destructive @@ -158,8 +158,8 @@ impl Behave for VNode { } cast_from_value! { - VNode, - v: Content => v.to::().cloned().ok_or("expected vnode")?, + VElem, + v: Content => v.to::().cloned().ok_or("expected `v` element")?, } /// Kinds of spacing. diff --git a/library/src/layout/stack.rs b/library/src/layout/stack.rs index 1dd81a606..77cd3f8f5 100644 --- a/library/src/layout/stack.rs +++ b/library/src/layout/stack.rs @@ -1,6 +1,4 @@ -use typst::model::StyledNode; - -use super::{AlignNode, Spacing}; +use super::{AlignElem, Spacing}; use crate::prelude::*; /// Arrange content and spacing horizontally or vertically. @@ -20,8 +18,8 @@ use crate::prelude::*; /// /// Display: Stack /// Category: layout -#[node(Layout)] -pub struct StackNode { +#[element(Layout)] +pub struct StackElem { /// The direction along which the items are stacked. Possible values are: /// /// - `{ltr}`: Left to right. @@ -39,7 +37,7 @@ pub struct StackNode { pub children: Vec, } -impl Layout for StackNode { +impl Layout for StackElem { fn layout( &self, vt: &mut Vt, @@ -73,7 +71,7 @@ impl Layout for StackNode { } } -/// A child of a stack node. +/// A child of a stack element. #[derive(Hash)] pub enum StackChild { /// Spacing between other children. @@ -196,14 +194,13 @@ impl<'a> StackLayouter<'a> { self.finish_region(); } - // Block-axis alignment of the `AlignNode` is respected - // by the stack node. - let aligns = if let Some(align) = block.to::() { + // Block-axis alignment of the `AlignElement` is respected by stacks. + let aligns = if let Some(align) = block.to::() { align.alignment(styles) - } else if let Some(styled) = block.to::() { - AlignNode::alignment_in(styles.chain(&styled.styles())) + } else if let Some((_, local)) = block.to_styled() { + AlignElem::alignment_in(styles.chain(&local)) } else { - AlignNode::alignment_in(styles) + AlignElem::alignment_in(styles) } .resolve(styles); diff --git a/library/src/layout/table.rs b/library/src/layout/table.rs index d4b6e7d7f..809c7ea7d 100644 --- a/library/src/layout/table.rs +++ b/library/src/layout/table.rs @@ -1,4 +1,4 @@ -use crate::layout::{AlignNode, GridLayouter, TrackSizings}; +use crate::layout::{AlignElem, GridLayouter, TrackSizings}; use crate::meta::LocalName; use crate::prelude::*; @@ -32,8 +32,8 @@ use crate::prelude::*; /// /// Display: Table /// Category: layout -#[node(Layout, LocalName)] -pub struct TableNode { +#[element(Layout, LocalName)] +pub struct TableElem { /// Defines the column sizes. See the [grid documentation]($func/grid) for /// more information on track sizing. pub columns: TrackSizings, @@ -109,7 +109,7 @@ pub struct TableNode { pub children: Vec, } -impl Layout for TableNode { +impl Layout for TableElem { fn layout( &self, vt: &mut Vt, @@ -132,7 +132,7 @@ impl Layout for TableNode { let x = i % cols; let y = i / cols; if let Smart::Custom(alignment) = align.resolve(vt, x, y)? { - child = child.styled(AlignNode::set_alignment(alignment)); + child = child.styled(AlignElem::set_alignment(alignment)); } Ok(child) @@ -168,7 +168,7 @@ impl Layout for TableNode { let hline = Geometry::Line(target).stroked(stroke); frame.prepend( Point::new(-half, offset), - Element::Shape(hline, self.span()), + FrameItem::Shape(hline, self.span()), ); } @@ -178,7 +178,7 @@ impl Layout for TableNode { let vline = Geometry::Line(target).stroked(stroke); frame.prepend( Point::new(offset, -half), - Element::Shape(vline, self.span()), + FrameItem::Shape(vline, self.span()), ); } } @@ -192,7 +192,7 @@ impl Layout for TableNode { let pos = Point::new(dx, dy); let size = Size::new(col, row.height); let rect = Geometry::Rect(size).filled(fill); - frame.prepend(pos, Element::Shape(rect, self.span())); + frame.prepend(pos, FrameItem::Shape(rect, self.span())); } dy += row.height; } @@ -271,7 +271,7 @@ impl> From> for Value { } } -impl LocalName for TableNode { +impl LocalName for TableElem { fn local_name(&self, lang: Lang) -> &'static str { match lang { Lang::GERMAN => "Tabelle", diff --git a/library/src/layout/terms.rs b/library/src/layout/terms.rs index 853dd32dc..1200076f8 100644 --- a/library/src/layout/terms.rs +++ b/library/src/layout/terms.rs @@ -1,7 +1,7 @@ -use super::{HNode, VNode}; -use crate::layout::{BlockNode, ParNode, Spacing}; +use super::{HElem, VElem}; +use crate::layout::{BlockElem, ParElem, Spacing}; use crate::prelude::*; -use crate::text::{SpaceNode, TextNode}; +use crate::text::{SpaceElem, TextElem}; /// A list of terms and their descriptions. /// @@ -22,8 +22,8 @@ use crate::text::{SpaceNode, TextNode}; /// /// Display: Term List /// Category: layout -#[node(Layout)] -pub struct TermsNode { +#[element(Layout)] +pub struct TermsElem { /// If this is `{false}`, the items are spaced apart with [term list /// spacing]($func/terms.spacing). If it is `{true}`, they use normal /// [leading]($func/par.leading) instead. This makes the term list more @@ -76,7 +76,7 @@ pub struct TermsNode { pub children: Vec, } -impl Layout for TermsNode { +impl Layout for TermsElem { fn layout( &self, vt: &mut Vt, @@ -86,27 +86,27 @@ impl Layout for TermsNode { let indent = self.indent(styles); let hanging_indent = self.hanging_indent(styles); let gutter = if self.tight(styles) { - ParNode::leading_in(styles).into() + ParElem::leading_in(styles).into() } else { self.spacing(styles) - .unwrap_or_else(|| BlockNode::below_in(styles).amount()) + .unwrap_or_else(|| BlockElem::below_in(styles).amount()) }; let mut seq = vec![]; for (i, child) in self.children().into_iter().enumerate() { if i > 0 { - seq.push(VNode::new(gutter).with_weakness(1).pack()); + seq.push(VElem::new(gutter).with_weakness(1).pack()); } if indent.is_zero() { - seq.push(HNode::new(indent.into()).pack()); + seq.push(HElem::new(indent.into()).pack()); } - seq.push((child.term() + TextNode::packed(':')).strong()); - seq.push(SpaceNode::new().pack()); + seq.push((child.term() + TextElem::packed(':')).strong()); + seq.push(SpaceElem::new().pack()); seq.push(child.description()); } Content::sequence(seq) - .styled(ParNode::set_hanging_indent(hanging_indent + indent)) + .styled(ParElem::set_hanging_indent(hanging_indent + indent)) .layout(vt, styles, regions) } } @@ -115,7 +115,7 @@ impl Layout for TermsNode { /// /// Display: Term List Item /// Category: layout -#[node] +#[element] pub struct TermItem { /// The term described by the list item. #[required] diff --git a/library/src/layout/transform.rs b/library/src/layout/transform.rs index 2afe82010..2045e9ed7 100644 --- a/library/src/layout/transform.rs +++ b/library/src/layout/transform.rs @@ -23,8 +23,8 @@ use crate::prelude::*; /// /// Display: Move /// Category: layout -#[node(Layout)] -pub struct MoveNode { +#[element(Layout)] +pub struct MoveElem { /// The horizontal displacement of the content. pub dx: Rel, @@ -36,7 +36,7 @@ pub struct MoveNode { pub body: Content, } -impl Layout for MoveNode { +impl Layout for MoveElem { fn layout( &self, vt: &mut Vt, @@ -69,8 +69,8 @@ impl Layout for MoveNode { /// /// Display: Rotate /// Category: layout -#[node(Layout)] -pub struct RotateNode { +#[element(Layout)] +pub struct RotateElem { /// The amount of rotation. /// /// ```example @@ -104,7 +104,7 @@ pub struct RotateNode { pub body: Content, } -impl Layout for RotateNode { +impl Layout for RotateElem { fn layout( &self, vt: &mut Vt, @@ -137,8 +137,8 @@ impl Layout for RotateNode { /// /// Display: Scale /// Category: layout -#[node(Layout)] -pub struct ScaleNode { +#[element(Layout)] +pub struct ScaleElem { /// The horizontal scaling factor. /// /// The body will be mirrored horizontally if the parameter is negative. @@ -172,7 +172,7 @@ pub struct ScaleNode { pub body: Content, } -impl Layout for ScaleNode { +impl Layout for ScaleElem { fn layout( &self, vt: &mut Vt, diff --git a/library/src/lib.rs b/library/src/lib.rs index 2f951b922..1a9987006 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -13,7 +13,7 @@ pub mod visualize; use typst::diag::At; use typst::eval::{LangItems, Library, Module, Scope}; use typst::geom::{Align, Color, Dir, GenAlign, Smart}; -use typst::model::{Node, NodeId, StyleMap}; +use typst::model::{Element, Styles}; use self::layout::LayoutRoot; @@ -30,69 +30,69 @@ fn global(math: Module, calc: Module) -> Module { let mut global = Scope::deduplicating(); // Text. - global.define("text", text::TextNode::id()); - global.define("linebreak", text::LinebreakNode::id()); - global.define("smartquote", text::SmartQuoteNode::id()); - global.define("strong", text::StrongNode::id()); - global.define("emph", text::EmphNode::id()); + global.define("text", text::TextElem::func()); + global.define("linebreak", text::LinebreakElem::func()); + global.define("smartquote", text::SmartQuoteElem::func()); + global.define("strong", text::StrongElem::func()); + global.define("emph", text::EmphElem::func()); global.define("lower", text::lower); global.define("upper", text::upper); global.define("smallcaps", text::smallcaps); - global.define("sub", text::SubNode::id()); - global.define("super", text::SuperNode::id()); - global.define("underline", text::UnderlineNode::id()); - global.define("strike", text::StrikeNode::id()); - global.define("overline", text::OverlineNode::id()); - global.define("raw", text::RawNode::id()); + global.define("sub", text::SubElem::func()); + global.define("super", text::SuperElem::func()); + global.define("underline", text::UnderlineElem::func()); + global.define("strike", text::StrikeElem::func()); + global.define("overline", text::OverlineElem::func()); + global.define("raw", text::RawElem::func()); global.define("lorem", text::lorem); // Math. global.define("math", math); // Layout. - global.define("page", layout::PageNode::id()); - global.define("pagebreak", layout::PagebreakNode::id()); - global.define("v", layout::VNode::id()); - global.define("par", layout::ParNode::id()); - global.define("parbreak", layout::ParbreakNode::id()); - global.define("h", layout::HNode::id()); - global.define("box", layout::BoxNode::id()); - global.define("block", layout::BlockNode::id()); - global.define("list", layout::ListNode::id()); - global.define("enum", layout::EnumNode::id()); - global.define("terms", layout::TermsNode::id()); - global.define("table", layout::TableNode::id()); - global.define("stack", layout::StackNode::id()); - global.define("grid", layout::GridNode::id()); - global.define("columns", layout::ColumnsNode::id()); - global.define("colbreak", layout::ColbreakNode::id()); - global.define("place", layout::PlaceNode::id()); - global.define("align", layout::AlignNode::id()); - global.define("pad", layout::PadNode::id()); - global.define("repeat", layout::RepeatNode::id()); - global.define("move", layout::MoveNode::id()); - global.define("scale", layout::ScaleNode::id()); - global.define("rotate", layout::RotateNode::id()); - global.define("hide", layout::HideNode::id()); + global.define("page", layout::PageElem::func()); + global.define("pagebreak", layout::PagebreakElem::func()); + global.define("v", layout::VElem::func()); + global.define("par", layout::ParElem::func()); + global.define("parbreak", layout::ParbreakElem::func()); + global.define("h", layout::HElem::func()); + global.define("box", layout::BoxElem::func()); + global.define("block", layout::BlockElem::func()); + global.define("list", layout::ListElem::func()); + global.define("enum", layout::EnumElem::func()); + global.define("terms", layout::TermsElem::func()); + global.define("table", layout::TableElem::func()); + global.define("stack", layout::StackElem::func()); + global.define("grid", layout::GridElem::func()); + global.define("columns", layout::ColumnsElem::func()); + global.define("colbreak", layout::ColbreakElem::func()); + global.define("place", layout::PlaceElem::func()); + global.define("align", layout::AlignElem::func()); + global.define("pad", layout::PadElem::func()); + global.define("repeat", layout::RepeatElem::func()); + global.define("move", layout::MoveElem::func()); + global.define("scale", layout::ScaleElem::func()); + global.define("rotate", layout::RotateElem::func()); + global.define("hide", layout::HideElem::func()); global.define("measure", layout::measure); // Visualize. - global.define("image", visualize::ImageNode::id()); - global.define("line", visualize::LineNode::id()); - global.define("rect", visualize::RectNode::id()); - global.define("square", visualize::SquareNode::id()); - global.define("ellipse", visualize::EllipseNode::id()); - global.define("circle", visualize::CircleNode::id()); + global.define("image", visualize::ImageElem::func()); + global.define("line", visualize::LineElem::func()); + global.define("rect", visualize::RectElem::func()); + global.define("square", visualize::SquareElem::func()); + global.define("ellipse", visualize::EllipseElem::func()); + global.define("circle", visualize::CircleElem::func()); // Meta. - global.define("document", meta::DocumentNode::id()); - global.define("ref", meta::RefNode::id()); - global.define("link", meta::LinkNode::id()); - global.define("outline", meta::OutlineNode::id()); - global.define("heading", meta::HeadingNode::id()); - global.define("figure", meta::FigureNode::id()); - global.define("cite", meta::CiteNode::id()); - global.define("bibliography", meta::BibliographyNode::id()); + global.define("document", meta::DocumentElem::func()); + global.define("ref", meta::RefElem::func()); + global.define("link", meta::LinkElem::func()); + global.define("outline", meta::OutlineElem::func()); + global.define("heading", meta::HeadingElem::func()); + global.define("figure", meta::FigureElem::func()); + global.define("cite", meta::CiteElem::func()); + global.define("bibliography", meta::BibliographyElem::func()); global.define("locate", meta::locate); global.define("style", meta::style); global.define("counter", meta::counter); @@ -166,71 +166,71 @@ fn global(math: Module, calc: Module) -> Module { } /// Construct the standard style map. -fn styles() -> StyleMap { - StyleMap::new() +fn styles() -> Styles { + Styles::new() } /// Construct the standard lang item mapping. fn items() -> LangItems { LangItems { layout: |world, content, styles| content.layout_root(world, styles), - em: text::TextNode::size_in, - dir: text::TextNode::dir_in, - space: || text::SpaceNode::new().pack(), - linebreak: || text::LinebreakNode::new().pack(), - text: |text| text::TextNode::new(text).pack(), - text_id: NodeId::of::(), - text_str: |content| Some(content.to::()?.text()), - smart_quote: |double| text::SmartQuoteNode::new().with_double(double).pack(), - parbreak: || layout::ParbreakNode::new().pack(), - strong: |body| text::StrongNode::new(body).pack(), - emph: |body| text::EmphNode::new(body).pack(), + em: text::TextElem::size_in, + dir: text::TextElem::dir_in, + space: || text::SpaceElem::new().pack(), + linebreak: || text::LinebreakElem::new().pack(), + text: |text| text::TextElem::new(text).pack(), + text_func: text::TextElem::func(), + text_str: |content| Some(content.to::()?.text()), + smart_quote: |double| text::SmartQuoteElem::new().with_double(double).pack(), + parbreak: || layout::ParbreakElem::new().pack(), + strong: |body| text::StrongElem::new(body).pack(), + emph: |body| text::EmphElem::new(body).pack(), raw: |text, lang, block| { - let mut node = text::RawNode::new(text).with_block(block); + let mut elem = text::RawElem::new(text).with_block(block); if let Some(lang) = lang { - node.push_lang(Some(lang)); + elem.push_lang(Some(lang)); } - node.pack() + elem.pack() }, - raw_languages: text::RawNode::languages, - link: |url| meta::LinkNode::from_url(url).pack(), + raw_languages: text::RawElem::languages, + link: |url| meta::LinkElem::from_url(url).pack(), reference: |target, supplement| { - let mut node = meta::RefNode::new(target); + let mut elem = meta::RefElem::new(target); if let Some(supplement) = supplement { - node.push_supplement(Smart::Custom(Some(meta::Supplement::Content( + elem.push_supplement(Smart::Custom(Some(meta::Supplement::Content( supplement, )))); } - node.pack() + elem.pack() }, - bibliography_keys: meta::BibliographyNode::keys, - heading: |level, title| meta::HeadingNode::new(title).with_level(level).pack(), + bibliography_keys: meta::BibliographyElem::keys, + heading: |level, title| meta::HeadingElem::new(title).with_level(level).pack(), list_item: |body| layout::ListItem::new(body).pack(), enum_item: |number, body| { - let mut node = layout::EnumItem::new(body); + let mut elem = layout::EnumItem::new(body); if let Some(number) = number { - node.push_number(Some(number)); + elem.push_number(Some(number)); } - node.pack() + elem.pack() }, term_item: |term, description| layout::TermItem::new(term, description).pack(), - equation: |body, block| math::EquationNode::new(body).with_block(block).pack(), - math_align_point: || math::AlignPointNode::new().pack(), - math_delimited: |open, body, close| math::LrNode::new(open + body + close).pack(), + equation: |body, block| math::EquationElem::new(body).with_block(block).pack(), + math_align_point: || math::AlignPointElem::new().pack(), + math_delimited: |open, body, close| math::LrElem::new(open + body + close).pack(), math_attach: |base, bottom, top| { - let mut node = math::AttachNode::new(base); + let mut elem = math::AttachElem::new(base); if let Some(bottom) = bottom { - node.push_bottom(Some(bottom)); + elem.push_bottom(Some(bottom)); } if let Some(top) = top { - node.push_top(Some(top)); + elem.push_top(Some(top)); } - node.pack() + elem.pack() }, math_accent: |base, accent| { - math::AccentNode::new(base, math::Accent::new(accent)).pack() + math::AccentElem::new(base, math::Accent::new(accent)).pack() }, - math_frac: |num, denom| math::FracNode::new(num, denom).pack(), + math_frac: |num, denom| math::FracElem::new(num, denom).pack(), library_method: |vm, dynamic, method, args, span| { if let Some(counter) = dynamic.downcast::().cloned() { counter.call_method(vm, method, args, span) diff --git a/library/src/math/accent.rs b/library/src/math/accent.rs index 9ef762793..471507c50 100644 --- a/library/src/math/accent.rs +++ b/library/src/math/accent.rs @@ -1,5 +1,3 @@ -use typst::eval::combining_accent; - use super::*; /// How much the accent can be shorter than the base. @@ -16,8 +14,8 @@ const ACCENT_SHORT_FALL: Em = Em::new(0.5); /// /// Display: Accent /// Category: math -#[node(LayoutMath)] -pub struct AccentNode { +#[element(LayoutMath)] +pub struct AccentElem { /// The base to which the accent is applied. /// May consist of multiple letters. /// @@ -50,7 +48,7 @@ pub struct AccentNode { pub accent: Accent, } -impl LayoutMath for AccentNode { +impl LayoutMath for AccentElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { ctx.style(ctx.style.with_cramped(true)); let base = ctx.layout_fragment(&self.base())?; @@ -116,15 +114,15 @@ pub struct Accent(char); impl Accent { /// Normalize a character into an accent. pub fn new(c: char) -> Self { - Self(combining_accent(c).unwrap_or(c)) + Self(Symbol::combining_accent(c).unwrap_or(c)) } } cast_from_value! { Accent, v: char => Self::new(v), - v: Content => match v.to::() { - Some(node) => Value::Str(node.text().into()).cast()?, + v: Content => match v.to::() { + Some(elem) => Value::Str(elem.text().into()).cast()?, None => Err("expected text")?, }, } diff --git a/library/src/math/align.rs b/library/src/math/align.rs index 6cf13a0ff..d34379e22 100644 --- a/library/src/math/align.rs +++ b/library/src/math/align.rs @@ -4,10 +4,10 @@ use super::*; /// /// Display: Alignment Point /// Category: math -#[node(LayoutMath)] -pub struct AlignPointNode {} +#[element(LayoutMath)] +pub struct AlignPointElem {} -impl LayoutMath for AlignPointNode { +impl LayoutMath for AlignPointElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { ctx.push(MathFragment::Align); Ok(()) diff --git a/library/src/math/attach.rs b/library/src/math/attach.rs index 7d8749f2a..1b315f771 100644 --- a/library/src/math/attach.rs +++ b/library/src/math/attach.rs @@ -13,8 +13,8 @@ use super::*; /// /// Display: Attachment /// Category: math -#[node(LayoutMath)] -pub struct AttachNode { +#[element(LayoutMath)] +pub struct AttachElem { /// The base to which things are attached. #[required] pub base: Content, @@ -26,25 +26,25 @@ pub struct AttachNode { pub bottom: Option, } -impl LayoutMath for AttachNode { +impl LayoutMath for AttachElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { let base = self.base(); - let display_limits = base.is::(); - let display_scripts = base.is::(); + let display_limits = base.is::(); + let display_scripts = base.is::(); let base = ctx.layout_fragment(&base)?; ctx.style(ctx.style.for_subscript()); let top = self .top(ctx.styles()) - .map(|node| ctx.layout_fragment(&node)) + .map(|elem| ctx.layout_fragment(&elem)) .transpose()?; ctx.unstyle(); ctx.style(ctx.style.for_superscript()); let bottom = self .bottom(ctx.styles()) - .map(|node| ctx.layout_fragment(&node)) + .map(|elem| ctx.layout_fragment(&elem)) .transpose()?; ctx.unstyle(); @@ -75,14 +75,14 @@ impl LayoutMath for AttachNode { /// /// Display: Scripts /// Category: math -#[node(LayoutMath)] -pub struct ScriptsNode { +#[element(LayoutMath)] +pub struct ScriptsElem { /// The base to attach the scripts to. #[required] pub body: Content, } -impl LayoutMath for ScriptsNode { +impl LayoutMath for ScriptsElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { self.body().layout_math(ctx) } @@ -97,14 +97,14 @@ impl LayoutMath for ScriptsNode { /// /// Display: Limits /// Category: math -#[node(LayoutMath)] -pub struct LimitsNode { +#[element(LayoutMath)] +pub struct LimitsElem { /// The base to attach the limits to. #[required] pub body: Content, } -impl LayoutMath for LimitsNode { +impl LayoutMath for LimitsElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { self.body().layout_math(ctx) } diff --git a/library/src/math/ctx.rs b/library/src/math/ctx.rs index bd44546d1..aed826b53 100644 --- a/library/src/math/ctx.rs +++ b/library/src/math/ctx.rs @@ -32,7 +32,7 @@ pub struct MathContext<'a, 'b, 'v> { pub constants: ttf_parser::math::Constants<'a>, pub space_width: Em, pub fragments: Vec, - pub map: StyleMap, + pub local: Styles, pub style: MathStyle, pub size: Abs, outer: StyleChain<'a>, @@ -49,7 +49,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { ) -> Self { let table = font.ttf().tables().math.unwrap(); let constants = table.constants.unwrap(); - let size = TextNode::size_in(styles); + let size = TextElem::size_in(styles); let ttf = font.ttf(); let space_width = ttf .glyph_index(' ') @@ -67,7 +67,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { constants, space_width, fragments: vec![], - map: StyleMap::new(), + local: Styles::new(), style: MathStyle { variant: MathVariant::Serif, size: if block { MathSize::Display } else { MathSize::Text }, @@ -94,39 +94,39 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { pub fn layout_fragment( &mut self, - node: &dyn LayoutMath, + elem: &dyn LayoutMath, ) -> SourceResult { - let row = self.layout_fragments(node)?; + let row = self.layout_fragments(elem)?; Ok(MathRow::new(row).to_fragment(self)) } pub fn layout_fragments( &mut self, - node: &dyn LayoutMath, + elem: &dyn LayoutMath, ) -> SourceResult> { let prev = std::mem::take(&mut self.fragments); - node.layout_math(self)?; + elem.layout_math(self)?; Ok(std::mem::replace(&mut self.fragments, prev)) } - pub fn layout_row(&mut self, node: &dyn LayoutMath) -> SourceResult { - let fragments = self.layout_fragments(node)?; + pub fn layout_row(&mut self, elem: &dyn LayoutMath) -> SourceResult { + let fragments = self.layout_fragments(elem)?; Ok(MathRow::new(fragments)) } - pub fn layout_frame(&mut self, node: &dyn LayoutMath) -> SourceResult { - Ok(self.layout_fragment(node)?.to_frame()) + pub fn layout_frame(&mut self, elem: &dyn LayoutMath) -> SourceResult { + Ok(self.layout_fragment(elem)?.to_frame()) } pub fn layout_content(&mut self, content: &Content) -> SourceResult { Ok(content - .layout(&mut self.vt, self.outer.chain(&self.map), self.regions)? + .layout(&mut self.vt, self.outer.chain(&self.local), self.regions)? .into_frame()) } - pub fn layout_text(&mut self, node: &TextNode) -> SourceResult<()> { - let text = node.text(); - let span = node.span(); + pub fn layout_text(&mut self, elem: &TextElem) -> SourceResult<()> { + let text = elem.text(); + let span = elem.span(); let mut chars = text.chars(); if let Some(glyph) = chars .next() @@ -160,7 +160,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { style = style.with_italic(false); } let text: EcoString = text.chars().map(|c| style.styled_char(c)).collect(); - let frame = self.layout_content(&TextNode::packed(text).spanned(span))?; + let frame = self.layout_content(&TextElem::packed(text).spanned(span))?; self.push( FrameFragment::new(self, frame) .with_class(MathClass::Alphabetic) @@ -172,21 +172,21 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { } pub fn styles(&self) -> StyleChain { - self.outer.chain(&self.map) + self.outer.chain(&self.local) } pub fn style(&mut self, style: MathStyle) { self.style_stack.push((self.style, self.size)); - let base_size = TextNode::size_in(self.styles()) / self.style.size.factor(self); + let base_size = TextElem::size_in(self.styles()) / self.style.size.factor(self); self.size = base_size * style.size.factor(self); - self.map.set(TextNode::set_size(TextSize(self.size.into()))); - self.map - .set(TextNode::set_style(if style.italic == Smart::Custom(true) { + self.local.set(TextElem::set_size(TextSize(self.size.into()))); + self.local + .set(TextElem::set_style(if style.italic == Smart::Custom(true) { FontStyle::Italic } else { FontStyle::Normal })); - self.map.set(TextNode::set_weight(if style.bold { + self.local.set(TextElem::set_weight(if style.bold { FontWeight::BOLD } else { FontWeight::REGULAR @@ -196,9 +196,9 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { pub fn unstyle(&mut self) { (self.style, self.size) = self.style_stack.pop().unwrap(); - self.map.unset(); - self.map.unset(); - self.map.unset(); + self.local.unset(); + self.local.unset(); + self.local.unset(); } } diff --git a/library/src/math/delimited.rs b/library/src/math/delimited.rs index 2b9ee5ed2..3be17eae3 100644 --- a/library/src/math/delimited.rs +++ b/library/src/math/delimited.rs @@ -16,8 +16,8 @@ pub(super) const DELIM_SHORT_FALL: Em = Em::new(0.1); /// /// Display: Left/Right /// Category: math -#[node(LayoutMath)] -pub struct LrNode { +#[element(LayoutMath)] +pub struct LrElem { /// The size of the brackets, relative to the height of the wrapped content. /// /// Defaults to `{100%}`. @@ -29,7 +29,7 @@ pub struct LrNode { let mut body = Content::empty(); for (i, arg) in args.all::()?.into_iter().enumerate() { if i > 0 { - body += TextNode::packed(','); + body += TextElem::packed(','); } body += arg; } @@ -38,12 +38,12 @@ pub struct LrNode { pub body: Content, } -impl LayoutMath for LrNode { +impl LayoutMath for LrElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { let mut body = self.body(); - if let Some(node) = body.to::() { - if node.size(ctx.styles()).is_auto() { - body = node.body(); + if let Some(elem) = body.to::() { + if elem.size(ctx.styles()).is_auto() { + body = elem.body(); } } @@ -179,12 +179,11 @@ pub fn norm( } fn delimited(body: Content, left: char, right: char) -> Value { - Value::Content( - LrNode::new(Content::sequence(vec![ - TextNode::packed(left), - body, - TextNode::packed(right), - ])) - .pack(), - ) + LrElem::new(Content::sequence([ + TextElem::packed(left), + body, + TextElem::packed(right), + ])) + .pack() + .into() } diff --git a/library/src/math/frac.rs b/library/src/math/frac.rs index 90bc69b3b..f19fb32e7 100644 --- a/library/src/math/frac.rs +++ b/library/src/math/frac.rs @@ -19,8 +19,8 @@ const FRAC_AROUND: Em = Em::new(0.1); /// /// Display: Fraction /// Category: math -#[node(LayoutMath)] -pub struct FracNode { +#[element(LayoutMath)] +pub struct FracElem { /// The fraction's numerator. #[required] pub num: Content, @@ -30,7 +30,7 @@ pub struct FracNode { pub denom: Content, } -impl LayoutMath for FracNode { +impl LayoutMath for FracElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { layout(ctx, &self.num(), &self.denom(), false, self.span()) } @@ -45,8 +45,8 @@ impl LayoutMath for FracNode { /// /// Display: Binomial /// Category: math -#[node(LayoutMath)] -pub struct BinomNode { +#[element(LayoutMath)] +pub struct BinomElem { /// The binomial's upper index. #[required] pub upper: Content, @@ -56,7 +56,7 @@ pub struct BinomNode { pub lower: Content, } -impl LayoutMath for BinomNode { +impl LayoutMath for BinomElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { layout(ctx, &self.upper(), &self.lower(), true, self.span()) } @@ -132,9 +132,9 @@ fn layout( } else { frame.push( line_pos, - Element::Shape( + FrameItem::Shape( Geometry::Line(Point::with_x(line_width)).stroked(Stroke { - paint: TextNode::fill_in(ctx.styles()), + paint: TextElem::fill_in(ctx.styles()), thickness, }), span, diff --git a/library/src/math/fragment.rs b/library/src/math/fragment.rs index 87e285554..0d663d3fd 100644 --- a/library/src/math/fragment.rs +++ b/library/src/math/fragment.rs @@ -181,8 +181,8 @@ impl GlyphFragment { id, c, font: ctx.font.clone(), - lang: TextNode::lang_in(ctx.styles()), - fill: TextNode::fill_in(ctx.styles()), + lang: TextElem::lang_in(ctx.styles()), + fill: TextElem::fill_in(ctx.styles()), style: ctx.style, font_size: ctx.size, width, @@ -215,7 +215,7 @@ impl GlyphFragment { } pub fn to_frame(&self) -> Frame { - let text = Text { + let item = TextItem { font: self.font.clone(), size: self.font_size, fill: self.fill, @@ -232,7 +232,7 @@ impl GlyphFragment { let size = Size::new(self.width, self.ascent + self.descent); let mut frame = Frame::new(size); frame.set_baseline(self.ascent); - frame.push(Point::with_y(self.ascent), Element::Text(text)); + frame.push(Point::with_y(self.ascent), FrameItem::Text(item)); frame } } diff --git a/library/src/math/matrix.rs b/library/src/math/matrix.rs index d79c7ca57..8fba10e71 100644 --- a/library/src/math/matrix.rs +++ b/library/src/math/matrix.rs @@ -16,8 +16,8 @@ const VERTICAL_PADDING: Ratio = Ratio::new(0.1); /// /// Display: Vector /// Category: math -#[node(LayoutMath)] -pub struct VecNode { +#[element(LayoutMath)] +pub struct VecElem { /// The delimiter to use. /// /// ```example @@ -32,7 +32,7 @@ pub struct VecNode { pub children: Vec, } -impl LayoutMath for VecNode { +impl LayoutMath for VecElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { let delim = self.delim(ctx.styles()); let frame = layout_vec_body(ctx, &self.children(), Align::Center)?; @@ -68,8 +68,8 @@ impl LayoutMath for VecNode { /// /// Display: Matrix /// Category: math -#[node(LayoutMath)] -pub struct MatNode { +#[element(LayoutMath)] +pub struct MatElem { /// The delimiter to use. /// /// ```example @@ -114,7 +114,7 @@ pub struct MatNode { pub rows: Vec>, } -impl LayoutMath for MatNode { +impl LayoutMath for MatElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { let delim = self.delim(ctx.styles()); let frame = layout_mat_body(ctx, &self.rows())?; @@ -144,8 +144,8 @@ impl LayoutMath for MatNode { /// /// Display: Cases /// Category: math -#[node(LayoutMath)] -pub struct CasesNode { +#[element(LayoutMath)] +pub struct CasesElem { /// The delimiter to use. /// /// ```example @@ -160,7 +160,7 @@ pub struct CasesNode { pub children: Vec, } -impl LayoutMath for CasesNode { +impl LayoutMath for CasesElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { let delim = self.delim(ctx.styles()); let frame = layout_vec_body(ctx, &self.children(), Align::Left)?; @@ -221,8 +221,8 @@ fn layout_vec_body( let gap = ROW_GAP.scaled(ctx); ctx.style(ctx.style.for_denominator()); let mut flat = vec![]; - for element in column { - flat.push(ctx.layout_row(element)?); + for child in column { + flat.push(ctx.layout_row(child)?); } ctx.unstyle(); Ok(stack(ctx, flat, align, gap, 0)) diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index 7fb1aadfa..cf6652039 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -31,69 +31,69 @@ pub use self::underover::*; use ttf_parser::{GlyphId, Rect}; use typst::eval::{Module, Scope}; use typst::font::{Font, FontWeight}; -use typst::model::{Guard, SequenceNode, StyledNode}; +use typst::model::Guard; use unicode_math_class::MathClass; use self::ctx::*; use self::fragment::*; use self::row::*; use self::spacing::*; -use crate::layout::{HNode, ParNode, Spacing}; +use crate::layout::{HElem, ParElem, Spacing}; use crate::meta::{Count, Counter, CounterUpdate, LocalName, Numbering}; use crate::prelude::*; use crate::text::{ - families, variant, FontFamily, FontList, LinebreakNode, SpaceNode, TextNode, TextSize, + families, variant, FontFamily, FontList, LinebreakElem, SpaceElem, TextElem, TextSize, }; /// Create a module with all math definitions. pub fn module() -> Module { let mut math = Scope::deduplicating(); - math.define("equation", EquationNode::id()); - math.define("text", TextNode::id()); + math.define("equation", EquationElem::func()); + math.define("text", TextElem::func()); // Grouping. - math.define("lr", LrNode::id()); + math.define("lr", LrElem::func()); math.define("abs", abs); math.define("norm", norm); math.define("floor", floor); math.define("ceil", ceil); // Attachments and accents. - math.define("attach", AttachNode::id()); - math.define("scripts", ScriptsNode::id()); - math.define("limits", LimitsNode::id()); - math.define("accent", AccentNode::id()); - math.define("underline", UnderlineNode::id()); - math.define("overline", OverlineNode::id()); - math.define("underbrace", UnderbraceNode::id()); - math.define("overbrace", OverbraceNode::id()); - math.define("underbracket", UnderbracketNode::id()); - math.define("overbracket", OverbracketNode::id()); + math.define("attach", AttachElem::func()); + math.define("scripts", ScriptsElem::func()); + math.define("limits", LimitsElem::func()); + math.define("accent", AccentElem::func()); + math.define("underline", UnderlineElem::func()); + math.define("overline", OverlineElem::func()); + math.define("underbrace", UnderbraceElem::func()); + math.define("overbrace", OverbraceElem::func()); + math.define("underbracket", UnderbracketElem::func()); + math.define("overbracket", OverbracketElem::func()); // Fractions and matrix-likes. - math.define("frac", FracNode::id()); - math.define("binom", BinomNode::id()); - math.define("vec", VecNode::id()); - math.define("mat", MatNode::id()); - math.define("cases", CasesNode::id()); + math.define("frac", FracElem::func()); + math.define("binom", BinomElem::func()); + math.define("vec", VecElem::func()); + math.define("mat", MatElem::func()); + math.define("cases", CasesElem::func()); // Roots. - math.define("sqrt", SqrtNode::id()); - math.define("root", RootNode::id()); + math.define("sqrt", sqrt); + math.define("root", RootElem::func()); // Styles. - math.define("upright", UprightNode::id()); - math.define("bold", BoldNode::id()); - math.define("italic", ItalicNode::id()); - math.define("serif", SerifNode::id()); - math.define("sans", SansNode::id()); - math.define("cal", CalNode::id()); - math.define("frak", FrakNode::id()); - math.define("mono", MonoNode::id()); - math.define("bb", BbNode::id()); + math.define("upright", upright); + math.define("bold", bold); + math.define("italic", italic); + math.define("serif", serif); + math.define("sans", sans); + math.define("cal", cal); + math.define("frak", frak); + math.define("mono", mono); + math.define("bb", bb); // Text operators. - math.define("op", OpNode::id()); + math.define("op", OpElem::func()); op::define(&mut math); // Spacings. @@ -133,8 +133,8 @@ pub fn module() -> Module { /// /// Display: Equation /// Category: math -#[node(Locatable, Synthesize, Show, Finalize, Layout, LayoutMath, Count, LocalName)] -pub struct EquationNode { +#[element(Locatable, Synthesize, Show, Finalize, Layout, LayoutMath, Count, LocalName)] +pub struct EquationElem { /// Whether the equation is displayed as a separate block. #[default(false)] pub block: bool, @@ -157,16 +157,16 @@ pub struct EquationNode { pub body: Content, } -impl Synthesize for EquationNode { +impl Synthesize for EquationElem { fn synthesize(&mut self, _: &Vt, styles: StyleChain) { self.push_block(self.block(styles)); self.push_numbering(self.numbering(styles)); } } -impl Show for EquationNode { +impl Show for EquationElem { fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult { - let mut realized = self.clone().pack().guarded(Guard::Base(NodeId::of::())); + let mut realized = self.clone().pack().guarded(Guard::Base(Self::func())); if self.block(styles) { realized = realized.aligned(Axes::with_x(Some(Align::Center.into()))) } @@ -174,17 +174,17 @@ impl Show for EquationNode { } } -impl Finalize for EquationNode { +impl Finalize for EquationElem { fn finalize(&self, realized: Content, _: StyleChain) -> Content { realized - .styled(TextNode::set_weight(FontWeight::from_number(450))) - .styled(TextNode::set_font(FontList(vec![FontFamily::new( + .styled(TextElem::set_weight(FontWeight::from_number(450))) + .styled(TextElem::set_font(FontList(vec![FontFamily::new( "New Computer Modern Math", )]))) } } -impl Layout for EquationNode { +impl Layout for EquationElem { fn layout( &self, vt: &mut Vt, @@ -215,7 +215,7 @@ impl Layout for EquationNode { if block { if let Some(numbering) = self.numbering(styles) { let pod = Regions::one(regions.base(), Axes::splat(false)); - let counter = Counter::of(Self::id()) + let counter = Counter::of(Self::func()) .display(numbering, false) .layout(vt, styles, pod)? .into_frame(); @@ -230,7 +230,7 @@ impl Layout for EquationNode { let height = frame.height().max(counter.height()); frame.resize(Size::new(width, height), Align::CENTER_HORIZON); - let x = if TextNode::dir_in(styles).is_positive() { + let x = if TextElem::dir_in(styles).is_positive() { frame.width() - counter.width() } else { Abs::zero() @@ -240,10 +240,10 @@ impl Layout for EquationNode { frame.push_frame(Point::new(x, y), counter) } } else { - let slack = ParNode::leading_in(styles) * 0.7; - let top_edge = TextNode::top_edge_in(styles).resolve(styles, font.metrics()); + let slack = ParElem::leading_in(styles) * 0.7; + let top_edge = TextElem::top_edge_in(styles).resolve(styles, font.metrics()); let bottom_edge = - -TextNode::bottom_edge_in(styles).resolve(styles, font.metrics()); + -TextElem::bottom_edge_in(styles).resolve(styles, font.metrics()); let ascent = top_edge.max(frame.ascent() - slack); let descent = bottom_edge.max(frame.descent() - slack); @@ -255,7 +255,7 @@ impl Layout for EquationNode { } } -impl Count for EquationNode { +impl Count for EquationElem { fn update(&self) -> Option { (self.block(StyleChain::default()) && self.numbering(StyleChain::default()).is_some()) @@ -263,7 +263,7 @@ impl Count for EquationNode { } } -impl LocalName for EquationNode { +impl LocalName for EquationElem { fn local_name(&self, lang: Lang) -> &'static str { match lang { Lang::GERMAN => "Gleichung", @@ -276,7 +276,7 @@ pub trait LayoutMath { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()>; } -impl LayoutMath for EquationNode { +impl LayoutMath for EquationElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { self.body().layout_math(ctx) } @@ -284,45 +284,44 @@ impl LayoutMath for EquationNode { impl LayoutMath for Content { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - if let Some(node) = self.to::() { - for child in node.children() { + if let Some(children) = self.to_sequence() { + for child in children { child.layout_math(ctx)?; } return Ok(()); } - if let Some(styled) = self.to::() { - let map = styled.styles(); - if TextNode::font_in(ctx.styles().chain(&map)) - != TextNode::font_in(ctx.styles()) + if let Some((elem, styles)) = self.to_styled() { + if TextElem::font_in(ctx.styles().chain(&styles)) + != TextElem::font_in(ctx.styles()) { let frame = ctx.layout_content(self)?; ctx.push(FrameFragment::new(ctx, frame).with_spaced(true)); return Ok(()); } - let prev_map = std::mem::replace(&mut ctx.map, map); + let prev_map = std::mem::replace(&mut ctx.local, styles.clone()); let prev_size = ctx.size; - ctx.map.apply(prev_map.clone()); - ctx.size = TextNode::size_in(ctx.styles()); - styled.body().layout_math(ctx)?; + ctx.local.apply(prev_map.clone()); + ctx.size = TextElem::size_in(ctx.styles()); + elem.layout_math(ctx)?; ctx.size = prev_size; - ctx.map = prev_map; + ctx.local = prev_map; return Ok(()); } - if self.is::() { + if self.is::() { ctx.push(MathFragment::Space(ctx.space_width.scaled(ctx))); return Ok(()); } - if self.is::() { + if self.is::() { ctx.push(MathFragment::Linebreak); return Ok(()); } - if let Some(node) = self.to::() { - if let Spacing::Rel(rel) = node.amount() { + if let Some(elem) = self.to::() { + if let Spacing::Rel(rel) = elem.amount() { if rel.rel.is_zero() { ctx.push(MathFragment::Spacing(rel.abs.resolve(ctx.styles()))); } @@ -330,13 +329,13 @@ impl LayoutMath for Content { return Ok(()); } - if let Some(node) = self.to::() { - ctx.layout_text(node)?; + if let Some(elem) = self.to::() { + ctx.layout_text(elem)?; return Ok(()); } - if let Some(node) = self.with::() { - return node.layout_math(ctx); + if let Some(elem) = self.with::() { + return elem.layout_math(ctx); } let mut frame = ctx.layout_content(self)?; diff --git a/library/src/math/op.rs b/library/src/math/op.rs index dae43c3a9..e8db0c5db 100644 --- a/library/src/math/op.rs +++ b/library/src/math/op.rs @@ -20,8 +20,8 @@ use super::*; /// /// Display: Text Operator /// Category: math -#[node(LayoutMath)] -pub struct OpNode { +#[element(LayoutMath)] +pub struct OpElem { /// The operator's text. #[required] pub text: EcoString, @@ -33,9 +33,9 @@ pub struct OpNode { pub limits: bool, } -impl LayoutMath for OpNode { +impl LayoutMath for OpElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - let frame = ctx.layout_content(&TextNode::packed(self.text()))?; + let frame = ctx.layout_content(&TextElem::packed(self.text()))?; ctx.push( FrameFragment::new(ctx, frame) .with_class(MathClass::Large) @@ -50,14 +50,14 @@ macro_rules! ops { pub(super) fn define(math: &mut Scope) { $(math.define( stringify!($name), - OpNode::new(ops!(@name $name $(: $value)?).into()) + OpElem::new(ops!(@name $name $(: $value)?).into()) .with_limits(ops!(@limit $($tts)*)) .pack() );)* let dif = |d| { - HNode::new(THIN.into()).pack() - + UprightNode::new(TextNode::packed(d)).pack() + HElem::new(THIN.into()).pack() + + MathStyleElem::new(TextElem::packed(d)).with_italic(Some(false)).pack() }; math.define("dif", dif('d')); math.define("Dif", dif('D')); diff --git a/library/src/math/root.rs b/library/src/math/root.rs index b4756b9d4..037c6ce76 100644 --- a/library/src/math/root.rs +++ b/library/src/math/root.rs @@ -9,17 +9,13 @@ use super::*; /// /// Display: Square Root /// Category: math -#[node(LayoutMath)] -pub struct SqrtNode { +/// Returns: content +#[func] +pub fn sqrt( /// The expression to take the square root of. - #[required] - pub radicand: Content, -} - -impl LayoutMath for SqrtNode { - fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - layout(ctx, None, &self.radicand(), self.span()) - } + radicand: Content, +) -> Value { + RootElem::new(radicand).pack().into() } /// A general root. @@ -31,20 +27,20 @@ impl LayoutMath for SqrtNode { /// /// Display: Root /// Category: math -#[node(LayoutMath)] -pub struct RootNode { +#[element(LayoutMath)] +pub struct RootElem { /// Which root of the radicand to take. - #[required] - index: Content, + #[positional] + index: Option, /// The expression to take the root of. #[required] radicand: Content, } -impl LayoutMath for RootNode { +impl LayoutMath for RootElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - layout(ctx, Some(&self.index()), &self.radicand(), self.span()) + layout(ctx, self.index(ctx.styles()).as_ref(), &self.radicand(), self.span()) } } @@ -88,7 +84,7 @@ fn layout( // Layout the index. // Script-script style looks too small, we use Script style instead. ctx.style(ctx.style.with_size(MathSize::Script)); - let index = index.map(|node| ctx.layout_frame(node)).transpose()?; + let index = index.map(|elem| ctx.layout_frame(elem)).transpose()?; ctx.unstyle(); let gap = gap.max((sqrt.height() - radicand.height() - thickness) / 2.0); @@ -124,9 +120,9 @@ fn layout( frame.push_frame(sqrt_pos, sqrt); frame.push( line_pos, - Element::Shape( + FrameItem::Shape( Geometry::Line(Point::with_x(radicand.width())) - .stroked(Stroke { paint: TextNode::fill_in(ctx.styles()), thickness }), + .stroked(Stroke { paint: TextElem::fill_in(ctx.styles()), thickness }), span, ), ); @@ -139,15 +135,15 @@ fn layout( /// Select a precomposed radical, if the font has it. fn precomposed(ctx: &MathContext, index: Option<&Content>, target: Abs) -> Option { - let node = index?.to::()?; - let c = match node.text().as_str() { + let elem = index?.to::()?; + let c = match elem.text().as_str() { "3" => '∛', "4" => '∜', _ => return None, }; ctx.ttf.glyph_index(c)?; - let glyph = GlyphFragment::new(ctx, c, node.span()); + let glyph = GlyphFragment::new(ctx, c, elem.span()); let variant = glyph.stretch_vertical(ctx, target, Abs::zero()).frame; if variant.height() < target { return None; diff --git a/library/src/math/row.rs b/library/src/math/row.rs index ecb2e31e9..67d9eeafa 100644 --- a/library/src/math/row.rs +++ b/library/src/math/row.rs @@ -1,4 +1,4 @@ -use crate::layout::AlignNode; +use crate::layout::AlignElem; use super::*; @@ -103,7 +103,7 @@ impl MathRow { pub fn to_frame(self, ctx: &MathContext) -> Frame { let styles = ctx.styles(); - let align = AlignNode::alignment_in(styles).x.resolve(styles); + let align = AlignElem::alignment_in(styles).x.resolve(styles); self.to_aligned_frame(ctx, &[], align) } @@ -124,7 +124,7 @@ impl MathRow { if self.iter().any(|frag| matches!(frag, MathFragment::Linebreak)) { let fragments: Vec<_> = std::mem::take(&mut self.0); let leading = if ctx.style.size >= MathSize::Text { - ParNode::leading_in(ctx.styles()) + ParElem::leading_in(ctx.styles()) } else { TIGHT_LEADING.scaled(ctx) }; diff --git a/library/src/math/spacing.rs b/library/src/math/spacing.rs index e1b9d4084..848aca789 100644 --- a/library/src/math/spacing.rs +++ b/library/src/math/spacing.rs @@ -7,10 +7,10 @@ pub(super) const QUAD: Em = Em::new(1.0); /// Hook up all spacings. pub(super) fn define(math: &mut Scope) { - math.define("thin", HNode::new(THIN.into()).pack()); - math.define("med", HNode::new(MEDIUM.into()).pack()); - math.define("thick", HNode::new(THICK.into()).pack()); - math.define("quad", HNode::new(QUAD.into()).pack()); + math.define("thin", HElem::new(THIN.into()).pack()); + math.define("med", HElem::new(MEDIUM.into()).pack()); + math.define("thick", HElem::new(THICK.into()).pack()); + math.define("quad", HElem::new(QUAD.into()).pack()); } /// Create the spacing between two fragments in a given style. diff --git a/library/src/math/style.rs b/library/src/math/style.rs index a3383a0c0..7a911a0a7 100644 --- a/library/src/math/style.rs +++ b/library/src/math/style.rs @@ -9,20 +9,13 @@ use super::*; /// /// Display: Bold /// Category: math -#[node(LayoutMath)] -pub struct BoldNode { +/// Returns: content +#[func] +pub fn bold( /// The content to style. - #[required] - pub body: Content, -} - -impl LayoutMath for BoldNode { - fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - ctx.style(ctx.style.with_bold(true)); - self.body().layout_math(ctx)?; - ctx.unstyle(); - Ok(()) - } + body: Content, +) -> Value { + MathStyleElem::new(body).with_bold(Some(true)).pack().into() } /// Upright (non-italic) font style in math. @@ -34,20 +27,13 @@ impl LayoutMath for BoldNode { /// /// Display: Upright /// Category: math -#[node(LayoutMath)] -pub struct UprightNode { +/// Returns: content +#[func] +pub fn upright( /// The content to style. - #[required] - pub body: Content, -} - -impl LayoutMath for UprightNode { - fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - ctx.style(ctx.style.with_italic(false)); - self.body().layout_math(ctx)?; - ctx.unstyle(); - Ok(()) - } + body: Content, +) -> Value { + MathStyleElem::new(body).with_italic(Some(false)).pack().into() } /// Italic font style in math. @@ -56,42 +42,30 @@ impl LayoutMath for UprightNode { /// /// Display: Italic /// Category: math -#[node(LayoutMath)] -pub struct ItalicNode { +/// Returns: content +#[func] +pub fn italic( /// The content to style. - #[required] - pub body: Content, + body: Content, +) -> Value { + MathStyleElem::new(body).with_italic(Some(true)).pack().into() } - -impl LayoutMath for ItalicNode { - fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - ctx.style(ctx.style.with_italic(true)); - self.body().layout_math(ctx)?; - ctx.unstyle(); - Ok(()) - } -} - /// Serif (roman) font style in math. /// /// This is already the default. /// /// Display: Serif /// Category: math -#[node(LayoutMath)] -pub struct SerifNode { +/// Returns: content +#[func] +pub fn serif( /// The content to style. - #[required] - pub body: Content, -} - -impl LayoutMath for SerifNode { - fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - ctx.style(ctx.style.with_variant(MathVariant::Serif)); - self.body().layout_math(ctx)?; - ctx.unstyle(); - Ok(()) - } + body: Content, +) -> Value { + MathStyleElem::new(body) + .with_variant(Some(MathVariant::Serif)) + .pack() + .into() } /// Sans-serif font style in math. @@ -103,20 +77,16 @@ impl LayoutMath for SerifNode { /// /// Display: Sans-serif /// Category: math -#[node(LayoutMath)] -pub struct SansNode { +/// Returns: content +#[func] +pub fn sans( /// The content to style. - #[required] - pub body: Content, -} - -impl LayoutMath for SansNode { - fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - ctx.style(ctx.style.with_variant(MathVariant::Sans)); - self.body().layout_math(ctx)?; - ctx.unstyle(); - Ok(()) - } + body: Content, +) -> Value { + MathStyleElem::new(body) + .with_variant(Some(MathVariant::Sans)) + .pack() + .into() } /// Calligraphic font style in math. @@ -128,20 +98,16 @@ impl LayoutMath for SansNode { /// /// Display: Calligraphic /// Category: math -#[node(LayoutMath)] -pub struct CalNode { +/// Returns: content +#[func] +pub fn cal( /// The content to style. - #[required] - pub body: Content, -} - -impl LayoutMath for CalNode { - fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - ctx.style(ctx.style.with_variant(MathVariant::Cal)); - self.body().layout_math(ctx)?; - ctx.unstyle(); - Ok(()) - } + body: Content, +) -> Value { + MathStyleElem::new(body) + .with_variant(Some(MathVariant::Cal)) + .pack() + .into() } /// Fraktur font style in math. @@ -153,20 +119,16 @@ impl LayoutMath for CalNode { /// /// Display: Fraktur /// Category: math -#[node(LayoutMath)] -pub struct FrakNode { +/// Returns: content +#[func] +pub fn frak( /// The content to style. - #[required] - pub body: Content, -} - -impl LayoutMath for FrakNode { - fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - ctx.style(ctx.style.with_variant(MathVariant::Frak)); - self.body().layout_math(ctx)?; - ctx.unstyle(); - Ok(()) - } + body: Content, +) -> Value { + MathStyleElem::new(body) + .with_variant(Some(MathVariant::Frak)) + .pack() + .into() } /// Monospace font style in math. @@ -178,20 +140,16 @@ impl LayoutMath for FrakNode { /// /// Display: Monospace /// Category: math -#[node(LayoutMath)] -pub struct MonoNode { +/// Returns: content +#[func] +pub fn mono( /// The content to style. - #[required] - pub body: Content, -} - -impl LayoutMath for MonoNode { - fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - ctx.style(ctx.style.with_variant(MathVariant::Mono)); - self.body().layout_math(ctx)?; - ctx.unstyle(); - Ok(()) - } + body: Content, +) -> Value { + MathStyleElem::new(body) + .with_variant(Some(MathVariant::Mono)) + .pack() + .into() } /// Blackboard bold (double-struck) font style in math. @@ -208,16 +166,51 @@ impl LayoutMath for MonoNode { /// /// Display: Blackboard Bold /// Category: math -#[node(LayoutMath)] -pub struct BbNode { +/// Returns: content +#[func] +pub fn bb( + /// The content to style. + body: Content, +) -> Value { + MathStyleElem::new(body) + .with_variant(Some(MathVariant::Bb)) + .pack() + .into() +} + +/// A font variant in math. +/// +/// Display: Bold +/// Category: math +#[element(LayoutMath)] +pub struct MathStyleElem { /// The content to style. #[required] pub body: Content, + + /// The variant to select. + pub variant: Option, + + /// Whether to use bold glyphs. + pub bold: Option, + + /// Whether to use italic glyphs. + pub italic: Option, } -impl LayoutMath for BbNode { +impl LayoutMath for MathStyleElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - ctx.style(ctx.style.with_variant(MathVariant::Bb)); + let mut style = ctx.style; + if let Some(variant) = self.variant(StyleChain::default()) { + style = style.with_variant(variant); + } + if let Some(bold) = self.bold(StyleChain::default()) { + style = style.with_bold(bold); + } + if let Some(italic) = self.italic(StyleChain::default()) { + style = style.with_italic(italic); + } + ctx.style(style); self.body().layout_math(ctx)?; ctx.unstyle(); Ok(()) @@ -324,7 +317,7 @@ impl MathSize { } /// A mathematical style variant, as defined by Unicode. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Cast)] pub enum MathVariant { Serif, Sans, diff --git a/library/src/math/underover.rs b/library/src/math/underover.rs index cfbb30b65..654da3547 100644 --- a/library/src/math/underover.rs +++ b/library/src/math/underover.rs @@ -13,14 +13,14 @@ const BRACKET_GAP: Em = Em::new(0.25); /// /// Display: Underline /// Category: math -#[node(LayoutMath)] -pub struct UnderlineNode { +#[element(LayoutMath)] +pub struct UnderlineElem { /// The content above the line. #[required] pub body: Content, } -impl LayoutMath for UnderlineNode { +impl LayoutMath for UnderlineElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { layout(ctx, &self.body(), &None, '\u{305}', LINE_GAP, false, self.span()) } @@ -35,14 +35,14 @@ impl LayoutMath for UnderlineNode { /// /// Display: Overline /// Category: math -#[node(LayoutMath)] -pub struct OverlineNode { +#[element(LayoutMath)] +pub struct OverlineElem { /// The content below the line. #[required] pub body: Content, } -impl LayoutMath for OverlineNode { +impl LayoutMath for OverlineElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { layout(ctx, &self.body(), &None, '\u{332}', LINE_GAP, true, self.span()) } @@ -57,8 +57,8 @@ impl LayoutMath for OverlineNode { /// /// Display: Underbrace /// Category: math -#[node(LayoutMath)] -pub struct UnderbraceNode { +#[element(LayoutMath)] +pub struct UnderbraceElem { /// The content above the brace. #[required] pub body: Content, @@ -68,7 +68,7 @@ pub struct UnderbraceNode { pub annotation: Option, } -impl LayoutMath for UnderbraceNode { +impl LayoutMath for UnderbraceElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { layout( ctx, @@ -91,8 +91,8 @@ impl LayoutMath for UnderbraceNode { /// /// Display: Overbrace /// Category: math -#[node(LayoutMath)] -pub struct OverbraceNode { +#[element(LayoutMath)] +pub struct OverbraceElem { /// The content below the brace. #[required] pub body: Content, @@ -102,7 +102,7 @@ pub struct OverbraceNode { pub annotation: Option, } -impl LayoutMath for OverbraceNode { +impl LayoutMath for OverbraceElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { layout( ctx, @@ -125,8 +125,8 @@ impl LayoutMath for OverbraceNode { /// /// Display: Underbracket /// Category: math -#[node(LayoutMath)] -pub struct UnderbracketNode { +#[element(LayoutMath)] +pub struct UnderbracketElem { /// The content above the bracket. #[required] pub body: Content, @@ -136,7 +136,7 @@ pub struct UnderbracketNode { pub annotation: Option, } -impl LayoutMath for UnderbracketNode { +impl LayoutMath for UnderbracketElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { layout( ctx, @@ -159,8 +159,8 @@ impl LayoutMath for UnderbracketNode { /// /// Display: Overbracket /// Category: math -#[node(LayoutMath)] -pub struct OverbracketNode { +#[element(LayoutMath)] +pub struct OverbracketElem { /// The content below the bracket. #[required] pub body: Content, @@ -170,7 +170,7 @@ pub struct OverbracketNode { pub annotation: Option, } -impl LayoutMath for OverbracketNode { +impl LayoutMath for OverbracketElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { layout( ctx, diff --git a/library/src/meta/bibliography.rs b/library/src/meta/bibliography.rs index 64ac1f8e5..5f244a8f7 100644 --- a/library/src/meta/bibliography.rs +++ b/library/src/meta/bibliography.rs @@ -8,18 +8,18 @@ use hayagriva::io::{BibLaTeXError, YamlBibliographyError}; use hayagriva::style::{self, Brackets, Citation, Database, DisplayString, Formatting}; use hayagriva::Entry; -use super::{LocalName, RefNode}; -use crate::layout::{BlockNode, GridNode, ParNode, Sizing, TrackSizings, VNode}; -use crate::meta::HeadingNode; +use super::{LocalName, RefElem}; +use crate::layout::{BlockElem, GridElem, ParElem, Sizing, TrackSizings, VElem}; +use crate::meta::HeadingElem; use crate::prelude::*; -use crate::text::TextNode; +use crate::text::TextElem; /// A bibliography / reference listing. /// /// Display: Bibliography /// Category: meta -#[node(Locatable, Synthesize, Show, LocalName)] -pub struct BibliographyNode { +#[element(Locatable, Synthesize, Show, LocalName)] +pub struct BibliographyElem { /// Path to a Hayagriva `.yml` or BibLaTeX `.bib` file. #[required] #[parse( @@ -45,11 +45,11 @@ pub struct BibliographyNode { pub style: BibliographyStyle, } -impl BibliographyNode { +impl BibliographyElem { /// Find the document's bibliography. pub fn find(introspector: Tracked) -> StrResult { - let mut iter = introspector.query(Selector::node::()).into_iter(); - let Some(node) = iter.next() else { + let mut iter = introspector.query(Self::func().select()).into_iter(); + let Some(elem) = iter.next() else { return Err("the document does not contain a bibliography".into()); }; @@ -57,15 +57,15 @@ impl BibliographyNode { Err("multiple bibliographies are not supported")?; } - Ok(node.to::().unwrap().clone()) + Ok(elem.to::().unwrap().clone()) } /// Whether the bibliography contains the given key. pub fn has(vt: &Vt, key: &str) -> bool { vt.introspector - .query(Selector::node::()) + .query(Self::func().select()) .into_iter() - .flat_map(|node| load(vt.world, &node.to::().unwrap().path())) + .flat_map(|elem| load(vt.world, &elem.to::().unwrap().path())) .flatten() .any(|entry| entry.key() == key) } @@ -76,7 +76,7 @@ impl BibliographyNode { introspector: Tracked, ) -> Vec<(EcoString, Option)> { Self::find(introspector) - .and_then(|node| load(world, &node.path())) + .and_then(|elem| load(world, &elem.path())) .into_iter() .flatten() .map(|entry| { @@ -89,13 +89,13 @@ impl BibliographyNode { } } -impl Synthesize for BibliographyNode { +impl Synthesize for BibliographyElem { fn synthesize(&mut self, _: &Vt, styles: StyleChain) { self.push_style(self.style(styles)); } } -impl Show for BibliographyNode { +impl Show for BibliographyElem { fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult { const COLUMN_GUTTER: Em = Em::new(0.65); const INDENT: Em = Em::new(1.5); @@ -103,12 +103,12 @@ impl Show for BibliographyNode { let mut seq = vec![]; if let Some(title) = self.title(styles) { let title = title.clone().unwrap_or_else(|| { - TextNode::packed(self.local_name(TextNode::lang_in(styles))) + TextElem::packed(self.local_name(TextElem::lang_in(styles))) .spanned(self.span()) }); seq.push( - HeadingNode::new(title) + HeadingElem::new(title) .with_level(NonZeroUsize::ONE) .with_numbering(None) .pack(), @@ -121,7 +121,7 @@ impl Show for BibliographyNode { let works = Works::new(vt).at(self.span())?; - let row_gutter = BlockNode::below_in(styles).amount(); + let row_gutter = BlockElem::below_in(styles).amount(); if works.references.iter().any(|(prefix, _)| prefix.is_some()) { let mut cells = vec![]; for (prefix, reference) in &works.references { @@ -129,9 +129,9 @@ impl Show for BibliographyNode { cells.push(reference.clone()); } - seq.push(VNode::new(row_gutter).with_weakness(3).pack()); + seq.push(VElem::new(row_gutter).with_weakness(3).pack()); seq.push( - GridNode::new(cells) + GridElem::new(cells) .with_columns(TrackSizings(vec![Sizing::Auto; 2])) .with_column_gutter(TrackSizings(vec![COLUMN_GUTTER.into()])) .with_row_gutter(TrackSizings(vec![row_gutter.into()])) @@ -140,13 +140,13 @@ impl Show for BibliographyNode { } else { let mut entries = vec![]; for (_, reference) in &works.references { - entries.push(VNode::new(row_gutter).with_weakness(3).pack()); + entries.push(VElem::new(row_gutter).with_weakness(3).pack()); entries.push(reference.clone()); } seq.push( Content::sequence(entries) - .styled(ParNode::set_hanging_indent(INDENT.into())), + .styled(ParElem::set_hanging_indent(INDENT.into())), ); } @@ -154,7 +154,7 @@ impl Show for BibliographyNode { } } -impl LocalName for BibliographyNode { +impl LocalName for BibliographyElem { fn local_name(&self, lang: Lang) -> &'static str { match lang { Lang::GERMAN => "Bibliographie", @@ -196,8 +196,8 @@ impl BibliographyStyle { /// /// Display: Citation /// Category: meta -#[node(Locatable, Synthesize, Show)] -pub struct CiteNode { +#[element(Locatable, Synthesize, Show)] +pub struct CiteElem { /// The citation key. #[variadic] pub keys: Vec, @@ -217,7 +217,7 @@ pub struct CiteNode { pub style: Smart, } -impl Synthesize for CiteNode { +impl Synthesize for CiteElem { fn synthesize(&mut self, _: &Vt, styles: StyleChain) { self.push_supplement(self.supplement(styles)); self.push_brackets(self.brackets(styles)); @@ -225,17 +225,17 @@ impl Synthesize for CiteNode { } } -impl Show for CiteNode { +impl Show for CiteElem { fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult { if !vt.introspector.init() { return Ok(Content::empty()); } let works = Works::new(vt).at(self.span())?; - let id = self.0.stable_id().unwrap(); + let location = self.0.location().unwrap(); works .citations - .get(&id) + .get(&location) .cloned() .flatten() .ok_or("bibliography does not contain this key") @@ -264,24 +264,24 @@ pub enum CitationStyle { /// Fully formatted citations and references. #[derive(Default)] struct Works { - citations: HashMap>, + citations: HashMap>, references: Vec<(Option, Content)>, } impl Works { /// Prepare all things need to cite a work or format a bibliography. fn new(vt: &Vt) -> StrResult> { - let bibliography = BibliographyNode::find(vt.introspector)?; + let bibliography = BibliographyElem::find(vt.introspector)?; let citations = vt .introspector .query(Selector::Any(eco_vec![ - Selector::node::(), - Selector::node::(), + RefElem::func().select(), + CiteElem::func().select(), ])) .into_iter() - .map(|node| match node.to::() { + .map(|elem| match elem.to::() { Some(reference) => reference.to_citation(StyleChain::default()), - _ => node.to::().unwrap().clone(), + _ => elem.to::().unwrap().clone(), }) .collect(); Ok(create(vt.world, bibliography, citations)) @@ -292,19 +292,19 @@ impl Works { #[comemo::memoize] fn create( world: Tracked, - bibliography: BibliographyNode, - citations: Vec, + bibliography: BibliographyElem, + citations: Vec, ) -> Arc { let span = bibliography.span(); let entries = load(world, &bibliography.path()).unwrap(); let style = bibliography.style(StyleChain::default()); - let bib_id = bibliography.0.stable_id().unwrap(); - let ref_id = |target: &Entry| { + let bib_location = bibliography.0.location().unwrap(); + let ref_location = |target: &Entry| { let i = entries .iter() .position(|entry| entry.key() == target.key()) .unwrap_or_default(); - bib_id.variant(i) + bib_location.variant(i) }; let mut db = Database::new(); @@ -312,7 +312,7 @@ fn create( let mut preliminary = vec![]; for citation in citations { - let cite_id = citation.0.stable_id().unwrap(); + let cite_id = citation.0.location().unwrap(); let entries = citation .keys() .into_iter() @@ -333,8 +333,8 @@ fn create( let citations = preliminary .into_iter() .map(|(citation, cited)| { - let id = citation.0.stable_id().unwrap(); - let Some(cited) = cited else { return (id, None) }; + let location = citation.0.location().unwrap(); + let Some(cited) = cited else { return (location, None) }; let mut supplement = citation.supplement(StyleChain::default()); let brackets = citation.brackets(StyleChain::default()); @@ -376,27 +376,27 @@ fn create( } if i > 0 { - content += TextNode::packed(",\u{a0}"); + content += TextElem::packed(",\u{a0}"); } // Format and link to the reference entry. content += format_display_string(&display, supplement, citation.span()) - .linked(Link::Node(ref_id(entry))); + .linked(Destination::Location(ref_location(entry))); } if brackets && len > 1 { content = match citation_style.brackets() { Brackets::None => content, Brackets::Round => { - TextNode::packed('(') + content + TextNode::packed(')') + TextElem::packed('(') + content + TextElem::packed(')') } Brackets::Square => { - TextNode::packed('[') + content + TextNode::packed(']') + TextElem::packed('[') + content + TextElem::packed(']') } }; } - (id, Some(content)) + (location, Some(content)) }) .collect(); @@ -414,15 +414,15 @@ fn create( // Make link from citation to here work. let backlink = { let mut content = Content::empty(); - content.set_stable_id(ref_id(&reference.entry)); - MetaNode::set_data(vec![Meta::Node(content)]) + content.set_location(ref_location(&reference.entry)); + MetaElem::set_data(vec![Meta::Elem(content)]) }; let prefix = reference.prefix.map(|prefix| { // Format and link to first citation. let bracketed = prefix.with_default_brackets(&*citation_style); format_display_string(&bracketed, None, span) - .linked(Link::Node(ids[reference.entry.key()])) + .linked(Destination::Location(ids[reference.entry.key()])) .styled(backlink.clone()) }); @@ -510,7 +510,7 @@ fn format_display_string( let mut content = if segment == SUPPLEMENT && supplement.is_some() { supplement.take().unwrap_or_default() } else { - TextNode::packed(segment).spanned(span) + TextElem::packed(segment).spanned(span) }; for (range, fmt) in &string.formatting { @@ -522,7 +522,7 @@ fn format_display_string( Formatting::Bold => content.strong(), Formatting::Italic => content.emph(), Formatting::Link(link) => { - content.linked(Link::Dest(Destination::Url(link.as_str().into()))) + content.linked(Destination::Url(link.as_str().into())) } }; } diff --git a/library/src/meta/context.rs b/library/src/meta/context.rs index 9e5428476..dbb848129 100644 --- a/library/src/meta/context.rs +++ b/library/src/meta/context.rs @@ -10,28 +10,28 @@ pub fn locate( /// The function to call with the location. func: Func, ) -> Value { - LocateNode::new(func).pack().into() + LocateElem::new(func).pack().into() } /// Executes a `locate` call. /// /// Display: Styled /// Category: special -#[node(Locatable, Show)] -struct LocateNode { +#[element(Locatable, Show)] +struct LocateElem { /// The function to call with the location. #[required] func: Func, } -impl Show for LocateNode { +impl Show for LocateElem { fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult { if !vt.introspector.init() { return Ok(Content::empty()); } - let id = self.0.stable_id().unwrap(); - Ok(self.func().call_vt(vt, [id.into()])?.display()) + let location = self.0.location().unwrap(); + Ok(self.func().call_vt(vt, [location.into()])?.display()) } } @@ -45,21 +45,21 @@ pub fn style( /// The function to call with the styles. func: Func, ) -> Value { - StyleNode::new(func).pack().into() + StyleElem::new(func).pack().into() } /// Executes a style access. /// /// Display: Style /// Category: special -#[node(Show)] -struct StyleNode { +#[element(Show)] +struct StyleElem { /// The function to call with the styles. #[required] func: Func, } -impl Show for StyleNode { +impl Show for StyleElem { fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult { Ok(self.func().call_vt(vt, [styles.to_map().into()])?.display()) } diff --git a/library/src/meta/counter.rs b/library/src/meta/counter.rs index f033a04f2..3cfc2cd5c 100644 --- a/library/src/meta/counter.rs +++ b/library/src/meta/counter.rs @@ -6,7 +6,7 @@ use smallvec::{smallvec, SmallVec}; use typst::eval::Tracer; use super::{Numbering, NumberingPattern}; -use crate::layout::PageNode; +use crate::layout::PageElem; use crate::prelude::*; /// Count through pages, elements, and more. @@ -32,9 +32,9 @@ impl Counter { Self(key) } - /// The counter for the given node. - pub fn of(id: NodeId) -> Self { - Self::new(CounterKey::Selector(Selector::Node(id, None))) + /// 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. @@ -69,23 +69,23 @@ impl Counter { /// Display the current value of the counter. pub fn display(self, numbering: Numbering, both: bool) -> Content { - DisplayNode::new(self, numbering, both).pack() + DisplayElem::new(self, numbering, both).pack() } /// Get the value of the state at the given location. - pub fn at(&self, vt: &mut Vt, id: StableId) -> SourceResult { + pub fn at(&self, vt: &mut Vt, location: Location) -> SourceResult { let sequence = self.sequence(vt)?; - let offset = vt.introspector.query_before(self.selector(), id).len(); + let offset = vt.introspector.query_before(self.selector(), location).len(); let (mut state, page) = sequence[offset].clone(); if self.is_page() { - let delta = vt.introspector.page(id).get() - page.get(); + let delta = vt.introspector.page(location).get() - 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, _: StableId) -> SourceResult { + pub fn final_(&self, vt: &mut Vt, _: Location) -> SourceResult { let sequence = self.sequence(vt)?; let (mut state, page) = sequence.last().unwrap().clone(); if self.is_page() { @@ -96,13 +96,13 @@ impl Counter { } /// Get the current and final value of the state combined in one state. - pub fn both(&self, vt: &mut Vt, id: StableId) -> SourceResult { + pub fn both(&self, vt: &mut Vt, location: Location) -> SourceResult { let sequence = self.sequence(vt)?; - let offset = vt.introspector.query_before(self.selector(), id).len(); + let offset = vt.introspector.query_before(self.selector(), location).len(); 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 = vt.introspector.page(id).get() - at_page.get(); + let at_delta = vt.introspector.page(location).get() - at_page.get(); at_state.step(NonZeroUsize::ONE, at_delta); let final_delta = vt.introspector.pages().get() - final_page.get(); final_state.step(NonZeroUsize::ONE, final_delta); @@ -112,7 +112,7 @@ impl Counter { /// Produce content that performs a state update. pub fn update(self, update: CounterUpdate) -> Content { - UpdateNode::new(self, update).pack() + UpdateElem::new(self, update).pack() } /// Produce the whole sequence of counter states. @@ -148,11 +148,11 @@ impl Counter { let mut page = NonZeroUsize::ONE; let mut stops = eco_vec![(state.clone(), page)]; - for node in introspector.query(self.selector()) { + for elem in introspector.query(self.selector()) { if self.is_page() { - let id = node.stable_id().unwrap(); + let location = elem.location().unwrap(); let prev = page; - page = introspector.page(id); + page = introspector.page(location); let delta = page.get() - prev.get(); if delta > 0 { @@ -160,9 +160,9 @@ impl Counter { } } - if let Some(update) = match node.to::() { - Some(node) => Some(node.update()), - None => match node.with::() { + if let Some(update) = match elem.to::() { + Some(elem) => Some(elem.update()), + None => match elem.with::() { Some(countable) => countable.update(), None => Some(CounterUpdate::Step(NonZeroUsize::ONE)), }, @@ -178,10 +178,8 @@ impl Counter { /// The selector relevant for this counter's updates. fn selector(&self) -> Selector { - let mut selector = Selector::Node( - NodeId::of::(), - Some(dict! { "counter" => self.clone() }), - ); + let mut selector = + Selector::Elem(UpdateElem::func(), Some(dict! { "counter" => self.clone() })); if let CounterKey::Selector(key) = &self.0 { selector = Selector::Any(eco_vec![selector, key.clone()]); @@ -224,20 +222,16 @@ cast_from_value! { CounterKey, v: Str => Self::Str(v), label: Label => Self::Selector(Selector::Label(label)), - func: Func => { - let Some(id) = func.id() else { - return Err("this function is not selectable".into()); - }; - - if id == NodeId::of::() { + element: ElemFunc => { + if element == PageElem::func() { return Ok(Self::Page); } - if !Content::new(id).can::() { - Err(eco_format!("cannot count through {}s", id.name))?; + if !Content::new(element).can::() { + Err(eco_format!("cannot count through {}s", element.name()))?; } - Self::Selector(Selector::Node(id, None)) + Self::Selector(Selector::Elem(element, None)) } } @@ -274,9 +268,9 @@ cast_from_value! { v: Func => Self::Func(v), } -/// Nodes that have special counting behaviour. +/// Elements that have special counting behaviour. pub trait Count { - /// Get the counter update for this node. + /// Get the counter update for this element. fn update(&self) -> Option; } @@ -342,8 +336,8 @@ cast_to_value! { /// /// Display: State /// Category: special -#[node(Locatable, Show)] -struct DisplayNode { +#[element(Locatable, Show)] +struct DisplayElem { /// The counter. #[required] counter: Counter, @@ -357,12 +351,16 @@ struct DisplayNode { both: bool, } -impl Show for DisplayNode { +impl Show for DisplayElem { fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult { - let id = self.0.stable_id().unwrap(); + let location = self.0.location().unwrap(); let counter = self.counter(); let numbering = self.numbering(); - let state = if self.both() { counter.both(vt, id) } else { counter.at(vt, id) }?; + let state = if self.both() { + counter.both(vt, location)? + } else { + counter.at(vt, location)? + }; state.display(vt, &numbering) } } @@ -371,8 +369,8 @@ impl Show for DisplayNode { /// /// Display: State /// Category: special -#[node(Locatable, Show)] -struct UpdateNode { +#[element(Locatable, Show)] +struct UpdateElem { /// The counter. #[required] counter: Counter, @@ -382,7 +380,7 @@ struct UpdateNode { update: CounterUpdate, } -impl Show for UpdateNode { +impl Show for UpdateElem { fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult { Ok(Content::empty()) } diff --git a/library/src/meta/document.rs b/library/src/meta/document.rs index 75e78184c..99e5c6b60 100644 --- a/library/src/meta/document.rs +++ b/library/src/meta/document.rs @@ -1,6 +1,4 @@ -use typst::model::StyledNode; - -use crate::layout::{LayoutRoot, PageNode}; +use crate::layout::{LayoutRoot, PageElem}; use crate::prelude::*; /// The root element of a document and its metadata. @@ -14,8 +12,8 @@ use crate::prelude::*; /// /// Display: Document /// Category: meta -#[node(LayoutRoot)] -pub struct DocumentNode { +#[element(LayoutRoot)] +pub struct DocumentElem { /// The document's title. This is often rendered as the title of the /// PDF viewer window. pub title: Option, @@ -29,22 +27,20 @@ pub struct DocumentNode { pub children: Vec, } -impl LayoutRoot for DocumentNode { +impl LayoutRoot for DocumentElem { /// Layout the document into a sequence of frames, one per page. fn layout_root(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult { let mut pages = vec![]; - for mut child in self.children() { - let map; + for mut child in &self.children() { let outer = styles; - let mut styles = outer; - if let Some(node) = child.to::() { - map = node.styles(); - styles = outer.chain(&map); - child = node.body(); + let mut styles = styles; + if let Some((elem, local)) = child.to_styled() { + styles = outer.chain(local); + child = elem; } - if let Some(page) = child.to::() { + if let Some(page) = child.to::() { let fragment = page.layout(vt, styles)?; pages.extend(fragment); } else { diff --git a/library/src/meta/figure.rs b/library/src/meta/figure.rs index 6f9011b8a..9251f3ef3 100644 --- a/library/src/meta/figure.rs +++ b/library/src/meta/figure.rs @@ -1,9 +1,9 @@ use std::str::FromStr; use super::{Count, Counter, CounterUpdate, LocalName, Numbering, NumberingPattern}; -use crate::layout::{BlockNode, VNode}; +use crate::layout::{BlockElem, VElem}; use crate::prelude::*; -use crate::text::TextNode; +use crate::text::TextElem; /// A figure with an optional caption. /// @@ -23,8 +23,8 @@ use crate::text::TextNode; /// /// Display: Figure /// Category: meta -#[node(Locatable, Synthesize, Count, Show, LocalName)] -pub struct FigureNode { +#[element(Locatable, Synthesize, Count, Show, LocalName)] +pub struct FigureElem { /// The content of the figure. Often, an [image]($func/image). #[required] pub body: Content, @@ -42,32 +42,32 @@ pub struct FigureNode { pub gap: Length, } -impl Synthesize for FigureNode { +impl Synthesize for FigureElem { fn synthesize(&mut self, _: &Vt, styles: StyleChain) { self.push_numbering(self.numbering(styles)); } } -impl Show for FigureNode { +impl Show for FigureElem { fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult { let mut realized = self.body(); if let Some(mut caption) = self.caption(styles) { if let Some(numbering) = self.numbering(styles) { - let name = self.local_name(TextNode::lang_in(styles)); - caption = TextNode::packed(eco_format!("{name}\u{a0}")) - + Counter::of(Self::id()) + let name = self.local_name(TextElem::lang_in(styles)); + caption = TextElem::packed(eco_format!("{name}\u{a0}")) + + Counter::of(Self::func()) .display(numbering, false) .spanned(self.span()) - + TextNode::packed(": ") + + TextElem::packed(": ") + caption; } - realized += VNode::weak(self.gap(styles).into()).pack(); + realized += VElem::weak(self.gap(styles).into()).pack(); realized += caption; } - Ok(BlockNode::new() + Ok(BlockElem::new() .with_body(Some(realized)) .with_breakable(false) .pack() @@ -75,7 +75,7 @@ impl Show for FigureNode { } } -impl Count for FigureNode { +impl Count for FigureElem { fn update(&self) -> Option { self.numbering(StyleChain::default()) .is_some() @@ -83,7 +83,7 @@ impl Count for FigureNode { } } -impl LocalName for FigureNode { +impl LocalName for FigureElem { fn local_name(&self, lang: Lang) -> &'static str { match lang { Lang::GERMAN => "Abbildung", diff --git a/library/src/meta/heading.rs b/library/src/meta/heading.rs index 61605e67d..1eaca187f 100644 --- a/library/src/meta/heading.rs +++ b/library/src/meta/heading.rs @@ -1,10 +1,10 @@ use typst::font::FontWeight; use super::{Counter, CounterUpdate, LocalName, Numbering}; -use crate::layout::{BlockNode, HNode, VNode}; +use crate::layout::{BlockElem, HElem, VElem}; use crate::meta::Count; use crate::prelude::*; -use crate::text::{TextNode, TextSize}; +use crate::text::{TextElem, TextSize}; /// A section heading. /// @@ -41,8 +41,8 @@ use crate::text::{TextNode, TextSize}; /// /// Display: Heading /// Category: meta -#[node(Locatable, Synthesize, Count, Show, Finalize, LocalName)] -pub struct HeadingNode { +#[element(Locatable, Synthesize, Count, Show, Finalize, LocalName)] +pub struct HeadingElem { /// The logical nesting depth of the heading, starting from one. #[default(NonZeroUsize::ONE)] pub level: NonZeroUsize, @@ -79,7 +79,7 @@ pub struct HeadingNode { pub body: Content, } -impl Synthesize for HeadingNode { +impl Synthesize for HeadingElem { fn synthesize(&mut self, _: &Vt, styles: StyleChain) { self.push_level(self.level(styles)); self.push_numbering(self.numbering(styles)); @@ -87,20 +87,21 @@ impl Synthesize for HeadingNode { } } -impl Show for HeadingNode { +impl Show for HeadingElem { fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult { let mut realized = self.body(); if let Some(numbering) = self.numbering(styles) { - realized = - Counter::of(Self::id()).display(numbering, false).spanned(self.span()) - + HNode::new(Em::new(0.3).into()).with_weak(true).pack() - + realized; + realized = Counter::of(Self::func()) + .display(numbering, false) + .spanned(self.span()) + + HElem::new(Em::new(0.3).into()).with_weak(true).pack() + + realized; } - Ok(BlockNode::new().with_body(Some(realized)).pack()) + Ok(BlockElem::new().with_body(Some(realized)).pack()) } } -impl Finalize for HeadingNode { +impl Finalize for HeadingElem { fn finalize(&self, realized: Content, styles: StyleChain) -> Content { let level = self.level(styles).get(); let scale = match level { @@ -113,17 +114,17 @@ impl Finalize for HeadingNode { let above = Em::new(if level == 1 { 1.8 } else { 1.44 }) / scale; let below = Em::new(0.75) / scale; - let mut map = StyleMap::new(); - map.set(TextNode::set_size(TextSize(size.into()))); - map.set(TextNode::set_weight(FontWeight::BOLD)); - map.set(BlockNode::set_above(VNode::block_around(above.into()))); - map.set(BlockNode::set_below(VNode::block_around(below.into()))); - map.set(BlockNode::set_sticky(true)); - realized.styled_with_map(map) + let mut styles = Styles::new(); + styles.set(TextElem::set_size(TextSize(size.into()))); + styles.set(TextElem::set_weight(FontWeight::BOLD)); + styles.set(BlockElem::set_above(VElem::block_around(above.into()))); + styles.set(BlockElem::set_below(VElem::block_around(below.into()))); + styles.set(BlockElem::set_sticky(true)); + realized.styled_with_map(styles) } } -impl Count for HeadingNode { +impl Count for HeadingElem { fn update(&self) -> Option { self.numbering(StyleChain::default()) .is_some() @@ -132,11 +133,11 @@ impl Count for HeadingNode { } cast_from_value! { - HeadingNode, + HeadingElem, v: Content => v.to::().ok_or("expected heading")?.clone(), } -impl LocalName for HeadingNode { +impl LocalName for HeadingElem { fn local_name(&self, lang: Lang) -> &'static str { match lang { Lang::GERMAN => "Abschnitt", diff --git a/library/src/meta/link.rs b/library/src/meta/link.rs index e9b8bcc67..c82a74436 100644 --- a/library/src/meta/link.rs +++ b/library/src/meta/link.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use crate::text::{Hyphenate, TextNode}; +use crate::text::{Hyphenate, TextElem}; /// Link to a URL or another location in the document. /// @@ -25,19 +25,20 @@ use crate::text::{Hyphenate, TextNode}; /// /// Display: Link /// Category: meta -#[node(Show, Finalize)] -pub struct LinkNode { +#[element(Show, Finalize)] +pub struct LinkElem { /// The destination the link points to. /// - /// - To link to web pages, `dest` should be a valid URL string. If the URL is - /// in the `mailto:` or `tel:` scheme and the `body` parameter is omitted, - /// the email address or phone number will be the link's body, without the - /// scheme. + /// - To link to web pages, `dest` should be a valid URL string. If the URL + /// is in the `mailto:` or `tel:` scheme and the `body` parameter is + /// omitted, the email address or phone number will be the link's body, + /// without the scheme. /// - /// - To link to another part of the document, `dest` must contain a - /// dictionary with a `page` key of type `integer` and `x` and `y` - /// coordinates of type `length`. Pages are counted from one, and the - /// coordinates are relative to the page's top left corner. + /// - To link to another part of the document, `dest` can take one of two + /// forms: A [`location`]($func/locate) or a dictionary with a `page` key + /// of type `integer` and `x` and `y` coordinates of type `length`. Pages + /// are counted from one, and the coordinates are relative to the page's + /// top left corner. /// /// ```example /// #link("mailto:hello@typst.app") \ @@ -45,7 +46,6 @@ pub struct LinkNode { /// Go to top /// ] /// ``` - /// #[required] #[parse( let dest = args.expect::("destination")?; @@ -64,30 +64,30 @@ pub struct LinkNode { Some(body) => body, None => body_from_url(url), }, - Destination::Internal(_) => args.expect("body")?, + _ => args.expect("body")?, })] pub body: Content, } -impl LinkNode { - /// Create a link node from a URL with its bare text. +impl LinkElem { + /// Create a link element from a URL with its bare text. pub fn from_url(url: EcoString) -> Self { let body = body_from_url(&url); Self::new(Destination::Url(url), body) } } -impl Show for LinkNode { +impl Show for LinkElem { fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult { Ok(self.body()) } } -impl Finalize for LinkNode { +impl Finalize for LinkElem { fn finalize(&self, realized: Content, _: StyleChain) -> Content { realized - .linked(Link::Dest(self.dest())) - .styled(TextNode::set_hyphenate(Hyphenate(Smart::Custom(false)))) + .linked(self.dest()) + .styled(TextElem::set_hyphenate(Hyphenate(Smart::Custom(false)))) } } @@ -97,5 +97,5 @@ fn body_from_url(url: &EcoString) -> Content { text = text.trim_start_matches(prefix); } let shorter = text.len() < url.len(); - TextNode::packed(if shorter { text.into() } else { url.clone() }) + TextElem::packed(if shorter { text.into() } else { url.clone() }) } diff --git a/library/src/meta/outline.rs b/library/src/meta/outline.rs index a0a238973..002d757e0 100644 --- a/library/src/meta/outline.rs +++ b/library/src/meta/outline.rs @@ -1,7 +1,7 @@ -use super::{Counter, HeadingNode, LocalName}; -use crate::layout::{BoxNode, HNode, HideNode, ParbreakNode, RepeatNode}; +use super::{Counter, HeadingElem, LocalName}; +use crate::layout::{BoxElem, HElem, HideElem, ParbreakElem, RepeatElem}; use crate::prelude::*; -use crate::text::{LinebreakNode, SpaceNode, TextNode}; +use crate::text::{LinebreakElem, SpaceElem, TextElem}; /// A section outline / table of contents. /// @@ -22,8 +22,8 @@ use crate::text::{LinebreakNode, SpaceNode, TextNode}; /// /// Display: Outline /// Category: meta -#[node(Show, LocalName)] -pub struct OutlineNode { +#[element(Show, LocalName)] +pub struct OutlineElem { /// The title of the outline. /// /// - When set to `{auto}`, an appropriate title for the [text @@ -65,21 +65,21 @@ pub struct OutlineNode { /// /// = A New Beginning /// ``` - #[default(Some(RepeatNode::new(TextNode::packed(".")).pack()))] + #[default(Some(RepeatElem::new(TextElem::packed(".")).pack()))] pub fill: Option, } -impl Show for OutlineNode { +impl Show for OutlineElem { fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult { - let mut seq = vec![ParbreakNode::new().pack()]; + let mut seq = vec![ParbreakElem::new().pack()]; if let Some(title) = self.title(styles) { let title = title.clone().unwrap_or_else(|| { - TextNode::packed(self.local_name(TextNode::lang_in(styles))) + TextElem::packed(self.local_name(TextElem::lang_in(styles))) .spanned(self.span()) }); seq.push( - HeadingNode::new(title) + HeadingElem::new(title) .with_level(NonZeroUsize::ONE) .with_numbering(None) .with_outlined(false) @@ -90,15 +90,15 @@ impl Show for OutlineNode { let indent = self.indent(styles); let depth = self.depth(styles); - let mut ancestors: Vec<&HeadingNode> = vec![]; - let nodes = vt.introspector.query(Selector::Node( - NodeId::of::(), + let mut ancestors: Vec<&HeadingElem> = vec![]; + let elems = vt.introspector.query(Selector::Elem( + HeadingElem::func(), Some(dict! { "outlined" => true }), )); - for node in &nodes { - let heading = node.to::().unwrap(); - let stable_id = heading.0.stable_id().unwrap(); + for elem in &elems { + let heading = elem.to::().unwrap(); + let location = heading.0.location().unwrap(); if !heading.outlined(StyleChain::default()) { continue; } @@ -120,60 +120,60 @@ impl Show for OutlineNode { let mut hidden = Content::empty(); for ancestor in &ancestors { if let Some(numbering) = ancestor.numbering(StyleChain::default()) { - let numbers = Counter::of(HeadingNode::id()) - .at(vt, ancestor.0.stable_id().unwrap())? + let numbers = Counter::of(HeadingElem::func()) + .at(vt, ancestor.0.location().unwrap())? .display(vt, &numbering)?; - hidden += numbers + SpaceNode::new().pack(); + hidden += numbers + SpaceElem::new().pack(); }; } if !ancestors.is_empty() { - seq.push(HideNode::new(hidden).pack()); - seq.push(SpaceNode::new().pack()); + seq.push(HideElem::new(hidden).pack()); + seq.push(SpaceElem::new().pack()); } } // Format the numbering. let mut start = heading.body(); if let Some(numbering) = heading.numbering(StyleChain::default()) { - let numbers = Counter::of(HeadingNode::id()) - .at(vt, stable_id)? + let numbers = Counter::of(HeadingElem::func()) + .at(vt, location)? .display(vt, &numbering)?; - start = numbers + SpaceNode::new().pack() + start; + start = numbers + SpaceElem::new().pack() + start; }; // Add the numbering and section name. - seq.push(start.linked(Link::Node(stable_id))); + seq.push(start.linked(Destination::Location(location))); // Add filler symbols between the section name and page number. if let Some(filler) = self.fill(styles) { - seq.push(SpaceNode::new().pack()); + seq.push(SpaceElem::new().pack()); seq.push( - BoxNode::new() + BoxElem::new() .with_body(Some(filler.clone())) .with_width(Fr::one().into()) .pack(), ); - seq.push(SpaceNode::new().pack()); + seq.push(SpaceElem::new().pack()); } else { - seq.push(HNode::new(Fr::one().into()).pack()); + seq.push(HElem::new(Fr::one().into()).pack()); } // Add the page number and linebreak. - let page = vt.introspector.page(stable_id); - let end = TextNode::packed(eco_format!("{page}")); - seq.push(end.linked(Link::Node(stable_id))); - seq.push(LinebreakNode::new().pack()); + let page = vt.introspector.page(location); + let end = TextElem::packed(eco_format!("{page}")); + seq.push(end.linked(Destination::Location(location))); + seq.push(LinebreakElem::new().pack()); ancestors.push(heading); } - seq.push(ParbreakNode::new().pack()); + seq.push(ParbreakElem::new().pack()); Ok(Content::sequence(seq)) } } -impl LocalName for OutlineNode { +impl LocalName for OutlineElem { fn local_name(&self, lang: Lang) -> &'static str { match lang { Lang::GERMAN => "Inhaltsverzeichnis", diff --git a/library/src/meta/query.rs b/library/src/meta/query.rs index 23e310fe3..94b25cbc8 100644 --- a/library/src/meta/query.rs +++ b/library/src/meta/query.rs @@ -11,24 +11,24 @@ pub fn query( target: Target, /// The location. #[external] - location: StableId, + location: Location, /// The location before which to query. #[named] #[external] - before: StableId, + before: Location, /// The location after which to query. #[named] #[external] - after: StableId, + after: Location, ) -> Value { let selector = target.0; let introspector = vm.vt.introspector; - let elements = if let Some(id) = args.named("before")? { - introspector.query_before(selector, id) - } else if let Some(id) = args.named("after")? { - introspector.query_after(selector, id) + let elements = if let Some(location) = args.named("before")? { + introspector.query_before(selector, location) + } else if let Some(location) = args.named("after")? { + introspector.query_after(selector, location) } else { - let _: StableId = args.expect("id")?; + let _: Location = args.expect("location")?; introspector.query(selector) }; elements.into() @@ -40,15 +40,11 @@ struct Target(Selector); cast_from_value! { Target, label: Label => Self(Selector::Label(label)), - func: Func => { - let Some(id) = func.id() else { - return Err("this function is not selectable".into()); - }; - - if !Content::new(id).can::() { - Err(eco_format!("cannot query for {}s", id.name))?; + element: ElemFunc => { + if !Content::new(element).can::() { + Err(eco_format!("cannot query for {}s", element.name()))?; } - Self(Selector::Node(id, None)) + Self(Selector::Elem(element, None)) } } diff --git a/library/src/meta/reference.rs b/library/src/meta/reference.rs index 000080d87..6e794d2dd 100644 --- a/library/src/meta/reference.rs +++ b/library/src/meta/reference.rs @@ -1,6 +1,6 @@ -use super::{BibliographyNode, CiteNode, Counter, LocalName, Numbering}; +use super::{BibliographyElem, CiteElem, Counter, LocalName, Numbering}; use crate::prelude::*; -use crate::text::TextNode; +use crate::text::TextElem; /// A reference to a label. /// @@ -35,8 +35,8 @@ use crate::text::TextNode; /// /// Display: Reference /// Category: meta -#[node(Locatable, Show)] -pub struct RefNode { +#[element(Locatable, Show)] +pub struct RefElem { /// The target label that should be referenced. #[required] pub target: Label, @@ -63,7 +63,7 @@ pub struct RefNode { pub supplement: Smart>, } -impl Show for RefNode { +impl Show for RefElem { fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult { if !vt.introspector.init() { return Ok(Content::empty()); @@ -72,7 +72,7 @@ impl Show for RefNode { let target = self.target(); let matches = vt.introspector.query(Selector::Label(self.target())); - if BibliographyNode::has(vt, &target.0) { + if BibliographyElem::has(vt, &target.0) { if !matches.is_empty() { bail!(self.span(), "label occurs in the document and its bibliography"); } @@ -80,7 +80,7 @@ impl Show for RefNode { return self.to_citation(styles).show(vt, styles); } - let [node] = matches.as_slice() else { + let [elem] = matches.as_slice() else { bail!(self.span(), if matches.is_empty() { "label does not exist in the document" } else { @@ -88,50 +88,50 @@ impl Show for RefNode { }); }; - if !node.can::() { - bail!(self.span(), "cannot reference {}", node.id().name); + if !elem.can::() { + bail!(self.span(), "cannot reference {}", elem.func().name()); } let supplement = self.supplement(styles); let mut supplement = match supplement { - Smart::Auto => node + Smart::Auto => elem .with::() - .map(|node| node.local_name(TextNode::lang_in(styles))) - .map(TextNode::packed) + .map(|elem| elem.local_name(TextElem::lang_in(styles))) + .map(TextElem::packed) .unwrap_or_default(), Smart::Custom(None) => Content::empty(), Smart::Custom(Some(Supplement::Content(content))) => content.clone(), Smart::Custom(Some(Supplement::Func(func))) => { - func.call_vt(vt, [node.clone().into()])?.display() + func.call_vt(vt, [elem.clone().into()])?.display() } }; if !supplement.is_empty() { - supplement += TextNode::packed('\u{a0}'); + supplement += TextElem::packed('\u{a0}'); } - let Some(numbering) = node.cast_field::("numbering") else { + let Some(numbering) = elem.cast_field::("numbering") else { bail!(self.span(), "only numbered elements can be referenced"); }; - let numbers = Counter::of(node.id()) - .at(vt, node.stable_id().unwrap())? + let numbers = Counter::of(elem.func()) + .at(vt, elem.location().unwrap())? .display(vt, &numbering.trimmed())?; - Ok((supplement + numbers).linked(Link::Node(node.stable_id().unwrap()))) + Ok((supplement + numbers).linked(Destination::Location(elem.location().unwrap()))) } } -impl RefNode { +impl RefElem { /// Turn the rference into a citation. - pub fn to_citation(&self, styles: StyleChain) -> CiteNode { - let mut node = CiteNode::new(vec![self.target().0]); - node.push_supplement(match self.supplement(styles) { + pub fn to_citation(&self, styles: StyleChain) -> CiteElem { + let mut elem = CiteElem::new(vec![self.target().0]); + elem.push_supplement(match self.supplement(styles) { Smart::Custom(Some(Supplement::Content(content))) => Some(content), _ => None, }); - node.0.set_stable_id(self.0.stable_id().unwrap()); - node + elem.0.set_location(self.0.location().unwrap()); + elem } } diff --git a/library/src/meta/state.rs b/library/src/meta/state.rs index b19a26716..090f1ccfd 100644 --- a/library/src/meta/state.rs +++ b/library/src/meta/state.rs @@ -52,25 +52,25 @@ impl State { /// Display the current value of the state. pub fn display(self, func: Option) -> Content { - DisplayNode::new(self, func).pack() + DisplayElem::new(self, func).pack() } /// Get the value of the state at the given location. - pub fn at(self, vt: &mut Vt, id: StableId) -> SourceResult { + pub fn at(self, vt: &mut Vt, location: Location) -> SourceResult { let sequence = self.sequence(vt)?; - let offset = vt.introspector.query_before(self.selector(), id).len(); + let offset = vt.introspector.query_before(self.selector(), location).len(); Ok(sequence[offset].clone()) } /// Get the value of the state at the final location. - pub fn final_(self, vt: &mut Vt, _: StableId) -> SourceResult { + pub fn final_(self, vt: &mut Vt, _: Location) -> SourceResult { let sequence = self.sequence(vt)?; Ok(sequence.last().unwrap().clone()) } /// Produce content that performs a state update. pub fn update(self, update: StateUpdate) -> Content { - UpdateNode::new(self, update).pack() + UpdateElem::new(self, update).pack() } /// Produce the whole sequence of states. @@ -99,9 +99,9 @@ impl State { let mut state = self.init.clone(); let mut stops = eco_vec![state.clone()]; - for node in introspector.query(self.selector()) { - let node = node.to::().unwrap(); - match node.update() { + for elem in introspector.query(self.selector()) { + let elem = elem.to::().unwrap(); + match elem.update() { StateUpdate::Set(value) => state = value, StateUpdate::Func(func) => state = func.call_vt(&mut vt, [state])?, } @@ -113,10 +113,7 @@ impl State { /// The selector for this state's updates. fn selector(&self) -> Selector { - Selector::Node( - NodeId::of::(), - Some(dict! { "state" => self.clone() }), - ) + Selector::Elem(UpdateElem::func(), Some(dict! { "state" => self.clone() })) } } @@ -159,8 +156,8 @@ cast_from_value! { /// /// Display: State /// Category: special -#[node(Locatable, Show)] -struct DisplayNode { +#[element(Locatable, Show)] +struct DisplayElem { /// The state. #[required] state: State, @@ -170,10 +167,10 @@ struct DisplayNode { func: Option, } -impl Show for DisplayNode { +impl Show for DisplayElem { fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult { - let id = self.0.stable_id().unwrap(); - let value = self.state().at(vt, id)?; + let location = self.0.location().unwrap(); + let value = self.state().at(vt, location)?; Ok(match self.func() { Some(func) => func.call_vt(vt, [value])?.display(), None => value.display(), @@ -185,8 +182,8 @@ impl Show for DisplayNode { /// /// Display: State /// Category: special -#[node(Locatable, Show)] -struct UpdateNode { +#[element(Locatable, Show)] +struct UpdateElem { /// The state. #[required] state: State, @@ -196,7 +193,7 @@ struct UpdateNode { update: StateUpdate, } -impl Show for UpdateNode { +impl Show for UpdateElem { fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult { Ok(Content::empty()) } diff --git a/library/src/prelude.rs b/library/src/prelude.rs index 55e5f17b4..4c83cf312 100644 --- a/library/src/prelude.rs +++ b/library/src/prelude.rs @@ -22,9 +22,9 @@ pub use typst::eval::{ pub use typst::geom::*; #[doc(no_inline)] pub use typst::model::{ - node, Behave, Behaviour, Construct, Content, Finalize, Fold, Introspector, Label, - Locatable, MetaNode, Node, NodeId, Resolve, Selector, Set, Show, StabilityProvider, - StableId, StyleChain, StyleMap, StyleVec, Synthesize, Unlabellable, Vt, + element, Behave, Behaviour, Construct, Content, ElemFunc, Element, Finalize, Fold, + Introspector, Label, Locatable, Location, MetaElem, Resolve, Selector, Set, Show, + StabilityProvider, StyleChain, StyleVec, Styles, Synthesize, Unlabellable, Vt, }; #[doc(no_inline)] pub use typst::syntax::{Span, Spanned}; @@ -36,4 +36,4 @@ pub use typst::World; #[doc(no_inline)] pub use crate::layout::{Fragment, Layout, Regions}; #[doc(no_inline)] -pub use crate::shared::{ContentExt, StyleMapExt}; +pub use crate::shared::{ContentExt, StylesExt}; diff --git a/library/src/shared/behave.rs b/library/src/shared/behave.rs index eff41c0bd..6a1aa1274 100644 --- a/library/src/shared/behave.rs +++ b/library/src/shared/behave.rs @@ -1,14 +1,15 @@ -//! Node interaction. +//! Element interaction. use typst::model::{Behave, Behaviour, Content, StyleChain, StyleVec, StyleVecBuilder}; -/// A wrapper around a [`StyleVecBuilder`] that allows items to interact. +/// A wrapper around a [`StyleVecBuilder`] that allows elements to interact. #[derive(Debug)] pub struct BehavedBuilder<'a> { /// The internal builder. builder: StyleVecBuilder<'a, Content>, - /// Staged weak and ignorant items that we can't yet commit to the builder. - /// The option is `Some(_)` for weak items and `None` for ignorant items. + /// Staged weak and ignorant elements that we can't yet commit to the + /// builder. The option is `Some(_)` for weak elements and `None` for + /// ignorant elements. staged: Vec<(Content, Behaviour, StyleChain<'a>)>, /// What the last non-ignorant item was. last: Behaviour, @@ -29,7 +30,7 @@ impl<'a> BehavedBuilder<'a> { self.builder.is_empty() && self.staged.is_empty() } - /// Whether the builder is empty except for some weak items that will + /// Whether the builder is empty except for some weak elements that will /// probably collapse. pub fn is_basically_empty(&self) -> bool { self.builder.is_empty() @@ -40,15 +41,15 @@ impl<'a> BehavedBuilder<'a> { } /// Push an item into the sequence. - pub fn push(&mut self, item: Content, styles: StyleChain<'a>) { - let interaction = item + pub fn push(&mut self, elem: Content, styles: StyleChain<'a>) { + let interaction = elem .with::() .map_or(Behaviour::Supportive, Behave::behaviour); match interaction { Behaviour::Weak(level) => { if matches!(self.last, Behaviour::Weak(_)) { - let item = item.with::().unwrap(); + let item = elem.with::().unwrap(); let i = self.staged.iter().position(|prev| { let Behaviour::Weak(prev_level) = prev.1 else { return false }; level < prev_level @@ -59,29 +60,29 @@ impl<'a> BehavedBuilder<'a> { } if self.last != Behaviour::Destructive { - self.staged.push((item, interaction, styles)); + self.staged.push((elem, interaction, styles)); self.last = interaction; } } Behaviour::Supportive => { self.flush(true); - self.builder.push(item, styles); + self.builder.push(elem, styles); self.last = interaction; } Behaviour::Destructive => { self.flush(false); - self.builder.push(item, styles); + self.builder.push(elem, styles); self.last = interaction; } Behaviour::Ignorant => { - self.staged.push((item, interaction, styles)); + self.staged.push((elem, interaction, styles)); } } } - /// Iterate over the contained items. - pub fn items(&self) -> impl DoubleEndedIterator { - self.builder.items().chain(self.staged.iter().map(|(item, ..)| item)) + /// Iterate over the contained elements. + pub fn elems(&self) -> impl DoubleEndedIterator { + self.builder.elems().chain(self.staged.iter().map(|(item, ..)| item)) } /// Return the finish style vec and the common prefix chain. @@ -90,7 +91,7 @@ impl<'a> BehavedBuilder<'a> { self.builder.finish() } - /// Push the staged items, filtering out weak items if `supportive` is + /// Push the staged elements, filtering out weak elements if `supportive` is /// false. fn flush(&mut self, supportive: bool) { for (item, interaction, styles) in self.staged.drain(..) { diff --git a/library/src/shared/ext.rs b/library/src/shared/ext.rs index 14674c9d0..72a82749b 100644 --- a/library/src/shared/ext.rs +++ b/library/src/shared/ext.rs @@ -1,8 +1,8 @@ //! Extension traits. -use crate::layout::{AlignNode, MoveNode, PadNode}; +use crate::layout::{AlignElem, MoveElem, PadElem}; use crate::prelude::*; -use crate::text::{EmphNode, FontFamily, FontList, StrongNode, TextNode, UnderlineNode}; +use crate::text::{EmphElem, FontFamily, FontList, StrongElem, TextElem, UnderlineElem}; /// Additional methods on content. pub trait ContentExt { @@ -16,7 +16,7 @@ pub trait ContentExt { fn underlined(self) -> Self; /// Link the content somewhere. - fn linked(self, link: Link) -> Self; + fn linked(self, dest: Destination) -> Self; /// Set alignments for this content. fn aligned(self, aligns: Axes>) -> Self; @@ -30,27 +30,27 @@ pub trait ContentExt { impl ContentExt for Content { fn strong(self) -> Self { - StrongNode::new(self).pack() + StrongElem::new(self).pack() } fn emph(self) -> Self { - EmphNode::new(self).pack() + EmphElem::new(self).pack() } fn underlined(self) -> Self { - UnderlineNode::new(self).pack() + UnderlineElem::new(self).pack() } - fn linked(self, link: Link) -> Self { - self.styled(MetaNode::set_data(vec![Meta::Link(link)])) + fn linked(self, dest: Destination) -> Self { + self.styled(MetaElem::set_data(vec![Meta::Link(dest)])) } fn aligned(self, aligns: Axes>) -> Self { - self.styled(AlignNode::set_alignment(aligns)) + self.styled(AlignElem::set_alignment(aligns)) } fn padded(self, padding: Sides>) -> Self { - PadNode::new(self) + PadElem::new(self) .with_left(padding.left) .with_top(padding.top) .with_right(padding.right) @@ -59,22 +59,22 @@ impl ContentExt for Content { } fn moved(self, delta: Axes>) -> Self { - MoveNode::new(self).with_dx(delta.x).with_dy(delta.y).pack() + MoveElem::new(self).with_dx(delta.x).with_dy(delta.y).pack() } } -/// Additional methods for style maps. -pub trait StyleMapExt { +/// Additional methods for style lists. +pub trait StylesExt { /// Set a font family composed of a preferred family and existing families /// from a style chain. fn set_family(&mut self, preferred: FontFamily, existing: StyleChain); } -impl StyleMapExt for StyleMap { +impl StylesExt for Styles { fn set_family(&mut self, preferred: FontFamily, existing: StyleChain) { - self.set(TextNode::set_font(FontList( + self.set(TextElem::set_font(FontList( std::iter::once(preferred) - .chain(TextNode::font_in(existing)) + .chain(TextElem::font_in(existing)) .collect(), ))); } diff --git a/library/src/symbols/emoji.rs b/library/src/symbols/emoji.rs index 07c7004b5..5db3a7993 100644 --- a/library/src/symbols/emoji.rs +++ b/library/src/symbols/emoji.rs @@ -993,7 +993,7 @@ const EMOJI: &[(&'static str, Symbol)] = symbols! { piano: '🎹', pick: '⛏', pie: '🥧', - pig: ['🐖', face: '🐷', node: '🐽'], + pig: ['🐖', face: '🐷', nose: '🐽'], pill: '💊', pin: ['📌', round: '📍'], pinata: '🪅', diff --git a/library/src/text/deco.rs b/library/src/text/deco.rs index a29564f3a..90a6ca857 100644 --- a/library/src/text/deco.rs +++ b/library/src/text/deco.rs @@ -1,7 +1,7 @@ use kurbo::{BezPath, Line, ParamCurve}; use ttf_parser::{GlyphId, OutlineBuilder}; -use super::TextNode; +use super::TextElem; use crate::prelude::*; /// Underline text. @@ -13,8 +13,8 @@ use crate::prelude::*; /// /// Display: Underline /// Category: text -#[node(Show)] -pub struct UnderlineNode { +#[element(Show)] +pub struct UnderlineElem { /// How to stroke the line. The text color and thickness are read from the /// font tables if `{auto}`. /// @@ -65,9 +65,9 @@ pub struct UnderlineNode { pub body: Content, } -impl Show for UnderlineNode { +impl Show for UnderlineElem { fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult { - Ok(self.body().styled(TextNode::set_deco(Decoration { + Ok(self.body().styled(TextElem::set_deco(Decoration { line: DecoLine::Underline, stroke: self.stroke(styles).unwrap_or_default(), offset: self.offset(styles), @@ -86,8 +86,8 @@ impl Show for UnderlineNode { /// /// Display: Overline /// Category: text -#[node(Show)] -pub struct OverlineNode { +#[element(Show)] +pub struct OverlineElem { /// How to stroke the line. The text color and thickness are read from the /// font tables if `{auto}`. /// @@ -144,9 +144,9 @@ pub struct OverlineNode { pub body: Content, } -impl Show for OverlineNode { +impl Show for OverlineElem { fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult { - Ok(self.body().styled(TextNode::set_deco(Decoration { + Ok(self.body().styled(TextElem::set_deco(Decoration { line: DecoLine::Overline, stroke: self.stroke(styles).unwrap_or_default(), offset: self.offset(styles), @@ -165,8 +165,8 @@ impl Show for OverlineNode { /// /// Display: Strikethrough /// Category: text -#[node(Show)] -pub struct StrikeNode { +#[element(Show)] +pub struct StrikeElem { /// How to stroke the line. The text color and thickness are read from the /// font tables if `{auto}`. /// @@ -208,9 +208,9 @@ pub struct StrikeNode { pub body: Content, } -impl Show for StrikeNode { +impl Show for StrikeElem { fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult { - Ok(self.body().styled(TextNode::set_deco(Decoration { + Ok(self.body().styled(TextElem::set_deco(Decoration { line: DecoLine::Strikethrough, stroke: self.stroke(styles).unwrap_or_default(), offset: self.offset(styles), @@ -255,7 +255,7 @@ pub enum DecoLine { pub(super) fn decorate( frame: &mut Frame, deco: &Decoration, - text: &Text, + text: &TextItem, shift: Abs, pos: Point, width: Abs, @@ -285,7 +285,7 @@ pub(super) fn decorate( if target.x >= min_width || !deco.evade { let shape = Geometry::Line(target).stroked(stroke); - frame.push(origin, Element::Shape(shape, Span::detached())); + frame.push(origin, FrameItem::Shape(shape, Span::detached())); } }; diff --git a/library/src/text/misc.rs b/library/src/text/misc.rs index 5a5c8514d..e1d9c0f28 100644 --- a/library/src/text/misc.rs +++ b/library/src/text/misc.rs @@ -1,20 +1,20 @@ -use super::TextNode; +use super::TextElem; use crate::prelude::*; /// A text space. /// /// Display: Space /// Category: text -#[node(Unlabellable, Behave)] -pub struct SpaceNode {} +#[element(Unlabellable, Behave)] +pub struct SpaceElem {} -impl Behave for SpaceNode { +impl Behave for SpaceElem { fn behaviour(&self) -> Behaviour { Behaviour::Weak(2) } } -impl Unlabellable for SpaceNode {} +impl Unlabellable for SpaceElem {} /// Inserts a line break. /// @@ -36,8 +36,8 @@ impl Unlabellable for SpaceNode {} /// /// Display: Line Break /// Category: text -#[node(Behave)] -pub struct LinebreakNode { +#[element(Behave)] +pub struct LinebreakElem { /// Whether to justify the line before the break. /// /// This is useful if you found a better line break opportunity in your @@ -55,7 +55,7 @@ pub struct LinebreakNode { pub justify: bool, } -impl Behave for LinebreakNode { +impl Behave for LinebreakElem { fn behaviour(&self) -> Behaviour { Behaviour::Destructive } @@ -82,8 +82,8 @@ impl Behave for LinebreakNode { /// /// Display: Strong Emphasis /// Category: text -#[node(Show)] -pub struct StrongNode { +#[element(Show)] +pub struct StrongElem { /// The delta to apply on the font weight. /// /// ```example @@ -98,9 +98,9 @@ pub struct StrongNode { pub body: Content, } -impl Show for StrongNode { +impl Show for StrongElem { fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult { - Ok(self.body().styled(TextNode::set_delta(Delta(self.delta(styles))))) + Ok(self.body().styled(TextElem::set_delta(Delta(self.delta(styles))))) } } @@ -151,16 +151,16 @@ impl Fold for Delta { /// /// Display: Emphasis /// Category: text -#[node(Show)] -pub struct EmphNode { +#[element(Show)] +pub struct EmphElem { /// The content to emphasize. #[required] pub body: Content, } -impl Show for EmphNode { +impl Show for EmphElem { fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult { - Ok(self.body().styled(TextNode::set_emph(Toggle))) + Ok(self.body().styled(TextElem::set_emph(Toggle))) } } @@ -229,7 +229,7 @@ pub fn upper( fn case(text: ToCase, case: Case) -> Value { match text { ToCase::Str(v) => Value::Str(case.apply(&v).into()), - ToCase::Content(v) => Value::Content(v.styled(TextNode::set_case(Some(case)))), + ToCase::Content(v) => Value::Content(v.styled(TextElem::set_case(Some(case)))), } } @@ -295,7 +295,7 @@ pub fn smallcaps( /// The text to display to small capitals. body: Content, ) -> Value { - Value::Content(body.styled(TextNode::set_smallcaps(true))) + Value::Content(body.styled(TextElem::set_smallcaps(true))) } /// Create blind text. diff --git a/library/src/text/mod.rs b/library/src/text/mod.rs index 845ffe294..16268aad8 100644 --- a/library/src/text/mod.rs +++ b/library/src/text/mod.rs @@ -19,7 +19,7 @@ use std::borrow::Cow; use rustybuzz::Tag; use typst::font::{FontMetrics, FontStretch, FontStyle, FontWeight, VerticalFontMetric}; -use crate::layout::ParNode; +use crate::layout::ParElem; use crate::prelude::*; /// Customize the look and layout of text in a variety of ways. @@ -40,8 +40,8 @@ use crate::prelude::*; /// /// Display: Text /// Category: text -#[node(Construct)] -pub struct TextNode { +#[element(Construct)] +pub struct TextElem { /// A prioritized sequence of font families. /// /// When processing text, Typst tries all specified font families in order @@ -291,7 +291,7 @@ pub struct TextNode { /// هذا عربي. /// ``` #[resolve] - pub dir: HorizontalDir, + pub dir: TextDir, /// Whether to hyphenate text to improve line breaking. When `{auto}`, text /// will be hyphenated if and only if justification is enabled. @@ -479,16 +479,16 @@ pub struct TextNode { pub smallcaps: bool, } -impl TextNode { - /// Create a new packed text node. +impl TextElem { + /// Create a new packed text element. pub fn packed(text: impl Into) -> Content { Self::new(text.into()).pack() } } -impl Construct for TextNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult { - // The text constructor is special: It doesn't create a text node. +impl Construct for TextElem { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { + // The text constructor is special: It doesn't create a text element. // Instead, it leaves the passed argument structurally unchanged, but // styles all text in it. let styles = Self::set(args)?; @@ -606,28 +606,28 @@ cast_to_value! { /// The direction of text and inline objects in their line. #[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] -pub struct HorizontalDir(pub Smart); +pub struct TextDir(pub Smart); cast_from_value! { - HorizontalDir, + TextDir, v: Smart => { if v.map_or(false, |dir| dir.axis() == Axis::Y) { - Err("must be horizontal")?; + Err("text direction must be horizontal")?; } Self(v) }, } cast_to_value! { - v: HorizontalDir => v.0.into() + v: TextDir => v.0.into() } -impl Resolve for HorizontalDir { +impl Resolve for TextDir { type Output = Dir; fn resolve(self, styles: StyleChain) -> Self::Output { match self.0 { - Smart::Auto => TextNode::lang_in(styles).dir(), + Smart::Auto => TextElem::lang_in(styles).dir(), Smart::Custom(dir) => dir, } } @@ -651,7 +651,7 @@ impl Resolve for Hyphenate { fn resolve(self, styles: StyleChain) -> Self::Output { match self.0 { - Smart::Auto => ParNode::justify_in(styles), + Smart::Auto => ParElem::justify_in(styles), Smart::Custom(v) => v, } } @@ -677,7 +677,7 @@ cast_from_value! { StylisticSet, v: i64 => match v { 1 ..= 20 => Self::new(v as u8), - _ => Err("must be between 1 and 20")?, + _ => Err("stylistic set must be between 1 and 20")?, }, } diff --git a/library/src/text/quotes.rs b/library/src/text/quotes.rs index 863cc3bde..be923304d 100644 --- a/library/src/text/quotes.rs +++ b/library/src/text/quotes.rs @@ -24,8 +24,8 @@ use crate::prelude::*; /// /// Display: Smart Quote /// Category: text -#[node] -pub struct SmartQuoteNode { +#[element] +pub struct SmartQuoteElem { /// Whether this should be a double quote. #[default(true)] pub double: bool, diff --git a/library/src/text/raw.rs b/library/src/text/raw.rs index b6cc0d3d7..d24254ed1 100644 --- a/library/src/text/raw.rs +++ b/library/src/text/raw.rs @@ -3,9 +3,9 @@ use syntect::highlighting as synt; use typst::syntax::{self, LinkedNode}; use super::{ - FontFamily, FontList, Hyphenate, LinebreakNode, SmartQuoteNode, TextNode, TextSize, + FontFamily, FontList, Hyphenate, LinebreakElem, SmartQuoteElem, TextElem, TextSize, }; -use crate::layout::BlockNode; +use crate::layout::BlockElem; use crate::prelude::*; /// Raw text with optional syntax highlighting. @@ -35,8 +35,8 @@ use crate::prelude::*; /// /// Display: Raw Text / Code /// Category: text -#[node(Synthesize, Show, Finalize)] -pub struct RawNode { +#[element(Synthesize, Show, Finalize)] +pub struct RawElem { /// The raw text. /// /// You can also use raw blocks creatively to create custom syntaxes for @@ -103,7 +103,7 @@ pub struct RawNode { pub lang: Option, } -impl RawNode { +impl RawElem { /// The supported language names and tags. pub fn languages() -> Vec<(&'static str, Vec<&'static str>)> { SYNTAXES @@ -120,13 +120,13 @@ impl RawNode { } } -impl Synthesize for RawNode { +impl Synthesize for RawElem { fn synthesize(&mut self, _: &Vt, styles: StyleChain) { self.push_lang(self.lang(styles)); } } -impl Show for RawNode { +impl Show for RawElem { fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult { let text = self.text(); let lang = self.lang(styles).as_ref().map(|s| s.to_lowercase()); @@ -162,7 +162,7 @@ impl Show for RawNode { let mut highlighter = syntect::easy::HighlightLines::new(syntax, &THEME); for (i, line) in text.lines().enumerate() { if i != 0 { - seq.push(LinebreakNode::new().pack()); + seq.push(LinebreakElem::new().pack()); } for (style, piece) in @@ -174,26 +174,27 @@ impl Show for RawNode { Content::sequence(seq) } else { - TextNode::packed(text) + TextElem::packed(text) }; if self.block(styles) { - realized = BlockNode::new().with_body(Some(realized)).pack(); + realized = BlockElem::new().with_body(Some(realized)).pack(); } Ok(realized) } } -impl Finalize for RawNode { +impl Finalize for RawElem { fn finalize(&self, realized: Content, _: StyleChain) -> Content { - let mut map = StyleMap::new(); - map.set(TextNode::set_overhang(false)); - map.set(TextNode::set_hyphenate(Hyphenate(Smart::Custom(false)))); - map.set(TextNode::set_size(TextSize(Em::new(0.8).into()))); - map.set(TextNode::set_font(FontList(vec![FontFamily::new("DejaVu Sans Mono")]))); - map.set(SmartQuoteNode::set_enabled(false)); - realized.styled_with_map(map) + let mut styles = Styles::new(); + styles.set(TextElem::set_overhang(false)); + styles.set(TextElem::set_hyphenate(Hyphenate(Smart::Custom(false)))); + styles.set(TextElem::set_size(TextSize(Em::new(0.8).into()))); + styles + .set(TextElem::set_font(FontList(vec![FontFamily::new("DejaVu Sans Mono")]))); + styles.set(SmartQuoteElem::set_enabled(false)); + realized.styled_with_map(styles) } } @@ -224,11 +225,11 @@ fn highlight_themed( /// Style a piece of text with a syntect style. fn styled(piece: &str, foreground: Paint, style: synt::Style) -> Content { - let mut body = TextNode::packed(piece); + let mut body = TextElem::packed(piece); let paint = to_typst(style.foreground).into(); if paint != foreground { - body = body.styled(TextNode::set_fill(paint)); + body = body.styled(TextElem::set_fill(paint)); } if style.font_style.contains(synt::FontStyle::BOLD) { diff --git a/library/src/text/shaping.rs b/library/src/text/shaping.rs index 244e7afe5..15fbcd3f2 100644 --- a/library/src/text/shaping.rs +++ b/library/src/text/shaping.rs @@ -95,10 +95,10 @@ impl<'a> ShapedText<'a> { let mut frame = Frame::new(size); frame.set_baseline(top); - let shift = TextNode::baseline_in(self.styles); - let lang = TextNode::lang_in(self.styles); - let decos = TextNode::deco_in(self.styles); - let fill = TextNode::fill_in(self.styles); + let shift = TextElem::baseline_in(self.styles); + let lang = TextElem::lang_in(self.styles); + let decos = TextElem::deco_in(self.styles); + let fill = TextElem::fill_in(self.styles); for ((font, y_offset), group) in self.glyphs.as_ref().group_by_key(|g| (g.font.clone(), g.y_offset)) @@ -122,16 +122,16 @@ impl<'a> ShapedText<'a> { }) .collect(); - let text = Text { font, size: self.size, lang, fill, glyphs }; - let text_layer = frame.layer(); - let width = text.width(); + let item = TextItem { font, size: self.size, lang, fill, glyphs }; + let layer = frame.layer(); + let width = item.width(); // Apply line decorations. for deco in &decos { - decorate(&mut frame, deco, &text, shift, pos, width); + decorate(&mut frame, deco, &item, shift, pos, width); } - frame.insert(text_layer, pos, Element::Text(text)); + frame.insert(layer, pos, FrameItem::Text(item)); offset += width; } @@ -146,8 +146,8 @@ impl<'a> ShapedText<'a> { let mut top = Abs::zero(); let mut bottom = Abs::zero(); - let top_edge = TextNode::top_edge_in(self.styles); - let bottom_edge = TextNode::bottom_edge_in(self.styles); + let top_edge = TextElem::top_edge_in(self.styles); + let bottom_edge = TextElem::bottom_edge_in(self.styles); // Expand top and bottom by reading the font's vertical metrics. let mut expand = |font: &Font| { @@ -343,7 +343,7 @@ pub fn shape<'a>( styles: StyleChain<'a>, dir: Dir, ) -> ShapedText<'a> { - let size = TextNode::size_in(styles); + let size = TextElem::size_in(styles); let mut ctx = ShapingContext { vt, base, @@ -354,7 +354,7 @@ pub fn shape<'a>( styles, variant: variant(styles), tags: tags(styles), - fallback: TextNode::fallback_in(styles), + fallback: TextElem::fallback_in(styles), dir, }; @@ -531,9 +531,9 @@ fn shape_tofus(ctx: &mut ShapingContext, base: usize, text: &str, font: Font) { /// Apply tracking and spacing to the shaped glyphs. fn track_and_space(ctx: &mut ShapingContext) { - let tracking = Em::from_length(TextNode::tracking_in(ctx.styles), ctx.size); + let tracking = Em::from_length(TextElem::tracking_in(ctx.styles), ctx.size); let spacing = - TextNode::spacing_in(ctx.styles).map(|abs| Em::from_length(abs, ctx.size)); + TextElem::spacing_in(ctx.styles).map(|abs| Em::from_length(abs, ctx.size)); let mut glyphs = ctx.glyphs.iter_mut().peekable(); while let Some(glyph) = glyphs.next() { @@ -562,17 +562,17 @@ fn nbsp_delta(font: &Font) -> Option { /// Resolve the font variant. pub fn variant(styles: StyleChain) -> FontVariant { let mut variant = FontVariant::new( - TextNode::style_in(styles), - TextNode::weight_in(styles), - TextNode::stretch_in(styles), + TextElem::style_in(styles), + TextElem::weight_in(styles), + TextElem::stretch_in(styles), ); - let delta = TextNode::delta_in(styles); + let delta = TextElem::delta_in(styles); variant.weight = variant .weight .thicken(delta.clamp(i16::MIN as i64, i16::MAX as i64) as i16); - if TextNode::emph_in(styles) { + if TextElem::emph_in(styles) { variant.style = match variant.style { FontStyle::Normal => FontStyle::Italic, FontStyle::Italic => FontStyle::Normal, @@ -593,8 +593,8 @@ pub fn families(styles: StyleChain) -> impl Iterator + Clone "segoe ui emoji", ]; - let tail = if TextNode::fallback_in(styles) { FALLBACKS } else { &[] }; - TextNode::font_in(styles) + let tail = if TextElem::fallback_in(styles) { FALLBACKS } else { &[] }; + TextElem::font_in(styles) .into_iter() .chain(tail.iter().copied().map(FontFamily::new)) } @@ -607,59 +607,59 @@ fn tags(styles: StyleChain) -> Vec { }; // Features that are on by default in Harfbuzz are only added if disabled. - if !TextNode::kerning_in(styles) { + if !TextElem::kerning_in(styles) { feat(b"kern", 0); } // Features that are off by default in Harfbuzz are only added if enabled. - if TextNode::smallcaps_in(styles) { + if TextElem::smallcaps_in(styles) { feat(b"smcp", 1); } - if TextNode::alternates_in(styles) { + if TextElem::alternates_in(styles) { feat(b"salt", 1); } let storage; - if let Some(set) = TextNode::stylistic_set_in(styles) { + if let Some(set) = TextElem::stylistic_set_in(styles) { storage = [b's', b's', b'0' + set.get() / 10, b'0' + set.get() % 10]; feat(&storage, 1); } - if !TextNode::ligatures_in(styles) { + if !TextElem::ligatures_in(styles) { feat(b"liga", 0); feat(b"clig", 0); } - if TextNode::discretionary_ligatures_in(styles) { + if TextElem::discretionary_ligatures_in(styles) { feat(b"dlig", 1); } - if TextNode::historical_ligatures_in(styles) { + if TextElem::historical_ligatures_in(styles) { feat(b"hilg", 1); } - match TextNode::number_type_in(styles) { + match TextElem::number_type_in(styles) { Smart::Auto => {} Smart::Custom(NumberType::Lining) => feat(b"lnum", 1), Smart::Custom(NumberType::OldStyle) => feat(b"onum", 1), } - match TextNode::number_width_in(styles) { + match TextElem::number_width_in(styles) { Smart::Auto => {} Smart::Custom(NumberWidth::Proportional) => feat(b"pnum", 1), Smart::Custom(NumberWidth::Tabular) => feat(b"tnum", 1), } - if TextNode::slashed_zero_in(styles) { + if TextElem::slashed_zero_in(styles) { feat(b"zero", 1); } - if TextNode::fractions_in(styles) { + if TextElem::fractions_in(styles) { feat(b"frac", 1); } - for (tag, value) in TextNode::features_in(styles).0 { + for (tag, value) in TextElem::features_in(styles).0 { tags.push(Feature::new(tag, value, ..)) } @@ -669,8 +669,8 @@ fn tags(styles: StyleChain) -> Vec { /// Process the language and and region of a style chain into a /// rustybuzz-compatible BCP 47 language. fn language(styles: StyleChain) -> rustybuzz::Language { - let mut bcp: EcoString = TextNode::lang_in(styles).as_str().into(); - if let Some(region) = TextNode::region_in(styles) { + let mut bcp: EcoString = TextElem::lang_in(styles).as_str().into(); + if let Some(region) = TextElem::region_in(styles) { bcp.push('-'); bcp.push_str(region.as_str()); } diff --git a/library/src/text/shift.rs b/library/src/text/shift.rs index 74bb70c75..1bea3673f 100644 --- a/library/src/text/shift.rs +++ b/library/src/text/shift.rs @@ -1,6 +1,4 @@ -use typst::model::SequenceNode; - -use super::{variant, SpaceNode, TextNode, TextSize}; +use super::{variant, SpaceElem, TextElem, TextSize}; use crate::prelude::*; /// Set text in subscript. @@ -14,8 +12,8 @@ use crate::prelude::*; /// /// Display: Subscript /// Category: text -#[node(Show)] -pub struct SubNode { +#[element(Show)] +pub struct SubElem { /// Whether to prefer the dedicated subscript characters of the font. /// /// If this is enabled, Typst first tries to transform the text to subscript @@ -46,21 +44,21 @@ pub struct SubNode { pub body: Content, } -impl Show for SubNode { +impl Show for SubElem { fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult { let body = self.body(); let mut transformed = None; if self.typographic(styles) { if let Some(text) = search_text(&body, true) { if is_shapable(vt, &text, styles) { - transformed = Some(TextNode::packed(text)); + transformed = Some(TextElem::packed(text)); } } }; Ok(transformed.unwrap_or_else(|| { - body.styled(TextNode::set_baseline(self.baseline(styles))) - .styled(TextNode::set_size(self.size(styles))) + body.styled(TextElem::set_baseline(self.baseline(styles))) + .styled(TextElem::set_size(self.size(styles))) })) } } @@ -76,8 +74,8 @@ impl Show for SubNode { /// /// Display: Superscript /// Category: text -#[node(Show)] -pub struct SuperNode { +#[element(Show)] +pub struct SuperElem { /// Whether to prefer the dedicated superscript characters of the font. /// /// If this is enabled, Typst first tries to transform the text to @@ -108,35 +106,35 @@ pub struct SuperNode { pub body: Content, } -impl Show for SuperNode { +impl Show for SuperElem { fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult { let body = self.body(); let mut transformed = None; if self.typographic(styles) { if let Some(text) = search_text(&body, false) { if is_shapable(vt, &text, styles) { - transformed = Some(TextNode::packed(text)); + transformed = Some(TextElem::packed(text)); } } }; Ok(transformed.unwrap_or_else(|| { - body.styled(TextNode::set_baseline(self.baseline(styles))) - .styled(TextNode::set_size(self.size(styles))) + body.styled(TextElem::set_baseline(self.baseline(styles))) + .styled(TextElem::set_size(self.size(styles))) })) } } /// Find and transform the text contained in `content` to the given script kind -/// if and only if it only consists of `Text`, `Space`, and `Empty` leaf nodes. +/// if and only if it only consists of `Text`, `Space`, and `Empty` leafs. fn search_text(content: &Content, sub: bool) -> Option { - if content.is::() { + if content.is::() { Some(' '.into()) - } else if let Some(node) = content.to::() { - convert_script(&node.text(), sub) - } else if let Some(seq) = content.to::() { + } else if let Some(elem) = content.to::() { + convert_script(&elem.text(), sub) + } else if let Some(children) = content.to_sequence() { let mut full = EcoString::new(); - for item in seq.children() { + for item in children { match search_text(&item, sub) { Some(text) => full.push_str(&text), None => return None, @@ -152,7 +150,7 @@ fn search_text(content: &Content, sub: bool) -> Option { /// given string. fn is_shapable(vt: &Vt, text: &str, styles: StyleChain) -> bool { let world = vt.world; - for family in TextNode::font_in(styles) { + for family in TextElem::font_in(styles) { if let Some(font) = world .book() .select(family.as_str(), variant(styles)) diff --git a/library/src/visualize/image.rs b/library/src/visualize/image.rs index c5016436c..8a81a40ea 100644 --- a/library/src/visualize/image.rs +++ b/library/src/visualize/image.rs @@ -22,8 +22,8 @@ use crate::prelude::*; /// /// Display: Image /// Category: visualize -#[node(Layout)] -pub struct ImageNode { +#[element(Layout)] +pub struct ImageElem { /// Path to an image file. #[required] #[parse( @@ -46,7 +46,7 @@ pub struct ImageNode { pub fit: ImageFit, } -impl Layout for ImageNode { +impl Layout for ImageElem { fn layout( &self, vt: &mut Vt, @@ -97,7 +97,7 @@ impl Layout for ImageNode { // the frame to the target size, center aligning the image in the // process. let mut frame = Frame::new(fitted); - frame.push(Point::zero(), Element::Image(image, fitted, self.span())); + frame.push(Point::zero(), FrameItem::Image(image, fitted, self.span())); frame.resize(target, Align::CENTER_HORIZON); // Create a clipping group if only part of the image should be visible. diff --git a/library/src/visualize/line.rs b/library/src/visualize/line.rs index 6614e3eef..0932a9f17 100644 --- a/library/src/visualize/line.rs +++ b/library/src/visualize/line.rs @@ -11,8 +11,8 @@ use crate::prelude::*; /// /// Display: Line /// Category: visualize -#[node(Layout)] -pub struct LineNode { +#[element(Layout)] +pub struct LineElem { /// The start point of the line. /// /// Must be an array of exactly two relative lengths. @@ -49,7 +49,7 @@ pub struct LineNode { pub stroke: PartialStroke, } -impl Layout for LineNode { +impl Layout for LineElem { fn layout( &self, _: &mut Vt, @@ -76,7 +76,7 @@ impl Layout for LineNode { let mut frame = Frame::new(target); let shape = Geometry::Line(delta.to_point()).stroked(stroke); - frame.push(start.to_point(), Element::Shape(shape, self.span())); + frame.push(start.to_point(), FrameItem::Shape(shape, self.span())); Ok(Fragment::frame(frame)) } } diff --git a/library/src/visualize/shape.rs b/library/src/visualize/shape.rs index 8aef3629f..601f0d85b 100644 --- a/library/src/visualize/shape.rs +++ b/library/src/visualize/shape.rs @@ -18,8 +18,8 @@ use crate::prelude::*; /// /// Display: Rectangle /// Category: visualize -#[node(Layout)] -pub struct RectNode { +#[element(Layout)] +pub struct RectElem { /// The rectangle's width, relative to its parent container. pub width: Smart>, @@ -139,7 +139,7 @@ pub struct RectNode { pub body: Option, } -impl Layout for RectNode { +impl Layout for RectElem { fn layout( &self, vt: &mut Vt, @@ -179,8 +179,8 @@ impl Layout for RectNode { /// /// Display: Square /// Category: visualize -#[node(Layout)] -pub struct SquareNode { +#[element(Layout)] +pub struct SquareElem { /// The square's side length. This is mutually exclusive with `width` and /// `height`. #[external] @@ -249,7 +249,7 @@ pub struct SquareNode { pub body: Option, } -impl Layout for SquareNode { +impl Layout for SquareElem { fn layout( &self, vt: &mut Vt, @@ -290,8 +290,8 @@ impl Layout for SquareNode { /// /// Display: Ellipse /// Category: visualize -#[node(Layout)] -pub struct EllipseNode { +#[element(Layout)] +pub struct EllipseElem { /// The ellipse's width, relative to its parent container. pub width: Smart>, @@ -331,7 +331,7 @@ pub struct EllipseNode { pub body: Option, } -impl Layout for EllipseNode { +impl Layout for EllipseElem { fn layout( &self, vt: &mut Vt, @@ -372,8 +372,8 @@ impl Layout for EllipseNode { /// /// Display: Circle /// Category: visualize -#[node(Layout)] -pub struct CircleNode { +#[element(Layout)] +pub struct CircleElem { /// The circle's radius. This is mutually exclusive with `width` and /// `height`. #[external] @@ -438,7 +438,7 @@ pub struct CircleNode { pub body: Option, } -impl Layout for CircleNode { +impl Layout for CircleElem { fn layout( &self, vt: &mut Vt, @@ -529,7 +529,7 @@ fn layout( let size = frame.size() + outset.sum_by_axis(); let pos = Point::new(-outset.left, -outset.top); let shape = ellipse(size, fill, stroke.left); - frame.prepend(pos, Element::Shape(shape, span)); + frame.prepend(pos, FrameItem::Shape(shape, span)); } else { frame.fill_and_stroke(fill, stroke, outset, radius, span); } diff --git a/macros/src/node.rs b/macros/src/element.rs similarity index 80% rename from macros/src/node.rs rename to macros/src/element.rs index 68d43d9c6..9d6b5c88a 100644 --- a/macros/src/node.rs +++ b/macros/src/element.rs @@ -1,12 +1,12 @@ use super::*; -/// Expand the `#[node]` macro. -pub fn node(stream: TokenStream, body: syn::ItemStruct) -> Result { - let node = prepare(stream, &body)?; - Ok(create(&node)) +/// Expand the `#[element]` macro. +pub fn element(stream: TokenStream, body: syn::ItemStruct) -> Result { + let element = prepare(stream, &body)?; + Ok(create(&element)) } -struct Node { +struct Elem { name: String, display: String, category: String, @@ -65,8 +65,8 @@ impl Parse for FieldParser { } } -/// Preprocess the node's definition. -fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result { +/// Preprocess the element's definition. +fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result { let syn::Fields::Named(named) = &body.fields else { bail!(body, "expected named fields"); }; @@ -143,8 +143,8 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result { let display = meta_line(&mut lines, "Display")?.into(); let docs = lines.join("\n").trim().into(); - let node = Node { - name: body.ident.to_string().trim_end_matches("Node").to_lowercase(), + let element = Elem { + name: body.ident.to_string().trim_end_matches("Elem").to_lowercase(), display, category, docs, @@ -155,17 +155,17 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result { }; validate_attrs(&body.attrs)?; - Ok(node) + Ok(element) } -/// Produce the node's definition. -fn create(node: &Node) -> TokenStream { - let Node { vis, ident, docs, .. } = node; - let all = node.fields.iter().filter(|field| !field.external); +/// Produce the element's definition. +fn create(element: &Elem) -> TokenStream { + let Elem { vis, ident, docs, .. } = element; + let all = element.fields.iter().filter(|field| !field.external); let settable = all.clone().filter(|field| !field.synthesized && field.settable()); // Inherent methods and functions. - let new = create_new_func(node); + let new = create_new_func(element); let field_methods = all.clone().map(create_field_method); let field_in_methods = settable.clone().map(create_field_in_method); let with_field_methods = all.clone().map(create_with_field_method); @@ -173,14 +173,14 @@ fn create(node: &Node) -> TokenStream { let field_style_methods = settable.map(create_set_field_method); // Trait implementations. - let node_impl = create_node_impl(node); - let construct_impl = node + let element_impl = create_pack_impl(element); + let construct_impl = element .capable .iter() .all(|capability| capability != "Construct") - .then(|| create_construct_impl(node)); - let set_impl = create_set_impl(node); - let locatable_impl = node + .then(|| create_construct_impl(element)); + let set_impl = create_set_impl(element); + let locatable_impl = element .capable .iter() .any(|capability| capability == "Locatable") @@ -200,13 +200,13 @@ fn create(node: &Node) -> TokenStream { #(#push_field_methods)* #(#field_style_methods)* - /// The node's span. + /// The element's span. pub fn span(&self) -> ::typst::syntax::Span { self.0.span() } } - #node_impl + #element_impl #construct_impl #set_impl #locatable_impl @@ -219,9 +219,9 @@ fn create(node: &Node) -> TokenStream { } } -/// Create the `new` function for the node. -fn create_new_func(node: &Node) -> TokenStream { - let relevant = node +/// Create the `new` function for the element. +fn create_new_func(element: &Elem) -> TokenStream { + let relevant = element .fields .iter() .filter(|field| !field.external && !field.synthesized && field.inherent()); @@ -232,9 +232,11 @@ fn create_new_func(node: &Node) -> TokenStream { quote! { .#with_ident(#ident) } }); quote! { - /// Create a new node. + /// Create a new element. pub fn new(#(#params),*) -> Self { - Self(::typst::model::Content::new(::id())) + Self(::typst::model::Content::new( + ::func() + )) #(#builder_calls)* } } @@ -252,8 +254,7 @@ fn create_field_method(field: &Field) -> TokenStream { } } } else { - let access = - create_style_chain_access(field, quote! { self.0.field(#name).cloned() }); + let access = create_style_chain_access(field, quote! { self.0.field(#name) }); quote! { #[doc = #docs] #vis fn #ident(&self, styles: ::typst::model::StyleChain) -> #output { @@ -288,7 +289,7 @@ fn create_style_chain_access(field: &Field, inherent: TokenStream) -> TokenStrea quote! { styles.#getter::<#ty>( - ::typst::model::NodeId::of::(), + ::func(), #name, #inherent, || #default, @@ -328,7 +329,7 @@ fn create_set_field_method(field: &Field) -> TokenStream { #[doc = #doc] #vis fn #set_ident(#ident: #ty) -> ::typst::model::Style { ::typst::model::Style::Property(::typst::model::Property::new( - ::typst::model::NodeId::of::(), + ::func(), #name.into(), #ident.into() )) @@ -336,19 +337,30 @@ fn create_set_field_method(field: &Field) -> TokenStream { } } -/// Create the node's `Node` implementation. -fn create_node_impl(node: &Node) -> TokenStream { - let Node { ident, name, display, category, docs, .. } = node; - let vtable_func = create_vtable_func(node); - let infos = node +/// Create the element's `Pack` implementation. +fn create_pack_impl(element: &Elem) -> TokenStream { + let Elem { ident, name, display, category, docs, .. } = element; + let vtable_func = create_vtable_func(element); + let infos = element .fields .iter() .filter(|field| !field.internal && !field.synthesized) .map(create_param_info); quote! { - impl ::typst::model::Node for #ident { - fn id() -> ::typst::model::NodeId { - static META: ::typst::model::NodeMeta = ::typst::model::NodeMeta { + impl ::typst::model::Element for #ident { + fn pack(self) -> ::typst::model::Content { + self.0 + } + + fn unpack(content: &::typst::model::Content) -> ::std::option::Option<&Self> { + // Safety: Elements are #[repr(transparent)]. + content.is::().then(|| unsafe { + ::std::mem::transmute(content) + }) + } + + fn func() -> ::typst::model::ElemFunc { + static NATIVE: ::typst::model::NativeElemFunc = ::typst::model::NativeElemFunc { name: #name, vtable: #vtable_func, construct: <#ident as ::typst::model::Construct>::construct, @@ -362,20 +374,16 @@ fn create_node_impl(node: &Node) -> TokenStream { category: #category, }), }; - ::typst::model::NodeId(&META) - } - - fn pack(self) -> ::typst::model::Content { - self.0 + (&NATIVE).into() } } } } -/// Create the node's casting vtable. -fn create_vtable_func(node: &Node) -> TokenStream { - let ident = &node.ident; - let relevant = node.capable.iter().filter(|&ident| ident != "Construct"); +/// Create the element's casting vtable. +fn create_vtable_func(element: &Elem) -> TokenStream { + let ident = &element.ident; + let relevant = element.capable.iter().filter(|&ident| ident != "Construct"); let checks = relevant.map(|capability| { quote! { if id == ::std::any::TypeId::of::() { @@ -388,7 +396,9 @@ fn create_vtable_func(node: &Node) -> TokenStream { quote! { |id| { - let null = Self(::typst::model::Content::new(<#ident as ::typst::model::Node>::id())); + let null = Self(::typst::model::Content::new( + <#ident as ::typst::model::Element>::func() + )); #(#checks)* None } @@ -421,10 +431,10 @@ fn create_param_info(field: &Field) -> TokenStream { } } -/// Create the node's `Construct` implementation. -fn create_construct_impl(node: &Node) -> TokenStream { - let ident = &node.ident; - let handlers = node +/// Create the element's `Construct` implementation. +fn create_construct_impl(element: &Elem) -> TokenStream { + let ident = &element.ident; + let handlers = element .fields .iter() .filter(|field| { @@ -439,13 +449,13 @@ fn create_construct_impl(node: &Node) -> TokenStream { quote! { #prefix if let Some(value) = #value { - node.#push_ident(value); + element.#push_ident(value); } } } else { quote! { #prefix - node.#push_ident(#value); + element.#push_ident(#value); } } }); @@ -453,21 +463,23 @@ fn create_construct_impl(node: &Node) -> TokenStream { quote! { impl ::typst::model::Construct for #ident { fn construct( - vm: &::typst::eval::Vm, + vm: &mut ::typst::eval::Vm, args: &mut ::typst::eval::Args, ) -> ::typst::diag::SourceResult<::typst::model::Content> { - let mut node = Self(::typst::model::Content::new(::id())); + let mut element = Self(::typst::model::Content::new( + ::func() + )); #(#handlers)* - Ok(node.0) + Ok(element.0) } } } } -/// Create the node's `Set` implementation. -fn create_set_impl(node: &Node) -> TokenStream { - let ident = &node.ident; - let handlers = node +/// Create the element's `Set` implementation. +fn create_set_impl(element: &Elem) -> TokenStream { + let ident = &element.ident; + let handlers = element .fields .iter() .filter(|field| { @@ -491,8 +503,8 @@ fn create_set_impl(node: &Node) -> TokenStream { impl ::typst::model::Set for #ident { fn set( args: &mut ::typst::eval::Args, - ) -> ::typst::diag::SourceResult<::typst::model::StyleMap> { - let mut styles = ::typst::model::StyleMap::new(); + ) -> ::typst::diag::SourceResult<::typst::model::Styles> { + let mut styles = ::typst::model::Styles::new(); #(#handlers)* Ok(styles) } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index fafe8eea1..945bbcd0d 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -5,8 +5,8 @@ extern crate proc_macro; #[macro_use] mod util; mod castable; +mod element; mod func; -mod node; mod symbols; use proc_macro::TokenStream as BoundaryStream; @@ -26,11 +26,11 @@ pub fn func(_: BoundaryStream, item: BoundaryStream) -> BoundaryStream { func::func(item).unwrap_or_else(|err| err.to_compile_error()).into() } -/// Implement `Node` for a struct. +/// Turns a struct into an element. #[proc_macro_attribute] -pub fn node(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream { +pub fn element(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream { let item = syn::parse_macro_input!(item as syn::ItemStruct); - node::node(stream.into(), item) + element::element(stream.into(), item) .unwrap_or_else(|err| err.to_compile_error()) .into() } diff --git a/src/doc.rs b/src/doc.rs index f575ff1f9..ebdca43ea 100644 --- a/src/doc.rs +++ b/src/doc.rs @@ -14,7 +14,7 @@ use crate::geom::{ Numeric, Paint, Point, Rel, RgbaColor, Shape, Sides, Size, Stroke, Transform, }; use crate::image::Image; -use crate::model::{Content, Introspector, MetaNode, StableId, StyleChain}; +use crate::model::{Content, Location, MetaElem, StyleChain}; use crate::syntax::Span; /// A finished document with metadata and page frames. @@ -28,7 +28,7 @@ pub struct Document { pub author: Vec, } -/// A finished layout with elements at fixed positions. +/// A finished layout with items at fixed positions. #[derive(Default, Clone, Hash)] pub struct Frame { /// The size of the frame. @@ -36,8 +36,8 @@ pub struct Frame { /// The baseline of the frame measured from the top. If this is `None`, the /// frame's implicit baseline is at the bottom. baseline: Option, - /// The elements composing this layout. - elements: Arc>, + /// The items composing this layout. + items: Arc>, } /// Constructor, accessors and setters. @@ -48,12 +48,12 @@ impl Frame { #[track_caller] pub fn new(size: Size) -> Self { assert!(size.is_finite()); - Self { size, baseline: None, elements: Arc::new(vec![]) } + Self { size, baseline: None, items: Arc::new(vec![]) } } - /// Whether the frame contains no elements. + /// Whether the frame contains no items. pub fn is_empty(&self) -> bool { - self.elements.is_empty() + self.items.is_empty() } /// The size of the frame. @@ -109,23 +109,23 @@ impl Frame { self.size.y - self.baseline() } - /// An iterator over the elements inside this frame alongside their - /// positions relative to the top-left of the frame. - pub fn elements(&self) -> std::slice::Iter<'_, (Point, Element)> { - self.elements.iter() + /// An iterator over the items inside this frame alongside their positions + /// relative to the top-left of the frame. + pub fn items(&self) -> std::slice::Iter<'_, (Point, FrameItem)> { + self.items.iter() } - /// Recover the text inside of the frame and its children. + /// Approximately recover the text inside of the frame and its children. pub fn text(&self) -> EcoString { let mut text = EcoString::new(); - for (_, element) in self.elements() { - match element { - Element::Text(element) => { - for glyph in &element.glyphs { + for (_, item) in self.items() { + match item { + FrameItem::Text(item) => { + for glyph in &item.glyphs { text.push(glyph.c); } } - Element::Group(group) => text.push_str(&group.frame.text()), + FrameItem::Group(group) => text.push_str(&group.frame.text()), _ => {} } } @@ -133,53 +133,53 @@ impl Frame { } } -/// Insert elements and subframes. +/// Insert items and subframes. impl Frame { /// The layer the next item will be added on. This corresponds to the number - /// of elements in the frame. + /// of items in the frame. pub fn layer(&self) -> usize { - self.elements.len() + self.items.len() } - /// Add an element at a position in the foreground. - pub fn push(&mut self, pos: Point, element: Element) { - Arc::make_mut(&mut self.elements).push((pos, element)); + /// Add an item at a position in the foreground. + pub fn push(&mut self, pos: Point, item: FrameItem) { + Arc::make_mut(&mut self.items).push((pos, item)); } /// Add a frame at a position in the foreground. /// /// Automatically decides whether to inline the frame or to include it as a - /// group based on the number of elements in it. + /// group based on the number of items in it. pub fn push_frame(&mut self, pos: Point, frame: Frame) { if self.should_inline(&frame) { self.inline(self.layer(), pos, frame); } else { - self.push(pos, Element::Group(Group::new(frame))); + self.push(pos, FrameItem::Group(GroupItem::new(frame))); } } - /// Insert an element at the given layer in the frame. + /// Insert an item at the given layer in the frame. /// /// This panics if the layer is greater than the number of layers present. #[track_caller] - pub fn insert(&mut self, layer: usize, pos: Point, element: Element) { - Arc::make_mut(&mut self.elements).insert(layer, (pos, element)); + pub fn insert(&mut self, layer: usize, pos: Point, items: FrameItem) { + Arc::make_mut(&mut self.items).insert(layer, (pos, items)); } - /// Add an element at a position in the background. - pub fn prepend(&mut self, pos: Point, element: Element) { - Arc::make_mut(&mut self.elements).insert(0, (pos, element)); + /// Add an item at a position in the background. + pub fn prepend(&mut self, pos: Point, item: FrameItem) { + Arc::make_mut(&mut self.items).insert(0, (pos, item)); } - /// Add multiple elements at a position in the background. + /// Add multiple items at a position in the background. /// - /// The first element in the iterator will be the one that is most in the + /// The first item in the iterator will be the one that is most in the /// background. - pub fn prepend_multiple(&mut self, elements: I) + pub fn prepend_multiple(&mut self, items: I) where - I: IntoIterator, + I: IntoIterator, { - Arc::make_mut(&mut self.elements).splice(0..0, elements); + Arc::make_mut(&mut self.items).splice(0..0, items); } /// Add a frame at a position in the background. @@ -187,31 +187,31 @@ impl Frame { if self.should_inline(&frame) { self.inline(0, pos, frame); } else { - self.prepend(pos, Element::Group(Group::new(frame))); + self.prepend(pos, FrameItem::Group(GroupItem::new(frame))); } } /// Whether the given frame should be inlined. fn should_inline(&self, frame: &Frame) -> bool { - self.elements.is_empty() || frame.elements.len() <= 5 + self.items.is_empty() || frame.items.len() <= 5 } /// Inline a frame at the given layer. fn inline(&mut self, layer: usize, pos: Point, frame: Frame) { - // Try to just reuse the elements. - if pos.is_zero() && self.elements.is_empty() { - self.elements = frame.elements; + // Try to just reuse the items. + if pos.is_zero() && self.items.is_empty() { + self.items = frame.items; return; } - // Try to transfer the elements without adjusting the position. - // Also try to reuse the elements if the Arc isn't shared. + // Try to transfer the items without adjusting the position. + // Also try to reuse the items if the Arc isn't shared. let range = layer..layer; if pos.is_zero() { - let sink = Arc::make_mut(&mut self.elements); - match Arc::try_unwrap(frame.elements) { - Ok(elements) => { - sink.splice(range, elements); + let sink = Arc::make_mut(&mut self.items); + match Arc::try_unwrap(frame.items) { + Ok(items) => { + sink.splice(range, items); } Err(arc) => { sink.splice(range, arc.iter().cloned()); @@ -220,12 +220,12 @@ impl Frame { return; } - // We must adjust the element positions. - // But still try to reuse the elements if the Arc isn't shared. - let sink = Arc::make_mut(&mut self.elements); - match Arc::try_unwrap(frame.elements) { - Ok(elements) => { - sink.splice(range, elements.into_iter().map(|(p, e)| (p + pos, e))); + // We have to adjust the item positions. + // But still try to reuse the items if the Arc isn't shared. + let sink = Arc::make_mut(&mut self.items); + match Arc::try_unwrap(frame.items) { + Ok(items) => { + sink.splice(range, items.into_iter().map(|(p, e)| (p + pos, e))); } Err(arc) => { sink.splice(range, arc.iter().cloned().map(|(p, e)| (p + pos, e))); @@ -236,12 +236,12 @@ impl Frame { /// Modify the frame. impl Frame { - /// Remove all elements from the frame. + /// Remove all items from the frame. pub fn clear(&mut self) { - if Arc::strong_count(&self.elements) == 1 { - Arc::make_mut(&mut self.elements).clear(); + if Arc::strong_count(&self.items) == 1 { + Arc::make_mut(&mut self.items).clear(); } else { - self.elements = Arc::new(vec![]); + self.items = Arc::new(vec![]); } } @@ -264,7 +264,7 @@ impl Frame { if let Some(baseline) = &mut self.baseline { *baseline += offset.y; } - for (point, _) in Arc::make_mut(&mut self.elements) { + for (point, _) in Arc::make_mut(&mut self.items) { *point += offset; } } @@ -273,12 +273,12 @@ impl Frame { /// Attach the metadata from this style chain to the frame. pub fn meta(&mut self, styles: StyleChain, force: bool) { if force || !self.is_empty() { - for meta in MetaNode::data_in(styles) { + for meta in MetaElem::data_in(styles) { if matches!(meta, Meta::Hide) { self.clear(); break; } - self.prepend(Point::zero(), Element::Meta(meta, self.size)); + self.prepend(Point::zero(), FrameItem::Meta(meta, self.size)); } } } @@ -287,7 +287,7 @@ impl Frame { pub fn fill(&mut self, fill: Paint) { self.prepend( Point::zero(), - Element::Shape(Geometry::Rect(self.size()).filled(fill), Span::detached()), + FrameItem::Shape(Geometry::Rect(self.size()).filled(fill), Span::detached()), ); } @@ -307,7 +307,7 @@ impl Frame { self.prepend_multiple( rounded_rect(size, radius, fill, stroke) .into_iter() - .map(|x| (pos, Element::Shape(x, span))), + .map(|x| (pos, FrameItem::Shape(x, span))), ) } @@ -328,13 +328,13 @@ impl Frame { /// Wrap the frame's contents in a group and modify that group with `f`. fn group(&mut self, f: F) where - F: FnOnce(&mut Group), + F: FnOnce(&mut GroupItem), { let mut wrapper = Frame::new(self.size); wrapper.baseline = self.baseline; - let mut group = Group::new(std::mem::take(self)); + let mut group = GroupItem::new(std::mem::take(self)); f(&mut group); - wrapper.push(Point::zero(), Element::Group(group)); + wrapper.push(Point::zero(), FrameItem::Group(group)); *self = wrapper; } } @@ -346,7 +346,7 @@ impl Frame { self.insert( 0, Point::zero(), - Element::Shape( + FrameItem::Shape( Geometry::Rect(self.size) .filled(RgbaColor { a: 100, ..Color::TEAL.to_rgba() }.into()), Span::detached(), @@ -355,7 +355,7 @@ impl Frame { self.insert( 1, Point::with_y(self.baseline()), - Element::Shape( + FrameItem::Shape( Geometry::Line(Point::with_x(self.size.x)).stroked(Stroke { paint: Color::RED.into(), thickness: Abs::pt(1.0), @@ -371,7 +371,7 @@ impl Frame { let radius = Abs::pt(2.0); self.push( pos - Point::splat(radius), - Element::Shape( + FrameItem::Shape( geom::ellipse(Size::splat(2.0 * radius), Some(Color::GREEN.into()), None), Span::detached(), ), @@ -382,7 +382,7 @@ impl Frame { pub fn mark_line(&mut self, y: Abs) { self.push( Point::with_y(y), - Element::Shape( + FrameItem::Shape( Geometry::Line(Point::with_x(self.size.x)).stroked(Stroke { paint: Color::GREEN.into(), thickness: Abs::pt(1.0), @@ -397,18 +397,18 @@ impl Debug for Frame { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.write_str("Frame ")?; f.debug_list() - .entries(self.elements.iter().map(|(_, element)| element)) + .entries(self.items.iter().map(|(_, item)| item)) .finish() } } /// The building block frames are composed of. #[derive(Clone, Hash)] -pub enum Element { - /// A group of elements. - Group(Group), +pub enum FrameItem { + /// A subframe with optional transformation and clipping. + Group(GroupItem), /// A run of shaped text. - Text(Text), + Text(TextItem), /// A geometric shape with optional fill and stroke. Shape(Shape, Span), /// An image and its size. @@ -417,7 +417,7 @@ pub enum Element { Meta(Meta, Size), } -impl Debug for Element { +impl Debug for FrameItem { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Self::Group(group) => group.fmt(f), @@ -429,9 +429,9 @@ impl Debug for Element { } } -/// A group of elements with optional clipping. +/// A subframe with optional transformation and clipping. #[derive(Clone, Hash)] -pub struct Group { +pub struct GroupItem { /// The group's frame. pub frame: Frame, /// A transformation to apply to the group. @@ -440,7 +440,7 @@ pub struct Group { pub clips: bool, } -impl Group { +impl GroupItem { /// Create a new group with default settings. pub fn new(frame: Frame) -> Self { Self { @@ -451,7 +451,7 @@ impl Group { } } -impl Debug for Group { +impl Debug for GroupItem { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.write_str("Group ")?; self.frame.fmt(f) @@ -460,7 +460,7 @@ impl Debug for Group { /// A run of shaped text. #[derive(Clone, Eq, PartialEq, Hash)] -pub struct Text { +pub struct TextItem { /// The font the glyphs are contained in. pub font: Font, /// The font size. @@ -473,14 +473,14 @@ pub struct Text { pub glyphs: Vec, } -impl Text { +impl TextItem { /// The width of the text run. pub fn width(&self) -> Abs { self.glyphs.iter().map(|g| g.x_advance).sum::().at(self.size) } } -impl Debug for Text { +impl Debug for TextItem { fn fmt(&self, f: &mut Formatter) -> fmt::Result { // This is only a rough approxmiation of the source text. f.write_str("Text(\"")?; @@ -595,97 +595,73 @@ cast_to_value! { } /// Meta information that isn't visible or renderable. -#[derive(Debug, Clone, Hash)] +#[derive(Debug, Clone, PartialEq, Hash)] pub enum Meta { - /// Indicates that the content should be hidden. + /// An internal or external link to a destination. + Link(Destination), + /// An identifiable element that produces something within the area this + /// metadata is attached to. + Elem(Content), + /// Indicates that content should be hidden. This variant doesn't appear + /// in the final frames as it is removed alongside the content that should + /// be hidden. Hide, - /// An internal or external link. - Link(Link), - /// An identifiable piece of content that produces something within the - /// area this metadata is attached to. - Node(Content), } cast_from_value! { Meta: "meta", } -impl PartialEq for Meta { - fn eq(&self, other: &Self) -> bool { - crate::util::hash128(self) == crate::util::hash128(other) - } -} - -/// A possibly unresolved link. -#[derive(Debug, Clone, Hash)] -pub enum Link { - /// A fully resolved. - Dest(Destination), - /// An unresolved link to a node. - Node(StableId), -} - -impl Link { - /// Resolve a destination. - /// - /// Needs to lazily provide an introspector. - pub fn resolve<'a>( - &self, - introspector: impl FnOnce() -> &'a Introspector, - ) -> Destination { - match self { - Self::Dest(dest) => dest.clone(), - Self::Node(id) => Destination::Internal(introspector().location(*id)), - } - } -} - /// A link destination. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum Destination { - /// A link to a point on a page. - Internal(Location), /// A link to a URL. Url(EcoString), + /// A link to a point on a page. + Position(Position), + /// An unresolved link to a location in the document. + Location(Location), } cast_from_value! { Destination, - loc: Location => Self::Internal(loc), - string: EcoString => Self::Url(string), + v: EcoString => Self::Url(v), + v: Position => Self::Position(v), + v: Location => Self::Location(v), } cast_to_value! { v: Destination => match v { - Destination::Internal(loc) => loc.into(), - Destination::Url(url) => url.into(), + Destination::Url(v) => v.into(), + Destination::Position(v) => v.into(), + Destination::Location(v) => v.into(), } } -/// A physical location in a document. +/// A physical position in a document. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Location { +pub struct Position { /// The page, starting at 1. pub page: NonZeroUsize, /// The exact coordinates on the page (from the top left, as usual). - pub pos: Point, + pub point: Point, } cast_from_value! { - Location, + Position, mut dict: Dict => { let page = dict.take("page")?.cast()?; let x: Length = dict.take("x")?.cast()?; let y: Length = dict.take("y")?.cast()?; dict.finish(&["page", "x", "y"])?; - Self { page, pos: Point::new(x.abs, y.abs) } + Self { page, point: Point::new(x.abs, y.abs) } }, } cast_to_value! { - v: Location => Value::Dict(dict! { + v: Position => Value::Dict(dict! { "page" => Value::Int(v.page.get() as i64), - "x" => Value::Length(v.pos.x.into()), - "y" => Value::Length(v.pos.y.into()), + "x" => Value::Length(v.point.x.into()), + "y" => Value::Length(v.point.y.into()), }) } diff --git a/src/eval/array.rs b/src/eval/array.rs index fa71ff1a5..bebbe8094 100644 --- a/src/eval/array.rs +++ b/src/eval/array.rs @@ -137,7 +137,7 @@ impl Array { self.0.contains(value) } - /// Return the first matching element. + /// Return the first matching item. pub fn find(&self, vm: &mut Vm, func: Func) -> SourceResult> { for item in self.iter() { let args = Args::new(func.span(), [item.clone()]); @@ -148,7 +148,7 @@ impl Array { Ok(None) } - /// Return the index of the first matching element. + /// Return the index of the first matching item. pub fn position(&self, vm: &mut Vm, func: Func) -> SourceResult> { for (i, item) in self.iter().enumerate() { let args = Args::new(func.span(), [item.clone()]); @@ -160,8 +160,8 @@ impl Array { Ok(None) } - /// Return a new array with only those elements for which the function - /// returns true. + /// Return a new array with only those items for which the function returns + /// true. pub fn filter(&self, vm: &mut Vm, func: Func) -> SourceResult { let mut kept = EcoVec::new(); for item in self.iter() { @@ -189,7 +189,7 @@ impl Array { .collect() } - /// Fold all of the array's elements into one with a function. + /// Fold all of the array's items into one with a function. pub fn fold(&self, vm: &mut Vm, init: Value, func: Func) -> SourceResult { let mut acc = init; for item in self.iter() { @@ -199,7 +199,7 @@ impl Array { Ok(acc) } - /// Whether any element matches. + /// Whether any item matches. pub fn any(&self, vm: &mut Vm, func: Func) -> SourceResult { for item in self.iter() { let args = Args::new(func.span(), [item.clone()]); @@ -211,7 +211,7 @@ impl Array { Ok(false) } - /// Whether all elements match. + /// Whether all items match. pub fn all(&self, vm: &mut Vm, func: Func) -> SourceResult { for item in self.iter() { let args = Args::new(func.span(), [item.clone()]); diff --git a/src/eval/func.rs b/src/eval/func.rs index 7bf1814fd..ef042d6d0 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -8,14 +8,12 @@ use comemo::{Prehashed, Track, Tracked, TrackedMut}; use once_cell::sync::Lazy; use super::{ - cast_to_value, Args, CastInfo, Dict, Eval, Flow, Route, Scope, Scopes, Tracer, Value, - Vm, + cast_to_value, Args, CastInfo, Eval, Flow, Route, Scope, Scopes, Tracer, Value, Vm, }; -use crate::diag::{bail, SourceResult, StrResult}; -use crate::model::{Introspector, NodeId, Selector, StabilityProvider, StyleMap, Vt}; +use crate::diag::{bail, SourceResult}; +use crate::model::{ElemFunc, Introspector, StabilityProvider, Vt}; use crate::syntax::ast::{self, AstNode, Expr, Ident}; use crate::syntax::{SourceId, Span, SyntaxNode}; -use crate::util::hash128; use crate::World; /// An evaluatable function. @@ -32,8 +30,8 @@ pub struct Func { enum Repr { /// A native Rust function. Native(NativeFunc), - /// A function for a node. - Node(NodeId), + /// A function for an element. + Elem(ElemFunc), /// A user-defined closure. Closure(Closure), /// A nested function with pre-applied arguments. @@ -45,7 +43,7 @@ impl Func { pub fn name(&self) -> Option<&str> { match &**self.repr { Repr::Native(native) => Some(native.info.name), - Repr::Node(node) => Some(node.info.name), + Repr::Elem(func) => Some(func.info().name), Repr::Closure(closure) => closure.name.as_deref(), Repr::With(func, _) => func.name(), } @@ -55,7 +53,7 @@ impl Func { pub fn info(&self) -> Option<&FuncInfo> { match &**self.repr { Repr::Native(native) => Some(&native.info), - Repr::Node(node) => Some(&node.info), + Repr::Elem(func) => Some(func.info()), Repr::With(func, _) => func.info(), _ => None, } @@ -93,8 +91,8 @@ impl Func { args.finish()?; Ok(value) } - Repr::Node(node) => { - let value = (node.construct)(vm, &mut args)?; + Repr::Elem(func) => { + let value = func.construct(vm, &mut args)?; args.finish()?; Ok(Value::Content(value)) } @@ -145,46 +143,13 @@ impl Func { } } - /// Create a selector for this function's node type, filtering by node's - /// whose [fields](super::Content::field) match the given arguments. - pub fn where_(self, args: &mut Args) -> StrResult { - let fields = args.to_named(); - args.items.retain(|arg| arg.name.is_none()); - self.select(Some(fields)) - } - - /// The node id of this function if it is an element function. - pub fn id(&self) -> Option { + /// Extract the element function, if it is one. + pub fn element(&self) -> Option { match **self.repr { - Repr::Node(id) => Some(id), + Repr::Elem(func) => Some(func), _ => None, } } - - /// Execute the function's set rule and return the resulting style map. - pub fn set(&self, mut args: Args) -> SourceResult { - Ok(match &**self.repr { - Repr::Node(node) => { - let styles = (node.set)(&mut args)?; - args.finish()?; - styles - } - _ => StyleMap::new(), - }) - } - - /// Create a selector for this function's node type. - pub fn select(&self, fields: Option) -> StrResult { - let Some(id) = self.id() else { - return Err("this function is not selectable".into()); - }; - - if id == item!(text_id) { - Err("to select text, please use a string or regex instead")?; - } - - Ok(Selector::Node(id, fields)) - } } impl Debug for Func { @@ -198,7 +163,7 @@ impl Debug for Func { impl PartialEq for Func { fn eq(&self, other: &Self) -> bool { - hash128(&self.repr) == hash128(&other.repr) + self.repr == other.repr } } @@ -211,13 +176,13 @@ impl From for Func { } } -impl From for Func { - fn from(id: NodeId) -> Self { - Repr::Node(id).into() +impl From for Func { + fn from(func: ElemFunc) -> Self { + Repr::Elem(func).into() } } -/// A native Rust function. +/// A Typst function defined by a native Rust function. pub struct NativeFunc { /// The function's implementation. pub func: fn(&mut Vm, &mut Args) -> SourceResult, diff --git a/src/eval/library.rs b/src/eval/library.rs index eae342c28..85d5647b9 100644 --- a/src/eval/library.rs +++ b/src/eval/library.rs @@ -10,7 +10,7 @@ use super::{Args, Dynamic, Module, Value, Vm}; use crate::diag::SourceResult; use crate::doc::Document; use crate::geom::{Abs, Dir}; -use crate::model::{Content, Introspector, Label, NodeId, StyleChain, StyleMap, Vt}; +use crate::model::{Content, ElemFunc, Introspector, Label, StyleChain, Styles, Vt}; use crate::syntax::Span; use crate::util::hash128; use crate::World; @@ -23,7 +23,7 @@ pub struct Library { /// The scope containing definitions available in math mode. pub math: Module, /// The default properties for page size, font selection and so on. - pub styles: StyleMap, + pub styles: Styles, /// Defines which standard library items fulfill which syntactical roles. pub items: LangItems, } @@ -44,9 +44,9 @@ pub struct LangItems { pub linebreak: fn() -> Content, /// Plain text without markup. pub text: fn(text: EcoString) -> Content, - /// The id of the text node. - pub text_id: NodeId, - /// Get the string if this is a text node. + /// The text function. + pub text_func: ElemFunc, + /// Get the string if this is a text element. pub text_str: fn(&Content) -> Option, /// A smart quote: `'` or `"`. pub smart_quote: fn(double: bool) -> Content, @@ -114,7 +114,7 @@ impl Hash for LangItems { self.space.hash(state); self.linebreak.hash(state); self.text.hash(state); - self.text_id.hash(state); + self.text_func.hash(state); (self.text_str as usize).hash(state); self.smart_quote.hash(state); self.parbreak.hash(state); @@ -140,13 +140,15 @@ impl Hash for LangItems { #[doc(hidden)] pub static LANG_ITEMS: OnceCell = OnceCell::new(); -/// Set the lang items. This is a hack :( +/// Set the lang items. /// -/// Passing the lang items everywhere they are needed (especially the text node -/// related things) is very painful. By storing them globally, in theory, we -/// break incremental, but only when different sets of lang items are used in -/// the same program. For this reason, if this function is called multiple -/// times, the items must be the same. +/// This is a hack :( +/// +/// Passing the lang items everywhere they are needed (especially text related +/// things) is very painful. By storing them globally, in theory, we break +/// incremental, but only when different sets of lang items are used in the same +/// program. For this reason, if this function is called multiple times, the +/// items must be the same (and this is enforced). pub fn set_lang_items(items: LangItems) { if let Err(items) = LANG_ITEMS.set(items) { let first = hash128(LANG_ITEMS.get().unwrap()); diff --git a/src/eval/methods.rs b/src/eval/methods.rs index 324191ab3..72245fb01 100644 --- a/src/eval/methods.rs +++ b/src/eval/methods.rs @@ -4,7 +4,7 @@ use ecow::EcoString; use super::{Args, Str, Value, Vm}; use crate::diag::{At, SourceResult}; -use crate::model::StableId; +use crate::model::Location; use crate::syntax::Span; /// Call a method on a value. @@ -71,12 +71,12 @@ pub fn call( }, Value::Content(content) => match method { - "func" => Value::Func(content.id().into()), + "func" => content.func().into(), "has" => Value::Bool(content.has(&args.expect::("field")?)), "at" => content.at(&args.expect::("field")?).at(span)?.clone(), - "id" => content - .stable_id() - .ok_or("this method can only be called on content returned by query()") + "location" => content + .location() + .ok_or("this method can only be called on content returned by query(..)") .at(span)? .into(), _ => return missing(), @@ -130,7 +130,16 @@ pub fn call( Value::Func(func) => match method { "with" => Value::Func(func.with(args.take())), - "where" => Value::dynamic(func.where_(&mut args).at(span)?), + "where" => { + let fields = args.to_named(); + args.items.retain(|arg| arg.name.is_none()); + Value::dynamic( + func.element() + .ok_or("`where()` can only be called on element functions") + .at(span)? + .where_(fields), + ) + } _ => return missing(), }, @@ -141,10 +150,10 @@ pub fn call( }, Value::Dyn(dynamic) => { - if let Some(&id) = dynamic.downcast::() { + if let Some(&location) = dynamic.downcast::() { match method { - "page" => vm.vt.introspector.page(id).into(), - "location" => vm.vt.introspector.location(id).into(), + "page" => vm.vt.introspector.page(location).into(), + "position" => vm.vt.introspector.position(location).into(), _ => return missing(), } } else { @@ -263,7 +272,7 @@ pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] { ("starts-with", true), ("trim", true), ], - "content" => &[("func", false), ("has", true), ("at", true), ("id", false)], + "content" => &[("func", false), ("has", true), ("at", true), ("location", false)], "array" => &[ ("all", true), ("any", true), @@ -299,7 +308,7 @@ pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] { ], "function" => &[("where", true), ("with", true)], "arguments" => &[("named", false), ("pos", false)], - "stable id" => &[("page", false), ("location", false)], + "location" => &[("page", false), ("position", false)], "counter" => &[ ("display", true), ("at", true), diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 74c5f0b3b..f19e43058 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -29,13 +29,14 @@ pub use self::cast::*; pub use self::dict::*; pub use self::func::*; pub use self::library::*; -pub use self::methods::*; pub use self::module::*; pub use self::scope::*; pub use self::str::*; pub use self::symbol::*; pub use self::value::*; +pub(crate) use self::methods::methods_on; + use std::collections::BTreeMap; use std::mem; use std::path::{Path, PathBuf}; @@ -47,11 +48,10 @@ use unicode_segmentation::UnicodeSegmentation; use crate::diag::{ bail, error, At, SourceError, SourceResult, StrResult, Trace, Tracepoint, }; -use crate::model::Introspector; -use crate::model::StabilityProvider; -use crate::model::Unlabellable; -use crate::model::Vt; -use crate::model::{Content, Label, Recipe, Selector, StyleMap, Transform}; +use crate::model::{ + Content, Introspector, Label, Recipe, Selector, StabilityProvider, Styles, Transform, + Unlabellable, Vt, +}; use crate::syntax::ast::AstNode; use crate::syntax::{ ast, parse_code, Source, SourceId, Span, Spanned, SyntaxKind, SyntaxNode, @@ -114,12 +114,12 @@ pub fn eval( /// /// Everything in the output is associated with the given `span`. #[comemo::memoize] -pub fn eval_code_str( +pub fn eval_string( world: Tracked, - text: &str, + code: &str, span: Span, ) -> SourceResult { - let mut root = parse_code(text); + let mut root = parse_code(code); root.synthesize(span); let errors = root.errors(); @@ -290,7 +290,7 @@ impl Route { } } -/// Traces which values existed for the expression with the given span. +/// Traces which values existed for the expression at a span. #[derive(Default, Clone)] pub struct Tracer { span: Option, @@ -377,10 +377,10 @@ fn eval_markup( } expr => match expr.eval(vm)? { Value::Label(label) => { - if let Some(node) = + if let Some(elem) = seq.iter_mut().rev().find(|node| !node.can::()) { - *node = mem::take(node).labelled(label); + *elem = mem::take(elem).labelled(label); } } value => seq.push(value.display().spanned(expr.span())), @@ -643,7 +643,7 @@ impl Eval for ast::Math { Ok(Content::sequence( self.exprs() .map(|expr| expr.eval_display(vm)) - .collect::>()?, + .collect::>>()?, )) } } @@ -1049,7 +1049,7 @@ impl Eval for ast::FuncCall { if in_math && !matches!(callee, Value::Func(_)) { if let Value::Symbol(sym) = &callee { let c = sym.get(); - if let Some(accent) = combining_accent(c) { + if let Some(accent) = Symbol::combining_accent(c) { let base = args.expect("base")?; args.finish()?; return Ok(Value::Content((vm.items.math_accent)(base, accent))); @@ -1198,17 +1198,25 @@ impl Eval for ast::LetBinding { } impl Eval for ast::SetRule { - type Output = StyleMap; + type Output = Styles; fn eval(&self, vm: &mut Vm) -> SourceResult { if let Some(condition) = self.condition() { if !condition.eval(vm)?.cast::().at(condition.span())? { - return Ok(StyleMap::new()); + return Ok(Styles::new()); } } let target = self.target(); - let target = target.eval(vm)?.cast::().at(target.span())?; + let target = target + .eval(vm)? + .cast::() + .and_then(|func| { + func.element().ok_or_else(|| { + "only element functions can be used in set rules".into() + }) + }) + .at(target.span())?; let args = self.args().eval(vm)?; Ok(target.set(args)?.spanned(self.span())) } diff --git a/src/eval/scope.rs b/src/eval/scope.rs index d4338b5ce..e241cac59 100644 --- a/src/eval/scope.rs +++ b/src/eval/scope.rs @@ -163,7 +163,9 @@ impl Slot { fn write(&mut self) -> StrResult<&mut Value> { match self.kind { Kind::Normal => Ok(&mut self.value), - Kind::Captured => Err("cannot mutate a captured variable")?, + Kind::Captured => { + Err("variables from outside the function are read-only and cannot be modified")? + } } } } diff --git a/src/eval/symbol.rs b/src/eval/symbol.rs index 73c410678..6a199a1d7 100644 --- a/src/eval/symbol.rs +++ b/src/eval/symbol.rs @@ -1,94 +1,127 @@ use std::cmp::Reverse; use std::collections::BTreeSet; use std::fmt::{self, Debug, Display, Formatter, Write}; +use std::sync::Arc; -use ecow::{EcoString, EcoVec}; +use ecow::EcoString; use crate::diag::StrResult; #[doc(inline)] pub use typst_macros::symbols; -/// A symbol. +/// A symbol, possibly with variants. #[derive(Clone, Eq, PartialEq, Hash)] -pub struct Symbol { - repr: Repr, - modifiers: EcoString, +pub struct Symbol(Repr); + +/// The internal representation. +#[derive(Clone, Eq, PartialEq, Hash)] +enum Repr { + Single(char), + Const(&'static [(&'static str, char)]), + Multi(Arc<(List, EcoString)>), } /// A collection of symbols. #[derive(Clone, Eq, PartialEq, Hash)] -enum Repr { - Single(char), +enum List { Static(&'static [(&'static str, char)]), - Runtime(EcoVec<(EcoString, char)>), + Runtime(Box<[(EcoString, char)]>), } impl Symbol { /// Create a new symbol from a single character. pub const fn new(c: char) -> Self { - Self { repr: Repr::Single(c), modifiers: EcoString::new() } + Self(Repr::Single(c)) } /// Create a symbol with a static variant list. #[track_caller] pub const fn list(list: &'static [(&'static str, char)]) -> Self { debug_assert!(!list.is_empty()); - Self { - repr: Repr::Static(list), - modifiers: EcoString::new(), - } + Self(Repr::Const(list)) } /// Create a symbol with a runtime variant list. #[track_caller] - pub fn runtime(list: EcoVec<(EcoString, char)>) -> Self { + pub fn runtime(list: Box<[(EcoString, char)]>) -> Self { debug_assert!(!list.is_empty()); - Self { - repr: Repr::Runtime(list), - modifiers: EcoString::new(), - } + Self(Repr::Multi(Arc::new((List::Runtime(list), EcoString::new())))) } /// Get the symbol's text. pub fn get(&self) -> char { - match self.repr { - Repr::Single(c) => c, - _ => find(self.variants(), &self.modifiers).unwrap(), + match &self.0 { + Repr::Single(c) => *c, + Repr::Const(_) => find(self.variants(), "").unwrap(), + Repr::Multi(arc) => find(self.variants(), &arc.1).unwrap(), } } /// Apply a modifier to the symbol. pub fn modified(mut self, modifier: &str) -> StrResult { - if !self.modifiers.is_empty() { - self.modifiers.push('.'); + if let Repr::Const(list) = self.0 { + self.0 = Repr::Multi(Arc::new((List::Static(list), EcoString::new()))); } - self.modifiers.push_str(modifier); - if find(self.variants(), &self.modifiers).is_none() { - Err("unknown modifier")? + + if let Repr::Multi(arc) = &mut self.0 { + let (list, modifiers) = Arc::make_mut(arc); + if !modifiers.is_empty() { + modifiers.push('.'); + } + modifiers.push_str(modifier); + if find(list.variants(), &modifiers).is_some() { + return Ok(self); + } } - Ok(self) + + Err("unknown symbol modifier".into()) } /// The characters that are covered by this symbol. pub fn variants(&self) -> impl Iterator { - match &self.repr { + match &self.0 { Repr::Single(c) => Variants::Single(Some(*c).into_iter()), - Repr::Static(list) => Variants::Static(list.iter()), - Repr::Runtime(list) => Variants::Runtime(list.iter()), + Repr::Const(list) => Variants::Static(list.iter()), + Repr::Multi(arc) => arc.0.variants(), } } /// Possible modifiers. pub fn modifiers(&self) -> impl Iterator + '_ { let mut set = BTreeSet::new(); + let modifiers = match &self.0 { + Repr::Multi(arc) => arc.1.as_str(), + _ => "", + }; for modifier in self.variants().flat_map(|(name, _)| name.split('.')) { - if !modifier.is_empty() && !contained(&self.modifiers, modifier) { + if !modifier.is_empty() && !contained(modifiers, modifier) { set.insert(modifier); } } set.into_iter() } + + /// Normalize an accent to a combining one. + pub fn combining_accent(c: char) -> Option { + Some(match c { + '\u{0300}' | '`' => '\u{0300}', + '\u{0301}' | '´' => '\u{0301}', + '\u{0302}' | '^' | 'ˆ' => '\u{0302}', + '\u{0303}' | '~' | '∼' | '˜' => '\u{0303}', + '\u{0304}' | '¯' => '\u{0304}', + '\u{0305}' | '-' | '‾' | '−' => '\u{0305}', + '\u{0306}' | '˘' => '\u{0306}', + '\u{0307}' | '.' | '˙' | '⋅' => '\u{0307}', + '\u{0308}' | '¨' => '\u{0308}', + '\u{030a}' | '∘' | '○' => '\u{030a}', + '\u{030b}' | '˝' => '\u{030b}', + '\u{030c}' | 'ˇ' => '\u{030c}', + '\u{20d6}' | '←' => '\u{20d6}', + '\u{20d7}' | '→' | '⟶' => '\u{20d7}', + _ => return None, + }) + } } impl Debug for Symbol { @@ -103,6 +136,16 @@ impl Display for Symbol { } } +impl List { + /// The characters that are covered by this list. + fn variants(&self) -> Variants<'_> { + match self { + List::Static(list) => Variants::Static(list.iter()), + List::Runtime(list) => Variants::Runtime(list.iter()), + } + } +} + /// Iterator over variants. enum Variants<'a> { Single(std::option::IntoIter), @@ -166,24 +209,3 @@ fn parts(modifiers: &str) -> impl Iterator { fn contained(modifiers: &str, m: &str) -> bool { parts(modifiers).any(|part| part == m) } - -/// Normalize an accent to a combining one. -pub fn combining_accent(c: char) -> Option { - Some(match c { - '\u{0300}' | '`' => '\u{0300}', - '\u{0301}' | '´' => '\u{0301}', - '\u{0302}' | '^' | 'ˆ' => '\u{0302}', - '\u{0303}' | '~' | '∼' | '˜' => '\u{0303}', - '\u{0304}' | '¯' => '\u{0304}', - '\u{0305}' | '-' | '‾' | '−' => '\u{0305}', - '\u{0306}' | '˘' => '\u{0306}', - '\u{0307}' | '.' | '˙' | '⋅' => '\u{0307}', - '\u{0308}' | '¨' => '\u{0308}', - '\u{030a}' | '∘' | '○' => '\u{030a}', - '\u{030b}' | '˝' => '\u{030b}', - '\u{030c}' | 'ˇ' => '\u{030c}', - '\u{20d6}' | '←' => '\u{20d6}', - '\u{20d7}' | '→' | '⟶' => '\u{20d7}', - _ => return None, - }) -} diff --git a/src/eval/value.rs b/src/eval/value.rs index 61af36f5a..ce9c4e0e0 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -13,6 +13,7 @@ use super::{ }; use crate::diag::StrResult; use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel}; +use crate::model::Styles; use crate::syntax::{ast, Span}; /// A computational value. @@ -48,6 +49,8 @@ pub enum Value { Label(Label), /// A content value: `[*Hi* there]`. Content(Content), + // Content styles. + Styles(Styles), /// An array of values: `(1, "hi", 12cm)`. Array(Array), /// A dictionary value: `(color: #f79143, pattern: dashed)`. @@ -101,6 +104,7 @@ impl Value { Self::Str(_) => Str::TYPE_NAME, Self::Label(_) => Label::TYPE_NAME, Self::Content(_) => Content::TYPE_NAME, + Self::Styles(_) => Styles::TYPE_NAME, Self::Array(_) => Array::TYPE_NAME, Self::Dict(_) => Dict::TYPE_NAME, Self::Func(_) => Func::TYPE_NAME, @@ -120,7 +124,7 @@ impl Value { match self { Self::Symbol(symbol) => symbol.clone().modified(&field).map(Self::Symbol), Self::Dict(dict) => dict.at(&field).cloned(), - Self::Content(content) => content.at(&field).cloned(), + Self::Content(content) => content.at(&field), Self::Module(module) => module.get(&field).cloned(), v => Err(eco_format!("cannot access fields on type {}", v.type_name())), } @@ -188,6 +192,7 @@ impl Debug for Value { Self::Str(v) => Debug::fmt(v, f), Self::Label(v) => Debug::fmt(v, f), Self::Content(v) => Debug::fmt(v, f), + Self::Styles(v) => Debug::fmt(v, f), Self::Array(v) => Debug::fmt(v, f), Self::Dict(v) => Debug::fmt(v, f), Self::Func(v) => Debug::fmt(v, f), @@ -229,6 +234,7 @@ impl Hash for Value { Self::Str(v) => v.hash(state), Self::Label(v) => v.hash(state), Self::Content(v) => v.hash(state), + Self::Styles(v) => v.hash(state), Self::Array(v) => v.hash(state), Self::Dict(v) => v.hash(state), Self::Func(v) => v.hash(state), @@ -400,6 +406,7 @@ primitive! { Content: "content", Symbol(v) => item!(text)(v.get().into()), Str(v) => item!(text)(v.into()) } +primitive! { Styles: "styles", Styles } primitive! { Array: "array", Array } primitive! { Dict: "dictionary", Dict } primitive! { Func: "function", Func } diff --git a/src/export/pdf/page.rs b/src/export/pdf/page.rs index 5347d831a..f3c81cb87 100644 --- a/src/export/pdf/page.rs +++ b/src/export/pdf/page.rs @@ -4,7 +4,7 @@ use pdf_writer::writers::ColorSpace; use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str}; use super::{deflate, AbsExt, EmExt, PdfContext, RefExt, D65_GRAY, SRGB}; -use crate::doc::{Destination, Element, Frame, Group, Link, Meta, Text}; +use crate::doc::{Destination, Frame, FrameItem, GroupItem, Meta, TextItem}; use crate::font::Font; use crate::geom::{ self, Abs, Color, Em, Geometry, Numeric, Paint, Point, Ratio, Shape, Size, Stroke, @@ -110,29 +110,32 @@ fn write_page(ctx: &mut PdfContext, page: Page) { page_writer.contents(content_id); let mut annotations = page_writer.annotations(); - for (link, rect) in page.links { + for (dest, rect) in page.links { let mut annotation = annotations.push(); annotation.subtype(AnnotationType::Link).rect(rect); annotation.border(0.0, 0.0, 0.0, None); - match link.resolve(|| &ctx.introspector) { + + let pos = match dest { Destination::Url(uri) => { annotation .action() .action_type(ActionType::Uri) .uri(Str(uri.as_bytes())); + continue; } - Destination::Internal(loc) => { - let index = loc.page.get() - 1; - let y = (loc.pos.y - Abs::pt(10.0)).max(Abs::zero()); - if let Some(&height) = ctx.page_heights.get(index) { - annotation - .action() - .action_type(ActionType::GoTo) - .destination_direct() - .page(ctx.page_refs[index]) - .xyz(loc.pos.x.to_f32(), height - y.to_f32(), None); - } - } + Destination::Position(pos) => pos, + Destination::Location(loc) => ctx.introspector.position(loc), + }; + + let index = pos.page.get() - 1; + let y = (pos.point.y - Abs::pt(10.0)).max(Abs::zero()); + if let Some(&height) = ctx.page_heights.get(index) { + annotation + .action() + .action_type(ActionType::GoTo) + .destination_direct() + .page(ctx.page_refs[index]) + .xyz(pos.point.x.to_f32(), height - y.to_f32(), None); } } @@ -153,7 +156,7 @@ pub struct Page { /// The page's content stream. pub content: Content, /// Links in the PDF coordinate system. - pub links: Vec<(Link, Rect)>, + pub links: Vec<(Destination, Rect)>, } /// An exporter for the contents of a single PDF page. @@ -164,7 +167,7 @@ struct PageContext<'a, 'b> { state: State, saves: Vec, bottom: f32, - links: Vec<(Link, Rect)>, + links: Vec<(Destination, Rect)>, } /// A simulated graphics state used to deduplicate graphics state changes and @@ -283,17 +286,17 @@ impl PageContext<'_, '_> { /// Encode a frame into the content stream. fn write_frame(ctx: &mut PageContext, frame: &Frame) { - for &(pos, ref element) in frame.elements() { + for &(pos, ref item) in frame.items() { let x = pos.x.to_f32(); let y = pos.y.to_f32(); - match element { - Element::Group(group) => write_group(ctx, pos, group), - Element::Text(text) => write_text(ctx, x, y, text), - Element::Shape(shape, _) => write_shape(ctx, x, y, shape), - Element::Image(image, size, _) => write_image(ctx, x, y, image, *size), - Element::Meta(meta, size) => match meta { - Meta::Link(link) => write_link(ctx, pos, link, *size), - Meta::Node(_) => {} + match item { + FrameItem::Group(group) => write_group(ctx, pos, group), + FrameItem::Text(text) => write_text(ctx, x, y, text), + FrameItem::Shape(shape, _) => write_shape(ctx, x, y, shape), + FrameItem::Image(image, size, _) => write_image(ctx, x, y, image, *size), + FrameItem::Meta(meta, size) => match meta { + Meta::Link(dest) => write_link(ctx, pos, dest, *size), + Meta::Elem(_) => {} Meta::Hide => {} }, } @@ -301,7 +304,7 @@ fn write_frame(ctx: &mut PageContext, frame: &Frame) { } /// Encode a group into the content stream. -fn write_group(ctx: &mut PageContext, pos: Point, group: &Group) { +fn write_group(ctx: &mut PageContext, pos: Point, group: &GroupItem) { let translation = Transform::translate(pos.x, pos.y); ctx.save_state(); @@ -324,7 +327,7 @@ fn write_group(ctx: &mut PageContext, pos: Point, group: &Group) { } /// Encode a text run into the content stream. -fn write_text(ctx: &mut PageContext, x: f32, y: f32, text: &Text) { +fn write_text(ctx: &mut PageContext, x: f32, y: f32, text: &TextItem) { *ctx.parent.languages.entry(text.lang).or_insert(0) += text.glyphs.len(); ctx.parent .glyph_sets @@ -422,13 +425,13 @@ fn write_shape(ctx: &mut PageContext, x: f32, y: f32, shape: &Shape) { fn write_path(ctx: &mut PageContext, x: f32, y: f32, path: &geom::Path) { for elem in &path.0 { match elem { - geom::PathElement::MoveTo(p) => { + geom::PathItem::MoveTo(p) => { ctx.content.move_to(x + p.x.to_f32(), y + p.y.to_f32()) } - geom::PathElement::LineTo(p) => { + geom::PathItem::LineTo(p) => { ctx.content.line_to(x + p.x.to_f32(), y + p.y.to_f32()) } - geom::PathElement::CubicTo(p1, p2, p3) => ctx.content.cubic_to( + geom::PathItem::CubicTo(p1, p2, p3) => ctx.content.cubic_to( x + p1.x.to_f32(), y + p1.y.to_f32(), x + p2.x.to_f32(), @@ -436,7 +439,7 @@ fn write_path(ctx: &mut PageContext, x: f32, y: f32, path: &geom::Path) { x + p3.x.to_f32(), y + p3.y.to_f32(), ), - geom::PathElement::ClosePath => ctx.content.close_path(), + geom::PathItem::ClosePath => ctx.content.close_path(), }; } } @@ -454,7 +457,7 @@ fn write_image(ctx: &mut PageContext, x: f32, y: f32, image: &Image, size: Size) } /// Save a link for later writing in the annotations dictionary. -fn write_link(ctx: &mut PageContext, pos: Point, link: &Link, size: Size) { +fn write_link(ctx: &mut PageContext, pos: Point, dest: &Destination, size: Size) { let mut min_x = Abs::inf(); let mut min_y = Abs::inf(); let mut max_x = -Abs::inf(); @@ -480,5 +483,5 @@ fn write_link(ctx: &mut PageContext, pos: Point, link: &Link, size: Size) { let y2 = min_y.to_f32(); let rect = Rect::new(x1, y1, x2, y2); - ctx.links.push((link.clone(), rect)); + ctx.links.push((dest.clone(), rect)); } diff --git a/src/export/render.rs b/src/export/render.rs index 58659b986..11ab5447f 100644 --- a/src/export/render.rs +++ b/src/export/render.rs @@ -9,9 +9,9 @@ use tiny_skia as sk; use ttf_parser::{GlyphId, OutlineBuilder}; use usvg::FitTo; -use crate::doc::{Element, Frame, Group, Meta, Text}; +use crate::doc::{Frame, FrameItem, GroupItem, Meta, TextItem}; use crate::geom::{ - self, Abs, Color, Geometry, Paint, PathElement, Shape, Size, Stroke, Transform, + self, Abs, Color, Geometry, Paint, PathItem, Shape, Size, Stroke, Transform, }; use crate::image::{DecodedImage, Image}; @@ -33,34 +33,34 @@ pub fn render(frame: &Frame, pixel_per_pt: f32, fill: Color) -> sk::Pixmap { canvas } -/// Render all elements in a frame into the canvas. +/// Render a frame into the canvas. fn render_frame( canvas: &mut sk::Pixmap, ts: sk::Transform, mask: Option<&sk::ClipMask>, frame: &Frame, ) { - for (pos, element) in frame.elements() { + for (pos, item) in frame.items() { let x = pos.x.to_f32(); let y = pos.y.to_f32(); let ts = ts.pre_translate(x, y); - match element { - Element::Group(group) => { + match item { + FrameItem::Group(group) => { render_group(canvas, ts, mask, group); } - Element::Text(text) => { + FrameItem::Text(text) => { render_text(canvas, ts, mask, text); } - Element::Shape(shape, _) => { + FrameItem::Shape(shape, _) => { render_shape(canvas, ts, mask, shape); } - Element::Image(image, size, _) => { + FrameItem::Image(image, size, _) => { render_image(canvas, ts, mask, image, *size); } - Element::Meta(meta, _) => match meta { + FrameItem::Meta(meta, _) => match meta { Meta::Link(_) => {} - Meta::Node(_) => {} + Meta::Elem(_) => {} Meta::Hide => {} }, } @@ -72,7 +72,7 @@ fn render_group( canvas: &mut sk::Pixmap, ts: sk::Transform, mask: Option<&sk::ClipMask>, - group: &Group, + group: &GroupItem, ) { let ts = ts.pre_concat(group.transform.into()); @@ -114,7 +114,7 @@ fn render_text( canvas: &mut sk::Pixmap, ts: sk::Transform, mask: Option<&sk::ClipMask>, - text: &Text, + text: &TextItem, ) { let mut x = 0.0; for glyph in &text.glyphs { @@ -135,7 +135,7 @@ fn render_svg_glyph( canvas: &mut sk::Pixmap, ts: sk::Transform, _: Option<&sk::ClipMask>, - text: &Text, + text: &TextItem, id: GlyphId, ) -> Option<()> { let mut data = text.font.ttf().glyph_svg_image(id)?; @@ -184,7 +184,7 @@ fn render_bitmap_glyph( canvas: &mut sk::Pixmap, ts: sk::Transform, mask: Option<&sk::ClipMask>, - text: &Text, + text: &TextItem, id: GlyphId, ) -> Option<()> { let size = text.size.to_f32(); @@ -208,7 +208,7 @@ fn render_outline_glyph( canvas: &mut sk::Pixmap, ts: sk::Transform, mask: Option<&sk::ClipMask>, - text: &Text, + text: &TextItem, id: GlyphId, ) -> Option<()> { let ppem = text.size.to_f32() * ts.sy; @@ -326,13 +326,13 @@ fn convert_path(path: &geom::Path) -> Option { let mut builder = sk::PathBuilder::new(); for elem in &path.0 { match elem { - PathElement::MoveTo(p) => { + PathItem::MoveTo(p) => { builder.move_to(p.x.to_f32(), p.y.to_f32()); } - PathElement::LineTo(p) => { + PathItem::LineTo(p) => { builder.line_to(p.x.to_f32(), p.y.to_f32()); } - PathElement::CubicTo(p1, p2, p3) => { + PathItem::CubicTo(p1, p2, p3) => { builder.cubic_to( p1.x.to_f32(), p1.y.to_f32(), @@ -342,7 +342,7 @@ fn convert_path(path: &geom::Path) -> Option { p3.y.to_f32(), ); } - PathElement::ClosePath => { + PathItem::ClosePath => { builder.close(); } }; diff --git a/src/geom/paint.rs b/src/geom/paint.rs index c01b21dad..eacd6f95a 100644 --- a/src/geom/paint.rs +++ b/src/geom/paint.rs @@ -236,7 +236,7 @@ impl FromStr for RgbaColor { fn from_str(hex_str: &str) -> Result { let hex_str = hex_str.strip_prefix('#').unwrap_or(hex_str); if hex_str.chars().any(|c| !c.is_ascii_hexdigit()) { - return Err("string contains non-hexadecimal letters"); + return Err("color string contains non-hexadecimal letters"); } let len = hex_str.len(); @@ -244,7 +244,7 @@ impl FromStr for RgbaColor { let short = len == 3 || len == 4; let alpha = len == 4 || len == 8; if !long && !short { - return Err("string has wrong length"); + return Err("color string has wrong length"); } let mut values: [u8; 4] = [u8::MAX; 4]; @@ -406,10 +406,10 @@ mod tests { assert_eq!(RgbaColor::from_str(hex), Err(message)); } - test("a5", "string has wrong length"); - test("12345", "string has wrong length"); - test("f075ff011", "string has wrong length"); - test("hmmm", "string contains non-hexadecimal letters"); - test("14B2AH", "string contains non-hexadecimal letters"); + test("a5", "color string has wrong length"); + test("12345", "color string has wrong length"); + test("f075ff011", "color string has wrong length"); + test("hmmm", "color string contains non-hexadecimal letters"); + test("14B2AH", "color string contains non-hexadecimal letters"); } } diff --git a/src/geom/path.rs b/src/geom/path.rs index 3a7c3033a..1c5325a3a 100644 --- a/src/geom/path.rs +++ b/src/geom/path.rs @@ -2,11 +2,11 @@ use super::*; /// A bezier path. #[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] -pub struct Path(pub Vec); +pub struct Path(pub Vec); -/// An element in a bezier path. +/// An item in a bezier path. #[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum PathElement { +pub enum PathItem { MoveTo(Point), LineTo(Point), CubicTo(Point, Point, Point), @@ -32,23 +32,23 @@ impl Path { path } - /// Push a [`MoveTo`](PathElement::MoveTo) element. + /// Push a [`MoveTo`](PathItem::MoveTo) item. pub fn move_to(&mut self, p: Point) { - self.0.push(PathElement::MoveTo(p)); + self.0.push(PathItem::MoveTo(p)); } - /// Push a [`LineTo`](PathElement::LineTo) element. + /// Push a [`LineTo`](PathItem::LineTo) item. pub fn line_to(&mut self, p: Point) { - self.0.push(PathElement::LineTo(p)); + self.0.push(PathItem::LineTo(p)); } - /// Push a [`CubicTo`](PathElement::CubicTo) element. + /// Push a [`CubicTo`](PathItem::CubicTo) item. pub fn cubic_to(&mut self, p1: Point, p2: Point, p3: Point) { - self.0.push(PathElement::CubicTo(p1, p2, p3)); + self.0.push(PathItem::CubicTo(p1, p2, p3)); } - /// Push a [`ClosePath`](PathElement::ClosePath) element. + /// Push a [`ClosePath`](PathItem::ClosePath) item. pub fn close_path(&mut self) { - self.0.push(PathElement::ClosePath); + self.0.push(PathItem::ClosePath); } } diff --git a/src/ide/analyze.rs b/src/ide/analyze.rs index 68b82b059..27c6c2a4d 100644 --- a/src/ide/analyze.rs +++ b/src/ide/analyze.rs @@ -81,6 +81,11 @@ pub fn analyze_import( } /// Find all labels and details for them. +/// +/// Returns: +/// - All labels and descriptions for them, if available +/// - A split offset: All labels before this offset belong to nodes, all after +/// belong to a bibliography. pub fn analyze_labels( world: &(dyn World + 'static), frames: &[Frame], @@ -90,16 +95,16 @@ pub fn analyze_labels( let items = &world.library().items; // Labels in the document. - for node in introspector.all() { - let Some(label) = node.label() else { continue }; - let details = node + for elem in introspector.all() { + let Some(label) = elem.label() else { continue }; + let details = elem .field("caption") - .or_else(|| node.field("body")) + .or_else(|| elem.field("body")) .and_then(|field| match field { Value::Content(content) => Some(content), _ => None, }) - .and_then(|content| (items.text_str)(content)); + .and_then(|content| (items.text_str)(&content)); output.push((label.clone(), details)); } diff --git a/src/ide/complete.rs b/src/ide/complete.rs index 4a1f0216c..886c12453 100644 --- a/src/ide/complete.rs +++ b/src/ide/complete.rs @@ -373,7 +373,7 @@ fn field_access_completions(ctx: &mut CompletionContext, value: &Value) { } Value::Content(content) => { for (name, value) in content.fields() { - ctx.value_completion(Some(name.clone()), value, false, None); + ctx.value_completion(Some(name.clone()), &value, false, None); } } Value::Dict(dict) => { @@ -509,7 +509,7 @@ fn set_rule_completions(ctx: &mut CompletionContext) { fn show_rule_selector_completions(ctx: &mut CompletionContext) { ctx.scope_completions( false, - |value| matches!(value, Value::Func(func) if func.select(None).is_ok()), + |value| matches!(value, Value::Func(func) if func.element().is_some()), ); ctx.enrich("", ": "); diff --git a/src/ide/jump.rs b/src/ide/jump.rs index 17e318a77..d123ac064 100644 --- a/src/ide/jump.rs +++ b/src/ide/jump.rs @@ -1,6 +1,8 @@ use std::num::NonZeroUsize; -use crate::doc::{Destination, Element, Frame, Location, Meta}; +use ecow::EcoString; + +use crate::doc::{Destination, Frame, FrameItem, Meta, Position}; use crate::geom::{Geometry, Point, Size}; use crate::model::Introspector; use crate::syntax::{LinkedNode, Source, SourceId, Span, SyntaxKind}; @@ -11,8 +13,10 @@ use crate::World; pub enum Jump { /// Jump to a position in a source file. Source(SourceId, usize), - /// Jump to position in the output or to an external URL. - Dest(Destination), + /// Jump to an external URL. + Url(EcoString), + /// Jump to a point on a page. + Position(Position), } impl Jump { @@ -32,20 +36,27 @@ pub fn jump_from_click( ) -> Option { let mut introspector = None; - // Prefer metadata. - for (pos, element) in frame.elements() { - if let Element::Meta(Meta::Link(link), size) = element { + // Try to find a link first. + for (pos, item) in frame.items() { + if let FrameItem::Meta(Meta::Link(dest), size) = item { if is_in_rect(*pos, *size, click) { - return Some(Jump::Dest(link.resolve(|| { - introspector.get_or_insert_with(|| Introspector::new(frames)) - }))); + return Some(match dest { + Destination::Url(url) => Jump::Url(url.clone()), + Destination::Position(pos) => Jump::Position(*pos), + Destination::Location(loc) => Jump::Position( + introspector + .get_or_insert_with(|| Introspector::new(frames)) + .position(*loc), + ), + }); } } } - for (mut pos, element) in frame.elements().rev() { - match element { - Element::Group(group) => { + // If there's no link, search for a jump target. + for (mut pos, item) in frame.items().rev() { + match item { + FrameItem::Group(group) => { // TODO: Handle transformation. if let Some(span) = jump_from_click(world, frames, &group.frame, click - pos) @@ -54,7 +65,7 @@ pub fn jump_from_click( } } - Element::Text(text) => { + FrameItem::Text(text) => { for glyph in &text.glyphs { if glyph.span.is_detached() { continue; @@ -85,14 +96,14 @@ pub fn jump_from_click( } } - Element::Shape(shape, span) => { + FrameItem::Shape(shape, span) => { let Geometry::Rect(size) = shape.geometry else { continue }; if is_in_rect(pos, size, click) { return Some(Jump::from_span(world, *span)); } } - Element::Image(_, size, span) if is_in_rect(pos, *size, click) => { + FrameItem::Image(_, size, span) if is_in_rect(pos, *size, click) => { return Some(Jump::from_span(world, *span)); } @@ -108,7 +119,7 @@ pub fn jump_from_cursor( frames: &[Frame], source: &Source, cursor: usize, -) -> Option { +) -> Option { let node = LinkedNode::new(source.root()).leaf_at(cursor)?; if node.kind() != SyntaxKind::Text { return None; @@ -117,7 +128,10 @@ pub fn jump_from_cursor( let span = node.span(); for (i, frame) in frames.iter().enumerate() { if let Some(pos) = find_in_frame(frame, span) { - return Some(Location { page: NonZeroUsize::new(i + 1).unwrap(), pos }); + return Some(Position { + page: NonZeroUsize::new(i + 1).unwrap(), + point: pos, + }); } } @@ -126,15 +140,15 @@ pub fn jump_from_cursor( /// Find the position of a span in a frame. fn find_in_frame(frame: &Frame, span: Span) -> Option { - for (mut pos, element) in frame.elements() { - if let Element::Group(group) = element { + for (mut pos, item) in frame.items() { + if let FrameItem::Group(group) = item { // TODO: Handle transformation. if let Some(point) = find_in_frame(&group.frame, span) { return Some(point + pos); } } - if let Element::Text(text) = element { + if let FrameItem::Text(text) = item { for glyph in &text.glyphs { if glyph.span == span { return Some(pos); diff --git a/src/lib.rs b/src/lib.rs index 9dbaf7213..392bd7ac4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,12 +9,12 @@ //! The next step is to [evaluate] the markup. This produces a [module], //! consisting of a scope of values that were exported by the code and //! [content], a hierarchical, styled representation of what was written in -//! the source file. The nodes of the content tree are well structured and +//! the source file. The elements of the content tree are well structured and //! order-independent and thus much better suited for further processing than //! the raw markup. //! - **Typesetting:** //! Next, the content is [typeset] into a [document] containing one [frame] -//! per page with elements and fixed positions. +//! per page with items at fixed positions. //! - **Exporting:** //! These frames can finally be exported into an output format (currently //! supported are [PDF] and [raster images]). diff --git a/src/model/content.rs b/src/model/content.rs index 5317236e6..b47da62c7 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -1,160 +1,144 @@ use std::any::TypeId; use std::fmt::{self, Debug, Formatter, Write}; -use std::hash::{Hash, Hasher}; -use std::iter::{self, Sum}; -use std::ops::{Add, AddAssign, Deref}; +use std::iter::Sum; +use std::ops::{Add, AddAssign}; use ecow::{eco_format, EcoString, EcoVec}; -use once_cell::sync::Lazy; use super::{ - node, Behave, Behaviour, Fold, Guard, Locatable, Recipe, StableId, Style, StyleMap, - Synthesize, + element, Behave, Behaviour, ElemFunc, Element, Fold, Guard, Label, Locatable, + Location, Recipe, Style, Styles, Synthesize, }; use crate::diag::{SourceResult, StrResult}; use crate::doc::Meta; -use crate::eval::{ - cast_from_value, cast_to_value, Args, Cast, Func, FuncInfo, Str, Value, Vm, -}; +use crate::eval::{Cast, Str, Value, Vm}; use crate::syntax::Span; use crate::util::pretty_array_like; /// Composable representation of styled content. #[derive(Clone, Hash)] pub struct Content { - id: NodeId, - span: Span, - fields: EcoVec<(EcoString, Value)>, - modifiers: EcoVec, + func: ElemFunc, + attrs: EcoVec, } -/// Modifiers that can be attached to content. +/// Attributes that can be attached to content. #[derive(Debug, Clone, PartialEq, Hash)] -enum Modifier { +enum Attr { + Span(Span), + Field(EcoString), + Value(Value), + Child(Content), + Styles(Styles), Prepared, Guard(Guard), - Id(StableId), + Location(Location), } impl Content { - /// Create a content of the given node kind. - pub fn new(id: NodeId) -> Self { - Self { - id, - span: Span::detached(), - fields: EcoVec::new(), - modifiers: EcoVec::new(), - } + /// Create an empty element. + pub fn new(func: ElemFunc) -> Self { + Self { func, attrs: EcoVec::new() } } /// Create empty content. pub fn empty() -> Self { - SequenceNode::new(vec![]).pack() + Self::new(SequenceElem::func()) } - /// Create a new sequence node from multiples nodes. - pub fn sequence(seq: Vec) -> Self { - match seq.as_slice() { - [_] => seq.into_iter().next().unwrap(), - _ => SequenceNode::new(seq).pack(), - } + /// Create a new sequence element from multiples elements. + pub fn sequence(iter: impl IntoIterator) -> Self { + let mut iter = iter.into_iter(); + let Some(first) = iter.next() else { return Self::empty() }; + let Some(second) = iter.next() else { return first }; + let mut content = Content::empty(); + content.attrs.push(Attr::Child(first)); + content.attrs.push(Attr::Child(second)); + content.attrs.extend(iter.map(Attr::Child)); + content } - /// The id of the contained node. - pub fn id(&self) -> NodeId { - self.id + /// The element function of the contained content. + pub fn func(&self) -> ElemFunc { + self.func } - /// Whether the content is empty. + /// Whether the content is an empty sequence. pub fn is_empty(&self) -> bool { - self.to::() - .map_or(false, |seq| seq.children().is_empty()) + self.is::() && self.attrs.is_empty() } - /// Whether the contained node is of type `T`. - pub fn is(&self) -> bool - where - T: Node + 'static, - { - self.id == NodeId::of::() + /// Whether the contained element is of type `T`. + pub fn is(&self) -> bool { + self.func == T::func() } - /// Cast to `T` if the contained node is of type `T`. - pub fn to(&self) -> Option<&T> - where - T: Node + 'static, - { - self.is::().then(|| unsafe { std::mem::transmute(self) }) + /// Cast to `T` if the contained element is of type `T`. + pub fn to(&self) -> Option<&T> { + T::unpack(self) } - /// Whether this content has the given capability. + /// Access the children if this is a sequence. + pub fn to_sequence(&self) -> Option> { + if !self.is::() { + return None; + } + Some(self.attrs.iter().filter_map(Attr::child)) + } + + /// Access the child and styles. + pub fn to_styled(&self) -> Option<(&Content, &Styles)> { + if !self.is::() { + return None; + } + let child = self.attrs.iter().find_map(Attr::child)?; + let styles = self.attrs.iter().find_map(Attr::styles)?; + Some((child, styles)) + } + + /// Whether the contained element has the given capability. pub fn can(&self) -> bool where C: ?Sized + 'static, { - (self.id.0.vtable)(TypeId::of::()).is_some() + (self.func.0.vtable)(TypeId::of::()).is_some() } - /// Cast to a trait object if this content has the given capability. + /// Cast to a trait object if the contained element has the given + /// capability. pub fn with(&self) -> Option<&C> where C: ?Sized + 'static, { - let vtable = (self.id.0.vtable)(TypeId::of::())?; + let vtable = (self.func.0.vtable)(TypeId::of::())?; let data = self as *const Self as *const (); Some(unsafe { &*crate::util::fat::from_raw_parts(data, vtable) }) } - /// Cast to a trait object if this content has the given capability. + /// Cast to a mutable trait object if the contained element has the given + /// capability. pub fn with_mut(&mut self) -> Option<&mut C> where C: ?Sized + 'static, { - let vtable = (self.id.0.vtable)(TypeId::of::())?; + let vtable = (self.func.0.vtable)(TypeId::of::())?; let data = self as *mut Self as *mut (); Some(unsafe { &mut *crate::util::fat::from_raw_parts_mut(data, vtable) }) } - /// The node's span. + /// The content's span. pub fn span(&self) -> Span { - self.span + self.attrs.iter().find_map(Attr::span).unwrap_or(Span::detached()) } /// Attach a span to the content if it doesn't already have one. pub fn spanned(mut self, span: Span) -> Self { - if self.span.is_detached() { - self.span = span; + if self.span().is_detached() { + self.attrs.push(Attr::Span(span)); } self } - /// Access a field on the content. - pub fn field(&self, name: &str) -> Option<&Value> { - self.fields - .iter() - .find(|(field, _)| field == name) - .map(|(_, value)| value) - } - - /// Try to access a field on the content as a specified type. - pub fn cast_field(&self, name: &str) -> Option { - match self.field(name) { - Some(value) => value.clone().cast().ok(), - None => None, - } - } - - /// Expect a field on the content to exist as a specified type. - #[track_caller] - pub fn expect_field(&self, name: &str) -> T { - self.field(name).unwrap().clone().cast().unwrap() - } - - /// List all fields on the content. - pub fn fields(&self) -> &[(EcoString, Value)] { - &self.fields - } - /// Attach a field to the content. pub fn with_field( mut self, @@ -168,26 +152,97 @@ impl Content { /// Attach a field to the content. pub fn push_field(&mut self, name: impl Into, value: impl Into) { let name = name.into(); - if let Some(i) = self.fields.iter().position(|(field, _)| *field == name) { - self.fields.make_mut()[i] = (name, value.into()); + if let Some(i) = self.attrs.iter().position(|attr| match attr { + Attr::Field(field) => *field == name, + _ => false, + }) { + self.attrs.make_mut()[i + 1] = Attr::Value(value.into()); } else { - self.fields.push((name, value.into())); + self.attrs.push(Attr::Field(name)); + self.attrs.push(Attr::Value(value.into())); } } + /// Access a field on the content. + pub fn field(&self, name: &str) -> Option { + if let Some(iter) = self.to_sequence() { + (name == "children") + .then(|| Value::Array(iter.cloned().map(Value::Content).collect())) + } else if let Some((child, _)) = self.to_styled() { + (name == "child").then(|| Value::Content(child.clone())) + } else { + self.field_ref(name).cloned() + } + } + + /// Access a field on the content by reference. + /// + /// Does not include synthesized fields for sequence and styled elements. + pub fn field_ref(&self, name: &str) -> Option<&Value> { + self.fields_ref() + .find(|&(field, _)| field == name) + .map(|(_, value)| value) + } + + /// Iter over all fields on the content. + /// + /// Does not include synthesized fields for sequence and styled elements. + pub fn fields(&self) -> impl Iterator { + static CHILD: EcoString = EcoString::inline("child"); + static CHILDREN: EcoString = EcoString::inline("children"); + + let option = if let Some(iter) = self.to_sequence() { + Some((&CHILDREN, Value::Array(iter.cloned().map(Value::Content).collect()))) + } else if let Some((child, _)) = self.to_styled() { + Some((&CHILD, Value::Content(child.clone()))) + } else { + None + }; + + self.fields_ref() + .map(|(name, value)| (name, value.clone())) + .chain(option) + } + + /// Iter over all fields on the content. + /// + /// Does not include synthesized fields for sequence and styled elements. + pub fn fields_ref(&self) -> impl Iterator { + let mut iter = self.attrs.iter(); + std::iter::from_fn(move || { + let field = iter.find_map(Attr::field)?; + let value = iter.next()?.value()?; + Some((field, value)) + }) + } + + /// Try to access a field on the content as a specified type. + pub fn cast_field(&self, name: &str) -> Option { + match self.field(name) { + Some(value) => value.cast().ok(), + None => None, + } + } + + /// Expect a field on the content to exist as a specified type. + #[track_caller] + pub fn expect_field(&self, name: &str) -> T { + self.field(name).unwrap().cast().unwrap() + } + /// Whether the content has the specified field. pub fn has(&self, field: &str) -> bool { self.field(field).is_some() } /// Borrow the value of the given field. - pub fn at(&self, field: &str) -> StrResult<&Value> { + pub fn at(&self, field: &str) -> StrResult { self.field(field).ok_or_else(|| missing_field(field)) } /// The content's label. pub fn label(&self) -> Option<&Label> { - match self.field("label")? { + match self.field_ref("label")? { Value::Label(label) => Some(label), _ => None, } @@ -199,20 +254,33 @@ impl Content { } /// Style this content with a style entry. - pub fn styled(self, style: impl Into