Renaming and refactoring

This commit is contained in:
Laurenz 2023-03-19 22:28:49 +01:00
parent d6aaae0cea
commit ab43bd802e
118 changed files with 2643 additions and 2538 deletions

View File

@ -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 the list items just yet. This only happens lazily once the list items are
layouted. layouted.
When we a have realized the content into a layoutable When we a have realized the content into a layoutable element, we can then
node, we can then layout it into _regions,_ which describe the space into which layout it into _regions,_ which describe the space into which the content shall
the content shall be layouted. Within these, a node is free to layout itself be layouted. Within these, an element is free to layout itself as it sees fit,
as it sees fit, returning one `Frame` per region it wants to occupy. returning one `Frame` per region it wants to occupy.
**Introspection:** **Introspection:**
How content layouts (and realizes) may depend on how _it itself_ is layouted 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. stabilize, so we give up after five attempts.
**Incremental:** **Incremental:**
Layout caching happens at the granularity of a node. This is important because Layout caching happens at the granularity of the element. This is important
overall layout is the most expensive compilation phase, so we want to reuse as because overall layout is the most expensive compilation phase, so we want to
much as possible. reuse as much as possible.
## Export ## Export

View File

@ -18,7 +18,7 @@ use typst::doc::Frame;
use typst::eval::{CastInfo, Func, FuncInfo, Library, Module, ParamInfo, Value}; use typst::eval::{CastInfo, Func, FuncInfo, Library, Module, ParamInfo, Value};
use typst::font::{Font, FontBook}; use typst::font::{Font, FontBook};
use typst::geom::{Abs, Sides, Smart}; use typst::geom::{Abs, Sides, Smart};
use typst_library::layout::PageNode; use typst_library::layout::PageElem;
use unscanny::Scanner; use unscanny::Scanner;
static SRC: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/src"); 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(|| { static LIBRARY: Lazy<Prehashed<Library>> = Lazy::new(|| {
let mut lib = typst_library::build(); let mut lib = typst_library::build();
lib.styles lib.styles
.set(PageNode::set_width(Smart::Custom(Abs::pt(240.0).into()))); .set(PageElem::set_width(Smart::Custom(Abs::pt(240.0).into())));
lib.styles.set(PageNode::set_height(Smart::Auto)); lib.styles.set(PageElem::set_height(Smart::Auto));
lib.styles.set(PageNode::set_margin(Sides::splat(Some(Smart::Custom( lib.styles.set(PageElem::set_margin(Sides::splat(Some(Smart::Custom(
Abs::pt(15.0).into(), Abs::pt(15.0).into(),
))))); )))));
typst::eval::set_lang_items(lib.items.clone()); typst::eval::set_lang_items(lib.items.clone());
@ -299,8 +299,8 @@ pub struct FuncModel {
pub name: &'static str, pub name: &'static str,
pub display: &'static str, pub display: &'static str,
pub oneliner: &'static str, pub oneliner: &'static str,
pub details: Html,
pub showable: bool, pub showable: bool,
pub details: Html,
pub params: Vec<ParamModel>, pub params: Vec<ParamModel>,
pub returns: Vec<&'static str>, pub returns: Vec<&'static str>,
} }
@ -336,8 +336,8 @@ fn func_model(resolver: &dyn Resolver, func: &Func, info: &FuncInfo) -> FuncMode
name: info.name.into(), name: info.name.into(),
display: info.display, display: info.display,
oneliner: oneliner(info.docs), oneliner: oneliner(info.docs),
showable: func.element().is_some(),
details: Html::markdown(resolver, info.docs), 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(), params: info.params.iter().map(|param| param_model(resolver, param)).collect(),
returns: info.returns.clone(), returns: info.returns.clone(),
} }
@ -632,7 +632,7 @@ fn symbol_page(resolver: &dyn Resolver, parent: &str, name: &str) -> PageModel {
.find(|&(_, x)| x == c) .find(|&(_, x)| x == c)
.map(|(s, _)| s), .map(|(s, _)| s),
codepoint: c as u32, 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) unicode_name: unicode_names2::name(c)
.map(|s| s.to_string().to_title_case()), .map(|s| s.to_string().to_title_case()),
alternates: symbol alternates: symbol

View File

@ -138,7 +138,7 @@ For loops can iterate over a variety of collections:
- `{for value in array {..}}` \ - `{for value in array {..}}` \
`{for index, value in array {..}}`\ `{for index, value in array {..}}`\
Iterates over the items in the [array]($type/array). Can also provide the 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 value in dict {..}}` \
`{for key, value in dict {..}}` \ `{for key, value in dict {..}}` \

View File

@ -420,7 +420,7 @@ A sequence of values.
You can construct an array by enclosing a comma-separated 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. 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 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). iterate over an array using a [for loop]($scripting/#loops).
Arrays can be added together with the `+` operator, Arrays can be added together with the `+` operator,
@ -453,26 +453,26 @@ The number of values in the array.
- returns: integer - returns: integer
### first() ### 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. May be used on the left-hand side of an assignment.
Fails with an error if the array is empty. Fails with an error if the array is empty.
- returns: any - returns: any
### last() ### 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. May be used on the left-hand side of an assignment.
Fails with an error if the array is empty. Fails with an error if the array is empty.
- returns: any - returns: any
### at() ### 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. May be used on the left-hand side of an assignment.
Fails with an error if the index is out of bounds. Fails with an error if the index is out of bounds.
- index: integer (positional, required) - index: integer (positional, required)
The index at which to retrieve the element. The index at which to retrieve the item.
- returns: any - returns: any
### push() ### push()
@ -482,7 +482,7 @@ Add a value to the end of the array.
The value to insert at the end of the array. The value to insert at the end of the array.
### pop() ### 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. Fails with an error if the array is empty.
- returns: any - 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. Fails with an error if the index is out of bounds.
- index: integer (positional, required) - index: integer (positional, required)
The index at which to insert the element. The index at which to insert the item.
- value: any (positional, required) - value: any (positional, required)
The value to insert into the array. 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. Remove the value at the specified index from the array and return it.
- index: integer (positional, required) - index: integer (positional, required)
The index at which to remove the element. The index at which to remove the item.
- returns: any - returns: any
### slice() ### 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 The end index (exclusive). If omitted, the whole slice until the end of the
array is extracted. array is extracted.
- count: integer (named) - 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`. count` as the `end` position. Mutually exclusive with `end`.
- returns: array - returns: array
@ -529,59 +529,59 @@ of `{(1, 2, 3).contains(2)}`.
- returns: boolean - returns: boolean
### find() ### 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. returns the first match or `{none}` if there is no match.
- searcher: function (positional, required) - 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 - returns: any or none
### position() ### 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. returns the index of the first match or `{none}` if there is no match.
- searcher: function (positional, required) - 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 - returns: integer or none
### filter() ### 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. given function returns true.
- test: function (positional, required) - 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 - returns: array
### map() ### 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. transformed with the given function.
- mapper: function (positional, required) - mapper: function (positional, required)
The function to apply to each element. The function to apply to each item.
- returns: array - returns: array
### fold() ### 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) - init: any (positional, required)
The initial value to start with. The initial value to start with.
- folder: function (positional, required) - folder: function (positional, required)
The folding function. Must have two parameters: One for the accumulated value The folding function. Must have two parameters: One for the accumulated value
and one for an element. and one for an item.
- returns: any - returns: any
### 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) - 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 - returns: boolean
### all() ### 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) - 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 - returns: boolean
### flatten() ### flatten()
@ -590,21 +590,21 @@ Combine all nested arrays into a single flat one.
- returns: array - returns: array
### rev() ### 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 - returns: array
### join() ### join()
Combine all elements in the array into one. Combine all items in the array into one.
- separator: any (positional) - 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) - last: any (named)
An alternative separator between the last two elements An alternative separator between the last two items
- returns: any - returns: any
### sorted() ### sorted()
Return a new array with the same elements, but sorted. Return a new array with the same items, but sorted.
- returns: array - returns: array
@ -658,7 +658,7 @@ present in the dictionary.
Fails with an error if the key is not part of the dictionary. Fails with an error if the key is not part of the dictionary.
- index: integer (positional, required) - index: integer (positional, required)
The index at which to retrieve the element. The index at which to retrieve the item.
- returns: any - returns: any
### insert() ### insert()

View File

@ -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 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 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 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 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 use as multiple arguments for the grid. We can do that by using the [`spread`
[`spread` operator]($type/arguments). It takes an array and applies each of its elements as a separate argument to the function. 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: The resulting template function looks like this:

View File

@ -1,7 +1,6 @@
use std::num::NonZeroI64; use std::num::NonZeroI64;
use std::str::FromStr; use std::str::FromStr;
use ecow::EcoVec;
use typst::eval::Regex; use typst::eval::Regex;
use crate::prelude::*; use crate::prelude::*;
@ -173,12 +172,12 @@ cast_from_value! {
Component, Component,
v: i64 => match v { v: i64 => match v {
0 ..= 255 => Self(v as u8), 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()) { v: Ratio => if (0.0 ..= 1.0).contains(&v.get()) {
Self((v.get() * 255.0).round() as u8) Self((v.get() * 255.0).round() as u8)
} else { } 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()) { v: Ratio => if (0.0 ..= 1.0).contains(&v.get()) {
Self((v.get() * 255.0).round() as u8) Self((v.get() * 255.0).round() as u8)
} else { } else {
Err("must be between 0% and 100%")? Err("ratio must be between 0% and 100%")?
}, },
} }
@ -258,14 +257,14 @@ pub fn symbol(
#[variadic] #[variadic]
variants: Vec<Spanned<Variant>>, variants: Vec<Spanned<Variant>>,
) -> Value { ) -> Value {
let mut list = EcoVec::new(); let mut list = Vec::new();
for Spanned { v, span } in variants { for Spanned { v, span } in variants {
if list.iter().any(|(prev, _)| &v.0 == prev) { if list.iter().any(|(prev, _)| &v.0 == prev) {
bail!(span, "duplicate variant"); bail!(span, "duplicate variant");
} }
list.push((v.0, v.1)); 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. /// A value that can be cast to a symbol.

View File

@ -134,5 +134,5 @@ pub fn eval(
source: Spanned<String>, source: Spanned<String>,
) -> Value { ) -> Value {
let Spanned { v: text, span } = source; let Spanned { v: text, span } = source;
typst::eval::eval_code_str(vm.world(), &text, span)? typst::eval::eval_string(vm.world(), &text, span)?
} }

View File

@ -14,8 +14,8 @@ use crate::prelude::*;
/// ///
/// Display: Align /// Display: Align
/// Category: layout /// Category: layout
#[node(Show)] #[element(Show)]
pub struct AlignNode { pub struct AlignElem {
/// The alignment along both axes. /// The alignment along both axes.
/// ///
/// Possible values for horizontal alignments are: /// Possible values for horizontal alignments are:
@ -57,7 +57,7 @@ pub struct AlignNode {
pub body: Content, pub body: Content,
} }
impl Show for AlignNode { impl Show for AlignElem {
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> { fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
Ok(self Ok(self
.body() .body()

View File

@ -1,5 +1,5 @@
use crate::prelude::*; use crate::prelude::*;
use crate::text::TextNode; use crate::text::TextElem;
/// Separate a region into multiple equally sized columns. /// Separate a region into multiple equally sized columns.
/// ///
@ -32,8 +32,8 @@ use crate::text::TextNode;
/// ///
/// Display: Columns /// Display: Columns
/// Category: layout /// Category: layout
#[node(Layout)] #[element(Layout)]
pub struct ColumnsNode { pub struct ColumnsElem {
/// The number of columns. /// The number of columns.
#[positional] #[positional]
#[default(NonZeroUsize::new(2).unwrap())] #[default(NonZeroUsize::new(2).unwrap())]
@ -49,7 +49,7 @@ pub struct ColumnsNode {
pub body: Content, pub body: Content,
} }
impl Layout for ColumnsNode { impl Layout for ColumnsElem {
fn layout( fn layout(
&self, &self,
vt: &mut Vt, vt: &mut Vt,
@ -88,7 +88,7 @@ impl Layout for ColumnsNode {
let mut frames = body.layout(vt, styles, pod)?.into_iter(); let mut frames = body.layout(vt, styles, pod)?.into_iter();
let mut finished = vec![]; 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; let total_regions = (frames.len() as f32 / columns as f32).ceil() as usize;
// Stitch together the columns for each region. // Stitch together the columns for each region.
@ -151,15 +151,15 @@ impl Layout for ColumnsNode {
/// ///
/// Display: Column Break /// Display: Column Break
/// Category: layout /// Category: layout
#[node(Behave)] #[element(Behave)]
pub struct ColbreakNode { pub struct ColbreakElem {
/// If `{true}`, the column break is skipped if the current column is /// If `{true}`, the column break is skipped if the current column is
/// already empty. /// already empty.
#[default(false)] #[default(false)]
pub weak: bool, pub weak: bool,
} }
impl Behave for ColbreakNode { impl Behave for ColbreakElem {
fn behaviour(&self) -> Behaviour { fn behaviour(&self) -> Behaviour {
if self.weak(StyleChain::default()) { if self.weak(StyleChain::default()) {
Behaviour::Weak(1) Behaviour::Weak(1)

View File

@ -1,4 +1,4 @@
use super::VNode; use super::VElem;
use crate::layout::Spacing; use crate::layout::Spacing;
use crate::prelude::*; use crate::prelude::*;
@ -21,8 +21,8 @@ use crate::prelude::*;
/// ///
/// Display: Box /// Display: Box
/// Category: layout /// Category: layout
#[node(Layout)] #[element(Layout)]
pub struct BoxNode { pub struct BoxElem {
/// The width of the box. /// The width of the box.
/// ///
/// Boxes can have [fractional]($type/fraction) widths, as the example /// Boxes can have [fractional]($type/fraction) widths, as the example
@ -93,7 +93,7 @@ pub struct BoxNode {
pub body: Option<Content>, pub body: Option<Content>,
} }
impl Layout for BoxNode { impl Layout for BoxElem {
fn layout( fn layout(
&self, &self,
vt: &mut Vt, vt: &mut Vt,
@ -183,8 +183,8 @@ impl Layout for BoxNode {
/// ///
/// Display: Block /// Display: Block
/// Category: layout /// Category: layout
#[node(Layout)] #[element(Layout)]
pub struct BlockNode { pub struct BlockElem {
/// The block's width. /// The block's width.
/// ///
/// ```example /// ```example
@ -278,11 +278,11 @@ pub struct BlockNode {
#[parse( #[parse(
let spacing = args.named("spacing")?; let spacing = args.named("spacing")?;
args.named("above")? args.named("above")?
.map(VNode::block_around) .map(VElem::block_around)
.or_else(|| spacing.map(VNode::block_spacing)) .or_else(|| spacing.map(VElem::block_spacing))
)] )]
#[default(VNode::block_spacing(Em::new(1.2).into()))] #[default(VElem::block_spacing(Em::new(1.2).into()))]
pub above: VNode, pub above: VElem,
/// The spacing between this block and its successor. Takes precedence /// The spacing between this block and its successor. Takes precedence
/// over `spacing`. /// over `spacing`.
@ -290,11 +290,11 @@ pub struct BlockNode {
/// The default value is `{1.2em}`. /// The default value is `{1.2em}`.
#[parse( #[parse(
args.named("below")? args.named("below")?
.map(VNode::block_around) .map(VElem::block_around)
.or_else(|| spacing.map(VNode::block_spacing)) .or_else(|| spacing.map(VElem::block_spacing))
)] )]
#[default(VNode::block_spacing(Em::new(1.2).into()))] #[default(VElem::block_spacing(Em::new(1.2).into()))]
pub below: VNode, pub below: VElem,
/// The contents of the block. /// The contents of the block.
#[positional] #[positional]
@ -308,7 +308,7 @@ pub struct BlockNode {
pub sticky: bool, pub sticky: bool,
} }
impl Layout for BlockNode { impl Layout for BlockElem {
fn layout( fn layout(
&self, &self,
vt: &mut Vt, vt: &mut Vt,

View File

@ -1,9 +1,9 @@
use std::str::FromStr; 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::meta::{Numbering, NumberingPattern};
use crate::prelude::*; use crate::prelude::*;
use crate::text::TextNode; use crate::text::TextElem;
use super::GridLayouter; use super::GridLayouter;
@ -50,8 +50,8 @@ use super::GridLayouter;
/// ///
/// Display: Numbered List /// Display: Numbered List
/// Category: layout /// Category: layout
#[node(Layout)] #[element(Layout)]
pub struct EnumNode { pub struct EnumElem {
/// If this is `{false}`, the items are spaced apart with /// If this is `{false}`, the items are spaced apart with
/// [enum spacing]($func/enum.spacing). If it is `{true}`, they use normal /// [enum spacing]($func/enum.spacing). If it is `{true}`, they use normal
/// [leading]($func/par.leading) instead. This makes the enumeration more /// [leading]($func/par.leading) instead. This makes the enumeration more
@ -153,7 +153,7 @@ pub struct EnumNode {
parents: Parent, parents: Parent,
} }
impl Layout for EnumNode { impl Layout for EnumElem {
fn layout( fn layout(
&self, &self,
vt: &mut Vt, vt: &mut Vt,
@ -164,10 +164,10 @@ impl Layout for EnumNode {
let indent = self.indent(styles); let indent = self.indent(styles);
let body_indent = self.body_indent(styles); let body_indent = self.body_indent(styles);
let gutter = if self.tight(styles) { let gutter = if self.tight(styles) {
ParNode::leading_in(styles).into() ParElem::leading_in(styles).into()
} else { } else {
self.spacing(styles) self.spacing(styles)
.unwrap_or_else(|| BlockNode::below_in(styles).amount()) .unwrap_or_else(|| BlockElem::below_in(styles).amount())
}; };
let mut cells = vec![]; let mut cells = vec![];
@ -186,7 +186,7 @@ impl Layout for EnumNode {
} else { } else {
match &numbering { match &numbering {
Numbering::Pattern(pattern) => { 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(), other => other.apply_vt(vt, &[number])?.display(),
} }
@ -221,7 +221,7 @@ impl Layout for EnumNode {
/// ///
/// Display: Numbered List Item /// Display: Numbered List Item
/// Category: layout /// Category: layout
#[node] #[element]
pub struct EnumItem { pub struct EnumItem {
/// The item's number. /// The item's number.
#[positional] #[positional]

View File

@ -1,24 +1,22 @@
use typst::model::StyledNode; use super::{AlignElem, BlockElem, ColbreakElem, ParElem, PlaceElem, Spacing, VElem};
use super::{AlignNode, BlockNode, ColbreakNode, ParNode, PlaceNode, Spacing, VNode};
use crate::prelude::*; 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. /// the contents of boxes.
/// ///
/// Display: Flow /// Display: Flow
/// Category: layout /// Category: layout
#[node(Layout)] #[element(Layout)]
pub struct FlowNode { pub struct FlowElem {
/// The children that will be arranges into a flow. /// The children that will be arranges into a flow.
#[variadic] #[variadic]
pub children: Vec<Content>, pub children: Vec<Content>,
} }
impl Layout for FlowNode { impl Layout for FlowElem {
fn layout( fn layout(
&self, &self,
vt: &mut Vt, vt: &mut Vt,
@ -27,29 +25,27 @@ impl Layout for FlowNode {
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
let mut layouter = FlowLayouter::new(regions); let mut layouter = FlowLayouter::new(regions);
for mut child in self.children() { for mut child in &self.children() {
let map;
let outer = styles; let outer = styles;
let mut styles = outer; let mut styles = styles;
if let Some(node) = child.to::<StyledNode>() { if let Some((elem, map)) = child.to_styled() {
map = node.styles(); child = elem;
styles = outer.chain(&map); styles = outer.chain(&map);
child = node.body();
} }
if let Some(node) = child.to::<VNode>() { if let Some(elem) = child.to::<VElem>() {
layouter.layout_spacing(node, styles); layouter.layout_spacing(elem, styles);
} else if let Some(node) = child.to::<ParNode>() { } else if let Some(elem) = child.to::<ParElem>() {
layouter.layout_par(vt, node, styles)?; layouter.layout_par(vt, elem, styles)?;
} else if child.is::<RectNode>() } else if child.is::<RectElem>()
|| child.is::<SquareNode>() || child.is::<SquareElem>()
|| child.is::<EllipseNode>() || child.is::<EllipseElem>()
|| child.is::<CircleNode>() || child.is::<CircleElem>()
|| child.is::<ImageNode>() || child.is::<ImageElem>()
{ {
let layoutable = child.with::<dyn Layout>().unwrap(); let layoutable = child.with::<dyn Layout>().unwrap();
layouter.layout_single(vt, layoutable, styles)?; layouter.layout_single(vt, layoutable, styles)?;
} else if child.is::<MetaNode>() { } else if child.is::<MetaElem>() {
let mut frame = Frame::new(Size::zero()); let mut frame = Frame::new(Size::zero());
frame.meta(styles, true); frame.meta(styles, true);
layouter.items.push(FlowItem::Frame( layouter.items.push(FlowItem::Frame(
@ -59,7 +55,7 @@ impl Layout for FlowNode {
)); ));
} else if child.can::<dyn Layout>() { } else if child.can::<dyn Layout>() {
layouter.layout_multiple(vt, &child, styles)?; 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() if !layouter.regions.backlog.is_empty() || layouter.regions.last.is_some()
{ {
layouter.finish_region(); layouter.finish_region();
@ -122,13 +118,13 @@ impl<'a> FlowLayouter<'a> {
} }
/// Layout vertical spacing. /// Layout vertical spacing.
fn layout_spacing(&mut self, node: &VNode, styles: StyleChain) { fn layout_spacing(&mut self, v: &VElem, styles: StyleChain) {
self.layout_item(match node.amount() { self.layout_item(match v.amount() {
Spacing::Rel(v) => FlowItem::Absolute( Spacing::Rel(rel) => FlowItem::Absolute(
v.resolve(styles).relative_to(self.initial.y), rel.resolve(styles).relative_to(self.initial.y),
node.weakness(styles) > 0, 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( fn layout_par(
&mut self, &mut self,
vt: &mut Vt, vt: &mut Vt,
par: &ParNode, par: &ParElem,
styles: StyleChain, styles: StyleChain,
) -> SourceResult<()> { ) -> SourceResult<()> {
let aligns = AlignNode::alignment_in(styles).resolve(styles); let aligns = AlignElem::alignment_in(styles).resolve(styles);
let leading = ParNode::leading_in(styles); let leading = ParElem::leading_in(styles);
let consecutive = self.last_was_par; let consecutive = self.last_was_par;
let frames = par let frames = par
.layout(vt, styles, consecutive, self.regions.base(), self.regions.expand.x)? .layout(vt, styles, consecutive, self.regions.base(), self.regions.expand.x)?
@ -185,8 +181,8 @@ impl<'a> FlowLayouter<'a> {
content: &dyn Layout, content: &dyn Layout,
styles: StyleChain, styles: StyleChain,
) -> SourceResult<()> { ) -> SourceResult<()> {
let aligns = AlignNode::alignment_in(styles).resolve(styles); let aligns = AlignElem::alignment_in(styles).resolve(styles);
let sticky = BlockNode::sticky_in(styles); let sticky = BlockElem::sticky_in(styles);
let pod = Regions::one(self.regions.base(), Axes::splat(false)); let pod = Regions::one(self.regions.base(), Axes::splat(false));
let frame = content.layout(vt, styles, pod)?.into_frame(); let frame = content.layout(vt, styles, pod)?.into_frame();
self.layout_item(FlowItem::Frame(frame, aligns, sticky)); self.layout_item(FlowItem::Frame(frame, aligns, sticky));
@ -201,9 +197,9 @@ impl<'a> FlowLayouter<'a> {
block: &Content, block: &Content,
styles: StyleChain, styles: StyleChain,
) -> SourceResult<()> { ) -> SourceResult<()> {
// Placed nodes that are out of flow produce placed items which aren't // Placed elements that are out of flow produce placed items which
// aligned later. // aren't aligned later.
if let Some(placed) = block.to::<PlaceNode>() { if let Some(placed) = block.to::<PlaceElem>() {
if placed.out_of_flow(styles) { if placed.out_of_flow(styles) {
let frame = block.layout(vt, styles, self.regions)?.into_frame(); let frame = block.layout(vt, styles, self.regions)?.into_frame();
self.layout_item(FlowItem::Placed(frame)); self.layout_item(FlowItem::Placed(frame));
@ -212,17 +208,17 @@ impl<'a> FlowLayouter<'a> {
} }
// How to align the block. // 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) align.alignment(styles)
} else if let Some(styled) = block.to::<StyledNode>() { } else if let Some((_, local)) = block.to_styled() {
AlignNode::alignment_in(styles.chain(&styled.styles())) AlignElem::alignment_in(styles.chain(local))
} else { } else {
AlignNode::alignment_in(styles) AlignElem::alignment_in(styles)
} }
.resolve(styles); .resolve(styles);
// Layout the block itself. // Layout the block itself.
let sticky = BlockNode::sticky_in(styles); let sticky = BlockElem::sticky_in(styles);
let fragment = block.layout(vt, styles, self.regions)?; let fragment = block.layout(vt, styles, self.regions)?;
for (i, frame) in fragment.into_iter().enumerate() { for (i, frame) in fragment.into_iter().enumerate() {
if i > 0 { if i > 0 {

View File

@ -1,5 +1,5 @@
use crate::prelude::*; use crate::prelude::*;
use crate::text::TextNode; use crate::text::TextElem;
use super::Sizing; use super::Sizing;
@ -61,8 +61,8 @@ use super::Sizing;
/// ///
/// Display: Grid /// Display: Grid
/// Category: layout /// Category: layout
#[node(Layout)] #[element(Layout)]
pub struct GridNode { pub struct GridElem {
/// Defines the column sizes. /// Defines the column sizes.
/// ///
/// Either specify a track size array or provide an integer to create a grid /// 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>, pub children: Vec<Content>,
} }
impl Layout for GridNode { impl Layout for GridElem {
fn layout( fn layout(
&self, &self,
vt: &mut Vt, vt: &mut Vt,
@ -257,7 +257,7 @@ impl<'a, 'v> GridLayouter<'a, 'v> {
} }
// Reverse for RTL. // Reverse for RTL.
let is_rtl = TextNode::dir_in(styles) == Dir::RTL; let is_rtl = TextElem::dir_in(styles) == Dir::RTL;
if is_rtl { if is_rtl {
cols.reverse(); cols.reverse();
} }

View File

@ -15,15 +15,15 @@ use crate::prelude::*;
/// ///
/// Display: Hide /// Display: Hide
/// Category: layout /// Category: layout
#[node(Show)] #[element(Show)]
pub struct HideNode { pub struct HideElem {
/// The content to hide. /// The content to hide.
#[required] #[required]
pub body: Content, pub body: Content,
} }
impl Show for HideNode { impl Show for HideElem {
fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> { 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])))
} }
} }

View File

@ -1,6 +1,6 @@
use crate::layout::{BlockNode, ParNode, Sizing, Spacing}; use crate::layout::{BlockElem, ParElem, Sizing, Spacing};
use crate::prelude::*; use crate::prelude::*;
use crate::text::TextNode; use crate::text::TextElem;
use super::GridLayouter; use super::GridLayouter;
@ -36,8 +36,8 @@ use super::GridLayouter;
/// ///
/// Display: Bullet List /// Display: Bullet List
/// Category: layout /// Category: layout
#[node(Layout)] #[element(Layout)]
pub struct ListNode { pub struct ListElem {
/// If this is `{false}`, the items are spaced apart with [list /// If this is `{false}`, the items are spaced apart with [list
/// spacing]($func/list.spacing). If it is `{true}`, they use normal /// spacing]($func/list.spacing). If it is `{true}`, they use normal
/// [leading]($func/par.leading) instead. This makes the list more compact, /// [leading]($func/par.leading) instead. This makes the list more compact,
@ -111,7 +111,7 @@ pub struct ListNode {
depth: Depth, depth: Depth,
} }
impl Layout for ListNode { impl Layout for ListElem {
fn layout( fn layout(
&self, &self,
vt: &mut Vt, vt: &mut Vt,
@ -121,10 +121,10 @@ impl Layout for ListNode {
let indent = self.indent(styles); let indent = self.indent(styles);
let body_indent = self.body_indent(styles); let body_indent = self.body_indent(styles);
let gutter = if self.tight(styles) { let gutter = if self.tight(styles) {
ParNode::leading_in(styles).into() ParElem::leading_in(styles).into()
} else { } else {
self.spacing(styles) self.spacing(styles)
.unwrap_or_else(|| BlockNode::below_in(styles).amount()) .unwrap_or_else(|| BlockElem::below_in(styles).amount())
}; };
let depth = self.depth(styles); let depth = self.depth(styles);
@ -160,7 +160,7 @@ impl Layout for ListNode {
/// ///
/// Display: Bullet List Item /// Display: Bullet List Item
/// Category: layout /// Category: layout
#[node] #[element]
pub struct ListItem { pub struct ListItem {
/// The item's body. /// The item's body.
#[required] #[required]
@ -187,7 +187,7 @@ impl ListMarker {
.get(depth) .get(depth)
.or(list.last()) .or(list.last())
.cloned() .cloned()
.unwrap_or_else(|| TextNode::packed('•')), .unwrap_or_else(|| TextElem::packed('•')),
Self::Func(func) => func.call_vt(vt, [Value::Int(depth as i64)])?.display(), 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]), v: Content => Self::Content(vec![v]),
array: Array => { array: Array => {
if array.len() == 0 { 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()) Self::Content(array.into_iter().map(Value::display).collect())
}, },

View File

@ -10,7 +10,7 @@ pub fn measure(
/// The content whose size to measure. /// The content whose size to measure.
content: Content, content: Content,
/// The styles with which to layout the content. /// The styles with which to layout the content.
styles: StyleMap, styles: Styles,
) -> Value { ) -> Value {
let pod = Regions::one(Axes::splat(Abs::inf()), Axes::splat(false)); let pod = Regions::one(Axes::splat(Abs::inf()), Axes::splat(false));
let styles = StyleChain::new(&styles); let styles = StyleChain::new(&styles);

View File

@ -50,14 +50,14 @@ use std::mem;
use typed_arena::Arena; use typed_arena::Arena;
use typst::diag::SourceResult; use typst::diag::SourceResult;
use typst::eval::Tracer; 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::math::{EquationElem, LayoutMath};
use crate::meta::DocumentNode; use crate::meta::DocumentElem;
use crate::prelude::*; use crate::prelude::*;
use crate::shared::BehavedBuilder; use crate::shared::BehavedBuilder;
use crate::text::{LinebreakNode, SmartQuoteNode, SpaceNode, TextNode}; use crate::text::{LinebreakElem, SmartQuoteElem, SpaceElem, TextElem};
use crate::visualize::{CircleNode, EllipseNode, ImageNode, RectNode, SquareNode}; use crate::visualize::{CircleElem, EllipseElem, ImageElem, RectElem, SquareElem};
/// Root-level layout. /// Root-level layout.
pub trait LayoutRoot { pub trait LayoutRoot {
@ -69,7 +69,7 @@ impl LayoutRoot for Content {
fn layout_root(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Document> { fn layout_root(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Document> {
#[comemo::memoize] #[comemo::memoize]
fn cached( fn cached(
node: &Content, content: &Content,
world: Tracked<dyn World>, world: Tracked<dyn World>,
tracer: TrackedMut<Tracer>, tracer: TrackedMut<Tracer>,
provider: TrackedMut<StabilityProvider>, provider: TrackedMut<StabilityProvider>,
@ -78,7 +78,7 @@ impl LayoutRoot for Content {
) -> SourceResult<Document> { ) -> SourceResult<Document> {
let mut vt = Vt { world, tracer, provider, introspector }; let mut vt = Vt { world, tracer, provider, introspector };
let scratch = Scratch::default(); 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 realized
.with::<dyn LayoutRoot>() .with::<dyn LayoutRoot>()
.unwrap() .unwrap()
@ -108,8 +108,8 @@ pub trait Layout {
/// Layout without side effects. /// Layout without side effects.
/// ///
/// This node must be layouted again in the same order for the results to be /// This element must be layouted again in the same order for the results to
/// valid. /// be valid.
fn measure( fn measure(
&self, &self,
vt: &mut Vt, vt: &mut Vt,
@ -132,7 +132,7 @@ impl Layout for Content {
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
#[comemo::memoize] #[comemo::memoize]
fn cached( fn cached(
node: &Content, content: &Content,
world: Tracked<dyn World>, world: Tracked<dyn World>,
tracer: TrackedMut<Tracer>, tracer: TrackedMut<Tracer>,
provider: TrackedMut<StabilityProvider>, provider: TrackedMut<StabilityProvider>,
@ -142,7 +142,7 @@ impl Layout for Content {
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
let mut vt = Vt { world, tracer, provider, introspector }; let mut vt = Vt { world, tracer, provider, introspector };
let scratch = Scratch::default(); 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 realized
.with::<dyn Layout>() .with::<dyn Layout>()
.unwrap() .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>( fn realize_root<'a>(
vt: &mut Vt, vt: &mut Vt,
scratch: &'a Scratch<'a>, scratch: &'a Scratch<'a>,
@ -176,10 +176,10 @@ fn realize_root<'a>(
builder.accept(content, styles)?; builder.accept(content, styles)?;
builder.interrupt_page(Some(styles))?; builder.interrupt_page(Some(styles))?;
let (pages, shared) = builder.doc.unwrap().pages.finish(); 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>( fn realize_block<'a>(
vt: &mut Vt, vt: &mut Vt,
scratch: &'a Scratch<'a>, scratch: &'a Scratch<'a>,
@ -187,11 +187,11 @@ fn realize_block<'a>(
styles: StyleChain<'a>, styles: StyleChain<'a>,
) -> SourceResult<(Content, StyleChain<'a>)> { ) -> SourceResult<(Content, StyleChain<'a>)> {
if content.can::<dyn Layout>() if content.can::<dyn Layout>()
&& !content.is::<RectNode>() && !content.is::<RectElem>()
&& !content.is::<SquareNode>() && !content.is::<SquareElem>()
&& !content.is::<EllipseNode>() && !content.is::<EllipseElem>()
&& !content.is::<CircleNode>() && !content.is::<CircleElem>()
&& !content.is::<ImageNode>() && !content.is::<ImageElem>()
&& !applicable(content, styles) && !applicable(content, styles)
{ {
return Ok((content.clone(), styles)); return Ok((content.clone(), styles));
@ -201,10 +201,10 @@ fn realize_block<'a>(
builder.accept(content, styles)?; builder.accept(content, styles)?;
builder.interrupt_par()?; builder.interrupt_par()?;
let (children, shared) = builder.flow.0.finish(); 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> { struct Builder<'a, 'v, 't> {
/// The virtual typesetter. /// The virtual typesetter.
vt: &'v mut Vt<'t>, vt: &'v mut Vt<'t>,
@ -227,7 +227,6 @@ struct Scratch<'a> {
styles: Arena<StyleChain<'a>>, styles: Arena<StyleChain<'a>>,
/// An arena where intermediate content resulting from show rules is stored. /// An arena where intermediate content resulting from show rules is stored.
content: Arena<Content>, content: Arena<Content>,
maps: Arena<StyleMap>,
} }
impl<'a, 'v, 't> Builder<'a, 'v, 't> { impl<'a, 'v, 't> Builder<'a, 'v, 't> {
@ -247,19 +246,18 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
mut content: &'a Content, mut content: &'a Content,
styles: StyleChain<'a>, styles: StyleChain<'a>,
) -> SourceResult<()> { ) -> SourceResult<()> {
if content.can::<dyn LayoutMath>() && !content.is::<EquationNode>() { if content.can::<dyn LayoutMath>() && !content.is::<EquationElem>() {
content = 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>() { if let Some((elem, local)) = content.to_styled() {
return self.styled(styled, styles); return self.styled(elem, local, styles);
} }
if let Some(seq) = content.to::<SequenceNode>() { if let Some(children) = content.to_sequence() {
for sub in seq.children() { for elem in children {
let stored = self.scratch.content.alloc(sub); self.accept(elem, styles)?;
self.accept(stored, styles)?;
} }
return Ok(()); return Ok(());
} }
@ -290,7 +288,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
} }
let keep = content let keep = content
.to::<PagebreakNode>() .to::<PagebreakElem>()
.map_or(false, |pagebreak| !pagebreak.weak(styles)); .map_or(false, |pagebreak| !pagebreak.weak(styles));
self.interrupt_page(keep.then(|| 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( fn styled(
&mut self, &mut self,
styled: &'a StyledNode, elem: &'a Content,
map: &'a Styles,
styles: StyleChain<'a>, styles: StyleChain<'a>,
) -> SourceResult<()> { ) -> SourceResult<()> {
let map = self.scratch.maps.alloc(styled.styles());
let stored = self.scratch.styles.alloc(styles); let stored = self.scratch.styles.alloc(styles);
let content = self.scratch.content.alloc(styled.body());
let styles = stored.chain(map); let styles = stored.chain(map);
self.interrupt_style(&map, None)?; self.interrupt_style(&map, None)?;
self.accept(content, styles)?; self.accept(elem, styles)?;
self.interrupt_style(map, Some(styles))?; self.interrupt_style(map, Some(styles))?;
Ok(()) Ok(())
} }
fn interrupt_style( fn interrupt_style(
&mut self, &mut self,
map: &StyleMap, local: &Styles,
styles: Option<StyleChain<'a>>, outer: Option<StyleChain<'a>>,
) -> SourceResult<()> { ) -> SourceResult<()> {
if let Some(Some(span)) = map.interruption::<DocumentNode>() { if let Some(Some(span)) = local.interruption::<DocumentElem>() {
if self.doc.is_none() { 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.flow.0.is_empty()
|| !self.par.0.is_empty() || !self.par.0.is_empty()
|| !self.list.items.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() { if self.doc.is_none() {
bail!(span, "not allowed here"); bail!(span, "page configuration is not allowed inside of containers");
} }
self.interrupt_page(styles)?; self.interrupt_page(outer)?;
} else if map.interruption::<ParNode>().is_some() } else if local.interruption::<ParElem>().is_some()
|| map.interruption::<AlignNode>().is_some() || local.interruption::<AlignElem>().is_some()
{ {
self.interrupt_par()?; self.interrupt_par()?;
} else if map.interruption::<ListNode>().is_some() } else if local.interruption::<ListElem>().is_some()
|| map.interruption::<EnumNode>().is_some() || local.interruption::<EnumElem>().is_some()
|| map.interruption::<TermsNode>().is_some() || local.interruption::<TermsElem>().is_some()
{ {
self.interrupt_list()?; self.interrupt_list()?;
} }
@ -387,7 +388,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
} else { } else {
shared 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); let stored = self.scratch.content.alloc(page);
self.accept(stored, styles)?; self.accept(stored, styles)?;
} }
@ -405,12 +406,12 @@ struct DocBuilder<'a> {
impl<'a> DocBuilder<'a> { impl<'a> DocBuilder<'a> {
fn accept(&mut self, content: &Content, styles: StyleChain<'a>) -> bool { 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); self.keep_next = !pagebreak.weak(styles);
return true; return true;
} }
if content.is::<PageNode>() { if content.is::<PageElem>() {
self.pages.push(content.clone(), styles); self.pages.push(content.clone(), styles);
self.keep_next = false; self.keep_next = false;
return true; return true;
@ -432,7 +433,7 @@ struct FlowBuilder<'a>(BehavedBuilder<'a>, bool);
impl<'a> FlowBuilder<'a> { impl<'a> FlowBuilder<'a> {
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
if content.is::<ParbreakNode>() { if content.is::<ParbreakElem>() {
self.1 = true; self.1 = true;
return true; return true;
} }
@ -440,33 +441,33 @@ impl<'a> FlowBuilder<'a> {
let last_was_parbreak = self.1; let last_was_parbreak = self.1;
self.1 = false; self.1 = false;
if content.is::<VNode>() if content.is::<VElem>()
|| content.is::<ColbreakNode>() || content.is::<ColbreakElem>()
|| content.is::<MetaNode>() || content.is::<MetaElem>()
{ {
self.0.push(content.clone(), styles); self.0.push(content.clone(), styles);
return true; return true;
} }
if content.can::<dyn Layout>() || content.is::<ParNode>() { if content.can::<dyn Layout>() || content.is::<ParElem>() {
let is_tight_list = if let Some(node) = content.to::<ListNode>() { let is_tight_list = if let Some(elem) = content.to::<ListElem>() {
node.tight(styles) elem.tight(styles)
} else if let Some(node) = content.to::<EnumNode>() { } else if let Some(elem) = content.to::<EnumElem>() {
node.tight(styles) elem.tight(styles)
} else if let Some(node) = content.to::<TermsNode>() { } else if let Some(elem) = content.to::<TermsElem>() {
node.tight(styles) elem.tight(styles)
} else { } else {
false false
}; };
if !last_was_parbreak && is_tight_list { if !last_was_parbreak && is_tight_list {
let leading = ParNode::leading_in(styles); let leading = ParElem::leading_in(styles);
let spacing = VNode::list_attach(leading.into()); let spacing = VElem::list_attach(leading.into());
self.0.push(spacing.pack(), styles); self.0.push(spacing.pack(), styles);
} }
let above = BlockNode::above_in(styles); let above = BlockElem::above_in(styles);
let below = BlockNode::below_in(styles); let below = BlockElem::below_in(styles);
self.0.push(above.clone().pack(), styles); self.0.push(above.clone().pack(), styles);
self.0.push(content.clone(), styles); self.0.push(content.clone(), styles);
self.0.push(below.clone().pack(), styles); self.0.push(below.clone().pack(), styles);
@ -483,18 +484,18 @@ struct ParBuilder<'a>(BehavedBuilder<'a>);
impl<'a> ParBuilder<'a> { impl<'a> ParBuilder<'a> {
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { 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() { if !self.0.is_basically_empty() {
self.0.push(content.clone(), styles); self.0.push(content.clone(), styles);
return true; return true;
} }
} else if content.is::<SpaceNode>() } else if content.is::<SpaceElem>()
|| content.is::<TextNode>() || content.is::<TextElem>()
|| content.is::<HNode>() || content.is::<HElem>()
|| content.is::<LinebreakNode>() || content.is::<LinebreakElem>()
|| content.is::<SmartQuoteNode>() || content.is::<SmartQuoteElem>()
|| content.to::<EquationNode>().map_or(false, |node| !node.block(styles)) || content.to::<EquationElem>().map_or(false, |elem| !elem.block(styles))
|| content.is::<BoxNode>() || content.is::<BoxElem>()
{ {
self.0.push(content.clone(), styles); self.0.push(content.clone(), styles);
return true; return true;
@ -505,7 +506,7 @@ impl<'a> ParBuilder<'a> {
fn finish(self) -> (Content, StyleChain<'a>) { fn finish(self) -> (Content, StyleChain<'a>) {
let (children, shared) = self.0.finish(); 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> { impl<'a> ListBuilder<'a> {
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
if !self.items.is_empty() if !self.items.is_empty()
&& (content.is::<SpaceNode>() || content.is::<ParbreakNode>()) && (content.is::<SpaceElem>() || content.is::<ParbreakElem>())
{ {
self.staged.push((content, styles)); self.staged.push((content, styles));
return true; return true;
@ -533,12 +534,12 @@ impl<'a> ListBuilder<'a> {
|| content.is::<TermItem>()) || content.is::<TermItem>())
&& self && self
.items .items
.items() .elems()
.next() .next()
.map_or(true, |first| first.id() == content.id()) .map_or(true, |first| first.func() == content.func())
{ {
self.items.push(content.clone(), styles); 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; return true;
} }
@ -549,39 +550,39 @@ impl<'a> ListBuilder<'a> {
let (items, shared) = self.items.finish(); let (items, shared) = self.items.finish();
let item = items.items().next().unwrap(); let item = items.items().next().unwrap();
let output = if item.is::<ListItem>() { let output = if item.is::<ListItem>() {
ListNode::new( ListElem::new(
items items
.iter() .iter()
.map(|(item, map)| { .map(|(item, local)| {
let item = item.to::<ListItem>().unwrap(); 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<_>>(), .collect::<Vec<_>>(),
) )
.with_tight(self.tight) .with_tight(self.tight)
.pack() .pack()
} else if item.is::<EnumItem>() { } else if item.is::<EnumItem>() {
EnumNode::new( EnumElem::new(
items items
.iter() .iter()
.map(|(item, map)| { .map(|(item, local)| {
let item = item.to::<EnumItem>().unwrap(); 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<_>>(), .collect::<Vec<_>>(),
) )
.with_tight(self.tight) .with_tight(self.tight)
.pack() .pack()
} else if item.is::<TermItem>() { } else if item.is::<TermItem>() {
TermsNode::new( TermsElem::new(
items items
.iter() .iter()
.map(|(item, map)| { .map(|(item, local)| {
let item = item.to::<TermItem>().unwrap(); let item = item.to::<TermItem>().unwrap();
item.clone() item.clone()
.with_term(item.term().styled_with_map(map.clone())) .with_term(item.term().styled_with_map(local.clone()))
.with_description( .with_description(
item.description().styled_with_map(map.clone()), item.description().styled_with_map(local.clone()),
) )
}) })
.collect::<Vec<_>>(), .collect::<Vec<_>>(),

View File

@ -17,8 +17,8 @@ use crate::prelude::*;
/// ///
/// Display: Padding /// Display: Padding
/// Category: layout /// Category: layout
#[node(Layout)] #[element(Layout)]
pub struct PadNode { pub struct PadElem {
/// The padding at the left side. /// The padding at the left side.
#[parse( #[parse(
let all = args.named("rest")?.or(args.find()?); let all = args.named("rest")?.or(args.find()?);
@ -59,7 +59,7 @@ pub struct PadNode {
pub body: Content, pub body: Content,
} }
impl Layout for PadNode { impl Layout for PadElem {
fn layout( fn layout(
&self, &self,
vt: &mut Vt, vt: &mut Vt,

View File

@ -1,7 +1,7 @@
use std::ptr; use std::ptr;
use std::str::FromStr; use std::str::FromStr;
use super::{AlignNode, ColumnsNode}; use super::{AlignElem, ColumnsElem};
use crate::meta::{Counter, CounterKey, Numbering}; use crate::meta::{Counter, CounterKey, Numbering};
use crate::prelude::*; use crate::prelude::*;
@ -24,8 +24,8 @@ use crate::prelude::*;
/// ///
/// Display: Page /// Display: Page
/// Category: layout /// Category: layout
#[node] #[element]
pub struct PageNode { pub struct PageElem {
/// A standard paper size to set width and height. When this is not /// A standard paper size to set width and height. When this is not
/// specified, Typst defaults to `{"a4"}` paper. /// specified, Typst defaults to `{"a4"}` paper.
#[external] #[external]
@ -270,7 +270,7 @@ pub struct PageNode {
pub body: Content, pub body: Content,
} }
impl PageNode { impl PageElem {
/// Layout the page run into a sequence of frames, one per page. /// Layout the page run into a sequence of frames, one per page.
pub fn layout(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Fragment> { pub fn layout(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Fragment> {
// When one of the lengths is infinite the page fits its content along // When one of the lengths is infinite the page fits its content along
@ -296,7 +296,7 @@ impl PageNode {
// Realize columns. // Realize columns.
let columns = self.columns(styles); let columns = self.columns(styles);
if columns.get() > 1 { if columns.get() > 1 {
child = ColumnsNode::new(child).with_count(columns).pack(); child = ColumnsElem::new(child).with_count(columns).pack();
} }
// Realize margins. // Realize margins.
@ -356,7 +356,7 @@ impl PageNode {
let pod = Regions::one(area, Axes::splat(true)); let pod = Regions::one(area, Axes::splat(true));
let sub = content let sub = content
.clone() .clone()
.styled(AlignNode::set_alignment(align)) .styled(AlignElem::set_alignment(align))
.layout(vt, styles, pod)? .layout(vt, styles, pod)?
.into_frame(); .into_frame();
if ptr::eq(marginal, &header) || ptr::eq(marginal, &background) { if ptr::eq(marginal, &header) || ptr::eq(marginal, &background) {
@ -387,8 +387,8 @@ impl PageNode {
/// ///
/// Display: Page Break /// Display: Page Break
/// Category: layout /// Category: layout
#[node] #[element]
pub struct PagebreakNode { pub struct PagebreakElem {
/// If `{true}`, the page break is skipped if the current page is already /// If `{true}`, the page break is skipped if the current page is already
/// empty. /// empty.
#[default(false)] #[default(false)]
@ -467,7 +467,7 @@ macro_rules! papers {
fn from_str(name: &str) -> Result<Self, Self::Err> { fn from_str(name: &str) -> Result<Self, Self::Err> {
match name.to_lowercase().as_str() { match name.to_lowercase().as_str() {
$($pat => Ok(Self::$var),)* $($pat => Ok(Self::$var),)*
_ => Err("invalid paper name"), _ => Err("unknown paper size"),
} }
} }
} }

View File

@ -3,17 +3,15 @@ use unicode_bidi::{BidiInfo, Level as BidiLevel};
use unicode_script::{Script, UnicodeScript}; use unicode_script::{Script, UnicodeScript};
use xi_unicode::LineBreakIterator; use xi_unicode::LineBreakIterator;
use typst::model::StyledNode; use super::{BoxElem, HElem, Sizing, Spacing};
use crate::layout::AlignElem;
use super::{BoxNode, HNode, Sizing, Spacing}; use crate::math::EquationElem;
use crate::layout::AlignNode;
use crate::math::EquationNode;
use crate::prelude::*; use crate::prelude::*;
use crate::text::{ 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 /// 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 /// properties, it can also be used to explicitly render its argument onto a
@ -38,8 +36,8 @@ use crate::text::{
/// ///
/// Display: Paragraph /// Display: Paragraph
/// Category: layout /// Category: layout
#[node(Construct)] #[element(Construct)]
pub struct ParNode { pub struct ParElem {
/// The spacing between lines. /// The spacing between lines.
/// ///
/// The default value is `{0.65em}`. /// The default value is `{0.65em}`.
@ -110,22 +108,22 @@ pub struct ParNode {
pub children: Vec<Content>, pub children: Vec<Content>,
} }
impl Construct for ParNode { impl Construct for ParElem {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
// The paragraph constructor is special: It doesn't create a paragraph // 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. // separate paragraph and styles it.
let styles = Self::set(args)?; let styles = Self::set(args)?;
let body = args.expect::<Content>("body")?; let body = args.expect::<Content>("body")?;
Ok(Content::sequence(vec![ Ok(Content::sequence([
ParbreakNode::new().pack(), ParbreakElem::new().pack(),
body.styled_with_map(styles), body.styled_with_map(styles),
ParbreakNode::new().pack(), ParbreakElem::new().pack(),
])) ]))
} }
} }
impl ParNode { impl ParElem {
/// Layout the paragraph into a collection of lines. /// Layout the paragraph into a collection of lines.
pub fn layout( pub fn layout(
&self, &self,
@ -137,7 +135,7 @@ impl ParNode {
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
#[comemo::memoize] #[comemo::memoize]
fn cached( fn cached(
par: &ParNode, par: &ParElem,
world: Tracked<dyn World>, world: Tracked<dyn World>,
tracer: TrackedMut<Tracer>, tracer: TrackedMut<Tracer>,
provider: TrackedMut<StabilityProvider>, 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. /// How to determine line breaks in a paragraph.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
pub enum Linebreaks { pub enum Linebreaks {
@ -232,10 +210,10 @@ pub enum Linebreaks {
/// ///
/// Display: Paragraph Break /// Display: Paragraph Break
/// Category: layout /// Category: layout
#[node(Unlabellable)] #[element(Unlabellable)]
pub struct ParbreakNode {} pub struct ParbreakElem {}
impl Unlabellable for ParbreakNode {} impl Unlabellable for ParbreakElem {}
/// Range of a substring of text. /// Range of a substring of text.
type Range = std::ops::Range<usize>; 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 // The characters by which spacing, inline content and pins are replaced in the
// paragraph's full text. // paragraph's full text.
const SPACING_REPLACE: char = ' '; // Space 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 /// A paragraph representation in which children are already layouted and text
/// is already preshaped. /// is already preshaped.
@ -254,7 +232,7 @@ const NODE_REPLACE: char = '\u{FFFC}'; // Object Replacement Character
struct Preparation<'a> { struct Preparation<'a> {
/// Bidirectional text embedding levels for the paragraph. /// Bidirectional text embedding levels for the paragraph.
bidi: BidiInfo<'a>, bidi: BidiInfo<'a>,
/// Text runs, spacing and layouted nodes. /// Text runs, spacing and layouted elements.
items: Vec<Item<'a>>, items: Vec<Item<'a>>,
/// The span mapper. /// The span mapper.
spans: SpanMapper, spans: SpanMapper,
@ -325,9 +303,9 @@ enum Segment<'a> {
/// Horizontal spacing between other segments. /// Horizontal spacing between other segments.
Spacing(Spacing), Spacing(Spacing),
/// A mathematical equation. /// A mathematical equation.
Equation(&'a EquationNode), Equation(&'a EquationElem),
/// A box with arbitrary content. /// A box with arbitrary content.
Box(&'a BoxNode, bool), Box(&'a BoxElem, bool),
/// Metadata. /// Metadata.
Meta, Meta,
} }
@ -339,7 +317,7 @@ impl Segment<'_> {
Self::Text(len) => len, Self::Text(len) => len,
Self::Spacing(_) => SPACING_REPLACE.len_utf8(), Self::Spacing(_) => SPACING_REPLACE.len_utf8(),
Self::Box(_, true) => 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 spacing between other items.
Absolute(Abs), Absolute(Abs),
/// Fractional spacing between other items. /// Fractional spacing between other items.
Fractional(Fr, Option<(&'a BoxNode, StyleChain<'a>)>), Fractional(Fr, Option<(&'a BoxElem, StyleChain<'a>)>),
/// Layouted inline-level content. /// Layouted inline-level content.
Frame(Frame), Frame(Frame),
} }
@ -371,7 +349,7 @@ impl<'a> Item<'a> {
match self { match self {
Self::Text(shaped) => shaped.text.len(), Self::Text(shaped) => shaped.text.len(),
Self::Absolute(_) | Self::Fractional(_, _) => SPACING_REPLACE.len_utf8(), 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(); let mut iter = children.iter().peekable();
if consecutive { 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() if !first_line_indent.is_zero()
&& children && children
.iter() .iter()
@ -529,7 +507,7 @@ fn collect<'a>(
behaved.behaviour() == Behaviour::Ignorant behaved.behaviour() == Behaviour::Ignorant
}) { }) {
None None
} else if child.is::<TextNode>() || child.is::<SmartQuoteNode>() { } else if child.is::<TextElem>() || child.is::<SmartQuoteElem>() {
Some(true) Some(true)
} else { } else {
Some(false) 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() { if !hang.is_zero() {
full.push(SPACING_REPLACE); full.push(SPACING_REPLACE);
segments.push((Segment::Spacing((-hang).into()), *styles)); segments.push((Segment::Spacing((-hang).into()), *styles));
@ -551,61 +529,61 @@ fn collect<'a>(
while let Some(mut child) = iter.next() { while let Some(mut child) = iter.next() {
let outer = styles; let outer = styles;
let mut styles = *styles; let mut styles = *styles;
if let Some(node) = child.to::<StyledNode>() { if let Some((elem, local)) = child.to_styled() {
child = Box::leak(Box::new(node.body())); child = elem;
styles = outer.chain(Box::leak(Box::new(node.styles()))); styles = outer.chain(local);
} }
let segment = if child.is::<SpaceNode>() { let segment = if child.is::<SpaceElem>() {
full.push(' '); full.push(' ');
Segment::Text(1) Segment::Text(1)
} else if let Some(node) = child.to::<TextNode>() { } else if let Some(elem) = child.to::<TextElem>() {
let prev = full.len(); let prev = full.len();
if let Some(case) = TextNode::case_in(styles) { if let Some(case) = TextElem::case_in(styles) {
full.push_str(&case.apply(&node.text())); full.push_str(&case.apply(&elem.text()));
} else { } else {
full.push_str(&node.text()); full.push_str(&elem.text());
} }
Segment::Text(full.len() - prev) 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); full.push(SPACING_REPLACE);
Segment::Spacing(node.amount()) Segment::Spacing(elem.amount())
} else if let Some(node) = child.to::<LinebreakNode>() { } else if let Some(elem) = child.to::<LinebreakElem>() {
let c = if node.justify(styles) { '\u{2028}' } else { '\n' }; let c = if elem.justify(styles) { '\u{2028}' } else { '\n' };
full.push(c); full.push(c);
Segment::Text(c.len_utf8()) 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(); let prev = full.len();
if SmartQuoteNode::enabled_in(styles) { if SmartQuoteElem::enabled_in(styles) {
let lang = TextNode::lang_in(styles); let lang = TextElem::lang_in(styles);
let region = TextNode::region_in(styles); let region = TextElem::region_in(styles);
let quotes = Quotes::from_lang(lang, region); let quotes = Quotes::from_lang(lang, region);
let peeked = iter.peek().and_then(|child| { let peeked = iter.peek().and_then(|child| {
if let Some(node) = child.to::<TextNode>() { if let Some(elem) = child.to::<TextElem>() {
node.text().chars().next() elem.text().chars().next()
} else if child.is::<SmartQuoteNode>() { } else if child.is::<SmartQuoteElem>() {
Some('"') Some('"')
} else if child.is::<SpaceNode>() || child.is::<HNode>() { } else if child.is::<SpaceElem>() || child.is::<HElem>() {
Some(SPACING_REPLACE) Some(SPACING_REPLACE)
} else { } else {
Some(NODE_REPLACE) Some(OBJ_REPLACE)
} }
}); });
full.push_str(quoter.quote(&quotes, node.double(styles), peeked)); full.push_str(quoter.quote(&quotes, elem.double(styles), peeked));
} else { } else {
full.push(if node.double(styles) { '"' } else { '\'' }); full.push(if elem.double(styles) { '"' } else { '\'' });
} }
Segment::Text(full.len() - prev) Segment::Text(full.len() - prev)
} else if let Some(node) = child.to::<EquationNode>() { } else if let Some(elem) = child.to::<EquationElem>() {
full.push(NODE_REPLACE); full.push(OBJ_REPLACE);
Segment::Equation(node) Segment::Equation(elem)
} else if let Some(node) = child.to::<BoxNode>() { } else if let Some(elem) = child.to::<BoxElem>() {
let frac = node.width(styles).is_fractional(); let frac = elem.width(styles).is_fractional();
full.push(if frac { SPACING_REPLACE } else { NODE_REPLACE }); full.push(if frac { SPACING_REPLACE } else { OBJ_REPLACE });
Segment::Box(node, frac) Segment::Box(elem, frac)
} else if child.is::<MetaNode>() { } else if child.is::<MetaElem>() {
full.push(NODE_REPLACE); full.push(OBJ_REPLACE);
Segment::Meta Segment::Meta
} else { } else {
bail!(child.span(), "unexpected paragraph child"); bail!(child.span(), "unexpected paragraph child");
@ -645,7 +623,7 @@ fn prepare<'a>(
) -> SourceResult<Preparation<'a>> { ) -> SourceResult<Preparation<'a>> {
let bidi = BidiInfo::new( let bidi = BidiInfo::new(
text, text,
match TextNode::dir_in(styles) { match TextElem::dir_in(styles) {
Dir::LTR => Some(BidiLevel::ltr()), Dir::LTR => Some(BidiLevel::ltr()),
Dir::RTL => Some(BidiLevel::rtl()), Dir::RTL => Some(BidiLevel::rtl()),
_ => None, _ => None,
@ -674,16 +652,16 @@ fn prepare<'a>(
Segment::Equation(equation) => { Segment::Equation(equation) => {
let pod = Regions::one(region, Axes::splat(false)); let pod = Regions::one(region, Axes::splat(false));
let mut frame = equation.layout(vt, styles, pod)?.into_frame(); 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)); items.push(Item::Frame(frame));
} }
Segment::Box(node, _) => { Segment::Box(elem, _) => {
if let Sizing::Fr(v) = node.width(styles) { if let Sizing::Fr(v) = elem.width(styles) {
items.push(Item::Fractional(v, Some((node, styles)))); items.push(Item::Fractional(v, Some((elem, styles))));
} else { } else {
let pod = Regions::one(region, Axes::splat(false)); let pod = Regions::one(region, Axes::splat(false));
let mut frame = node.layout(vt, styles, pod)?.into_frame(); let mut frame = elem.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)); items.push(Item::Frame(frame));
} }
} }
@ -702,11 +680,11 @@ fn prepare<'a>(
items, items,
spans, spans,
styles, styles,
hyphenate: shared_get(styles, children, TextNode::hyphenate_in), hyphenate: shared_get(styles, children, TextElem::hyphenate_in),
lang: shared_get(styles, children, TextNode::lang_in), lang: shared_get(styles, children, TextElem::lang_in),
align: AlignNode::alignment_in(styles).x.resolve(styles), align: AlignElem::alignment_in(styles).x.resolve(styles),
justify: ParNode::justify_in(styles), justify: ParElem::justify_in(styles),
hang: ParNode::hanging_indent_in(styles), hang: ParElem::hanging_indent_in(styles),
}) })
} }
@ -775,15 +753,15 @@ fn shared_get<'a, T: PartialEq>(
let value = getter(styles); let value = getter(styles);
children children
.iter() .iter()
.filter_map(|child| child.to::<StyledNode>()) .filter_map(|child| child.to_styled())
.all(|node| getter(styles.chain(&node.styles())) == value) .all(|(_, local)| getter(styles.chain(&local)) == value)
.then(|| value) .then(|| value)
} }
/// Find suitable linebreaks. /// Find suitable linebreaks.
fn linebreak<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec<Line<'a>> { fn linebreak<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec<Line<'a>> {
let linebreaks = ParNode::linebreaks_in(p.styles).unwrap_or_else(|| { let linebreaks = ParElem::linebreaks_in(p.styles).unwrap_or_else(|| {
if ParNode::justify_in(p.styles) { if ParElem::justify_in(p.styles) {
Linebreaks::Optimized Linebreaks::Optimized
} else { } else {
Linebreaks::Simple 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), 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) { for (end, mandatory, hyphen) in breakpoints(p) {
let k = table.len(); let k = table.len();
@ -1046,7 +1024,7 @@ impl Breakpoints<'_> {
.hyphenate .hyphenate
.or_else(|| { .or_else(|| {
let shaped = self.p.find(offset)?.text()?; let shaped = self.p.find(offset)?.text()?;
Some(TextNode::hyphenate_in(shaped.styles)) Some(TextElem::hyphenate_in(shaped.styles))
}) })
.unwrap_or(false) .unwrap_or(false)
} }
@ -1055,7 +1033,7 @@ impl Breakpoints<'_> {
fn lang(&self, offset: usize) -> Option<hypher::Lang> { fn lang(&self, offset: usize) -> Option<hypher::Lang> {
let lang = self.p.lang.or_else(|| { let lang = self.p.lang.or_else(|| {
let shaped = self.p.find(offset)?.text()?; 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()?; let bytes = lang.as_str().as_bytes().try_into().ok()?;
@ -1196,7 +1174,7 @@ fn finalize(
.collect::<SourceResult<_>>()?; .collect::<SourceResult<_>>()?;
// Prevent orphans. // Prevent orphans.
let leading = ParNode::leading_in(p.styles); let leading = ParElem::leading_in(p.styles);
if frames.len() >= 2 && !frames[1].is_empty() { if frames.len() >= 2 && !frames[1].is_empty() {
let second = frames.remove(1); let second = frames.remove(1);
let first = &mut frames[0]; let first = &mut frames[0];
@ -1243,7 +1221,7 @@ fn commit(
if let Some(Item::Text(text)) = reordered.first() { if let Some(Item::Text(text)) = reordered.first() {
if let Some(glyph) = text.glyphs.first() { if let Some(glyph) = text.glyphs.first() {
if !text.dir.is_positive() if !text.dir.is_positive()
&& TextNode::overhang_in(text.styles) && TextElem::overhang_in(text.styles)
&& (reordered.len() > 1 || text.glyphs.len() > 1) && (reordered.len() > 1 || text.glyphs.len() > 1)
{ {
let amount = overhang(glyph.c) * glyph.x_advance.at(text.size); 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(Item::Text(text)) = reordered.last() {
if let Some(glyph) = text.glyphs.last() { if let Some(glyph) = text.glyphs.last() {
if text.dir.is_positive() if text.dir.is_positive()
&& TextNode::overhang_in(text.styles) && TextElem::overhang_in(text.styles)
&& (reordered.len() > 1 || text.glyphs.len() > 1) && (reordered.len() > 1 || text.glyphs.len() > 1)
{ {
let amount = overhang(glyph.c) * glyph.x_advance.at(text.size); let amount = overhang(glyph.c) * glyph.x_advance.at(text.size);
@ -1295,13 +1273,13 @@ fn commit(
Item::Absolute(v) => { Item::Absolute(v) => {
offset += *v; offset += *v;
} }
Item::Fractional(v, node) => { Item::Fractional(v, elem) => {
let amount = v.share(fr, remaining); 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 region = Size::new(amount, full);
let pod = Regions::one(region, Axes::new(true, false)); let pod = Regions::one(region, Axes::new(true, false));
let mut frame = node.layout(vt, *styles, pod)?.into_frame(); let mut frame = elem.layout(vt, *styles, pod)?.into_frame();
frame.translate(Point::with_y(TextNode::baseline_in(*styles))); frame.translate(Point::with_y(TextElem::baseline_in(*styles)));
push(&mut offset, frame); push(&mut offset, frame);
} else { } else {
offset += amount; offset += amount;

View File

@ -23,8 +23,8 @@ use crate::prelude::*;
/// ///
/// Display: Place /// Display: Place
/// Category: layout /// Category: layout
#[node(Layout, Behave)] #[element(Layout, Behave)]
pub struct PlaceNode { pub struct PlaceElem {
/// Relative to which position in the parent container to place the content. /// 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 /// When an axis of the page is `{auto}` sized, all alignments relative to that
@ -53,7 +53,7 @@ pub struct PlaceNode {
pub body: Content, pub body: Content,
} }
impl Layout for PlaceNode { impl Layout for PlaceElem {
fn layout( fn layout(
&self, &self,
vt: &mut Vt, vt: &mut Vt,
@ -86,16 +86,16 @@ impl Layout for PlaceNode {
} }
} }
impl PlaceNode { impl PlaceElem {
/// Whether this node wants to be placed relative to its its parent's base /// Whether this element wants to be placed relative to its its parent's
/// origin. Instead of relative to the parent's current flow/cursor /// base origin. Instead of relative to the parent's current flow/cursor
/// position. /// position.
pub fn out_of_flow(&self, styles: StyleChain) -> bool { pub fn out_of_flow(&self, styles: StyleChain) -> bool {
self.alignment(styles).y.is_some() self.alignment(styles).y.is_some()
} }
} }
impl Behave for PlaceNode { impl Behave for PlaceElem {
fn behaviour(&self) -> Behaviour { fn behaviour(&self) -> Behaviour {
Behaviour::Ignorant Behaviour::Ignorant
} }

View File

@ -14,8 +14,8 @@ pub struct Regions<'a> {
/// The height of the final region that is repeated once the backlog is /// The height of the final region that is repeated once the backlog is
/// drained. The width is the same for all regions. /// drained. The width is the same for all regions.
pub last: Option<Abs>, pub last: Option<Abs>,
/// Whether nodes should expand to fill the regions instead of shrinking to /// Whether elements should expand to fill the regions instead of shrinking
/// fit the content. /// to fit the content.
pub expand: Axes<bool>, pub expand: Axes<bool>,
} }

View File

@ -1,6 +1,6 @@
use crate::prelude::*; use crate::prelude::*;
use super::AlignNode; use super::AlignElem;
/// Repeats content to the available space. /// Repeats content to the available space.
/// ///
@ -23,14 +23,14 @@ use super::AlignNode;
/// ///
/// Display: Repeat /// Display: Repeat
/// Category: layout /// Category: layout
#[node(Layout)] #[element(Layout)]
pub struct RepeatNode { pub struct RepeatElem {
/// The content to repeat. /// The content to repeat.
#[required] #[required]
pub body: Content, pub body: Content,
} }
impl Layout for RepeatNode { impl Layout for RepeatElem {
fn layout( fn layout(
&self, &self,
vt: &mut Vt, vt: &mut Vt,
@ -39,7 +39,7 @@ impl Layout for RepeatNode {
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
let pod = Regions::one(regions.size, Axes::new(false, false)); let pod = Regions::one(regions.size, Axes::new(false, false));
let piece = self.body().layout(vt, styles, pod)?.into_frame(); 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 fill = regions.size.x;
let width = piece.width(); let width = piece.width();

View File

@ -21,8 +21,8 @@ use crate::prelude::*;
/// ///
/// Display: Spacing (H) /// Display: Spacing (H)
/// Category: layout /// Category: layout
#[node(Behave)] #[element(Behave)]
pub struct HNode { pub struct HElem {
/// How much spacing to insert. /// How much spacing to insert.
#[required] #[required]
pub amount: Spacing, pub amount: Spacing,
@ -45,7 +45,7 @@ pub struct HNode {
pub weak: bool, pub weak: bool,
} }
impl Behave for HNode { impl Behave for HElem {
fn behaviour(&self) -> Behaviour { fn behaviour(&self) -> Behaviour {
if self.amount().is_fractional() { if self.amount().is_fractional() {
Behaviour::Destructive Behaviour::Destructive
@ -85,8 +85,8 @@ impl Behave for HNode {
/// ///
/// Display: Spacing (V) /// Display: Spacing (V)
/// Category: layout /// Category: layout
#[node(Behave)] #[element(Behave)]
pub struct VNode { pub struct VElem {
/// How much spacing to insert. /// How much spacing to insert.
#[required] #[required]
pub amount: Spacing, pub amount: Spacing,
@ -107,13 +107,13 @@ pub struct VNode {
#[external] #[external]
pub weak: bool, pub weak: bool,
/// The node's weakness level, see also [`Behaviour`]. /// The elements's weakness level, see also [`Behaviour`].
#[internal] #[internal]
#[parse(args.named("weak")?.map(|v: bool| v as usize))] #[parse(args.named("weak")?.map(|v: bool| v as usize))]
pub weakness: usize, pub weakness: usize,
} }
impl VNode { impl VElem {
/// Normal strong spacing. /// Normal strong spacing.
pub fn strong(amount: Spacing) -> Self { pub fn strong(amount: Spacing) -> Self {
Self::new(amount).with_weakness(0) Self::new(amount).with_weakness(0)
@ -129,18 +129,18 @@ impl VNode {
Self::new(amount).with_weakness(2) 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 { pub fn block_around(amount: Spacing) -> Self {
Self::new(amount).with_weakness(3) 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 { pub fn block_spacing(amount: Spacing) -> Self {
Self::new(amount).with_weakness(4) Self::new(amount).with_weakness(4)
} }
} }
impl Behave for VNode { impl Behave for VElem {
fn behaviour(&self) -> Behaviour { fn behaviour(&self) -> Behaviour {
if self.amount().is_fractional() { if self.amount().is_fractional() {
Behaviour::Destructive Behaviour::Destructive
@ -158,8 +158,8 @@ impl Behave for VNode {
} }
cast_from_value! { cast_from_value! {
VNode, VElem,
v: Content => v.to::<Self>().cloned().ok_or("expected vnode")?, v: Content => v.to::<Self>().cloned().ok_or("expected `v` element")?,
} }
/// Kinds of spacing. /// Kinds of spacing.

View File

@ -1,6 +1,4 @@
use typst::model::StyledNode; use super::{AlignElem, Spacing};
use super::{AlignNode, Spacing};
use crate::prelude::*; use crate::prelude::*;
/// Arrange content and spacing horizontally or vertically. /// Arrange content and spacing horizontally or vertically.
@ -20,8 +18,8 @@ use crate::prelude::*;
/// ///
/// Display: Stack /// Display: Stack
/// Category: layout /// Category: layout
#[node(Layout)] #[element(Layout)]
pub struct StackNode { pub struct StackElem {
/// The direction along which the items are stacked. Possible values are: /// The direction along which the items are stacked. Possible values are:
/// ///
/// - `{ltr}`: Left to right. /// - `{ltr}`: Left to right.
@ -39,7 +37,7 @@ pub struct StackNode {
pub children: Vec<StackChild>, pub children: Vec<StackChild>,
} }
impl Layout for StackNode { impl Layout for StackElem {
fn layout( fn layout(
&self, &self,
vt: &mut Vt, 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)] #[derive(Hash)]
pub enum StackChild { pub enum StackChild {
/// Spacing between other children. /// Spacing between other children.
@ -196,14 +194,13 @@ impl<'a> StackLayouter<'a> {
self.finish_region(); self.finish_region();
} }
// Block-axis alignment of the `AlignNode` is respected // Block-axis alignment of the `AlignElement` is respected by stacks.
// by the stack node. let aligns = if let Some(align) = block.to::<AlignElem>() {
let aligns = if let Some(align) = block.to::<AlignNode>() {
align.alignment(styles) align.alignment(styles)
} else if let Some(styled) = block.to::<StyledNode>() { } else if let Some((_, local)) = block.to_styled() {
AlignNode::alignment_in(styles.chain(&styled.styles())) AlignElem::alignment_in(styles.chain(&local))
} else { } else {
AlignNode::alignment_in(styles) AlignElem::alignment_in(styles)
} }
.resolve(styles); .resolve(styles);

View File

@ -1,4 +1,4 @@
use crate::layout::{AlignNode, GridLayouter, TrackSizings}; use crate::layout::{AlignElem, GridLayouter, TrackSizings};
use crate::meta::LocalName; use crate::meta::LocalName;
use crate::prelude::*; use crate::prelude::*;
@ -32,8 +32,8 @@ use crate::prelude::*;
/// ///
/// Display: Table /// Display: Table
/// Category: layout /// Category: layout
#[node(Layout, LocalName)] #[element(Layout, LocalName)]
pub struct TableNode { pub struct TableElem {
/// Defines the column sizes. See the [grid documentation]($func/grid) for /// Defines the column sizes. See the [grid documentation]($func/grid) for
/// more information on track sizing. /// more information on track sizing.
pub columns: TrackSizings, pub columns: TrackSizings,
@ -109,7 +109,7 @@ pub struct TableNode {
pub children: Vec<Content>, pub children: Vec<Content>,
} }
impl Layout for TableNode { impl Layout for TableElem {
fn layout( fn layout(
&self, &self,
vt: &mut Vt, vt: &mut Vt,
@ -132,7 +132,7 @@ impl Layout for TableNode {
let x = i % cols; let x = i % cols;
let y = i / cols; let y = i / cols;
if let Smart::Custom(alignment) = align.resolve(vt, x, y)? { 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) Ok(child)
@ -168,7 +168,7 @@ impl Layout for TableNode {
let hline = Geometry::Line(target).stroked(stroke); let hline = Geometry::Line(target).stroked(stroke);
frame.prepend( frame.prepend(
Point::new(-half, offset), 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); let vline = Geometry::Line(target).stroked(stroke);
frame.prepend( frame.prepend(
Point::new(offset, -half), 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 pos = Point::new(dx, dy);
let size = Size::new(col, row.height); let size = Size::new(col, row.height);
let rect = Geometry::Rect(size).filled(fill); 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; 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 { fn local_name(&self, lang: Lang) -> &'static str {
match lang { match lang {
Lang::GERMAN => "Tabelle", Lang::GERMAN => "Tabelle",

View File

@ -1,7 +1,7 @@
use super::{HNode, VNode}; use super::{HElem, VElem};
use crate::layout::{BlockNode, ParNode, Spacing}; use crate::layout::{BlockElem, ParElem, Spacing};
use crate::prelude::*; use crate::prelude::*;
use crate::text::{SpaceNode, TextNode}; use crate::text::{SpaceElem, TextElem};
/// A list of terms and their descriptions. /// A list of terms and their descriptions.
/// ///
@ -22,8 +22,8 @@ use crate::text::{SpaceNode, TextNode};
/// ///
/// Display: Term List /// Display: Term List
/// Category: layout /// Category: layout
#[node(Layout)] #[element(Layout)]
pub struct TermsNode { pub struct TermsElem {
/// If this is `{false}`, the items are spaced apart with [term list /// If this is `{false}`, the items are spaced apart with [term list
/// spacing]($func/terms.spacing). If it is `{true}`, they use normal /// spacing]($func/terms.spacing). If it is `{true}`, they use normal
/// [leading]($func/par.leading) instead. This makes the term list more /// [leading]($func/par.leading) instead. This makes the term list more
@ -76,7 +76,7 @@ pub struct TermsNode {
pub children: Vec<TermItem>, pub children: Vec<TermItem>,
} }
impl Layout for TermsNode { impl Layout for TermsElem {
fn layout( fn layout(
&self, &self,
vt: &mut Vt, vt: &mut Vt,
@ -86,27 +86,27 @@ impl Layout for TermsNode {
let indent = self.indent(styles); let indent = self.indent(styles);
let hanging_indent = self.hanging_indent(styles); let hanging_indent = self.hanging_indent(styles);
let gutter = if self.tight(styles) { let gutter = if self.tight(styles) {
ParNode::leading_in(styles).into() ParElem::leading_in(styles).into()
} else { } else {
self.spacing(styles) self.spacing(styles)
.unwrap_or_else(|| BlockNode::below_in(styles).amount()) .unwrap_or_else(|| BlockElem::below_in(styles).amount())
}; };
let mut seq = vec![]; let mut seq = vec![];
for (i, child) in self.children().into_iter().enumerate() { for (i, child) in self.children().into_iter().enumerate() {
if i > 0 { 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() { 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((child.term() + TextElem::packed(':')).strong());
seq.push(SpaceNode::new().pack()); seq.push(SpaceElem::new().pack());
seq.push(child.description()); seq.push(child.description());
} }
Content::sequence(seq) Content::sequence(seq)
.styled(ParNode::set_hanging_indent(hanging_indent + indent)) .styled(ParElem::set_hanging_indent(hanging_indent + indent))
.layout(vt, styles, regions) .layout(vt, styles, regions)
} }
} }
@ -115,7 +115,7 @@ impl Layout for TermsNode {
/// ///
/// Display: Term List Item /// Display: Term List Item
/// Category: layout /// Category: layout
#[node] #[element]
pub struct TermItem { pub struct TermItem {
/// The term described by the list item. /// The term described by the list item.
#[required] #[required]

View File

@ -23,8 +23,8 @@ use crate::prelude::*;
/// ///
/// Display: Move /// Display: Move
/// Category: layout /// Category: layout
#[node(Layout)] #[element(Layout)]
pub struct MoveNode { pub struct MoveElem {
/// The horizontal displacement of the content. /// The horizontal displacement of the content.
pub dx: Rel<Length>, pub dx: Rel<Length>,
@ -36,7 +36,7 @@ pub struct MoveNode {
pub body: Content, pub body: Content,
} }
impl Layout for MoveNode { impl Layout for MoveElem {
fn layout( fn layout(
&self, &self,
vt: &mut Vt, vt: &mut Vt,
@ -69,8 +69,8 @@ impl Layout for MoveNode {
/// ///
/// Display: Rotate /// Display: Rotate
/// Category: layout /// Category: layout
#[node(Layout)] #[element(Layout)]
pub struct RotateNode { pub struct RotateElem {
/// The amount of rotation. /// The amount of rotation.
/// ///
/// ```example /// ```example
@ -104,7 +104,7 @@ pub struct RotateNode {
pub body: Content, pub body: Content,
} }
impl Layout for RotateNode { impl Layout for RotateElem {
fn layout( fn layout(
&self, &self,
vt: &mut Vt, vt: &mut Vt,
@ -137,8 +137,8 @@ impl Layout for RotateNode {
/// ///
/// Display: Scale /// Display: Scale
/// Category: layout /// Category: layout
#[node(Layout)] #[element(Layout)]
pub struct ScaleNode { pub struct ScaleElem {
/// The horizontal scaling factor. /// The horizontal scaling factor.
/// ///
/// The body will be mirrored horizontally if the parameter is negative. /// The body will be mirrored horizontally if the parameter is negative.
@ -172,7 +172,7 @@ pub struct ScaleNode {
pub body: Content, pub body: Content,
} }
impl Layout for ScaleNode { impl Layout for ScaleElem {
fn layout( fn layout(
&self, &self,
vt: &mut Vt, vt: &mut Vt,

View File

@ -13,7 +13,7 @@ pub mod visualize;
use typst::diag::At; use typst::diag::At;
use typst::eval::{LangItems, Library, Module, Scope}; use typst::eval::{LangItems, Library, Module, Scope};
use typst::geom::{Align, Color, Dir, GenAlign, Smart}; use typst::geom::{Align, Color, Dir, GenAlign, Smart};
use typst::model::{Node, NodeId, StyleMap}; use typst::model::{Element, Styles};
use self::layout::LayoutRoot; use self::layout::LayoutRoot;
@ -30,69 +30,69 @@ fn global(math: Module, calc: Module) -> Module {
let mut global = Scope::deduplicating(); let mut global = Scope::deduplicating();
// Text. // Text.
global.define("text", text::TextNode::id()); global.define("text", text::TextElem::func());
global.define("linebreak", text::LinebreakNode::id()); global.define("linebreak", text::LinebreakElem::func());
global.define("smartquote", text::SmartQuoteNode::id()); global.define("smartquote", text::SmartQuoteElem::func());
global.define("strong", text::StrongNode::id()); global.define("strong", text::StrongElem::func());
global.define("emph", text::EmphNode::id()); global.define("emph", text::EmphElem::func());
global.define("lower", text::lower); global.define("lower", text::lower);
global.define("upper", text::upper); global.define("upper", text::upper);
global.define("smallcaps", text::smallcaps); global.define("smallcaps", text::smallcaps);
global.define("sub", text::SubNode::id()); global.define("sub", text::SubElem::func());
global.define("super", text::SuperNode::id()); global.define("super", text::SuperElem::func());
global.define("underline", text::UnderlineNode::id()); global.define("underline", text::UnderlineElem::func());
global.define("strike", text::StrikeNode::id()); global.define("strike", text::StrikeElem::func());
global.define("overline", text::OverlineNode::id()); global.define("overline", text::OverlineElem::func());
global.define("raw", text::RawNode::id()); global.define("raw", text::RawElem::func());
global.define("lorem", text::lorem); global.define("lorem", text::lorem);
// Math. // Math.
global.define("math", math); global.define("math", math);
// Layout. // Layout.
global.define("page", layout::PageNode::id()); global.define("page", layout::PageElem::func());
global.define("pagebreak", layout::PagebreakNode::id()); global.define("pagebreak", layout::PagebreakElem::func());
global.define("v", layout::VNode::id()); global.define("v", layout::VElem::func());
global.define("par", layout::ParNode::id()); global.define("par", layout::ParElem::func());
global.define("parbreak", layout::ParbreakNode::id()); global.define("parbreak", layout::ParbreakElem::func());
global.define("h", layout::HNode::id()); global.define("h", layout::HElem::func());
global.define("box", layout::BoxNode::id()); global.define("box", layout::BoxElem::func());
global.define("block", layout::BlockNode::id()); global.define("block", layout::BlockElem::func());
global.define("list", layout::ListNode::id()); global.define("list", layout::ListElem::func());
global.define("enum", layout::EnumNode::id()); global.define("enum", layout::EnumElem::func());
global.define("terms", layout::TermsNode::id()); global.define("terms", layout::TermsElem::func());
global.define("table", layout::TableNode::id()); global.define("table", layout::TableElem::func());
global.define("stack", layout::StackNode::id()); global.define("stack", layout::StackElem::func());
global.define("grid", layout::GridNode::id()); global.define("grid", layout::GridElem::func());
global.define("columns", layout::ColumnsNode::id()); global.define("columns", layout::ColumnsElem::func());
global.define("colbreak", layout::ColbreakNode::id()); global.define("colbreak", layout::ColbreakElem::func());
global.define("place", layout::PlaceNode::id()); global.define("place", layout::PlaceElem::func());
global.define("align", layout::AlignNode::id()); global.define("align", layout::AlignElem::func());
global.define("pad", layout::PadNode::id()); global.define("pad", layout::PadElem::func());
global.define("repeat", layout::RepeatNode::id()); global.define("repeat", layout::RepeatElem::func());
global.define("move", layout::MoveNode::id()); global.define("move", layout::MoveElem::func());
global.define("scale", layout::ScaleNode::id()); global.define("scale", layout::ScaleElem::func());
global.define("rotate", layout::RotateNode::id()); global.define("rotate", layout::RotateElem::func());
global.define("hide", layout::HideNode::id()); global.define("hide", layout::HideElem::func());
global.define("measure", layout::measure); global.define("measure", layout::measure);
// Visualize. // Visualize.
global.define("image", visualize::ImageNode::id()); global.define("image", visualize::ImageElem::func());
global.define("line", visualize::LineNode::id()); global.define("line", visualize::LineElem::func());
global.define("rect", visualize::RectNode::id()); global.define("rect", visualize::RectElem::func());
global.define("square", visualize::SquareNode::id()); global.define("square", visualize::SquareElem::func());
global.define("ellipse", visualize::EllipseNode::id()); global.define("ellipse", visualize::EllipseElem::func());
global.define("circle", visualize::CircleNode::id()); global.define("circle", visualize::CircleElem::func());
// Meta. // Meta.
global.define("document", meta::DocumentNode::id()); global.define("document", meta::DocumentElem::func());
global.define("ref", meta::RefNode::id()); global.define("ref", meta::RefElem::func());
global.define("link", meta::LinkNode::id()); global.define("link", meta::LinkElem::func());
global.define("outline", meta::OutlineNode::id()); global.define("outline", meta::OutlineElem::func());
global.define("heading", meta::HeadingNode::id()); global.define("heading", meta::HeadingElem::func());
global.define("figure", meta::FigureNode::id()); global.define("figure", meta::FigureElem::func());
global.define("cite", meta::CiteNode::id()); global.define("cite", meta::CiteElem::func());
global.define("bibliography", meta::BibliographyNode::id()); global.define("bibliography", meta::BibliographyElem::func());
global.define("locate", meta::locate); global.define("locate", meta::locate);
global.define("style", meta::style); global.define("style", meta::style);
global.define("counter", meta::counter); global.define("counter", meta::counter);
@ -166,71 +166,71 @@ fn global(math: Module, calc: Module) -> Module {
} }
/// Construct the standard style map. /// Construct the standard style map.
fn styles() -> StyleMap { fn styles() -> Styles {
StyleMap::new() Styles::new()
} }
/// Construct the standard lang item mapping. /// Construct the standard lang item mapping.
fn items() -> LangItems { fn items() -> LangItems {
LangItems { LangItems {
layout: |world, content, styles| content.layout_root(world, styles), layout: |world, content, styles| content.layout_root(world, styles),
em: text::TextNode::size_in, em: text::TextElem::size_in,
dir: text::TextNode::dir_in, dir: text::TextElem::dir_in,
space: || text::SpaceNode::new().pack(), space: || text::SpaceElem::new().pack(),
linebreak: || text::LinebreakNode::new().pack(), linebreak: || text::LinebreakElem::new().pack(),
text: |text| text::TextNode::new(text).pack(), text: |text| text::TextElem::new(text).pack(),
text_id: NodeId::of::<text::TextNode>(), text_func: text::TextElem::func(),
text_str: |content| Some(content.to::<text::TextNode>()?.text()), text_str: |content| Some(content.to::<text::TextElem>()?.text()),
smart_quote: |double| text::SmartQuoteNode::new().with_double(double).pack(), smart_quote: |double| text::SmartQuoteElem::new().with_double(double).pack(),
parbreak: || layout::ParbreakNode::new().pack(), parbreak: || layout::ParbreakElem::new().pack(),
strong: |body| text::StrongNode::new(body).pack(), strong: |body| text::StrongElem::new(body).pack(),
emph: |body| text::EmphNode::new(body).pack(), emph: |body| text::EmphElem::new(body).pack(),
raw: |text, lang, block| { 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 { if let Some(lang) = lang {
node.push_lang(Some(lang)); elem.push_lang(Some(lang));
} }
node.pack() elem.pack()
}, },
raw_languages: text::RawNode::languages, raw_languages: text::RawElem::languages,
link: |url| meta::LinkNode::from_url(url).pack(), link: |url| meta::LinkElem::from_url(url).pack(),
reference: |target, supplement| { reference: |target, supplement| {
let mut node = meta::RefNode::new(target); let mut elem = meta::RefElem::new(target);
if let Some(supplement) = supplement { if let Some(supplement) = supplement {
node.push_supplement(Smart::Custom(Some(meta::Supplement::Content( elem.push_supplement(Smart::Custom(Some(meta::Supplement::Content(
supplement, supplement,
)))); ))));
} }
node.pack() elem.pack()
}, },
bibliography_keys: meta::BibliographyNode::keys, bibliography_keys: meta::BibliographyElem::keys,
heading: |level, title| meta::HeadingNode::new(title).with_level(level).pack(), heading: |level, title| meta::HeadingElem::new(title).with_level(level).pack(),
list_item: |body| layout::ListItem::new(body).pack(), list_item: |body| layout::ListItem::new(body).pack(),
enum_item: |number, body| { enum_item: |number, body| {
let mut node = layout::EnumItem::new(body); let mut elem = layout::EnumItem::new(body);
if let Some(number) = number { 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(), term_item: |term, description| layout::TermItem::new(term, description).pack(),
equation: |body, block| math::EquationNode::new(body).with_block(block).pack(), equation: |body, block| math::EquationElem::new(body).with_block(block).pack(),
math_align_point: || math::AlignPointNode::new().pack(), math_align_point: || math::AlignPointElem::new().pack(),
math_delimited: |open, body, close| math::LrNode::new(open + body + close).pack(), math_delimited: |open, body, close| math::LrElem::new(open + body + close).pack(),
math_attach: |base, bottom, top| { math_attach: |base, bottom, top| {
let mut node = math::AttachNode::new(base); let mut elem = math::AttachElem::new(base);
if let Some(bottom) = bottom { if let Some(bottom) = bottom {
node.push_bottom(Some(bottom)); elem.push_bottom(Some(bottom));
} }
if let Some(top) = top { if let Some(top) = top {
node.push_top(Some(top)); elem.push_top(Some(top));
} }
node.pack() elem.pack()
}, },
math_accent: |base, accent| { 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| { library_method: |vm, dynamic, method, args, span| {
if let Some(counter) = dynamic.downcast::<meta::Counter>().cloned() { if let Some(counter) = dynamic.downcast::<meta::Counter>().cloned() {
counter.call_method(vm, method, args, span) counter.call_method(vm, method, args, span)

View File

@ -1,5 +1,3 @@
use typst::eval::combining_accent;
use super::*; use super::*;
/// How much the accent can be shorter than the base. /// 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 /// Display: Accent
/// Category: math /// Category: math
#[node(LayoutMath)] #[element(LayoutMath)]
pub struct AccentNode { pub struct AccentElem {
/// The base to which the accent is applied. /// The base to which the accent is applied.
/// May consist of multiple letters. /// May consist of multiple letters.
/// ///
@ -50,7 +48,7 @@ pub struct AccentNode {
pub accent: Accent, pub accent: Accent,
} }
impl LayoutMath for AccentNode { impl LayoutMath for AccentElem {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
ctx.style(ctx.style.with_cramped(true)); ctx.style(ctx.style.with_cramped(true));
let base = ctx.layout_fragment(&self.base())?; let base = ctx.layout_fragment(&self.base())?;
@ -116,15 +114,15 @@ pub struct Accent(char);
impl Accent { impl Accent {
/// Normalize a character into an accent. /// Normalize a character into an accent.
pub fn new(c: char) -> Self { pub fn new(c: char) -> Self {
Self(combining_accent(c).unwrap_or(c)) Self(Symbol::combining_accent(c).unwrap_or(c))
} }
} }
cast_from_value! { cast_from_value! {
Accent, Accent,
v: char => Self::new(v), v: char => Self::new(v),
v: Content => match v.to::<TextNode>() { v: Content => match v.to::<TextElem>() {
Some(node) => Value::Str(node.text().into()).cast()?, Some(elem) => Value::Str(elem.text().into()).cast()?,
None => Err("expected text")?, None => Err("expected text")?,
}, },
} }

View File

@ -4,10 +4,10 @@ use super::*;
/// ///
/// Display: Alignment Point /// Display: Alignment Point
/// Category: math /// Category: math
#[node(LayoutMath)] #[element(LayoutMath)]
pub struct AlignPointNode {} pub struct AlignPointElem {}
impl LayoutMath for AlignPointNode { impl LayoutMath for AlignPointElem {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
ctx.push(MathFragment::Align); ctx.push(MathFragment::Align);
Ok(()) Ok(())

View File

@ -13,8 +13,8 @@ use super::*;
/// ///
/// Display: Attachment /// Display: Attachment
/// Category: math /// Category: math
#[node(LayoutMath)] #[element(LayoutMath)]
pub struct AttachNode { pub struct AttachElem {
/// The base to which things are attached. /// The base to which things are attached.
#[required] #[required]
pub base: Content, pub base: Content,
@ -26,25 +26,25 @@ pub struct AttachNode {
pub bottom: Option<Content>, pub bottom: Option<Content>,
} }
impl LayoutMath for AttachNode { impl LayoutMath for AttachElem {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
let base = self.base(); let base = self.base();
let display_limits = base.is::<LimitsNode>(); let display_limits = base.is::<LimitsElem>();
let display_scripts = base.is::<ScriptsNode>(); let display_scripts = base.is::<ScriptsElem>();
let base = ctx.layout_fragment(&base)?; let base = ctx.layout_fragment(&base)?;
ctx.style(ctx.style.for_subscript()); ctx.style(ctx.style.for_subscript());
let top = self let top = self
.top(ctx.styles()) .top(ctx.styles())
.map(|node| ctx.layout_fragment(&node)) .map(|elem| ctx.layout_fragment(&elem))
.transpose()?; .transpose()?;
ctx.unstyle(); ctx.unstyle();
ctx.style(ctx.style.for_superscript()); ctx.style(ctx.style.for_superscript());
let bottom = self let bottom = self
.bottom(ctx.styles()) .bottom(ctx.styles())
.map(|node| ctx.layout_fragment(&node)) .map(|elem| ctx.layout_fragment(&elem))
.transpose()?; .transpose()?;
ctx.unstyle(); ctx.unstyle();
@ -75,14 +75,14 @@ impl LayoutMath for AttachNode {
/// ///
/// Display: Scripts /// Display: Scripts
/// Category: math /// Category: math
#[node(LayoutMath)] #[element(LayoutMath)]
pub struct ScriptsNode { pub struct ScriptsElem {
/// The base to attach the scripts to. /// The base to attach the scripts to.
#[required] #[required]
pub body: Content, pub body: Content,
} }
impl LayoutMath for ScriptsNode { impl LayoutMath for ScriptsElem {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
self.body().layout_math(ctx) self.body().layout_math(ctx)
} }
@ -97,14 +97,14 @@ impl LayoutMath for ScriptsNode {
/// ///
/// Display: Limits /// Display: Limits
/// Category: math /// Category: math
#[node(LayoutMath)] #[element(LayoutMath)]
pub struct LimitsNode { pub struct LimitsElem {
/// The base to attach the limits to. /// The base to attach the limits to.
#[required] #[required]
pub body: Content, pub body: Content,
} }
impl LayoutMath for LimitsNode { impl LayoutMath for LimitsElem {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
self.body().layout_math(ctx) self.body().layout_math(ctx)
} }

View File

@ -32,7 +32,7 @@ pub struct MathContext<'a, 'b, 'v> {
pub constants: ttf_parser::math::Constants<'a>, pub constants: ttf_parser::math::Constants<'a>,
pub space_width: Em, pub space_width: Em,
pub fragments: Vec<MathFragment>, pub fragments: Vec<MathFragment>,
pub map: StyleMap, pub local: Styles,
pub style: MathStyle, pub style: MathStyle,
pub size: Abs, pub size: Abs,
outer: StyleChain<'a>, outer: StyleChain<'a>,
@ -49,7 +49,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
) -> Self { ) -> Self {
let table = font.ttf().tables().math.unwrap(); let table = font.ttf().tables().math.unwrap();
let constants = table.constants.unwrap(); let constants = table.constants.unwrap();
let size = TextNode::size_in(styles); let size = TextElem::size_in(styles);
let ttf = font.ttf(); let ttf = font.ttf();
let space_width = ttf let space_width = ttf
.glyph_index(' ') .glyph_index(' ')
@ -67,7 +67,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
constants, constants,
space_width, space_width,
fragments: vec![], fragments: vec![],
map: StyleMap::new(), local: Styles::new(),
style: MathStyle { style: MathStyle {
variant: MathVariant::Serif, variant: MathVariant::Serif,
size: if block { MathSize::Display } else { MathSize::Text }, size: if block { MathSize::Display } else { MathSize::Text },
@ -94,39 +94,39 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
pub fn layout_fragment( pub fn layout_fragment(
&mut self, &mut self,
node: &dyn LayoutMath, elem: &dyn LayoutMath,
) -> SourceResult<MathFragment> { ) -> SourceResult<MathFragment> {
let row = self.layout_fragments(node)?; let row = self.layout_fragments(elem)?;
Ok(MathRow::new(row).to_fragment(self)) Ok(MathRow::new(row).to_fragment(self))
} }
pub fn layout_fragments( pub fn layout_fragments(
&mut self, &mut self,
node: &dyn LayoutMath, elem: &dyn LayoutMath,
) -> SourceResult<Vec<MathFragment>> { ) -> SourceResult<Vec<MathFragment>> {
let prev = std::mem::take(&mut self.fragments); let prev = std::mem::take(&mut self.fragments);
node.layout_math(self)?; elem.layout_math(self)?;
Ok(std::mem::replace(&mut self.fragments, prev)) Ok(std::mem::replace(&mut self.fragments, prev))
} }
pub fn layout_row(&mut self, node: &dyn LayoutMath) -> SourceResult<MathRow> { pub fn layout_row(&mut self, elem: &dyn LayoutMath) -> SourceResult<MathRow> {
let fragments = self.layout_fragments(node)?; let fragments = self.layout_fragments(elem)?;
Ok(MathRow::new(fragments)) Ok(MathRow::new(fragments))
} }
pub fn layout_frame(&mut self, node: &dyn LayoutMath) -> SourceResult<Frame> { pub fn layout_frame(&mut self, elem: &dyn LayoutMath) -> SourceResult<Frame> {
Ok(self.layout_fragment(node)?.to_frame()) Ok(self.layout_fragment(elem)?.to_frame())
} }
pub fn layout_content(&mut self, content: &Content) -> SourceResult<Frame> { pub fn layout_content(&mut self, content: &Content) -> SourceResult<Frame> {
Ok(content 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()) .into_frame())
} }
pub fn layout_text(&mut self, node: &TextNode) -> SourceResult<()> { pub fn layout_text(&mut self, elem: &TextElem) -> SourceResult<()> {
let text = node.text(); let text = elem.text();
let span = node.span(); let span = elem.span();
let mut chars = text.chars(); let mut chars = text.chars();
if let Some(glyph) = chars if let Some(glyph) = chars
.next() .next()
@ -160,7 +160,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
style = style.with_italic(false); style = style.with_italic(false);
} }
let text: EcoString = text.chars().map(|c| style.styled_char(c)).collect(); 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( self.push(
FrameFragment::new(self, frame) FrameFragment::new(self, frame)
.with_class(MathClass::Alphabetic) .with_class(MathClass::Alphabetic)
@ -172,21 +172,21 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
} }
pub fn styles(&self) -> StyleChain { pub fn styles(&self) -> StyleChain {
self.outer.chain(&self.map) self.outer.chain(&self.local)
} }
pub fn style(&mut self, style: MathStyle) { pub fn style(&mut self, style: MathStyle) {
self.style_stack.push((self.style, self.size)); 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.size = base_size * style.size.factor(self);
self.map.set(TextNode::set_size(TextSize(self.size.into()))); self.local.set(TextElem::set_size(TextSize(self.size.into())));
self.map self.local
.set(TextNode::set_style(if style.italic == Smart::Custom(true) { .set(TextElem::set_style(if style.italic == Smart::Custom(true) {
FontStyle::Italic FontStyle::Italic
} else { } else {
FontStyle::Normal FontStyle::Normal
})); }));
self.map.set(TextNode::set_weight(if style.bold { self.local.set(TextElem::set_weight(if style.bold {
FontWeight::BOLD FontWeight::BOLD
} else { } else {
FontWeight::REGULAR FontWeight::REGULAR
@ -196,9 +196,9 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
pub fn unstyle(&mut self) { pub fn unstyle(&mut self) {
(self.style, self.size) = self.style_stack.pop().unwrap(); (self.style, self.size) = self.style_stack.pop().unwrap();
self.map.unset(); self.local.unset();
self.map.unset(); self.local.unset();
self.map.unset(); self.local.unset();
} }
} }

View File

@ -16,8 +16,8 @@ pub(super) const DELIM_SHORT_FALL: Em = Em::new(0.1);
/// ///
/// Display: Left/Right /// Display: Left/Right
/// Category: math /// Category: math
#[node(LayoutMath)] #[element(LayoutMath)]
pub struct LrNode { pub struct LrElem {
/// The size of the brackets, relative to the height of the wrapped content. /// The size of the brackets, relative to the height of the wrapped content.
/// ///
/// Defaults to `{100%}`. /// Defaults to `{100%}`.
@ -29,7 +29,7 @@ pub struct LrNode {
let mut body = Content::empty(); let mut body = Content::empty();
for (i, arg) in args.all::<Content>()?.into_iter().enumerate() { for (i, arg) in args.all::<Content>()?.into_iter().enumerate() {
if i > 0 { if i > 0 {
body += TextNode::packed(','); body += TextElem::packed(',');
} }
body += arg; body += arg;
} }
@ -38,12 +38,12 @@ pub struct LrNode {
pub body: Content, pub body: Content,
} }
impl LayoutMath for LrNode { impl LayoutMath for LrElem {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
let mut body = self.body(); let mut body = self.body();
if let Some(node) = body.to::<LrNode>() { if let Some(elem) = body.to::<LrElem>() {
if node.size(ctx.styles()).is_auto() { if elem.size(ctx.styles()).is_auto() {
body = node.body(); body = elem.body();
} }
} }
@ -179,12 +179,11 @@ pub fn norm(
} }
fn delimited(body: Content, left: char, right: char) -> Value { fn delimited(body: Content, left: char, right: char) -> Value {
Value::Content( LrElem::new(Content::sequence([
LrNode::new(Content::sequence(vec![ TextElem::packed(left),
TextNode::packed(left), body,
body, TextElem::packed(right),
TextNode::packed(right), ]))
])) .pack()
.pack(), .into()
)
} }

View File

@ -19,8 +19,8 @@ const FRAC_AROUND: Em = Em::new(0.1);
/// ///
/// Display: Fraction /// Display: Fraction
/// Category: math /// Category: math
#[node(LayoutMath)] #[element(LayoutMath)]
pub struct FracNode { pub struct FracElem {
/// The fraction's numerator. /// The fraction's numerator.
#[required] #[required]
pub num: Content, pub num: Content,
@ -30,7 +30,7 @@ pub struct FracNode {
pub denom: Content, pub denom: Content,
} }
impl LayoutMath for FracNode { impl LayoutMath for FracElem {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
layout(ctx, &self.num(), &self.denom(), false, self.span()) layout(ctx, &self.num(), &self.denom(), false, self.span())
} }
@ -45,8 +45,8 @@ impl LayoutMath for FracNode {
/// ///
/// Display: Binomial /// Display: Binomial
/// Category: math /// Category: math
#[node(LayoutMath)] #[element(LayoutMath)]
pub struct BinomNode { pub struct BinomElem {
/// The binomial's upper index. /// The binomial's upper index.
#[required] #[required]
pub upper: Content, pub upper: Content,
@ -56,7 +56,7 @@ pub struct BinomNode {
pub lower: Content, pub lower: Content,
} }
impl LayoutMath for BinomNode { impl LayoutMath for BinomElem {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
layout(ctx, &self.upper(), &self.lower(), true, self.span()) layout(ctx, &self.upper(), &self.lower(), true, self.span())
} }
@ -132,9 +132,9 @@ fn layout(
} else { } else {
frame.push( frame.push(
line_pos, line_pos,
Element::Shape( FrameItem::Shape(
Geometry::Line(Point::with_x(line_width)).stroked(Stroke { Geometry::Line(Point::with_x(line_width)).stroked(Stroke {
paint: TextNode::fill_in(ctx.styles()), paint: TextElem::fill_in(ctx.styles()),
thickness, thickness,
}), }),
span, span,

View File

@ -181,8 +181,8 @@ impl GlyphFragment {
id, id,
c, c,
font: ctx.font.clone(), font: ctx.font.clone(),
lang: TextNode::lang_in(ctx.styles()), lang: TextElem::lang_in(ctx.styles()),
fill: TextNode::fill_in(ctx.styles()), fill: TextElem::fill_in(ctx.styles()),
style: ctx.style, style: ctx.style,
font_size: ctx.size, font_size: ctx.size,
width, width,
@ -215,7 +215,7 @@ impl GlyphFragment {
} }
pub fn to_frame(&self) -> Frame { pub fn to_frame(&self) -> Frame {
let text = Text { let item = TextItem {
font: self.font.clone(), font: self.font.clone(),
size: self.font_size, size: self.font_size,
fill: self.fill, fill: self.fill,
@ -232,7 +232,7 @@ impl GlyphFragment {
let size = Size::new(self.width, self.ascent + self.descent); let size = Size::new(self.width, self.ascent + self.descent);
let mut frame = Frame::new(size); let mut frame = Frame::new(size);
frame.set_baseline(self.ascent); 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 frame
} }
} }

View File

@ -16,8 +16,8 @@ const VERTICAL_PADDING: Ratio = Ratio::new(0.1);
/// ///
/// Display: Vector /// Display: Vector
/// Category: math /// Category: math
#[node(LayoutMath)] #[element(LayoutMath)]
pub struct VecNode { pub struct VecElem {
/// The delimiter to use. /// The delimiter to use.
/// ///
/// ```example /// ```example
@ -32,7 +32,7 @@ pub struct VecNode {
pub children: Vec<Content>, pub children: Vec<Content>,
} }
impl LayoutMath for VecNode { impl LayoutMath for VecElem {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
let delim = self.delim(ctx.styles()); let delim = self.delim(ctx.styles());
let frame = layout_vec_body(ctx, &self.children(), Align::Center)?; let frame = layout_vec_body(ctx, &self.children(), Align::Center)?;
@ -68,8 +68,8 @@ impl LayoutMath for VecNode {
/// ///
/// Display: Matrix /// Display: Matrix
/// Category: math /// Category: math
#[node(LayoutMath)] #[element(LayoutMath)]
pub struct MatNode { pub struct MatElem {
/// The delimiter to use. /// The delimiter to use.
/// ///
/// ```example /// ```example
@ -114,7 +114,7 @@ pub struct MatNode {
pub rows: Vec<Vec<Content>>, pub rows: Vec<Vec<Content>>,
} }
impl LayoutMath for MatNode { impl LayoutMath for MatElem {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
let delim = self.delim(ctx.styles()); let delim = self.delim(ctx.styles());
let frame = layout_mat_body(ctx, &self.rows())?; let frame = layout_mat_body(ctx, &self.rows())?;
@ -144,8 +144,8 @@ impl LayoutMath for MatNode {
/// ///
/// Display: Cases /// Display: Cases
/// Category: math /// Category: math
#[node(LayoutMath)] #[element(LayoutMath)]
pub struct CasesNode { pub struct CasesElem {
/// The delimiter to use. /// The delimiter to use.
/// ///
/// ```example /// ```example
@ -160,7 +160,7 @@ pub struct CasesNode {
pub children: Vec<Content>, pub children: Vec<Content>,
} }
impl LayoutMath for CasesNode { impl LayoutMath for CasesElem {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
let delim = self.delim(ctx.styles()); let delim = self.delim(ctx.styles());
let frame = layout_vec_body(ctx, &self.children(), Align::Left)?; let frame = layout_vec_body(ctx, &self.children(), Align::Left)?;
@ -221,8 +221,8 @@ fn layout_vec_body(
let gap = ROW_GAP.scaled(ctx); let gap = ROW_GAP.scaled(ctx);
ctx.style(ctx.style.for_denominator()); ctx.style(ctx.style.for_denominator());
let mut flat = vec![]; let mut flat = vec![];
for element in column { for child in column {
flat.push(ctx.layout_row(element)?); flat.push(ctx.layout_row(child)?);
} }
ctx.unstyle(); ctx.unstyle();
Ok(stack(ctx, flat, align, gap, 0)) Ok(stack(ctx, flat, align, gap, 0))

View File

@ -31,69 +31,69 @@ pub use self::underover::*;
use ttf_parser::{GlyphId, Rect}; use ttf_parser::{GlyphId, Rect};
use typst::eval::{Module, Scope}; use typst::eval::{Module, Scope};
use typst::font::{Font, FontWeight}; use typst::font::{Font, FontWeight};
use typst::model::{Guard, SequenceNode, StyledNode}; use typst::model::Guard;
use unicode_math_class::MathClass; use unicode_math_class::MathClass;
use self::ctx::*; use self::ctx::*;
use self::fragment::*; use self::fragment::*;
use self::row::*; use self::row::*;
use self::spacing::*; 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::meta::{Count, Counter, CounterUpdate, LocalName, Numbering};
use crate::prelude::*; use crate::prelude::*;
use crate::text::{ 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. /// Create a module with all math definitions.
pub fn module() -> Module { pub fn module() -> Module {
let mut math = Scope::deduplicating(); let mut math = Scope::deduplicating();
math.define("equation", EquationNode::id()); math.define("equation", EquationElem::func());
math.define("text", TextNode::id()); math.define("text", TextElem::func());
// Grouping. // Grouping.
math.define("lr", LrNode::id()); math.define("lr", LrElem::func());
math.define("abs", abs); math.define("abs", abs);
math.define("norm", norm); math.define("norm", norm);
math.define("floor", floor); math.define("floor", floor);
math.define("ceil", ceil); math.define("ceil", ceil);
// Attachments and accents. // Attachments and accents.
math.define("attach", AttachNode::id()); math.define("attach", AttachElem::func());
math.define("scripts", ScriptsNode::id()); math.define("scripts", ScriptsElem::func());
math.define("limits", LimitsNode::id()); math.define("limits", LimitsElem::func());
math.define("accent", AccentNode::id()); math.define("accent", AccentElem::func());
math.define("underline", UnderlineNode::id()); math.define("underline", UnderlineElem::func());
math.define("overline", OverlineNode::id()); math.define("overline", OverlineElem::func());
math.define("underbrace", UnderbraceNode::id()); math.define("underbrace", UnderbraceElem::func());
math.define("overbrace", OverbraceNode::id()); math.define("overbrace", OverbraceElem::func());
math.define("underbracket", UnderbracketNode::id()); math.define("underbracket", UnderbracketElem::func());
math.define("overbracket", OverbracketNode::id()); math.define("overbracket", OverbracketElem::func());
// Fractions and matrix-likes. // Fractions and matrix-likes.
math.define("frac", FracNode::id()); math.define("frac", FracElem::func());
math.define("binom", BinomNode::id()); math.define("binom", BinomElem::func());
math.define("vec", VecNode::id()); math.define("vec", VecElem::func());
math.define("mat", MatNode::id()); math.define("mat", MatElem::func());
math.define("cases", CasesNode::id()); math.define("cases", CasesElem::func());
// Roots. // Roots.
math.define("sqrt", SqrtNode::id()); math.define("sqrt", sqrt);
math.define("root", RootNode::id()); math.define("root", RootElem::func());
// Styles. // Styles.
math.define("upright", UprightNode::id()); math.define("upright", upright);
math.define("bold", BoldNode::id()); math.define("bold", bold);
math.define("italic", ItalicNode::id()); math.define("italic", italic);
math.define("serif", SerifNode::id()); math.define("serif", serif);
math.define("sans", SansNode::id()); math.define("sans", sans);
math.define("cal", CalNode::id()); math.define("cal", cal);
math.define("frak", FrakNode::id()); math.define("frak", frak);
math.define("mono", MonoNode::id()); math.define("mono", mono);
math.define("bb", BbNode::id()); math.define("bb", bb);
// Text operators. // Text operators.
math.define("op", OpNode::id()); math.define("op", OpElem::func());
op::define(&mut math); op::define(&mut math);
// Spacings. // Spacings.
@ -133,8 +133,8 @@ pub fn module() -> Module {
/// ///
/// Display: Equation /// Display: Equation
/// Category: math /// Category: math
#[node(Locatable, Synthesize, Show, Finalize, Layout, LayoutMath, Count, LocalName)] #[element(Locatable, Synthesize, Show, Finalize, Layout, LayoutMath, Count, LocalName)]
pub struct EquationNode { pub struct EquationElem {
/// Whether the equation is displayed as a separate block. /// Whether the equation is displayed as a separate block.
#[default(false)] #[default(false)]
pub block: bool, pub block: bool,
@ -157,16 +157,16 @@ pub struct EquationNode {
pub body: Content, pub body: Content,
} }
impl Synthesize for EquationNode { impl Synthesize for EquationElem {
fn synthesize(&mut self, _: &Vt, styles: StyleChain) { fn synthesize(&mut self, _: &Vt, styles: StyleChain) {
self.push_block(self.block(styles)); self.push_block(self.block(styles));
self.push_numbering(self.numbering(styles)); self.push_numbering(self.numbering(styles));
} }
} }
impl Show for EquationNode { impl Show for EquationElem {
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> { 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) { if self.block(styles) {
realized = realized.aligned(Axes::with_x(Some(Align::Center.into()))) 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 { fn finalize(&self, realized: Content, _: StyleChain) -> Content {
realized realized
.styled(TextNode::set_weight(FontWeight::from_number(450))) .styled(TextElem::set_weight(FontWeight::from_number(450)))
.styled(TextNode::set_font(FontList(vec![FontFamily::new( .styled(TextElem::set_font(FontList(vec![FontFamily::new(
"New Computer Modern Math", "New Computer Modern Math",
)]))) )])))
} }
} }
impl Layout for EquationNode { impl Layout for EquationElem {
fn layout( fn layout(
&self, &self,
vt: &mut Vt, vt: &mut Vt,
@ -215,7 +215,7 @@ impl Layout for EquationNode {
if block { if block {
if let Some(numbering) = self.numbering(styles) { if let Some(numbering) = self.numbering(styles) {
let pod = Regions::one(regions.base(), Axes::splat(false)); let pod = Regions::one(regions.base(), Axes::splat(false));
let counter = Counter::of(Self::id()) let counter = Counter::of(Self::func())
.display(numbering, false) .display(numbering, false)
.layout(vt, styles, pod)? .layout(vt, styles, pod)?
.into_frame(); .into_frame();
@ -230,7 +230,7 @@ impl Layout for EquationNode {
let height = frame.height().max(counter.height()); let height = frame.height().max(counter.height());
frame.resize(Size::new(width, height), Align::CENTER_HORIZON); 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() frame.width() - counter.width()
} else { } else {
Abs::zero() Abs::zero()
@ -240,10 +240,10 @@ impl Layout for EquationNode {
frame.push_frame(Point::new(x, y), counter) frame.push_frame(Point::new(x, y), counter)
} }
} else { } else {
let slack = ParNode::leading_in(styles) * 0.7; let slack = ParElem::leading_in(styles) * 0.7;
let top_edge = TextNode::top_edge_in(styles).resolve(styles, font.metrics()); let top_edge = TextElem::top_edge_in(styles).resolve(styles, font.metrics());
let bottom_edge = 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 ascent = top_edge.max(frame.ascent() - slack);
let descent = bottom_edge.max(frame.descent() - 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> { fn update(&self) -> Option<CounterUpdate> {
(self.block(StyleChain::default()) (self.block(StyleChain::default())
&& self.numbering(StyleChain::default()).is_some()) && 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 { fn local_name(&self, lang: Lang) -> &'static str {
match lang { match lang {
Lang::GERMAN => "Gleichung", Lang::GERMAN => "Gleichung",
@ -276,7 +276,7 @@ pub trait LayoutMath {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()>; fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()>;
} }
impl LayoutMath for EquationNode { impl LayoutMath for EquationElem {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
self.body().layout_math(ctx) self.body().layout_math(ctx)
} }
@ -284,45 +284,44 @@ impl LayoutMath for EquationNode {
impl LayoutMath for Content { impl LayoutMath for Content {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
if let Some(node) = self.to::<SequenceNode>() { if let Some(children) = self.to_sequence() {
for child in node.children() { for child in children {
child.layout_math(ctx)?; child.layout_math(ctx)?;
} }
return Ok(()); return Ok(());
} }
if let Some(styled) = self.to::<StyledNode>() { if let Some((elem, styles)) = self.to_styled() {
let map = styled.styles(); if TextElem::font_in(ctx.styles().chain(&styles))
if TextNode::font_in(ctx.styles().chain(&map)) != TextElem::font_in(ctx.styles())
!= TextNode::font_in(ctx.styles())
{ {
let frame = ctx.layout_content(self)?; let frame = ctx.layout_content(self)?;
ctx.push(FrameFragment::new(ctx, frame).with_spaced(true)); ctx.push(FrameFragment::new(ctx, frame).with_spaced(true));
return Ok(()); 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; let prev_size = ctx.size;
ctx.map.apply(prev_map.clone()); ctx.local.apply(prev_map.clone());
ctx.size = TextNode::size_in(ctx.styles()); ctx.size = TextElem::size_in(ctx.styles());
styled.body().layout_math(ctx)?; elem.layout_math(ctx)?;
ctx.size = prev_size; ctx.size = prev_size;
ctx.map = prev_map; ctx.local = prev_map;
return Ok(()); return Ok(());
} }
if self.is::<SpaceNode>() { if self.is::<SpaceElem>() {
ctx.push(MathFragment::Space(ctx.space_width.scaled(ctx))); ctx.push(MathFragment::Space(ctx.space_width.scaled(ctx)));
return Ok(()); return Ok(());
} }
if self.is::<LinebreakNode>() { if self.is::<LinebreakElem>() {
ctx.push(MathFragment::Linebreak); ctx.push(MathFragment::Linebreak);
return Ok(()); return Ok(());
} }
if let Some(node) = self.to::<HNode>() { if let Some(elem) = self.to::<HElem>() {
if let Spacing::Rel(rel) = node.amount() { if let Spacing::Rel(rel) = elem.amount() {
if rel.rel.is_zero() { if rel.rel.is_zero() {
ctx.push(MathFragment::Spacing(rel.abs.resolve(ctx.styles()))); ctx.push(MathFragment::Spacing(rel.abs.resolve(ctx.styles())));
} }
@ -330,13 +329,13 @@ impl LayoutMath for Content {
return Ok(()); return Ok(());
} }
if let Some(node) = self.to::<TextNode>() { if let Some(elem) = self.to::<TextElem>() {
ctx.layout_text(node)?; ctx.layout_text(elem)?;
return Ok(()); return Ok(());
} }
if let Some(node) = self.with::<dyn LayoutMath>() { if let Some(elem) = self.with::<dyn LayoutMath>() {
return node.layout_math(ctx); return elem.layout_math(ctx);
} }
let mut frame = ctx.layout_content(self)?; let mut frame = ctx.layout_content(self)?;

View File

@ -20,8 +20,8 @@ use super::*;
/// ///
/// Display: Text Operator /// Display: Text Operator
/// Category: math /// Category: math
#[node(LayoutMath)] #[element(LayoutMath)]
pub struct OpNode { pub struct OpElem {
/// The operator's text. /// The operator's text.
#[required] #[required]
pub text: EcoString, pub text: EcoString,
@ -33,9 +33,9 @@ pub struct OpNode {
pub limits: bool, pub limits: bool,
} }
impl LayoutMath for OpNode { impl LayoutMath for OpElem {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { 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( ctx.push(
FrameFragment::new(ctx, frame) FrameFragment::new(ctx, frame)
.with_class(MathClass::Large) .with_class(MathClass::Large)
@ -50,14 +50,14 @@ macro_rules! ops {
pub(super) fn define(math: &mut Scope) { pub(super) fn define(math: &mut Scope) {
$(math.define( $(math.define(
stringify!($name), stringify!($name),
OpNode::new(ops!(@name $name $(: $value)?).into()) OpElem::new(ops!(@name $name $(: $value)?).into())
.with_limits(ops!(@limit $($tts)*)) .with_limits(ops!(@limit $($tts)*))
.pack() .pack()
);)* );)*
let dif = |d| { let dif = |d| {
HNode::new(THIN.into()).pack() HElem::new(THIN.into()).pack()
+ UprightNode::new(TextNode::packed(d)).pack() + MathStyleElem::new(TextElem::packed(d)).with_italic(Some(false)).pack()
}; };
math.define("dif", dif('d')); math.define("dif", dif('d'));
math.define("Dif", dif('D')); math.define("Dif", dif('D'));

View File

@ -9,17 +9,13 @@ use super::*;
/// ///
/// Display: Square Root /// Display: Square Root
/// Category: math /// Category: math
#[node(LayoutMath)] /// Returns: content
pub struct SqrtNode { #[func]
pub fn sqrt(
/// The expression to take the square root of. /// The expression to take the square root of.
#[required] radicand: Content,
pub radicand: Content, ) -> Value {
} RootElem::new(radicand).pack().into()
impl LayoutMath for SqrtNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
layout(ctx, None, &self.radicand(), self.span())
}
} }
/// A general root. /// A general root.
@ -31,20 +27,20 @@ impl LayoutMath for SqrtNode {
/// ///
/// Display: Root /// Display: Root
/// Category: math /// Category: math
#[node(LayoutMath)] #[element(LayoutMath)]
pub struct RootNode { pub struct RootElem {
/// Which root of the radicand to take. /// Which root of the radicand to take.
#[required] #[positional]
index: Content, index: Option<Content>,
/// The expression to take the root of. /// The expression to take the root of.
#[required] #[required]
radicand: Content, radicand: Content,
} }
impl LayoutMath for RootNode { impl LayoutMath for RootElem {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { 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. // Layout the index.
// Script-script style looks too small, we use Script style instead. // Script-script style looks too small, we use Script style instead.
ctx.style(ctx.style.with_size(MathSize::Script)); 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(); ctx.unstyle();
let gap = gap.max((sqrt.height() - radicand.height() - thickness) / 2.0); 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_frame(sqrt_pos, sqrt);
frame.push( frame.push(
line_pos, line_pos,
Element::Shape( FrameItem::Shape(
Geometry::Line(Point::with_x(radicand.width())) 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, span,
), ),
); );
@ -139,15 +135,15 @@ fn layout(
/// Select a precomposed radical, if the font has it. /// Select a precomposed radical, if the font has it.
fn precomposed(ctx: &MathContext, index: Option<&Content>, target: Abs) -> Option<Frame> { fn precomposed(ctx: &MathContext, index: Option<&Content>, target: Abs) -> Option<Frame> {
let node = index?.to::<TextNode>()?; let elem = index?.to::<TextElem>()?;
let c = match node.text().as_str() { let c = match elem.text().as_str() {
"3" => '∛', "3" => '∛',
"4" => '∜', "4" => '∜',
_ => return None, _ => return None,
}; };
ctx.ttf.glyph_index(c)?; 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; let variant = glyph.stretch_vertical(ctx, target, Abs::zero()).frame;
if variant.height() < target { if variant.height() < target {
return None; return None;

View File

@ -1,4 +1,4 @@
use crate::layout::AlignNode; use crate::layout::AlignElem;
use super::*; use super::*;
@ -103,7 +103,7 @@ impl MathRow {
pub fn to_frame(self, ctx: &MathContext) -> Frame { pub fn to_frame(self, ctx: &MathContext) -> Frame {
let styles = ctx.styles(); 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) self.to_aligned_frame(ctx, &[], align)
} }
@ -124,7 +124,7 @@ impl MathRow {
if self.iter().any(|frag| matches!(frag, MathFragment::Linebreak)) { if self.iter().any(|frag| matches!(frag, MathFragment::Linebreak)) {
let fragments: Vec<_> = std::mem::take(&mut self.0); let fragments: Vec<_> = std::mem::take(&mut self.0);
let leading = if ctx.style.size >= MathSize::Text { let leading = if ctx.style.size >= MathSize::Text {
ParNode::leading_in(ctx.styles()) ParElem::leading_in(ctx.styles())
} else { } else {
TIGHT_LEADING.scaled(ctx) TIGHT_LEADING.scaled(ctx)
}; };

View File

@ -7,10 +7,10 @@ pub(super) const QUAD: Em = Em::new(1.0);
/// Hook up all spacings. /// Hook up all spacings.
pub(super) fn define(math: &mut Scope) { pub(super) fn define(math: &mut Scope) {
math.define("thin", HNode::new(THIN.into()).pack()); math.define("thin", HElem::new(THIN.into()).pack());
math.define("med", HNode::new(MEDIUM.into()).pack()); math.define("med", HElem::new(MEDIUM.into()).pack());
math.define("thick", HNode::new(THICK.into()).pack()); math.define("thick", HElem::new(THICK.into()).pack());
math.define("quad", HNode::new(QUAD.into()).pack()); math.define("quad", HElem::new(QUAD.into()).pack());
} }
/// Create the spacing between two fragments in a given style. /// Create the spacing between two fragments in a given style.

View File

@ -9,20 +9,13 @@ use super::*;
/// ///
/// Display: Bold /// Display: Bold
/// Category: math /// Category: math
#[node(LayoutMath)] /// Returns: content
pub struct BoldNode { #[func]
pub fn bold(
/// The content to style. /// The content to style.
#[required] body: Content,
pub body: Content, ) -> Value {
} MathStyleElem::new(body).with_bold(Some(true)).pack().into()
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(())
}
} }
/// Upright (non-italic) font style in math. /// Upright (non-italic) font style in math.
@ -34,20 +27,13 @@ impl LayoutMath for BoldNode {
/// ///
/// Display: Upright /// Display: Upright
/// Category: math /// Category: math
#[node(LayoutMath)] /// Returns: content
pub struct UprightNode { #[func]
pub fn upright(
/// The content to style. /// The content to style.
#[required] body: Content,
pub body: Content, ) -> Value {
} MathStyleElem::new(body).with_italic(Some(false)).pack().into()
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(())
}
} }
/// Italic font style in math. /// Italic font style in math.
@ -56,42 +42,30 @@ impl LayoutMath for UprightNode {
/// ///
/// Display: Italic /// Display: Italic
/// Category: math /// Category: math
#[node(LayoutMath)] /// Returns: content
pub struct ItalicNode { #[func]
pub fn italic(
/// The content to style. /// The content to style.
#[required] body: Content,
pub body: Content, ) -> Value {
MathStyleElem::new(body).with_italic(Some(true)).pack().into()
} }
impl LayoutMath for ItalicNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
ctx.style(ctx.style.with_italic(true));
self.body().layout_math(ctx)?;
ctx.unstyle();
Ok(())
}
}
/// Serif (roman) font style in math. /// Serif (roman) font style in math.
/// ///
/// This is already the default. /// This is already the default.
/// ///
/// Display: Serif /// Display: Serif
/// Category: math /// Category: math
#[node(LayoutMath)] /// Returns: content
pub struct SerifNode { #[func]
pub fn serif(
/// The content to style. /// The content to style.
#[required] body: Content,
pub body: Content, ) -> Value {
} MathStyleElem::new(body)
.with_variant(Some(MathVariant::Serif))
impl LayoutMath for SerifNode { .pack()
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { .into()
ctx.style(ctx.style.with_variant(MathVariant::Serif));
self.body().layout_math(ctx)?;
ctx.unstyle();
Ok(())
}
} }
/// Sans-serif font style in math. /// Sans-serif font style in math.
@ -103,20 +77,16 @@ impl LayoutMath for SerifNode {
/// ///
/// Display: Sans-serif /// Display: Sans-serif
/// Category: math /// Category: math
#[node(LayoutMath)] /// Returns: content
pub struct SansNode { #[func]
pub fn sans(
/// The content to style. /// The content to style.
#[required] body: Content,
pub body: Content, ) -> Value {
} MathStyleElem::new(body)
.with_variant(Some(MathVariant::Sans))
impl LayoutMath for SansNode { .pack()
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { .into()
ctx.style(ctx.style.with_variant(MathVariant::Sans));
self.body().layout_math(ctx)?;
ctx.unstyle();
Ok(())
}
} }
/// Calligraphic font style in math. /// Calligraphic font style in math.
@ -128,20 +98,16 @@ impl LayoutMath for SansNode {
/// ///
/// Display: Calligraphic /// Display: Calligraphic
/// Category: math /// Category: math
#[node(LayoutMath)] /// Returns: content
pub struct CalNode { #[func]
pub fn cal(
/// The content to style. /// The content to style.
#[required] body: Content,
pub body: Content, ) -> Value {
} MathStyleElem::new(body)
.with_variant(Some(MathVariant::Cal))
impl LayoutMath for CalNode { .pack()
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { .into()
ctx.style(ctx.style.with_variant(MathVariant::Cal));
self.body().layout_math(ctx)?;
ctx.unstyle();
Ok(())
}
} }
/// Fraktur font style in math. /// Fraktur font style in math.
@ -153,20 +119,16 @@ impl LayoutMath for CalNode {
/// ///
/// Display: Fraktur /// Display: Fraktur
/// Category: math /// Category: math
#[node(LayoutMath)] /// Returns: content
pub struct FrakNode { #[func]
pub fn frak(
/// The content to style. /// The content to style.
#[required] body: Content,
pub body: Content, ) -> Value {
} MathStyleElem::new(body)
.with_variant(Some(MathVariant::Frak))
impl LayoutMath for FrakNode { .pack()
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { .into()
ctx.style(ctx.style.with_variant(MathVariant::Frak));
self.body().layout_math(ctx)?;
ctx.unstyle();
Ok(())
}
} }
/// Monospace font style in math. /// Monospace font style in math.
@ -178,20 +140,16 @@ impl LayoutMath for FrakNode {
/// ///
/// Display: Monospace /// Display: Monospace
/// Category: math /// Category: math
#[node(LayoutMath)] /// Returns: content
pub struct MonoNode { #[func]
pub fn mono(
/// The content to style. /// The content to style.
#[required] body: Content,
pub body: Content, ) -> Value {
} MathStyleElem::new(body)
.with_variant(Some(MathVariant::Mono))
impl LayoutMath for MonoNode { .pack()
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { .into()
ctx.style(ctx.style.with_variant(MathVariant::Mono));
self.body().layout_math(ctx)?;
ctx.unstyle();
Ok(())
}
} }
/// Blackboard bold (double-struck) font style in math. /// Blackboard bold (double-struck) font style in math.
@ -208,16 +166,51 @@ impl LayoutMath for MonoNode {
/// ///
/// Display: Blackboard Bold /// Display: Blackboard Bold
/// Category: math /// Category: math
#[node(LayoutMath)] /// Returns: content
pub struct BbNode { #[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. /// The content to style.
#[required] #[required]
pub body: Content, 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<()> { 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)?; self.body().layout_math(ctx)?;
ctx.unstyle(); ctx.unstyle();
Ok(()) Ok(())
@ -324,7 +317,7 @@ impl MathSize {
} }
/// A mathematical style variant, as defined by Unicode. /// A mathematical style variant, as defined by Unicode.
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Cast)]
pub enum MathVariant { pub enum MathVariant {
Serif, Serif,
Sans, Sans,

View File

@ -13,14 +13,14 @@ const BRACKET_GAP: Em = Em::new(0.25);
/// ///
/// Display: Underline /// Display: Underline
/// Category: math /// Category: math
#[node(LayoutMath)] #[element(LayoutMath)]
pub struct UnderlineNode { pub struct UnderlineElem {
/// The content above the line. /// The content above the line.
#[required] #[required]
pub body: Content, pub body: Content,
} }
impl LayoutMath for UnderlineNode { impl LayoutMath for UnderlineElem {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
layout(ctx, &self.body(), &None, '\u{305}', LINE_GAP, false, self.span()) layout(ctx, &self.body(), &None, '\u{305}', LINE_GAP, false, self.span())
} }
@ -35,14 +35,14 @@ impl LayoutMath for UnderlineNode {
/// ///
/// Display: Overline /// Display: Overline
/// Category: math /// Category: math
#[node(LayoutMath)] #[element(LayoutMath)]
pub struct OverlineNode { pub struct OverlineElem {
/// The content below the line. /// The content below the line.
#[required] #[required]
pub body: Content, pub body: Content,
} }
impl LayoutMath for OverlineNode { impl LayoutMath for OverlineElem {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
layout(ctx, &self.body(), &None, '\u{332}', LINE_GAP, true, self.span()) layout(ctx, &self.body(), &None, '\u{332}', LINE_GAP, true, self.span())
} }
@ -57,8 +57,8 @@ impl LayoutMath for OverlineNode {
/// ///
/// Display: Underbrace /// Display: Underbrace
/// Category: math /// Category: math
#[node(LayoutMath)] #[element(LayoutMath)]
pub struct UnderbraceNode { pub struct UnderbraceElem {
/// The content above the brace. /// The content above the brace.
#[required] #[required]
pub body: Content, pub body: Content,
@ -68,7 +68,7 @@ pub struct UnderbraceNode {
pub annotation: Option<Content>, pub annotation: Option<Content>,
} }
impl LayoutMath for UnderbraceNode { impl LayoutMath for UnderbraceElem {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
layout( layout(
ctx, ctx,
@ -91,8 +91,8 @@ impl LayoutMath for UnderbraceNode {
/// ///
/// Display: Overbrace /// Display: Overbrace
/// Category: math /// Category: math
#[node(LayoutMath)] #[element(LayoutMath)]
pub struct OverbraceNode { pub struct OverbraceElem {
/// The content below the brace. /// The content below the brace.
#[required] #[required]
pub body: Content, pub body: Content,
@ -102,7 +102,7 @@ pub struct OverbraceNode {
pub annotation: Option<Content>, pub annotation: Option<Content>,
} }
impl LayoutMath for OverbraceNode { impl LayoutMath for OverbraceElem {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
layout( layout(
ctx, ctx,
@ -125,8 +125,8 @@ impl LayoutMath for OverbraceNode {
/// ///
/// Display: Underbracket /// Display: Underbracket
/// Category: math /// Category: math
#[node(LayoutMath)] #[element(LayoutMath)]
pub struct UnderbracketNode { pub struct UnderbracketElem {
/// The content above the bracket. /// The content above the bracket.
#[required] #[required]
pub body: Content, pub body: Content,
@ -136,7 +136,7 @@ pub struct UnderbracketNode {
pub annotation: Option<Content>, pub annotation: Option<Content>,
} }
impl LayoutMath for UnderbracketNode { impl LayoutMath for UnderbracketElem {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
layout( layout(
ctx, ctx,
@ -159,8 +159,8 @@ impl LayoutMath for UnderbracketNode {
/// ///
/// Display: Overbracket /// Display: Overbracket
/// Category: math /// Category: math
#[node(LayoutMath)] #[element(LayoutMath)]
pub struct OverbracketNode { pub struct OverbracketElem {
/// The content below the bracket. /// The content below the bracket.
#[required] #[required]
pub body: Content, pub body: Content,
@ -170,7 +170,7 @@ pub struct OverbracketNode {
pub annotation: Option<Content>, pub annotation: Option<Content>,
} }
impl LayoutMath for OverbracketNode { impl LayoutMath for OverbracketElem {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
layout( layout(
ctx, ctx,

View File

@ -8,18 +8,18 @@ use hayagriva::io::{BibLaTeXError, YamlBibliographyError};
use hayagriva::style::{self, Brackets, Citation, Database, DisplayString, Formatting}; use hayagriva::style::{self, Brackets, Citation, Database, DisplayString, Formatting};
use hayagriva::Entry; use hayagriva::Entry;
use super::{LocalName, RefNode}; use super::{LocalName, RefElem};
use crate::layout::{BlockNode, GridNode, ParNode, Sizing, TrackSizings, VNode}; use crate::layout::{BlockElem, GridElem, ParElem, Sizing, TrackSizings, VElem};
use crate::meta::HeadingNode; use crate::meta::HeadingElem;
use crate::prelude::*; use crate::prelude::*;
use crate::text::TextNode; use crate::text::TextElem;
/// A bibliography / reference listing. /// A bibliography / reference listing.
/// ///
/// Display: Bibliography /// Display: Bibliography
/// Category: meta /// Category: meta
#[node(Locatable, Synthesize, Show, LocalName)] #[element(Locatable, Synthesize, Show, LocalName)]
pub struct BibliographyNode { pub struct BibliographyElem {
/// Path to a Hayagriva `.yml` or BibLaTeX `.bib` file. /// Path to a Hayagriva `.yml` or BibLaTeX `.bib` file.
#[required] #[required]
#[parse( #[parse(
@ -45,11 +45,11 @@ pub struct BibliographyNode {
pub style: BibliographyStyle, pub style: BibliographyStyle,
} }
impl BibliographyNode { impl BibliographyElem {
/// Find the document's bibliography. /// Find the document's bibliography.
pub fn find(introspector: Tracked<Introspector>) -> StrResult<Self> { pub fn find(introspector: Tracked<Introspector>) -> StrResult<Self> {
let mut iter = introspector.query(Selector::node::<Self>()).into_iter(); let mut iter = introspector.query(Self::func().select()).into_iter();
let Some(node) = iter.next() else { let Some(elem) = iter.next() else {
return Err("the document does not contain a bibliography".into()); return Err("the document does not contain a bibliography".into());
}; };
@ -57,15 +57,15 @@ impl BibliographyNode {
Err("multiple bibliographies are not supported")?; 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. /// Whether the bibliography contains the given key.
pub fn has(vt: &Vt, key: &str) -> bool { pub fn has(vt: &Vt, key: &str) -> bool {
vt.introspector vt.introspector
.query(Selector::node::<Self>()) .query(Self::func().select())
.into_iter() .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() .flatten()
.any(|entry| entry.key() == key) .any(|entry| entry.key() == key)
} }
@ -76,7 +76,7 @@ impl BibliographyNode {
introspector: Tracked<Introspector>, introspector: Tracked<Introspector>,
) -> Vec<(EcoString, Option<EcoString>)> { ) -> Vec<(EcoString, Option<EcoString>)> {
Self::find(introspector) Self::find(introspector)
.and_then(|node| load(world, &node.path())) .and_then(|elem| load(world, &elem.path()))
.into_iter() .into_iter()
.flatten() .flatten()
.map(|entry| { .map(|entry| {
@ -89,13 +89,13 @@ impl BibliographyNode {
} }
} }
impl Synthesize for BibliographyNode { impl Synthesize for BibliographyElem {
fn synthesize(&mut self, _: &Vt, styles: StyleChain) { fn synthesize(&mut self, _: &Vt, styles: StyleChain) {
self.push_style(self.style(styles)); self.push_style(self.style(styles));
} }
} }
impl Show for BibliographyNode { impl Show for BibliographyElem {
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> { fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
const COLUMN_GUTTER: Em = Em::new(0.65); const COLUMN_GUTTER: Em = Em::new(0.65);
const INDENT: Em = Em::new(1.5); const INDENT: Em = Em::new(1.5);
@ -103,12 +103,12 @@ impl Show for BibliographyNode {
let mut seq = vec![]; let mut seq = vec![];
if let Some(title) = self.title(styles) { if let Some(title) = self.title(styles) {
let title = title.clone().unwrap_or_else(|| { 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()) .spanned(self.span())
}); });
seq.push( seq.push(
HeadingNode::new(title) HeadingElem::new(title)
.with_level(NonZeroUsize::ONE) .with_level(NonZeroUsize::ONE)
.with_numbering(None) .with_numbering(None)
.pack(), .pack(),
@ -121,7 +121,7 @@ impl Show for BibliographyNode {
let works = Works::new(vt).at(self.span())?; 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()) { if works.references.iter().any(|(prefix, _)| prefix.is_some()) {
let mut cells = vec![]; let mut cells = vec![];
for (prefix, reference) in &works.references { for (prefix, reference) in &works.references {
@ -129,9 +129,9 @@ impl Show for BibliographyNode {
cells.push(reference.clone()); 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( seq.push(
GridNode::new(cells) GridElem::new(cells)
.with_columns(TrackSizings(vec![Sizing::Auto; 2])) .with_columns(TrackSizings(vec![Sizing::Auto; 2]))
.with_column_gutter(TrackSizings(vec![COLUMN_GUTTER.into()])) .with_column_gutter(TrackSizings(vec![COLUMN_GUTTER.into()]))
.with_row_gutter(TrackSizings(vec![row_gutter.into()])) .with_row_gutter(TrackSizings(vec![row_gutter.into()]))
@ -140,13 +140,13 @@ impl Show for BibliographyNode {
} else { } else {
let mut entries = vec![]; let mut entries = vec![];
for (_, reference) in &works.references { 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()); entries.push(reference.clone());
} }
seq.push( seq.push(
Content::sequence(entries) 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 { fn local_name(&self, lang: Lang) -> &'static str {
match lang { match lang {
Lang::GERMAN => "Bibliographie", Lang::GERMAN => "Bibliographie",
@ -196,8 +196,8 @@ impl BibliographyStyle {
/// ///
/// Display: Citation /// Display: Citation
/// Category: meta /// Category: meta
#[node(Locatable, Synthesize, Show)] #[element(Locatable, Synthesize, Show)]
pub struct CiteNode { pub struct CiteElem {
/// The citation key. /// The citation key.
#[variadic] #[variadic]
pub keys: Vec<EcoString>, pub keys: Vec<EcoString>,
@ -217,7 +217,7 @@ pub struct CiteNode {
pub style: Smart<CitationStyle>, pub style: Smart<CitationStyle>,
} }
impl Synthesize for CiteNode { impl Synthesize for CiteElem {
fn synthesize(&mut self, _: &Vt, styles: StyleChain) { fn synthesize(&mut self, _: &Vt, styles: StyleChain) {
self.push_supplement(self.supplement(styles)); self.push_supplement(self.supplement(styles));
self.push_brackets(self.brackets(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> { fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> {
if !vt.introspector.init() { if !vt.introspector.init() {
return Ok(Content::empty()); return Ok(Content::empty());
} }
let works = Works::new(vt).at(self.span())?; let works = Works::new(vt).at(self.span())?;
let id = self.0.stable_id().unwrap(); let location = self.0.location().unwrap();
works works
.citations .citations
.get(&id) .get(&location)
.cloned() .cloned()
.flatten() .flatten()
.ok_or("bibliography does not contain this key") .ok_or("bibliography does not contain this key")
@ -264,24 +264,24 @@ pub enum CitationStyle {
/// Fully formatted citations and references. /// Fully formatted citations and references.
#[derive(Default)] #[derive(Default)]
struct Works { struct Works {
citations: HashMap<StableId, Option<Content>>, citations: HashMap<Location, Option<Content>>,
references: Vec<(Option<Content>, Content)>, references: Vec<(Option<Content>, Content)>,
} }
impl Works { impl Works {
/// Prepare all things need to cite a work or format a bibliography. /// Prepare all things need to cite a work or format a bibliography.
fn new(vt: &Vt) -> StrResult<Arc<Self>> { fn new(vt: &Vt) -> StrResult<Arc<Self>> {
let bibliography = BibliographyNode::find(vt.introspector)?; let bibliography = BibliographyElem::find(vt.introspector)?;
let citations = vt let citations = vt
.introspector .introspector
.query(Selector::Any(eco_vec![ .query(Selector::Any(eco_vec![
Selector::node::<RefNode>(), RefElem::func().select(),
Selector::node::<CiteNode>(), CiteElem::func().select(),
])) ]))
.into_iter() .into_iter()
.map(|node| match node.to::<RefNode>() { .map(|elem| match elem.to::<RefElem>() {
Some(reference) => reference.to_citation(StyleChain::default()), Some(reference) => reference.to_citation(StyleChain::default()),
_ => node.to::<CiteNode>().unwrap().clone(), _ => elem.to::<CiteElem>().unwrap().clone(),
}) })
.collect(); .collect();
Ok(create(vt.world, bibliography, citations)) Ok(create(vt.world, bibliography, citations))
@ -292,19 +292,19 @@ impl Works {
#[comemo::memoize] #[comemo::memoize]
fn create( fn create(
world: Tracked<dyn World>, world: Tracked<dyn World>,
bibliography: BibliographyNode, bibliography: BibliographyElem,
citations: Vec<CiteNode>, citations: Vec<CiteElem>,
) -> Arc<Works> { ) -> Arc<Works> {
let span = bibliography.span(); let span = bibliography.span();
let entries = load(world, &bibliography.path()).unwrap(); let entries = load(world, &bibliography.path()).unwrap();
let style = bibliography.style(StyleChain::default()); let style = bibliography.style(StyleChain::default());
let bib_id = bibliography.0.stable_id().unwrap(); let bib_location = bibliography.0.location().unwrap();
let ref_id = |target: &Entry| { let ref_location = |target: &Entry| {
let i = entries let i = entries
.iter() .iter()
.position(|entry| entry.key() == target.key()) .position(|entry| entry.key() == target.key())
.unwrap_or_default(); .unwrap_or_default();
bib_id.variant(i) bib_location.variant(i)
}; };
let mut db = Database::new(); let mut db = Database::new();
@ -312,7 +312,7 @@ fn create(
let mut preliminary = vec![]; let mut preliminary = vec![];
for citation in citations { for citation in citations {
let cite_id = citation.0.stable_id().unwrap(); let cite_id = citation.0.location().unwrap();
let entries = citation let entries = citation
.keys() .keys()
.into_iter() .into_iter()
@ -333,8 +333,8 @@ fn create(
let citations = preliminary let citations = preliminary
.into_iter() .into_iter()
.map(|(citation, cited)| { .map(|(citation, cited)| {
let id = citation.0.stable_id().unwrap(); let location = citation.0.location().unwrap();
let Some(cited) = cited else { return (id, None) }; let Some(cited) = cited else { return (location, None) };
let mut supplement = citation.supplement(StyleChain::default()); let mut supplement = citation.supplement(StyleChain::default());
let brackets = citation.brackets(StyleChain::default()); let brackets = citation.brackets(StyleChain::default());
@ -376,27 +376,27 @@ fn create(
} }
if i > 0 { if i > 0 {
content += TextNode::packed(",\u{a0}"); content += TextElem::packed(",\u{a0}");
} }
// Format and link to the reference entry. // Format and link to the reference entry.
content += format_display_string(&display, supplement, citation.span()) content += format_display_string(&display, supplement, citation.span())
.linked(Link::Node(ref_id(entry))); .linked(Destination::Location(ref_location(entry)));
} }
if brackets && len > 1 { if brackets && len > 1 {
content = match citation_style.brackets() { content = match citation_style.brackets() {
Brackets::None => content, Brackets::None => content,
Brackets::Round => { Brackets::Round => {
TextNode::packed('(') + content + TextNode::packed(')') TextElem::packed('(') + content + TextElem::packed(')')
} }
Brackets::Square => { Brackets::Square => {
TextNode::packed('[') + content + TextNode::packed(']') TextElem::packed('[') + content + TextElem::packed(']')
} }
}; };
} }
(id, Some(content)) (location, Some(content))
}) })
.collect(); .collect();
@ -414,15 +414,15 @@ fn create(
// Make link from citation to here work. // Make link from citation to here work.
let backlink = { let backlink = {
let mut content = Content::empty(); let mut content = Content::empty();
content.set_stable_id(ref_id(&reference.entry)); content.set_location(ref_location(&reference.entry));
MetaNode::set_data(vec![Meta::Node(content)]) MetaElem::set_data(vec![Meta::Elem(content)])
}; };
let prefix = reference.prefix.map(|prefix| { let prefix = reference.prefix.map(|prefix| {
// Format and link to first citation. // Format and link to first citation.
let bracketed = prefix.with_default_brackets(&*citation_style); let bracketed = prefix.with_default_brackets(&*citation_style);
format_display_string(&bracketed, None, span) format_display_string(&bracketed, None, span)
.linked(Link::Node(ids[reference.entry.key()])) .linked(Destination::Location(ids[reference.entry.key()]))
.styled(backlink.clone()) .styled(backlink.clone())
}); });
@ -510,7 +510,7 @@ fn format_display_string(
let mut content = if segment == SUPPLEMENT && supplement.is_some() { let mut content = if segment == SUPPLEMENT && supplement.is_some() {
supplement.take().unwrap_or_default() supplement.take().unwrap_or_default()
} else { } else {
TextNode::packed(segment).spanned(span) TextElem::packed(segment).spanned(span)
}; };
for (range, fmt) in &string.formatting { for (range, fmt) in &string.formatting {
@ -522,7 +522,7 @@ fn format_display_string(
Formatting::Bold => content.strong(), Formatting::Bold => content.strong(),
Formatting::Italic => content.emph(), Formatting::Italic => content.emph(),
Formatting::Link(link) => { Formatting::Link(link) => {
content.linked(Link::Dest(Destination::Url(link.as_str().into()))) content.linked(Destination::Url(link.as_str().into()))
} }
}; };
} }

View File

@ -10,28 +10,28 @@ pub fn locate(
/// The function to call with the location. /// The function to call with the location.
func: Func, func: Func,
) -> Value { ) -> Value {
LocateNode::new(func).pack().into() LocateElem::new(func).pack().into()
} }
/// Executes a `locate` call. /// Executes a `locate` call.
/// ///
/// Display: Styled /// Display: Styled
/// Category: special /// Category: special
#[node(Locatable, Show)] #[element(Locatable, Show)]
struct LocateNode { struct LocateElem {
/// The function to call with the location. /// The function to call with the location.
#[required] #[required]
func: Func, func: Func,
} }
impl Show for LocateNode { impl Show for LocateElem {
fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> { fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> {
if !vt.introspector.init() { if !vt.introspector.init() {
return Ok(Content::empty()); return Ok(Content::empty());
} }
let id = self.0.stable_id().unwrap(); let location = self.0.location().unwrap();
Ok(self.func().call_vt(vt, [id.into()])?.display()) Ok(self.func().call_vt(vt, [location.into()])?.display())
} }
} }
@ -45,21 +45,21 @@ pub fn style(
/// The function to call with the styles. /// The function to call with the styles.
func: Func, func: Func,
) -> Value { ) -> Value {
StyleNode::new(func).pack().into() StyleElem::new(func).pack().into()
} }
/// Executes a style access. /// Executes a style access.
/// ///
/// Display: Style /// Display: Style
/// Category: special /// Category: special
#[node(Show)] #[element(Show)]
struct StyleNode { struct StyleElem {
/// The function to call with the styles. /// The function to call with the styles.
#[required] #[required]
func: Func, func: Func,
} }
impl Show for StyleNode { impl Show for StyleElem {
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> { fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
Ok(self.func().call_vt(vt, [styles.to_map().into()])?.display()) Ok(self.func().call_vt(vt, [styles.to_map().into()])?.display())
} }

View File

@ -6,7 +6,7 @@ use smallvec::{smallvec, SmallVec};
use typst::eval::Tracer; use typst::eval::Tracer;
use super::{Numbering, NumberingPattern}; use super::{Numbering, NumberingPattern};
use crate::layout::PageNode; use crate::layout::PageElem;
use crate::prelude::*; use crate::prelude::*;
/// Count through pages, elements, and more. /// Count through pages, elements, and more.
@ -32,9 +32,9 @@ impl Counter {
Self(key) Self(key)
} }
/// The counter for the given node. /// The counter for the given element.
pub fn of(id: NodeId) -> Self { pub fn of(func: ElemFunc) -> Self {
Self::new(CounterKey::Selector(Selector::Node(id, None))) Self::new(CounterKey::Selector(Selector::Elem(func, None)))
} }
/// Call a method on counter. /// Call a method on counter.
@ -69,23 +69,23 @@ impl Counter {
/// Display the current value of the counter. /// Display the current value of the counter.
pub fn display(self, numbering: Numbering, both: bool) -> Content { 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. /// 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 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(); let (mut state, page) = sequence[offset].clone();
if self.is_page() { 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); state.step(NonZeroUsize::ONE, delta);
} }
Ok(state) Ok(state)
} }
/// Get the value of the state at the final location. /// 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 sequence = self.sequence(vt)?;
let (mut state, page) = sequence.last().unwrap().clone(); let (mut state, page) = sequence.last().unwrap().clone();
if self.is_page() { if self.is_page() {
@ -96,13 +96,13 @@ impl Counter {
} }
/// Get the current and final value of the state combined in one state. /// 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 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 at_state, at_page) = sequence[offset].clone();
let (mut final_state, final_page) = sequence.last().unwrap().clone(); let (mut final_state, final_page) = sequence.last().unwrap().clone();
if self.is_page() { 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); at_state.step(NonZeroUsize::ONE, at_delta);
let final_delta = vt.introspector.pages().get() - final_page.get(); let final_delta = vt.introspector.pages().get() - final_page.get();
final_state.step(NonZeroUsize::ONE, final_delta); final_state.step(NonZeroUsize::ONE, final_delta);
@ -112,7 +112,7 @@ impl Counter {
/// Produce content that performs a state update. /// Produce content that performs a state update.
pub fn update(self, update: CounterUpdate) -> Content { pub fn update(self, update: CounterUpdate) -> Content {
UpdateNode::new(self, update).pack() UpdateElem::new(self, update).pack()
} }
/// Produce the whole sequence of counter states. /// Produce the whole sequence of counter states.
@ -148,11 +148,11 @@ impl Counter {
let mut page = NonZeroUsize::ONE; let mut page = NonZeroUsize::ONE;
let mut stops = eco_vec![(state.clone(), page)]; 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() { if self.is_page() {
let id = node.stable_id().unwrap(); let location = elem.location().unwrap();
let prev = page; let prev = page;
page = introspector.page(id); page = introspector.page(location);
let delta = page.get() - prev.get(); let delta = page.get() - prev.get();
if delta > 0 { if delta > 0 {
@ -160,9 +160,9 @@ impl Counter {
} }
} }
if let Some(update) = match node.to::<UpdateNode>() { if let Some(update) = match elem.to::<UpdateElem>() {
Some(node) => Some(node.update()), Some(elem) => Some(elem.update()),
None => match node.with::<dyn Count>() { None => match elem.with::<dyn Count>() {
Some(countable) => countable.update(), Some(countable) => countable.update(),
None => Some(CounterUpdate::Step(NonZeroUsize::ONE)), None => Some(CounterUpdate::Step(NonZeroUsize::ONE)),
}, },
@ -178,10 +178,8 @@ impl Counter {
/// The selector relevant for this counter's updates. /// The selector relevant for this counter's updates.
fn selector(&self) -> Selector { fn selector(&self) -> Selector {
let mut selector = Selector::Node( let mut selector =
NodeId::of::<UpdateNode>(), Selector::Elem(UpdateElem::func(), Some(dict! { "counter" => self.clone() }));
Some(dict! { "counter" => self.clone() }),
);
if let CounterKey::Selector(key) = &self.0 { if let CounterKey::Selector(key) = &self.0 {
selector = Selector::Any(eco_vec![selector, key.clone()]); selector = Selector::Any(eco_vec![selector, key.clone()]);
@ -224,20 +222,16 @@ cast_from_value! {
CounterKey, CounterKey,
v: Str => Self::Str(v), v: Str => Self::Str(v),
label: Label => Self::Selector(Selector::Label(label)), label: Label => Self::Selector(Selector::Label(label)),
func: Func => { element: ElemFunc => {
let Some(id) = func.id() else { if element == PageElem::func() {
return Err("this function is not selectable".into());
};
if id == NodeId::of::<PageNode>() {
return Ok(Self::Page); return Ok(Self::Page);
} }
if !Content::new(id).can::<dyn Locatable>() { if !Content::new(element).can::<dyn Locatable>() {
Err(eco_format!("cannot count through {}s", id.name))?; 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), v: Func => Self::Func(v),
} }
/// Nodes that have special counting behaviour. /// Elements that have special counting behaviour.
pub trait Count { pub trait Count {
/// Get the counter update for this node. /// Get the counter update for this element.
fn update(&self) -> Option<CounterUpdate>; fn update(&self) -> Option<CounterUpdate>;
} }
@ -342,8 +336,8 @@ cast_to_value! {
/// ///
/// Display: State /// Display: State
/// Category: special /// Category: special
#[node(Locatable, Show)] #[element(Locatable, Show)]
struct DisplayNode { struct DisplayElem {
/// The counter. /// The counter.
#[required] #[required]
counter: Counter, counter: Counter,
@ -357,12 +351,16 @@ struct DisplayNode {
both: bool, both: bool,
} }
impl Show for DisplayNode { impl Show for DisplayElem {
fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> { 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 counter = self.counter();
let numbering = self.numbering(); 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) state.display(vt, &numbering)
} }
} }
@ -371,8 +369,8 @@ impl Show for DisplayNode {
/// ///
/// Display: State /// Display: State
/// Category: special /// Category: special
#[node(Locatable, Show)] #[element(Locatable, Show)]
struct UpdateNode { struct UpdateElem {
/// The counter. /// The counter.
#[required] #[required]
counter: Counter, counter: Counter,
@ -382,7 +380,7 @@ struct UpdateNode {
update: CounterUpdate, update: CounterUpdate,
} }
impl Show for UpdateNode { impl Show for UpdateElem {
fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> { fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> {
Ok(Content::empty()) Ok(Content::empty())
} }

View File

@ -1,6 +1,4 @@
use typst::model::StyledNode; use crate::layout::{LayoutRoot, PageElem};
use crate::layout::{LayoutRoot, PageNode};
use crate::prelude::*; use crate::prelude::*;
/// The root element of a document and its metadata. /// The root element of a document and its metadata.
@ -14,8 +12,8 @@ use crate::prelude::*;
/// ///
/// Display: Document /// Display: Document
/// Category: meta /// Category: meta
#[node(LayoutRoot)] #[element(LayoutRoot)]
pub struct DocumentNode { pub struct DocumentElem {
/// The document's title. This is often rendered as the title of the /// The document's title. This is often rendered as the title of the
/// PDF viewer window. /// PDF viewer window.
pub title: Option<EcoString>, pub title: Option<EcoString>,
@ -29,22 +27,20 @@ pub struct DocumentNode {
pub children: Vec<Content>, pub children: Vec<Content>,
} }
impl LayoutRoot for DocumentNode { impl LayoutRoot for DocumentElem {
/// Layout the document into a sequence of frames, one per page. /// Layout the document into a sequence of frames, one per page.
fn layout_root(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Document> { fn layout_root(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Document> {
let mut pages = vec![]; let mut pages = vec![];
for mut child in self.children() { for mut child in &self.children() {
let map;
let outer = styles; let outer = styles;
let mut styles = outer; let mut styles = styles;
if let Some(node) = child.to::<StyledNode>() { if let Some((elem, local)) = child.to_styled() {
map = node.styles(); styles = outer.chain(local);
styles = outer.chain(&map); child = elem;
child = node.body();
} }
if let Some(page) = child.to::<PageNode>() { if let Some(page) = child.to::<PageElem>() {
let fragment = page.layout(vt, styles)?; let fragment = page.layout(vt, styles)?;
pages.extend(fragment); pages.extend(fragment);
} else { } else {

View File

@ -1,9 +1,9 @@
use std::str::FromStr; use std::str::FromStr;
use super::{Count, Counter, CounterUpdate, LocalName, Numbering, NumberingPattern}; use super::{Count, Counter, CounterUpdate, LocalName, Numbering, NumberingPattern};
use crate::layout::{BlockNode, VNode}; use crate::layout::{BlockElem, VElem};
use crate::prelude::*; use crate::prelude::*;
use crate::text::TextNode; use crate::text::TextElem;
/// A figure with an optional caption. /// A figure with an optional caption.
/// ///
@ -23,8 +23,8 @@ use crate::text::TextNode;
/// ///
/// Display: Figure /// Display: Figure
/// Category: meta /// Category: meta
#[node(Locatable, Synthesize, Count, Show, LocalName)] #[element(Locatable, Synthesize, Count, Show, LocalName)]
pub struct FigureNode { pub struct FigureElem {
/// The content of the figure. Often, an [image]($func/image). /// The content of the figure. Often, an [image]($func/image).
#[required] #[required]
pub body: Content, pub body: Content,
@ -42,32 +42,32 @@ pub struct FigureNode {
pub gap: Length, pub gap: Length,
} }
impl Synthesize for FigureNode { impl Synthesize for FigureElem {
fn synthesize(&mut self, _: &Vt, styles: StyleChain) { fn synthesize(&mut self, _: &Vt, styles: StyleChain) {
self.push_numbering(self.numbering(styles)); self.push_numbering(self.numbering(styles));
} }
} }
impl Show for FigureNode { impl Show for FigureElem {
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> { fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
let mut realized = self.body(); let mut realized = self.body();
if let Some(mut caption) = self.caption(styles) { if let Some(mut caption) = self.caption(styles) {
if let Some(numbering) = self.numbering(styles) { if let Some(numbering) = self.numbering(styles) {
let name = self.local_name(TextNode::lang_in(styles)); let name = self.local_name(TextElem::lang_in(styles));
caption = TextNode::packed(eco_format!("{name}\u{a0}")) caption = TextElem::packed(eco_format!("{name}\u{a0}"))
+ Counter::of(Self::id()) + Counter::of(Self::func())
.display(numbering, false) .display(numbering, false)
.spanned(self.span()) .spanned(self.span())
+ TextNode::packed(": ") + TextElem::packed(": ")
+ caption; + caption;
} }
realized += VNode::weak(self.gap(styles).into()).pack(); realized += VElem::weak(self.gap(styles).into()).pack();
realized += caption; realized += caption;
} }
Ok(BlockNode::new() Ok(BlockElem::new()
.with_body(Some(realized)) .with_body(Some(realized))
.with_breakable(false) .with_breakable(false)
.pack() .pack()
@ -75,7 +75,7 @@ impl Show for FigureNode {
} }
} }
impl Count for FigureNode { impl Count for FigureElem {
fn update(&self) -> Option<CounterUpdate> { fn update(&self) -> Option<CounterUpdate> {
self.numbering(StyleChain::default()) self.numbering(StyleChain::default())
.is_some() .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 { fn local_name(&self, lang: Lang) -> &'static str {
match lang { match lang {
Lang::GERMAN => "Abbildung", Lang::GERMAN => "Abbildung",

View File

@ -1,10 +1,10 @@
use typst::font::FontWeight; use typst::font::FontWeight;
use super::{Counter, CounterUpdate, LocalName, Numbering}; use super::{Counter, CounterUpdate, LocalName, Numbering};
use crate::layout::{BlockNode, HNode, VNode}; use crate::layout::{BlockElem, HElem, VElem};
use crate::meta::Count; use crate::meta::Count;
use crate::prelude::*; use crate::prelude::*;
use crate::text::{TextNode, TextSize}; use crate::text::{TextElem, TextSize};
/// A section heading. /// A section heading.
/// ///
@ -41,8 +41,8 @@ use crate::text::{TextNode, TextSize};
/// ///
/// Display: Heading /// Display: Heading
/// Category: meta /// Category: meta
#[node(Locatable, Synthesize, Count, Show, Finalize, LocalName)] #[element(Locatable, Synthesize, Count, Show, Finalize, LocalName)]
pub struct HeadingNode { pub struct HeadingElem {
/// The logical nesting depth of the heading, starting from one. /// The logical nesting depth of the heading, starting from one.
#[default(NonZeroUsize::ONE)] #[default(NonZeroUsize::ONE)]
pub level: NonZeroUsize, pub level: NonZeroUsize,
@ -79,7 +79,7 @@ pub struct HeadingNode {
pub body: Content, pub body: Content,
} }
impl Synthesize for HeadingNode { impl Synthesize for HeadingElem {
fn synthesize(&mut self, _: &Vt, styles: StyleChain) { fn synthesize(&mut self, _: &Vt, styles: StyleChain) {
self.push_level(self.level(styles)); self.push_level(self.level(styles));
self.push_numbering(self.numbering(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> { fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
let mut realized = self.body(); let mut realized = self.body();
if let Some(numbering) = self.numbering(styles) { if let Some(numbering) = self.numbering(styles) {
realized = realized = Counter::of(Self::func())
Counter::of(Self::id()).display(numbering, false).spanned(self.span()) .display(numbering, false)
+ HNode::new(Em::new(0.3).into()).with_weak(true).pack() .spanned(self.span())
+ realized; + 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 { fn finalize(&self, realized: Content, styles: StyleChain) -> Content {
let level = self.level(styles).get(); let level = self.level(styles).get();
let scale = match level { 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 above = Em::new(if level == 1 { 1.8 } else { 1.44 }) / scale;
let below = Em::new(0.75) / scale; let below = Em::new(0.75) / scale;
let mut map = StyleMap::new(); let mut styles = Styles::new();
map.set(TextNode::set_size(TextSize(size.into()))); styles.set(TextElem::set_size(TextSize(size.into())));
map.set(TextNode::set_weight(FontWeight::BOLD)); styles.set(TextElem::set_weight(FontWeight::BOLD));
map.set(BlockNode::set_above(VNode::block_around(above.into()))); styles.set(BlockElem::set_above(VElem::block_around(above.into())));
map.set(BlockNode::set_below(VNode::block_around(below.into()))); styles.set(BlockElem::set_below(VElem::block_around(below.into())));
map.set(BlockNode::set_sticky(true)); styles.set(BlockElem::set_sticky(true));
realized.styled_with_map(map) realized.styled_with_map(styles)
} }
} }
impl Count for HeadingNode { impl Count for HeadingElem {
fn update(&self) -> Option<CounterUpdate> { fn update(&self) -> Option<CounterUpdate> {
self.numbering(StyleChain::default()) self.numbering(StyleChain::default())
.is_some() .is_some()
@ -132,11 +133,11 @@ impl Count for HeadingNode {
} }
cast_from_value! { cast_from_value! {
HeadingNode, HeadingElem,
v: Content => v.to::<Self>().ok_or("expected heading")?.clone(), 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 { fn local_name(&self, lang: Lang) -> &'static str {
match lang { match lang {
Lang::GERMAN => "Abschnitt", Lang::GERMAN => "Abschnitt",

View File

@ -1,5 +1,5 @@
use crate::prelude::*; use crate::prelude::*;
use crate::text::{Hyphenate, TextNode}; use crate::text::{Hyphenate, TextElem};
/// Link to a URL or another location in the document. /// Link to a URL or another location in the document.
/// ///
@ -25,19 +25,20 @@ use crate::text::{Hyphenate, TextNode};
/// ///
/// Display: Link /// Display: Link
/// Category: meta /// Category: meta
#[node(Show, Finalize)] #[element(Show, Finalize)]
pub struct LinkNode { pub struct LinkElem {
/// The destination the link points to. /// The destination the link points to.
/// ///
/// - To link to web pages, `dest` should be a valid URL string. If the URL is /// - To link to web pages, `dest` should be a valid URL string. If the URL
/// in the `mailto:` or `tel:` scheme and the `body` parameter is omitted, /// is in the `mailto:` or `tel:` scheme and the `body` parameter is
/// the email address or phone number will be the link's body, without the /// omitted, the email address or phone number will be the link's body,
/// scheme. /// without the scheme.
/// ///
/// - To link to another part of the document, `dest` must contain a /// - To link to another part of the document, `dest` can take one of two
/// dictionary with a `page` key of type `integer` and `x` and `y` /// forms: A [`location`]($func/locate) or a dictionary with a `page` key
/// coordinates of type `length`. Pages are counted from one, and the /// of type `integer` and `x` and `y` coordinates of type `length`. Pages
/// coordinates are relative to the page's top left corner. /// are counted from one, and the coordinates are relative to the page's
/// top left corner.
/// ///
/// ```example /// ```example
/// #link("mailto:hello@typst.app") \ /// #link("mailto:hello@typst.app") \
@ -45,7 +46,6 @@ pub struct LinkNode {
/// Go to top /// Go to top
/// ] /// ]
/// ``` /// ```
///
#[required] #[required]
#[parse( #[parse(
let dest = args.expect::<Destination>("destination")?; let dest = args.expect::<Destination>("destination")?;
@ -64,30 +64,30 @@ pub struct LinkNode {
Some(body) => body, Some(body) => body,
None => body_from_url(url), None => body_from_url(url),
}, },
Destination::Internal(_) => args.expect("body")?, _ => args.expect("body")?,
})] })]
pub body: Content, pub body: Content,
} }
impl LinkNode { impl LinkElem {
/// Create a link node from a URL with its bare text. /// Create a link element from a URL with its bare text.
pub fn from_url(url: EcoString) -> Self { pub fn from_url(url: EcoString) -> Self {
let body = body_from_url(&url); let body = body_from_url(&url);
Self::new(Destination::Url(url), body) Self::new(Destination::Url(url), body)
} }
} }
impl Show for LinkNode { impl Show for LinkElem {
fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> { fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> {
Ok(self.body()) Ok(self.body())
} }
} }
impl Finalize for LinkNode { impl Finalize for LinkElem {
fn finalize(&self, realized: Content, _: StyleChain) -> Content { fn finalize(&self, realized: Content, _: StyleChain) -> Content {
realized realized
.linked(Link::Dest(self.dest())) .linked(self.dest())
.styled(TextNode::set_hyphenate(Hyphenate(Smart::Custom(false)))) .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); text = text.trim_start_matches(prefix);
} }
let shorter = text.len() < url.len(); let shorter = text.len() < url.len();
TextNode::packed(if shorter { text.into() } else { url.clone() }) TextElem::packed(if shorter { text.into() } else { url.clone() })
} }

View File

@ -1,7 +1,7 @@
use super::{Counter, HeadingNode, LocalName}; use super::{Counter, HeadingElem, LocalName};
use crate::layout::{BoxNode, HNode, HideNode, ParbreakNode, RepeatNode}; use crate::layout::{BoxElem, HElem, HideElem, ParbreakElem, RepeatElem};
use crate::prelude::*; use crate::prelude::*;
use crate::text::{LinebreakNode, SpaceNode, TextNode}; use crate::text::{LinebreakElem, SpaceElem, TextElem};
/// A section outline / table of contents. /// A section outline / table of contents.
/// ///
@ -22,8 +22,8 @@ use crate::text::{LinebreakNode, SpaceNode, TextNode};
/// ///
/// Display: Outline /// Display: Outline
/// Category: meta /// Category: meta
#[node(Show, LocalName)] #[element(Show, LocalName)]
pub struct OutlineNode { pub struct OutlineElem {
/// The title of the outline. /// The title of the outline.
/// ///
/// - When set to `{auto}`, an appropriate title for the [text /// - When set to `{auto}`, an appropriate title for the [text
@ -65,21 +65,21 @@ pub struct OutlineNode {
/// ///
/// = A New Beginning /// = A New Beginning
/// ``` /// ```
#[default(Some(RepeatNode::new(TextNode::packed(".")).pack()))] #[default(Some(RepeatElem::new(TextElem::packed(".")).pack()))]
pub fill: Option<Content>, pub fill: Option<Content>,
} }
impl Show for OutlineNode { impl Show for OutlineElem {
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> { 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) { if let Some(title) = self.title(styles) {
let title = title.clone().unwrap_or_else(|| { 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()) .spanned(self.span())
}); });
seq.push( seq.push(
HeadingNode::new(title) HeadingElem::new(title)
.with_level(NonZeroUsize::ONE) .with_level(NonZeroUsize::ONE)
.with_numbering(None) .with_numbering(None)
.with_outlined(false) .with_outlined(false)
@ -90,15 +90,15 @@ impl Show for OutlineNode {
let indent = self.indent(styles); let indent = self.indent(styles);
let depth = self.depth(styles); let depth = self.depth(styles);
let mut ancestors: Vec<&HeadingNode> = vec![]; let mut ancestors: Vec<&HeadingElem> = vec![];
let nodes = vt.introspector.query(Selector::Node( let elems = vt.introspector.query(Selector::Elem(
NodeId::of::<HeadingNode>(), HeadingElem::func(),
Some(dict! { "outlined" => true }), Some(dict! { "outlined" => true }),
)); ));
for node in &nodes { for elem in &elems {
let heading = node.to::<HeadingNode>().unwrap(); let heading = elem.to::<HeadingElem>().unwrap();
let stable_id = heading.0.stable_id().unwrap(); let location = heading.0.location().unwrap();
if !heading.outlined(StyleChain::default()) { if !heading.outlined(StyleChain::default()) {
continue; continue;
} }
@ -120,60 +120,60 @@ impl Show for OutlineNode {
let mut hidden = Content::empty(); let mut hidden = Content::empty();
for ancestor in &ancestors { for ancestor in &ancestors {
if let Some(numbering) = ancestor.numbering(StyleChain::default()) { if let Some(numbering) = ancestor.numbering(StyleChain::default()) {
let numbers = Counter::of(HeadingNode::id()) let numbers = Counter::of(HeadingElem::func())
.at(vt, ancestor.0.stable_id().unwrap())? .at(vt, ancestor.0.location().unwrap())?
.display(vt, &numbering)?; .display(vt, &numbering)?;
hidden += numbers + SpaceNode::new().pack(); hidden += numbers + SpaceElem::new().pack();
}; };
} }
if !ancestors.is_empty() { if !ancestors.is_empty() {
seq.push(HideNode::new(hidden).pack()); seq.push(HideElem::new(hidden).pack());
seq.push(SpaceNode::new().pack()); seq.push(SpaceElem::new().pack());
} }
} }
// Format the numbering. // Format the numbering.
let mut start = heading.body(); let mut start = heading.body();
if let Some(numbering) = heading.numbering(StyleChain::default()) { if let Some(numbering) = heading.numbering(StyleChain::default()) {
let numbers = Counter::of(HeadingNode::id()) let numbers = Counter::of(HeadingElem::func())
.at(vt, stable_id)? .at(vt, location)?
.display(vt, &numbering)?; .display(vt, &numbering)?;
start = numbers + SpaceNode::new().pack() + start; start = numbers + SpaceElem::new().pack() + start;
}; };
// Add the numbering and section name. // 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. // Add filler symbols between the section name and page number.
if let Some(filler) = self.fill(styles) { if let Some(filler) = self.fill(styles) {
seq.push(SpaceNode::new().pack()); seq.push(SpaceElem::new().pack());
seq.push( seq.push(
BoxNode::new() BoxElem::new()
.with_body(Some(filler.clone())) .with_body(Some(filler.clone()))
.with_width(Fr::one().into()) .with_width(Fr::one().into())
.pack(), .pack(),
); );
seq.push(SpaceNode::new().pack()); seq.push(SpaceElem::new().pack());
} else { } else {
seq.push(HNode::new(Fr::one().into()).pack()); seq.push(HElem::new(Fr::one().into()).pack());
} }
// Add the page number and linebreak. // Add the page number and linebreak.
let page = vt.introspector.page(stable_id); let page = vt.introspector.page(location);
let end = TextNode::packed(eco_format!("{page}")); let end = TextElem::packed(eco_format!("{page}"));
seq.push(end.linked(Link::Node(stable_id))); seq.push(end.linked(Destination::Location(location)));
seq.push(LinebreakNode::new().pack()); seq.push(LinebreakElem::new().pack());
ancestors.push(heading); ancestors.push(heading);
} }
seq.push(ParbreakNode::new().pack()); seq.push(ParbreakElem::new().pack());
Ok(Content::sequence(seq)) Ok(Content::sequence(seq))
} }
} }
impl LocalName for OutlineNode { impl LocalName for OutlineElem {
fn local_name(&self, lang: Lang) -> &'static str { fn local_name(&self, lang: Lang) -> &'static str {
match lang { match lang {
Lang::GERMAN => "Inhaltsverzeichnis", Lang::GERMAN => "Inhaltsverzeichnis",

View File

@ -11,24 +11,24 @@ pub fn query(
target: Target, target: Target,
/// The location. /// The location.
#[external] #[external]
location: StableId, location: Location,
/// The location before which to query. /// The location before which to query.
#[named] #[named]
#[external] #[external]
before: StableId, before: Location,
/// The location after which to query. /// The location after which to query.
#[named] #[named]
#[external] #[external]
after: StableId, after: Location,
) -> Value { ) -> Value {
let selector = target.0; let selector = target.0;
let introspector = vm.vt.introspector; let introspector = vm.vt.introspector;
let elements = if let Some(id) = args.named("before")? { let elements = if let Some(location) = args.named("before")? {
introspector.query_before(selector, id) introspector.query_before(selector, location)
} else if let Some(id) = args.named("after")? { } else if let Some(location) = args.named("after")? {
introspector.query_after(selector, id) introspector.query_after(selector, location)
} else { } else {
let _: StableId = args.expect("id")?; let _: Location = args.expect("location")?;
introspector.query(selector) introspector.query(selector)
}; };
elements.into() elements.into()
@ -40,15 +40,11 @@ struct Target(Selector);
cast_from_value! { cast_from_value! {
Target, Target,
label: Label => Self(Selector::Label(label)), label: Label => Self(Selector::Label(label)),
func: Func => { element: ElemFunc => {
let Some(id) = func.id() else { if !Content::new(element).can::<dyn Locatable>() {
return Err("this function is not selectable".into()); Err(eco_format!("cannot query for {}s", element.name()))?;
};
if !Content::new(id).can::<dyn Locatable>() {
Err(eco_format!("cannot query for {}s", id.name))?;
} }
Self(Selector::Node(id, None)) Self(Selector::Elem(element, None))
} }
} }

View File

@ -1,6 +1,6 @@
use super::{BibliographyNode, CiteNode, Counter, LocalName, Numbering}; use super::{BibliographyElem, CiteElem, Counter, LocalName, Numbering};
use crate::prelude::*; use crate::prelude::*;
use crate::text::TextNode; use crate::text::TextElem;
/// A reference to a label. /// A reference to a label.
/// ///
@ -35,8 +35,8 @@ use crate::text::TextNode;
/// ///
/// Display: Reference /// Display: Reference
/// Category: meta /// Category: meta
#[node(Locatable, Show)] #[element(Locatable, Show)]
pub struct RefNode { pub struct RefElem {
/// The target label that should be referenced. /// The target label that should be referenced.
#[required] #[required]
pub target: Label, pub target: Label,
@ -63,7 +63,7 @@ pub struct RefNode {
pub supplement: Smart<Option<Supplement>>, pub supplement: Smart<Option<Supplement>>,
} }
impl Show for RefNode { impl Show for RefElem {
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> { fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
if !vt.introspector.init() { if !vt.introspector.init() {
return Ok(Content::empty()); return Ok(Content::empty());
@ -72,7 +72,7 @@ impl Show for RefNode {
let target = self.target(); let target = self.target();
let matches = vt.introspector.query(Selector::Label(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() { if !matches.is_empty() {
bail!(self.span(), "label occurs in the document and its bibliography"); 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); 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() { bail!(self.span(), if matches.is_empty() {
"label does not exist in the document" "label does not exist in the document"
} else { } else {
@ -88,50 +88,50 @@ impl Show for RefNode {
}); });
}; };
if !node.can::<dyn Locatable>() { if !elem.can::<dyn Locatable>() {
bail!(self.span(), "cannot reference {}", node.id().name); bail!(self.span(), "cannot reference {}", elem.func().name());
} }
let supplement = self.supplement(styles); let supplement = self.supplement(styles);
let mut supplement = match supplement { let mut supplement = match supplement {
Smart::Auto => node Smart::Auto => elem
.with::<dyn LocalName>() .with::<dyn LocalName>()
.map(|node| node.local_name(TextNode::lang_in(styles))) .map(|elem| elem.local_name(TextElem::lang_in(styles)))
.map(TextNode::packed) .map(TextElem::packed)
.unwrap_or_default(), .unwrap_or_default(),
Smart::Custom(None) => Content::empty(), Smart::Custom(None) => Content::empty(),
Smart::Custom(Some(Supplement::Content(content))) => content.clone(), Smart::Custom(Some(Supplement::Content(content))) => content.clone(),
Smart::Custom(Some(Supplement::Func(func))) => { 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() { 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"); bail!(self.span(), "only numbered elements can be referenced");
}; };
let numbers = Counter::of(node.id()) let numbers = Counter::of(elem.func())
.at(vt, node.stable_id().unwrap())? .at(vt, elem.location().unwrap())?
.display(vt, &numbering.trimmed())?; .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. /// Turn the rference into a citation.
pub fn to_citation(&self, styles: StyleChain) -> CiteNode { pub fn to_citation(&self, styles: StyleChain) -> CiteElem {
let mut node = CiteNode::new(vec![self.target().0]); let mut elem = CiteElem::new(vec![self.target().0]);
node.push_supplement(match self.supplement(styles) { elem.push_supplement(match self.supplement(styles) {
Smart::Custom(Some(Supplement::Content(content))) => Some(content), Smart::Custom(Some(Supplement::Content(content))) => Some(content),
_ => None, _ => None,
}); });
node.0.set_stable_id(self.0.stable_id().unwrap()); elem.0.set_location(self.0.location().unwrap());
node elem
} }
} }

View File

@ -52,25 +52,25 @@ impl State {
/// Display the current value of the state. /// Display the current value of the state.
pub fn display(self, func: Option<Func>) -> Content { 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. /// 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 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()) Ok(sequence[offset].clone())
} }
/// Get the value of the state at the final location. /// 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)?; let sequence = self.sequence(vt)?;
Ok(sequence.last().unwrap().clone()) Ok(sequence.last().unwrap().clone())
} }
/// Produce content that performs a state update. /// Produce content that performs a state update.
pub fn update(self, update: StateUpdate) -> Content { pub fn update(self, update: StateUpdate) -> Content {
UpdateNode::new(self, update).pack() UpdateElem::new(self, update).pack()
} }
/// Produce the whole sequence of states. /// Produce the whole sequence of states.
@ -99,9 +99,9 @@ impl State {
let mut state = self.init.clone(); let mut state = self.init.clone();
let mut stops = eco_vec![state.clone()]; let mut stops = eco_vec![state.clone()];
for node in introspector.query(self.selector()) { for elem in introspector.query(self.selector()) {
let node = node.to::<UpdateNode>().unwrap(); let elem = elem.to::<UpdateElem>().unwrap();
match node.update() { match elem.update() {
StateUpdate::Set(value) => state = value, StateUpdate::Set(value) => state = value,
StateUpdate::Func(func) => state = func.call_vt(&mut vt, [state])?, StateUpdate::Func(func) => state = func.call_vt(&mut vt, [state])?,
} }
@ -113,10 +113,7 @@ impl State {
/// The selector for this state's updates. /// The selector for this state's updates.
fn selector(&self) -> Selector { fn selector(&self) -> Selector {
Selector::Node( Selector::Elem(UpdateElem::func(), Some(dict! { "state" => self.clone() }))
NodeId::of::<UpdateNode>(),
Some(dict! { "state" => self.clone() }),
)
} }
} }
@ -159,8 +156,8 @@ cast_from_value! {
/// ///
/// Display: State /// Display: State
/// Category: special /// Category: special
#[node(Locatable, Show)] #[element(Locatable, Show)]
struct DisplayNode { struct DisplayElem {
/// The state. /// The state.
#[required] #[required]
state: State, state: State,
@ -170,10 +167,10 @@ struct DisplayNode {
func: Option<Func>, func: Option<Func>,
} }
impl Show for DisplayNode { impl Show for DisplayElem {
fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> { fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> {
let id = self.0.stable_id().unwrap(); let location = self.0.location().unwrap();
let value = self.state().at(vt, id)?; let value = self.state().at(vt, location)?;
Ok(match self.func() { Ok(match self.func() {
Some(func) => func.call_vt(vt, [value])?.display(), Some(func) => func.call_vt(vt, [value])?.display(),
None => value.display(), None => value.display(),
@ -185,8 +182,8 @@ impl Show for DisplayNode {
/// ///
/// Display: State /// Display: State
/// Category: special /// Category: special
#[node(Locatable, Show)] #[element(Locatable, Show)]
struct UpdateNode { struct UpdateElem {
/// The state. /// The state.
#[required] #[required]
state: State, state: State,
@ -196,7 +193,7 @@ struct UpdateNode {
update: StateUpdate, update: StateUpdate,
} }
impl Show for UpdateNode { impl Show for UpdateElem {
fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> { fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> {
Ok(Content::empty()) Ok(Content::empty())
} }

View File

@ -22,9 +22,9 @@ pub use typst::eval::{
pub use typst::geom::*; pub use typst::geom::*;
#[doc(no_inline)] #[doc(no_inline)]
pub use typst::model::{ pub use typst::model::{
node, Behave, Behaviour, Construct, Content, Finalize, Fold, Introspector, Label, element, Behave, Behaviour, Construct, Content, ElemFunc, Element, Finalize, Fold,
Locatable, MetaNode, Node, NodeId, Resolve, Selector, Set, Show, StabilityProvider, Introspector, Label, Locatable, Location, MetaElem, Resolve, Selector, Set, Show,
StableId, StyleChain, StyleMap, StyleVec, Synthesize, Unlabellable, Vt, StabilityProvider, StyleChain, StyleVec, Styles, Synthesize, Unlabellable, Vt,
}; };
#[doc(no_inline)] #[doc(no_inline)]
pub use typst::syntax::{Span, Spanned}; pub use typst::syntax::{Span, Spanned};
@ -36,4 +36,4 @@ pub use typst::World;
#[doc(no_inline)] #[doc(no_inline)]
pub use crate::layout::{Fragment, Layout, Regions}; pub use crate::layout::{Fragment, Layout, Regions};
#[doc(no_inline)] #[doc(no_inline)]
pub use crate::shared::{ContentExt, StyleMapExt}; pub use crate::shared::{ContentExt, StylesExt};

View File

@ -1,14 +1,15 @@
//! Node interaction. //! Element interaction.
use typst::model::{Behave, Behaviour, Content, StyleChain, StyleVec, StyleVecBuilder}; 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)] #[derive(Debug)]
pub struct BehavedBuilder<'a> { pub struct BehavedBuilder<'a> {
/// The internal builder. /// The internal builder.
builder: StyleVecBuilder<'a, Content>, builder: StyleVecBuilder<'a, Content>,
/// Staged weak and ignorant items that we can't yet commit to the builder. /// Staged weak and ignorant elements that we can't yet commit to the
/// The option is `Some(_)` for weak items and `None` for ignorant items. /// builder. The option is `Some(_)` for weak elements and `None` for
/// ignorant elements.
staged: Vec<(Content, Behaviour, StyleChain<'a>)>, staged: Vec<(Content, Behaviour, StyleChain<'a>)>,
/// What the last non-ignorant item was. /// What the last non-ignorant item was.
last: Behaviour, last: Behaviour,
@ -29,7 +30,7 @@ impl<'a> BehavedBuilder<'a> {
self.builder.is_empty() && self.staged.is_empty() 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. /// probably collapse.
pub fn is_basically_empty(&self) -> bool { pub fn is_basically_empty(&self) -> bool {
self.builder.is_empty() self.builder.is_empty()
@ -40,15 +41,15 @@ impl<'a> BehavedBuilder<'a> {
} }
/// Push an item into the sequence. /// Push an item into the sequence.
pub fn push(&mut self, item: Content, styles: StyleChain<'a>) { pub fn push(&mut self, elem: Content, styles: StyleChain<'a>) {
let interaction = item let interaction = elem
.with::<dyn Behave>() .with::<dyn Behave>()
.map_or(Behaviour::Supportive, Behave::behaviour); .map_or(Behaviour::Supportive, Behave::behaviour);
match interaction { match interaction {
Behaviour::Weak(level) => { Behaviour::Weak(level) => {
if matches!(self.last, Behaviour::Weak(_)) { 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 i = self.staged.iter().position(|prev| {
let Behaviour::Weak(prev_level) = prev.1 else { return false }; let Behaviour::Weak(prev_level) = prev.1 else { return false };
level < prev_level level < prev_level
@ -59,29 +60,29 @@ impl<'a> BehavedBuilder<'a> {
} }
if self.last != Behaviour::Destructive { if self.last != Behaviour::Destructive {
self.staged.push((item, interaction, styles)); self.staged.push((elem, interaction, styles));
self.last = interaction; self.last = interaction;
} }
} }
Behaviour::Supportive => { Behaviour::Supportive => {
self.flush(true); self.flush(true);
self.builder.push(item, styles); self.builder.push(elem, styles);
self.last = interaction; self.last = interaction;
} }
Behaviour::Destructive => { Behaviour::Destructive => {
self.flush(false); self.flush(false);
self.builder.push(item, styles); self.builder.push(elem, styles);
self.last = interaction; self.last = interaction;
} }
Behaviour::Ignorant => { Behaviour::Ignorant => {
self.staged.push((item, interaction, styles)); self.staged.push((elem, interaction, styles));
} }
} }
} }
/// Iterate over the contained items. /// Iterate over the contained elements.
pub fn items(&self) -> impl DoubleEndedIterator<Item = &Content> { pub fn elems(&self) -> impl DoubleEndedIterator<Item = &Content> {
self.builder.items().chain(self.staged.iter().map(|(item, ..)| item)) self.builder.elems().chain(self.staged.iter().map(|(item, ..)| item))
} }
/// Return the finish style vec and the common prefix chain. /// Return the finish style vec and the common prefix chain.
@ -90,7 +91,7 @@ impl<'a> BehavedBuilder<'a> {
self.builder.finish() 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. /// false.
fn flush(&mut self, supportive: bool) { fn flush(&mut self, supportive: bool) {
for (item, interaction, styles) in self.staged.drain(..) { for (item, interaction, styles) in self.staged.drain(..) {

View File

@ -1,8 +1,8 @@
//! Extension traits. //! Extension traits.
use crate::layout::{AlignNode, MoveNode, PadNode}; use crate::layout::{AlignElem, MoveElem, PadElem};
use crate::prelude::*; 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. /// Additional methods on content.
pub trait ContentExt { pub trait ContentExt {
@ -16,7 +16,7 @@ pub trait ContentExt {
fn underlined(self) -> Self; fn underlined(self) -> Self;
/// Link the content somewhere. /// Link the content somewhere.
fn linked(self, link: Link) -> Self; fn linked(self, dest: Destination) -> Self;
/// Set alignments for this content. /// Set alignments for this content.
fn aligned(self, aligns: Axes<Option<GenAlign>>) -> Self; fn aligned(self, aligns: Axes<Option<GenAlign>>) -> Self;
@ -30,27 +30,27 @@ pub trait ContentExt {
impl ContentExt for Content { impl ContentExt for Content {
fn strong(self) -> Self { fn strong(self) -> Self {
StrongNode::new(self).pack() StrongElem::new(self).pack()
} }
fn emph(self) -> Self { fn emph(self) -> Self {
EmphNode::new(self).pack() EmphElem::new(self).pack()
} }
fn underlined(self) -> Self { fn underlined(self) -> Self {
UnderlineNode::new(self).pack() UnderlineElem::new(self).pack()
} }
fn linked(self, link: Link) -> Self { fn linked(self, dest: Destination) -> Self {
self.styled(MetaNode::set_data(vec![Meta::Link(link)])) self.styled(MetaElem::set_data(vec![Meta::Link(dest)]))
} }
fn aligned(self, aligns: Axes<Option<GenAlign>>) -> Self { 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 { fn padded(self, padding: Sides<Rel<Length>>) -> Self {
PadNode::new(self) PadElem::new(self)
.with_left(padding.left) .with_left(padding.left)
.with_top(padding.top) .with_top(padding.top)
.with_right(padding.right) .with_right(padding.right)
@ -59,22 +59,22 @@ impl ContentExt for Content {
} }
fn moved(self, delta: Axes<Rel<Length>>) -> Self { 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. /// Additional methods for style lists.
pub trait StyleMapExt { pub trait StylesExt {
/// Set a font family composed of a preferred family and existing families /// Set a font family composed of a preferred family and existing families
/// from a style chain. /// from a style chain.
fn set_family(&mut self, preferred: FontFamily, existing: StyleChain); 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) { 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) std::iter::once(preferred)
.chain(TextNode::font_in(existing)) .chain(TextElem::font_in(existing))
.collect(), .collect(),
))); )));
} }

View File

@ -993,7 +993,7 @@ const EMOJI: &[(&'static str, Symbol)] = symbols! {
piano: '🎹', piano: '🎹',
pick: '', pick: '',
pie: '🥧', pie: '🥧',
pig: ['🐖', face: '🐷', node: '🐽'], pig: ['🐖', face: '🐷', nose: '🐽'],
pill: '💊', pill: '💊',
pin: ['📌', round: '📍'], pin: ['📌', round: '📍'],
pinata: '🪅', pinata: '🪅',

View File

@ -1,7 +1,7 @@
use kurbo::{BezPath, Line, ParamCurve}; use kurbo::{BezPath, Line, ParamCurve};
use ttf_parser::{GlyphId, OutlineBuilder}; use ttf_parser::{GlyphId, OutlineBuilder};
use super::TextNode; use super::TextElem;
use crate::prelude::*; use crate::prelude::*;
/// Underline text. /// Underline text.
@ -13,8 +13,8 @@ use crate::prelude::*;
/// ///
/// Display: Underline /// Display: Underline
/// Category: text /// Category: text
#[node(Show)] #[element(Show)]
pub struct UnderlineNode { pub struct UnderlineElem {
/// How to stroke the line. The text color and thickness are read from the /// How to stroke the line. The text color and thickness are read from the
/// font tables if `{auto}`. /// font tables if `{auto}`.
/// ///
@ -65,9 +65,9 @@ pub struct UnderlineNode {
pub body: Content, pub body: Content,
} }
impl Show for UnderlineNode { impl Show for UnderlineElem {
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> { 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, line: DecoLine::Underline,
stroke: self.stroke(styles).unwrap_or_default(), stroke: self.stroke(styles).unwrap_or_default(),
offset: self.offset(styles), offset: self.offset(styles),
@ -86,8 +86,8 @@ impl Show for UnderlineNode {
/// ///
/// Display: Overline /// Display: Overline
/// Category: text /// Category: text
#[node(Show)] #[element(Show)]
pub struct OverlineNode { pub struct OverlineElem {
/// How to stroke the line. The text color and thickness are read from the /// How to stroke the line. The text color and thickness are read from the
/// font tables if `{auto}`. /// font tables if `{auto}`.
/// ///
@ -144,9 +144,9 @@ pub struct OverlineNode {
pub body: Content, pub body: Content,
} }
impl Show for OverlineNode { impl Show for OverlineElem {
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> { 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, line: DecoLine::Overline,
stroke: self.stroke(styles).unwrap_or_default(), stroke: self.stroke(styles).unwrap_or_default(),
offset: self.offset(styles), offset: self.offset(styles),
@ -165,8 +165,8 @@ impl Show for OverlineNode {
/// ///
/// Display: Strikethrough /// Display: Strikethrough
/// Category: text /// Category: text
#[node(Show)] #[element(Show)]
pub struct StrikeNode { pub struct StrikeElem {
/// How to stroke the line. The text color and thickness are read from the /// How to stroke the line. The text color and thickness are read from the
/// font tables if `{auto}`. /// font tables if `{auto}`.
/// ///
@ -208,9 +208,9 @@ pub struct StrikeNode {
pub body: Content, pub body: Content,
} }
impl Show for StrikeNode { impl Show for StrikeElem {
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> { 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, line: DecoLine::Strikethrough,
stroke: self.stroke(styles).unwrap_or_default(), stroke: self.stroke(styles).unwrap_or_default(),
offset: self.offset(styles), offset: self.offset(styles),
@ -255,7 +255,7 @@ pub enum DecoLine {
pub(super) fn decorate( pub(super) fn decorate(
frame: &mut Frame, frame: &mut Frame,
deco: &Decoration, deco: &Decoration,
text: &Text, text: &TextItem,
shift: Abs, shift: Abs,
pos: Point, pos: Point,
width: Abs, width: Abs,
@ -285,7 +285,7 @@ pub(super) fn decorate(
if target.x >= min_width || !deco.evade { if target.x >= min_width || !deco.evade {
let shape = Geometry::Line(target).stroked(stroke); let shape = Geometry::Line(target).stroked(stroke);
frame.push(origin, Element::Shape(shape, Span::detached())); frame.push(origin, FrameItem::Shape(shape, Span::detached()));
} }
}; };

View File

@ -1,20 +1,20 @@
use super::TextNode; use super::TextElem;
use crate::prelude::*; use crate::prelude::*;
/// A text space. /// A text space.
/// ///
/// Display: Space /// Display: Space
/// Category: text /// Category: text
#[node(Unlabellable, Behave)] #[element(Unlabellable, Behave)]
pub struct SpaceNode {} pub struct SpaceElem {}
impl Behave for SpaceNode { impl Behave for SpaceElem {
fn behaviour(&self) -> Behaviour { fn behaviour(&self) -> Behaviour {
Behaviour::Weak(2) Behaviour::Weak(2)
} }
} }
impl Unlabellable for SpaceNode {} impl Unlabellable for SpaceElem {}
/// Inserts a line break. /// Inserts a line break.
/// ///
@ -36,8 +36,8 @@ impl Unlabellable for SpaceNode {}
/// ///
/// Display: Line Break /// Display: Line Break
/// Category: text /// Category: text
#[node(Behave)] #[element(Behave)]
pub struct LinebreakNode { pub struct LinebreakElem {
/// Whether to justify the line before the break. /// Whether to justify the line before the break.
/// ///
/// This is useful if you found a better line break opportunity in your /// This is useful if you found a better line break opportunity in your
@ -55,7 +55,7 @@ pub struct LinebreakNode {
pub justify: bool, pub justify: bool,
} }
impl Behave for LinebreakNode { impl Behave for LinebreakElem {
fn behaviour(&self) -> Behaviour { fn behaviour(&self) -> Behaviour {
Behaviour::Destructive Behaviour::Destructive
} }
@ -82,8 +82,8 @@ impl Behave for LinebreakNode {
/// ///
/// Display: Strong Emphasis /// Display: Strong Emphasis
/// Category: text /// Category: text
#[node(Show)] #[element(Show)]
pub struct StrongNode { pub struct StrongElem {
/// The delta to apply on the font weight. /// The delta to apply on the font weight.
/// ///
/// ```example /// ```example
@ -98,9 +98,9 @@ pub struct StrongNode {
pub body: Content, pub body: Content,
} }
impl Show for StrongNode { impl Show for StrongElem {
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> { 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 /// Display: Emphasis
/// Category: text /// Category: text
#[node(Show)] #[element(Show)]
pub struct EmphNode { pub struct EmphElem {
/// The content to emphasize. /// The content to emphasize.
#[required] #[required]
pub body: Content, pub body: Content,
} }
impl Show for EmphNode { impl Show for EmphElem {
fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> { 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 { fn case(text: ToCase, case: Case) -> Value {
match text { match text {
ToCase::Str(v) => Value::Str(case.apply(&v).into()), 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. /// The text to display to small capitals.
body: Content, body: Content,
) -> Value { ) -> Value {
Value::Content(body.styled(TextNode::set_smallcaps(true))) Value::Content(body.styled(TextElem::set_smallcaps(true)))
} }
/// Create blind text. /// Create blind text.

View File

@ -19,7 +19,7 @@ use std::borrow::Cow;
use rustybuzz::Tag; use rustybuzz::Tag;
use typst::font::{FontMetrics, FontStretch, FontStyle, FontWeight, VerticalFontMetric}; use typst::font::{FontMetrics, FontStretch, FontStyle, FontWeight, VerticalFontMetric};
use crate::layout::ParNode; use crate::layout::ParElem;
use crate::prelude::*; use crate::prelude::*;
/// Customize the look and layout of text in a variety of ways. /// Customize the look and layout of text in a variety of ways.
@ -40,8 +40,8 @@ use crate::prelude::*;
/// ///
/// Display: Text /// Display: Text
/// Category: text /// Category: text
#[node(Construct)] #[element(Construct)]
pub struct TextNode { pub struct TextElem {
/// A prioritized sequence of font families. /// A prioritized sequence of font families.
/// ///
/// When processing text, Typst tries all specified font families in order /// When processing text, Typst tries all specified font families in order
@ -291,7 +291,7 @@ pub struct TextNode {
/// هذا عربي. /// هذا عربي.
/// ``` /// ```
#[resolve] #[resolve]
pub dir: HorizontalDir, pub dir: TextDir,
/// Whether to hyphenate text to improve line breaking. When `{auto}`, text /// Whether to hyphenate text to improve line breaking. When `{auto}`, text
/// will be hyphenated if and only if justification is enabled. /// will be hyphenated if and only if justification is enabled.
@ -479,16 +479,16 @@ pub struct TextNode {
pub smallcaps: bool, pub smallcaps: bool,
} }
impl TextNode { impl TextElem {
/// Create a new packed text node. /// Create a new packed text element.
pub fn packed(text: impl Into<EcoString>) -> Content { pub fn packed(text: impl Into<EcoString>) -> Content {
Self::new(text.into()).pack() Self::new(text.into()).pack()
} }
} }
impl Construct for TextNode { impl Construct for TextElem {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
// The text constructor is special: It doesn't create a text node. // The text constructor is special: It doesn't create a text element.
// Instead, it leaves the passed argument structurally unchanged, but // Instead, it leaves the passed argument structurally unchanged, but
// styles all text in it. // styles all text in it.
let styles = Self::set(args)?; let styles = Self::set(args)?;
@ -606,28 +606,28 @@ cast_to_value! {
/// The direction of text and inline objects in their line. /// The direction of text and inline objects in their line.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct HorizontalDir(pub Smart<Dir>); pub struct TextDir(pub Smart<Dir>);
cast_from_value! { cast_from_value! {
HorizontalDir, TextDir,
v: Smart<Dir> => { v: Smart<Dir> => {
if v.map_or(false, |dir| dir.axis() == Axis::Y) { if v.map_or(false, |dir| dir.axis() == Axis::Y) {
Err("must be horizontal")?; Err("text direction must be horizontal")?;
} }
Self(v) Self(v)
}, },
} }
cast_to_value! { cast_to_value! {
v: HorizontalDir => v.0.into() v: TextDir => v.0.into()
} }
impl Resolve for HorizontalDir { impl Resolve for TextDir {
type Output = Dir; type Output = Dir;
fn resolve(self, styles: StyleChain) -> Self::Output { fn resolve(self, styles: StyleChain) -> Self::Output {
match self.0 { match self.0 {
Smart::Auto => TextNode::lang_in(styles).dir(), Smart::Auto => TextElem::lang_in(styles).dir(),
Smart::Custom(dir) => dir, Smart::Custom(dir) => dir,
} }
} }
@ -651,7 +651,7 @@ impl Resolve for Hyphenate {
fn resolve(self, styles: StyleChain) -> Self::Output { fn resolve(self, styles: StyleChain) -> Self::Output {
match self.0 { match self.0 {
Smart::Auto => ParNode::justify_in(styles), Smart::Auto => ParElem::justify_in(styles),
Smart::Custom(v) => v, Smart::Custom(v) => v,
} }
} }
@ -677,7 +677,7 @@ cast_from_value! {
StylisticSet, StylisticSet,
v: i64 => match v { v: i64 => match v {
1 ..= 20 => Self::new(v as u8), 1 ..= 20 => Self::new(v as u8),
_ => Err("must be between 1 and 20")?, _ => Err("stylistic set must be between 1 and 20")?,
}, },
} }

View File

@ -24,8 +24,8 @@ use crate::prelude::*;
/// ///
/// Display: Smart Quote /// Display: Smart Quote
/// Category: text /// Category: text
#[node] #[element]
pub struct SmartQuoteNode { pub struct SmartQuoteElem {
/// Whether this should be a double quote. /// Whether this should be a double quote.
#[default(true)] #[default(true)]
pub double: bool, pub double: bool,

View File

@ -3,9 +3,9 @@ use syntect::highlighting as synt;
use typst::syntax::{self, LinkedNode}; use typst::syntax::{self, LinkedNode};
use super::{ 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::*; use crate::prelude::*;
/// Raw text with optional syntax highlighting. /// Raw text with optional syntax highlighting.
@ -35,8 +35,8 @@ use crate::prelude::*;
/// ///
/// Display: Raw Text / Code /// Display: Raw Text / Code
/// Category: text /// Category: text
#[node(Synthesize, Show, Finalize)] #[element(Synthesize, Show, Finalize)]
pub struct RawNode { pub struct RawElem {
/// The raw text. /// The raw text.
/// ///
/// You can also use raw blocks creatively to create custom syntaxes for /// You can also use raw blocks creatively to create custom syntaxes for
@ -103,7 +103,7 @@ pub struct RawNode {
pub lang: Option<EcoString>, pub lang: Option<EcoString>,
} }
impl RawNode { impl RawElem {
/// The supported language names and tags. /// The supported language names and tags.
pub fn languages() -> Vec<(&'static str, Vec<&'static str>)> { pub fn languages() -> Vec<(&'static str, Vec<&'static str>)> {
SYNTAXES SYNTAXES
@ -120,13 +120,13 @@ impl RawNode {
} }
} }
impl Synthesize for RawNode { impl Synthesize for RawElem {
fn synthesize(&mut self, _: &Vt, styles: StyleChain) { fn synthesize(&mut self, _: &Vt, styles: StyleChain) {
self.push_lang(self.lang(styles)); self.push_lang(self.lang(styles));
} }
} }
impl Show for RawNode { impl Show for RawElem {
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> { fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
let text = self.text(); let text = self.text();
let lang = self.lang(styles).as_ref().map(|s| s.to_lowercase()); 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); let mut highlighter = syntect::easy::HighlightLines::new(syntax, &THEME);
for (i, line) in text.lines().enumerate() { for (i, line) in text.lines().enumerate() {
if i != 0 { if i != 0 {
seq.push(LinebreakNode::new().pack()); seq.push(LinebreakElem::new().pack());
} }
for (style, piece) in for (style, piece) in
@ -174,26 +174,27 @@ impl Show for RawNode {
Content::sequence(seq) Content::sequence(seq)
} else { } else {
TextNode::packed(text) TextElem::packed(text)
}; };
if self.block(styles) { if self.block(styles) {
realized = BlockNode::new().with_body(Some(realized)).pack(); realized = BlockElem::new().with_body(Some(realized)).pack();
} }
Ok(realized) Ok(realized)
} }
} }
impl Finalize for RawNode { impl Finalize for RawElem {
fn finalize(&self, realized: Content, _: StyleChain) -> Content { fn finalize(&self, realized: Content, _: StyleChain) -> Content {
let mut map = StyleMap::new(); let mut styles = Styles::new();
map.set(TextNode::set_overhang(false)); styles.set(TextElem::set_overhang(false));
map.set(TextNode::set_hyphenate(Hyphenate(Smart::Custom(false)))); styles.set(TextElem::set_hyphenate(Hyphenate(Smart::Custom(false))));
map.set(TextNode::set_size(TextSize(Em::new(0.8).into()))); styles.set(TextElem::set_size(TextSize(Em::new(0.8).into())));
map.set(TextNode::set_font(FontList(vec![FontFamily::new("DejaVu Sans Mono")]))); styles
map.set(SmartQuoteNode::set_enabled(false)); .set(TextElem::set_font(FontList(vec![FontFamily::new("DejaVu Sans Mono")])));
realized.styled_with_map(map) 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. /// Style a piece of text with a syntect style.
fn styled(piece: &str, foreground: Paint, style: synt::Style) -> Content { 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(); let paint = to_typst(style.foreground).into();
if paint != foreground { 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) { if style.font_style.contains(synt::FontStyle::BOLD) {

View File

@ -95,10 +95,10 @@ impl<'a> ShapedText<'a> {
let mut frame = Frame::new(size); let mut frame = Frame::new(size);
frame.set_baseline(top); frame.set_baseline(top);
let shift = TextNode::baseline_in(self.styles); let shift = TextElem::baseline_in(self.styles);
let lang = TextNode::lang_in(self.styles); let lang = TextElem::lang_in(self.styles);
let decos = TextNode::deco_in(self.styles); let decos = TextElem::deco_in(self.styles);
let fill = TextNode::fill_in(self.styles); let fill = TextElem::fill_in(self.styles);
for ((font, y_offset), group) in for ((font, y_offset), group) in
self.glyphs.as_ref().group_by_key(|g| (g.font.clone(), g.y_offset)) self.glyphs.as_ref().group_by_key(|g| (g.font.clone(), g.y_offset))
@ -122,16 +122,16 @@ impl<'a> ShapedText<'a> {
}) })
.collect(); .collect();
let text = Text { font, size: self.size, lang, fill, glyphs }; let item = TextItem { font, size: self.size, lang, fill, glyphs };
let text_layer = frame.layer(); let layer = frame.layer();
let width = text.width(); let width = item.width();
// Apply line decorations. // Apply line decorations.
for deco in &decos { 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; offset += width;
} }
@ -146,8 +146,8 @@ impl<'a> ShapedText<'a> {
let mut top = Abs::zero(); let mut top = Abs::zero();
let mut bottom = Abs::zero(); let mut bottom = Abs::zero();
let top_edge = TextNode::top_edge_in(self.styles); let top_edge = TextElem::top_edge_in(self.styles);
let bottom_edge = TextNode::bottom_edge_in(self.styles); let bottom_edge = TextElem::bottom_edge_in(self.styles);
// Expand top and bottom by reading the font's vertical metrics. // Expand top and bottom by reading the font's vertical metrics.
let mut expand = |font: &Font| { let mut expand = |font: &Font| {
@ -343,7 +343,7 @@ pub fn shape<'a>(
styles: StyleChain<'a>, styles: StyleChain<'a>,
dir: Dir, dir: Dir,
) -> ShapedText<'a> { ) -> ShapedText<'a> {
let size = TextNode::size_in(styles); let size = TextElem::size_in(styles);
let mut ctx = ShapingContext { let mut ctx = ShapingContext {
vt, vt,
base, base,
@ -354,7 +354,7 @@ pub fn shape<'a>(
styles, styles,
variant: variant(styles), variant: variant(styles),
tags: tags(styles), tags: tags(styles),
fallback: TextNode::fallback_in(styles), fallback: TextElem::fallback_in(styles),
dir, 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. /// Apply tracking and spacing to the shaped glyphs.
fn track_and_space(ctx: &mut ShapingContext) { 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 = 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(); let mut glyphs = ctx.glyphs.iter_mut().peekable();
while let Some(glyph) = glyphs.next() { while let Some(glyph) = glyphs.next() {
@ -562,17 +562,17 @@ fn nbsp_delta(font: &Font) -> Option<Em> {
/// Resolve the font variant. /// Resolve the font variant.
pub fn variant(styles: StyleChain) -> FontVariant { pub fn variant(styles: StyleChain) -> FontVariant {
let mut variant = FontVariant::new( let mut variant = FontVariant::new(
TextNode::style_in(styles), TextElem::style_in(styles),
TextNode::weight_in(styles), TextElem::weight_in(styles),
TextNode::stretch_in(styles), TextElem::stretch_in(styles),
); );
let delta = TextNode::delta_in(styles); let delta = TextElem::delta_in(styles);
variant.weight = variant variant.weight = variant
.weight .weight
.thicken(delta.clamp(i16::MIN as i64, i16::MAX as i64) as i16); .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 { variant.style = match variant.style {
FontStyle::Normal => FontStyle::Italic, FontStyle::Normal => FontStyle::Italic,
FontStyle::Italic => FontStyle::Normal, FontStyle::Italic => FontStyle::Normal,
@ -593,8 +593,8 @@ pub fn families(styles: StyleChain) -> impl Iterator<Item = FontFamily> + Clone
"segoe ui emoji", "segoe ui emoji",
]; ];
let tail = if TextNode::fallback_in(styles) { FALLBACKS } else { &[] }; let tail = if TextElem::fallback_in(styles) { FALLBACKS } else { &[] };
TextNode::font_in(styles) TextElem::font_in(styles)
.into_iter() .into_iter()
.chain(tail.iter().copied().map(FontFamily::new)) .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. // 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); feat(b"kern", 0);
} }
// Features that are off by default in Harfbuzz are only added if enabled. // 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); feat(b"smcp", 1);
} }
if TextNode::alternates_in(styles) { if TextElem::alternates_in(styles) {
feat(b"salt", 1); feat(b"salt", 1);
} }
let storage; 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]; storage = [b's', b's', b'0' + set.get() / 10, b'0' + set.get() % 10];
feat(&storage, 1); feat(&storage, 1);
} }
if !TextNode::ligatures_in(styles) { if !TextElem::ligatures_in(styles) {
feat(b"liga", 0); feat(b"liga", 0);
feat(b"clig", 0); feat(b"clig", 0);
} }
if TextNode::discretionary_ligatures_in(styles) { if TextElem::discretionary_ligatures_in(styles) {
feat(b"dlig", 1); feat(b"dlig", 1);
} }
if TextNode::historical_ligatures_in(styles) { if TextElem::historical_ligatures_in(styles) {
feat(b"hilg", 1); feat(b"hilg", 1);
} }
match TextNode::number_type_in(styles) { match TextElem::number_type_in(styles) {
Smart::Auto => {} Smart::Auto => {}
Smart::Custom(NumberType::Lining) => feat(b"lnum", 1), Smart::Custom(NumberType::Lining) => feat(b"lnum", 1),
Smart::Custom(NumberType::OldStyle) => feat(b"onum", 1), Smart::Custom(NumberType::OldStyle) => feat(b"onum", 1),
} }
match TextNode::number_width_in(styles) { match TextElem::number_width_in(styles) {
Smart::Auto => {} Smart::Auto => {}
Smart::Custom(NumberWidth::Proportional) => feat(b"pnum", 1), Smart::Custom(NumberWidth::Proportional) => feat(b"pnum", 1),
Smart::Custom(NumberWidth::Tabular) => feat(b"tnum", 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); feat(b"zero", 1);
} }
if TextNode::fractions_in(styles) { if TextElem::fractions_in(styles) {
feat(b"frac", 1); 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, ..)) 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 /// Process the language and and region of a style chain into a
/// rustybuzz-compatible BCP 47 language. /// rustybuzz-compatible BCP 47 language.
fn language(styles: StyleChain) -> rustybuzz::Language { fn language(styles: StyleChain) -> rustybuzz::Language {
let mut bcp: EcoString = TextNode::lang_in(styles).as_str().into(); let mut bcp: EcoString = TextElem::lang_in(styles).as_str().into();
if let Some(region) = TextNode::region_in(styles) { if let Some(region) = TextElem::region_in(styles) {
bcp.push('-'); bcp.push('-');
bcp.push_str(region.as_str()); bcp.push_str(region.as_str());
} }

View File

@ -1,6 +1,4 @@
use typst::model::SequenceNode; use super::{variant, SpaceElem, TextElem, TextSize};
use super::{variant, SpaceNode, TextNode, TextSize};
use crate::prelude::*; use crate::prelude::*;
/// Set text in subscript. /// Set text in subscript.
@ -14,8 +12,8 @@ use crate::prelude::*;
/// ///
/// Display: Subscript /// Display: Subscript
/// Category: text /// Category: text
#[node(Show)] #[element(Show)]
pub struct SubNode { pub struct SubElem {
/// Whether to prefer the dedicated subscript characters of the font. /// Whether to prefer the dedicated subscript characters of the font.
/// ///
/// If this is enabled, Typst first tries to transform the text to subscript /// If this is enabled, Typst first tries to transform the text to subscript
@ -46,21 +44,21 @@ pub struct SubNode {
pub body: Content, pub body: Content,
} }
impl Show for SubNode { impl Show for SubElem {
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> { fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
let body = self.body(); let body = self.body();
let mut transformed = None; let mut transformed = None;
if self.typographic(styles) { if self.typographic(styles) {
if let Some(text) = search_text(&body, true) { if let Some(text) = search_text(&body, true) {
if is_shapable(vt, &text, styles) { if is_shapable(vt, &text, styles) {
transformed = Some(TextNode::packed(text)); transformed = Some(TextElem::packed(text));
} }
} }
}; };
Ok(transformed.unwrap_or_else(|| { Ok(transformed.unwrap_or_else(|| {
body.styled(TextNode::set_baseline(self.baseline(styles))) body.styled(TextElem::set_baseline(self.baseline(styles)))
.styled(TextNode::set_size(self.size(styles))) .styled(TextElem::set_size(self.size(styles)))
})) }))
} }
} }
@ -76,8 +74,8 @@ impl Show for SubNode {
/// ///
/// Display: Superscript /// Display: Superscript
/// Category: text /// Category: text
#[node(Show)] #[element(Show)]
pub struct SuperNode { pub struct SuperElem {
/// Whether to prefer the dedicated superscript characters of the font. /// Whether to prefer the dedicated superscript characters of the font.
/// ///
/// If this is enabled, Typst first tries to transform the text to /// If this is enabled, Typst first tries to transform the text to
@ -108,35 +106,35 @@ pub struct SuperNode {
pub body: Content, pub body: Content,
} }
impl Show for SuperNode { impl Show for SuperElem {
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> { fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
let body = self.body(); let body = self.body();
let mut transformed = None; let mut transformed = None;
if self.typographic(styles) { if self.typographic(styles) {
if let Some(text) = search_text(&body, false) { if let Some(text) = search_text(&body, false) {
if is_shapable(vt, &text, styles) { if is_shapable(vt, &text, styles) {
transformed = Some(TextNode::packed(text)); transformed = Some(TextElem::packed(text));
} }
} }
}; };
Ok(transformed.unwrap_or_else(|| { Ok(transformed.unwrap_or_else(|| {
body.styled(TextNode::set_baseline(self.baseline(styles))) body.styled(TextElem::set_baseline(self.baseline(styles)))
.styled(TextNode::set_size(self.size(styles))) .styled(TextElem::set_size(self.size(styles)))
})) }))
} }
} }
/// Find and transform the text contained in `content` to the given script kind /// 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> { fn search_text(content: &Content, sub: bool) -> Option<EcoString> {
if content.is::<SpaceNode>() { if content.is::<SpaceElem>() {
Some(' '.into()) Some(' '.into())
} else if let Some(node) = content.to::<TextNode>() { } else if let Some(elem) = content.to::<TextElem>() {
convert_script(&node.text(), sub) convert_script(&elem.text(), sub)
} else if let Some(seq) = content.to::<SequenceNode>() { } else if let Some(children) = content.to_sequence() {
let mut full = EcoString::new(); let mut full = EcoString::new();
for item in seq.children() { for item in children {
match search_text(&item, sub) { match search_text(&item, sub) {
Some(text) => full.push_str(&text), Some(text) => full.push_str(&text),
None => return None, None => return None,
@ -152,7 +150,7 @@ fn search_text(content: &Content, sub: bool) -> Option<EcoString> {
/// given string. /// given string.
fn is_shapable(vt: &Vt, text: &str, styles: StyleChain) -> bool { fn is_shapable(vt: &Vt, text: &str, styles: StyleChain) -> bool {
let world = vt.world; let world = vt.world;
for family in TextNode::font_in(styles) { for family in TextElem::font_in(styles) {
if let Some(font) = world if let Some(font) = world
.book() .book()
.select(family.as_str(), variant(styles)) .select(family.as_str(), variant(styles))

View File

@ -22,8 +22,8 @@ use crate::prelude::*;
/// ///
/// Display: Image /// Display: Image
/// Category: visualize /// Category: visualize
#[node(Layout)] #[element(Layout)]
pub struct ImageNode { pub struct ImageElem {
/// Path to an image file. /// Path to an image file.
#[required] #[required]
#[parse( #[parse(
@ -46,7 +46,7 @@ pub struct ImageNode {
pub fit: ImageFit, pub fit: ImageFit,
} }
impl Layout for ImageNode { impl Layout for ImageElem {
fn layout( fn layout(
&self, &self,
vt: &mut Vt, vt: &mut Vt,
@ -97,7 +97,7 @@ impl Layout for ImageNode {
// the frame to the target size, center aligning the image in the // the frame to the target size, center aligning the image in the
// process. // process.
let mut frame = Frame::new(fitted); 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); frame.resize(target, Align::CENTER_HORIZON);
// Create a clipping group if only part of the image should be visible. // Create a clipping group if only part of the image should be visible.

View File

@ -11,8 +11,8 @@ use crate::prelude::*;
/// ///
/// Display: Line /// Display: Line
/// Category: visualize /// Category: visualize
#[node(Layout)] #[element(Layout)]
pub struct LineNode { pub struct LineElem {
/// The start point of the line. /// The start point of the line.
/// ///
/// Must be an array of exactly two relative lengths. /// Must be an array of exactly two relative lengths.
@ -49,7 +49,7 @@ pub struct LineNode {
pub stroke: PartialStroke, pub stroke: PartialStroke,
} }
impl Layout for LineNode { impl Layout for LineElem {
fn layout( fn layout(
&self, &self,
_: &mut Vt, _: &mut Vt,
@ -76,7 +76,7 @@ impl Layout for LineNode {
let mut frame = Frame::new(target); let mut frame = Frame::new(target);
let shape = Geometry::Line(delta.to_point()).stroked(stroke); 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)) Ok(Fragment::frame(frame))
} }
} }

View File

@ -18,8 +18,8 @@ use crate::prelude::*;
/// ///
/// Display: Rectangle /// Display: Rectangle
/// Category: visualize /// Category: visualize
#[node(Layout)] #[element(Layout)]
pub struct RectNode { pub struct RectElem {
/// The rectangle's width, relative to its parent container. /// The rectangle's width, relative to its parent container.
pub width: Smart<Rel<Length>>, pub width: Smart<Rel<Length>>,
@ -139,7 +139,7 @@ pub struct RectNode {
pub body: Option<Content>, pub body: Option<Content>,
} }
impl Layout for RectNode { impl Layout for RectElem {
fn layout( fn layout(
&self, &self,
vt: &mut Vt, vt: &mut Vt,
@ -179,8 +179,8 @@ impl Layout for RectNode {
/// ///
/// Display: Square /// Display: Square
/// Category: visualize /// Category: visualize
#[node(Layout)] #[element(Layout)]
pub struct SquareNode { pub struct SquareElem {
/// The square's side length. This is mutually exclusive with `width` and /// The square's side length. This is mutually exclusive with `width` and
/// `height`. /// `height`.
#[external] #[external]
@ -249,7 +249,7 @@ pub struct SquareNode {
pub body: Option<Content>, pub body: Option<Content>,
} }
impl Layout for SquareNode { impl Layout for SquareElem {
fn layout( fn layout(
&self, &self,
vt: &mut Vt, vt: &mut Vt,
@ -290,8 +290,8 @@ impl Layout for SquareNode {
/// ///
/// Display: Ellipse /// Display: Ellipse
/// Category: visualize /// Category: visualize
#[node(Layout)] #[element(Layout)]
pub struct EllipseNode { pub struct EllipseElem {
/// The ellipse's width, relative to its parent container. /// The ellipse's width, relative to its parent container.
pub width: Smart<Rel<Length>>, pub width: Smart<Rel<Length>>,
@ -331,7 +331,7 @@ pub struct EllipseNode {
pub body: Option<Content>, pub body: Option<Content>,
} }
impl Layout for EllipseNode { impl Layout for EllipseElem {
fn layout( fn layout(
&self, &self,
vt: &mut Vt, vt: &mut Vt,
@ -372,8 +372,8 @@ impl Layout for EllipseNode {
/// ///
/// Display: Circle /// Display: Circle
/// Category: visualize /// Category: visualize
#[node(Layout)] #[element(Layout)]
pub struct CircleNode { pub struct CircleElem {
/// The circle's radius. This is mutually exclusive with `width` and /// The circle's radius. This is mutually exclusive with `width` and
/// `height`. /// `height`.
#[external] #[external]
@ -438,7 +438,7 @@ pub struct CircleNode {
pub body: Option<Content>, pub body: Option<Content>,
} }
impl Layout for CircleNode { impl Layout for CircleElem {
fn layout( fn layout(
&self, &self,
vt: &mut Vt, vt: &mut Vt,
@ -529,7 +529,7 @@ fn layout(
let size = frame.size() + outset.sum_by_axis(); let size = frame.size() + outset.sum_by_axis();
let pos = Point::new(-outset.left, -outset.top); let pos = Point::new(-outset.left, -outset.top);
let shape = ellipse(size, fill, stroke.left); let shape = ellipse(size, fill, stroke.left);
frame.prepend(pos, Element::Shape(shape, span)); frame.prepend(pos, FrameItem::Shape(shape, span));
} else { } else {
frame.fill_and_stroke(fill, stroke, outset, radius, span); frame.fill_and_stroke(fill, stroke, outset, radius, span);
} }

View File

@ -1,12 +1,12 @@
use super::*; use super::*;
/// Expand the `#[node]` macro. /// Expand the `#[element]` macro.
pub fn node(stream: TokenStream, body: syn::ItemStruct) -> Result<TokenStream> { pub fn element(stream: TokenStream, body: syn::ItemStruct) -> Result<TokenStream> {
let node = prepare(stream, &body)?; let element = prepare(stream, &body)?;
Ok(create(&node)) Ok(create(&element))
} }
struct Node { struct Elem {
name: String, name: String,
display: String, display: String,
category: String, category: String,
@ -65,8 +65,8 @@ impl Parse for FieldParser {
} }
} }
/// Preprocess the node's definition. /// Preprocess the element's definition.
fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> { fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Elem> {
let syn::Fields::Named(named) = &body.fields else { let syn::Fields::Named(named) = &body.fields else {
bail!(body, "expected named fields"); 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 display = meta_line(&mut lines, "Display")?.into();
let docs = lines.join("\n").trim().into(); let docs = lines.join("\n").trim().into();
let node = Node { let element = Elem {
name: body.ident.to_string().trim_end_matches("Node").to_lowercase(), name: body.ident.to_string().trim_end_matches("Elem").to_lowercase(),
display, display,
category, category,
docs, docs,
@ -155,17 +155,17 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> {
}; };
validate_attrs(&body.attrs)?; validate_attrs(&body.attrs)?;
Ok(node) Ok(element)
} }
/// Produce the node's definition. /// Produce the element's definition.
fn create(node: &Node) -> TokenStream { fn create(element: &Elem) -> TokenStream {
let Node { vis, ident, docs, .. } = node; let Elem { vis, ident, docs, .. } = element;
let all = node.fields.iter().filter(|field| !field.external); let all = element.fields.iter().filter(|field| !field.external);
let settable = all.clone().filter(|field| !field.synthesized && field.settable()); let settable = all.clone().filter(|field| !field.synthesized && field.settable());
// Inherent methods and functions. // 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_methods = all.clone().map(create_field_method);
let field_in_methods = settable.clone().map(create_field_in_method); let field_in_methods = settable.clone().map(create_field_in_method);
let with_field_methods = all.clone().map(create_with_field_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); let field_style_methods = settable.map(create_set_field_method);
// Trait implementations. // Trait implementations.
let node_impl = create_node_impl(node); let element_impl = create_pack_impl(element);
let construct_impl = node let construct_impl = element
.capable .capable
.iter() .iter()
.all(|capability| capability != "Construct") .all(|capability| capability != "Construct")
.then(|| create_construct_impl(node)); .then(|| create_construct_impl(element));
let set_impl = create_set_impl(node); let set_impl = create_set_impl(element);
let locatable_impl = node let locatable_impl = element
.capable .capable
.iter() .iter()
.any(|capability| capability == "Locatable") .any(|capability| capability == "Locatable")
@ -200,13 +200,13 @@ fn create(node: &Node) -> TokenStream {
#(#push_field_methods)* #(#push_field_methods)*
#(#field_style_methods)* #(#field_style_methods)*
/// The node's span. /// The element's span.
pub fn span(&self) -> ::typst::syntax::Span { pub fn span(&self) -> ::typst::syntax::Span {
self.0.span() self.0.span()
} }
} }
#node_impl #element_impl
#construct_impl #construct_impl
#set_impl #set_impl
#locatable_impl #locatable_impl
@ -219,9 +219,9 @@ fn create(node: &Node) -> TokenStream {
} }
} }
/// Create the `new` function for the node. /// Create the `new` function for the element.
fn create_new_func(node: &Node) -> TokenStream { fn create_new_func(element: &Elem) -> TokenStream {
let relevant = node let relevant = element
.fields .fields
.iter() .iter()
.filter(|field| !field.external && !field.synthesized && field.inherent()); .filter(|field| !field.external && !field.synthesized && field.inherent());
@ -232,9 +232,11 @@ fn create_new_func(node: &Node) -> TokenStream {
quote! { .#with_ident(#ident) } quote! { .#with_ident(#ident) }
}); });
quote! { quote! {
/// Create a new node. /// Create a new element.
pub fn new(#(#params),*) -> Self { 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)* #(#builder_calls)*
} }
} }
@ -252,8 +254,7 @@ fn create_field_method(field: &Field) -> TokenStream {
} }
} }
} else { } else {
let access = let access = create_style_chain_access(field, quote! { self.0.field(#name) });
create_style_chain_access(field, quote! { self.0.field(#name).cloned() });
quote! { quote! {
#[doc = #docs] #[doc = #docs]
#vis fn #ident(&self, styles: ::typst::model::StyleChain) -> #output { #vis fn #ident(&self, styles: ::typst::model::StyleChain) -> #output {
@ -288,7 +289,7 @@ fn create_style_chain_access(field: &Field, inherent: TokenStream) -> TokenStrea
quote! { quote! {
styles.#getter::<#ty>( styles.#getter::<#ty>(
::typst::model::NodeId::of::<Self>(), <Self as ::typst::model::Element>::func(),
#name, #name,
#inherent, #inherent,
|| #default, || #default,
@ -328,7 +329,7 @@ fn create_set_field_method(field: &Field) -> TokenStream {
#[doc = #doc] #[doc = #doc]
#vis fn #set_ident(#ident: #ty) -> ::typst::model::Style { #vis fn #set_ident(#ident: #ty) -> ::typst::model::Style {
::typst::model::Style::Property(::typst::model::Property::new( ::typst::model::Style::Property(::typst::model::Property::new(
::typst::model::NodeId::of::<Self>(), <Self as ::typst::model::Element>::func(),
#name.into(), #name.into(),
#ident.into() #ident.into()
)) ))
@ -336,19 +337,30 @@ fn create_set_field_method(field: &Field) -> TokenStream {
} }
} }
/// Create the node's `Node` implementation. /// Create the element's `Pack` implementation.
fn create_node_impl(node: &Node) -> TokenStream { fn create_pack_impl(element: &Elem) -> TokenStream {
let Node { ident, name, display, category, docs, .. } = node; let Elem { ident, name, display, category, docs, .. } = element;
let vtable_func = create_vtable_func(node); let vtable_func = create_vtable_func(element);
let infos = node let infos = element
.fields .fields
.iter() .iter()
.filter(|field| !field.internal && !field.synthesized) .filter(|field| !field.internal && !field.synthesized)
.map(create_param_info); .map(create_param_info);
quote! { quote! {
impl ::typst::model::Node for #ident { impl ::typst::model::Element for #ident {
fn id() -> ::typst::model::NodeId { fn pack(self) -> ::typst::model::Content {
static META: ::typst::model::NodeMeta = ::typst::model::NodeMeta { 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, name: #name,
vtable: #vtable_func, vtable: #vtable_func,
construct: <#ident as ::typst::model::Construct>::construct, construct: <#ident as ::typst::model::Construct>::construct,
@ -362,20 +374,16 @@ fn create_node_impl(node: &Node) -> TokenStream {
category: #category, category: #category,
}), }),
}; };
::typst::model::NodeId(&META) (&NATIVE).into()
}
fn pack(self) -> ::typst::model::Content {
self.0
} }
} }
} }
} }
/// Create the node's casting vtable. /// Create the element's casting vtable.
fn create_vtable_func(node: &Node) -> TokenStream { fn create_vtable_func(element: &Elem) -> TokenStream {
let ident = &node.ident; let ident = &element.ident;
let relevant = node.capable.iter().filter(|&ident| ident != "Construct"); let relevant = element.capable.iter().filter(|&ident| ident != "Construct");
let checks = relevant.map(|capability| { let checks = relevant.map(|capability| {
quote! { quote! {
if id == ::std::any::TypeId::of::<dyn #capability>() { if id == ::std::any::TypeId::of::<dyn #capability>() {
@ -388,7 +396,9 @@ fn create_vtable_func(node: &Node) -> TokenStream {
quote! { quote! {
|id| { |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)* #(#checks)*
None None
} }
@ -421,10 +431,10 @@ fn create_param_info(field: &Field) -> TokenStream {
} }
} }
/// Create the node's `Construct` implementation. /// Create the element's `Construct` implementation.
fn create_construct_impl(node: &Node) -> TokenStream { fn create_construct_impl(element: &Elem) -> TokenStream {
let ident = &node.ident; let ident = &element.ident;
let handlers = node let handlers = element
.fields .fields
.iter() .iter()
.filter(|field| { .filter(|field| {
@ -439,13 +449,13 @@ fn create_construct_impl(node: &Node) -> TokenStream {
quote! { quote! {
#prefix #prefix
if let Some(value) = #value { if let Some(value) = #value {
node.#push_ident(value); element.#push_ident(value);
} }
} }
} else { } else {
quote! { quote! {
#prefix #prefix
node.#push_ident(#value); element.#push_ident(#value);
} }
} }
}); });
@ -453,21 +463,23 @@ fn create_construct_impl(node: &Node) -> TokenStream {
quote! { quote! {
impl ::typst::model::Construct for #ident { impl ::typst::model::Construct for #ident {
fn construct( fn construct(
vm: &::typst::eval::Vm, vm: &mut ::typst::eval::Vm,
args: &mut ::typst::eval::Args, args: &mut ::typst::eval::Args,
) -> ::typst::diag::SourceResult<::typst::model::Content> { ) -> ::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)* #(#handlers)*
Ok(node.0) Ok(element.0)
} }
} }
} }
} }
/// Create the node's `Set` implementation. /// Create the element's `Set` implementation.
fn create_set_impl(node: &Node) -> TokenStream { fn create_set_impl(element: &Elem) -> TokenStream {
let ident = &node.ident; let ident = &element.ident;
let handlers = node let handlers = element
.fields .fields
.iter() .iter()
.filter(|field| { .filter(|field| {
@ -491,8 +503,8 @@ fn create_set_impl(node: &Node) -> TokenStream {
impl ::typst::model::Set for #ident { impl ::typst::model::Set for #ident {
fn set( fn set(
args: &mut ::typst::eval::Args, args: &mut ::typst::eval::Args,
) -> ::typst::diag::SourceResult<::typst::model::StyleMap> { ) -> ::typst::diag::SourceResult<::typst::model::Styles> {
let mut styles = ::typst::model::StyleMap::new(); let mut styles = ::typst::model::Styles::new();
#(#handlers)* #(#handlers)*
Ok(styles) Ok(styles)
} }

View File

@ -5,8 +5,8 @@ extern crate proc_macro;
#[macro_use] #[macro_use]
mod util; mod util;
mod castable; mod castable;
mod element;
mod func; mod func;
mod node;
mod symbols; mod symbols;
use proc_macro::TokenStream as BoundaryStream; 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() 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] #[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); 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()) .unwrap_or_else(|err| err.to_compile_error())
.into() .into()
} }

View File

@ -14,7 +14,7 @@ use crate::geom::{
Numeric, Paint, Point, Rel, RgbaColor, Shape, Sides, Size, Stroke, Transform, Numeric, Paint, Point, Rel, RgbaColor, Shape, Sides, Size, Stroke, Transform,
}; };
use crate::image::Image; use crate::image::Image;
use crate::model::{Content, Introspector, MetaNode, StableId, StyleChain}; use crate::model::{Content, Location, MetaElem, StyleChain};
use crate::syntax::Span; use crate::syntax::Span;
/// A finished document with metadata and page frames. /// A finished document with metadata and page frames.
@ -28,7 +28,7 @@ pub struct Document {
pub author: Vec<EcoString>, pub author: Vec<EcoString>,
} }
/// A finished layout with elements at fixed positions. /// A finished layout with items at fixed positions.
#[derive(Default, Clone, Hash)] #[derive(Default, Clone, Hash)]
pub struct Frame { pub struct Frame {
/// The size of the 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 /// The baseline of the frame measured from the top. If this is `None`, the
/// frame's implicit baseline is at the bottom. /// frame's implicit baseline is at the bottom.
baseline: Option<Abs>, baseline: Option<Abs>,
/// The elements composing this layout. /// The items composing this layout.
elements: Arc<Vec<(Point, Element)>>, items: Arc<Vec<(Point, FrameItem)>>,
} }
/// Constructor, accessors and setters. /// Constructor, accessors and setters.
@ -48,12 +48,12 @@ impl Frame {
#[track_caller] #[track_caller]
pub fn new(size: Size) -> Self { pub fn new(size: Size) -> Self {
assert!(size.is_finite()); 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 { pub fn is_empty(&self) -> bool {
self.elements.is_empty() self.items.is_empty()
} }
/// The size of the frame. /// The size of the frame.
@ -109,23 +109,23 @@ impl Frame {
self.size.y - self.baseline() self.size.y - self.baseline()
} }
/// An iterator over the elements inside this frame alongside their /// An iterator over the items inside this frame alongside their positions
/// positions relative to the top-left of the frame. /// relative to the top-left of the frame.
pub fn elements(&self) -> std::slice::Iter<'_, (Point, Element)> { pub fn items(&self) -> std::slice::Iter<'_, (Point, FrameItem)> {
self.elements.iter() 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 { pub fn text(&self) -> EcoString {
let mut text = EcoString::new(); let mut text = EcoString::new();
for (_, element) in self.elements() { for (_, item) in self.items() {
match element { match item {
Element::Text(element) => { FrameItem::Text(item) => {
for glyph in &element.glyphs { for glyph in &item.glyphs {
text.push(glyph.c); 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 { impl Frame {
/// The layer the next item will be added on. This corresponds to the number /// 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 { pub fn layer(&self) -> usize {
self.elements.len() self.items.len()
} }
/// Add an element at a position in the foreground. /// Add an item at a position in the foreground.
pub fn push(&mut self, pos: Point, element: Element) { pub fn push(&mut self, pos: Point, item: FrameItem) {
Arc::make_mut(&mut self.elements).push((pos, element)); Arc::make_mut(&mut self.items).push((pos, item));
} }
/// Add a frame at a position in the foreground. /// Add a frame at a position in the foreground.
/// ///
/// Automatically decides whether to inline the frame or to include it as a /// 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) { pub fn push_frame(&mut self, pos: Point, frame: Frame) {
if self.should_inline(&frame) { if self.should_inline(&frame) {
self.inline(self.layer(), pos, frame); self.inline(self.layer(), pos, frame);
} else { } 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. /// This panics if the layer is greater than the number of layers present.
#[track_caller] #[track_caller]
pub fn insert(&mut self, layer: usize, pos: Point, element: Element) { pub fn insert(&mut self, layer: usize, pos: Point, items: FrameItem) {
Arc::make_mut(&mut self.elements).insert(layer, (pos, element)); Arc::make_mut(&mut self.items).insert(layer, (pos, items));
} }
/// Add an element at a position in the background. /// Add an item at a position in the background.
pub fn prepend(&mut self, pos: Point, element: Element) { pub fn prepend(&mut self, pos: Point, item: FrameItem) {
Arc::make_mut(&mut self.elements).insert(0, (pos, element)); 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. /// background.
pub fn prepend_multiple<I>(&mut self, elements: I) pub fn prepend_multiple<I>(&mut self, items: I)
where 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. /// Add a frame at a position in the background.
@ -187,31 +187,31 @@ impl Frame {
if self.should_inline(&frame) { if self.should_inline(&frame) {
self.inline(0, pos, frame); self.inline(0, pos, frame);
} else { } else {
self.prepend(pos, Element::Group(Group::new(frame))); self.prepend(pos, FrameItem::Group(GroupItem::new(frame)));
} }
} }
/// Whether the given frame should be inlined. /// Whether the given frame should be inlined.
fn should_inline(&self, frame: &Frame) -> bool { 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. /// Inline a frame at the given layer.
fn inline(&mut self, layer: usize, pos: Point, frame: Frame) { fn inline(&mut self, layer: usize, pos: Point, frame: Frame) {
// Try to just reuse the elements. // Try to just reuse the items.
if pos.is_zero() && self.elements.is_empty() { if pos.is_zero() && self.items.is_empty() {
self.elements = frame.elements; self.items = frame.items;
return; return;
} }
// Try to transfer the elements without adjusting the position. // Try to transfer the items without adjusting the position.
// Also try to reuse the elements if the Arc isn't shared. // Also try to reuse the items if the Arc isn't shared.
let range = layer..layer; let range = layer..layer;
if pos.is_zero() { if pos.is_zero() {
let sink = Arc::make_mut(&mut self.elements); let sink = Arc::make_mut(&mut self.items);
match Arc::try_unwrap(frame.elements) { match Arc::try_unwrap(frame.items) {
Ok(elements) => { Ok(items) => {
sink.splice(range, elements); sink.splice(range, items);
} }
Err(arc) => { Err(arc) => {
sink.splice(range, arc.iter().cloned()); sink.splice(range, arc.iter().cloned());
@ -220,12 +220,12 @@ impl Frame {
return; return;
} }
// We must adjust the element positions. // We have to adjust the item positions.
// But still try to reuse the elements if the Arc isn't shared. // But still try to reuse the items if the Arc isn't shared.
let sink = Arc::make_mut(&mut self.elements); let sink = Arc::make_mut(&mut self.items);
match Arc::try_unwrap(frame.elements) { match Arc::try_unwrap(frame.items) {
Ok(elements) => { Ok(items) => {
sink.splice(range, elements.into_iter().map(|(p, e)| (p + pos, e))); sink.splice(range, items.into_iter().map(|(p, e)| (p + pos, e)));
} }
Err(arc) => { Err(arc) => {
sink.splice(range, arc.iter().cloned().map(|(p, e)| (p + pos, e))); sink.splice(range, arc.iter().cloned().map(|(p, e)| (p + pos, e)));
@ -236,12 +236,12 @@ impl Frame {
/// Modify the frame. /// Modify the frame.
impl Frame { impl Frame {
/// Remove all elements from the frame. /// Remove all items from the frame.
pub fn clear(&mut self) { pub fn clear(&mut self) {
if Arc::strong_count(&self.elements) == 1 { if Arc::strong_count(&self.items) == 1 {
Arc::make_mut(&mut self.elements).clear(); Arc::make_mut(&mut self.items).clear();
} else { } else {
self.elements = Arc::new(vec![]); self.items = Arc::new(vec![]);
} }
} }
@ -264,7 +264,7 @@ impl Frame {
if let Some(baseline) = &mut self.baseline { if let Some(baseline) = &mut self.baseline {
*baseline += offset.y; *baseline += offset.y;
} }
for (point, _) in Arc::make_mut(&mut self.elements) { for (point, _) in Arc::make_mut(&mut self.items) {
*point += offset; *point += offset;
} }
} }
@ -273,12 +273,12 @@ impl Frame {
/// Attach the metadata from this style chain to the frame. /// Attach the metadata from this style chain to the frame.
pub fn meta(&mut self, styles: StyleChain, force: bool) { pub fn meta(&mut self, styles: StyleChain, force: bool) {
if force || !self.is_empty() { if force || !self.is_empty() {
for meta in MetaNode::data_in(styles) { for meta in MetaElem::data_in(styles) {
if matches!(meta, Meta::Hide) { if matches!(meta, Meta::Hide) {
self.clear(); self.clear();
break; 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) { pub fn fill(&mut self, fill: Paint) {
self.prepend( self.prepend(
Point::zero(), 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( self.prepend_multiple(
rounded_rect(size, radius, fill, stroke) rounded_rect(size, radius, fill, stroke)
.into_iter() .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`. /// Wrap the frame's contents in a group and modify that group with `f`.
fn group<F>(&mut self, f: F) fn group<F>(&mut self, f: F)
where where
F: FnOnce(&mut Group), F: FnOnce(&mut GroupItem),
{ {
let mut wrapper = Frame::new(self.size); let mut wrapper = Frame::new(self.size);
wrapper.baseline = self.baseline; 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); f(&mut group);
wrapper.push(Point::zero(), Element::Group(group)); wrapper.push(Point::zero(), FrameItem::Group(group));
*self = wrapper; *self = wrapper;
} }
} }
@ -346,7 +346,7 @@ impl Frame {
self.insert( self.insert(
0, 0,
Point::zero(), Point::zero(),
Element::Shape( FrameItem::Shape(
Geometry::Rect(self.size) Geometry::Rect(self.size)
.filled(RgbaColor { a: 100, ..Color::TEAL.to_rgba() }.into()), .filled(RgbaColor { a: 100, ..Color::TEAL.to_rgba() }.into()),
Span::detached(), Span::detached(),
@ -355,7 +355,7 @@ impl Frame {
self.insert( self.insert(
1, 1,
Point::with_y(self.baseline()), Point::with_y(self.baseline()),
Element::Shape( FrameItem::Shape(
Geometry::Line(Point::with_x(self.size.x)).stroked(Stroke { Geometry::Line(Point::with_x(self.size.x)).stroked(Stroke {
paint: Color::RED.into(), paint: Color::RED.into(),
thickness: Abs::pt(1.0), thickness: Abs::pt(1.0),
@ -371,7 +371,7 @@ impl Frame {
let radius = Abs::pt(2.0); let radius = Abs::pt(2.0);
self.push( self.push(
pos - Point::splat(radius), pos - Point::splat(radius),
Element::Shape( FrameItem::Shape(
geom::ellipse(Size::splat(2.0 * radius), Some(Color::GREEN.into()), None), geom::ellipse(Size::splat(2.0 * radius), Some(Color::GREEN.into()), None),
Span::detached(), Span::detached(),
), ),
@ -382,7 +382,7 @@ impl Frame {
pub fn mark_line(&mut self, y: Abs) { pub fn mark_line(&mut self, y: Abs) {
self.push( self.push(
Point::with_y(y), Point::with_y(y),
Element::Shape( FrameItem::Shape(
Geometry::Line(Point::with_x(self.size.x)).stroked(Stroke { Geometry::Line(Point::with_x(self.size.x)).stroked(Stroke {
paint: Color::GREEN.into(), paint: Color::GREEN.into(),
thickness: Abs::pt(1.0), thickness: Abs::pt(1.0),
@ -397,18 +397,18 @@ impl Debug for Frame {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str("Frame ")?; f.write_str("Frame ")?;
f.debug_list() f.debug_list()
.entries(self.elements.iter().map(|(_, element)| element)) .entries(self.items.iter().map(|(_, item)| item))
.finish() .finish()
} }
} }
/// The building block frames are composed of. /// The building block frames are composed of.
#[derive(Clone, Hash)] #[derive(Clone, Hash)]
pub enum Element { pub enum FrameItem {
/// A group of elements. /// A subframe with optional transformation and clipping.
Group(Group), Group(GroupItem),
/// A run of shaped text. /// A run of shaped text.
Text(Text), Text(TextItem),
/// A geometric shape with optional fill and stroke. /// A geometric shape with optional fill and stroke.
Shape(Shape, Span), Shape(Shape, Span),
/// An image and its size. /// An image and its size.
@ -417,7 +417,7 @@ pub enum Element {
Meta(Meta, Size), Meta(Meta, Size),
} }
impl Debug for Element { impl Debug for FrameItem {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self { match self {
Self::Group(group) => group.fmt(f), 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)] #[derive(Clone, Hash)]
pub struct Group { pub struct GroupItem {
/// The group's frame. /// The group's frame.
pub frame: Frame, pub frame: Frame,
/// A transformation to apply to the group. /// A transformation to apply to the group.
@ -440,7 +440,7 @@ pub struct Group {
pub clips: bool, pub clips: bool,
} }
impl Group { impl GroupItem {
/// Create a new group with default settings. /// Create a new group with default settings.
pub fn new(frame: Frame) -> Self { pub fn new(frame: Frame) -> Self {
Self { Self {
@ -451,7 +451,7 @@ impl Group {
} }
} }
impl Debug for Group { impl Debug for GroupItem {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str("Group ")?; f.write_str("Group ")?;
self.frame.fmt(f) self.frame.fmt(f)
@ -460,7 +460,7 @@ impl Debug for Group {
/// A run of shaped text. /// A run of shaped text.
#[derive(Clone, Eq, PartialEq, Hash)] #[derive(Clone, Eq, PartialEq, Hash)]
pub struct Text { pub struct TextItem {
/// The font the glyphs are contained in. /// The font the glyphs are contained in.
pub font: Font, pub font: Font,
/// The font size. /// The font size.
@ -473,14 +473,14 @@ pub struct Text {
pub glyphs: Vec<Glyph>, pub glyphs: Vec<Glyph>,
} }
impl Text { impl TextItem {
/// The width of the text run. /// The width of the text run.
pub fn width(&self) -> Abs { pub fn width(&self) -> Abs {
self.glyphs.iter().map(|g| g.x_advance).sum::<Em>().at(self.size) 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 { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
// This is only a rough approxmiation of the source text. // This is only a rough approxmiation of the source text.
f.write_str("Text(\"")?; f.write_str("Text(\"")?;
@ -595,97 +595,73 @@ cast_to_value! {
} }
/// Meta information that isn't visible or renderable. /// Meta information that isn't visible or renderable.
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, PartialEq, Hash)]
pub enum Meta { 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, 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! { cast_from_value! {
Meta: "meta", 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. /// A link destination.
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum Destination { pub enum Destination {
/// A link to a point on a page.
Internal(Location),
/// A link to a URL. /// A link to a URL.
Url(EcoString), 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! { cast_from_value! {
Destination, Destination,
loc: Location => Self::Internal(loc), v: EcoString => Self::Url(v),
string: EcoString => Self::Url(string), v: Position => Self::Position(v),
v: Location => Self::Location(v),
} }
cast_to_value! { cast_to_value! {
v: Destination => match v { v: Destination => match v {
Destination::Internal(loc) => loc.into(), Destination::Url(v) => v.into(),
Destination::Url(url) => url.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)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Location { pub struct Position {
/// The page, starting at 1. /// The page, starting at 1.
pub page: NonZeroUsize, pub page: NonZeroUsize,
/// The exact coordinates on the page (from the top left, as usual). /// The exact coordinates on the page (from the top left, as usual).
pub pos: Point, pub point: Point,
} }
cast_from_value! { cast_from_value! {
Location, Position,
mut dict: Dict => { mut dict: Dict => {
let page = dict.take("page")?.cast()?; let page = dict.take("page")?.cast()?;
let x: Length = dict.take("x")?.cast()?; let x: Length = dict.take("x")?.cast()?;
let y: Length = dict.take("y")?.cast()?; let y: Length = dict.take("y")?.cast()?;
dict.finish(&["page", "x", "y"])?; 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! { cast_to_value! {
v: Location => Value::Dict(dict! { v: Position => Value::Dict(dict! {
"page" => Value::Int(v.page.get() as i64), "page" => Value::Int(v.page.get() as i64),
"x" => Value::Length(v.pos.x.into()), "x" => Value::Length(v.point.x.into()),
"y" => Value::Length(v.pos.y.into()), "y" => Value::Length(v.point.y.into()),
}) })
} }

View File

@ -137,7 +137,7 @@ impl Array {
self.0.contains(value) 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>> { pub fn find(&self, vm: &mut Vm, func: Func) -> SourceResult<Option<Value>> {
for item in self.iter() { for item in self.iter() {
let args = Args::new(func.span(), [item.clone()]); let args = Args::new(func.span(), [item.clone()]);
@ -148,7 +148,7 @@ impl Array {
Ok(None) 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>> { pub fn position(&self, vm: &mut Vm, func: Func) -> SourceResult<Option<i64>> {
for (i, item) in self.iter().enumerate() { for (i, item) in self.iter().enumerate() {
let args = Args::new(func.span(), [item.clone()]); let args = Args::new(func.span(), [item.clone()]);
@ -160,8 +160,8 @@ impl Array {
Ok(None) Ok(None)
} }
/// Return a new array with only those elements for which the function /// Return a new array with only those items for which the function returns
/// returns true. /// true.
pub fn filter(&self, vm: &mut Vm, func: Func) -> SourceResult<Self> { pub fn filter(&self, vm: &mut Vm, func: Func) -> SourceResult<Self> {
let mut kept = EcoVec::new(); let mut kept = EcoVec::new();
for item in self.iter() { for item in self.iter() {
@ -189,7 +189,7 @@ impl Array {
.collect() .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> { pub fn fold(&self, vm: &mut Vm, init: Value, func: Func) -> SourceResult<Value> {
let mut acc = init; let mut acc = init;
for item in self.iter() { for item in self.iter() {
@ -199,7 +199,7 @@ impl Array {
Ok(acc) Ok(acc)
} }
/// Whether any element matches. /// Whether any item matches.
pub fn any(&self, vm: &mut Vm, func: Func) -> SourceResult<bool> { pub fn any(&self, vm: &mut Vm, func: Func) -> SourceResult<bool> {
for item in self.iter() { for item in self.iter() {
let args = Args::new(func.span(), [item.clone()]); let args = Args::new(func.span(), [item.clone()]);
@ -211,7 +211,7 @@ impl Array {
Ok(false) Ok(false)
} }
/// Whether all elements match. /// Whether all items match.
pub fn all(&self, vm: &mut Vm, func: Func) -> SourceResult<bool> { pub fn all(&self, vm: &mut Vm, func: Func) -> SourceResult<bool> {
for item in self.iter() { for item in self.iter() {
let args = Args::new(func.span(), [item.clone()]); let args = Args::new(func.span(), [item.clone()]);

View File

@ -8,14 +8,12 @@ use comemo::{Prehashed, Track, Tracked, TrackedMut};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use super::{ use super::{
cast_to_value, Args, CastInfo, Dict, Eval, Flow, Route, Scope, Scopes, Tracer, Value, cast_to_value, Args, CastInfo, Eval, Flow, Route, Scope, Scopes, Tracer, Value, Vm,
Vm,
}; };
use crate::diag::{bail, SourceResult, StrResult}; use crate::diag::{bail, SourceResult};
use crate::model::{Introspector, NodeId, Selector, StabilityProvider, StyleMap, Vt}; use crate::model::{ElemFunc, Introspector, StabilityProvider, Vt};
use crate::syntax::ast::{self, AstNode, Expr, Ident}; use crate::syntax::ast::{self, AstNode, Expr, Ident};
use crate::syntax::{SourceId, Span, SyntaxNode}; use crate::syntax::{SourceId, Span, SyntaxNode};
use crate::util::hash128;
use crate::World; use crate::World;
/// An evaluatable function. /// An evaluatable function.
@ -32,8 +30,8 @@ pub struct Func {
enum Repr { enum Repr {
/// A native Rust function. /// A native Rust function.
Native(NativeFunc), Native(NativeFunc),
/// A function for a node. /// A function for an element.
Node(NodeId), Elem(ElemFunc),
/// A user-defined closure. /// A user-defined closure.
Closure(Closure), Closure(Closure),
/// A nested function with pre-applied arguments. /// A nested function with pre-applied arguments.
@ -45,7 +43,7 @@ impl Func {
pub fn name(&self) -> Option<&str> { pub fn name(&self) -> Option<&str> {
match &**self.repr { match &**self.repr {
Repr::Native(native) => Some(native.info.name), 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::Closure(closure) => closure.name.as_deref(),
Repr::With(func, _) => func.name(), Repr::With(func, _) => func.name(),
} }
@ -55,7 +53,7 @@ impl Func {
pub fn info(&self) -> Option<&FuncInfo> { pub fn info(&self) -> Option<&FuncInfo> {
match &**self.repr { match &**self.repr {
Repr::Native(native) => Some(&native.info), Repr::Native(native) => Some(&native.info),
Repr::Node(node) => Some(&node.info), Repr::Elem(func) => Some(func.info()),
Repr::With(func, _) => func.info(), Repr::With(func, _) => func.info(),
_ => None, _ => None,
} }
@ -93,8 +91,8 @@ impl Func {
args.finish()?; args.finish()?;
Ok(value) Ok(value)
} }
Repr::Node(node) => { Repr::Elem(func) => {
let value = (node.construct)(vm, &mut args)?; let value = func.construct(vm, &mut args)?;
args.finish()?; args.finish()?;
Ok(Value::Content(value)) Ok(Value::Content(value))
} }
@ -145,46 +143,13 @@ impl Func {
} }
} }
/// Create a selector for this function's node type, filtering by node's /// Extract the element function, if it is one.
/// whose [fields](super::Content::field) match the given arguments. pub fn element(&self) -> Option<ElemFunc> {
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> {
match **self.repr { match **self.repr {
Repr::Node(id) => Some(id), Repr::Elem(func) => Some(func),
_ => None, _ => 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 { impl Debug for Func {
@ -198,7 +163,7 @@ impl Debug for Func {
impl PartialEq for Func { impl PartialEq for Func {
fn eq(&self, other: &Self) -> bool { 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 { impl From<ElemFunc> for Func {
fn from(id: NodeId) -> Self { fn from(func: ElemFunc) -> Self {
Repr::Node(id).into() Repr::Elem(func).into()
} }
} }
/// A native Rust function. /// A Typst function defined by a native Rust function.
pub struct NativeFunc { pub struct NativeFunc {
/// The function's implementation. /// The function's implementation.
pub func: fn(&mut Vm, &mut Args) -> SourceResult<Value>, pub func: fn(&mut Vm, &mut Args) -> SourceResult<Value>,

View File

@ -10,7 +10,7 @@ use super::{Args, Dynamic, Module, Value, Vm};
use crate::diag::SourceResult; use crate::diag::SourceResult;
use crate::doc::Document; use crate::doc::Document;
use crate::geom::{Abs, Dir}; 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::syntax::Span;
use crate::util::hash128; use crate::util::hash128;
use crate::World; use crate::World;
@ -23,7 +23,7 @@ pub struct Library {
/// The scope containing definitions available in math mode. /// The scope containing definitions available in math mode.
pub math: Module, pub math: Module,
/// The default properties for page size, font selection and so on. /// 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. /// Defines which standard library items fulfill which syntactical roles.
pub items: LangItems, pub items: LangItems,
} }
@ -44,9 +44,9 @@ pub struct LangItems {
pub linebreak: fn() -> Content, pub linebreak: fn() -> Content,
/// Plain text without markup. /// Plain text without markup.
pub text: fn(text: EcoString) -> Content, pub text: fn(text: EcoString) -> Content,
/// The id of the text node. /// The text function.
pub text_id: NodeId, pub text_func: ElemFunc,
/// Get the string if this is a text node. /// Get the string if this is a text element.
pub text_str: fn(&Content) -> Option<EcoString>, pub text_str: fn(&Content) -> Option<EcoString>,
/// A smart quote: `'` or `"`. /// A smart quote: `'` or `"`.
pub smart_quote: fn(double: bool) -> Content, pub smart_quote: fn(double: bool) -> Content,
@ -114,7 +114,7 @@ impl Hash for LangItems {
self.space.hash(state); self.space.hash(state);
self.linebreak.hash(state); self.linebreak.hash(state);
self.text.hash(state); self.text.hash(state);
self.text_id.hash(state); self.text_func.hash(state);
(self.text_str as usize).hash(state); (self.text_str as usize).hash(state);
self.smart_quote.hash(state); self.smart_quote.hash(state);
self.parbreak.hash(state); self.parbreak.hash(state);
@ -140,13 +140,15 @@ impl Hash for LangItems {
#[doc(hidden)] #[doc(hidden)]
pub static LANG_ITEMS: OnceCell<LangItems> = OnceCell::new(); 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 /// This is a hack :(
/// 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 /// Passing the lang items everywhere they are needed (especially text related
/// the same program. For this reason, if this function is called multiple /// things) is very painful. By storing them globally, in theory, we break
/// times, the items must be the same. /// 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) { pub fn set_lang_items(items: LangItems) {
if let Err(items) = LANG_ITEMS.set(items) { if let Err(items) = LANG_ITEMS.set(items) {
let first = hash128(LANG_ITEMS.get().unwrap()); let first = hash128(LANG_ITEMS.get().unwrap());

View File

@ -4,7 +4,7 @@ use ecow::EcoString;
use super::{Args, Str, Value, Vm}; use super::{Args, Str, Value, Vm};
use crate::diag::{At, SourceResult}; use crate::diag::{At, SourceResult};
use crate::model::StableId; use crate::model::Location;
use crate::syntax::Span; use crate::syntax::Span;
/// Call a method on a value. /// Call a method on a value.
@ -71,12 +71,12 @@ pub fn call(
}, },
Value::Content(content) => match method { Value::Content(content) => match method {
"func" => Value::Func(content.id().into()), "func" => content.func().into(),
"has" => Value::Bool(content.has(&args.expect::<EcoString>("field")?)), "has" => Value::Bool(content.has(&args.expect::<EcoString>("field")?)),
"at" => content.at(&args.expect::<EcoString>("field")?).at(span)?.clone(), "at" => content.at(&args.expect::<EcoString>("field")?).at(span)?.clone(),
"id" => content "location" => content
.stable_id() .location()
.ok_or("this method can only be called on content returned by query()") .ok_or("this method can only be called on content returned by query(..)")
.at(span)? .at(span)?
.into(), .into(),
_ => return missing(), _ => return missing(),
@ -130,7 +130,16 @@ pub fn call(
Value::Func(func) => match method { Value::Func(func) => match method {
"with" => Value::Func(func.with(args.take())), "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(), _ => return missing(),
}, },
@ -141,10 +150,10 @@ pub fn call(
}, },
Value::Dyn(dynamic) => { Value::Dyn(dynamic) => {
if let Some(&id) = dynamic.downcast::<StableId>() { if let Some(&location) = dynamic.downcast::<Location>() {
match method { match method {
"page" => vm.vt.introspector.page(id).into(), "page" => vm.vt.introspector.page(location).into(),
"location" => vm.vt.introspector.location(id).into(), "position" => vm.vt.introspector.position(location).into(),
_ => return missing(), _ => return missing(),
} }
} else { } else {
@ -263,7 +272,7 @@ pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] {
("starts-with", true), ("starts-with", true),
("trim", true), ("trim", true),
], ],
"content" => &[("func", false), ("has", true), ("at", true), ("id", false)], "content" => &[("func", false), ("has", true), ("at", true), ("location", false)],
"array" => &[ "array" => &[
("all", true), ("all", true),
("any", true), ("any", true),
@ -299,7 +308,7 @@ pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] {
], ],
"function" => &[("where", true), ("with", true)], "function" => &[("where", true), ("with", true)],
"arguments" => &[("named", false), ("pos", false)], "arguments" => &[("named", false), ("pos", false)],
"stable id" => &[("page", false), ("location", false)], "location" => &[("page", false), ("position", false)],
"counter" => &[ "counter" => &[
("display", true), ("display", true),
("at", true), ("at", true),

View File

@ -29,13 +29,14 @@ pub use self::cast::*;
pub use self::dict::*; pub use self::dict::*;
pub use self::func::*; pub use self::func::*;
pub use self::library::*; pub use self::library::*;
pub use self::methods::*;
pub use self::module::*; pub use self::module::*;
pub use self::scope::*; pub use self::scope::*;
pub use self::str::*; pub use self::str::*;
pub use self::symbol::*; pub use self::symbol::*;
pub use self::value::*; pub use self::value::*;
pub(crate) use self::methods::methods_on;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::mem; use std::mem;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -47,11 +48,10 @@ use unicode_segmentation::UnicodeSegmentation;
use crate::diag::{ use crate::diag::{
bail, error, At, SourceError, SourceResult, StrResult, Trace, Tracepoint, bail, error, At, SourceError, SourceResult, StrResult, Trace, Tracepoint,
}; };
use crate::model::Introspector; use crate::model::{
use crate::model::StabilityProvider; Content, Introspector, Label, Recipe, Selector, StabilityProvider, Styles, Transform,
use crate::model::Unlabellable; Unlabellable, Vt,
use crate::model::Vt; };
use crate::model::{Content, Label, Recipe, Selector, StyleMap, Transform};
use crate::syntax::ast::AstNode; use crate::syntax::ast::AstNode;
use crate::syntax::{ use crate::syntax::{
ast, parse_code, Source, SourceId, Span, Spanned, SyntaxKind, SyntaxNode, 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`. /// Everything in the output is associated with the given `span`.
#[comemo::memoize] #[comemo::memoize]
pub fn eval_code_str( pub fn eval_string(
world: Tracked<dyn World>, world: Tracked<dyn World>,
text: &str, code: &str,
span: Span, span: Span,
) -> SourceResult<Value> { ) -> SourceResult<Value> {
let mut root = parse_code(text); let mut root = parse_code(code);
root.synthesize(span); root.synthesize(span);
let errors = root.errors(); 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)] #[derive(Default, Clone)]
pub struct Tracer { pub struct Tracer {
span: Option<Span>, span: Option<Span>,
@ -377,10 +377,10 @@ fn eval_markup(
} }
expr => match expr.eval(vm)? { expr => match expr.eval(vm)? {
Value::Label(label) => { Value::Label(label) => {
if let Some(node) = if let Some(elem) =
seq.iter_mut().rev().find(|node| !node.can::<dyn Unlabellable>()) 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())), value => seq.push(value.display().spanned(expr.span())),
@ -643,7 +643,7 @@ impl Eval for ast::Math {
Ok(Content::sequence( Ok(Content::sequence(
self.exprs() self.exprs()
.map(|expr| expr.eval_display(vm)) .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 in_math && !matches!(callee, Value::Func(_)) {
if let Value::Symbol(sym) = &callee { if let Value::Symbol(sym) = &callee {
let c = sym.get(); let c = sym.get();
if let Some(accent) = combining_accent(c) { if let Some(accent) = Symbol::combining_accent(c) {
let base = args.expect("base")?; let base = args.expect("base")?;
args.finish()?; args.finish()?;
return Ok(Value::Content((vm.items.math_accent)(base, accent))); return Ok(Value::Content((vm.items.math_accent)(base, accent)));
@ -1198,17 +1198,25 @@ impl Eval for ast::LetBinding {
} }
impl Eval for ast::SetRule { impl Eval for ast::SetRule {
type Output = StyleMap; type Output = Styles;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
if let Some(condition) = self.condition() { if let Some(condition) = self.condition() {
if !condition.eval(vm)?.cast::<bool>().at(condition.span())? { if !condition.eval(vm)?.cast::<bool>().at(condition.span())? {
return Ok(StyleMap::new()); return Ok(Styles::new());
} }
} }
let target = self.target(); 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)?; let args = self.args().eval(vm)?;
Ok(target.set(args)?.spanned(self.span())) Ok(target.set(args)?.spanned(self.span()))
} }

View File

@ -163,7 +163,9 @@ impl Slot {
fn write(&mut self) -> StrResult<&mut Value> { fn write(&mut self) -> StrResult<&mut Value> {
match self.kind { match self.kind {
Kind::Normal => Ok(&mut self.value), 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")?
}
} }
} }
} }

View File

@ -1,94 +1,127 @@
use std::cmp::Reverse; use std::cmp::Reverse;
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::fmt::{self, Debug, Display, Formatter, Write}; use std::fmt::{self, Debug, Display, Formatter, Write};
use std::sync::Arc;
use ecow::{EcoString, EcoVec}; use ecow::EcoString;
use crate::diag::StrResult; use crate::diag::StrResult;
#[doc(inline)] #[doc(inline)]
pub use typst_macros::symbols; pub use typst_macros::symbols;
/// A symbol. /// A symbol, possibly with variants.
#[derive(Clone, Eq, PartialEq, Hash)] #[derive(Clone, Eq, PartialEq, Hash)]
pub struct Symbol { pub struct Symbol(Repr);
repr: Repr,
modifiers: EcoString, /// 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. /// A collection of symbols.
#[derive(Clone, Eq, PartialEq, Hash)] #[derive(Clone, Eq, PartialEq, Hash)]
enum Repr { enum List {
Single(char),
Static(&'static [(&'static str, char)]), Static(&'static [(&'static str, char)]),
Runtime(EcoVec<(EcoString, char)>), Runtime(Box<[(EcoString, char)]>),
} }
impl Symbol { impl Symbol {
/// Create a new symbol from a single character. /// Create a new symbol from a single character.
pub const fn new(c: char) -> Self { 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. /// Create a symbol with a static variant list.
#[track_caller] #[track_caller]
pub const fn list(list: &'static [(&'static str, char)]) -> Self { pub const fn list(list: &'static [(&'static str, char)]) -> Self {
debug_assert!(!list.is_empty()); debug_assert!(!list.is_empty());
Self { Self(Repr::Const(list))
repr: Repr::Static(list),
modifiers: EcoString::new(),
}
} }
/// Create a symbol with a runtime variant list. /// Create a symbol with a runtime variant list.
#[track_caller] #[track_caller]
pub fn runtime(list: EcoVec<(EcoString, char)>) -> Self { pub fn runtime(list: Box<[(EcoString, char)]>) -> Self {
debug_assert!(!list.is_empty()); debug_assert!(!list.is_empty());
Self { Self(Repr::Multi(Arc::new((List::Runtime(list), EcoString::new()))))
repr: Repr::Runtime(list),
modifiers: EcoString::new(),
}
} }
/// Get the symbol's text. /// Get the symbol's text.
pub fn get(&self) -> char { pub fn get(&self) -> char {
match self.repr { match &self.0 {
Repr::Single(c) => c, Repr::Single(c) => *c,
_ => find(self.variants(), &self.modifiers).unwrap(), Repr::Const(_) => find(self.variants(), "").unwrap(),
Repr::Multi(arc) => find(self.variants(), &arc.1).unwrap(),
} }
} }
/// Apply a modifier to the symbol. /// Apply a modifier to the symbol.
pub fn modified(mut self, modifier: &str) -> StrResult<Self> { pub fn modified(mut self, modifier: &str) -> StrResult<Self> {
if !self.modifiers.is_empty() { if let Repr::Const(list) = self.0 {
self.modifiers.push('.'); self.0 = Repr::Multi(Arc::new((List::Static(list), EcoString::new())));
} }
self.modifiers.push_str(modifier);
if find(self.variants(), &self.modifiers).is_none() { if let Repr::Multi(arc) = &mut self.0 {
Err("unknown modifier")? 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. /// The characters that are covered by this symbol.
pub fn variants(&self) -> impl Iterator<Item = (&str, char)> { 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::Single(c) => Variants::Single(Some(*c).into_iter()),
Repr::Static(list) => Variants::Static(list.iter()), Repr::Const(list) => Variants::Static(list.iter()),
Repr::Runtime(list) => Variants::Runtime(list.iter()), Repr::Multi(arc) => arc.0.variants(),
} }
} }
/// Possible modifiers. /// Possible modifiers.
pub fn modifiers(&self) -> impl Iterator<Item = &str> + '_ { pub fn modifiers(&self) -> impl Iterator<Item = &str> + '_ {
let mut set = BTreeSet::new(); 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('.')) { 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.insert(modifier);
} }
} }
set.into_iter() 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 { 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. /// Iterator over variants.
enum Variants<'a> { enum Variants<'a> {
Single(std::option::IntoIter<char>), Single(std::option::IntoIter<char>),
@ -166,24 +209,3 @@ fn parts(modifiers: &str) -> impl Iterator<Item = &str> {
fn contained(modifiers: &str, m: &str) -> bool { fn contained(modifiers: &str, m: &str) -> bool {
parts(modifiers).any(|part| part == m) 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,
})
}

View File

@ -13,6 +13,7 @@ use super::{
}; };
use crate::diag::StrResult; use crate::diag::StrResult;
use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel}; use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel};
use crate::model::Styles;
use crate::syntax::{ast, Span}; use crate::syntax::{ast, Span};
/// A computational value. /// A computational value.
@ -48,6 +49,8 @@ pub enum Value {
Label(Label), Label(Label),
/// A content value: `[*Hi* there]`. /// A content value: `[*Hi* there]`.
Content(Content), Content(Content),
// Content styles.
Styles(Styles),
/// An array of values: `(1, "hi", 12cm)`. /// An array of values: `(1, "hi", 12cm)`.
Array(Array), Array(Array),
/// A dictionary value: `(color: #f79143, pattern: dashed)`. /// A dictionary value: `(color: #f79143, pattern: dashed)`.
@ -101,6 +104,7 @@ impl Value {
Self::Str(_) => Str::TYPE_NAME, Self::Str(_) => Str::TYPE_NAME,
Self::Label(_) => Label::TYPE_NAME, Self::Label(_) => Label::TYPE_NAME,
Self::Content(_) => Content::TYPE_NAME, Self::Content(_) => Content::TYPE_NAME,
Self::Styles(_) => Styles::TYPE_NAME,
Self::Array(_) => Array::TYPE_NAME, Self::Array(_) => Array::TYPE_NAME,
Self::Dict(_) => Dict::TYPE_NAME, Self::Dict(_) => Dict::TYPE_NAME,
Self::Func(_) => Func::TYPE_NAME, Self::Func(_) => Func::TYPE_NAME,
@ -120,7 +124,7 @@ impl Value {
match self { match self {
Self::Symbol(symbol) => symbol.clone().modified(&field).map(Self::Symbol), Self::Symbol(symbol) => symbol.clone().modified(&field).map(Self::Symbol),
Self::Dict(dict) => dict.at(&field).cloned(), 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(), Self::Module(module) => module.get(&field).cloned(),
v => Err(eco_format!("cannot access fields on type {}", v.type_name())), 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::Str(v) => Debug::fmt(v, f),
Self::Label(v) => Debug::fmt(v, f), Self::Label(v) => Debug::fmt(v, f),
Self::Content(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::Array(v) => Debug::fmt(v, f),
Self::Dict(v) => Debug::fmt(v, f), Self::Dict(v) => Debug::fmt(v, f),
Self::Func(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::Str(v) => v.hash(state),
Self::Label(v) => v.hash(state), Self::Label(v) => v.hash(state),
Self::Content(v) => v.hash(state), Self::Content(v) => v.hash(state),
Self::Styles(v) => v.hash(state),
Self::Array(v) => v.hash(state), Self::Array(v) => v.hash(state),
Self::Dict(v) => v.hash(state), Self::Dict(v) => v.hash(state),
Self::Func(v) => v.hash(state), Self::Func(v) => v.hash(state),
@ -400,6 +406,7 @@ primitive! { Content: "content",
Symbol(v) => item!(text)(v.get().into()), Symbol(v) => item!(text)(v.get().into()),
Str(v) => item!(text)(v.into()) Str(v) => item!(text)(v.into())
} }
primitive! { Styles: "styles", Styles }
primitive! { Array: "array", Array } primitive! { Array: "array", Array }
primitive! { Dict: "dictionary", Dict } primitive! { Dict: "dictionary", Dict }
primitive! { Func: "function", Func } primitive! { Func: "function", Func }

View File

@ -4,7 +4,7 @@ use pdf_writer::writers::ColorSpace;
use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str}; use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str};
use super::{deflate, AbsExt, EmExt, PdfContext, RefExt, D65_GRAY, SRGB}; 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::font::Font;
use crate::geom::{ use crate::geom::{
self, Abs, Color, Em, Geometry, Numeric, Paint, Point, Ratio, Shape, Size, Stroke, 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); page_writer.contents(content_id);
let mut annotations = page_writer.annotations(); let mut annotations = page_writer.annotations();
for (link, rect) in page.links { for (dest, rect) in page.links {
let mut annotation = annotations.push(); let mut annotation = annotations.push();
annotation.subtype(AnnotationType::Link).rect(rect); annotation.subtype(AnnotationType::Link).rect(rect);
annotation.border(0.0, 0.0, 0.0, None); annotation.border(0.0, 0.0, 0.0, None);
match link.resolve(|| &ctx.introspector) {
let pos = match dest {
Destination::Url(uri) => { Destination::Url(uri) => {
annotation annotation
.action() .action()
.action_type(ActionType::Uri) .action_type(ActionType::Uri)
.uri(Str(uri.as_bytes())); .uri(Str(uri.as_bytes()));
continue;
} }
Destination::Internal(loc) => { Destination::Position(pos) => pos,
let index = loc.page.get() - 1; Destination::Location(loc) => ctx.introspector.position(loc),
let y = (loc.pos.y - Abs::pt(10.0)).max(Abs::zero()); };
if let Some(&height) = ctx.page_heights.get(index) {
annotation let index = pos.page.get() - 1;
.action() let y = (pos.point.y - Abs::pt(10.0)).max(Abs::zero());
.action_type(ActionType::GoTo) if let Some(&height) = ctx.page_heights.get(index) {
.destination_direct() annotation
.page(ctx.page_refs[index]) .action()
.xyz(loc.pos.x.to_f32(), height - y.to_f32(), None); .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. /// The page's content stream.
pub content: Content, pub content: Content,
/// Links in the PDF coordinate system. /// 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. /// An exporter for the contents of a single PDF page.
@ -164,7 +167,7 @@ struct PageContext<'a, 'b> {
state: State, state: State,
saves: Vec<State>, saves: Vec<State>,
bottom: f32, bottom: f32,
links: Vec<(Link, Rect)>, links: Vec<(Destination, Rect)>,
} }
/// A simulated graphics state used to deduplicate graphics state changes and /// A simulated graphics state used to deduplicate graphics state changes and
@ -283,17 +286,17 @@ impl PageContext<'_, '_> {
/// Encode a frame into the content stream. /// Encode a frame into the content stream.
fn write_frame(ctx: &mut PageContext, frame: &Frame) { 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 x = pos.x.to_f32();
let y = pos.y.to_f32(); let y = pos.y.to_f32();
match element { match item {
Element::Group(group) => write_group(ctx, pos, group), FrameItem::Group(group) => write_group(ctx, pos, group),
Element::Text(text) => write_text(ctx, x, y, text), FrameItem::Text(text) => write_text(ctx, x, y, text),
Element::Shape(shape, _) => write_shape(ctx, x, y, shape), FrameItem::Shape(shape, _) => write_shape(ctx, x, y, shape),
Element::Image(image, size, _) => write_image(ctx, x, y, image, *size), FrameItem::Image(image, size, _) => write_image(ctx, x, y, image, *size),
Element::Meta(meta, size) => match meta { FrameItem::Meta(meta, size) => match meta {
Meta::Link(link) => write_link(ctx, pos, link, *size), Meta::Link(dest) => write_link(ctx, pos, dest, *size),
Meta::Node(_) => {} Meta::Elem(_) => {}
Meta::Hide => {} Meta::Hide => {}
}, },
} }
@ -301,7 +304,7 @@ fn write_frame(ctx: &mut PageContext, frame: &Frame) {
} }
/// Encode a group into the content stream. /// 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); let translation = Transform::translate(pos.x, pos.y);
ctx.save_state(); 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. /// 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.languages.entry(text.lang).or_insert(0) += text.glyphs.len();
ctx.parent ctx.parent
.glyph_sets .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) { fn write_path(ctx: &mut PageContext, x: f32, y: f32, path: &geom::Path) {
for elem in &path.0 { for elem in &path.0 {
match elem { match elem {
geom::PathElement::MoveTo(p) => { geom::PathItem::MoveTo(p) => {
ctx.content.move_to(x + p.x.to_f32(), y + p.y.to_f32()) 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()) 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(), x + p1.x.to_f32(),
y + p1.y.to_f32(), y + p1.y.to_f32(),
x + p2.x.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(), x + p3.x.to_f32(),
y + p3.y.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. /// 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_x = Abs::inf();
let mut min_y = Abs::inf(); let mut min_y = Abs::inf();
let mut max_x = -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 y2 = min_y.to_f32();
let rect = Rect::new(x1, y1, x2, y2); let rect = Rect::new(x1, y1, x2, y2);
ctx.links.push((link.clone(), rect)); ctx.links.push((dest.clone(), rect));
} }

View File

@ -9,9 +9,9 @@ use tiny_skia as sk;
use ttf_parser::{GlyphId, OutlineBuilder}; use ttf_parser::{GlyphId, OutlineBuilder};
use usvg::FitTo; use usvg::FitTo;
use crate::doc::{Element, Frame, Group, Meta, Text}; use crate::doc::{Frame, FrameItem, GroupItem, Meta, TextItem};
use crate::geom::{ 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}; use crate::image::{DecodedImage, Image};
@ -33,34 +33,34 @@ pub fn render(frame: &Frame, pixel_per_pt: f32, fill: Color) -> sk::Pixmap {
canvas canvas
} }
/// Render all elements in a frame into the canvas. /// Render a frame into the canvas.
fn render_frame( fn render_frame(
canvas: &mut sk::Pixmap, canvas: &mut sk::Pixmap,
ts: sk::Transform, ts: sk::Transform,
mask: Option<&sk::ClipMask>, mask: Option<&sk::ClipMask>,
frame: &Frame, frame: &Frame,
) { ) {
for (pos, element) in frame.elements() { for (pos, item) in frame.items() {
let x = pos.x.to_f32(); let x = pos.x.to_f32();
let y = pos.y.to_f32(); let y = pos.y.to_f32();
let ts = ts.pre_translate(x, y); let ts = ts.pre_translate(x, y);
match element { match item {
Element::Group(group) => { FrameItem::Group(group) => {
render_group(canvas, ts, mask, group); render_group(canvas, ts, mask, group);
} }
Element::Text(text) => { FrameItem::Text(text) => {
render_text(canvas, ts, mask, text); render_text(canvas, ts, mask, text);
} }
Element::Shape(shape, _) => { FrameItem::Shape(shape, _) => {
render_shape(canvas, ts, mask, shape); render_shape(canvas, ts, mask, shape);
} }
Element::Image(image, size, _) => { FrameItem::Image(image, size, _) => {
render_image(canvas, ts, mask, image, *size); render_image(canvas, ts, mask, image, *size);
} }
Element::Meta(meta, _) => match meta { FrameItem::Meta(meta, _) => match meta {
Meta::Link(_) => {} Meta::Link(_) => {}
Meta::Node(_) => {} Meta::Elem(_) => {}
Meta::Hide => {} Meta::Hide => {}
}, },
} }
@ -72,7 +72,7 @@ fn render_group(
canvas: &mut sk::Pixmap, canvas: &mut sk::Pixmap,
ts: sk::Transform, ts: sk::Transform,
mask: Option<&sk::ClipMask>, mask: Option<&sk::ClipMask>,
group: &Group, group: &GroupItem,
) { ) {
let ts = ts.pre_concat(group.transform.into()); let ts = ts.pre_concat(group.transform.into());
@ -114,7 +114,7 @@ fn render_text(
canvas: &mut sk::Pixmap, canvas: &mut sk::Pixmap,
ts: sk::Transform, ts: sk::Transform,
mask: Option<&sk::ClipMask>, mask: Option<&sk::ClipMask>,
text: &Text, text: &TextItem,
) { ) {
let mut x = 0.0; let mut x = 0.0;
for glyph in &text.glyphs { for glyph in &text.glyphs {
@ -135,7 +135,7 @@ fn render_svg_glyph(
canvas: &mut sk::Pixmap, canvas: &mut sk::Pixmap,
ts: sk::Transform, ts: sk::Transform,
_: Option<&sk::ClipMask>, _: Option<&sk::ClipMask>,
text: &Text, text: &TextItem,
id: GlyphId, id: GlyphId,
) -> Option<()> { ) -> Option<()> {
let mut data = text.font.ttf().glyph_svg_image(id)?; let mut data = text.font.ttf().glyph_svg_image(id)?;
@ -184,7 +184,7 @@ fn render_bitmap_glyph(
canvas: &mut sk::Pixmap, canvas: &mut sk::Pixmap,
ts: sk::Transform, ts: sk::Transform,
mask: Option<&sk::ClipMask>, mask: Option<&sk::ClipMask>,
text: &Text, text: &TextItem,
id: GlyphId, id: GlyphId,
) -> Option<()> { ) -> Option<()> {
let size = text.size.to_f32(); let size = text.size.to_f32();
@ -208,7 +208,7 @@ fn render_outline_glyph(
canvas: &mut sk::Pixmap, canvas: &mut sk::Pixmap,
ts: sk::Transform, ts: sk::Transform,
mask: Option<&sk::ClipMask>, mask: Option<&sk::ClipMask>,
text: &Text, text: &TextItem,
id: GlyphId, id: GlyphId,
) -> Option<()> { ) -> Option<()> {
let ppem = text.size.to_f32() * ts.sy; 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(); let mut builder = sk::PathBuilder::new();
for elem in &path.0 { for elem in &path.0 {
match elem { match elem {
PathElement::MoveTo(p) => { PathItem::MoveTo(p) => {
builder.move_to(p.x.to_f32(), p.y.to_f32()); 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()); builder.line_to(p.x.to_f32(), p.y.to_f32());
} }
PathElement::CubicTo(p1, p2, p3) => { PathItem::CubicTo(p1, p2, p3) => {
builder.cubic_to( builder.cubic_to(
p1.x.to_f32(), p1.x.to_f32(),
p1.y.to_f32(), p1.y.to_f32(),
@ -342,7 +342,7 @@ fn convert_path(path: &geom::Path) -> Option<sk::Path> {
p3.y.to_f32(), p3.y.to_f32(),
); );
} }
PathElement::ClosePath => { PathItem::ClosePath => {
builder.close(); builder.close();
} }
}; };

View File

@ -236,7 +236,7 @@ impl FromStr for RgbaColor {
fn from_str(hex_str: &str) -> Result<Self, Self::Err> { fn from_str(hex_str: &str) -> Result<Self, Self::Err> {
let hex_str = hex_str.strip_prefix('#').unwrap_or(hex_str); let hex_str = hex_str.strip_prefix('#').unwrap_or(hex_str);
if hex_str.chars().any(|c| !c.is_ascii_hexdigit()) { 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(); let len = hex_str.len();
@ -244,7 +244,7 @@ impl FromStr for RgbaColor {
let short = len == 3 || len == 4; let short = len == 3 || len == 4;
let alpha = len == 4 || len == 8; let alpha = len == 4 || len == 8;
if !long && !short { if !long && !short {
return Err("string has wrong length"); return Err("color string has wrong length");
} }
let mut values: [u8; 4] = [u8::MAX; 4]; let mut values: [u8; 4] = [u8::MAX; 4];
@ -406,10 +406,10 @@ mod tests {
assert_eq!(RgbaColor::from_str(hex), Err(message)); assert_eq!(RgbaColor::from_str(hex), Err(message));
} }
test("a5", "string has wrong length"); test("a5", "color string has wrong length");
test("12345", "string has wrong length"); test("12345", "color string has wrong length");
test("f075ff011", "string has wrong length"); test("f075ff011", "color string has wrong length");
test("hmmm", "string contains non-hexadecimal letters"); test("hmmm", "color string contains non-hexadecimal letters");
test("14B2AH", "string contains non-hexadecimal letters"); test("14B2AH", "color string contains non-hexadecimal letters");
} }
} }

View File

@ -2,11 +2,11 @@ use super::*;
/// A bezier path. /// A bezier path.
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] #[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)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum PathElement { pub enum PathItem {
MoveTo(Point), MoveTo(Point),
LineTo(Point), LineTo(Point),
CubicTo(Point, Point, Point), CubicTo(Point, Point, Point),
@ -32,23 +32,23 @@ impl Path {
path path
} }
/// Push a [`MoveTo`](PathElement::MoveTo) element. /// Push a [`MoveTo`](PathItem::MoveTo) item.
pub fn move_to(&mut self, p: Point) { 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) { 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) { 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) { pub fn close_path(&mut self) {
self.0.push(PathElement::ClosePath); self.0.push(PathItem::ClosePath);
} }
} }

View File

@ -81,6 +81,11 @@ pub fn analyze_import(
} }
/// Find all labels and details for them. /// 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( pub fn analyze_labels(
world: &(dyn World + 'static), world: &(dyn World + 'static),
frames: &[Frame], frames: &[Frame],
@ -90,16 +95,16 @@ pub fn analyze_labels(
let items = &world.library().items; let items = &world.library().items;
// Labels in the document. // Labels in the document.
for node in introspector.all() { for elem in introspector.all() {
let Some(label) = node.label() else { continue }; let Some(label) = elem.label() else { continue };
let details = node let details = elem
.field("caption") .field("caption")
.or_else(|| node.field("body")) .or_else(|| elem.field("body"))
.and_then(|field| match field { .and_then(|field| match field {
Value::Content(content) => Some(content), Value::Content(content) => Some(content),
_ => None, _ => None,
}) })
.and_then(|content| (items.text_str)(content)); .and_then(|content| (items.text_str)(&content));
output.push((label.clone(), details)); output.push((label.clone(), details));
} }

View File

@ -373,7 +373,7 @@ fn field_access_completions(ctx: &mut CompletionContext, value: &Value) {
} }
Value::Content(content) => { Value::Content(content) => {
for (name, value) in content.fields() { 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) => { Value::Dict(dict) => {
@ -509,7 +509,7 @@ fn set_rule_completions(ctx: &mut CompletionContext) {
fn show_rule_selector_completions(ctx: &mut CompletionContext) { fn show_rule_selector_completions(ctx: &mut CompletionContext) {
ctx.scope_completions( ctx.scope_completions(
false, 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("", ": "); ctx.enrich("", ": ");

View File

@ -1,6 +1,8 @@
use std::num::NonZeroUsize; 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::geom::{Geometry, Point, Size};
use crate::model::Introspector; use crate::model::Introspector;
use crate::syntax::{LinkedNode, Source, SourceId, Span, SyntaxKind}; use crate::syntax::{LinkedNode, Source, SourceId, Span, SyntaxKind};
@ -11,8 +13,10 @@ use crate::World;
pub enum Jump { pub enum Jump {
/// Jump to a position in a source file. /// Jump to a position in a source file.
Source(SourceId, usize), Source(SourceId, usize),
/// Jump to position in the output or to an external URL. /// Jump to an external URL.
Dest(Destination), Url(EcoString),
/// Jump to a point on a page.
Position(Position),
} }
impl Jump { impl Jump {
@ -32,20 +36,27 @@ pub fn jump_from_click(
) -> Option<Jump> { ) -> Option<Jump> {
let mut introspector = None; let mut introspector = None;
// Prefer metadata. // Try to find a link first.
for (pos, element) in frame.elements() { for (pos, item) in frame.items() {
if let Element::Meta(Meta::Link(link), size) = element { if let FrameItem::Meta(Meta::Link(dest), size) = item {
if is_in_rect(*pos, *size, click) { if is_in_rect(*pos, *size, click) {
return Some(Jump::Dest(link.resolve(|| { return Some(match dest {
introspector.get_or_insert_with(|| Introspector::new(frames)) 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() { // If there's no link, search for a jump target.
match element { for (mut pos, item) in frame.items().rev() {
Element::Group(group) => { match item {
FrameItem::Group(group) => {
// TODO: Handle transformation. // TODO: Handle transformation.
if let Some(span) = if let Some(span) =
jump_from_click(world, frames, &group.frame, click - pos) 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 { for glyph in &text.glyphs {
if glyph.span.is_detached() { if glyph.span.is_detached() {
continue; 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 }; let Geometry::Rect(size) = shape.geometry else { continue };
if is_in_rect(pos, size, click) { if is_in_rect(pos, size, click) {
return Some(Jump::from_span(world, *span)); 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)); return Some(Jump::from_span(world, *span));
} }
@ -108,7 +119,7 @@ pub fn jump_from_cursor(
frames: &[Frame], frames: &[Frame],
source: &Source, source: &Source,
cursor: usize, cursor: usize,
) -> Option<Location> { ) -> Option<Position> {
let node = LinkedNode::new(source.root()).leaf_at(cursor)?; let node = LinkedNode::new(source.root()).leaf_at(cursor)?;
if node.kind() != SyntaxKind::Text { if node.kind() != SyntaxKind::Text {
return None; return None;
@ -117,7 +128,10 @@ pub fn jump_from_cursor(
let span = node.span(); let span = node.span();
for (i, frame) in frames.iter().enumerate() { for (i, frame) in frames.iter().enumerate() {
if let Some(pos) = find_in_frame(frame, span) { 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. /// Find the position of a span in a frame.
fn find_in_frame(frame: &Frame, span: Span) -> Option<Point> { fn find_in_frame(frame: &Frame, span: Span) -> Option<Point> {
for (mut pos, element) in frame.elements() { for (mut pos, item) in frame.items() {
if let Element::Group(group) = element { if let FrameItem::Group(group) = item {
// TODO: Handle transformation. // TODO: Handle transformation.
if let Some(point) = find_in_frame(&group.frame, span) { if let Some(point) = find_in_frame(&group.frame, span) {
return Some(point + pos); return Some(point + pos);
} }
} }
if let Element::Text(text) = element { if let FrameItem::Text(text) = item {
for glyph in &text.glyphs { for glyph in &text.glyphs {
if glyph.span == span { if glyph.span == span {
return Some(pos); return Some(pos);

View File

@ -9,12 +9,12 @@
//! The next step is to [evaluate] the markup. This produces a [module], //! 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 //! consisting of a scope of values that were exported by the code and
//! [content], a hierarchical, styled representation of what was written in //! [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 //! order-independent and thus much better suited for further processing than
//! the raw markup. //! the raw markup.
//! - **Typesetting:** //! - **Typesetting:**
//! Next, the content is [typeset] into a [document] containing one [frame] //! 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:** //! - **Exporting:**
//! These frames can finally be exported into an output format (currently //! These frames can finally be exported into an output format (currently
//! supported are [PDF] and [raster images]). //! supported are [PDF] and [raster images]).

View File

@ -1,160 +1,144 @@
use std::any::TypeId; use std::any::TypeId;
use std::fmt::{self, Debug, Formatter, Write}; use std::fmt::{self, Debug, Formatter, Write};
use std::hash::{Hash, Hasher}; use std::iter::Sum;
use std::iter::{self, Sum}; use std::ops::{Add, AddAssign};
use std::ops::{Add, AddAssign, Deref};
use ecow::{eco_format, EcoString, EcoVec}; use ecow::{eco_format, EcoString, EcoVec};
use once_cell::sync::Lazy;
use super::{ use super::{
node, Behave, Behaviour, Fold, Guard, Locatable, Recipe, StableId, Style, StyleMap, element, Behave, Behaviour, ElemFunc, Element, Fold, Guard, Label, Locatable,
Synthesize, Location, Recipe, Style, Styles, Synthesize,
}; };
use crate::diag::{SourceResult, StrResult}; use crate::diag::{SourceResult, StrResult};
use crate::doc::Meta; use crate::doc::Meta;
use crate::eval::{ use crate::eval::{Cast, Str, Value, Vm};
cast_from_value, cast_to_value, Args, Cast, Func, FuncInfo, Str, Value, Vm,
};
use crate::syntax::Span; use crate::syntax::Span;
use crate::util::pretty_array_like; use crate::util::pretty_array_like;
/// Composable representation of styled content. /// Composable representation of styled content.
#[derive(Clone, Hash)] #[derive(Clone, Hash)]
pub struct Content { pub struct Content {
id: NodeId, func: ElemFunc,
span: Span, attrs: EcoVec<Attr>,
fields: EcoVec<(EcoString, Value)>,
modifiers: EcoVec<Modifier>,
} }
/// Modifiers that can be attached to content. /// Attributes that can be attached to content.
#[derive(Debug, Clone, PartialEq, Hash)] #[derive(Debug, Clone, PartialEq, Hash)]
enum Modifier { enum Attr {
Span(Span),
Field(EcoString),
Value(Value),
Child(Content),
Styles(Styles),
Prepared, Prepared,
Guard(Guard), Guard(Guard),
Id(StableId), Location(Location),
} }
impl Content { impl Content {
/// Create a content of the given node kind. /// Create an empty element.
pub fn new(id: NodeId) -> Self { pub fn new(func: ElemFunc) -> Self {
Self { Self { func, attrs: EcoVec::new() }
id,
span: Span::detached(),
fields: EcoVec::new(),
modifiers: EcoVec::new(),
}
} }
/// Create empty content. /// Create empty content.
pub fn empty() -> Self { pub fn empty() -> Self {
SequenceNode::new(vec![]).pack() Self::new(SequenceElem::func())
} }
/// Create a new sequence node from multiples nodes. /// Create a new sequence element from multiples elements.
pub fn sequence(seq: Vec<Self>) -> Self { pub fn sequence(iter: impl IntoIterator<Item = Self>) -> Self {
match seq.as_slice() { let mut iter = iter.into_iter();
[_] => seq.into_iter().next().unwrap(), let Some(first) = iter.next() else { return Self::empty() };
_ => SequenceNode::new(seq).pack(), 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. /// The element function of the contained content.
pub fn id(&self) -> NodeId { pub fn func(&self) -> ElemFunc {
self.id self.func
} }
/// Whether the content is empty. /// Whether the content is an empty sequence.
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.to::<SequenceNode>() self.is::<SequenceElem>() && self.attrs.is_empty()
.map_or(false, |seq| seq.children().is_empty())
} }
/// Whether the contained node is of type `T`. /// Whether the contained element is of type `T`.
pub fn is<T>(&self) -> bool pub fn is<T: Element>(&self) -> bool {
where self.func == T::func()
T: Node + 'static,
{
self.id == NodeId::of::<T>()
} }
/// Cast to `T` if the contained node is of type `T`. /// Cast to `T` if the contained element is of type `T`.
pub fn to<T>(&self) -> Option<&T> pub fn to<T: Element>(&self) -> Option<&T> {
where T::unpack(self)
T: Node + 'static,
{
self.is::<T>().then(|| unsafe { std::mem::transmute(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 pub fn can<C>(&self) -> bool
where where
C: ?Sized + 'static, 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> pub fn with<C>(&self) -> Option<&C>
where where
C: ?Sized + 'static, 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 (); let data = self as *const Self as *const ();
Some(unsafe { &*crate::util::fat::from_raw_parts(data, vtable) }) 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> pub fn with_mut<C>(&mut self) -> Option<&mut C>
where where
C: ?Sized + 'static, 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 (); let data = self as *mut Self as *mut ();
Some(unsafe { &mut *crate::util::fat::from_raw_parts_mut(data, vtable) }) 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 { 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. /// Attach a span to the content if it doesn't already have one.
pub fn spanned(mut self, span: Span) -> Self { pub fn spanned(mut self, span: Span) -> Self {
if self.span.is_detached() { if self.span().is_detached() {
self.span = span; self.attrs.push(Attr::Span(span));
} }
self 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. /// Attach a field to the content.
pub fn with_field( pub fn with_field(
mut self, mut self,
@ -168,26 +152,97 @@ impl Content {
/// Attach a field to the content. /// Attach a field to the content.
pub fn push_field(&mut self, name: impl Into<EcoString>, value: impl Into<Value>) { pub fn push_field(&mut self, name: impl Into<EcoString>, value: impl Into<Value>) {
let name = name.into(); let name = name.into();
if let Some(i) = self.fields.iter().position(|(field, _)| *field == name) { if let Some(i) = self.attrs.iter().position(|attr| match attr {
self.fields.make_mut()[i] = (name, value.into()); Attr::Field(field) => *field == name,
_ => false,
}) {
self.attrs.make_mut()[i + 1] = Attr::Value(value.into());
} else { } else {
self.fields.push((name, value.into())); self.attrs.push(Attr::Field(name));
self.attrs.push(Attr::Value(value.into()));
} }
} }
/// Access a field on the content.
pub fn field(&self, name: &str) -> Option<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.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. /// Whether the content has the specified field.
pub fn has(&self, field: &str) -> bool { pub fn has(&self, field: &str) -> bool {
self.field(field).is_some() self.field(field).is_some()
} }
/// Borrow the value of the given field. /// 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)) self.field(field).ok_or_else(|| missing_field(field))
} }
/// The content's label. /// The content's label.
pub fn label(&self) -> Option<&Label> { pub fn label(&self) -> Option<&Label> {
match self.field("label")? { match self.field_ref("label")? {
Value::Label(label) => Some(label), Value::Label(label) => Some(label),
_ => None, _ => None,
} }
@ -199,20 +254,33 @@ impl Content {
} }
/// Style this content with a style entry. /// Style this content with a style entry.
pub fn styled(self, style: impl Into<Style>) -> Self { pub fn styled(mut self, style: impl Into<Style>) -> Self {
self.styled_with_map(style.into().into()) 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. /// 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() { 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 self
} else if let Some(styled) = self.to::<StyledNode>() {
let mut map = styled.styles();
map.apply(styles);
StyledNode::new(map, styled.body()).pack()
} else { } 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() { if recipe.selector.is_none() {
recipe.apply_vm(vm, self) recipe.apply_vm(vm, self)
} else { } else {
Ok(self.styled(Style::Recipe(recipe))) Ok(self.styled(recipe))
} }
} }
@ -232,35 +300,34 @@ impl Content {
Ok(Self::sequence(vec![self.clone(); count])) Ok(Self::sequence(vec![self.clone(); count]))
} }
}
#[doc(hidden)]
impl Content {
/// Disable a show rule recipe. /// Disable a show rule recipe.
pub fn guarded(mut self, id: Guard) -> Self { pub fn guarded(mut self, guard: Guard) -> Self {
self.modifiers.push(Modifier::Guard(id)); self.attrs.push(Attr::Guard(guard));
self 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. /// Check whether a show rule recipe is disabled.
pub(super) fn is_guarded(&self, id: Guard) -> bool { pub fn is_guarded(&self, guard: Guard) -> bool {
self.modifiers.contains(&Modifier::Guard(id)) self.attrs.contains(&Attr::Guard(guard))
} }
/// Whether this node was prepared. /// 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 content has already been prepared.
pub fn is_prepared(&self) -> bool { 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 { pub fn needs_preparation(&self) -> bool {
(self.can::<dyn Locatable>() (self.can::<dyn Locatable>()
|| self.can::<dyn Synthesize>() || self.can::<dyn Synthesize>()
@ -268,37 +335,23 @@ impl Content {
&& !self.is_prepared() && !self.is_prepared()
} }
/// Mark this content as prepared. /// This content's location in the document flow.
pub fn mark_prepared(&mut self) { pub fn location(&self) -> Option<Location> {
self.modifiers.push(Modifier::Prepared); self.attrs.iter().find_map(|modifier| match modifier {
} Attr::Location(location) => Some(*location),
/// 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),
_ => None, _ => None,
}) })
} }
/// Copy the modifiers from another piece of content. /// Attach a location to this content.
pub(super) fn copy_modifiers(&mut self, from: &Content) { pub fn set_location(&mut self, location: Location) {
self.span = from.span; self.attrs.push(Attr::Location(location));
self.modifiers = from.modifiers.clone();
if let Some(label) = from.label() {
self.push_field("label", label.clone())
}
} }
} }
impl Debug for Content { impl Debug for Content {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { 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) { if let Some(text) = item!(text_str)(self) {
f.write_char('[')?; f.write_char('[')?;
f.write_str(&text)?; f.write_str(&text)?;
@ -308,12 +361,15 @@ impl Debug for Content {
return f.write_str("[ ]"); return f.write_str("[ ]");
} }
let pieces: Vec<_> = self let mut pieces: Vec<_> = self
.fields .fields()
.iter()
.map(|(name, value)| eco_format!("{name}: {value:?}")) .map(|(name, value)| eco_format!("{name}: {value:?}"))
.collect(); .collect();
if self.is::<StyledElem>() {
pieces.push(EcoString::from(".."));
}
f.write_str(name)?; f.write_str(name)?;
f.write_str(&pretty_array_like(&pieces, false)) f.write_str(&pretty_array_like(&pieces, false))
} }
@ -327,31 +383,36 @@ impl Default for Content {
impl PartialEq for Content { impl PartialEq for Content {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.id == other.id if let (Some(left), Some(right)) = (self.to_sequence(), other.to_sequence()) {
&& self.fields.len() == other.fields.len() left.eq(right)
&& self } else if let (Some(left), Some(right)) = (self.to_styled(), other.to_styled()) {
.fields left == right
.iter() } else {
.all(|(name, value)| other.field(name) == Some(value)) self.func == other.func && self.fields_ref().eq(other.fields_ref())
}
} }
} }
impl Add for Content { impl Add for Content {
type Output = Self; type Output = Self;
fn add(self, rhs: Self) -> Self::Output { fn add(self, mut rhs: Self) -> Self::Output {
let lhs = self; let mut lhs = self;
let seq = match (lhs.to::<SequenceNode>(), rhs.to::<SequenceNode>()) { match (lhs.is::<SequenceElem>(), rhs.is::<SequenceElem>()) {
(Some(lhs), Some(rhs)) => { (true, true) => {
lhs.children().into_iter().chain(rhs.children()).collect() lhs.attrs.extend(rhs.attrs);
lhs
} }
(Some(lhs), None) => { (true, false) => {
lhs.children().into_iter().chain(iter::once(rhs)).collect() lhs.attrs.push(Attr::Child(rhs));
lhs
} }
(None, Some(rhs)) => iter::once(lhs).chain(rhs.children()).collect(), (false, true) => {
(None, None) => vec![lhs, rhs], rhs.attrs.insert(0, Attr::Child(lhs));
}; rhs
SequenceNode::new(seq).pack() }
(false, false) => Self::sequence([lhs, rhs]),
}
} }
} }
@ -363,154 +424,77 @@ impl AddAssign for Content {
impl Sum for Content { impl Sum for Content {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self { fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
Self::sequence(iter.collect()) Self::sequence(iter)
} }
} }
/// A constructable, stylable content node. impl Attr {
pub trait Node: Construct + Set + Sized + 'static { fn child(&self) -> Option<&Content> {
/// The node's ID. match self {
fn id() -> NodeId; Self::Child(child) => Some(child),
_ => None,
}
}
/// Pack a node into type-erased content. fn styles(&self) -> Option<&Styles> {
fn pack(self) -> Content; match self {
} Self::Styles(styles) => Some(styles),
_ => None,
}
}
/// A unique identifier for a node. fn styles_mut(&mut self) -> Option<&mut Styles> {
#[derive(Copy, Clone)] match self {
pub struct NodeId(pub &'static NodeMeta); Self::Styles(styles) => Some(styles),
_ => None,
}
}
impl NodeId { fn field(&self) -> Option<&EcoString> {
/// Get the id of a node. match self {
pub fn of<T: Node>() -> Self { Self::Field(field) => Some(field),
T::id() _ => None,
}
}
fn value(&self) -> Option<&Value> {
match self {
Self::Value(value) => Some(value),
_ => None,
}
}
fn span(&self) -> Option<Span> {
match self {
Self::Span(span) => Some(*span),
_ => None,
}
} }
} }
impl Debug for NodeId {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(self.name)
}
}
impl Hash for NodeId {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_usize(self.0 as *const _ as usize);
}
}
impl Eq for NodeId {}
impl PartialEq for NodeId {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(self.0, other.0)
}
}
impl Deref for NodeId {
type Target = NodeMeta;
fn deref(&self) -> &Self::Target {
self.0
}
}
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)
}
}
/// 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 /// Display: Sequence
/// Category: special /// Category: special
#[node] #[element]
pub struct SequenceNode { struct SequenceElem {}
#[variadic]
pub children: Vec<Content>,
}
/// A node with applied styles. /// Display: Sequence
///
/// Display: Styled
/// Category: special /// Category: special
#[node] #[element]
pub struct StyledNode { struct StyledElem {}
/// The styles.
#[required]
pub styles: StyleMap,
/// The styled content. /// Hosts metadata and ensures metadata is produced even for empty elements.
#[required]
pub body: Content,
}
cast_from_value! {
StyleMap: "style map",
}
/// Host for metadata.
/// ///
/// Display: Meta /// Display: Meta
/// Category: special /// Category: special
#[node(Behave)] #[element(Behave)]
pub struct MetaNode { pub struct MetaElem {
/// Metadata that should be attached to all elements affected by this style /// Metadata that should be attached to all elements affected by this style
/// property. /// property.
#[fold] #[fold]
pub data: Vec<Meta>, pub data: Vec<Meta>,
} }
impl Behave for MetaNode { impl Behave for MetaElem {
fn behaviour(&self) -> Behaviour { fn behaviour(&self) -> Behaviour {
Behaviour::Ignorant Behaviour::Ignorant
} }

145
src/model/element.rs Normal file
View File

@ -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 {}

170
src/model/introspect.rs Normal file
View File

@ -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() })
}
}

View File

@ -1,14 +1,87 @@
//! The document model. //! The document model.
#[macro_use]
mod styles;
mod content; mod content;
mod element;
mod introspect;
mod realize; mod realize;
mod typeset; mod styles;
pub use self::content::*; pub use self::content::*;
pub use self::element::*;
pub use self::introspect::*;
pub use self::realize::*; pub use self::realize::*;
pub use self::styles::*; 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,
}
}
}

View File

@ -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::diag::SourceResult;
use crate::doc::Meta; use crate::doc::Meta;
use crate::util::hash128; use crate::util::hash128;
@ -35,28 +35,28 @@ pub fn realize(
) -> SourceResult<Option<Content>> { ) -> SourceResult<Option<Content>> {
// Pre-process. // Pre-process.
if target.needs_preparation() { if target.needs_preparation() {
let mut node = target.clone(); let mut elem = target.clone();
if target.can::<dyn Locatable>() || target.label().is_some() { if target.can::<dyn Locatable>() || target.label().is_some() {
let id = vt.provider.identify(hash128(target)); let location = vt.provider.locate(hash128(target));
node.set_stable_id(id); elem.set_location(location);
} }
if let Some(node) = node.with_mut::<dyn Synthesize>() { if let Some(elem) = elem.with_mut::<dyn Synthesize>() {
node.synthesize(vt, styles); elem.synthesize(vt, styles);
} }
node.mark_prepared(); elem.mark_prepared();
if node.stable_id().is_some() { if elem.location().is_some() {
let span = node.span(); let span = elem.span();
let meta = Meta::Node(node.clone()); let meta = Meta::Elem(elem.clone());
return Ok(Some( return Ok(Some(
(node + MetaNode::new().pack().spanned(span)) (elem + MetaElem::new().pack().spanned(span))
.styled(MetaNode::set_data(vec![meta])), .styled(MetaElem::set_data(vec![meta])),
)); ));
} }
return Ok(Some(node)); return Ok(Some(elem));
} }
// Find out how many recipes there are. // Find out how many recipes there are.
@ -77,17 +77,17 @@ pub fn realize(
// Realize if there was no matching recipe. // Realize if there was no matching recipe.
if let Some(showable) = target.with::<dyn Show>() { 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) { if realized.is_none() && !target.is_guarded(guard) {
realized = Some(showable.show(vt, styles)?); realized = Some(showable.show(vt, styles)?);
} }
} }
// Finalize only if this is the first application for this node. // Finalize only if this is the first application for this element.
if let Some(node) = target.with::<dyn Finalize>() { if let Some(elem) = target.with::<dyn Finalize>() {
if target.is_pristine() { if target.is_pristine() {
if let Some(already) = realized { 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, guard: Guard,
) -> SourceResult<Option<Content>> { ) -> SourceResult<Option<Content>> {
match &recipe.selector { match &recipe.selector {
Some(Selector::Node(id, _)) => { Some(Selector::Elem(element, _)) => {
if target.id() != *id { if target.func() != *element {
return Ok(None); return Ok(None);
} }
@ -124,22 +124,17 @@ fn try_apply(
return Ok(None); return Ok(None);
}; };
let make = |s| { let make = |s: &str| target.clone().with_field("text", s);
let mut content = item!(text)(s);
content.copy_modifiers(target);
content
};
let mut result = vec![]; let mut result = vec![];
let mut cursor = 0; let mut cursor = 0;
for m in regex.find_iter(&text) { for m in regex.find_iter(&text) {
let start = m.start(); let start = m.start();
if cursor < 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)?; let transformed = recipe.apply_vt(vt, piece)?;
result.push(transformed); result.push(transformed);
cursor = m.end(); cursor = m.end();
@ -150,7 +145,7 @@ fn try_apply(
} }
if cursor < text.len() { if cursor < text.len() {
result.push(make(text[cursor..].into())); result.push(make(&text[cursor..]));
} }
Ok(Some(Content::sequence(result))) 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 {} 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 { 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); fn synthesize(&mut self, vt: &Vt, styles: StyleChain);
} }
/// The base recipe for a node. /// The base recipe for an element.
pub trait Show { 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>; 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 { 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 /// 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; 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 { pub trait Behave {
/// The node's interaction behaviour. /// The element's interaction behaviour.
fn behaviour(&self) -> Behaviour; fn behaviour(&self) -> Behaviour;
/// Whether this weak node is larger than a previous one and thus picked as /// Whether this weak element is larger than a previous one and thus picked
/// the maximum when the levels are the same. /// as the maximum when the levels are the same.
#[allow(unused_variables)] #[allow(unused_variables)]
fn larger(&self, prev: &Content) -> bool { fn larger(&self, prev: &Content) -> bool {
false 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)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Behaviour { pub enum Behaviour {
/// A weak node which only survives when a supportive node is before and /// A weak element which only survives when a supportive element is before
/// after it. Furthermore, per consecutive run of weak nodes, only one /// and after it. Furthermore, per consecutive run of weak elements, only
/// survives: The one with the lowest weakness level (or the larger one if /// one survives: The one with the lowest weakness level (or the larger one
/// there is a tie). /// if there is a tie).
Weak(usize), 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, Supportive,
/// A node that destroys adjacent weak nodes. /// An element that destroys adjacent weak elements.
Destructive, 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. /// same effect as if it didn't exist.
Ignorant, Ignorant,
} }
@ -221,6 +217,6 @@ pub enum Behaviour {
pub enum Guard { pub enum Guard {
/// The nth recipe from the top of the chain. /// The nth recipe from the top of the chain.
Nth(usize), Nth(usize),
/// The [base recipe](Show) for a kind of node. /// The [base recipe](Show) for a kind of element.
Base(NodeId), Base(ElemFunc),
} }

View File

@ -1,25 +1,26 @@
use std::fmt::{self, Debug, Formatter, Write}; use std::fmt::{self, Debug, Formatter, Write};
use std::iter; 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::diag::{SourceResult, Trace, Tracepoint};
use crate::eval::{cast_from_value, Args, Cast, Dict, Func, Regex, Value, Vm}; use crate::eval::{cast_from_value, Args, Cast, Dict, Func, Regex, Value, Vm};
use crate::syntax::Span; use crate::syntax::Span;
use crate::util::pretty_array_like; use crate::util::pretty_array_like;
/// A map of style properties. /// A list of style properties.
#[derive(Default, Clone, Hash)] #[derive(Default, PartialEq, Clone, Hash)]
pub struct StyleMap(Vec<Style>); pub struct Styles(EcoVec<Style>);
impl StyleMap { impl Styles {
/// Create a new, empty style map. /// Create a new, empty style list.
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
} }
/// Whether this map contains no styles. /// Whether this contains no styles.
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.0.is_empty() self.0.is_empty()
} }
@ -39,13 +40,25 @@ impl StyleMap {
} }
/// Apply outer styles. Like [`chain`](StyleChain::chain), but in-place. /// Apply outer styles. Like [`chain`](StyleChain::chain), but in-place.
pub fn apply(&mut self, outer: Self) { pub fn apply(&mut self, mut outer: Self) {
self.0.splice(0..0, outer.0.iter().cloned()); 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. /// Add an origin span to all contained properties.
pub fn spanned(mut self, span: Span) -> Self { 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 { if let Style::Property(property) = entry {
property.span = Some(span); property.span = Some(span);
} }
@ -53,37 +66,31 @@ impl StyleMap {
self self
} }
/// Returns `Some(_)` with an optional span if this map contains styles for /// Returns `Some(_)` with an optional span if this list contains
/// the given `node`. /// styles for the given element.
pub fn interruption<T: Node>(&self) -> Option<Option<Span>> { pub fn interruption<T: Element>(&self) -> Option<Option<Span>> {
let node = NodeId::of::<T>(); let func = T::func();
self.0.iter().find_map(|entry| match entry { self.0.iter().find_map(|entry| match entry {
Style::Property(property) => property.is_of(node).then(|| property.span), Style::Property(property) => property.is_of(func).then(|| property.span),
Style::Recipe(recipe) => recipe.is_of(node).then(|| Some(recipe.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 { fn from(entry: Style) -> Self {
Self(vec![entry]) Self(eco_vec![entry])
} }
} }
impl PartialEq for StyleMap { impl Debug for Styles {
fn eq(&self, other: &Self) -> bool {
crate::util::hash128(self) == crate::util::hash128(other)
}
}
impl Debug for StyleMap {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad("..") f.pad("..")
} }
} }
/// A single style property or recipe. /// A single style property or recipe.
#[derive(Clone, Hash)] #[derive(Clone, PartialEq, Hash)]
pub enum Style { pub enum Style {
/// A style property originating from a set rule or constructor. /// A style property originating from a set rule or constructor.
Property(Property), 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. /// A style property originating from a set rule or constructor.
#[derive(Clone, Hash)] #[derive(Clone, PartialEq, Hash)]
pub struct Property { pub struct Property {
/// The id of the node the property belongs to. /// The element the property belongs to.
node: NodeId, element: ElemFunc,
/// The property's name. /// The property's name.
name: EcoString, name: EcoString,
/// The property's value. /// The property's value.
@ -139,44 +152,44 @@ pub struct Property {
impl Property { impl Property {
/// Create a new property from a key-value pair. /// Create a new property from a key-value pair.
pub fn new(node: NodeId, name: EcoString, value: Value) -> Self { pub fn new(element: ElemFunc, name: EcoString, value: Value) -> Self {
Self { node, name, value, span: None } Self { element, name, value, span: None }
} }
/// Whether this property is the given one. /// Whether this property is the given one.
pub fn is(&self, node: NodeId, name: &str) -> bool { pub fn is(&self, element: ElemFunc, name: &str) -> bool {
self.node == node && self.name == name self.element == element && self.name == name
} }
/// Whether this property belongs to the node with the given id. /// Whether this property belongs to the given element.
pub fn is_of(&self, node: NodeId) -> bool { pub fn is_of(&self, element: ElemFunc) -> bool {
self.node == node self.element == element
} }
} }
impl Debug for Property { impl Debug for Property {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { 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(()) Ok(())
} }
} }
/// A show rule recipe. /// A show rule recipe.
#[derive(Clone, Hash)] #[derive(Clone, PartialEq, Hash)]
pub struct Recipe { pub struct Recipe {
/// The span errors are reported with. /// The span errors are reported with.
pub span: Span, pub span: Span,
/// Determines whether the recipe applies to a node. /// Determines whether the recipe applies to an element.
pub selector: Option<Selector>, pub selector: Option<Selector>,
/// The transformation to perform on the match. /// The transformation to perform on the match.
pub transform: Transform, pub transform: Transform,
} }
impl Recipe { impl Recipe {
/// Whether this recipe is for the given node. /// Whether this recipe is for the given type of element.
pub fn is_of(&self, node: NodeId) -> bool { pub fn is_of(&self, element: ElemFunc) -> bool {
match self.selector { match self.selector {
Some(Selector::Node(id, _)) => id == node, Some(Selector::Elem(own, _)) => own == element,
_ => false, _ => false,
} }
} }
@ -197,7 +210,7 @@ impl Recipe {
let mut result = func.call_vm(vm, args); let mut result = func.call_vm(vm, args);
// For selector-less show rules, a tracepoint makes no sense. // For selector-less show rules, a tracepoint makes no sense.
if self.selector.is_some() { 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()); result = result.trace(vm.world(), point, content.span());
} }
Ok(result?.display()) Ok(result?.display())
@ -213,7 +226,7 @@ impl Recipe {
Transform::Func(func) => { Transform::Func(func) => {
let mut result = func.call_vt(vt, [Value::Content(content.clone())]); let mut result = func.call_vt(vt, [Value::Content(content.clone())]);
if self.selector.is_some() { 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()); result = result.trace(vt.world, point, content.span());
} }
Ok(result?.display()) Ok(result?.display())
@ -238,25 +251,20 @@ impl Debug for Recipe {
/// A selector in a show rule. /// A selector in a show rule.
#[derive(Clone, PartialEq, Hash)] #[derive(Clone, PartialEq, Hash)]
pub enum Selector { 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. /// dictionary match.
Node(NodeId, Option<Dict>), Elem(ElemFunc, Option<Dict>),
/// Matches nodes with a specific label. /// Matches elements with a specific label.
Label(Label), Label(Label),
/// Matches text nodes through a regular expression. /// Matches text elements through a regular expression.
Regex(Regex), Regex(Regex),
/// Matches if any of the subselectors match. /// Matches if any of the subselectors match.
Any(EcoVec<Self>), Any(EcoVec<Self>),
} }
impl Selector { impl Selector {
/// Define a simple node selector.
pub fn node<T: Node>() -> Self {
Self::Node(NodeId::of::<T>(), None)
}
/// Define a simple text selector. /// Define a simple text selector.
pub fn text(text: &str) -> Self { pub fn text(text: &str) -> Self {
Self::Regex(Regex::new(&regex::escape(text)).unwrap()) Self::Regex(Regex::new(&regex::escape(text)).unwrap())
@ -265,16 +273,16 @@ impl Selector {
/// Whether the selector matches for the target. /// Whether the selector matches for the target.
pub fn matches(&self, target: &Content) -> bool { pub fn matches(&self, target: &Content) -> bool {
match self { match self {
Self::Node(id, dict) => { Self::Elem(element, dict) => {
target.id() == *id target.func() == *element
&& dict && dict
.iter() .iter()
.flat_map(|dict| 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::Label(label) => target.label() == Some(label),
Self::Regex(regex) => { 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)) && item!(text_str)(target).map_or(false, |text| regex.is_match(&text))
} }
Self::Any(selectors) => selectors.iter().any(|sel| sel.matches(target)), Self::Any(selectors) => selectors.iter().any(|sel| sel.matches(target)),
@ -285,8 +293,8 @@ impl Selector {
impl Debug for Selector { impl Debug for Selector {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self { match self {
Self::Node(node, dict) => { Self::Elem(elem, dict) => {
f.write_str(node.name)?; f.write_str(elem.name())?;
if let Some(dict) = dict { if let Some(dict) = dict {
f.write_str(".where")?; f.write_str(".where")?;
dict.fmt(f)?; dict.fmt(f)?;
@ -307,21 +315,24 @@ impl Debug for Selector {
cast_from_value! { cast_from_value! {
Selector: "selector", 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), label: Label => Self::Label(label),
func: Func => func.select(None)?, text: EcoString => Self::text(&text),
regex: Regex => Self::Regex(regex), regex: Regex => Self::Regex(regex),
} }
/// A show rule transformation that can be applied to a match. /// A show rule transformation that can be applied to a match.
#[derive(Clone, Hash)] #[derive(Clone, PartialEq, Hash)]
pub enum Transform { pub enum Transform {
/// Replacement content. /// Replacement content.
Content(Content), Content(Content),
/// A function to apply to the match. /// A function to apply to the match.
Func(Func), Func(Func),
/// Apply styles to the content. /// Apply styles to the content.
Style(StyleMap), Style(Styles),
} }
impl Debug for Transform { impl Debug for Transform {
@ -340,11 +351,11 @@ cast_from_value! {
func: Func => Self::Func(func), 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 /// A style chain allows to combine properties from multiple style lists in a
/// node hierarchy in a non-allocating way. Rather than eagerly merging the /// element hierarchy in a non-allocating way. Rather than eagerly merging the
/// maps, each access walks the hierarchy from the innermost to the outermost /// 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 /// map, trying to find a match and then folding it with matches further up the
/// chain. /// chain.
#[derive(Default, Clone, Copy, Hash)] #[derive(Default, Clone, Copy, Hash)]
@ -356,21 +367,21 @@ pub struct StyleChain<'a> {
} }
impl<'a> StyleChain<'a> { impl<'a> StyleChain<'a> {
/// Start a new style chain with a root map. /// Start a new style chain with root styles.
pub fn new(root: &'a StyleMap) -> Self { pub fn new(root: &'a Styles) -> Self {
Self { head: &root.0, tail: None } 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 /// The resulting style chain contains styles from `local` as well as
/// `self`. The ones from `map` take precedence over the ones from /// `self`. The ones from `local` take precedence over the ones from
/// `self`. For folded properties `map` contributes the inner value. /// `self`. For folded properties `local` contributes the inner value.
pub fn chain<'b>(&'b self, map: &'b StyleMap) -> StyleChain<'b> { pub fn chain<'b>(&'b self, local: &'b Styles) -> StyleChain<'b> {
if map.is_empty() { if local.is_empty() {
*self *self
} else { } 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. /// Cast the first value for the given property in the chain.
pub fn get<T: Cast>( pub fn get<T: Cast>(
self, self,
node: NodeId, func: ElemFunc,
name: &'a str, name: &'a str,
inherent: Option<Value>, inherent: Option<Value>,
default: impl Fn() -> T, default: impl Fn() -> T,
) -> T { ) -> T {
self.properties::<T>(node, name, inherent) self.properties::<T>(func, name, inherent)
.next() .next()
.unwrap_or_else(default) .unwrap_or_else(default)
} }
@ -398,18 +409,18 @@ impl<'a> StyleChain<'a> {
/// Cast the first value for the given property in the chain. /// Cast the first value for the given property in the chain.
pub fn get_resolve<T: Cast + Resolve>( pub fn get_resolve<T: Cast + Resolve>(
self, self,
node: NodeId, func: ElemFunc,
name: &'a str, name: &'a str,
inherent: Option<Value>, inherent: Option<Value>,
default: impl Fn() -> T, default: impl Fn() -> T,
) -> T::Output { ) -> 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. /// Cast the first value for the given property in the chain.
pub fn get_fold<T: Cast + Fold>( pub fn get_fold<T: Cast + Fold>(
self, self,
node: NodeId, func: ElemFunc,
name: &'a str, name: &'a str,
inherent: Option<Value>, inherent: Option<Value>,
default: impl Fn() -> T::Output, default: impl Fn() -> T::Output,
@ -424,13 +435,13 @@ impl<'a> StyleChain<'a> {
.map(|value| value.fold(next(values, styles, default))) .map(|value| value.fold(next(values, styles, default)))
.unwrap_or_else(|| 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. /// Cast the first value for the given property in the chain.
pub fn get_resolve_fold<T>( pub fn get_resolve_fold<T>(
self, self,
node: NodeId, func: ElemFunc,
name: &'a str, name: &'a str,
inherent: Option<Value>, inherent: Option<Value>,
default: impl Fn() -> <T::Output as Fold>::Output, 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))) .map(|value| value.resolve(styles).fold(next(values, styles, default)))
.unwrap_or_else(|| 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. /// 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. /// Iterate over all values for the given property in the chain.
pub fn properties<T: Cast + 'a>( pub fn properties<T: Cast + 'a>(
self, self,
node: NodeId, func: ElemFunc,
name: &'a str, name: &'a str,
inherent: Option<Value>, inherent: Option<Value>,
) -> impl Iterator<Item = T> + '_ { ) -> impl Iterator<Item = T> + '_ {
@ -473,21 +484,21 @@ impl<'a> StyleChain<'a> {
.chain( .chain(
self.entries() self.entries()
.filter_map(Style::property) .filter_map(Style::property)
.filter(move |property| property.is(node, name)) .filter(move |property| property.is(func, name))
.map(|property| property.value.clone()), .map(|property| property.value.clone()),
) )
.map(move |value| { .map(move |value| {
value value.cast().unwrap_or_else(|err| {
.cast() panic!("{} (for {}.{})", err, func.name(), name)
.unwrap_or_else(|err| panic!("{} (for {}.{})", err, node.name, name)) })
}) })
} }
/// Convert to a style map. /// Convert to a style map.
pub fn to_map(self) -> StyleMap { pub fn to_map(self) -> Styles {
let mut suffix = StyleMap::new(); let mut suffix = Styles::new();
for link in self.links() { for link in self.links() {
suffix.0.splice(0..0, link.iter().cloned()); suffix.apply_slice(link);
} }
suffix suffix
} }
@ -502,13 +513,13 @@ impl<'a> StyleChain<'a> {
Links(Some(self)) 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. /// chain.
fn suffix(self, len: usize) -> StyleMap { fn suffix(self, len: usize) -> Styles {
let mut suffix = StyleMap::new(); let mut suffix = Styles::new();
let take = self.links().count().saturating_sub(len); let take = self.links().count().saturating_sub(len);
for link in self.links().take(take) { for link in self.links().take(take) {
suffix.0.splice(0..0, link.iter().cloned()); suffix.apply_slice(link);
} }
suffix suffix
} }
@ -517,6 +528,16 @@ impl<'a> StyleChain<'a> {
fn pop(&mut self) { fn pop(&mut self) {
*self = self.tail.copied().unwrap_or_default(); *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<'_> { impl Debug for StyleChain<'_> {
@ -530,7 +551,7 @@ impl Debug for StyleChain<'_> {
impl PartialEq for StyleChain<'_> { impl PartialEq for StyleChain<'_> {
fn eq(&self, other: &Self) -> bool { 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)] #[derive(Clone, Hash)]
pub struct StyleVec<T> { pub struct StyleVec<T> {
items: Vec<T>, items: Vec<T>,
maps: Vec<(StyleMap, usize)>, styles: Vec<(Styles, usize)>,
} }
impl<T> StyleVec<T> { impl<T> StyleVec<T> {
@ -588,14 +609,14 @@ impl<T> StyleVec<T> {
self.items.len() self.items.len()
} }
/// Insert an element in the front. The element will share the style of the /// Insert an item in the front. The item will share the style of the
/// current first element. /// current first item.
/// ///
/// This method has no effect if the vector is empty. /// This method has no effect if the vector is empty.
pub fn push_front(&mut self, item: T) { pub fn push_front(&mut self, item: T) {
if !self.maps.is_empty() { if !self.styles.is_empty() {
self.items.insert(0, item); self.items.insert(0, item);
self.maps[0].1 += 1; self.styles[0].1 += 1;
} }
} }
@ -606,14 +627,14 @@ impl<T> StyleVec<T> {
{ {
StyleVec { StyleVec {
items: self.items.iter().map(f).collect(), 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. /// Iterate over references to the contained items and associated styles.
pub fn iter(&self) -> impl Iterator<Item = (&T, &StyleMap)> + '_ { pub fn iter(&self) -> impl Iterator<Item = (&T, &Styles)> + '_ {
self.items().zip( self.items().zip(
self.maps self.styles
.iter() .iter()
.flat_map(|(map, count)| iter::repeat(map).take(*count)), .flat_map(|(map, count)| iter::repeat(map).take(*count)),
) )
@ -624,13 +645,13 @@ impl<T> StyleVec<T> {
self.items.iter() self.items.iter()
} }
/// Iterate over the contained maps. Note that zipping this with `items()` /// Iterate over the contained style lists. Note that zipping this with
/// does not yield the same result as calling `iter()` because this method /// `items()` does not yield the same result as calling `iter()` because
/// only returns maps once that are shared by consecutive items. This method /// this method only returns lists once that are shared by consecutive
/// is designed for use cases where you want to check, for example, whether /// items. This method is designed for use cases where you want to check,
/// any of the maps fulfills a specific property. /// for example, whether any of the lists fulfills a specific property.
pub fn styles(&self) -> impl Iterator<Item = &StyleMap> { pub fn styles(&self) -> impl Iterator<Item = &Styles> {
self.maps.iter().map(|(map, _)| map) self.styles.iter().map(|(map, _)| map)
} }
} }
@ -639,35 +660,35 @@ impl StyleVec<Content> {
self.items self.items
.into_iter() .into_iter()
.zip( .zip(
self.maps self.styles
.iter() .iter()
.flat_map(|(map, count)| iter::repeat(map).take(*count)), .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() .collect()
} }
} }
impl<T> Default for StyleVec<T> { impl<T> Default for StyleVec<T> {
fn default() -> Self { fn default() -> Self {
Self { items: vec![], maps: vec![] } Self { items: vec![], styles: vec![] }
} }
} }
impl<T> FromIterator<T> for StyleVec<T> { impl<T> FromIterator<T> for StyleVec<T> {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self { fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let items: Vec<_> = iter.into_iter().collect(); let items: Vec<_> = iter.into_iter().collect();
let maps = vec![(StyleMap::new(), items.len())]; let styles = vec![(Styles::new(), items.len())];
Self { items, maps } Self { items, styles }
} }
} }
impl<T: Debug> Debug for StyleVec<T> { impl<T: Debug> Debug for StyleVec<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_list() f.debug_list()
.entries(self.iter().map(|(item, map)| { .entries(self.iter().map(|(item, styles)| {
crate::util::debug(|f| { crate::util::debug(|f| {
map.fmt(f)?; styles.fmt(f)?;
item.fmt(f) item.fmt(f)
}) })
})) }))
@ -708,7 +729,7 @@ impl<'a, T> StyleVecBuilder<'a, T> {
} }
/// Iterate over the contained items. /// Iterate over the contained items.
pub fn items(&self) -> std::slice::Iter<'_, T> { pub fn elems(&self) -> std::slice::Iter<'_, T> {
self.items.iter() self.items.iter()
} }
@ -743,13 +764,13 @@ impl<'a, T> StyleVecBuilder<'a, T> {
} }
} }
let maps = self let styles = self
.chains .chains
.into_iter() .into_iter()
.map(|(chain, count)| (chain.suffix(shared), count)) .map(|(chain, count)| (chain.suffix(shared), count))
.collect(); .collect();
(StyleVec { items: self.items, maps }, trunk) (StyleVec { items: self.items, styles }, trunk)
} }
} }

View File

@ -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() })
}
}

View File

@ -4,11 +4,7 @@
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[repr(u8)] #[repr(u8)]
pub enum SyntaxKind { pub enum SyntaxKind {
/// Markup of which all lines must have a minimal indentation. /// Markup.
///
/// 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. /// Plain text without markup.
Text, Text,

View File

@ -473,7 +473,7 @@ impl Lexer<'_> {
c if is_id_start(c) => self.ident(start), 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 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: /// In addition to what is specified in the [Unicode Standard][uax31], we allow:
/// - `_` as a starting character, /// - `_` as a starting character,
@ -651,13 +651,13 @@ pub fn is_ident(string: &str) -> bool {
/// Whether a character can start an identifier. /// Whether a character can start an identifier.
#[inline] #[inline]
pub fn is_id_start(c: char) -> bool { pub(crate) fn is_id_start(c: char) -> bool {
c.is_xid_start() || c == '_' c.is_xid_start() || c == '_'
} }
/// Whether a character can continue an identifier. /// Whether a character can continue an identifier.
#[inline] #[inline]
pub fn is_id_continue(c: char) -> bool { pub(crate) fn is_id_continue(c: char) -> bool {
c.is_xid_continue() || c == '_' || c == '-' c.is_xid_continue() || c == '_' || c == '-'
} }

View File

@ -14,6 +14,5 @@ pub use self::kind::*;
pub use self::lexer::*; pub use self::lexer::*;
pub use self::node::*; pub use self::node::*;
pub use self::parser::*; pub use self::parser::*;
pub use self::reparser::*;
pub use self::source::*; pub use self::source::*;
pub use self::span::*; pub use self::span::*;

View File

@ -9,7 +9,8 @@ use comemo::Prehashed;
use unscanny::Scanner; use unscanny::Scanner;
use super::ast::Markup; 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::diag::SourceResult;
use crate::util::{PathExt, StrExt}; use crate::util::{PathExt, StrExt};

View File

@ -40,7 +40,7 @@ pub fn hash128<T: Hash + ?Sized>(value: &T) -> u128 {
state.finish128().as_u128() state.finish128().as_u128()
} }
/// Extra methods for [`NonZeroUsize`]. /// An extra constant for [`NonZeroUsize`].
pub trait NonZeroExt { pub trait NonZeroExt {
/// The number `1`. /// The number `1`.
const ONE: Self; const ONE: Self;
@ -210,7 +210,13 @@ pub fn pretty_array_like(parts: &[impl AsRef<str>], trailing_comma: bool) -> Str
buf.push('('); buf.push('(');
if list.contains('\n') { if list.contains('\n') {
buf.push('\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'); buf.push('\n');
} else { } else {
buf.push_str(&list); buf.push_str(&list);
@ -218,18 +224,3 @@ pub fn pretty_array_like(parts: &[impl AsRef<str>], trailing_comma: bool) -> Str
buf.push(')'); buf.push(')');
buf 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
}

Some files were not shown because too many files have changed in this diff Show More