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

View File

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

View File

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

View File

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

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
the array's [`map` method]($type/array.map) comes in handy. It takes a function
as an argument that gets called with each element of the array. We pass it a
as an argument that gets called with each item of the array. We pass it a
function that formats the details for each author and returns a new array
containing content values. We've now got one array of values that we'd like to
use as multiple arguments for the grid. We can do that by using the
[`spread` operator]($type/arguments). It takes an array and applies each of its elements as a separate argument to the function.
use as multiple arguments for the grid. We can do that by using the [`spread`
operator]($type/arguments). It takes an array and applies each of its items as a
separate argument to the function.
The resulting template function looks like this:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,15 +15,15 @@ use crate::prelude::*;
///
/// Display: Hide
/// Category: layout
#[node(Show)]
pub struct HideNode {
#[element(Show)]
pub struct HideElem {
/// The content to hide.
#[required]
pub body: Content,
}
impl Show for HideNode {
impl Show for HideElem {
fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> {
Ok(self.body().styled(MetaNode::set_data(vec![Meta::Hide])))
Ok(self.body().styled(MetaElem::set_data(vec![Meta::Hide])))
}
}

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,8 +5,8 @@ extern crate proc_macro;
#[macro_use]
mod util;
mod castable;
mod element;
mod func;
mod node;
mod symbols;
use proc_macro::TokenStream as BoundaryStream;
@ -26,11 +26,11 @@ pub fn func(_: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
func::func(item).unwrap_or_else(|err| err.to_compile_error()).into()
}
/// Implement `Node` for a struct.
/// Turns a struct into an element.
#[proc_macro_attribute]
pub fn node(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
pub fn element(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
let item = syn::parse_macro_input!(item as syn::ItemStruct);
node::node(stream.into(), item)
element::element(stream.into(), item)
.unwrap_or_else(|err| err.to_compile_error())
.into()
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -163,7 +163,9 @@ impl Slot {
fn write(&mut self) -> StrResult<&mut Value> {
match self.kind {
Kind::Normal => Ok(&mut self.value),
Kind::Captured => Err("cannot mutate a captured variable")?,
Kind::Captured => {
Err("variables from outside the function are read-only and cannot be modified")?
}
}
}
}

View File

@ -1,94 +1,127 @@
use std::cmp::Reverse;
use std::collections::BTreeSet;
use std::fmt::{self, Debug, Display, Formatter, Write};
use std::sync::Arc;
use ecow::{EcoString, EcoVec};
use ecow::EcoString;
use crate::diag::StrResult;
#[doc(inline)]
pub use typst_macros::symbols;
/// A symbol.
/// A symbol, possibly with variants.
#[derive(Clone, Eq, PartialEq, Hash)]
pub struct Symbol {
repr: Repr,
modifiers: EcoString,
pub struct Symbol(Repr);
/// The internal representation.
#[derive(Clone, Eq, PartialEq, Hash)]
enum Repr {
Single(char),
Const(&'static [(&'static str, char)]),
Multi(Arc<(List, EcoString)>),
}
/// A collection of symbols.
#[derive(Clone, Eq, PartialEq, Hash)]
enum Repr {
Single(char),
enum List {
Static(&'static [(&'static str, char)]),
Runtime(EcoVec<(EcoString, char)>),
Runtime(Box<[(EcoString, char)]>),
}
impl Symbol {
/// Create a new symbol from a single character.
pub const fn new(c: char) -> Self {
Self { repr: Repr::Single(c), modifiers: EcoString::new() }
Self(Repr::Single(c))
}
/// Create a symbol with a static variant list.
#[track_caller]
pub const fn list(list: &'static [(&'static str, char)]) -> Self {
debug_assert!(!list.is_empty());
Self {
repr: Repr::Static(list),
modifiers: EcoString::new(),
}
Self(Repr::Const(list))
}
/// Create a symbol with a runtime variant list.
#[track_caller]
pub fn runtime(list: EcoVec<(EcoString, char)>) -> Self {
pub fn runtime(list: Box<[(EcoString, char)]>) -> Self {
debug_assert!(!list.is_empty());
Self {
repr: Repr::Runtime(list),
modifiers: EcoString::new(),
}
Self(Repr::Multi(Arc::new((List::Runtime(list), EcoString::new()))))
}
/// Get the symbol's text.
pub fn get(&self) -> char {
match self.repr {
Repr::Single(c) => c,
_ => find(self.variants(), &self.modifiers).unwrap(),
match &self.0 {
Repr::Single(c) => *c,
Repr::Const(_) => find(self.variants(), "").unwrap(),
Repr::Multi(arc) => find(self.variants(), &arc.1).unwrap(),
}
}
/// Apply a modifier to the symbol.
pub fn modified(mut self, modifier: &str) -> StrResult<Self> {
if !self.modifiers.is_empty() {
self.modifiers.push('.');
if let Repr::Const(list) = self.0 {
self.0 = Repr::Multi(Arc::new((List::Static(list), EcoString::new())));
}
self.modifiers.push_str(modifier);
if find(self.variants(), &self.modifiers).is_none() {
Err("unknown modifier")?
if let Repr::Multi(arc) = &mut self.0 {
let (list, modifiers) = Arc::make_mut(arc);
if !modifiers.is_empty() {
modifiers.push('.');
}
modifiers.push_str(modifier);
if find(list.variants(), &modifiers).is_some() {
return Ok(self);
}
}
Ok(self)
Err("unknown symbol modifier".into())
}
/// The characters that are covered by this symbol.
pub fn variants(&self) -> impl Iterator<Item = (&str, char)> {
match &self.repr {
match &self.0 {
Repr::Single(c) => Variants::Single(Some(*c).into_iter()),
Repr::Static(list) => Variants::Static(list.iter()),
Repr::Runtime(list) => Variants::Runtime(list.iter()),
Repr::Const(list) => Variants::Static(list.iter()),
Repr::Multi(arc) => arc.0.variants(),
}
}
/// Possible modifiers.
pub fn modifiers(&self) -> impl Iterator<Item = &str> + '_ {
let mut set = BTreeSet::new();
let modifiers = match &self.0 {
Repr::Multi(arc) => arc.1.as_str(),
_ => "",
};
for modifier in self.variants().flat_map(|(name, _)| name.split('.')) {
if !modifier.is_empty() && !contained(&self.modifiers, modifier) {
if !modifier.is_empty() && !contained(modifiers, modifier) {
set.insert(modifier);
}
}
set.into_iter()
}
/// Normalize an accent to a combining one.
pub fn combining_accent(c: char) -> Option<char> {
Some(match c {
'\u{0300}' | '`' => '\u{0300}',
'\u{0301}' | '´' => '\u{0301}',
'\u{0302}' | '^' | 'ˆ' => '\u{0302}',
'\u{0303}' | '~' | '' | '˜' => '\u{0303}',
'\u{0304}' | '¯' => '\u{0304}',
'\u{0305}' | '-' | '‾' | '' => '\u{0305}',
'\u{0306}' | '˘' => '\u{0306}',
'\u{0307}' | '.' | '˙' | '⋅' => '\u{0307}',
'\u{0308}' | '¨' => '\u{0308}',
'\u{030a}' | '∘' | '○' => '\u{030a}',
'\u{030b}' | '˝' => '\u{030b}',
'\u{030c}' | 'ˇ' => '\u{030c}',
'\u{20d6}' | '←' => '\u{20d6}',
'\u{20d7}' | '→' | '⟶' => '\u{20d7}',
_ => return None,
})
}
}
impl Debug for Symbol {
@ -103,6 +136,16 @@ impl Display for Symbol {
}
}
impl List {
/// The characters that are covered by this list.
fn variants(&self) -> Variants<'_> {
match self {
List::Static(list) => Variants::Static(list.iter()),
List::Runtime(list) => Variants::Runtime(list.iter()),
}
}
}
/// Iterator over variants.
enum Variants<'a> {
Single(std::option::IntoIter<char>),
@ -166,24 +209,3 @@ fn parts(modifiers: &str) -> impl Iterator<Item = &str> {
fn contained(modifiers: &str, m: &str) -> bool {
parts(modifiers).any(|part| part == m)
}
/// Normalize an accent to a combining one.
pub fn combining_accent(c: char) -> Option<char> {
Some(match c {
'\u{0300}' | '`' => '\u{0300}',
'\u{0301}' | '´' => '\u{0301}',
'\u{0302}' | '^' | 'ˆ' => '\u{0302}',
'\u{0303}' | '~' | '' | '˜' => '\u{0303}',
'\u{0304}' | '¯' => '\u{0304}',
'\u{0305}' | '-' | '‾' | '' => '\u{0305}',
'\u{0306}' | '˘' => '\u{0306}',
'\u{0307}' | '.' | '˙' | '⋅' => '\u{0307}',
'\u{0308}' | '¨' => '\u{0308}',
'\u{030a}' | '∘' | '○' => '\u{030a}',
'\u{030b}' | '˝' => '\u{030b}',
'\u{030c}' | 'ˇ' => '\u{030c}',
'\u{20d6}' | '←' => '\u{20d6}',
'\u{20d7}' | '→' | '⟶' => '\u{20d7}',
_ => return None,
})
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -81,6 +81,11 @@ pub fn analyze_import(
}
/// Find all labels and details for them.
///
/// Returns:
/// - All labels and descriptions for them, if available
/// - A split offset: All labels before this offset belong to nodes, all after
/// belong to a bibliography.
pub fn analyze_labels(
world: &(dyn World + 'static),
frames: &[Frame],
@ -90,16 +95,16 @@ pub fn analyze_labels(
let items = &world.library().items;
// Labels in the document.
for node in introspector.all() {
let Some(label) = node.label() else { continue };
let details = node
for elem in introspector.all() {
let Some(label) = elem.label() else { continue };
let details = elem
.field("caption")
.or_else(|| node.field("body"))
.or_else(|| elem.field("body"))
.and_then(|field| match field {
Value::Content(content) => Some(content),
_ => None,
})
.and_then(|content| (items.text_str)(content));
.and_then(|content| (items.text_str)(&content));
output.push((label.clone(), details));
}

View File

@ -373,7 +373,7 @@ fn field_access_completions(ctx: &mut CompletionContext, value: &Value) {
}
Value::Content(content) => {
for (name, value) in content.fields() {
ctx.value_completion(Some(name.clone()), value, false, None);
ctx.value_completion(Some(name.clone()), &value, false, None);
}
}
Value::Dict(dict) => {
@ -509,7 +509,7 @@ fn set_rule_completions(ctx: &mut CompletionContext) {
fn show_rule_selector_completions(ctx: &mut CompletionContext) {
ctx.scope_completions(
false,
|value| matches!(value, Value::Func(func) if func.select(None).is_ok()),
|value| matches!(value, Value::Func(func) if func.element().is_some()),
);
ctx.enrich("", ": ");

View File

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

View File

@ -9,12 +9,12 @@
//! The next step is to [evaluate] the markup. This produces a [module],
//! consisting of a scope of values that were exported by the code and
//! [content], a hierarchical, styled representation of what was written in
//! the source file. The nodes of the content tree are well structured and
//! the source file. The elements of the content tree are well structured and
//! order-independent and thus much better suited for further processing than
//! the raw markup.
//! - **Typesetting:**
//! Next, the content is [typeset] into a [document] containing one [frame]
//! per page with elements and fixed positions.
//! per page with items at fixed positions.
//! - **Exporting:**
//! These frames can finally be exported into an output format (currently
//! supported are [PDF] and [raster images]).

View File

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

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.
#[macro_use]
mod styles;
mod content;
mod element;
mod introspect;
mod realize;
mod typeset;
mod styles;
pub use self::content::*;
pub use self::element::*;
pub use self::introspect::*;
pub use self::realize::*;
pub use self::styles::*;
pub use self::typeset::*;
pub use typst_macros::node;
pub use typst_macros::element;
use comemo::{Constraint, Track, Tracked, TrackedMut};
use crate::diag::SourceResult;
use crate::doc::Document;
use crate::eval::Tracer;
use crate::World;
/// Typeset content into a fully layouted document.
#[comemo::memoize]
pub fn typeset(
world: Tracked<dyn World>,
mut tracer: TrackedMut<Tracer>,
content: &Content,
) -> SourceResult<Document> {
let library = world.library();
let styles = StyleChain::new(&library.styles);
let mut document;
let mut iter = 0;
let mut introspector = Introspector::new(&[]);
// Relayout until all introspections stabilize.
// If that doesn't happen within five attempts, we give up.
loop {
let constraint = Constraint::new();
let mut provider = StabilityProvider::new();
let mut vt = Vt {
world,
tracer: TrackedMut::reborrow_mut(&mut tracer),
provider: provider.track_mut(),
introspector: introspector.track_with(&constraint),
};
document = (library.items.layout)(&mut vt, content, styles)?;
iter += 1;
introspector = Introspector::new(&document.pages);
if iter >= 5 || introspector.valid(&constraint) {
break;
}
}
Ok(document)
}
/// A virtual typesetter.
///
/// Holds the state needed to [typeset] content.
pub struct Vt<'a> {
/// The compilation environment.
pub world: Tracked<'a, dyn World>,
/// The tracer for inspection of the values an expression produces.
pub tracer: TrackedMut<'a, Tracer>,
/// Provides stable identities to elements.
pub provider: TrackedMut<'a, StabilityProvider>,
/// Provides access to information about the document.
pub introspector: Tracked<'a, Introspector>,
}
impl Vt<'_> {
/// Mutably reborrow with a shorter lifetime.
pub fn reborrow_mut(&mut self) -> Vt<'_> {
Vt {
world: self.world,
tracer: TrackedMut::reborrow_mut(&mut self.tracer),
provider: TrackedMut::reborrow_mut(&mut self.provider),
introspector: self.introspector,
}
}
}

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

View File

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

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)]
#[repr(u8)]
pub enum SyntaxKind {
/// Markup of which all lines must have a minimal indentation.
///
/// Notably, the number does not determine in which column the markup
/// started, but to the right of which column all markup elements must be,
/// so it is zero except inside indent-aware constructs like lists.
/// Markup.
Markup,
/// Plain text without markup.
Text,

View File

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

View File

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

View File

@ -9,7 +9,8 @@ use comemo::Prehashed;
use unscanny::Scanner;
use super::ast::Markup;
use super::{is_newline, parse, reparse, LinkedNode, Span, SyntaxNode};
use super::reparser::reparse;
use super::{is_newline, parse, LinkedNode, Span, SyntaxNode};
use crate::diag::SourceResult;
use crate::util::{PathExt, StrExt};

View File

@ -40,7 +40,7 @@ pub fn hash128<T: Hash + ?Sized>(value: &T) -> u128 {
state.finish128().as_u128()
}
/// Extra methods for [`NonZeroUsize`].
/// An extra constant for [`NonZeroUsize`].
pub trait NonZeroExt {
/// The number `1`.
const ONE: Self;
@ -210,7 +210,13 @@ pub fn pretty_array_like(parts: &[impl AsRef<str>], trailing_comma: bool) -> Str
buf.push('(');
if list.contains('\n') {
buf.push('\n');
buf.push_str(&indent(&list, 2));
for (i, line) in list.lines().enumerate() {
if i > 0 {
buf.push('\n');
}
buf.push_str(" ");
buf.push_str(line);
}
buf.push('\n');
} else {
buf.push_str(&list);
@ -218,18 +224,3 @@ pub fn pretty_array_like(parts: &[impl AsRef<str>], trailing_comma: bool) -> Str
buf.push(')');
buf
}
/// Indent a string by two spaces.
pub fn indent(text: &str, amount: usize) -> String {
let mut buf = String::new();
for (i, line) in text.lines().enumerate() {
if i > 0 {
buf.push('\n');
}
for _ in 0..amount {
buf.push(' ');
}
buf.push_str(line);
}
buf
}

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