mirror of
https://github.com/typst/typst
synced 2025-05-11 19:46:23 +08:00
Renaming and refactoring
This commit is contained in:
parent
d6aaae0cea
commit
ab43bd802e
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 {..}}` \
|
||||
|
@ -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()
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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)?
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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]
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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])))
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
},
|
||||
|
@ -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);
|
||||
|
@ -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<_>>(),
|
||||
|
@ -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,
|
||||
|
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,17 +3,15 @@ use unicode_bidi::{BidiInfo, Level as BidiLevel};
|
||||
use unicode_script::{Script, UnicodeScript};
|
||||
use xi_unicode::LineBreakIterator;
|
||||
|
||||
use typst::model::StyledNode;
|
||||
|
||||
use super::{BoxNode, HNode, Sizing, Spacing};
|
||||
use crate::layout::AlignNode;
|
||||
use crate::math::EquationNode;
|
||||
use super::{BoxElem, HElem, Sizing, Spacing};
|
||||
use crate::layout::AlignElem;
|
||||
use crate::math::EquationElem;
|
||||
use crate::prelude::*;
|
||||
use crate::text::{
|
||||
shape, LinebreakNode, Quoter, Quotes, ShapedText, SmartQuoteNode, SpaceNode, TextNode,
|
||||
shape, LinebreakElem, Quoter, Quotes, ShapedText, SmartQuoteElem, SpaceElem, TextElem,
|
||||
};
|
||||
|
||||
/// Arrange text, spacing and inline-level nodes into a paragraph.
|
||||
/// Arrange text, spacing and inline-level elements into a paragraph.
|
||||
///
|
||||
/// Although this function is primarily used in set rules to affect paragraph
|
||||
/// properties, it can also be used to explicitly render its argument onto a
|
||||
@ -38,8 +36,8 @@ use crate::text::{
|
||||
///
|
||||
/// Display: Paragraph
|
||||
/// Category: layout
|
||||
#[node(Construct)]
|
||||
pub struct ParNode {
|
||||
#[element(Construct)]
|
||||
pub struct ParElem {
|
||||
/// The spacing between lines.
|
||||
///
|
||||
/// The default value is `{0.65em}`.
|
||||
@ -110,22 +108,22 @@ pub struct ParNode {
|
||||
pub children: Vec<Content>,
|
||||
}
|
||||
|
||||
impl Construct for ParNode {
|
||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||
impl Construct for ParElem {
|
||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||
// The paragraph constructor is special: It doesn't create a paragraph
|
||||
// node. Instead, it just ensures that the passed content lives in a
|
||||
// element. Instead, it just ensures that the passed content lives in a
|
||||
// separate paragraph and styles it.
|
||||
let styles = Self::set(args)?;
|
||||
let body = args.expect::<Content>("body")?;
|
||||
Ok(Content::sequence(vec![
|
||||
ParbreakNode::new().pack(),
|
||||
Ok(Content::sequence([
|
||||
ParbreakElem::new().pack(),
|
||||
body.styled_with_map(styles),
|
||||
ParbreakNode::new().pack(),
|
||||
ParbreakElem::new().pack(),
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
impl ParNode {
|
||||
impl ParElem {
|
||||
/// Layout the paragraph into a collection of lines.
|
||||
pub fn layout(
|
||||
&self,
|
||||
@ -137,7 +135,7 @@ impl ParNode {
|
||||
) -> SourceResult<Fragment> {
|
||||
#[comemo::memoize]
|
||||
fn cached(
|
||||
par: &ParNode,
|
||||
par: &ParElem,
|
||||
world: Tracked<dyn World>,
|
||||
tracer: TrackedMut<Tracer>,
|
||||
provider: TrackedMut<StabilityProvider>,
|
||||
@ -179,26 +177,6 @@ impl ParNode {
|
||||
}
|
||||
}
|
||||
|
||||
/// A horizontal alignment.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct HorizontalAlign(pub GenAlign);
|
||||
|
||||
cast_from_value! {
|
||||
HorizontalAlign,
|
||||
align: GenAlign => match align.axis() {
|
||||
Axis::X => Self(align),
|
||||
Axis::Y => Err("must be horizontal")?,
|
||||
},
|
||||
}
|
||||
|
||||
impl Resolve for HorizontalAlign {
|
||||
type Output = Align;
|
||||
|
||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||
self.0.resolve(styles)
|
||||
}
|
||||
}
|
||||
|
||||
/// How to determine line breaks in a paragraph.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
|
||||
pub enum Linebreaks {
|
||||
@ -232,10 +210,10 @@ pub enum Linebreaks {
|
||||
///
|
||||
/// Display: Paragraph Break
|
||||
/// Category: layout
|
||||
#[node(Unlabellable)]
|
||||
pub struct ParbreakNode {}
|
||||
#[element(Unlabellable)]
|
||||
pub struct ParbreakElem {}
|
||||
|
||||
impl Unlabellable for ParbreakNode {}
|
||||
impl Unlabellable for ParbreakElem {}
|
||||
|
||||
/// Range of a substring of text.
|
||||
type Range = std::ops::Range<usize>;
|
||||
@ -243,7 +221,7 @@ type Range = std::ops::Range<usize>;
|
||||
// The characters by which spacing, inline content and pins are replaced in the
|
||||
// paragraph's full text.
|
||||
const SPACING_REPLACE: char = ' '; // Space
|
||||
const NODE_REPLACE: char = '\u{FFFC}'; // Object Replacement Character
|
||||
const OBJ_REPLACE: char = '\u{FFFC}'; // Object Replacement Character
|
||||
|
||||
/// A paragraph representation in which children are already layouted and text
|
||||
/// is already preshaped.
|
||||
@ -254,7 +232,7 @@ const NODE_REPLACE: char = '\u{FFFC}'; // Object Replacement Character
|
||||
struct Preparation<'a> {
|
||||
/// Bidirectional text embedding levels for the paragraph.
|
||||
bidi: BidiInfo<'a>,
|
||||
/// Text runs, spacing and layouted nodes.
|
||||
/// Text runs, spacing and layouted elements.
|
||||
items: Vec<Item<'a>>,
|
||||
/// The span mapper.
|
||||
spans: SpanMapper,
|
||||
@ -325,9 +303,9 @@ enum Segment<'a> {
|
||||
/// Horizontal spacing between other segments.
|
||||
Spacing(Spacing),
|
||||
/// A mathematical equation.
|
||||
Equation(&'a EquationNode),
|
||||
Equation(&'a EquationElem),
|
||||
/// A box with arbitrary content.
|
||||
Box(&'a BoxNode, bool),
|
||||
Box(&'a BoxElem, bool),
|
||||
/// Metadata.
|
||||
Meta,
|
||||
}
|
||||
@ -339,7 +317,7 @@ impl Segment<'_> {
|
||||
Self::Text(len) => len,
|
||||
Self::Spacing(_) => SPACING_REPLACE.len_utf8(),
|
||||
Self::Box(_, true) => SPACING_REPLACE.len_utf8(),
|
||||
Self::Equation(_) | Self::Box(_, _) | Self::Meta => NODE_REPLACE.len_utf8(),
|
||||
Self::Equation(_) | Self::Box(_, _) | Self::Meta => OBJ_REPLACE.len_utf8(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -352,7 +330,7 @@ enum Item<'a> {
|
||||
/// Absolute spacing between other items.
|
||||
Absolute(Abs),
|
||||
/// Fractional spacing between other items.
|
||||
Fractional(Fr, Option<(&'a BoxNode, StyleChain<'a>)>),
|
||||
Fractional(Fr, Option<(&'a BoxElem, StyleChain<'a>)>),
|
||||
/// Layouted inline-level content.
|
||||
Frame(Frame),
|
||||
}
|
||||
@ -371,7 +349,7 @@ impl<'a> Item<'a> {
|
||||
match self {
|
||||
Self::Text(shaped) => shaped.text.len(),
|
||||
Self::Absolute(_) | Self::Fractional(_, _) => SPACING_REPLACE.len_utf8(),
|
||||
Self::Frame(_) => NODE_REPLACE.len_utf8(),
|
||||
Self::Frame(_) => OBJ_REPLACE.len_utf8(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -520,7 +498,7 @@ fn collect<'a>(
|
||||
let mut iter = children.iter().peekable();
|
||||
|
||||
if consecutive {
|
||||
let first_line_indent = ParNode::first_line_indent_in(*styles);
|
||||
let first_line_indent = ParElem::first_line_indent_in(*styles);
|
||||
if !first_line_indent.is_zero()
|
||||
&& children
|
||||
.iter()
|
||||
@ -529,7 +507,7 @@ fn collect<'a>(
|
||||
behaved.behaviour() == Behaviour::Ignorant
|
||||
}) {
|
||||
None
|
||||
} else if child.is::<TextNode>() || child.is::<SmartQuoteNode>() {
|
||||
} else if child.is::<TextElem>() || child.is::<SmartQuoteElem>() {
|
||||
Some(true)
|
||||
} else {
|
||||
Some(false)
|
||||
@ -542,7 +520,7 @@ fn collect<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
let hang = ParNode::hanging_indent_in(*styles);
|
||||
let hang = ParElem::hanging_indent_in(*styles);
|
||||
if !hang.is_zero() {
|
||||
full.push(SPACING_REPLACE);
|
||||
segments.push((Segment::Spacing((-hang).into()), *styles));
|
||||
@ -551,61 +529,61 @@ fn collect<'a>(
|
||||
while let Some(mut child) = iter.next() {
|
||||
let outer = styles;
|
||||
let mut styles = *styles;
|
||||
if let Some(node) = child.to::<StyledNode>() {
|
||||
child = Box::leak(Box::new(node.body()));
|
||||
styles = outer.chain(Box::leak(Box::new(node.styles())));
|
||||
if let Some((elem, local)) = child.to_styled() {
|
||||
child = elem;
|
||||
styles = outer.chain(local);
|
||||
}
|
||||
|
||||
let segment = if child.is::<SpaceNode>() {
|
||||
let segment = if child.is::<SpaceElem>() {
|
||||
full.push(' ');
|
||||
Segment::Text(1)
|
||||
} else if let Some(node) = child.to::<TextNode>() {
|
||||
} else if let Some(elem) = child.to::<TextElem>() {
|
||||
let prev = full.len();
|
||||
if let Some(case) = TextNode::case_in(styles) {
|
||||
full.push_str(&case.apply(&node.text()));
|
||||
if let Some(case) = TextElem::case_in(styles) {
|
||||
full.push_str(&case.apply(&elem.text()));
|
||||
} else {
|
||||
full.push_str(&node.text());
|
||||
full.push_str(&elem.text());
|
||||
}
|
||||
Segment::Text(full.len() - prev)
|
||||
} else if let Some(node) = child.to::<HNode>() {
|
||||
} else if let Some(elem) = child.to::<HElem>() {
|
||||
full.push(SPACING_REPLACE);
|
||||
Segment::Spacing(node.amount())
|
||||
} else if let Some(node) = child.to::<LinebreakNode>() {
|
||||
let c = if node.justify(styles) { '\u{2028}' } else { '\n' };
|
||||
Segment::Spacing(elem.amount())
|
||||
} else if let Some(elem) = child.to::<LinebreakElem>() {
|
||||
let c = if elem.justify(styles) { '\u{2028}' } else { '\n' };
|
||||
full.push(c);
|
||||
Segment::Text(c.len_utf8())
|
||||
} else if let Some(node) = child.to::<SmartQuoteNode>() {
|
||||
} else if let Some(elem) = child.to::<SmartQuoteElem>() {
|
||||
let prev = full.len();
|
||||
if SmartQuoteNode::enabled_in(styles) {
|
||||
let lang = TextNode::lang_in(styles);
|
||||
let region = TextNode::region_in(styles);
|
||||
if SmartQuoteElem::enabled_in(styles) {
|
||||
let lang = TextElem::lang_in(styles);
|
||||
let region = TextElem::region_in(styles);
|
||||
let quotes = Quotes::from_lang(lang, region);
|
||||
let peeked = iter.peek().and_then(|child| {
|
||||
if let Some(node) = child.to::<TextNode>() {
|
||||
node.text().chars().next()
|
||||
} else if child.is::<SmartQuoteNode>() {
|
||||
if let Some(elem) = child.to::<TextElem>() {
|
||||
elem.text().chars().next()
|
||||
} else if child.is::<SmartQuoteElem>() {
|
||||
Some('"')
|
||||
} else if child.is::<SpaceNode>() || child.is::<HNode>() {
|
||||
} else if child.is::<SpaceElem>() || child.is::<HElem>() {
|
||||
Some(SPACING_REPLACE)
|
||||
} else {
|
||||
Some(NODE_REPLACE)
|
||||
Some(OBJ_REPLACE)
|
||||
}
|
||||
});
|
||||
|
||||
full.push_str(quoter.quote("es, node.double(styles), peeked));
|
||||
full.push_str(quoter.quote("es, elem.double(styles), peeked));
|
||||
} else {
|
||||
full.push(if node.double(styles) { '"' } else { '\'' });
|
||||
full.push(if elem.double(styles) { '"' } else { '\'' });
|
||||
}
|
||||
Segment::Text(full.len() - prev)
|
||||
} else if let Some(node) = child.to::<EquationNode>() {
|
||||
full.push(NODE_REPLACE);
|
||||
Segment::Equation(node)
|
||||
} else if let Some(node) = child.to::<BoxNode>() {
|
||||
let frac = node.width(styles).is_fractional();
|
||||
full.push(if frac { SPACING_REPLACE } else { NODE_REPLACE });
|
||||
Segment::Box(node, frac)
|
||||
} else if child.is::<MetaNode>() {
|
||||
full.push(NODE_REPLACE);
|
||||
} else if let Some(elem) = child.to::<EquationElem>() {
|
||||
full.push(OBJ_REPLACE);
|
||||
Segment::Equation(elem)
|
||||
} else if let Some(elem) = child.to::<BoxElem>() {
|
||||
let frac = elem.width(styles).is_fractional();
|
||||
full.push(if frac { SPACING_REPLACE } else { OBJ_REPLACE });
|
||||
Segment::Box(elem, frac)
|
||||
} else if child.is::<MetaElem>() {
|
||||
full.push(OBJ_REPLACE);
|
||||
Segment::Meta
|
||||
} else {
|
||||
bail!(child.span(), "unexpected paragraph child");
|
||||
@ -645,7 +623,7 @@ fn prepare<'a>(
|
||||
) -> SourceResult<Preparation<'a>> {
|
||||
let bidi = BidiInfo::new(
|
||||
text,
|
||||
match TextNode::dir_in(styles) {
|
||||
match TextElem::dir_in(styles) {
|
||||
Dir::LTR => Some(BidiLevel::ltr()),
|
||||
Dir::RTL => Some(BidiLevel::rtl()),
|
||||
_ => None,
|
||||
@ -674,16 +652,16 @@ fn prepare<'a>(
|
||||
Segment::Equation(equation) => {
|
||||
let pod = Regions::one(region, Axes::splat(false));
|
||||
let mut frame = equation.layout(vt, styles, pod)?.into_frame();
|
||||
frame.translate(Point::with_y(TextNode::baseline_in(styles)));
|
||||
frame.translate(Point::with_y(TextElem::baseline_in(styles)));
|
||||
items.push(Item::Frame(frame));
|
||||
}
|
||||
Segment::Box(node, _) => {
|
||||
if let Sizing::Fr(v) = node.width(styles) {
|
||||
items.push(Item::Fractional(v, Some((node, styles))));
|
||||
Segment::Box(elem, _) => {
|
||||
if let Sizing::Fr(v) = elem.width(styles) {
|
||||
items.push(Item::Fractional(v, Some((elem, styles))));
|
||||
} else {
|
||||
let pod = Regions::one(region, Axes::splat(false));
|
||||
let mut frame = node.layout(vt, styles, pod)?.into_frame();
|
||||
frame.translate(Point::with_y(TextNode::baseline_in(styles)));
|
||||
let mut frame = elem.layout(vt, styles, pod)?.into_frame();
|
||||
frame.translate(Point::with_y(TextElem::baseline_in(styles)));
|
||||
items.push(Item::Frame(frame));
|
||||
}
|
||||
}
|
||||
@ -702,11 +680,11 @@ fn prepare<'a>(
|
||||
items,
|
||||
spans,
|
||||
styles,
|
||||
hyphenate: shared_get(styles, children, TextNode::hyphenate_in),
|
||||
lang: shared_get(styles, children, TextNode::lang_in),
|
||||
align: AlignNode::alignment_in(styles).x.resolve(styles),
|
||||
justify: ParNode::justify_in(styles),
|
||||
hang: ParNode::hanging_indent_in(styles),
|
||||
hyphenate: shared_get(styles, children, TextElem::hyphenate_in),
|
||||
lang: shared_get(styles, children, TextElem::lang_in),
|
||||
align: AlignElem::alignment_in(styles).x.resolve(styles),
|
||||
justify: ParElem::justify_in(styles),
|
||||
hang: ParElem::hanging_indent_in(styles),
|
||||
})
|
||||
}
|
||||
|
||||
@ -775,15 +753,15 @@ fn shared_get<'a, T: PartialEq>(
|
||||
let value = getter(styles);
|
||||
children
|
||||
.iter()
|
||||
.filter_map(|child| child.to::<StyledNode>())
|
||||
.all(|node| getter(styles.chain(&node.styles())) == value)
|
||||
.filter_map(|child| child.to_styled())
|
||||
.all(|(_, local)| getter(styles.chain(&local)) == value)
|
||||
.then(|| value)
|
||||
}
|
||||
|
||||
/// Find suitable linebreaks.
|
||||
fn linebreak<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec<Line<'a>> {
|
||||
let linebreaks = ParNode::linebreaks_in(p.styles).unwrap_or_else(|| {
|
||||
if ParNode::justify_in(p.styles) {
|
||||
let linebreaks = ParElem::linebreaks_in(p.styles).unwrap_or_else(|| {
|
||||
if ParElem::justify_in(p.styles) {
|
||||
Linebreaks::Optimized
|
||||
} else {
|
||||
Linebreaks::Simple
|
||||
@ -881,7 +859,7 @@ fn linebreak_optimized<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec<L
|
||||
line: line(vt, p, 0..0, false, false),
|
||||
}];
|
||||
|
||||
let em = TextNode::size_in(p.styles);
|
||||
let em = TextElem::size_in(p.styles);
|
||||
|
||||
for (end, mandatory, hyphen) in breakpoints(p) {
|
||||
let k = table.len();
|
||||
@ -1046,7 +1024,7 @@ impl Breakpoints<'_> {
|
||||
.hyphenate
|
||||
.or_else(|| {
|
||||
let shaped = self.p.find(offset)?.text()?;
|
||||
Some(TextNode::hyphenate_in(shaped.styles))
|
||||
Some(TextElem::hyphenate_in(shaped.styles))
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
@ -1055,7 +1033,7 @@ impl Breakpoints<'_> {
|
||||
fn lang(&self, offset: usize) -> Option<hypher::Lang> {
|
||||
let lang = self.p.lang.or_else(|| {
|
||||
let shaped = self.p.find(offset)?.text()?;
|
||||
Some(TextNode::lang_in(shaped.styles))
|
||||
Some(TextElem::lang_in(shaped.styles))
|
||||
})?;
|
||||
|
||||
let bytes = lang.as_str().as_bytes().try_into().ok()?;
|
||||
@ -1196,7 +1174,7 @@ fn finalize(
|
||||
.collect::<SourceResult<_>>()?;
|
||||
|
||||
// Prevent orphans.
|
||||
let leading = ParNode::leading_in(p.styles);
|
||||
let leading = ParElem::leading_in(p.styles);
|
||||
if frames.len() >= 2 && !frames[1].is_empty() {
|
||||
let second = frames.remove(1);
|
||||
let first = &mut frames[0];
|
||||
@ -1243,7 +1221,7 @@ fn commit(
|
||||
if let Some(Item::Text(text)) = reordered.first() {
|
||||
if let Some(glyph) = text.glyphs.first() {
|
||||
if !text.dir.is_positive()
|
||||
&& TextNode::overhang_in(text.styles)
|
||||
&& TextElem::overhang_in(text.styles)
|
||||
&& (reordered.len() > 1 || text.glyphs.len() > 1)
|
||||
{
|
||||
let amount = overhang(glyph.c) * glyph.x_advance.at(text.size);
|
||||
@ -1257,7 +1235,7 @@ fn commit(
|
||||
if let Some(Item::Text(text)) = reordered.last() {
|
||||
if let Some(glyph) = text.glyphs.last() {
|
||||
if text.dir.is_positive()
|
||||
&& TextNode::overhang_in(text.styles)
|
||||
&& TextElem::overhang_in(text.styles)
|
||||
&& (reordered.len() > 1 || text.glyphs.len() > 1)
|
||||
{
|
||||
let amount = overhang(glyph.c) * glyph.x_advance.at(text.size);
|
||||
@ -1295,13 +1273,13 @@ fn commit(
|
||||
Item::Absolute(v) => {
|
||||
offset += *v;
|
||||
}
|
||||
Item::Fractional(v, node) => {
|
||||
Item::Fractional(v, elem) => {
|
||||
let amount = v.share(fr, remaining);
|
||||
if let Some((node, styles)) = node {
|
||||
if let Some((elem, styles)) = elem {
|
||||
let region = Size::new(amount, full);
|
||||
let pod = Regions::one(region, Axes::new(true, false));
|
||||
let mut frame = node.layout(vt, *styles, pod)?.into_frame();
|
||||
frame.translate(Point::with_y(TextNode::baseline_in(*styles)));
|
||||
let mut frame = elem.layout(vt, *styles, pod)?.into_frame();
|
||||
frame.translate(Point::with_y(TextElem::baseline_in(*styles)));
|
||||
push(&mut offset, frame);
|
||||
} else {
|
||||
offset += amount;
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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>,
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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]
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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")?,
|
||||
},
|
||||
}
|
||||
|
@ -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(())
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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)?;
|
||||
|
@ -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'));
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
};
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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()))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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() })
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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};
|
||||
|
@ -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(..) {
|
||||
|
@ -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(),
|
||||
)));
|
||||
}
|
||||
|
@ -993,7 +993,7 @@ const EMOJI: &[(&'static str, Symbol)] = symbols! {
|
||||
piano: '🎹',
|
||||
pick: '⛏',
|
||||
pie: '🥧',
|
||||
pig: ['🐖', face: '🐷', node: '🐽'],
|
||||
pig: ['🐖', face: '🐷', nose: '🐽'],
|
||||
pill: '💊',
|
||||
pin: ['📌', round: '📍'],
|
||||
pinata: '🪅',
|
||||
|
@ -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()));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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")?,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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.
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
242
src/doc.rs
242
src/doc.rs
@ -14,7 +14,7 @@ use crate::geom::{
|
||||
Numeric, Paint, Point, Rel, RgbaColor, Shape, Sides, Size, Stroke, Transform,
|
||||
};
|
||||
use crate::image::Image;
|
||||
use crate::model::{Content, Introspector, MetaNode, StableId, StyleChain};
|
||||
use crate::model::{Content, Location, MetaElem, StyleChain};
|
||||
use crate::syntax::Span;
|
||||
|
||||
/// A finished document with metadata and page frames.
|
||||
@ -28,7 +28,7 @@ pub struct Document {
|
||||
pub author: Vec<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()),
|
||||
})
|
||||
}
|
||||
|
@ -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()]);
|
||||
|
@ -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>,
|
||||
|
@ -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());
|
||||
|
@ -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),
|
||||
|
@ -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()))
|
||||
}
|
||||
|
@ -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")?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
@ -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 }
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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("", ": ");
|
||||
|
@ -1,6 +1,8 @@
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use crate::doc::{Destination, Element, Frame, Location, Meta};
|
||||
use ecow::EcoString;
|
||||
|
||||
use crate::doc::{Destination, Frame, FrameItem, Meta, Position};
|
||||
use crate::geom::{Geometry, Point, Size};
|
||||
use crate::model::Introspector;
|
||||
use crate::syntax::{LinkedNode, Source, SourceId, Span, SyntaxKind};
|
||||
@ -11,8 +13,10 @@ use crate::World;
|
||||
pub enum Jump {
|
||||
/// Jump to a position in a source file.
|
||||
Source(SourceId, usize),
|
||||
/// Jump to position in the output or to an external URL.
|
||||
Dest(Destination),
|
||||
/// Jump to an external URL.
|
||||
Url(EcoString),
|
||||
/// Jump to a point on a page.
|
||||
Position(Position),
|
||||
}
|
||||
|
||||
impl Jump {
|
||||
@ -32,20 +36,27 @@ pub fn jump_from_click(
|
||||
) -> Option<Jump> {
|
||||
let mut introspector = None;
|
||||
|
||||
// Prefer metadata.
|
||||
for (pos, element) in frame.elements() {
|
||||
if let Element::Meta(Meta::Link(link), size) = element {
|
||||
// Try to find a link first.
|
||||
for (pos, item) in frame.items() {
|
||||
if let FrameItem::Meta(Meta::Link(dest), size) = item {
|
||||
if is_in_rect(*pos, *size, click) {
|
||||
return Some(Jump::Dest(link.resolve(|| {
|
||||
introspector.get_or_insert_with(|| Introspector::new(frames))
|
||||
})));
|
||||
return Some(match dest {
|
||||
Destination::Url(url) => Jump::Url(url.clone()),
|
||||
Destination::Position(pos) => Jump::Position(*pos),
|
||||
Destination::Location(loc) => Jump::Position(
|
||||
introspector
|
||||
.get_or_insert_with(|| Introspector::new(frames))
|
||||
.position(*loc),
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (mut pos, element) in frame.elements().rev() {
|
||||
match element {
|
||||
Element::Group(group) => {
|
||||
// If there's no link, search for a jump target.
|
||||
for (mut pos, item) in frame.items().rev() {
|
||||
match item {
|
||||
FrameItem::Group(group) => {
|
||||
// TODO: Handle transformation.
|
||||
if let Some(span) =
|
||||
jump_from_click(world, frames, &group.frame, click - pos)
|
||||
@ -54,7 +65,7 @@ pub fn jump_from_click(
|
||||
}
|
||||
}
|
||||
|
||||
Element::Text(text) => {
|
||||
FrameItem::Text(text) => {
|
||||
for glyph in &text.glyphs {
|
||||
if glyph.span.is_detached() {
|
||||
continue;
|
||||
@ -85,14 +96,14 @@ pub fn jump_from_click(
|
||||
}
|
||||
}
|
||||
|
||||
Element::Shape(shape, span) => {
|
||||
FrameItem::Shape(shape, span) => {
|
||||
let Geometry::Rect(size) = shape.geometry else { continue };
|
||||
if is_in_rect(pos, size, click) {
|
||||
return Some(Jump::from_span(world, *span));
|
||||
}
|
||||
}
|
||||
|
||||
Element::Image(_, size, span) if is_in_rect(pos, *size, click) => {
|
||||
FrameItem::Image(_, size, span) if is_in_rect(pos, *size, click) => {
|
||||
return Some(Jump::from_span(world, *span));
|
||||
}
|
||||
|
||||
@ -108,7 +119,7 @@ pub fn jump_from_cursor(
|
||||
frames: &[Frame],
|
||||
source: &Source,
|
||||
cursor: usize,
|
||||
) -> Option<Location> {
|
||||
) -> Option<Position> {
|
||||
let node = LinkedNode::new(source.root()).leaf_at(cursor)?;
|
||||
if node.kind() != SyntaxKind::Text {
|
||||
return None;
|
||||
@ -117,7 +128,10 @@ pub fn jump_from_cursor(
|
||||
let span = node.span();
|
||||
for (i, frame) in frames.iter().enumerate() {
|
||||
if let Some(pos) = find_in_frame(frame, span) {
|
||||
return Some(Location { page: NonZeroUsize::new(i + 1).unwrap(), pos });
|
||||
return Some(Position {
|
||||
page: NonZeroUsize::new(i + 1).unwrap(),
|
||||
point: pos,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,15 +140,15 @@ pub fn jump_from_cursor(
|
||||
|
||||
/// Find the position of a span in a frame.
|
||||
fn find_in_frame(frame: &Frame, span: Span) -> Option<Point> {
|
||||
for (mut pos, element) in frame.elements() {
|
||||
if let Element::Group(group) = element {
|
||||
for (mut pos, item) in frame.items() {
|
||||
if let FrameItem::Group(group) = item {
|
||||
// TODO: Handle transformation.
|
||||
if let Some(point) = find_in_frame(&group.frame, span) {
|
||||
return Some(point + pos);
|
||||
}
|
||||
}
|
||||
|
||||
if let Element::Text(text) = element {
|
||||
if let FrameItem::Text(text) = item {
|
||||
for glyph in &text.glyphs {
|
||||
if glyph.span == span {
|
||||
return Some(pos);
|
||||
|
@ -9,12 +9,12 @@
|
||||
//! The next step is to [evaluate] the markup. This produces a [module],
|
||||
//! consisting of a scope of values that were exported by the code and
|
||||
//! [content], a hierarchical, styled representation of what was written in
|
||||
//! the source file. The nodes of the content tree are well structured and
|
||||
//! the source file. The elements of the content tree are well structured and
|
||||
//! order-independent and thus much better suited for further processing than
|
||||
//! the raw markup.
|
||||
//! - **Typesetting:**
|
||||
//! Next, the content is [typeset] into a [document] containing one [frame]
|
||||
//! per page with elements and fixed positions.
|
||||
//! per page with items at fixed positions.
|
||||
//! - **Exporting:**
|
||||
//! These frames can finally be exported into an output format (currently
|
||||
//! supported are [PDF] and [raster images]).
|
||||
|
@ -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
145
src/model/element.rs
Normal 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
170
src/model/introspect.rs
Normal 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() })
|
||||
}
|
||||
}
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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(®ex::escape(text)).unwrap())
|
||||
@ -265,16 +273,16 @@ impl Selector {
|
||||
/// Whether the selector matches for the target.
|
||||
pub fn matches(&self, target: &Content) -> bool {
|
||||
match self {
|
||||
Self::Node(id, dict) => {
|
||||
target.id() == *id
|
||||
Self::Elem(element, dict) => {
|
||||
target.func() == *element
|
||||
&& dict
|
||||
.iter()
|
||||
.flat_map(|dict| dict.iter())
|
||||
.all(|(name, value)| target.field(name) == Some(value))
|
||||
.all(|(name, value)| target.field_ref(name) == Some(value))
|
||||
}
|
||||
Self::Label(label) => target.label() == Some(label),
|
||||
Self::Regex(regex) => {
|
||||
target.id() == item!(text_id)
|
||||
target.func() == item!(text_func)
|
||||
&& item!(text_str)(target).map_or(false, |text| regex.is_match(&text))
|
||||
}
|
||||
Self::Any(selectors) => selectors.iter().any(|sel| sel.matches(target)),
|
||||
@ -285,8 +293,8 @@ impl Selector {
|
||||
impl Debug for Selector {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Node(node, dict) => {
|
||||
f.write_str(node.name)?;
|
||||
Self::Elem(elem, dict) => {
|
||||
f.write_str(elem.name())?;
|
||||
if let Some(dict) = dict {
|
||||
f.write_str(".where")?;
|
||||
dict.fmt(f)?;
|
||||
@ -307,21 +315,24 @@ impl Debug for Selector {
|
||||
|
||||
cast_from_value! {
|
||||
Selector: "selector",
|
||||
text: EcoString => Self::text(&text),
|
||||
func: Func => func
|
||||
.element()
|
||||
.ok_or("only element functions can be used as selectors")?
|
||||
.select(),
|
||||
label: Label => Self::Label(label),
|
||||
func: Func => func.select(None)?,
|
||||
text: EcoString => Self::text(&text),
|
||||
regex: Regex => Self::Regex(regex),
|
||||
}
|
||||
|
||||
/// A show rule transformation that can be applied to a match.
|
||||
#[derive(Clone, Hash)]
|
||||
#[derive(Clone, PartialEq, Hash)]
|
||||
pub enum Transform {
|
||||
/// Replacement content.
|
||||
Content(Content),
|
||||
/// A function to apply to the match.
|
||||
Func(Func),
|
||||
/// Apply styles to the content.
|
||||
Style(StyleMap),
|
||||
Style(Styles),
|
||||
}
|
||||
|
||||
impl Debug for Transform {
|
||||
@ -340,11 +351,11 @@ cast_from_value! {
|
||||
func: Func => Self::Func(func),
|
||||
}
|
||||
|
||||
/// A chain of style maps, similar to a linked list.
|
||||
/// A chain of styles, similar to a linked list.
|
||||
///
|
||||
/// A style chain allows to combine properties from multiple style maps in a
|
||||
/// node hierarchy in a non-allocating way. Rather than eagerly merging the
|
||||
/// maps, each access walks the hierarchy from the innermost to the outermost
|
||||
/// A style chain allows to combine properties from multiple style lists in a
|
||||
/// element hierarchy in a non-allocating way. Rather than eagerly merging the
|
||||
/// lists, each access walks the hierarchy from the innermost to the outermost
|
||||
/// map, trying to find a match and then folding it with matches further up the
|
||||
/// chain.
|
||||
#[derive(Default, Clone, Copy, Hash)]
|
||||
@ -356,21 +367,21 @@ pub struct StyleChain<'a> {
|
||||
}
|
||||
|
||||
impl<'a> StyleChain<'a> {
|
||||
/// Start a new style chain with a root map.
|
||||
pub fn new(root: &'a StyleMap) -> Self {
|
||||
/// Start a new style chain with root styles.
|
||||
pub fn new(root: &'a Styles) -> Self {
|
||||
Self { head: &root.0, tail: None }
|
||||
}
|
||||
|
||||
/// Make the given map the first link of this chain.
|
||||
/// Make the given style list the first link of this chain.
|
||||
///
|
||||
/// The resulting style chain contains styles from `map` as well as
|
||||
/// `self`. The ones from `map` take precedence over the ones from
|
||||
/// `self`. For folded properties `map` contributes the inner value.
|
||||
pub fn chain<'b>(&'b self, map: &'b StyleMap) -> StyleChain<'b> {
|
||||
if map.is_empty() {
|
||||
/// The resulting style chain contains styles from `local` as well as
|
||||
/// `self`. The ones from `local` take precedence over the ones from
|
||||
/// `self`. For folded properties `local` contributes the inner value.
|
||||
pub fn chain<'b>(&'b self, local: &'b Styles) -> StyleChain<'b> {
|
||||
if local.is_empty() {
|
||||
*self
|
||||
} else {
|
||||
StyleChain { head: &map.0, tail: Some(self) }
|
||||
StyleChain { head: &local.0, tail: Some(self) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -385,12 +396,12 @@ impl<'a> StyleChain<'a> {
|
||||
/// Cast the first value for the given property in the chain.
|
||||
pub fn get<T: Cast>(
|
||||
self,
|
||||
node: NodeId,
|
||||
func: ElemFunc,
|
||||
name: &'a str,
|
||||
inherent: Option<Value>,
|
||||
default: impl Fn() -> T,
|
||||
) -> T {
|
||||
self.properties::<T>(node, name, inherent)
|
||||
self.properties::<T>(func, name, inherent)
|
||||
.next()
|
||||
.unwrap_or_else(default)
|
||||
}
|
||||
@ -398,18 +409,18 @@ impl<'a> StyleChain<'a> {
|
||||
/// Cast the first value for the given property in the chain.
|
||||
pub fn get_resolve<T: Cast + Resolve>(
|
||||
self,
|
||||
node: NodeId,
|
||||
func: ElemFunc,
|
||||
name: &'a str,
|
||||
inherent: Option<Value>,
|
||||
default: impl Fn() -> T,
|
||||
) -> T::Output {
|
||||
self.get(node, name, inherent, default).resolve(self)
|
||||
self.get(func, name, inherent, default).resolve(self)
|
||||
}
|
||||
|
||||
/// Cast the first value for the given property in the chain.
|
||||
pub fn get_fold<T: Cast + Fold>(
|
||||
self,
|
||||
node: NodeId,
|
||||
func: ElemFunc,
|
||||
name: &'a str,
|
||||
inherent: Option<Value>,
|
||||
default: impl Fn() -> T::Output,
|
||||
@ -424,13 +435,13 @@ impl<'a> StyleChain<'a> {
|
||||
.map(|value| value.fold(next(values, styles, default)))
|
||||
.unwrap_or_else(|| default())
|
||||
}
|
||||
next(self.properties::<T>(node, name, inherent), self, &default)
|
||||
next(self.properties::<T>(func, name, inherent), self, &default)
|
||||
}
|
||||
|
||||
/// Cast the first value for the given property in the chain.
|
||||
pub fn get_resolve_fold<T>(
|
||||
self,
|
||||
node: NodeId,
|
||||
func: ElemFunc,
|
||||
name: &'a str,
|
||||
inherent: Option<Value>,
|
||||
default: impl Fn() -> <T::Output as Fold>::Output,
|
||||
@ -453,7 +464,7 @@ impl<'a> StyleChain<'a> {
|
||||
.map(|value| value.resolve(styles).fold(next(values, styles, default)))
|
||||
.unwrap_or_else(|| default())
|
||||
}
|
||||
next(self.properties::<T>(node, name, inherent), self, &default)
|
||||
next(self.properties::<T>(func, name, inherent), self, &default)
|
||||
}
|
||||
|
||||
/// Iterate over all style recipes in the chain.
|
||||
@ -464,7 +475,7 @@ impl<'a> StyleChain<'a> {
|
||||
/// Iterate over all values for the given property in the chain.
|
||||
pub fn properties<T: Cast + 'a>(
|
||||
self,
|
||||
node: NodeId,
|
||||
func: ElemFunc,
|
||||
name: &'a str,
|
||||
inherent: Option<Value>,
|
||||
) -> impl Iterator<Item = T> + '_ {
|
||||
@ -473,21 +484,21 @@ impl<'a> StyleChain<'a> {
|
||||
.chain(
|
||||
self.entries()
|
||||
.filter_map(Style::property)
|
||||
.filter(move |property| property.is(node, name))
|
||||
.filter(move |property| property.is(func, name))
|
||||
.map(|property| property.value.clone()),
|
||||
)
|
||||
.map(move |value| {
|
||||
value
|
||||
.cast()
|
||||
.unwrap_or_else(|err| panic!("{} (for {}.{})", err, node.name, name))
|
||||
value.cast().unwrap_or_else(|err| {
|
||||
panic!("{} (for {}.{})", err, func.name(), name)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Convert to a style map.
|
||||
pub fn to_map(self) -> StyleMap {
|
||||
let mut suffix = StyleMap::new();
|
||||
pub fn to_map(self) -> Styles {
|
||||
let mut suffix = Styles::new();
|
||||
for link in self.links() {
|
||||
suffix.0.splice(0..0, link.iter().cloned());
|
||||
suffix.apply_slice(link);
|
||||
}
|
||||
suffix
|
||||
}
|
||||
@ -502,13 +513,13 @@ impl<'a> StyleChain<'a> {
|
||||
Links(Some(self))
|
||||
}
|
||||
|
||||
/// Build a style map from the suffix (all links beyond the `len`) of the
|
||||
/// Build owned styles from the suffix (all links beyond the `len`) of the
|
||||
/// chain.
|
||||
fn suffix(self, len: usize) -> StyleMap {
|
||||
let mut suffix = StyleMap::new();
|
||||
fn suffix(self, len: usize) -> Styles {
|
||||
let mut suffix = Styles::new();
|
||||
let take = self.links().count().saturating_sub(len);
|
||||
for link in self.links().take(take) {
|
||||
suffix.0.splice(0..0, link.iter().cloned());
|
||||
suffix.apply_slice(link);
|
||||
}
|
||||
suffix
|
||||
}
|
||||
@ -517,6 +528,16 @@ impl<'a> StyleChain<'a> {
|
||||
fn pop(&mut self) {
|
||||
*self = self.tail.copied().unwrap_or_default();
|
||||
}
|
||||
|
||||
/// Whether two style chains contain the same pointers.
|
||||
fn ptr_eq(self, other: Self) -> bool {
|
||||
std::ptr::eq(self.head, other.head)
|
||||
&& match (self.tail, other.tail) {
|
||||
(Some(a), Some(b)) => std::ptr::eq(a, b),
|
||||
(None, None) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for StyleChain<'_> {
|
||||
@ -530,7 +551,7 @@ impl Debug for StyleChain<'_> {
|
||||
|
||||
impl PartialEq for StyleChain<'_> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
crate::util::hash128(self) == crate::util::hash128(other)
|
||||
self.ptr_eq(*other) || crate::util::hash128(self) == crate::util::hash128(other)
|
||||
}
|
||||
}
|
||||
|
||||
@ -574,7 +595,7 @@ impl<'a> Iterator for Links<'a> {
|
||||
#[derive(Clone, Hash)]
|
||||
pub struct StyleVec<T> {
|
||||
items: Vec<T>,
|
||||
maps: Vec<(StyleMap, usize)>,
|
||||
styles: Vec<(Styles, usize)>,
|
||||
}
|
||||
|
||||
impl<T> StyleVec<T> {
|
||||
@ -588,14 +609,14 @@ impl<T> StyleVec<T> {
|
||||
self.items.len()
|
||||
}
|
||||
|
||||
/// Insert an element in the front. The element will share the style of the
|
||||
/// current first element.
|
||||
/// Insert an item in the front. The item will share the style of the
|
||||
/// current first item.
|
||||
///
|
||||
/// This method has no effect if the vector is empty.
|
||||
pub fn push_front(&mut self, item: T) {
|
||||
if !self.maps.is_empty() {
|
||||
if !self.styles.is_empty() {
|
||||
self.items.insert(0, item);
|
||||
self.maps[0].1 += 1;
|
||||
self.styles[0].1 += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -606,14 +627,14 @@ impl<T> StyleVec<T> {
|
||||
{
|
||||
StyleVec {
|
||||
items: self.items.iter().map(f).collect(),
|
||||
maps: self.maps.clone(),
|
||||
styles: self.styles.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over references to the contained items and associated style maps.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&T, &StyleMap)> + '_ {
|
||||
/// Iterate over references to the contained items and associated styles.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&T, &Styles)> + '_ {
|
||||
self.items().zip(
|
||||
self.maps
|
||||
self.styles
|
||||
.iter()
|
||||
.flat_map(|(map, count)| iter::repeat(map).take(*count)),
|
||||
)
|
||||
@ -624,13 +645,13 @@ impl<T> StyleVec<T> {
|
||||
self.items.iter()
|
||||
}
|
||||
|
||||
/// Iterate over the contained maps. Note that zipping this with `items()`
|
||||
/// does not yield the same result as calling `iter()` because this method
|
||||
/// only returns maps once that are shared by consecutive items. This method
|
||||
/// is designed for use cases where you want to check, for example, whether
|
||||
/// any of the maps fulfills a specific property.
|
||||
pub fn styles(&self) -> impl Iterator<Item = &StyleMap> {
|
||||
self.maps.iter().map(|(map, _)| map)
|
||||
/// Iterate over the contained style lists. Note that zipping this with
|
||||
/// `items()` does not yield the same result as calling `iter()` because
|
||||
/// this method only returns lists once that are shared by consecutive
|
||||
/// items. This method is designed for use cases where you want to check,
|
||||
/// for example, whether any of the lists fulfills a specific property.
|
||||
pub fn styles(&self) -> impl Iterator<Item = &Styles> {
|
||||
self.styles.iter().map(|(map, _)| map)
|
||||
}
|
||||
}
|
||||
|
||||
@ -639,35 +660,35 @@ impl StyleVec<Content> {
|
||||
self.items
|
||||
.into_iter()
|
||||
.zip(
|
||||
self.maps
|
||||
self.styles
|
||||
.iter()
|
||||
.flat_map(|(map, count)| iter::repeat(map).take(*count)),
|
||||
)
|
||||
.map(|(content, map)| content.styled_with_map(map.clone()))
|
||||
.map(|(content, styles)| content.styled_with_map(styles.clone()))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for StyleVec<T> {
|
||||
fn default() -> Self {
|
||||
Self { items: vec![], maps: vec![] }
|
||||
Self { items: vec![], styles: vec![] }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FromIterator<T> for StyleVec<T> {
|
||||
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
|
||||
let items: Vec<_> = iter.into_iter().collect();
|
||||
let maps = vec![(StyleMap::new(), items.len())];
|
||||
Self { items, maps }
|
||||
let styles = vec![(Styles::new(), items.len())];
|
||||
Self { items, styles }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for StyleVec<T> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
f.debug_list()
|
||||
.entries(self.iter().map(|(item, map)| {
|
||||
.entries(self.iter().map(|(item, styles)| {
|
||||
crate::util::debug(|f| {
|
||||
map.fmt(f)?;
|
||||
styles.fmt(f)?;
|
||||
item.fmt(f)
|
||||
})
|
||||
}))
|
||||
@ -708,7 +729,7 @@ impl<'a, T> StyleVecBuilder<'a, T> {
|
||||
}
|
||||
|
||||
/// Iterate over the contained items.
|
||||
pub fn items(&self) -> std::slice::Iter<'_, T> {
|
||||
pub fn elems(&self) -> std::slice::Iter<'_, T> {
|
||||
self.items.iter()
|
||||
}
|
||||
|
||||
@ -743,13 +764,13 @@ impl<'a, T> StyleVecBuilder<'a, T> {
|
||||
}
|
||||
}
|
||||
|
||||
let maps = self
|
||||
let styles = self
|
||||
.chains
|
||||
.into_iter()
|
||||
.map(|(chain, count)| (chain.suffix(shared), count))
|
||||
.collect();
|
||||
|
||||
(StyleVec { items: self.items, maps }, trunk)
|
||||
(StyleVec { items: self.items, styles }, trunk)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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() })
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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 == '-'
|
||||
}
|
||||
|
||||
|
@ -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::*;
|
||||
|
@ -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};
|
||||
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user