summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-10-28 16:49:41 +0200
committerLaurenz <laurmaedje@gmail.com>2022-10-31 08:57:44 +0100
commit66ad023519e5250f88f9066a2607efe5442a7b76 (patch)
treeb858ec6e488edb4cdd639fd4078adab42c00c33f
parent95e9134a3c7d7a14d8c8928413fdffc665671895 (diff)
Split up some files
-rw-r--r--src/model/content.rs483
-rw-r--r--src/model/fold.rs81
-rw-r--r--src/model/mod.rs7
-rw-r--r--src/model/property.rs167
-rw-r--r--src/model/realize.rs484
-rw-r--r--src/model/resolve.rs84
-rw-r--r--tools/support/typst.tmLanguage.json14
7 files changed, 674 insertions, 646 deletions
diff --git a/src/model/content.rs b/src/model/content.rs
index 3ee59ecc..81c3c729 100644
--- a/src/model/content.rs
+++ b/src/model/content.rs
@@ -1,24 +1,20 @@
use std::fmt::{self, Debug, Formatter};
use std::hash::Hash;
use std::iter::Sum;
-use std::mem;
use std::ops::{Add, AddAssign};
use std::sync::Arc;
use comemo::Tracked;
-use typed_arena::Arena;
use super::{
- Barrier, CollapsingBuilder, Dict, Interruption, Key, Layout, LayoutNode, Property,
- Regions, Selector, Show, ShowNode, StyleChain, StyleEntry, StyleMap, StyleVecBuilder,
- Target,
+ Builder, Dict, Key, Layout, LayoutNode, Property, Regions, Scratch, Selector, Show,
+ ShowNode, StyleChain, StyleEntry, StyleMap,
};
use crate::diag::{SourceResult, StrResult};
use crate::frame::{Frame, Role};
-use crate::geom::{Abs, Numeric};
-use crate::library::layout::{FlowChild, FlowNode, PageNode, PlaceNode, Spacing};
-use crate::library::structure::{DocNode, ListItem, ListNode, DESC, ENUM, LIST};
-use crate::library::text::{ParChild, ParNode};
+use crate::geom::Abs;
+use crate::library::layout::{PageNode, Spacing};
+use crate::library::structure::ListItem;
use crate::util::EcoString;
use crate::World;
@@ -301,472 +297,3 @@ impl Sum for Content {
Self::sequence(iter.collect())
}
}
-
-/// Builds a document or a flow node from content.
-pub(super) struct Builder<'a> {
- /// The core context.
- world: Tracked<'a, dyn World>,
- /// Scratch arenas for building.
- scratch: &'a Scratch<'a>,
- /// The current document building state.
- doc: Option<DocBuilder<'a>>,
- /// The current flow building state.
- flow: FlowBuilder<'a>,
- /// The current paragraph building state.
- par: ParBuilder<'a>,
- /// The current list building state.
- list: ListBuilder<'a>,
-}
-
-/// Temporary storage arenas for building.
-#[derive(Default)]
-pub(super) struct Scratch<'a> {
- /// An arena where intermediate style chains are stored.
- styles: Arena<StyleChain<'a>>,
- /// An arena where intermediate content resulting from show rules is stored.
- templates: Arena<Content>,
-}
-
-impl<'a> Builder<'a> {
- pub fn new(
- world: Tracked<'a, dyn World>,
- scratch: &'a Scratch<'a>,
- top: bool,
- ) -> Self {
- Self {
- world,
- scratch,
- doc: top.then(|| DocBuilder::default()),
- flow: FlowBuilder::default(),
- par: ParBuilder::default(),
- list: ListBuilder::default(),
- }
- }
-
- pub fn into_doc(
- mut self,
- styles: StyleChain<'a>,
- ) -> SourceResult<(DocNode, StyleChain<'a>)> {
- self.interrupt(Interruption::Page, styles, true)?;
- let (pages, shared) = self.doc.unwrap().pages.finish();
- Ok((DocNode(pages), shared))
- }
-
- pub fn into_flow(
- mut self,
- styles: StyleChain<'a>,
- ) -> SourceResult<(FlowNode, StyleChain<'a>)> {
- self.interrupt(Interruption::Par, styles, false)?;
- let (children, shared) = self.flow.0.finish();
- Ok((FlowNode(children), shared))
- }
-
- pub fn accept(
- &mut self,
- content: &'a Content,
- styles: StyleChain<'a>,
- ) -> SourceResult<()> {
- match content {
- Content::Empty => return Ok(()),
- Content::Text(text) => {
- if let Some(realized) = styles.apply(self.world, Target::Text(text))? {
- let stored = self.scratch.templates.alloc(realized);
- return self.accept(stored, styles);
- }
- }
-
- Content::Show(node, _) => return self.show(node, styles),
- Content::Styled(styled) => return self.styled(styled, styles),
- Content::Sequence(seq) => return self.sequence(seq, styles),
-
- _ => {}
- }
-
- if self.list.accept(content, styles) {
- return Ok(());
- }
-
- self.interrupt(Interruption::List, styles, false)?;
-
- if let Content::Item(_) = content {
- self.list.accept(content, styles);
- return Ok(());
- }
-
- if self.par.accept(content, styles) {
- return Ok(());
- }
-
- self.interrupt(Interruption::Par, styles, false)?;
-
- if self.flow.accept(content, styles) {
- return Ok(());
- }
-
- let keep = matches!(content, Content::Pagebreak { weak: false });
- self.interrupt(Interruption::Page, styles, keep)?;
-
- if let Some(doc) = &mut self.doc {
- doc.accept(content, styles);
- }
-
- // We might want to issue a warning or error for content that wasn't
- // handled (e.g. a pagebreak in a flow building process). However, we
- // don't have the spans here at the moment.
- Ok(())
- }
-
- fn show(&mut self, node: &ShowNode, styles: StyleChain<'a>) -> SourceResult<()> {
- if let Some(mut realized) = styles.apply(self.world, Target::Node(node))? {
- let mut map = StyleMap::new();
- let barrier = Barrier::new(node.id());
- map.push(StyleEntry::Barrier(barrier));
- map.push(StyleEntry::Barrier(barrier));
- realized = realized.styled_with_map(map);
- let stored = self.scratch.templates.alloc(realized);
- self.accept(stored, styles)?;
- }
- Ok(())
- }
-
- fn styled(
- &mut self,
- (content, map): &'a (Content, StyleMap),
- styles: StyleChain<'a>,
- ) -> SourceResult<()> {
- let stored = self.scratch.styles.alloc(styles);
- let styles = map.chain(stored);
- let intr = map.interruption();
-
- if let Some(intr) = intr {
- self.interrupt(intr, styles, false)?;
- }
-
- self.accept(content, styles)?;
-
- if let Some(intr) = intr {
- self.interrupt(intr, styles, true)?;
- }
-
- Ok(())
- }
-
- fn interrupt(
- &mut self,
- intr: Interruption,
- styles: StyleChain<'a>,
- keep: bool,
- ) -> SourceResult<()> {
- if intr >= Interruption::List && !self.list.is_empty() {
- mem::take(&mut self.list).finish(self)?;
- }
-
- if intr >= Interruption::Par {
- if !self.par.is_empty() {
- mem::take(&mut self.par).finish(self);
- }
- }
-
- if intr >= Interruption::Page {
- if let Some(doc) = &mut self.doc {
- if !self.flow.is_empty() || (doc.keep_next && keep) {
- mem::take(&mut self.flow).finish(doc, styles);
- }
- doc.keep_next = !keep;
- }
- }
-
- Ok(())
- }
-
- fn sequence(
- &mut self,
- seq: &'a [Content],
- styles: StyleChain<'a>,
- ) -> SourceResult<()> {
- for content in seq {
- self.accept(content, styles)?;
- }
- Ok(())
- }
-}
-
-/// Accepts pagebreaks and pages.
-struct DocBuilder<'a> {
- /// The page runs built so far.
- pages: StyleVecBuilder<'a, PageNode>,
- /// Whether to keep a following page even if it is empty.
- keep_next: bool,
-}
-
-impl<'a> DocBuilder<'a> {
- fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) {
- match content {
- Content::Pagebreak { weak } => {
- self.keep_next = !weak;
- }
- Content::Page(page) => {
- self.pages.push(page.clone(), styles);
- self.keep_next = false;
- }
- _ => {}
- }
- }
-}
-
-impl Default for DocBuilder<'_> {
- fn default() -> Self {
- Self {
- pages: StyleVecBuilder::new(),
- keep_next: true,
- }
- }
-}
-
-/// Accepts flow content.
-#[derive(Default)]
-struct FlowBuilder<'a>(CollapsingBuilder<'a, FlowChild>);
-
-impl<'a> FlowBuilder<'a> {
- fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
- // Weak flow elements:
- // Weakness | Element
- // 0 | weak colbreak
- // 1 | weak fractional spacing
- // 2 | weak spacing
- // 3 | generated weak spacing
- // 4 | generated weak fractional spacing
- // 5 | par spacing
-
- match content {
- Content::Parbreak => {}
- Content::Colbreak { weak } => {
- if *weak {
- self.0.weak(FlowChild::Colbreak, styles, 0);
- } else {
- self.0.destructive(FlowChild::Colbreak, styles);
- }
- }
- &Content::Vertical { amount, weak, generated } => {
- let child = FlowChild::Spacing(amount);
- let frac = amount.is_fractional();
- if weak {
- let weakness = 1 + u8::from(frac) + 2 * u8::from(generated);
- self.0.weak(child, styles, weakness);
- } else if frac {
- self.0.destructive(child, styles);
- } else {
- self.0.ignorant(child, styles);
- }
- }
- Content::Block(node) => {
- let child = FlowChild::Node(node.clone());
- if node.is::<PlaceNode>() {
- self.0.ignorant(child, styles);
- } else {
- self.0.supportive(child, styles);
- }
- }
- _ => return false,
- }
-
- true
- }
-
- fn par(&mut self, par: ParNode, styles: StyleChain<'a>, indent: bool) {
- let amount = if indent && !styles.get(ParNode::SPACING_AND_INDENT) {
- styles.get(ParNode::LEADING).into()
- } else {
- styles.get(ParNode::SPACING).into()
- };
-
- self.0.weak(FlowChild::Spacing(amount), styles, 5);
- self.0.supportive(FlowChild::Node(par.pack()), styles);
- self.0.weak(FlowChild::Spacing(amount), styles, 5);
- }
-
- fn finish(self, doc: &mut DocBuilder<'a>, styles: StyleChain<'a>) {
- let (flow, shared) = self.0.finish();
- let styles = if flow.is_empty() { styles } else { shared };
- let node = PageNode(FlowNode(flow).pack());
- doc.pages.push(node, styles);
- }
-
- fn is_empty(&self) -> bool {
- self.0.is_empty()
- }
-}
-
-/// Accepts paragraph content.
-#[derive(Default)]
-struct ParBuilder<'a>(CollapsingBuilder<'a, ParChild>);
-
-impl<'a> ParBuilder<'a> {
- fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
- // Weak par elements:
- // Weakness | Element
- // 0 | weak fractional spacing
- // 1 | weak spacing
- // 2 | space
-
- match content {
- Content::Space => {
- self.0.weak(ParChild::Text(' '.into()), styles, 2);
- }
- &Content::Linebreak { justify } => {
- let c = if justify { '\u{2028}' } else { '\n' };
- self.0.destructive(ParChild::Text(c.into()), styles);
- }
- &Content::Horizontal { amount, weak } => {
- let child = ParChild::Spacing(amount);
- let frac = amount.is_fractional();
- if weak {
- let weakness = u8::from(!frac);
- self.0.weak(child, styles, weakness);
- } else if frac {
- self.0.destructive(child, styles);
- } else {
- self.0.ignorant(child, styles);
- }
- }
- &Content::Quote { double } => {
- self.0.supportive(ParChild::Quote { double }, styles);
- }
- Content::Text(text) => {
- self.0.supportive(ParChild::Text(text.clone()), styles);
- }
- Content::Inline(node) => {
- self.0.supportive(ParChild::Node(node.clone()), styles);
- }
- _ => return false,
- }
-
- true
- }
-
- fn finish(self, parent: &mut Builder<'a>) {
- let (mut children, shared) = self.0.finish();
- if children.is_empty() {
- return;
- }
-
- // Paragraph indent should only apply if the paragraph starts with
- // text and follows directly after another paragraph.
- let indent = shared.get(ParNode::INDENT);
- if !indent.is_zero()
- && children
- .items()
- .find_map(|child| match child {
- ParChild::Spacing(_) => None,
- ParChild::Text(_) | ParChild::Quote { .. } => Some(true),
- ParChild::Node(_) => Some(false),
- })
- .unwrap_or_default()
- && parent
- .flow
- .0
- .items()
- .rev()
- .find_map(|child| match child {
- FlowChild::Spacing(_) => None,
- FlowChild::Node(node) => Some(node.is::<ParNode>()),
- FlowChild::Colbreak => Some(false),
- })
- .unwrap_or_default()
- {
- children.push_front(ParChild::Spacing(indent.into()));
- }
-
- parent.flow.par(ParNode(children), shared, !indent.is_zero());
- }
-
- fn is_empty(&self) -> bool {
- self.0.is_empty()
- }
-}
-
-/// Accepts list / enum items, spaces, paragraph breaks.
-struct ListBuilder<'a> {
- /// The list items collected so far.
- items: StyleVecBuilder<'a, ListItem>,
- /// Whether the list contains no paragraph breaks.
- tight: bool,
- /// Whether the list can be attached.
- attachable: bool,
- /// Trailing content for which it is unclear whether it is part of the list.
- staged: Vec<(&'a Content, StyleChain<'a>)>,
-}
-
-impl<'a> ListBuilder<'a> {
- fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
- if self.items.is_empty() {
- match content {
- Content::Space => {}
- Content::Item(_) => {}
- Content::Parbreak => self.attachable = false,
- _ => self.attachable = true,
- }
- }
-
- match content {
- Content::Item(item)
- if self
- .items
- .items()
- .next()
- .map_or(true, |first| item.kind() == first.kind()) =>
- {
- self.items.push(item.clone(), styles);
- self.tight &= self.staged.drain(..).all(|(t, _)| *t != Content::Parbreak);
- }
- Content::Space | Content::Parbreak if !self.items.is_empty() => {
- self.staged.push((content, styles));
- }
- _ => return false,
- }
-
- true
- }
-
- fn finish(self, parent: &mut Builder<'a>) -> SourceResult<()> {
- let (items, shared) = self.items.finish();
- let kind = match items.items().next() {
- Some(item) => item.kind(),
- None => return Ok(()),
- };
-
- let tight = self.tight;
- let attached = tight && self.attachable;
- let content = match kind {
- LIST => Content::show(ListNode::<LIST> { tight, attached, items }),
- ENUM => Content::show(ListNode::<ENUM> { tight, attached, items }),
- DESC | _ => Content::show(ListNode::<DESC> { tight, attached, items }),
- };
-
- let stored = parent.scratch.templates.alloc(content);
- parent.accept(stored, shared)?;
-
- for (content, styles) in self.staged {
- parent.accept(content, styles)?;
- }
-
- parent.list.attachable = true;
-
- Ok(())
- }
-
- fn is_empty(&self) -> bool {
- self.items.is_empty()
- }
-}
-
-impl Default for ListBuilder<'_> {
- fn default() -> Self {
- Self {
- items: StyleVecBuilder::default(),
- tight: true,
- attachable: true,
- staged: vec![],
- }
- }
-}
diff --git a/src/model/fold.rs b/src/model/fold.rs
new file mode 100644
index 00000000..bc85be01
--- /dev/null
+++ b/src/model/fold.rs
@@ -0,0 +1,81 @@
+use super::Smart;
+use crate::geom::{Abs, Corners, Length, Rel, Sides};
+
+/// A property that is folded to determine its final value.
+pub trait Fold {
+ /// The type of the folded output.
+ type Output;
+
+ /// Fold this inner value with an outer folded value.
+ fn fold(self, outer: Self::Output) -> Self::Output;
+}
+
+impl<T> Fold for Option<T>
+where
+ T: Fold,
+ T::Output: Default,
+{
+ type Output = Option<T::Output>;
+
+ fn fold(self, outer: Self::Output) -> Self::Output {
+ self.map(|inner| inner.fold(outer.unwrap_or_default()))
+ }
+}
+
+impl<T> Fold for Smart<T>
+where
+ T: Fold,
+ T::Output: Default,
+{
+ type Output = Smart<T::Output>;
+
+ fn fold(self, outer: Self::Output) -> Self::Output {
+ self.map(|inner| inner.fold(outer.unwrap_or_default()))
+ }
+}
+
+impl<T> Fold for Sides<T>
+where
+ T: Fold,
+{
+ type Output = Sides<T::Output>;
+
+ fn fold(self, outer: Self::Output) -> Self::Output {
+ self.zip(outer, |inner, outer| inner.fold(outer))
+ }
+}
+
+impl Fold for Sides<Option<Rel<Abs>>> {
+ type Output = Sides<Rel<Abs>>;
+
+ fn fold(self, outer: Self::Output) -> Self::Output {
+ self.zip(outer, |inner, outer| inner.unwrap_or(outer))
+ }
+}
+
+impl Fold for Sides<Option<Smart<Rel<Length>>>> {
+ type Output = Sides<Smart<Rel<Length>>>;
+
+ fn fold(self, outer: Self::Output) -> Self::Output {
+ self.zip(outer, |inner, outer| inner.unwrap_or(outer))
+ }
+}
+
+impl<T> Fold for Corners<T>
+where
+ T: Fold,
+{
+ type Output = Corners<T::Output>;
+
+ fn fold(self, outer: Self::Output) -> Self::Output {
+ self.zip(outer, |inner, outer| inner.fold(outer))
+ }
+}
+
+impl Fold for Corners<Option<Rel<Abs>>> {
+ type Output = Corners<Rel<Abs>>;
+
+ fn fold(self, outer: Self::Output) -> Self::Output {
+ self.zip(outer, |inner, outer| inner.unwrap_or(outer))
+ }
+}
diff --git a/src/model/mod.rs b/src/model/mod.rs
index 0ea5cbdd..52d8c461 100644
--- a/src/model/mod.rs
+++ b/src/model/mod.rs
@@ -21,10 +21,13 @@ mod str;
mod value;
mod args;
mod capture;
+mod fold;
mod func;
pub mod methods;
pub mod ops;
mod raw;
+mod realize;
+mod resolve;
mod scope;
mod vm;
@@ -37,14 +40,18 @@ pub use collapse::*;
pub use content::*;
pub use dict::*;
pub use eval::*;
+pub use fold::*;
pub use func::*;
pub use layout::*;
pub use property::*;
pub use raw::*;
pub use recipe::*;
+pub use resolve::*;
pub use scope::*;
pub use show::*;
pub use styles::*;
pub use typst_macros::node;
pub use value::*;
pub use vm::*;
+
+use realize::*;
diff --git a/src/model/property.rs b/src/model/property.rs
index 2e2a76a5..acd31b8a 100644
--- a/src/model/property.rs
+++ b/src/model/property.rs
@@ -5,18 +5,17 @@ use std::sync::Arc;
use comemo::Prehashed;
-use super::{Interruption, NodeId, Smart, StyleChain};
-use crate::geom::{Abs, Axes, Corners, Em, Length, Numeric, Rel, Sides};
+use super::{Interruption, NodeId, StyleChain};
use crate::library::layout::PageNode;
use crate::library::structure::{DescNode, EnumNode, ListNode};
-use crate::library::text::{ParNode, TextNode};
+use crate::library::text::ParNode;
use crate::util::ReadableTypeId;
/// A style property originating from a set rule or constructor.
#[derive(Clone, Hash)]
pub struct Property {
/// The id of the property's [key](Key).
- pub key: KeyId,
+ key: KeyId,
/// The id of the node the property belongs to.
pub node: NodeId,
/// Whether the property should only affect the first node down the
@@ -154,166 +153,6 @@ pub trait Key<'a>: Copy + 'static {
) -> Self::Output;
}
-/// A property that is resolved with other properties from the style chain.
-pub trait Resolve {
- /// The type of the resolved output.
- type Output;
-
- /// Resolve the value using the style chain.
- fn resolve(self, styles: StyleChain) -> Self::Output;
-}
-
-impl Resolve for Em {
- type Output = Abs;
-
- fn resolve(self, styles: StyleChain) -> Self::Output {
- if self.is_zero() {
- Abs::zero()
- } else {
- self.at(styles.get(TextNode::SIZE))
- }
- }
-}
-
-impl Resolve for Length {
- type Output = Abs;
-
- fn resolve(self, styles: StyleChain) -> Self::Output {
- self.abs + self.em.resolve(styles)
- }
-}
-
-impl<T: Resolve> Resolve for Option<T> {
- type Output = Option<T::Output>;
-
- fn resolve(self, styles: StyleChain) -> Self::Output {
- self.map(|v| v.resolve(styles))
- }
-}
-
-impl<T: Resolve> Resolve for Smart<T> {
- type Output = Smart<T::Output>;
-
- fn resolve(self, styles: StyleChain) -> Self::Output {
- self.map(|v| v.resolve(styles))
- }
-}
-
-impl<T: Resolve> Resolve for Axes<T> {
- type Output = Axes<T::Output>;
-
- fn resolve(self, styles: StyleChain) -> Self::Output {
- self.map(|v| v.resolve(styles))
- }
-}
-
-impl<T: Resolve> Resolve for Sides<T> {
- type Output = Sides<T::Output>;
-
- fn resolve(self, styles: StyleChain) -> Self::Output {
- self.map(|v| v.resolve(styles))
- }
-}
-
-impl<T: Resolve> Resolve for Corners<T> {
- type Output = Corners<T::Output>;
-
- fn resolve(self, styles: StyleChain) -> Self::Output {
- self.map(|v| v.resolve(styles))
- }
-}
-
-impl<T> Resolve for Rel<T>
-where
- T: Resolve + Numeric,
- <T as Resolve>::Output: Numeric,
-{
- type Output = Rel<<T as Resolve>::Output>;
-
- fn resolve(self, styles: StyleChain) -> Self::Output {
- self.map(|abs| abs.resolve(styles))
- }
-}
-
-/// A property that is folded to determine its final value.
-pub trait Fold {
- /// The type of the folded output.
- type Output;
-
- /// Fold this inner value with an outer folded value.
- fn fold(self, outer: Self::Output) -> Self::Output;
-}
-
-impl<T> Fold for Option<T>
-where
- T: Fold,
- T::Output: Default,
-{
- type Output = Option<T::Output>;
-
- fn fold(self, outer: Self::Output) -> Self::Output {
- self.map(|inner| inner.fold(outer.unwrap_or_default()))
- }
-}
-
-impl<T> Fold for Smart<T>
-where
- T: Fold,
- T::Output: Default,
-{
- type Output = Smart<T::Output>;
-
- fn fold(self, outer: Self::Output) -> Self::Output {
- self.map(|inner| inner.fold(outer.unwrap_or_default()))
- }
-}
-
-impl<T> Fold for Sides<T>
-where
- T: Fold,
-{
- type Output = Sides<T::Output>;
-
- fn fold(self, outer: Self::Output) -> Self::Output {
- self.zip(outer, |inner, outer| inner.fold(outer))
- }
-}
-
-impl Fold for Sides<Option<Rel<Abs>>> {
- type Output = Sides<Rel<Abs>>;
-
- fn fold(self, outer: Self::Output) -> Self::Output {
- self.zip(outer, |inner, outer| inner.unwrap_or(outer))
- }
-}
-
-impl Fold for Sides<Option<Smart<Rel<Length>>>> {
- type Output = Sides<Smart<Rel<Length>>>;
-
- fn fold(self, outer: Self::Output) -> Self::Output {
- self.zip(outer, |inner, outer| inner.unwrap_or(outer))
- }
-}
-
-impl<T> Fold for Corners<T>
-where
- T: Fold,
-{
- type Output = Corners<T::Output>;
-
- fn fold(self, outer: Self::Output) -> Self::Output {
- self.zip(outer, |inner, outer| inner.fold(outer))
- }
-}
-
-impl Fold for Corners<Option<Rel<Abs>>> {
- type Output = Corners<Rel<Abs>>;
-
- fn fold(self, outer: Self::Output) -> Self::Output {
- self.zip(outer, |inner, outer| inner.unwrap_or(outer))
- }
-}
-
/// A scoped property barrier.
///
/// Barriers interact with [scoped](super::StyleMap::scoped) styles: A scoped
diff --git a/src/model/realize.rs b/src/model/realize.rs
new file mode 100644
index 00000000..895c4f33
--- /dev/null
+++ b/src/model/realize.rs
@@ -0,0 +1,484 @@
+use std::mem;
+
+use comemo::Tracked;
+use typed_arena::Arena;
+
+use super::{
+ Barrier, CollapsingBuilder, Content, Interruption, Layout, ShowNode, StyleChain,
+ StyleEntry, StyleMap, StyleVecBuilder, Target,
+};
+use crate::diag::SourceResult;
+use crate::geom::Numeric;
+use crate::library::layout::{FlowChild, FlowNode, PageNode, PlaceNode};
+use crate::library::structure::{DocNode, ListItem, ListNode, DESC, ENUM, LIST};
+use crate::library::text::{ParChild, ParNode};
+use crate::World;
+
+/// Builds a document or a flow node from content.
+pub(super) struct Builder<'a> {
+ /// The core context.
+ world: Tracked<'a, dyn World>,
+ /// Scratch arenas for building.
+ scratch: &'a Scratch<'a>,
+ /// The current document building state.
+ doc: Option<DocBuilder<'a>>,
+ /// The current flow building state.
+ flow: FlowBuilder<'a>,
+ /// The current paragraph building state.
+ par: ParBuilder<'a>,
+ /// The current list building state.
+ list: ListBuilder<'a>,
+}
+
+/// Temporary storage arenas for building.
+#[derive(Default)]
+pub(super) struct Scratch<'a> {
+ /// An arena where intermediate style chains are stored.
+ styles: Arena<StyleChain<'a>>,
+ /// An arena where intermediate content resulting from show rules is stored.
+ templates: Arena<Content>,
+}
+
+impl<'a> Builder<'a> {
+ pub fn new(
+ world: Tracked<'a, dyn World>,
+ scratch: &'a Scratch<'a>,
+ top: bool,
+ ) -> Self {
+ Self {
+ world,
+ scratch,
+ doc: top.then(|| DocBuilder::default()),
+ flow: FlowBuilder::default(),
+ par: ParBuilder::default(),
+ list: ListBuilder::default(),
+ }
+ }
+
+ pub fn into_doc(
+ mut self,
+ styles: StyleChain<'a>,
+ ) -> SourceResult<(DocNode, StyleChain<'a>)> {
+ self.interrupt(Interruption::Page, styles, true)?;
+ let (pages, shared) = self.doc.unwrap().pages.finish();
+ Ok((DocNode(pages), shared))
+ }
+
+ pub fn into_flow(
+ mut self,
+ styles: StyleChain<'a>,
+ ) -> SourceResult<(FlowNode, StyleChain<'a>)> {
+ self.interrupt(Interruption::Par, styles, false)?;
+ let (children, shared) = self.flow.0.finish();
+ Ok((FlowNode(children), shared))
+ }
+
+ pub fn accept(
+ &mut self,
+ content: &'a Content,
+ styles: StyleChain<'a>,
+ ) -> SourceResult<()> {
+ match content {
+ Content::Empty => return Ok(()),
+ Content::Text(text) => {
+ if let Some(realized) = styles.apply(self.world, Target::Text(text))? {
+ let stored = self.scratch.templates.alloc(realized);
+ return self.accept(stored, styles);
+ }
+ }
+
+ Content::Show(node, _) => return self.show(node, styles),
+ Content::Styled(styled) => return self.styled(styled, styles),
+ Content::Sequence(seq) => return self.sequence(seq, styles),
+
+ _ => {}
+ }
+
+ if self.list.accept(content, styles) {
+ return Ok(());
+ }
+
+ self.interrupt(Interruption::List, styles, false)?;
+
+ if let Content::Item(_) = content {
+ self.list.accept(content, styles);
+ return Ok(());
+ }
+
+ if self.par.accept(content, styles) {
+ return Ok(());
+ }
+
+ self.interrupt(Interruption::Par, styles, false)?;
+
+ if self.flow.accept(content, styles) {
+ return Ok(());
+ }
+
+ let keep = matches!(content, Content::Pagebreak { weak: false });
+ self.interrupt(Interruption::Page, styles, keep)?;
+
+ if let Some(doc) = &mut self.doc {
+ doc.accept(content, styles);
+ }
+
+ // We might want to issue a warning or error for content that wasn't
+ // handled (e.g. a pagebreak in a flow building process). However, we
+ // don't have the spans here at the moment.
+ Ok(())
+ }
+
+ fn show(&mut self, node: &ShowNode, styles: StyleChain<'a>) -> SourceResult<()> {
+ if let Some(mut realized) = styles.apply(self.world, Target::Node(node))? {
+ let mut map = StyleMap::new();
+ let barrier = Barrier::new(node.id());
+ map.push(StyleEntry::Barrier(barrier));
+ map.push(StyleEntry::Barrier(barrier));
+ realized = realized.styled_with_map(map);
+ let stored = self.scratch.templates.alloc(realized);
+ self.accept(stored, styles)?;
+ }
+ Ok(())
+ }
+
+ fn styled(
+ &mut self,
+ (content, map): &'a (Content, StyleMap),
+ styles: StyleChain<'a>,
+ ) -> SourceResult<()> {
+ let stored = self.scratch.styles.alloc(styles);
+ let styles = map.chain(stored);
+ let intr = map.interruption();
+
+ if let Some(intr) = intr {
+ self.interrupt(intr, styles, false)?;
+ }
+
+ self.accept(content, styles)?;
+
+ if let Some(intr) = intr {
+ self.interrupt(intr, styles, true)?;
+ }
+
+ Ok(())
+ }
+
+ fn interrupt(
+ &mut self,
+ intr: Interruption,
+ styles: StyleChain<'a>,
+ keep: bool,
+ ) -> SourceResult<()> {
+ if intr >= Interruption::List && !self.list.is_empty() {
+ mem::take(&mut self.list).finish(self)?;
+ }
+
+ if intr >= Interruption::Par {
+ if !self.par.is_empty() {
+ mem::take(&mut self.par).finish(self);
+ }
+ }
+
+ if intr >= Interruption::Page {
+ if let Some(doc) = &mut self.doc {
+ if !self.flow.is_empty() || (doc.keep_next && keep) {
+ mem::take(&mut self.flow).finish(doc, styles);
+ }
+ doc.keep_next = !keep;
+ }
+ }
+
+ Ok(())
+ }
+
+ fn sequence(
+ &mut self,
+ seq: &'a [Content],
+ styles: StyleChain<'a>,
+ ) -> SourceResult<()> {
+ for content in seq {
+ self.accept(content, styles)?;
+ }
+ Ok(())
+ }
+}
+
+/// Accepts pagebreaks and pages.
+struct DocBuilder<'a> {
+ /// The page runs built so far.
+ pages: StyleVecBuilder<'a, PageNode>,
+ /// Whether to keep a following page even if it is empty.
+ keep_next: bool,
+}
+
+impl<'a> DocBuilder<'a> {
+ fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) {
+ match content {
+ Content::Pagebreak { weak } => {
+ self.keep_next = !weak;
+ }
+ Content::Page(page) => {
+ self.pages.push(page.clone(), styles);
+ self.keep_next = false;
+ }
+ _ => {}
+ }
+ }
+}
+
+impl Default for DocBuilder<'_> {
+ fn default() -> Self {
+ Self {
+ pages: StyleVecBuilder::new(),
+ keep_next: true,
+ }
+ }
+}
+
+/// Accepts flow content.
+#[derive(Default)]
+struct FlowBuilder<'a>(CollapsingBuilder<'a, FlowChild>);
+
+impl<'a> FlowBuilder<'a> {
+ fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
+ // Weak flow elements:
+ // Weakness | Element
+ // 0 | weak colbreak
+ // 1 | weak fractional spacing
+ // 2 | weak spacing
+ // 3 | generated weak spacing
+ // 4 | generated weak fractional spacing
+ // 5 | par spacing
+
+ match content {
+ Content::Parbreak => {}
+ Content::Colbreak { weak } => {
+ if *weak {
+ self.0.weak(FlowChild::Colbreak, styles, 0);
+ } else {
+ self.0.destructive(FlowChild::Colbreak, styles);
+ }
+ }
+ &Content::Vertical { amount, weak, generated } => {
+ let child = FlowChild::Spacing(amount);
+ let frac = amount.is_fractional();
+ if weak {
+ let weakness = 1 + u8::from(frac) + 2 * u8::from(generated);
+ self.0.weak(child, styles, weakness);
+ } else if frac {
+ self.0.destructive(child, styles);
+ } else {
+ self.0.ignorant(child, styles);
+ }
+ }
+ Content::Block(node) => {
+ let child = FlowChild::Node(node.clone());
+ if node.is::<PlaceNode>() {
+ self.0.ignorant(child, styles);
+ } else {
+ self.0.supportive(child, styles);
+ }
+ }
+ _ => return false,
+ }
+
+ true
+ }
+
+ fn par(&mut self, par: ParNode, styles: StyleChain<'a>, indent: bool) {
+ let amount = if indent && !styles.get(ParNode::SPACING_AND_INDENT) {
+ styles.get(ParNode::LEADING).into()
+ } else {
+ styles.get(ParNode::SPACING).into()
+ };
+
+ self.0.weak(FlowChild::Spacing(amount), styles, 5);
+ self.0.supportive(FlowChild::Node(par.pack()), styles);
+ self.0.weak(FlowChild::Spacing(amount), styles, 5);
+ }
+
+ fn finish(self, doc: &mut DocBuilder<'a>, styles: StyleChain<'a>) {
+ let (flow, shared) = self.0.finish();
+ let styles = if flow.is_empty() { styles } else { shared };
+ let node = PageNode(FlowNode(flow).pack());
+ doc.pages.push(node, styles);
+ }
+
+ fn is_empty(&self) -> bool {
+ self.0.is_empty()
+ }
+}
+
+/// Accepts paragraph content.
+#[derive(Default)]
+struct ParBuilder<'a>(CollapsingBuilder<'a, ParChild>);
+
+impl<'a> ParBuilder<'a> {
+ fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
+ // Weak par elements:
+ // Weakness | Element
+ // 0 | weak fractional spacing
+ // 1 | weak spacing
+ // 2 | space
+
+ match content {
+ Content::Space => {
+ self.0.weak(ParChild::Text(' '.into()), styles, 2);
+ }
+ &Content::Linebreak { justify } => {
+ let c = if justify { '\u{2028}' } else { '\n' };
+ self.0.destructive(ParChild::Text(c.into()), styles);
+ }
+ &Content::Horizontal { amount, weak } => {
+ let child = ParChild::Spacing(amount);
+ let frac = amount.is_fractional();
+ if weak {
+ let weakness = u8::from(!frac);
+ self.0.weak(child, styles, weakness);
+ } else if frac {
+ self.0.destructive(child, styles);
+ } else {
+ self.0.ignorant(child, styles);
+ }
+ }
+ &Content::Quote { double } => {
+ self.0.supportive(ParChild::Quote { double }, styles);
+ }
+ Content::Text(text) => {
+ self.0.supportive(ParChild::Text(text.clone()), styles);
+ }
+ Content::Inline(node) => {
+ self.0.supportive(ParChild::Node(node.clone()), styles);
+ }
+ _ => return false,
+ }
+
+ true
+ }
+
+ fn finish(self, parent: &mut Builder<'a>) {
+ let (mut children, shared) = self.0.finish();
+ if children.is_empty() {
+ return;
+ }
+
+ // Paragraph indent should only apply if the paragraph starts with
+ // text and follows directly after another paragraph.
+ let indent = shared.get(ParNode::INDENT);
+ if !indent.is_zero()
+ && children
+ .items()
+ .find_map(|child| match child {
+ ParChild::Spacing(_) => None,
+ ParChild::Text(_) | ParChild::Quote { .. } => Some(true),
+ ParChild::Node(_) => Some(false),
+ })
+ .unwrap_or_default()
+ && parent
+ .flow
+ .0
+ .items()
+ .rev()
+ .find_map(|child| match child {
+ FlowChild::Spacing(_) => None,
+ FlowChild::Node(node) => Some(node.is::<ParNode>()),
+ FlowChild::Colbreak => Some(false),
+ })
+ .unwrap_or_default()
+ {
+ children.push_front(ParChild::Spacing(indent.into()));
+ }
+
+ parent.flow.par(ParNode(children), shared, !indent.is_zero());
+ }
+
+ fn is_empty(&self) -> bool {
+ self.0.is_empty()
+ }
+}
+
+/// Accepts list / enum items, spaces, paragraph breaks.
+struct ListBuilder<'a> {
+ /// The list items collected so far.
+ items: StyleVecBuilder<'a, ListItem>,
+ /// Whether the list contains no paragraph breaks.
+ tight: bool,
+ /// Whether the list can be attached.
+ attachable: bool,
+ /// Trailing content for which it is unclear whether it is part of the list.
+ staged: Vec<(&'a Content, StyleChain<'a>)>,
+}
+
+impl<'a> ListBuilder<'a> {
+ fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
+ if self.items.is_empty() {
+ match content {
+ Content::Space => {}
+ Content::Item(_) => {}
+ Content::Parbreak => self.attachable = false,
+ _ => self.attachable = true,
+ }
+ }
+
+ match content {
+ Content::Item(item)
+ if self
+ .items
+ .items()
+ .next()
+ .map_or(true, |first| item.kind() == first.kind()) =>
+ {
+ self.items.push(item.clone(), styles);
+ self.tight &= self.staged.drain(..).all(|(t, _)| *t != Content::Parbreak);
+ }
+ Content::Space | Content::Parbreak if !self.items.is_empty() => {
+ self.staged.push((content, styles));
+ }
+ _ => return false,
+ }
+
+ true
+ }
+
+ fn finish(self, parent: &mut Builder<'a>) -> SourceResult<()> {
+ let (items, shared) = self.items.finish();
+ let kind = match items.items().next() {
+ Some(item) => item.kind(),
+ None => return Ok(()),
+ };
+
+ let tight = self.tight;
+ let attached = tight && self.attachable;
+ let content = match kind {
+ LIST => Content::show(ListNode::<LIST> { tight, attached, items }),
+ ENUM => Content::show(ListNode::<ENUM> { tight, attached, items }),
+ DESC | _ => Content::show(ListNode::<DESC> { tight, attached, items }),
+ };
+
+ let stored = parent.scratch.templates.alloc(content);
+ parent.accept(stored, shared)?;
+
+ for (content, styles) in self.staged {
+ parent.accept(content, styles)?;
+ }
+
+ parent.list.attachable = true;
+
+ Ok(())
+ }
+
+ fn is_empty(&self) -> bool {
+ self.items.is_empty()
+ }
+}
+
+impl Default for ListBuilder<'_> {
+ fn default() -> Self {
+ Self {
+ items: StyleVecBuilder::default(),
+ tight: true,
+ attachable: true,
+ staged: vec![],
+ }
+ }
+}
diff --git a/src/model/resolve.rs b/src/model/resolve.rs
new file mode 100644
index 00000000..1ca4be69
--- /dev/null
+++ b/src/model/resolve.rs
@@ -0,0 +1,84 @@
+use super::{Smart, StyleChain};
+use crate::geom::{Abs, Axes, Corners, Em, Length, Numeric, Rel, Sides};
+use crate::library::text::TextNode;
+
+/// A property that is resolved with other properties from the style chain.
+pub trait Resolve {
+ /// The type of the resolved output.
+ type Output;
+
+ /// Resolve the value using the style chain.
+ fn resolve(self, styles: StyleChain) -> Self::Output;
+}
+
+impl Resolve for Em {
+ type Output = Abs;
+
+ fn resolve(self, styles: StyleChain) -> Self::Output {
+ if self.is_zero() {
+ Abs::zero()
+ } else {
+ self.at(styles.get(TextNode::SIZE))
+ }
+ }
+}
+
+impl Resolve for Length {
+ type Output = Abs;
+
+ fn resolve(self, styles: StyleChain) -> Self::Output {
+ self.abs + self.em.resolve(styles)
+ }
+}
+
+impl<T: Resolve> Resolve for Option<T> {
+ type Output = Option<T::Output>;
+
+ fn resolve(self, styles: StyleChain) -> Self::Output {
+ self.map(|v| v.resolve(styles))
+ }
+}
+
+impl<T: Resolve> Resolve for Smart<T> {
+ type Output = Smart<T::Output>;
+
+ fn resolve(self, styles: StyleChain) -> Self::Output {
+ self.map(|v| v.resolve(styles))
+ }
+}
+
+impl<T: Resolve> Resolve for Axes<T> {
+ type Output = Axes<T::Output>;
+
+ fn resolve(self, styles: StyleChain) -> Self::Output {
+ self.map(|v| v.resolve(styles))
+ }
+}
+
+impl<T: Resolve> Resolve for Sides<T> {
+ type Output = Sides<T::Output>;
+
+ fn resolve(self, styles: StyleChain) -> Self::Output {
+ self.map(|v| v.resolve(styles))
+ }
+}
+
+impl<T: Resolve> Resolve for Corners<T> {
+ type Output = Corners<T::Output>;
+
+ fn resolve(self, styles: StyleChain) -> Self::Output {
+ self.map(|v| v.resolve(styles))
+ }
+}
+
+impl<T> Resolve for Rel<T>
+where
+ T: Resolve + Numeric,
+ <T as Resolve>::Output: Numeric,
+{
+ type Output = Rel<<T as Resolve>::Output>;
+
+ fn resolve(self, styles: StyleChain) -> Self::Output {
+ self.map(|abs| abs.resolve(styles))
+ }
+}
diff --git a/tools/support/typst.tmLanguage.json b/tools/support/typst.tmLanguage.json
index 7a1c8458..74351323 100644
--- a/tools/support/typst.tmLanguage.json
+++ b/tools/support/typst.tmLanguage.json
@@ -142,7 +142,7 @@
"captures": { "1": { "name": "punctuation.definition.reference.typst" } }
},
{
- "begin": "(#)(pub|let|set|show|wrap)\\b",
+ "begin": "(#)(let|set|show|wrap|apply|select)\\b",
"end": "\n|(;)|(?=])",
"beginCaptures": {
"0": { "name": "keyword.other.typst" },
@@ -180,7 +180,7 @@
"captures": { "1": { "name": "punctuation.definition.keyword.typst" } }
},
{
- "begin": "(#)(import|include)\\b",
+ "begin": "(#)(import|include|export)\\b",
"end": "\n|(;)|(?=])",
"beginCaptures": {
"0": { "name": "keyword.control.import.typst" },
@@ -253,7 +253,7 @@
},
{
"name": "keyword.other.typst",
- "match": "\\b(pub|let|set|show|wrap|as|in|from)\\b"
+ "match": "\\b(let|as|in|from|set|show|wrap|apply|select)\\b"
},
{
"name": "keyword.control.conditional.typst",
@@ -265,7 +265,7 @@
},
{
"name": "keyword.control.import.typst",
- "match": "\\b(import|include)\\b"
+ "match": "\\b(import|include|export)\\b"
},
{
"name": "keyword.control.flow.typst",
@@ -344,6 +344,12 @@
"name": "constant.character.escape.string.typst",
"match": "\\\\([\\\\\"nrt]|u\\{?[0-9a-zA-Z]*\\}?)"
}]
+ },
+ {
+ "name": "string.other.math.typst",
+ "begin": "\\$",
+ "end": "\\$",
+ "captures": { "0": { "name": "punctuation.defintion.string.math.typst" } }
}
]
},