diff options
118 files changed, 2635 insertions, 2530 deletions
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 60254ba6..af48401d 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 563d565c..97535b1a 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<FontBook>, Vec<Font>)> = Lazy::new(|| { static LIBRARY: Lazy<Prehashed<Library>> = 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<ParamModel>, 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 68e73c62..1aa399d1 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 355027f5..a64f4eb3 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 d49ee6a1..3208f012 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 4d6068a1..a30faf2e 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<Spanned<Variant>>, ) -> 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 8b148c85..d5397e60 100644 --- a/library/src/compute/foundations.rs +++ b/library/src/compute/foundations.rs @@ -134,5 +134,5 @@ pub fn eval( source: Spanned<String>, ) -> 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 2a3998bf..c2f8262e 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<Content> { Ok(self .body() diff --git a/library/src/layout/columns.rs b/library/src/layout/columns.rs index 7704e9c4..3a1b012a 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 bdc147c3..ef7def7a 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<Content>, } -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 05b42bd8..1be57d4c 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 096c575e..b6476816 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<Content>, } -impl Layout for FlowNode { +impl Layout for FlowElem { fn layout( &self, vt: &mut Vt, @@ -27,29 +25,27 @@ impl Layout for FlowNode { ) -> SourceResult<Fragment> { 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::<StyledNode>() { - 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::<VNode>() { - layouter.layout_spacing(node, styles); - } else if let Some(node) = child.to::<ParNode>() { - layouter.layout_par(vt, node, styles)?; - } else if child.is::<RectNode>() - || child.is::<SquareNode>() - || child.is::<EllipseNode>() - || child.is::<CircleNode>() - || child.is::<ImageNode>() + if let Some(elem) = child.to::<VElem>() { + layouter.layout_spacing(elem, styles); + } else if let Some(elem) = child.to::<ParElem>() { + layouter.layout_par(vt, elem, styles)?; + } else if child.is::<RectElem>() + || child.is::<SquareElem>() + || child.is::<EllipseElem>() + || child.is::<CircleElem>() + || child.is::<ImageElem>() { let layoutable = child.with::<dyn Layout>().unwrap(); layouter.layout_single(vt, layoutable, styles)?; - } else if child.is::<MetaNode>() { + } else if child.is::<MetaElem>() { 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::<dyn Layout>() { layouter.layout_multiple(vt, &child, styles)?; - } else if child.is::<ColbreakNode>() { + } else if child.is::<ColbreakElem>() { 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::<PlaceNode>() { + // Placed elements that are out of flow produce placed items which + // aren't aligned later. + if let Some(placed) = block.to::<PlaceElem>() { 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::<AlignNode>() { + let aligns = if let Some(align) = block.to::<AlignElem>() { align.alignment(styles) - } else if let Some(styled) = block.to::<StyledNode>() { - 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 47d3ab86..5c3d132e 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<Content>, } -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 1d87d3e8..d9bee317 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<Content> { - 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 fe78131d..179c93eb 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 b116cbf8..df66d67f 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 b6ecce51..02d3bca5 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<Document> { #[comemo::memoize] fn cached( - node: &Content, + content: &Content, world: Tracked<dyn World>, tracer: TrackedMut<Tracer>, provider: TrackedMut<StabilityProvider>, @@ -78,7 +78,7 @@ impl LayoutRoot for Content { ) -> SourceResult<Document> { 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::<dyn LayoutRoot>() .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<Fragment> { #[comemo::memoize] fn cached( - node: &Content, + content: &Content, world: Tracked<dyn World>, tracer: TrackedMut<Tracer>, provider: TrackedMut<StabilityProvider>, @@ -142,7 +142,7 @@ impl Layout for Content { ) -> SourceResult<Fragment> { 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::<dyn Layout>() .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::<dyn Layout>() - && !content.is::<RectNode>() - && !content.is::<SquareNode>() - && !content.is::<EllipseNode>() - && !content.is::<CircleNode>() - && !content.is::<ImageNode>() + && !content.is::<RectElem>() + && !content.is::<SquareElem>() + && !content.is::<EllipseElem>() + && !content.is::<CircleElem>() + && !content.is::<ImageElem>() && !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<StyleChain<'a>>, /// An arena where intermediate content resulting from show rules is stored. content: Arena<Content>, - maps: Arena<StyleMap>, } 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::<dyn LayoutMath>() && !content.is::<EquationNode>() { + if content.can::<dyn LayoutMath>() && !content.is::<EquationElem>() { 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::<StyledNode>() { - 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::<SequenceNode>() { - 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::<PagebreakNode>() + .to::<PagebreakElem>() .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::<PagebreakElem>() { + 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<StyleChain<'a>>, + local: &Styles, + outer: Option<StyleChain<'a>>, ) -> SourceResult<()> { - if let Some(Some(span)) = map.interruption::<DocumentNode>() { + if let Some(Some(span)) = local.interruption::<DocumentElem>() { 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::<PageNode>() { + } else if let Some(Some(span)) = local.interruption::<PageElem>() { 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::<ParNode>().is_some() - || map.interruption::<AlignNode>().is_some() + self.interrupt_page(outer)?; + } else if local.interruption::<ParElem>().is_some() + || local.interruption::<AlignElem>().is_some() { self.interrupt_par()?; - } else if map.interruption::<ListNode>().is_some() - || map.interruption::<EnumNode>().is_some() - || map.interruption::<TermsNode>().is_some() + } else if local.interruption::<ListElem>().is_some() + || local.interruption::<EnumElem>().is_some() + || local.interruption::<TermsElem>().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::<PagebreakNode>() { + if let Some(pagebreak) = content.to::<PagebreakElem>() { self.keep_next = !pagebreak.weak(styles); return true; } - if content.is::<PageNode>() { + if content.is::<PageElem>() { 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::<ParbreakNode>() { + if content.is::<ParbreakElem>() { 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::<VNode>() - || content.is::<ColbreakNode>() - || content.is::<MetaNode>() + if content.is::<VElem>() + || content.is::<ColbreakElem>() + || content.is::<MetaElem>() { self.0.push(content.clone(), styles); return true; } - if content.can::<dyn Layout>() || content.is::<ParNode>() { - let is_tight_list = if let Some(node) = content.to::<ListNode>() { - node.tight(styles) - } else if let Some(node) = content.to::<EnumNode>() { - node.tight(styles) - } else if let Some(node) = content.to::<TermsNode>() { - node.tight(styles) + if content.can::<dyn Layout>() || content.is::<ParElem>() { + let is_tight_list = if let Some(elem) = content.to::<ListElem>() { + elem.tight(styles) + } else if let Some(elem) = content.to::<EnumElem>() { + elem.tight(styles) + } else if let Some(elem) = content.to::<TermsElem>() { + 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::<MetaNode>() { + if content.is::<MetaElem>() { if !self.0.is_basically_empty() { self.0.push(content.clone(), styles); return true; } - } else if content.is::<SpaceNode>() - || content.is::<TextNode>() - || content.is::<HNode>() - || content.is::<LinebreakNode>() - || content.is::<SmartQuoteNode>() - || content.to::<EquationNode>().map_or(false, |node| !node.block(styles)) - || content.is::<BoxNode>() + } else if content.is::<SpaceElem>() + || content.is::<TextElem>() + || content.is::<HElem>() + || content.is::<LinebreakElem>() + || content.is::<SmartQuoteElem>() + || content.to::<EquationElem>().map_or(false, |elem| !elem.block(styles)) + || content.is::<BoxElem>() { 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::<SpaceNode>() || content.is::<ParbreakNode>()) + && (content.is::<SpaceElem>() || content.is::<ParbreakElem>()) { self.staged.push((content, styles)); return true; @@ -533,12 +534,12 @@ impl<'a> ListBuilder<'a> { || content.is::<TermItem>()) && 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::<ParbreakNode>()); + self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::<ParbreakElem>()); 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::<ListItem>() { - ListNode::new( + ListElem::new( items .iter() - .map(|(item, map)| { + .map(|(item, local)| { let item = item.to::<ListItem>().unwrap(); - item.clone().with_body(item.body().styled_with_map(map.clone())) + item.clone().with_body(item.body().styled_with_map(local.clone())) }) .collect::<Vec<_>>(), ) .with_tight(self.tight) .pack() } else if item.is::<EnumItem>() { - EnumNode::new( + EnumElem::new( items .iter() - .map(|(item, map)| { + .map(|(item, local)| { let item = item.to::<EnumItem>().unwrap(); - item.clone().with_body(item.body().styled_with_map(map.clone())) + item.clone().with_body(item.body().styled_with_map(local.clone())) }) .collect::<Vec<_>>(), ) .with_tight(self.tight) .pack() } else if item.is::<TermItem>() { - TermsNode::new( + TermsElem::new( items .iter() - .map(|(item, map)| { + .map(|(item, local)| { let item = item.to::<TermItem>().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::<Vec<_>>(), diff --git a/library/src/layout/pad.rs b/library/src/layout/pad.rs index e8171560..441aa211 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 93ee08ce..a8a806ad 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<Fragment> { // 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<Self, Self::Err> { 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 cef0d11c..db65b125 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<Content>, } -impl Construct for ParNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { +impl Construct for ParElem { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { // 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::<Content>("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<Fragment> { #[comemo::memoize] fn cached( - par: &ParNode, + par: &ParElem, world: Tracked<dyn World>, tracer: TrackedMut<Tracer>, provider: TrackedMut<StabilityProvider>, @@ -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<usize>; @@ -243,7 +221,7 @@ type Range = std::ops::Range<usize>; // 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<Item<'a>>, /// 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::<TextNode>() || child.is::<SmartQuoteNode>() { + } else if child.is::<TextElem>() || child.is::<SmartQuoteElem>() { 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::<StyledNode>() { - 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::<SpaceNode>() { + let segment = if child.is::<SpaceElem>() { full.push(' '); Segment::Text(1) - } else if let Some(node) = child.to::<TextNode>() { + } else if let Some(elem) = child.to::<TextElem>() { 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::<HNode>() { + } else if let Some(elem) = child.to::<HElem>() { full.push(SPACING_REPLACE); - Segment::Spacing(node.amount()) - } else if let Some(node) = child.to::<LinebreakNode>() { - let c = if node.justify(styles) { '\u{2028}' } else { '\n' }; + Segment::Spacing(elem.amount()) + } else if let Some(elem) = child.to::<LinebreakElem>() { + 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::<SmartQuoteNode>() { + } else if let Some(elem) = child.to::<SmartQuoteElem>() { 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::<TextNode>() { - node.text().chars().next() - } else if child.is::<SmartQuoteNode>() { + if let Some(elem) = child.to::<TextElem>() { + elem.text().chars().next() + } else if child.is::<SmartQuoteElem>() { Some('"') - } else if child.is::<SpaceNode>() || child.is::<HNode>() { + } else if child.is::<SpaceElem>() || child.is::<HElem>() { 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::<EquationNode>() { - full.push(NODE_REPLACE); - Segment::Equation(node) - } else if let Some(node) = child.to::<BoxNode>() { - let frac = node.width(styles).is_fractional(); - full.push(if frac { SPACING_REPLACE } else { NODE_REPLACE }); - Segment::Box(node, frac) - } else if child.is::<MetaNode>() { - full.push(NODE_REPLACE); + } else if let Some(elem) = child.to::<EquationElem>() { + full.push(OBJ_REPLACE); + Segment::Equation(elem) + } else if let Some(elem) = child.to::<BoxElem>() { + let frac = elem.width(styles).is_fractional(); + full.push(if frac { SPACING_REPLACE } else { OBJ_REPLACE }); + Segment::Box(elem, frac) + } else if child.is::<MetaElem>() { + full.push(OBJ_REPLACE); Segment::Meta } else { bail!(child.span(), "unexpected paragraph child"); @@ -645,7 +623,7 @@ fn prepare<'a>( ) -> SourceResult<Preparation<'a>> { 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::<StyledNode>()) - .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<Line<'a>> { - 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<L line: line(vt, p, 0..0, false, false), }]; - let em = TextNode::size_in(p.styles); + let em = TextElem::size_in(p.styles); for (end, mandatory, hyphen) in breakpoints(p) { let k = table.len(); @@ -1046,7 +1024,7 @@ impl Breakpoints<'_> { .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<hypher::Lang> { 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::<SourceResult<_>>()?; // 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 bfabd0f3..057278df 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 94c81704..5a4db178 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<Abs>, - /// 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<bool>, } diff --git a/library/src/layout/repeat.rs b/library/src/layout/repeat.rs index c8f63ac3..a44bd075 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<Fragment> { 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 e67fec03..9253c497 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::<Self>().cloned().ok_or("expected vnode")?, + VElem, + v: Content => v.to::<Self>().cloned().ok_or("expected `v` element")?, } /// Kinds of spacing. diff --git a/library/src/layout/stack.rs b/library/src/layout/stack.rs index 1dd81a60..77cd3f8f 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<StackChild>, } -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::<AlignNode>() { + // Block-axis alignment of the `AlignElement` is respected by stacks. + let aligns = if let Some(align) = block.to::<AlignElem>() { align.alignment(styles) - } else if let Some(styled) = block.to::<StyledNode>() { - 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 d4b6e7d7..809c7ea7 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<Content>, } -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<T: Into<Value>> From<Celled<T>> 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 853dd32d..1200076f 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<TermItem>, } -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 2afe8201..2045e9ed 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<Length>, @@ -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 2f951b92..1a998700 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::TextNode>(), - text_str: |content| Some(content.to::<text::TextNode>()?.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::TextElem>()?.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::<meta::Counter>().cloned() { counter.call_method(vm, method, args, span) diff --git a/library/src/math/accent.rs b/library/src/math/accent.rs index 9ef76279..471507c5 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::<TextNode>() { - Some(node) => Value::Str(node.text().into()).cast()?, + v: Content => match v.to::<TextElem>() { + 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 6cf13a0f..d34379e2 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 7d8749f2..1b315f77 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<Content>, } -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::<LimitsNode>(); - let display_scripts = base.is::<ScriptsNode>(); + let display_limits = base.is::<LimitsElem>(); + let display_scripts = base.is::<ScriptsElem>(); 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 bd44546d..aed826b5 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<MathFragment>, - 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<MathFragment> { - 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<Vec<MathFragment>> { 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<MathRow> { - let fragments = self.layout_fragments(node)?; + pub fn layout_row(&mut self, elem: &dyn LayoutMath) -> SourceResult<MathRow> { + let fragments = self.layout_fragments(elem)?; Ok(MathRow::new(fragments)) } - pub fn layout_frame(&mut self, node: &dyn LayoutMath) -> SourceResult<Frame> { - Ok(self.layout_fragment(node)?.to_frame()) + pub fn layout_frame(&mut self, elem: &dyn LayoutMath) -> SourceResult<Frame> { + Ok(self.layout_fragment(elem)?.to_frame()) } pub fn layout_content(&mut self, content: &Content) -> SourceResult<Frame> { 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 2b9ee5ed..3be17eae 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::<Content>()?.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::<LrNode>() { - if node.size(ctx.styles()).is_auto() { - body = node.body(); + if let Some(elem) = body.to::<LrElem>() { + 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 90bc69b3..f19fb32e 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 87e28555..0d663d3f 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 d79c7ca5..8fba10e7 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<Content>, } -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<Vec<Content>>, } -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<Content>, } -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 7fb1aadf..cf665203 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<Content> { - let mut realized = self.clone().pack().guarded(Guard::Base(NodeId::of::<Self>())); + 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<CounterUpdate> { (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::<SequenceNode>() { - 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::<StyledNode>() { - 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::<SpaceNode>() { + if self.is::<SpaceElem>() { ctx.push(MathFragment::Space(ctx.space_width.scaled(ctx))); return Ok(()); } - if self.is::<LinebreakNode>() { + if self.is::<LinebreakElem>() { ctx.push(MathFragment::Linebreak); return Ok(()); } - if let Some(node) = self.to::<HNode>() { - if let Spacing::Rel(rel) = node.amount() { + if let Some(elem) = self.to::<HElem>() { + 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::<TextNode>() { - ctx.layout_text(node)?; + if let Some(elem) = self.to::<TextElem>() { + ctx.layout_text(elem)?; return Ok(()); } - if let Some(node) = self.with::<dyn LayoutMath>() { - return node.layout_math(ctx); + if let Some(elem) = self.with::<dyn LayoutMath>() { + 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 dae43c3a..e8db0c5d 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 b4756b9d..037c6ce7 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<Content>, /// 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<Frame> { - let node = index?.to::<TextNode>()?; - let c = match node.text().as_str() { + let elem = index?.to::<TextElem>()?; + 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 ecb2e31e..67d9eeaf 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 e1b9d408..848aca78 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 a3383a0c..7a911a0a 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, -} - -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(()) - } + body: Content, +) -> Value { + MathStyleElem::new(body).with_italic(Some(true)).pack().into() } - /// 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<MathVariant>, + + /// Whether to use bold glyphs. + pub bold: Option<bool>, + + /// Whether to use italic glyphs. + pub italic: Option<bool>, } -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 cfbb30b6..654da354 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<Content>, } -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<Content>, } -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<Content>, } -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<Content>, } -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 64ac1f8e..5f244a8f 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<Introspector>) -> StrResult<Self> { - let mut iter = introspector.query(Selector::node::<Self>()).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::<Self>().unwrap().clone()) + Ok(elem.to::<Self>().unwrap().clone()) } /// Whether the bibliography contains the given key. pub fn has(vt: &Vt, key: &str) -> bool { vt.introspector - .query(Selector::node::<Self>()) + .query(Self::func().select()) .into_iter() - .flat_map(|node| load(vt.world, &node.to::<Self>().unwrap().path())) + .flat_map(|elem| load(vt.world, &elem.to::<Self>().unwrap().path())) .flatten() .any(|entry| entry.key() == key) } @@ -76,7 +76,7 @@ impl BibliographyNode { introspector: Tracked<Introspector>, ) -> Vec<(EcoString, Option<EcoString>)> { 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<Content> { 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<EcoString>, @@ -217,7 +217,7 @@ pub struct CiteNode { pub style: Smart<CitationStyle>, } -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<Content> { 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<StableId, Option<Content>>, + citations: HashMap<Location, Option<Content>>, references: Vec<(Option<Content>, Content)>, } impl Works { /// Prepare all things need to cite a work or format a bibliography. fn new(vt: &Vt) -> StrResult<Arc<Self>> { - let bibliography = BibliographyNode::find(vt.introspector)?; + let bibliography = BibliographyElem::find(vt.introspector)?; let citations = vt .introspector .query(Selector::Any(eco_vec![ - Selector::node::<RefNode>(), - Selector::node::<CiteNode>(), + RefElem::func().select(), + CiteElem::func().select(), ])) .into_iter() - .map(|node| match node.to::<RefNode>() { + .map(|elem| match elem.to::<RefElem>() { Some(reference) => reference.to_citation(StyleChain::default()), - _ => node.to::<CiteNode>().unwrap().clone(), + _ => elem.to::<CiteElem>().unwrap().clone(), }) .collect(); Ok(create(vt.world, bibliography, citations)) @@ -292,19 +292,19 @@ impl Works { #[comemo::memoize] fn create( world: Tracked<dyn World>, - bibliography: BibliographyNode, - citations: Vec<CiteNode>, + bibliography: BibliographyElem, + citations: Vec<CiteElem>, ) -> Arc<Works> { 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 9e542847..dbb84812 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<Content> { 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<Content> { 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 f033a04f..3cfc2cd5 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<CounterState> { + pub fn at(&self, vt: &mut Vt, location: Location) -> SourceResult<CounterState> { 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<CounterState> { + pub fn final_(&self, vt: &mut Vt, _: Location) -> SourceResult<CounterState> { let sequence = self.sequence(vt)?; let (mut state, page) = sequence.last().unwrap().clone(); if self.is_page() { @@ -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<CounterState> { + pub fn both(&self, vt: &mut Vt, location: Location) -> SourceResult<CounterState> { 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::<UpdateNode>() { - Some(node) => Some(node.update()), - None => match node.with::<dyn Count>() { + if let Some(update) = match elem.to::<UpdateElem>() { + Some(elem) => Some(elem.update()), + None => match elem.with::<dyn Count>() { 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::<UpdateNode>(), - 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::<PageNode>() { + element: ElemFunc => { + if element == PageElem::func() { return Ok(Self::Page); } - if !Content::new(id).can::<dyn Locatable>() { - Err(eco_format!("cannot count through {}s", id.name))?; + if !Content::new(element).can::<dyn Locatable>() { + 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<CounterUpdate>; } @@ -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<Content> { - 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<Content> { Ok(Content::empty()) } diff --git a/library/src/meta/document.rs b/library/src/meta/document.rs index 75e78184..99e5c6b6 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<EcoString>, @@ -29,22 +27,20 @@ pub struct DocumentNode { pub children: Vec<Content>, } -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<Document> { 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::<StyledNode>() { - 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::<PageNode>() { + if let Some(page) = child.to::<PageElem>() { 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 6f9011b8..9251f3ef 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<Content> { 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<CounterUpdate> { 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 61605e67..1eaca187 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<Content> { 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<CounterUpdate> { self.numbering(StyleChain::default()) .is_some() @@ -132,11 +133,11 @@ impl Count for HeadingNode { } cast_from_value! { - HeadingNode, + HeadingElem, v: Content => v.to::<Self>().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 e9b8bcc6..c82a7443 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>("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<Content> { 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 a0a23897..002d757e 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<Content>, } -impl Show for OutlineNode { +impl Show for OutlineElem { fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> { - 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::<HeadingNode>(), + 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::<HeadingNode>().unwrap(); - let stable_id = heading.0.stable_id().unwrap(); + for elem in &elems { + let heading = elem.to::<HeadingElem>().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 23e310fe..94b25cbc 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::<dyn Locatable>() { - Err(eco_format!("cannot query for {}s", id.name))?; + element: ElemFunc => { + if !Content::new(element).can::<dyn Locatable>() { + 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 000080d8..6e794d2d 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<Option<Supplement>>, } -impl Show for RefNode { +impl Show for RefElem { fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> { 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::<dyn Locatable>() { - bail!(self.span(), "cannot reference {}", node.id().name); + if !elem.can::<dyn Locatable>() { + 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::<dyn LocalName>() - .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>("numbering") else { + let Some(numbering) = elem.cast_field::<Numbering>("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 b19a2671..090f1ccf 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<Func>) -> 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<Value> { + pub fn at(self, vt: &mut Vt, location: Location) -> SourceResult<Value> { 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<Value> { + pub fn final_(self, vt: &mut Vt, _: Location) -> SourceResult<Value> { let sequence = self.sequence(vt)?; Ok(sequence.last().unwrap().clone()) } /// Produce content that performs a state update. pub fn update(self, update: StateUpdate) -> Content { - 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::<UpdateNode>().unwrap(); - match node.update() { + for elem in introspector.query(self.selector()) { + let elem = elem.to::<UpdateElem>().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::<UpdateNode>(), - 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<Func>, } -impl Show for DisplayNode { +impl Show for DisplayElem { fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> { - 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<Content> { Ok(Content::empty()) } diff --git a/library/src/prelude.rs b/library/src/prelude.rs index 55e5f17b..4c83cf31 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 eff41c0b..6a1aa127 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::<dyn Behave>() .map_or(Behaviour::Supportive, Behave::behaviour); match interaction { Behaviour::Weak(level) => { if matches!(self.last, Behaviour::Weak(_)) { - let item = item.with::<dyn Behave>().unwrap(); + let item = elem.with::<dyn Behave>().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<Item = &Content> { - self.builder.items().chain(self.staged.iter().map(|(item, ..)| item)) + /// Iterate over the contained elements. + pub fn elems(&self) -> impl DoubleEndedIterator<Item = &Content> { + 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 14674c9d..72a82749 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<Option<GenAlign>>) -> 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<Option<GenAlign>>) -> Self { - self.styled(AlignNode::set_alignment(aligns)) + self.styled(AlignElem::set_alignment(aligns)) } fn padded(self, padding: Sides<Rel<Length>>) -> 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<Rel<Length>>) -> 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 07c7004b..5db3a799 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 a29564f3..90a6ca85 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<Content> { - 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<Content> { - 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<Content> { - 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 5a5c8514..e1d9c0f2 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<Content> { - 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<Content> { - 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 845ffe29..16268aad 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<EcoString>) -> Content { Self::new(text.into()).pack() } } -impl Construct for TextNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { - // The text constructor is special: It doesn't create a text node. +impl Construct for TextElem { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { + // 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<Dir>); +pub struct TextDir(pub Smart<Dir>); cast_from_value! { - HorizontalDir, + TextDir, v: Smart<Dir> => { 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 863cc3bd..be923304 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 b6cc0d3d..d24254ed 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<EcoString>, } -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<Content> { 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<F>( /// 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 244e7afe..15fbcd3f 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<Em> { /// 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<Item = FontFamily> + 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<Feature> { }; // 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<Feature> { /// 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 74bb70c7..1bea3673 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<Content> { 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<Content> { 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<EcoString> { - if content.is::<SpaceNode>() { + if content.is::<SpaceElem>() { Some(' '.into()) - } else if let Some(node) = content.to::<TextNode>() { - convert_script(&node.text(), sub) - } else if let Some(seq) = content.to::<SequenceNode>() { + } else if let Some(elem) = content.to::<TextElem>() { + 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<EcoString> { /// 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 c5016436..8a81a40e 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 6614e3ee..0932a9f1 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 8aef3629..601f0d85 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<Rel<Length>>, @@ -139,7 +139,7 @@ pub struct RectNode { pub body: Option<Content>, } -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<Content>, } -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<Rel<Length>>, @@ -331,7 +331,7 @@ pub struct EllipseNode { pub body: Option<Content>, } -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<Content>, } -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 index 68d43d9c..9d6b5c88 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<TokenStream> { - let node = prepare(stream, &body)?; - Ok(create(&node)) +/// Expand the `#[element]` macro. +pub fn element(stream: TokenStream, body: syn::ItemStruct) -> Result<TokenStream> { + 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<Node> { +/// Preprocess the element's definition. +fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Elem> { 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<Node> { 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<Node> { }; 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(<Self as ::typst::model::Node>::id())) + Self(::typst::model::Content::new( + <Self as ::typst::model::Element>::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::<Self>(), + <Self as ::typst::model::Element>::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::<Self>(), + <Self as ::typst::model::Element>::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::<Self>().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::<dyn #capability>() { @@ -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(<Self as ::typst::model::Node>::id())); + let mut element = Self(::typst::model::Content::new( + <Self as ::typst::model::Element>::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 fafe8eea..945bbcd0 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() } @@ -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<EcoString>, } -/// 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<Abs>, - /// The elements composing this layout. - elements: Arc<Vec<(Point, Element)>>, + /// The items composing this layout. + items: Arc<Vec<(Point, FrameItem)>>, } /// 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<I>(&mut self, elements: I) + pub fn prepend_multiple<I>(&mut self, items: I) where - I: IntoIterator<Item = (Point, Element)>, + I: IntoIterator<Item = (Point, FrameItem)>, { - 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<F>(&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<Glyph>, } -impl Text { +impl TextItem { /// The width of the text run. pub fn width(&self) -> Abs { self.glyphs.iter().map(|g| g.x_advance).sum::<Em>().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 fa71ff1a..bebbe809 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<Option<Value>> { 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<Option<i64>> { 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<Self> { 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<Value> { 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<bool> { 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<bool> { 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 7bf1814f..ef042d6d 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<Selector> { - 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<NodeId> { + /// Extract the element function, if it is one. + pub fn element(&self) -> Option<ElemFunc> { 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<StyleMap> { - 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<Dict>) -> StrResult<Selector> { - 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<Repr> for Func { } } -impl From<NodeId> for Func { - fn from(id: NodeId) -> Self { - Repr::Node(id).into() +impl From<ElemFunc> 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<Value>, diff --git a/src/eval/library.rs b/src/eval/library.rs index eae342c2..85d5647b 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<EcoString>, /// 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<LangItems> = 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 324191ab..72245fb0 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::<EcoString>("field")?)), "at" => content.at(&args.expect::<EcoString>("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::<StableId>() { + if let Some(&location) = dynamic.downcast::<Location>() { 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 74c5f0b3..f19e4305 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<dyn World>, - text: &str, + code: &str, span: Span, ) -> SourceResult<Value> { - 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<Span>, @@ -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::<dyn Unlabellable>()) { - *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::<SourceResult<_>>()?, + .collect::<SourceResult<Vec<_>>>()?, )) } } @@ -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<Self::Output> { if let Some(condition) = self.condition() { if !condition.eval(vm)?.cast::<bool>().at(condition.span())? { - return Ok(StyleMap::new()); + return Ok(Styles::new()); } } let target = self.target(); - let target = target.eval(vm)?.cast::<Func>().at(target.span())?; + let target = target + .eval(vm)? + .cast::<Func>() + .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 d4338b5c..e241cac5 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 73c41067..6a199a1d 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); -/// A collection of symbols. +/// 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 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<Self> { - 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<Item = (&str, char)> { - 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<Item = &str> + '_ { 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<char> { + 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<char>), @@ -166,24 +209,3 @@ fn parts(modifiers: &str) -> impl Iterator<Item = &str> { 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<char> { - 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 61af36f5..ce9c4e0e 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 5347d831..f3c81cb8 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<State>, 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 58659b98..11ab5447 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<sk::Path> { 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<sk::Path> { p3.y.to_f32(), ); } - PathElement::ClosePath => { + PathItem::ClosePath => { builder.close(); } }; diff --git a/src/geom/paint.rs b/src/geom/paint.rs index c01b21da..eacd6f95 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<Self, Self::Err> { 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 3a7c3033..1c5325a3 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<PathElement>); +pub struct Path(pub Vec<PathItem>); -/// 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 68b82b05..27c6c2a4 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 4a1f0216..886c1245 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 17e318a7..d123ac06 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<Jump> { 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<Location> { +) -> Option<Position> { 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<Point> { - 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); @@ -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 5317236e..b47da62c 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<Modifier>, + func: ElemFunc, + attrs: EcoVec<Attr>, } -/// 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>) -> 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<Item = Self>) -> 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::<SequenceNode>() - .map_or(false, |seq| seq.children().is_empty()) + self.is::<SequenceElem>() && self.attrs.is_empty() } - /// Whether the contained node is of type `T`. - pub fn is<T>(&self) -> bool - where - T: Node + 'static, - { - self.id == NodeId::of::<T>() + /// Whether the contained element is of type `T`. + pub fn is<T: Element>(&self) -> bool { + self.func == T::func() } - /// Cast to `T` if the contained node is of type `T`. - pub fn to<T>(&self) -> Option<&T> - where - T: Node + 'static, - { - self.is::<T>().then(|| unsafe { std::mem::transmute(self) }) + /// Cast to `T` if the contained element is of type `T`. + pub fn to<T: Element>(&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<impl Iterator<Item = &Self>> { + if !self.is::<SequenceElem>() { + 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::<StyledElem>() { + 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<C>(&self) -> bool where C: ?Sized + 'static, { - (self.id.0.vtable)(TypeId::of::<C>()).is_some() + (self.func.0.vtable)(TypeId::of::<C>()).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<C>(&self) -> Option<&C> where C: ?Sized + 'static, { - let vtable = (self.id.0.vtable)(TypeId::of::<C>())?; + let vtable = (self.func.0.vtable)(TypeId::of::<C>())?; 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<C>(&mut self) -> Option<&mut C> where C: ?Sized + 'static, { - let vtable = (self.id.0.vtable)(TypeId::of::<C>())?; + let vtable = (self.func.0.vtable)(TypeId::of::<C>())?; 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<T: Cast>(&self, name: &str) -> Option<T> { - 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<T: Cast>(&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<EcoString>, value: impl Into<Value>) { 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.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<Value> { + 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.fields.push((name, value.into())); + 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<Item = (&EcoString, Value)> { + 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<Item = (&EcoString, &Value)> { + 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<T: Cast>(&self, name: &str) -> Option<T> { + 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<T: Cast>(&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<Value> { 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<Style>) -> Self { - self.styled_with_map(style.into().into()) + pub fn styled(mut self, style: impl Into<Style>) -> Self { + if self.is::<StyledElem>() { + let prev = + self.attrs.make_mut().iter_mut().find_map(Attr::styles_mut).unwrap(); + prev.apply_one(style.into()); + self + } else { + self.styled_with_map(style.into().into()) + } } /// Style this content with a full style map. - pub fn styled_with_map(self, styles: StyleMap) -> Self { + pub fn styled_with_map(mut self, styles: Styles) -> Self { if styles.is_empty() { + return self; + } + + if self.is::<StyledElem>() { + let prev = + self.attrs.make_mut().iter_mut().find_map(Attr::styles_mut).unwrap(); + prev.apply(styles); self - } else if let Some(styled) = self.to::<StyledNode>() { - let mut map = styled.styles(); - map.apply(styles); - StyledNode::new(map, styled.body()).pack() } else { - StyledNode::new(styles, self).pack() + let mut content = Content::new(StyledElem::func()); + content.attrs.push(Attr::Child(self)); + content.attrs.push(Attr::Styles(styles)); + content } } @@ -221,7 +289,7 @@ impl Content { if recipe.selector.is_none() { recipe.apply_vm(vm, self) } else { - Ok(self.styled(Style::Recipe(recipe))) + Ok(self.styled(recipe)) } } @@ -232,35 +300,34 @@ impl Content { Ok(Self::sequence(vec![self.clone(); count])) } -} -#[doc(hidden)] -impl Content { /// Disable a show rule recipe. - pub fn guarded(mut self, id: Guard) -> Self { - self.modifiers.push(Modifier::Guard(id)); + pub fn guarded(mut self, guard: Guard) -> Self { + self.attrs.push(Attr::Guard(guard)); self } - /// Whether no show rule was executed for this node so far. - pub(super) fn is_pristine(&self) -> bool { - !self - .modifiers - .iter() - .any(|modifier| matches!(modifier, Modifier::Guard(_))) + /// Check whether a show rule recipe is disabled. + pub fn is_guarded(&self, guard: Guard) -> bool { + self.attrs.contains(&Attr::Guard(guard)) } - /// Check whether a show rule recipe is disabled. - pub(super) fn is_guarded(&self, id: Guard) -> bool { - self.modifiers.contains(&Modifier::Guard(id)) + /// Whether no show rule was executed for this content so far. + pub fn is_pristine(&self) -> bool { + !self.attrs.iter().any(|modifier| matches!(modifier, Attr::Guard(_))) } - /// Whether this node was prepared. + /// Whether this content has already been prepared. pub fn is_prepared(&self) -> bool { - self.modifiers.contains(&Modifier::Prepared) + self.attrs.contains(&Attr::Prepared) } - /// Whether the node needs to be realized specially. + /// Mark this content as prepared. + pub fn mark_prepared(&mut self) { + self.attrs.push(Attr::Prepared); + } + + /// Whether the content needs to be realized specially. pub fn needs_preparation(&self) -> bool { (self.can::<dyn Locatable>() || self.can::<dyn Synthesize>() @@ -268,37 +335,23 @@ impl Content { && !self.is_prepared() } - /// Mark this content as prepared. - pub fn mark_prepared(&mut self) { - self.modifiers.push(Modifier::Prepared); - } - - /// Attach a stable id to this content. - pub fn set_stable_id(&mut self, id: StableId) { - self.modifiers.push(Modifier::Id(id)); - } - - /// This content's stable identifier. - pub fn stable_id(&self) -> Option<StableId> { - self.modifiers.iter().find_map(|modifier| match modifier { - Modifier::Id(id) => Some(*id), + /// This content's location in the document flow. + pub fn location(&self) -> Option<Location> { + self.attrs.iter().find_map(|modifier| match modifier { + Attr::Location(location) => Some(*location), _ => None, }) } - /// Copy the modifiers from another piece of content. - pub(super) fn copy_modifiers(&mut self, from: &Content) { - self.span = from.span; - self.modifiers = from.modifiers.clone(); - if let Some(label) = from.label() { - self.push_field("label", label.clone()) - } + /// Attach a location to this content. + pub fn set_location(&mut self, location: Location) { + self.attrs.push(Attr::Location(location)); } } impl Debug for Content { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let name = self.id.name; + let name = self.func.name(); if let Some(text) = item!(text_str)(self) { f.write_char('[')?; f.write_str(&text)?; @@ -308,12 +361,15 @@ impl Debug for Content { return f.write_str("[ ]"); } - let pieces: Vec<_> = self - .fields - .iter() + let mut pieces: Vec<_> = self + .fields() .map(|(name, value)| eco_format!("{name}: {value:?}")) .collect(); + if self.is::<StyledElem>() { + pieces.push(EcoString::from("..")); + } + f.write_str(name)?; f.write_str(&pretty_array_like(&pieces, false)) } @@ -327,31 +383,36 @@ impl Default for Content { impl PartialEq for Content { fn eq(&self, other: &Self) -> bool { - self.id == other.id - && self.fields.len() == other.fields.len() - && self - .fields - .iter() - .all(|(name, value)| other.field(name) == Some(value)) + if let (Some(left), Some(right)) = (self.to_sequence(), other.to_sequence()) { + left.eq(right) + } else if let (Some(left), Some(right)) = (self.to_styled(), other.to_styled()) { + left == right + } else { + self.func == other.func && self.fields_ref().eq(other.fields_ref()) + } } } impl Add for Content { type Output = Self; - fn add(self, rhs: Self) -> Self::Output { - let lhs = self; - let seq = match (lhs.to::<SequenceNode>(), rhs.to::<SequenceNode>()) { - (Some(lhs), Some(rhs)) => { - lhs.children().into_iter().chain(rhs.children()).collect() + fn add(self, mut rhs: Self) -> Self::Output { + let mut lhs = self; + match (lhs.is::<SequenceElem>(), rhs.is::<SequenceElem>()) { + (true, true) => { + lhs.attrs.extend(rhs.attrs); + lhs } - (Some(lhs), None) => { - lhs.children().into_iter().chain(iter::once(rhs)).collect() + (true, false) => { + lhs.attrs.push(Attr::Child(rhs)); + lhs } - (None, Some(rhs)) => iter::once(lhs).chain(rhs.children()).collect(), - (None, None) => vec![lhs, rhs], - }; - SequenceNode::new(seq).pack() + (false, true) => { + rhs.attrs.insert(0, Attr::Child(lhs)); + rhs + } + (false, false) => Self::sequence([lhs, rhs]), + } } } @@ -363,154 +424,77 @@ impl AddAssign for Content { impl Sum for Content { fn sum<I: Iterator<Item = Self>>(iter: I) -> Self { - Self::sequence(iter.collect()) + Self::sequence(iter) } } -/// A constructable, stylable content node. -pub trait Node: Construct + Set + Sized + 'static { - /// The node's ID. - fn id() -> NodeId; - - /// Pack a node into type-erased content. - fn pack(self) -> Content; -} - -/// A unique identifier for a node. -#[derive(Copy, Clone)] -pub struct NodeId(pub &'static NodeMeta); - -impl NodeId { - /// Get the id of a node. - pub fn of<T: Node>() -> Self { - T::id() +impl Attr { + fn child(&self) -> Option<&Content> { + match self { + Self::Child(child) => Some(child), + _ => None, + } } -} -impl Debug for NodeId { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(self.name) + fn styles(&self) -> Option<&Styles> { + match self { + Self::Styles(styles) => Some(styles), + _ => None, + } } -} -impl Hash for NodeId { - fn hash<H: Hasher>(&self, state: &mut H) { - state.write_usize(self.0 as *const _ as usize); + fn styles_mut(&mut self) -> Option<&mut Styles> { + match self { + Self::Styles(styles) => Some(styles), + _ => None, + } } -} - -impl Eq for NodeId {} -impl PartialEq for NodeId { - fn eq(&self, other: &Self) -> bool { - std::ptr::eq(self.0, other.0) + fn field(&self) -> Option<&EcoString> { + match self { + Self::Field(field) => Some(field), + _ => None, + } } -} - -impl Deref for NodeId { - type Target = NodeMeta; - fn deref(&self) -> &Self::Target { - self.0 + fn value(&self) -> Option<&Value> { + match self { + Self::Value(value) => Some(value), + _ => None, + } } -} - -cast_from_value! { - NodeId, - v: Func => v.id().ok_or("this function is not an element")? -} - -cast_to_value! { - v: NodeId => Value::Func(v.into()) -} - -/// Static node for a node. -pub struct NodeMeta { - /// The node's name. - pub name: &'static str, - /// The node's vtable for caspability dispatch. - pub vtable: fn(of: TypeId) -> Option<*const ()>, - /// The node's constructor. - pub construct: fn(&Vm, &mut Args) -> SourceResult<Content>, - /// The node's set rule. - pub set: fn(&mut Args) -> SourceResult<StyleMap>, - /// Details about the function. - pub info: Lazy<FuncInfo>, -} - -/// A node's constructor function. -pub trait Construct { - /// Construct a node from the arguments. - /// - /// This is passed only the arguments that remain after execution of the - /// node's set rule. - fn construct(vm: &Vm, args: &mut Args) -> SourceResult<Content>; -} - -/// A node's set rule. -pub trait Set { - /// Parse relevant arguments into style properties for this node. - fn set(args: &mut Args) -> SourceResult<StyleMap>; -} - -/// Indicates that a node cannot be labelled. -pub trait Unlabellable {} -/// A label for a node. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Label(pub EcoString); - -impl Debug for Label { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "<{}>", self.0) + fn span(&self) -> Option<Span> { + match self { + Self::Span(span) => Some(*span), + _ => None, + } } } -/// A sequence of nodes. -/// -/// Combines other arbitrary content. So, when you write `[Hi] + [you]` in -/// Typst, the two text nodes are combined into a single sequence node. -/// /// Display: Sequence /// Category: special -#[node] -pub struct SequenceNode { - #[variadic] - pub children: Vec<Content>, -} +#[element] +struct SequenceElem {} -/// A node with applied styles. -/// -/// Display: Styled +/// Display: Sequence /// Category: special -#[node] -pub struct StyledNode { - /// The styles. - #[required] - pub styles: StyleMap, - - /// The styled content. - #[required] - pub body: Content, -} - -cast_from_value! { - StyleMap: "style map", -} +#[element] +struct StyledElem {} -/// Host for metadata. +/// Hosts metadata and ensures metadata is produced even for empty elements. /// /// Display: Meta /// Category: special -#[node(Behave)] -pub struct MetaNode { +#[element(Behave)] +pub struct MetaElem { /// Metadata that should be attached to all elements affected by this style /// property. #[fold] pub data: Vec<Meta>, } -impl Behave for MetaNode { +impl Behave for MetaElem { fn behaviour(&self) -> Behaviour { Behaviour::Ignorant } diff --git a/src/model/element.rs b/src/model/element.rs new file mode 100644 index 00000000..e25b22b4 --- /dev/null +++ b/src/model/element.rs @@ -0,0 +1,145 @@ +use std::any::TypeId; +use std::fmt::{self, Debug, Formatter}; +use std::hash::{Hash, Hasher}; + +use ecow::EcoString; +use once_cell::sync::Lazy; + +use super::{Content, Selector, Styles}; +use crate::diag::SourceResult; +use crate::eval::{ + cast_from_value, cast_to_value, Args, Dict, Func, FuncInfo, Value, Vm, +}; + +/// A document element. +pub trait Element: Construct + Set + Sized + 'static { + /// Pack the element into type-erased content. + fn pack(self) -> Content; + + /// Extract this element from type-erased content. + fn unpack(content: &Content) -> Option<&Self>; + + /// The element's function. + fn func() -> ElemFunc; +} + +/// An element's constructor function. +pub trait Construct { + /// Construct an element from the arguments. + /// + /// This is passed only the arguments that remain after execution of the + /// element's set rule. + fn construct(vm: &mut Vm, args: &mut Args) -> SourceResult<Content>; +} + +/// An element's set rule. +pub trait Set { + /// Parse relevant arguments into style properties for this element. + fn set(args: &mut Args) -> SourceResult<Styles>; +} + +/// An element's function. +#[derive(Copy, Clone)] +pub struct ElemFunc(pub(super) &'static NativeElemFunc); + +impl ElemFunc { + /// The function's name. + pub fn name(self) -> &'static str { + self.0.name + } + + /// Apply the given arguments to the function. + pub fn with(self, args: Args) -> Func { + Func::from(self).with(args) + } + + /// Extract details about the function. + pub fn info(&self) -> &'static FuncInfo { + &self.0.info + } + + /// Construct an element. + pub fn construct(self, vm: &mut Vm, args: &mut Args) -> SourceResult<Content> { + (self.0.construct)(vm, args) + } + + /// Create a selector for elements of this function. + pub fn select(self) -> Selector { + Selector::Elem(self, None) + } + + /// Create a selector for elements of this function, filtering for those + /// whose [fields](super::Content::field) match the given arguments. + pub fn where_(self, fields: Dict) -> Selector { + Selector::Elem(self, Some(fields)) + } + + /// Execute the set rule for the element and return the resulting style map. + pub fn set(self, mut args: Args) -> SourceResult<Styles> { + let styles = (self.0.set)(&mut args)?; + args.finish()?; + Ok(styles) + } +} + +impl Debug for ElemFunc { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(self.name()) + } +} + +impl Hash for ElemFunc { + fn hash<H: Hasher>(&self, state: &mut H) { + state.write_usize(self.0 as *const _ as usize); + } +} + +impl Eq for ElemFunc {} + +impl PartialEq for ElemFunc { + fn eq(&self, other: &Self) -> bool { + std::ptr::eq(self.0, other.0) + } +} + +cast_from_value! { + ElemFunc, + v: Func => v.element().ok_or("expected element function")?, +} + +cast_to_value! { + v: ElemFunc => Value::Func(v.into()) +} + +impl From<&'static NativeElemFunc> for ElemFunc { + fn from(native: &'static NativeElemFunc) -> Self { + Self(native) + } +} + +/// An element function backed by a Rust type. +pub struct NativeElemFunc { + /// The element's name. + pub name: &'static str, + /// The element's vtable for capability dispatch. + pub vtable: fn(of: TypeId) -> Option<*const ()>, + /// The element's constructor. + pub construct: fn(&mut Vm, &mut Args) -> SourceResult<Content>, + /// The element's set rule. + pub set: fn(&mut Args) -> SourceResult<Styles>, + /// Details about the function. + pub info: Lazy<FuncInfo>, +} + +/// A label for an element. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct Label(pub EcoString); + +impl Debug for Label { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "<{}>", self.0) + } +} + +/// Indicates that an element cannot be labelled. +pub trait Unlabellable {} diff --git a/src/model/introspect.rs b/src/model/introspect.rs new file mode 100644 index 00000000..35f0e628 --- /dev/null +++ b/src/model/introspect.rs @@ -0,0 +1,170 @@ +use std::fmt::{self, Debug, Formatter}; +use std::hash::Hash; +use std::num::NonZeroUsize; + +use super::{Content, Selector}; +use crate::doc::{Frame, FrameItem, Meta, Position}; +use crate::eval::cast_from_value; +use crate::geom::{Point, Transform}; +use crate::util::NonZeroExt; + +/// Stably identifies a location in the document across multiple layout passes. +/// +/// This struct is created by [`StabilityProvider::locate`]. +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub struct Location(u128, usize, usize); + +impl Location { + /// Produce a variant of this location. + pub fn variant(self, n: usize) -> Self { + Self(self.0, self.1, n) + } +} + +impl Debug for Location { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad("..") + } +} + +cast_from_value! { + Location: "location", +} + +/// Provides stable identities to elements. +#[derive(Clone)] +pub struct StabilityProvider { + hashes: Vec<u128>, + checkpoints: Vec<usize>, +} + +impl StabilityProvider { + /// Create a new stability provider. + pub fn new() -> Self { + Self { hashes: vec![], checkpoints: vec![] } + } +} + +#[comemo::track] +impl StabilityProvider { + /// Produce a stable identifier for this call site. + pub fn locate(&mut self, hash: u128) -> Location { + let count = self.hashes.iter().filter(|&&prev| prev == hash).count(); + self.hashes.push(hash); + Location(hash, count, 0) + } + + /// Create a checkpoint of the state that can be restored. + pub fn save(&mut self) { + self.checkpoints.push(self.hashes.len()); + } + + /// Restore the last checkpoint. + pub fn restore(&mut self) { + if let Some(checkpoint) = self.checkpoints.pop() { + self.hashes.truncate(checkpoint); + } + } +} + +/// Can be queried for elements and their positions. +pub struct Introspector { + pages: usize, + elems: Vec<(Content, Position)>, +} + +impl Introspector { + /// Create a new introspector. + pub fn new(frames: &[Frame]) -> Self { + let mut introspector = Self { pages: frames.len(), elems: vec![] }; + for (i, frame) in frames.iter().enumerate() { + let page = NonZeroUsize::new(1 + i).unwrap(); + introspector.extract(frame, page, Transform::identity()); + } + introspector + } + + /// Iterate over all elements. + pub fn all(&self) -> impl Iterator<Item = &Content> { + self.elems.iter().map(|(elem, _)| elem) + } + + /// Extract metadata from a frame. + fn extract(&mut self, frame: &Frame, page: NonZeroUsize, ts: Transform) { + for (pos, item) in frame.items() { + match item { + FrameItem::Group(group) => { + let ts = ts + .pre_concat(Transform::translate(pos.x, pos.y)) + .pre_concat(group.transform); + self.extract(&group.frame, page, ts); + } + FrameItem::Meta(Meta::Elem(content), _) + if !self + .elems + .iter() + .any(|(prev, _)| prev.location() == content.location()) => + { + let pos = pos.transform(ts); + self.elems.push((content.clone(), Position { page, point: pos })); + } + _ => {} + } + } + } +} + +#[comemo::track] +impl Introspector { + /// Whether this introspector is not yet initialized. + pub fn init(&self) -> bool { + self.pages > 0 + } + + /// Query for all matching elements. + pub fn query(&self, selector: Selector) -> Vec<Content> { + self.all().filter(|elem| selector.matches(elem)).cloned().collect() + } + + /// Query for all matching element up to the given location. + pub fn query_before(&self, selector: Selector, location: Location) -> Vec<Content> { + let mut matches = vec![]; + for elem in self.all() { + if selector.matches(elem) { + matches.push(elem.clone()); + } + if elem.location() == Some(location) { + break; + } + } + matches + } + + /// Query for all matching elements starting from the given location. + pub fn query_after(&self, selector: Selector, location: Location) -> Vec<Content> { + self.all() + .skip_while(|elem| elem.location() != Some(location)) + .filter(|elem| selector.matches(elem)) + .cloned() + .collect() + } + + /// The total number pages. + pub fn pages(&self) -> NonZeroUsize { + NonZeroUsize::new(self.pages).unwrap_or(NonZeroUsize::ONE) + } + + /// Find the page number for the given location. + pub fn page(&self, location: Location) -> NonZeroUsize { + self.position(location).page + } + + /// Find the position for the given location. + pub fn position(&self, location: Location) -> Position { + self.elems + .iter() + .find(|(elem, _)| elem.location() == Some(location)) + .map(|(_, loc)| *loc) + .unwrap_or(Position { page: NonZeroUsize::ONE, point: Point::zero() }) + } +} diff --git a/src/model/mod.rs b/src/model/mod.rs index 6015c365..7458dc3c 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -1,14 +1,87 @@ //! The document model. -#[macro_use] -mod styles; mod content; +mod element; +mod introspect; mod realize; -mod typeset; +mod styles; pub use self::content::*; +pub use self::element::*; +pub use self::introspect::*; pub use self::realize::*; pub use self::styles::*; -pub use self::typeset::*; -pub use typst_macros::node; +pub use typst_macros::element; + +use comemo::{Constraint, Track, Tracked, TrackedMut}; + +use crate::diag::SourceResult; +use crate::doc::Document; +use crate::eval::Tracer; +use crate::World; + +/// Typeset content into a fully layouted document. +#[comemo::memoize] +pub fn typeset( + world: Tracked<dyn World>, + mut tracer: TrackedMut<Tracer>, + content: &Content, +) -> SourceResult<Document> { + let library = world.library(); + let styles = StyleChain::new(&library.styles); + + let mut document; + let mut iter = 0; + let mut introspector = Introspector::new(&[]); + + // Relayout until all introspections stabilize. + // If that doesn't happen within five attempts, we give up. + loop { + let constraint = Constraint::new(); + let mut provider = StabilityProvider::new(); + let mut vt = Vt { + world, + tracer: TrackedMut::reborrow_mut(&mut tracer), + provider: provider.track_mut(), + introspector: introspector.track_with(&constraint), + }; + + document = (library.items.layout)(&mut vt, content, styles)?; + iter += 1; + + introspector = Introspector::new(&document.pages); + + if iter >= 5 || introspector.valid(&constraint) { + break; + } + } + + Ok(document) +} + +/// A virtual typesetter. +/// +/// Holds the state needed to [typeset] content. +pub struct Vt<'a> { + /// The compilation environment. + pub world: Tracked<'a, dyn World>, + /// The tracer for inspection of the values an expression produces. + pub tracer: TrackedMut<'a, Tracer>, + /// Provides stable identities to elements. + pub provider: TrackedMut<'a, StabilityProvider>, + /// Provides access to information about the document. + pub introspector: Tracked<'a, Introspector>, +} + +impl Vt<'_> { + /// Mutably reborrow with a shorter lifetime. + pub fn reborrow_mut(&mut self) -> Vt<'_> { + Vt { + world: self.world, + tracer: TrackedMut::reborrow_mut(&mut self.tracer), + provider: TrackedMut::reborrow_mut(&mut self.provider), + introspector: self.introspector, + } + } +} diff --git a/src/model/realize.rs b/src/model/realize.rs index 634a31fd..51d69fdc 100644 --- a/src/model/realize.rs +++ b/src/model/realize.rs @@ -1,4 +1,4 @@ -use super::{Content, MetaNode, Node, NodeId, Recipe, Selector, StyleChain, Vt}; +use super::{Content, ElemFunc, Element, MetaElem, Recipe, Selector, StyleChain, Vt}; use crate::diag::SourceResult; use crate::doc::Meta; use crate::util::hash128; @@ -35,28 +35,28 @@ pub fn realize( ) -> SourceResult<Option<Content>> { // Pre-process. if target.needs_preparation() { - let mut node = target.clone(); + let mut elem = target.clone(); if target.can::<dyn Locatable>() || target.label().is_some() { - let id = vt.provider.identify(hash128(target)); - node.set_stable_id(id); + let location = vt.provider.locate(hash128(target)); + elem.set_location(location); } - if let Some(node) = node.with_mut::<dyn Synthesize>() { - node.synthesize(vt, styles); + if let Some(elem) = elem.with_mut::<dyn Synthesize>() { + elem.synthesize(vt, styles); } - node.mark_prepared(); + elem.mark_prepared(); - if node.stable_id().is_some() { - let span = node.span(); - let meta = Meta::Node(node.clone()); + if elem.location().is_some() { + let span = elem.span(); + let meta = Meta::Elem(elem.clone()); return Ok(Some( - (node + MetaNode::new().pack().spanned(span)) - .styled(MetaNode::set_data(vec![meta])), + (elem + MetaElem::new().pack().spanned(span)) + .styled(MetaElem::set_data(vec![meta])), )); } - return Ok(Some(node)); + return Ok(Some(elem)); } // Find out how many recipes there are. @@ -77,17 +77,17 @@ pub fn realize( // Realize if there was no matching recipe. if let Some(showable) = target.with::<dyn Show>() { - let guard = Guard::Base(target.id()); + let guard = Guard::Base(target.func()); if realized.is_none() && !target.is_guarded(guard) { realized = Some(showable.show(vt, styles)?); } } - // Finalize only if this is the first application for this node. - if let Some(node) = target.with::<dyn Finalize>() { + // Finalize only if this is the first application for this element. + if let Some(elem) = target.with::<dyn Finalize>() { if target.is_pristine() { if let Some(already) = realized { - realized = Some(node.finalize(already, styles)); + realized = Some(elem.finalize(already, styles)); } } } @@ -103,8 +103,8 @@ fn try_apply( guard: Guard, ) -> SourceResult<Option<Content>> { match &recipe.selector { - Some(Selector::Node(id, _)) => { - if target.id() != *id { + Some(Selector::Elem(element, _)) => { + if target.func() != *element { return Ok(None); } @@ -124,22 +124,17 @@ fn try_apply( return Ok(None); }; - let make = |s| { - let mut content = item!(text)(s); - content.copy_modifiers(target); - content - }; - + let make = |s: &str| target.clone().with_field("text", s); let mut result = vec![]; let mut cursor = 0; for m in regex.find_iter(&text) { let start = m.start(); if cursor < start { - result.push(make(text[cursor..start].into())); + result.push(make(&text[cursor..start])); } - let piece = make(m.as_str().into()).guarded(guard); + let piece = make(m.as_str()).guarded(guard); let transformed = recipe.apply_vt(vt, piece)?; result.push(transformed); cursor = m.end(); @@ -150,7 +145,7 @@ fn try_apply( } if cursor < text.len() { - result.push(make(text[cursor..].into())); + result.push(make(&text[cursor..])); } Ok(Some(Content::sequence(result))) @@ -163,55 +158,56 @@ fn try_apply( } } -/// Makes this node locatable through `vt.locate`. +/// Makes this element locatable through `vt.locate`. pub trait Locatable {} -/// Synthesize fields on a node. This happens before execution of any show rule. +/// Synthesize fields on an element. This happens before execution of any show +/// rule. pub trait Synthesize { - /// Prepare the node for show rule application. + /// Prepare the element for show rule application. fn synthesize(&mut self, vt: &Vt, styles: StyleChain); } -/// The base recipe for a node. +/// The base recipe for an element. pub trait Show { - /// Execute the base recipe for this node. + /// Execute the base recipe for this element. fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content>; } -/// Post-process a node after it was realized. +/// Post-process an element after it was realized. pub trait Finalize { - /// Finalize the fully realized form of the node. Use this for effects that + /// Finalize the fully realized form of the element. Use this for effects that /// should work even in the face of a user-defined show rule, for example - /// the linking behaviour of a link node. + /// the linking behaviour of a link element. fn finalize(&self, realized: Content, styles: StyleChain) -> Content; } -/// How a node interacts with other nodes. +/// How the element interacts with other elements. pub trait Behave { - /// The node's interaction behaviour. + /// The element's interaction behaviour. fn behaviour(&self) -> Behaviour; - /// Whether this weak node is larger than a previous one and thus picked as - /// the maximum when the levels are the same. + /// Whether this weak element is larger than a previous one and thus picked + /// as the maximum when the levels are the same. #[allow(unused_variables)] fn larger(&self, prev: &Content) -> bool { false } } -/// How a node interacts with other nodes in a stream. +/// How an element interacts with other elements in a stream. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Behaviour { - /// A weak node which only survives when a supportive node is before and - /// after it. Furthermore, per consecutive run of weak nodes, only one - /// survives: The one with the lowest weakness level (or the larger one if - /// there is a tie). + /// A weak element which only survives when a supportive element is before + /// and after it. Furthermore, per consecutive run of weak elements, only + /// one survives: The one with the lowest weakness level (or the larger one + /// if there is a tie). Weak(usize), - /// A node that enables adjacent weak nodes to exist. The default. + /// An element that enables adjacent weak elements to exist. The default. Supportive, - /// A node that destroys adjacent weak nodes. + /// An element that destroys adjacent weak elements. Destructive, - /// A node that does not interact at all with other nodes, having the + /// An element that does not interact at all with other elements, having the /// same effect as if it didn't exist. Ignorant, } @@ -221,6 +217,6 @@ pub enum Behaviour { pub enum Guard { /// The nth recipe from the top of the chain. Nth(usize), - /// The [base recipe](Show) for a kind of node. - Base(NodeId), + /// The [base recipe](Show) for a kind of element. + Base(ElemFunc), } diff --git a/src/model/styles.rs b/src/model/styles.rs index b7d09774..db2b2053 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -1,25 +1,26 @@ use std::fmt::{self, Debug, Formatter, Write}; use std::iter; +use std::mem; -use ecow::{eco_format, EcoString, EcoVec}; +use ecow::{eco_format, eco_vec, EcoString, EcoVec}; -use super::{Content, Label, Node, NodeId, Vt}; +use super::{Content, ElemFunc, Element, Label, Vt}; use crate::diag::{SourceResult, Trace, Tracepoint}; use crate::eval::{cast_from_value, Args, Cast, Dict, Func, Regex, Value, Vm}; use crate::syntax::Span; use crate::util::pretty_array_like; -/// A map of style properties. -#[derive(Default, Clone, Hash)] -pub struct StyleMap(Vec<Style>); +/// A list of style properties. +#[derive(Default, PartialEq, Clone, Hash)] +pub struct Styles(EcoVec<Style>); -impl StyleMap { - /// Create a new, empty style map. +impl Styles { + /// Create a new, empty style list. pub fn new() -> Self { Self::default() } - /// Whether this map contains no styles. + /// Whether this contains no styles. pub fn is_empty(&self) -> bool { self.0.is_empty() } @@ -39,13 +40,25 @@ impl StyleMap { } /// Apply outer styles. Like [`chain`](StyleChain::chain), but in-place. - pub fn apply(&mut self, outer: Self) { - self.0.splice(0..0, outer.0.iter().cloned()); + pub fn apply(&mut self, mut outer: Self) { + outer.0.extend(mem::take(self).0.into_iter()); + *self = outer; + } + + /// Apply one outer styles. Like [`chain_one`](StyleChain::chain_one), but + /// in-place. + pub fn apply_one(&mut self, outer: Style) { + self.0.insert(0, outer); + } + + /// Apply a slice of outer styles. + pub fn apply_slice(&mut self, outer: &[Style]) { + self.0 = outer.iter().cloned().chain(mem::take(self).0.into_iter()).collect(); } /// Add an origin span to all contained properties. pub fn spanned(mut self, span: Span) -> Self { - for entry in &mut self.0 { + for entry in self.0.make_mut() { if let Style::Property(property) = entry { property.span = Some(span); } @@ -53,37 +66,31 @@ impl StyleMap { self } - /// Returns `Some(_)` with an optional span if this map contains styles for - /// the given `node`. - pub fn interruption<T: Node>(&self) -> Option<Option<Span>> { - let node = NodeId::of::<T>(); + /// Returns `Some(_)` with an optional span if this list contains + /// styles for the given element. + pub fn interruption<T: Element>(&self) -> Option<Option<Span>> { + let func = T::func(); self.0.iter().find_map(|entry| match entry { - Style::Property(property) => property.is_of(node).then(|| property.span), - Style::Recipe(recipe) => recipe.is_of(node).then(|| Some(recipe.span)), + Style::Property(property) => property.is_of(func).then(|| property.span), + Style::Recipe(recipe) => recipe.is_of(func).then(|| Some(recipe.span)), }) } } -impl From<Style> for StyleMap { +impl From<Style> for Styles { fn from(entry: Style) -> Self { - Self(vec![entry]) - } -} - -impl PartialEq for StyleMap { - fn eq(&self, other: &Self) -> bool { - crate::util::hash128(self) == crate::util::hash128(other) + Self(eco_vec![entry]) } } -impl Debug for StyleMap { +impl Debug for Styles { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.pad("..") } } /// A single style property or recipe. -#[derive(Clone, Hash)] +#[derive(Clone, PartialEq, Hash)] pub enum Style { /// A style property originating from a set rule or constructor. Property(Property), @@ -124,11 +131,17 @@ impl From<Property> for Style { } } +impl From<Recipe> for Style { + fn from(recipe: Recipe) -> Self { + Self::Recipe(recipe) + } +} + /// A style property originating from a set rule or constructor. -#[derive(Clone, Hash)] +#[derive(Clone, PartialEq, Hash)] pub struct Property { - /// The id of the node the property belongs to. - node: NodeId, + /// The element the property belongs to. + element: ElemFunc, /// The property's name. name: EcoString, /// The property's value. @@ -139,44 +152,44 @@ pub struct Property { impl Property { /// Create a new property from a key-value pair. - pub fn new(node: NodeId, name: EcoString, value: Value) -> Self { - Self { node, name, value, span: None } + pub fn new(element: ElemFunc, name: EcoString, value: Value) -> Self { + Self { element, name, value, span: None } } /// Whether this property is the given one. - pub fn is(&self, node: NodeId, name: &str) -> bool { - self.node == node && self.name == name + pub fn is(&self, element: ElemFunc, name: &str) -> bool { + self.element == element && self.name == name } - /// Whether this property belongs to the node with the given id. - pub fn is_of(&self, node: NodeId) -> bool { - self.node == node + /// Whether this property belongs to the given element. + pub fn is_of(&self, element: ElemFunc) -> bool { + self.element == element } } impl Debug for Property { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "set {}({}: {:?})", self.node.name, self.name, self.value)?; + write!(f, "set {}({}: {:?})", self.element.name(), self.name, self.value)?; Ok(()) } } /// A show rule recipe. -#[derive(Clone, Hash)] +#[derive(Clone, PartialEq, Hash)] pub struct Recipe { /// The span errors are reported with. pub span: Span, - /// Determines whether the recipe applies to a node. + /// Determines whether the recipe applies to an element. pub selector: Option<Selector>, /// The transformation to perform on the match. pub transform: Transform, } impl Recipe { - /// Whether this recipe is for the given node. - pub fn is_of(&self, node: NodeId) -> bool { + /// Whether this recipe is for the given type of element. + pub fn is_of(&self, element: ElemFunc) -> bool { match self.selector { - Some(Selector::Node(id, _)) => id == node, + Some(Selector::Elem(own, _)) => own == element, _ => false, } } @@ -197,7 +210,7 @@ impl Recipe { let mut result = func.call_vm(vm, args); // For selector-less show rules, a tracepoint makes no sense. if self.selector.is_some() { - let point = || Tracepoint::Show(content.id().name.into()); + let point = || Tracepoint::Show(content.func().name().into()); result = result.trace(vm.world(), point, content.span()); } Ok(result?.display()) @@ -213,7 +226,7 @@ impl Recipe { Transform::Func(func) => { let mut result = func.call_vt(vt, [Value::Content(content.clone())]); if self.selector.is_some() { - let point = || Tracepoint::Show(content.id().name.into()); + let point = || Tracepoint::Show(content.func().name().into()); result = result.trace(vt.world, point, content.span()); } Ok(result?.display()) @@ -238,25 +251,20 @@ impl Debug for Recipe { /// A selector in a show rule. #[derive(Clone, PartialEq, Hash)] pub enum Selector { - /// Matches a specific type of node. + /// Matches a specific type of element. /// - /// If there is a dictionary, only nodes with the fields from the + /// If there is a dictionary, only elements with the fields from the /// dictionary match. - Node(NodeId, Option<Dict>), - /// Matches nodes with a specific label. + Elem(ElemFunc, Option<Dict>), + /// Matches elements with a specific label. Label(Label), - /// Matches text nodes through a regular expression. + /// Matches text elements through a regular expression. Regex(Regex), /// Matches if any of the subselectors match. Any(EcoVec<Self>), } impl Selector { - /// Define a simple node selector. - pub fn node<T: Node>() -> Self { - Self::Node(NodeId::of::<T>(), None) - } - /// Define a simple text selector. pub fn text(text: &str) -> Self { Self::Regex(Regex::new(®ex::escape(text)).unwrap()) @@ -265,16 +273,16 @@ impl Selector { /// Whether the selector matches for the target. pub fn matches(&self, target: &Content) -> bool { match self { - Self::Node(id, dict) => { - target.id() == *id + Self::Elem(element, dict) => { + target.func() == *element && dict .iter() .flat_map(|dict| dict.iter()) - .all(|(name, value)| target.field(name) == Some(value)) + .all(|(name, value)| target.field_ref(name) == Some(value)) } Self::Label(label) => target.label() == Some(label), Self::Regex(regex) => { - target.id() == item!(text_id) + target.func() == item!(text_func) && item!(text_str)(target).map_or(false, |text| regex.is_match(&text)) } Self::Any(selectors) => selectors.iter().any(|sel| sel.matches(target)), @@ -285,8 +293,8 @@ impl Selector { impl Debug for Selector { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { - Self::Node(node, dict) => { - f.write_str(node.name)?; + Self::Elem(elem, dict) => { + f.write_str(elem.name())?; if let Some(dict) = dict { f.write_str(".where")?; dict.fmt(f)?; @@ -307,21 +315,24 @@ impl Debug for Selector { cast_from_value! { Selector: "selector", - text: EcoString => Self::text(&text), + func: Func => func + .element() + .ok_or("only element functions can be used as selectors")? + .select(), label: Label => Self::Label(label), - func: Func => func.select(None)?, + text: EcoString => Self::text(&text), regex: Regex => Self::Regex(regex), } /// A show rule transformation that can be applied to a match. -#[derive(Clone, Hash)] +#[derive(Clone, PartialEq, Hash)] pub enum Transform { /// Replacement content. Content(Content), /// A function to apply to the match. Func(Func), /// Apply styles to the content. - Style(StyleMap), + Style(Styles), } impl Debug for Transform { @@ -340,11 +351,11 @@ cast_from_value! { func: Func => Self::Func(func), } -/// A chain of style maps, similar to a linked list. +/// A chain of styles, similar to a linked list. /// -/// A style chain allows to combine properties from multiple style maps in a -/// node hierarchy in a non-allocating way. Rather than eagerly merging the -/// maps, each access walks the hierarchy from the innermost to the outermost +/// A style chain allows to combine properties from multiple style lists in a +/// element hierarchy in a non-allocating way. Rather than eagerly merging the +/// lists, each access walks the hierarchy from the innermost to the outermost /// map, trying to find a match and then folding it with matches further up the /// chain. #[derive(Default, Clone, Copy, Hash)] @@ -356,21 +367,21 @@ pub struct StyleChain<'a> { } impl<'a> StyleChain<'a> { - /// Start a new style chain with a root map. - pub fn new(root: &'a StyleMap) -> Self { + /// Start a new style chain with root styles. + pub fn new(root: &'a Styles) -> Self { Self { head: &root.0, tail: None } } - /// Make the given map the first link of this chain. + /// Make the given style list the first link of this chain. /// - /// The resulting style chain contains styles from `map` as well as - /// `self`. The ones from `map` take precedence over the ones from - /// `self`. For folded properties `map` contributes the inner value. - pub fn chain<'b>(&'b self, map: &'b StyleMap) -> StyleChain<'b> { - if map.is_empty() { + /// The resulting style chain contains styles from `local` as well as + /// `self`. The ones from `local` take precedence over the ones from + /// `self`. For folded properties `local` contributes the inner value. + pub fn chain<'b>(&'b self, local: &'b Styles) -> StyleChain<'b> { + if local.is_empty() { *self } else { - StyleChain { head: &map.0, tail: Some(self) } + StyleChain { head: &local.0, tail: Some(self) } } } @@ -385,12 +396,12 @@ impl<'a> StyleChain<'a> { /// Cast the first value for the given property in the chain. pub fn get<T: Cast>( self, - node: NodeId, + func: ElemFunc, name: &'a str, inherent: Option<Value>, default: impl Fn() -> T, ) -> T { - self.properties::<T>(node, name, inherent) + self.properties::<T>(func, name, inherent) .next() .unwrap_or_else(default) } @@ -398,18 +409,18 @@ impl<'a> StyleChain<'a> { /// Cast the first value for the given property in the chain. pub fn get_resolve<T: Cast + Resolve>( self, - node: NodeId, + func: ElemFunc, name: &'a str, inherent: Option<Value>, default: impl Fn() -> T, ) -> T::Output { - self.get(node, name, inherent, default).resolve(self) + self.get(func, name, inherent, default).resolve(self) } /// Cast the first value for the given property in the chain. pub fn get_fold<T: Cast + Fold>( self, - node: NodeId, + func: ElemFunc, name: &'a str, inherent: Option<Value>, default: impl Fn() -> T::Output, @@ -424,13 +435,13 @@ impl<'a> StyleChain<'a> { .map(|value| value.fold(next(values, styles, default))) .unwrap_or_else(|| default()) } - next(self.properties::<T>(node, name, inherent), self, &default) + next(self.properties::<T>(func, name, inherent), self, &default) } /// Cast the first value for the given property in the chain. pub fn get_resolve_fold<T>( self, - node: NodeId, + func: ElemFunc, name: &'a str, inherent: Option<Value>, default: impl Fn() -> <T::Output as Fold>::Output, @@ -453,7 +464,7 @@ impl<'a> StyleChain<'a> { .map(|value| value.resolve(styles).fold(next(values, styles, default))) .unwrap_or_else(|| default()) } - next(self.properties::<T>(node, name, inherent), self, &default) + next(self.properties::<T>(func, name, inherent), self, &default) } /// Iterate over all style recipes in the chain. @@ -464,7 +475,7 @@ impl<'a> StyleChain<'a> { /// Iterate over all values for the given property in the chain. pub fn properties<T: Cast + 'a>( self, - node: NodeId, + func: ElemFunc, name: &'a str, inherent: Option<Value>, ) -> impl Iterator<Item = T> + '_ { @@ -473,21 +484,21 @@ impl<'a> StyleChain<'a> { .chain( self.entries() .filter_map(Style::property) - .filter(move |property| property.is(node, name)) + .filter(move |property| property.is(func, name)) .map(|property| property.value.clone()), ) .map(move |value| { - value - .cast() - .unwrap_or_else(|err| panic!("{} (for {}.{})", err, node.name, name)) + value.cast().unwrap_or_else(|err| { + panic!("{} (for {}.{})", err, func.name(), name) + }) }) } /// Convert to a style map. - pub fn to_map(self) -> StyleMap { - let mut suffix = StyleMap::new(); + pub fn to_map(self) -> Styles { + let mut suffix = Styles::new(); for link in self.links() { - suffix.0.splice(0..0, link.iter().cloned()); + suffix.apply_slice(link); } suffix } @@ -502,13 +513,13 @@ impl<'a> StyleChain<'a> { Links(Some(self)) } - /// Build a style map from the suffix (all links beyond the `len`) of the + /// Build owned styles from the suffix (all links beyond the `len`) of the /// chain. - fn suffix(self, len: usize) -> StyleMap { - let mut suffix = StyleMap::new(); + fn suffix(self, len: usize) -> Styles { + let mut suffix = Styles::new(); let take = self.links().count().saturating_sub(len); for link in self.links().take(take) { - suffix.0.splice(0..0, link.iter().cloned()); + suffix.apply_slice(link); } suffix } @@ -517,6 +528,16 @@ impl<'a> StyleChain<'a> { fn pop(&mut self) { *self = self.tail.copied().unwrap_or_default(); } + + /// Whether two style chains contain the same pointers. + fn ptr_eq(self, other: Self) -> bool { + std::ptr::eq(self.head, other.head) + && match (self.tail, other.tail) { + (Some(a), Some(b)) => std::ptr::eq(a, b), + (None, None) => true, + _ => false, + } + } } impl Debug for StyleChain<'_> { @@ -530,7 +551,7 @@ impl Debug for StyleChain<'_> { impl PartialEq for StyleChain<'_> { fn eq(&self, other: &Self) -> bool { - crate::util::hash128(self) == crate::util::hash128(other) + self.ptr_eq(*other) || crate::util::hash128(self) == crate::util::hash128(other) } } @@ -574,7 +595,7 @@ impl<'a> Iterator for Links<'a> { #[derive(Clone, Hash)] pub struct StyleVec<T> { items: Vec<T>, - maps: Vec<(StyleMap, usize)>, + styles: Vec<(Styles, usize)>, } impl<T> StyleVec<T> { @@ -588,14 +609,14 @@ impl<T> StyleVec<T> { self.items.len() } - /// Insert an element in the front. The element will share the style of the - /// current first element. + /// Insert an item in the front. The item will share the style of the + /// current first item. /// /// This method has no effect if the vector is empty. pub fn push_front(&mut self, item: T) { - if !self.maps.is_empty() { + if !self.styles.is_empty() { self.items.insert(0, item); - self.maps[0].1 += 1; + self.styles[0].1 += 1; } } @@ -606,14 +627,14 @@ impl<T> StyleVec<T> { { StyleVec { items: self.items.iter().map(f).collect(), - maps: self.maps.clone(), + styles: self.styles.clone(), } } - /// Iterate over references to the contained items and associated style maps. - pub fn iter(&self) -> impl Iterator<Item = (&T, &StyleMap)> + '_ { + /// Iterate over references to the contained items and associated styles. + pub fn iter(&self) -> impl Iterator<Item = (&T, &Styles)> + '_ { self.items().zip( - self.maps + self.styles .iter() .flat_map(|(map, count)| iter::repeat(map).take(*count)), ) @@ -624,13 +645,13 @@ impl<T> StyleVec<T> { self.items.iter() } - /// Iterate over the contained maps. Note that zipping this with `items()` - /// does not yield the same result as calling `iter()` because this method - /// only returns maps once that are shared by consecutive items. This method - /// is designed for use cases where you want to check, for example, whether - /// any of the maps fulfills a specific property. - pub fn styles(&self) -> impl Iterator<Item = &StyleMap> { - self.maps.iter().map(|(map, _)| map) + /// Iterate over the contained style lists. Note that zipping this with + /// `items()` does not yield the same result as calling `iter()` because + /// this method only returns lists once that are shared by consecutive + /// items. This method is designed for use cases where you want to check, + /// for example, whether any of the lists fulfills a specific property. + pub fn styles(&self) -> impl Iterator<Item = &Styles> { + self.styles.iter().map(|(map, _)| map) } } @@ -639,35 +660,35 @@ impl StyleVec<Content> { self.items .into_iter() .zip( - self.maps + self.styles .iter() .flat_map(|(map, count)| iter::repeat(map).take(*count)), ) - .map(|(content, map)| content.styled_with_map(map.clone())) + .map(|(content, styles)| content.styled_with_map(styles.clone())) .collect() } } impl<T> Default for StyleVec<T> { fn default() -> Self { - Self { items: vec![], maps: vec![] } + Self { items: vec![], styles: vec![] } } } impl<T> FromIterator<T> for StyleVec<T> { fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self { let items: Vec<_> = iter.into_iter().collect(); - let maps = vec![(StyleMap::new(), items.len())]; - Self { items, maps } + let styles = vec![(Styles::new(), items.len())]; + Self { items, styles } } } impl<T: Debug> Debug for StyleVec<T> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_list() - .entries(self.iter().map(|(item, map)| { + .entries(self.iter().map(|(item, styles)| { crate::util::debug(|f| { - map.fmt(f)?; + styles.fmt(f)?; item.fmt(f) }) })) @@ -708,7 +729,7 @@ impl<'a, T> StyleVecBuilder<'a, T> { } /// Iterate over the contained items. - pub fn items(&self) -> std::slice::Iter<'_, T> { + pub fn elems(&self) -> std::slice::Iter<'_, T> { self.items.iter() } @@ -743,13 +764,13 @@ impl<'a, T> StyleVecBuilder<'a, T> { } } - let maps = self + let styles = self .chains .into_iter() .map(|(chain, count)| (chain.suffix(shared), count)) .collect(); - (StyleVec { items: self.items, maps }, trunk) + (StyleVec { items: self.items, styles }, trunk) } } diff --git a/src/model/typeset.rs b/src/model/typeset.rs deleted file mode 100644 index 8216d7a8..00000000 --- a/src/model/typeset.rs +++ /dev/null @@ -1,241 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; -use std::hash::Hash; -use std::num::NonZeroUsize; - -use comemo::{Constraint, Track, Tracked, TrackedMut}; - -use super::{Content, Selector, StyleChain}; -use crate::diag::SourceResult; -use crate::doc::{Document, Element, Frame, Location, Meta}; -use crate::eval::{cast_from_value, Tracer}; -use crate::geom::{Point, Transform}; -use crate::util::NonZeroExt; -use crate::World; - -/// Typeset content into a fully layouted document. -#[comemo::memoize] -pub fn typeset( - world: Tracked<dyn World>, - mut tracer: TrackedMut<Tracer>, - content: &Content, -) -> SourceResult<Document> { - let library = world.library(); - let styles = StyleChain::new(&library.styles); - - let mut document; - let mut iter = 0; - let mut introspector = Introspector::new(&[]); - - // Relayout until all introspections stabilize. - // If that doesn't happen within five attempts, we give up. - loop { - let constraint = Constraint::new(); - let mut provider = StabilityProvider::new(); - let mut vt = Vt { - world, - tracer: TrackedMut::reborrow_mut(&mut tracer), - provider: provider.track_mut(), - introspector: introspector.track_with(&constraint), - }; - - document = (library.items.layout)(&mut vt, content, styles)?; - iter += 1; - - introspector = Introspector::new(&document.pages); - introspector.init = true; - - if iter >= 5 || introspector.valid(&constraint) { - break; - } - } - - Ok(document) -} - -/// A virtual typesetter. -/// -/// Holds the state needed to [typeset] content. -pub struct Vt<'a> { - /// The compilation environment. - pub world: Tracked<'a, dyn World>, - /// The tracer for inspection of the values an expression produces. - pub tracer: TrackedMut<'a, Tracer>, - /// Provides stable identities to nodes. - pub provider: TrackedMut<'a, StabilityProvider>, - /// Provides access to information about the document. - pub introspector: Tracked<'a, Introspector>, -} - -impl Vt<'_> { - /// Mutably reborrow with a shorter lifetime. - pub fn reborrow_mut(&mut self) -> Vt<'_> { - Vt { - world: self.world, - tracer: TrackedMut::reborrow_mut(&mut self.tracer), - provider: TrackedMut::reborrow_mut(&mut self.provider), - introspector: self.introspector, - } - } -} - -/// Provides stable identities to nodes. -#[derive(Clone)] -pub struct StabilityProvider { - hashes: Vec<u128>, - checkpoints: Vec<usize>, -} - -impl StabilityProvider { - /// Create a new stability provider. - pub fn new() -> Self { - Self { hashes: vec![], checkpoints: vec![] } - } -} - -#[comemo::track] -impl StabilityProvider { - /// Produce a stable identifier for this call site. - pub fn identify(&mut self, hash: u128) -> StableId { - let count = self.hashes.iter().filter(|&&prev| prev == hash).count(); - self.hashes.push(hash); - StableId(hash, count, 0) - } - - /// Create a checkpoint of the state that can be restored. - pub fn save(&mut self) { - self.checkpoints.push(self.hashes.len()); - } - - /// Restore the last checkpoint. - pub fn restore(&mut self) { - if let Some(checkpoint) = self.checkpoints.pop() { - self.hashes.truncate(checkpoint); - } - } -} - -/// Stably identifies a call site across multiple layout passes. -/// -/// This struct is created by [`StabilityProvider::identify`]. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct StableId(u128, usize, usize); - -impl StableId { - /// Produce a variant of this id. - pub fn variant(self, n: usize) -> Self { - Self(self.0, self.1, n) - } -} - -impl Debug for StableId { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad("..") - } -} - -cast_from_value! { - StableId: "stable id", -} - -/// Provides access to information about the document. -pub struct Introspector { - init: bool, - pages: usize, - nodes: Vec<(Content, Location)>, -} - -impl Introspector { - /// Create a new introspector. - pub fn new(frames: &[Frame]) -> Self { - let mut introspector = Self { init: false, pages: frames.len(), nodes: vec![] }; - for (i, frame) in frames.iter().enumerate() { - let page = NonZeroUsize::new(1 + i).unwrap(); - introspector.extract(frame, page, Transform::identity()); - } - introspector - } - - /// Iterate over all nodes. - pub fn all(&self) -> impl Iterator<Item = &Content> { - self.nodes.iter().map(|(node, _)| node) - } - - /// Extract metadata from a frame. - fn extract(&mut self, frame: &Frame, page: NonZeroUsize, ts: Transform) { - for (pos, element) in frame.elements() { - match element { - Element::Group(group) => { - let ts = ts - .pre_concat(Transform::translate(pos.x, pos.y)) - .pre_concat(group.transform); - self.extract(&group.frame, page, ts); - } - Element::Meta(Meta::Node(content), _) - if !self - .nodes - .iter() - .any(|(prev, _)| prev.stable_id() == content.stable_id()) => - { - let pos = pos.transform(ts); - self.nodes.push((content.clone(), Location { page, pos })); - } - _ => {} - } - } - } -} - -#[comemo::track] -impl Introspector { - /// Whether this introspector is not yet initialized. - pub fn init(&self) -> bool { - self.init - } - - /// Query for all nodes for the given selector. - pub fn query(&self, selector: Selector) -> Vec<Content> { - self.all().filter(|node| selector.matches(node)).cloned().collect() - } - - /// Query for all nodes up to the given id. - pub fn query_before(&self, selector: Selector, id: StableId) -> Vec<Content> { - let mut matches = vec![]; - for node in self.all() { - if selector.matches(node) { - matches.push(node.clone()); - } - if node.stable_id() == Some(id) { - break; - } - } - matches - } - - /// Query for all nodes starting from the given id. - pub fn query_after(&self, selector: Selector, id: StableId) -> Vec<Content> { - self.all() - .skip_while(|node| node.stable_id() != Some(id)) - .filter(|node| selector.matches(node)) - .cloned() - .collect() - } - - /// The total number pages. - pub fn pages(&self) -> NonZeroUsize { - NonZeroUsize::new(self.pages).unwrap_or(NonZeroUsize::ONE) - } - - /// Find the page number for the given stable id. - pub fn page(&self, id: StableId) -> NonZeroUsize { - self.location(id).page - } - - /// Find the location for the given stable id. - pub fn location(&self, id: StableId) -> Location { - self.nodes - .iter() - .find(|(node, _)| node.stable_id() == Some(id)) - .map(|(_, loc)| *loc) - .unwrap_or(Location { page: NonZeroUsize::ONE, pos: Point::zero() }) - } -} diff --git a/src/syntax/kind.rs b/src/syntax/kind.rs index 869d77af..c96539d1 100644 --- a/src/syntax/kind.rs +++ b/src/syntax/kind.rs @@ -4,11 +4,7 @@ #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[repr(u8)] pub enum SyntaxKind { - /// Markup of which all lines must have a minimal indentation. - /// - /// Notably, the number does not determine in which column the markup - /// started, but to the right of which column all markup elements must be, - /// so it is zero except inside indent-aware constructs like lists. + /// Markup. Markup, /// Plain text without markup. Text, diff --git a/src/syntax/lexer.rs b/src/syntax/lexer.rs index 8e27d98d..3fea3fe1 100644 --- a/src/syntax/lexer.rs +++ b/src/syntax/lexer.rs @@ -473,7 +473,7 @@ impl Lexer<'_> { c if is_id_start(c) => self.ident(start), - _ => self.error("not valid here"), + _ => self.error("this character is not valid in code"), } } @@ -634,7 +634,7 @@ fn count_newlines(text: &str) -> usize { newlines } -/// Whether a string is a valid unicode identifier. +/// Whether a string is a valid Typst identifier. /// /// In addition to what is specified in the [Unicode Standard][uax31], we allow: /// - `_` as a starting character, @@ -651,13 +651,13 @@ pub fn is_ident(string: &str) -> bool { /// Whether a character can start an identifier. #[inline] -pub fn is_id_start(c: char) -> bool { +pub(crate) fn is_id_start(c: char) -> bool { c.is_xid_start() || c == '_' } /// Whether a character can continue an identifier. #[inline] -pub fn is_id_continue(c: char) -> bool { +pub(crate) fn is_id_continue(c: char) -> bool { c.is_xid_continue() || c == '_' || c == '-' } diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index ae12e818..c27547c4 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -14,6 +14,5 @@ pub use self::kind::*; pub use self::lexer::*; pub use self::node::*; pub use self::parser::*; -pub use self::reparser::*; pub use self::source::*; pub use self::span::*; diff --git a/src/syntax/source.rs b/src/syntax/source.rs index 607a2603..052e841a 100644 --- a/src/syntax/source.rs +++ b/src/syntax/source.rs @@ -9,7 +9,8 @@ use comemo::Prehashed; use unscanny::Scanner; use super::ast::Markup; -use super::{is_newline, parse, reparse, LinkedNode, Span, SyntaxNode}; +use super::reparser::reparse; +use super::{is_newline, parse, LinkedNode, Span, SyntaxNode}; use crate::diag::SourceResult; use crate::util::{PathExt, StrExt}; diff --git a/src/util/mod.rs b/src/util/mod.rs index 596282de..3e0e7aa2 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -40,7 +40,7 @@ pub fn hash128<T: Hash + ?Sized>(value: &T) -> u128 { state.finish128().as_u128() } -/// Extra methods for [`NonZeroUsize`]. +/// An extra constant for [`NonZeroUsize`]. pub trait NonZeroExt { /// The number `1`. const ONE: Self; @@ -210,7 +210,13 @@ pub fn pretty_array_like(parts: &[impl AsRef<str>], trailing_comma: bool) -> Str buf.push('('); if list.contains('\n') { buf.push('\n'); - buf.push_str(&indent(&list, 2)); + for (i, line) in list.lines().enumerate() { + if i > 0 { + buf.push('\n'); + } + buf.push_str(" "); + buf.push_str(line); + } buf.push('\n'); } else { buf.push_str(&list); @@ -218,18 +224,3 @@ pub fn pretty_array_like(parts: &[impl AsRef<str>], trailing_comma: bool) -> Str buf.push(')'); buf } - -/// Indent a string by two spaces. -pub fn indent(text: &str, amount: usize) -> String { - let mut buf = String::new(); - for (i, line) in text.lines().enumerate() { - if i > 0 { - buf.push('\n'); - } - for _ in 0..amount { - buf.push(' '); - } - buf.push_str(line); - } - buf -} diff --git a/tests/ref/compiler/show-node.png b/tests/ref/compiler/show-node.png Binary files differindex 014c45bf..cae9b932 100644 --- a/tests/ref/compiler/show-node.png +++ b/tests/ref/compiler/show-node.png diff --git a/tests/ref/layout/place.png b/tests/ref/layout/place.png Binary files differindex 10686ab5..968e613c 100644 --- a/tests/ref/layout/place.png +++ b/tests/ref/layout/place.png diff --git a/tests/src/tests.rs b/tests/src/tests.rs index 9e6d94a3..07312c5c 100644 --- a/tests/src/tests.rs +++ b/tests/src/tests.rs @@ -12,15 +12,15 @@ use elsa::FrozenVec; use once_cell::unsync::OnceCell; use tiny_skia as sk; use typst::diag::{bail, FileError, FileResult}; -use typst::doc::{Document, Element, Frame, Meta}; +use typst::doc::{Document, Frame, FrameItem, Meta}; use typst::eval::{func, Library, Value}; use typst::font::{Font, FontBook}; use typst::geom::{Abs, Color, RgbaColor, Sides, Smart}; use typst::syntax::{Source, SourceId, Span, SyntaxNode}; use typst::util::{Buffer, PathExt}; use typst::World; -use typst_library::layout::PageNode; -use typst_library::text::{TextNode, TextSize}; +use typst_library::layout::PageElem; +use typst_library::text::{TextElem, TextSize}; use unscanny::Scanner; use walkdir::WalkDir; @@ -179,12 +179,12 @@ fn library() -> Library { // exactly 100pt wide. Page height is unbounded and font size is 10pt so // that it multiplies to nice round numbers. lib.styles - .set(PageNode::set_width(Smart::Custom(Abs::pt(120.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(120.0).into()))); + lib.styles.set(PageElem::set_height(Smart::Auto)); + lib.styles.set(PageElem::set_margin(Sides::splat(Some(Smart::Custom( Abs::pt(10.0).into(), ))))); - lib.styles.set(TextNode::set_size(TextSize(Abs::pt(10.0).into()))); + lib.styles.set(TextElem::set_size(TextSize(Abs::pt(10.0).into()))); // Hook up helpers into the global scope. lib.global.scope_mut().define("test", test); @@ -732,14 +732,14 @@ fn render(frames: &[Frame]) -> sk::Pixmap { /// Draw extra boxes for links so we can see whether they are there. fn render_links(canvas: &mut sk::Pixmap, ts: sk::Transform, frame: &Frame) { - for (pos, element) in frame.elements() { + for (pos, item) in frame.items() { let ts = ts.pre_translate(pos.x.to_pt() as f32, pos.y.to_pt() as f32); - match *element { - Element::Group(ref group) => { + match *item { + FrameItem::Group(ref group) => { let ts = ts.pre_concat(group.transform.into()); render_links(canvas, ts, &group.frame); } - Element::Meta(Meta::Link(_), size) => { + FrameItem::Meta(Meta::Link(_), size) => { let w = size.x.to_pt() as f32; let h = size.y.to_pt() as f32; let rect = sk::Rect::from_xywh(0.0, 0.0, w, h).unwrap(); diff --git a/tests/typ/compiler/construct.typ b/tests/typ/compiler/construct.typ index 53fcfefb..da077e7e 100644 --- a/tests/typ/compiler/construct.typ +++ b/tests/typ/compiler/construct.typ @@ -1,4 +1,4 @@ -// Test node functions. +// Test constructors. --- // Ensure that constructor styles aren't passed down the tree. diff --git a/tests/typ/compiler/content-field.typ b/tests/typ/compiler/content-field.typ index b67c090d..031a65b8 100644 --- a/tests/typ/compiler/content-field.typ +++ b/tests/typ/compiler/content-field.typ @@ -2,10 +2,10 @@ #let compute(equation, ..vars) = { let vars = vars.named() - let f(node) = { - let func = node.func() + let f(elem) = { + let func = elem.func() if func == text { - let text = node.text + let text = elem.text if regex("^\d+$") in text { int(text) } else if text in vars { @@ -14,13 +14,13 @@ panic("unknown math variable: " + text) } } else if func == math.attach { - let value = f(node.base) - if node.has("top") { - value = calc.pow(value, f(node.top)) + let value = f(elem.base) + if elem.has("top") { + value = calc.pow(value, f(elem.top)) } value - } else if node.has("children") { - node + } else if elem.has("children") { + elem .children .filter(v => v != [ ]) .split[+] diff --git a/tests/typ/compiler/field.typ b/tests/typ/compiler/field.typ index 49ffca04..7529cd85 100644 --- a/tests/typ/compiler/field.typ +++ b/tests/typ/compiler/field.typ @@ -13,9 +13,9 @@ } --- -// Test field on node. -#show list: node => { - test(node.children.len(), 3) +// Test fields on elements. +#show list: it => { + test(it.children.len(), 3) } - A @@ -31,8 +31,8 @@ #false.ok --- -// Error: 29-32 content does not contain field "fun" -#show heading: node => node.fun +// Error: 25-28 content does not contain field "fun" +#show heading: it => it.fun = A --- diff --git a/tests/typ/compiler/show-node.typ b/tests/typ/compiler/show-node.typ index 0c1c1f52..99a4364e 100644 --- a/tests/typ/compiler/show-node.typ +++ b/tests/typ/compiler/show-node.typ @@ -1,4 +1,4 @@ -// Test node show rules. +// Test show rules. --- // Override lists. @@ -83,12 +83,12 @@ Another text. = Heading --- -// Error: 7-12 this function is not selectable -#show upper: it => {} +#show text: none +Hey --- -// Error: 7-11 to select text, please use a string or regex instead -#show text: it => {} +// Error: 7-12 only element functions can be used as selectors +#show upper: it => {} --- // Error: 16-20 expected content or function, found integer @@ -96,7 +96,7 @@ Another text. = Heading --- -// Error: 7-10 expected string, label, function, regular expression, or selector, found color +// Error: 7-10 expected function, label, string, regular expression, or selector, found color #show red: [] --- diff --git a/tests/typ/compute/construct.typ b/tests/typ/compute/construct.typ index 80fa9a2b..dc146719 100644 --- a/tests/typ/compute/construct.typ +++ b/tests/typ/compute/construct.typ @@ -21,11 +21,11 @@ --- // Error for values that are out of range. -// Error: 11-14 must be between 0 and 255 +// Error: 11-14 number must be between 0 and 255 #test(rgb(-30, 15, 50)) --- -// Error: 6-11 string contains non-hexadecimal letters +// Error: 6-11 color string contains non-hexadecimal letters #rgb("lol") --- diff --git a/tests/typ/layout/list-marker.typ b/tests/typ/layout/list-marker.typ index 0d223b58..97d1ce91 100644 --- a/tests/typ/layout/list-marker.typ +++ b/tests/typ/layout/list-marker.typ @@ -30,5 +30,5 @@ - a bad marker --- -// Error: 19-21 must contain at least one marker +// Error: 19-21 array must contain at least one marker #set list(marker: ()) diff --git a/tests/typ/layout/pad.typ b/tests/typ/layout/pad.typ index 2cc4d4ee..88e8510f 100644 --- a/tests/typ/layout/pad.typ +++ b/tests/typ/layout/pad.typ @@ -19,7 +19,7 @@ Hi #box(pad(left: 10pt)[A]) there #pad(left: 10pt, right: 10pt)[PL #h(1fr) PR] --- -// Test that the pad node doesn't consume the whole region. +// Test that the pad element doesn't consume the whole region. #set page(height: 6cm) #align(left)[Before] #pad(10pt, image("/tiger.jpg")) diff --git a/tests/typ/layout/par-bidi.typ b/tests/typ/layout/par-bidi.typ index d8c05d23..90c6a55e 100644 --- a/tests/typ/layout/par-bidi.typ +++ b/tests/typ/layout/par-bidi.typ @@ -51,5 +51,5 @@ Lריווח #h(1cm) R // Test setting a vertical direction. // Ref: false -// Error: 16-19 must be horizontal +// Error: 16-19 text direction must be horizontal #set text(dir: ttb) diff --git a/tests/typ/layout/place.typ b/tests/typ/layout/place.typ index 18f7642d..f0ffaf57 100644 --- a/tests/typ/layout/place.typ +++ b/tests/typ/layout/place.typ @@ -7,7 +7,7 @@ = Placement #place(right, image("/tiger.jpg", width: 1.8cm)) Hi there. This is \ -a placed node. \ +a placed element. \ Unfortunately, \ the line breaks still had to be inserted manually. @@ -25,7 +25,7 @@ the line breaks still had to be inserted manually. ) --- -// Test how the placed node interacts with paragraph spacing around it. +// Test how the placed element interacts with paragraph spacing around it. #set page("a8", height: 60pt) First diff --git a/tests/typ/meta/counter.typ b/tests/typ/meta/counter.typ index c3b97629..a3814216 100644 --- a/tests/typ/meta/counter.typ +++ b/tests/typ/meta/counter.typ @@ -41,7 +41,7 @@ In #counter(heading).display(). At Beta, it was #locate(loc => { let it = query(heading, loc).find(it => it.body == [Beta]) - numbering(it.numbering, ..counter(heading).at(it.id())) + numbering(it.numbering, ..counter(heading).at(it.location())) }) --- diff --git a/tests/typ/meta/document.typ b/tests/typ/meta/document.typ index f2c7a8bb..bc9d4908 100644 --- a/tests/typ/meta/document.typ +++ b/tests/typ/meta/document.typ @@ -19,23 +19,23 @@ What's up? --- Hello -// Error: 2-30 must appear before any content +// Error: 2-30 document set rules must appear before any content #set document(title: "Hello") --- #box[ - // Error: 4-32 not allowed here + // Error: 4-32 document set rules are not allowed inside of containers #set document(title: "Hello") ] --- #box[ - // Error: 4-18 not allowed here + // Error: 4-18 page configuration is not allowed inside of containers #set page("a4") ] --- #box[ - // Error: 4-15 not allowed here + // Error: 4-15 pagebreaks are not allowed inside of containers #pagebreak() ] diff --git a/tests/typ/meta/query.typ b/tests/typ/meta/query.typ index 38eadfab..8f8dcd3d 100644 --- a/tests/typ/meta/query.typ +++ b/tests/typ/meta/query.typ @@ -47,10 +47,10 @@ for it in elements [ Figure #numbering(it.numbering, - ..counter(figure).at(it.id())): + ..counter(figure).at(it.location())): #it.caption #box(width: 1fr, repeat[.]) - #counter(page).at(it.id()).first() \ + #counter(page).at(it.location()).first() \ ] }) diff --git a/tests/typ/meta/state.typ b/tests/typ/meta/state.typ index 8dfab05b..a7c3d26a 100644 --- a/tests/typ/meta/state.typ +++ b/tests/typ/meta/state.typ @@ -10,9 +10,9 @@ $ 2 + 3 $ #s.update(double) Is: #s.display(), -Was: #locate(id => { - let it = query(math.equation, id).first() - s.at(it.id()) +Was: #locate(location => { + let it = query(math.equation, location).first() + s.at(it.location()) }). --- diff --git a/tests/typ/text/features.typ b/tests/typ/text/features.typ index 360e8a11..69a7064f 100644 --- a/tests/typ/text/features.typ +++ b/tests/typ/text/features.typ @@ -47,7 +47,7 @@ fi vs. #text(features: (liga: 0))[No fi] #set text(stylistic-set: false) --- -// Error: 26-28 must be between 1 and 20 +// Error: 26-28 stylistic set must be between 1 and 20 #set text(stylistic-set: 25) --- diff --git a/tests/typ/text/symbol.typ b/tests/typ/text/symbol.typ index 949f82d2..f7179449 100644 --- a/tests/typ/text/symbol.typ +++ b/tests/typ/text/symbol.typ @@ -14,5 +14,5 @@ #sym.arrow.r;this and this#sym.arrow.l; --- -// Error: 13-20 unknown modifier +// Error: 13-20 unknown symbol modifier #emoji.face.garbage |
