From 8fbb11fc05b3313bf102c1f23693290661d00863 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sun, 24 Apr 2022 15:42:56 +0200 Subject: Extract `model` module --- src/eval/collapse.rs | 100 ------ src/eval/content.rs | 604 ----------------------------------- src/eval/func.rs | 3 +- src/eval/layout.rs | 382 ----------------------- src/eval/mod.rs | 13 +- src/eval/module.rs | 3 +- src/eval/ops.rs | 9 +- src/eval/raw.rs | 3 +- src/eval/show.rs | 111 ------- src/eval/styles.rs | 829 ------------------------------------------------ src/eval/value.rs | 3 +- src/lib.rs | 12 +- src/library/prelude.rs | 9 +- src/model/collapse.rs | 100 ++++++ src/model/content.rs | 604 +++++++++++++++++++++++++++++++++++ src/model/layout.rs | 383 +++++++++++++++++++++++ src/model/mod.rs | 14 + src/model/show.rs | 112 +++++++ src/model/styles.rs | 831 +++++++++++++++++++++++++++++++++++++++++++++++++ 19 files changed, 2072 insertions(+), 2053 deletions(-) delete mode 100644 src/eval/collapse.rs delete mode 100644 src/eval/content.rs delete mode 100644 src/eval/layout.rs delete mode 100644 src/eval/show.rs delete mode 100644 src/eval/styles.rs create mode 100644 src/model/collapse.rs create mode 100644 src/model/content.rs create mode 100644 src/model/layout.rs create mode 100644 src/model/mod.rs create mode 100644 src/model/show.rs create mode 100644 src/model/styles.rs (limited to 'src') diff --git a/src/eval/collapse.rs b/src/eval/collapse.rs deleted file mode 100644 index 5e7a25f8..00000000 --- a/src/eval/collapse.rs +++ /dev/null @@ -1,100 +0,0 @@ -use super::{StyleChain, StyleVec, StyleVecBuilder}; - -/// A wrapper around a [`StyleVecBuilder`] that allows to collapse items. -pub struct CollapsingBuilder<'a, T> { - builder: StyleVecBuilder<'a, T>, - staged: Vec<(T, StyleChain<'a>, Option)>, - last: Last, -} - -/// What the last non-ignorant item was. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -enum Last { - Weak, - Destructive, - Supportive, -} - -impl<'a, T> CollapsingBuilder<'a, T> { - /// Create a new style-vec builder. - pub fn new() -> Self { - Self { - builder: StyleVecBuilder::new(), - staged: vec![], - last: Last::Destructive, - } - } - - /// Can only exist when there is at least one supportive item to its left - /// and to its right, with no destructive items or weak items in between to - /// its left and no destructive items in between to its right. There may be - /// ignorant items in between in both directions. - pub fn weak(&mut self, item: T, strength: u8, styles: StyleChain<'a>) { - if self.last != Last::Destructive { - if self.last == Last::Weak { - if let Some(i) = self - .staged - .iter() - .position(|(.., prev)| prev.map_or(false, |p| p < strength)) - { - self.staged.remove(i); - } else { - return; - } - } - - self.staged.push((item, styles, Some(strength))); - self.last = Last::Weak; - } - } - - /// Forces nearby weak items to collapse. - pub fn destructive(&mut self, item: T, styles: StyleChain<'a>) { - self.flush(false); - self.push(item, styles); - self.last = Last::Destructive; - } - - /// Allows nearby weak items to exist. - pub fn supportive(&mut self, item: T, styles: StyleChain<'a>) { - self.flush(true); - self.push(item, styles); - self.last = Last::Supportive; - } - - /// Has no influence on other items. - pub fn ignorant(&mut self, item: T, styles: StyleChain<'a>) { - self.staged.push((item, styles, None)); - } - - /// Iterate over the contained items. - pub fn items(&self) -> impl DoubleEndedIterator { - self.builder.items().chain(self.staged.iter().map(|(item, ..)| item)) - } - - /// Return the finish style vec and the common prefix chain. - pub fn finish(mut self) -> (StyleVec, StyleChain<'a>) { - self.flush(false); - self.builder.finish() - } - - /// Push the staged items, filtering out weak items if `supportive` is false. - fn flush(&mut self, supportive: bool) { - for (item, styles, strength) in self.staged.drain(..) { - if supportive || strength.is_none() { - self.builder.push(item, styles); - } - } - } - - /// Push a new item into the style vector. - fn push(&mut self, item: T, styles: StyleChain<'a>) { - self.builder.push(item, styles); - } -} - -impl<'a, T> Default for CollapsingBuilder<'a, T> { - fn default() -> Self { - Self::new() - } -} diff --git a/src/eval/content.rs b/src/eval/content.rs deleted file mode 100644 index 2465f0e3..00000000 --- a/src/eval/content.rs +++ /dev/null @@ -1,604 +0,0 @@ -use std::fmt::Debug; -use std::hash::Hash; -use std::iter::Sum; -use std::ops::{Add, AddAssign}; - -use typed_arena::Arena; - -use super::{ - CollapsingBuilder, Interruption, Key, Layout, LayoutNode, Show, ShowNode, StyleMap, - StyleVecBuilder, -}; -use crate::diag::StrResult; -use crate::library::layout::{FlowChild, FlowNode, PageNode, PlaceNode, Spacing}; -use crate::library::prelude::*; -use crate::library::structure::{ListItem, ListKind, ListNode, ORDERED, UNORDERED}; -use crate::library::text::{DecoNode, ParChild, ParNode, UNDERLINE}; -use crate::util::EcoString; - -/// Composable representation of styled content. -/// -/// This results from: -/// - anything written between square brackets in Typst -/// - any node constructor -/// -/// Content is represented as a tree of nodes. There are two nodes of special -/// interest: -/// -/// 1. A `Styled` node attaches a style map to other content. For example, a -/// single bold word could be represented as a `Styled(Text("Hello"), -/// [TextNode::STRONG: true])` node. -/// -/// 2. A `Sequence` node content combines other arbitrary content and is the -/// representation of a "flow" of other nodes. So, when you write `[Hi] + -/// [you]` in Typst, this type's [`Add`] implementation is invoked and the -/// two [`Text`](Self::Text) nodes are combined into a single -/// [`Sequence`](Self::Sequence) node. A sequence may contain nested -/// sequences. -#[derive(PartialEq, Clone, Hash)] -pub enum Content { - /// A word space. - Space, - /// A forced line break. If soft (`true`), the preceding line can still be - /// justified, if hard (`false`) not. - Linebreak(bool), - /// Horizontal spacing. - Horizontal(Spacing), - /// Plain text. - Text(EcoString), - /// A smart quote, may be single (`false`) or double (`true`). - Quote(bool), - /// An inline-level node. - Inline(LayoutNode), - /// A paragraph break. - Parbreak, - /// A column break. - Colbreak, - /// Vertical spacing. - Vertical(Spacing), - /// A block-level node. - Block(LayoutNode), - /// An item in an unordered list. - List(ListItem), - /// An item in an ordered list. - Enum(ListItem), - /// A page break. - Pagebreak, - /// A page node. - Page(PageNode), - /// A node that can be realized with styles. - Show(ShowNode), - /// Content with attached styles. - Styled(Arc<(Self, StyleMap)>), - /// A sequence of multiple nodes. - Sequence(Arc>), -} - -impl Content { - /// Create empty content. - pub fn new() -> Self { - Self::sequence(vec![]) - } - - /// Create content from an inline-level node. - pub fn inline(node: T) -> Self - where - T: Layout + Debug + Hash + Sync + Send + 'static, - { - Self::Inline(node.pack()) - } - - /// Create content from a block-level node. - pub fn block(node: T) -> Self - where - T: Layout + Debug + Hash + Sync + Send + 'static, - { - Self::Block(node.pack()) - } - - /// Create content from a showable node. - pub fn show(node: T) -> Self - where - T: Show + Debug + Hash + Sync + Send + 'static, - { - Self::Show(node.pack()) - } - - /// Create a new sequence nodes from multiples nodes. - pub fn sequence(seq: Vec) -> Self { - if seq.len() == 1 { - seq.into_iter().next().unwrap() - } else { - Self::Sequence(Arc::new(seq)) - } - } - - /// Repeat this content `n` times. - pub fn repeat(&self, n: i64) -> StrResult { - let count = usize::try_from(n) - .map_err(|_| format!("cannot repeat this content {} times", n))?; - - Ok(Self::sequence(vec![self.clone(); count])) - } - - /// Style this content with a single style property. - pub fn styled<'k, K: Key<'k>>(mut self, key: K, value: K::Value) -> Self { - if let Self::Styled(styled) = &mut self { - if let Some((_, map)) = Arc::get_mut(styled) { - map.apply(key, value); - return self; - } - } - - Self::Styled(Arc::new((self, StyleMap::with(key, value)))) - } - - /// Style this content with a full style map. - pub fn styled_with_map(mut self, styles: StyleMap) -> Self { - if styles.is_empty() { - return self; - } - - if let Self::Styled(styled) = &mut self { - if let Some((_, map)) = Arc::get_mut(styled) { - map.apply_map(&styles); - return self; - } - } - - Self::Styled(Arc::new((self, styles))) - } - - /// Underline this content. - pub fn underlined(self) -> Self { - Self::show(DecoNode::(self)) - } - - /// Return a node that is spaced apart at top and bottom. - pub fn spaced(self, above: Length, below: Length) -> Self { - if above.is_zero() && below.is_zero() { - return self; - } - - let mut seq = vec![]; - if !above.is_zero() { - seq.push(Content::Vertical(above.into())); - } - - seq.push(self); - - if !below.is_zero() { - seq.push(Content::Vertical(below.into())); - } - - Self::sequence(seq) - } - - /// Layout this content into a collection of pages. - pub fn layout(&self, ctx: &mut Context) -> TypResult>> { - let sya = Arena::new(); - let tpa = Arena::new(); - - let styles = ctx.styles.clone(); - let styles = StyleChain::with_root(&styles); - - let mut builder = Builder::new(&sya, &tpa, true); - builder.process(ctx, self, styles)?; - builder.finish(ctx, styles)?; - - let mut frames = vec![]; - let (pages, shared) = builder.pages.unwrap().finish(); - - for (page, map) in pages.iter() { - let number = 1 + frames.len(); - frames.extend(page.layout(ctx, number, map.chain(&shared))?); - } - - Ok(frames) - } -} - -impl Layout for Content { - fn layout( - &self, - ctx: &mut Context, - regions: &Regions, - styles: StyleChain, - ) -> TypResult>> { - let sya = Arena::new(); - let tpa = Arena::new(); - - let mut builder = Builder::new(&sya, &tpa, false); - builder.process(ctx, self, styles)?; - builder.finish(ctx, styles)?; - - let (flow, shared) = builder.flow.finish(); - FlowNode(flow).layout(ctx, regions, shared) - } - - fn pack(self) -> LayoutNode { - match self { - Content::Block(node) => node, - other => LayoutNode::new(other), - } - } -} - -impl Default for Content { - fn default() -> Self { - Self::new() - } -} - -impl Debug for Content { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Space => f.pad("Space"), - Self::Linebreak(soft) => write!(f, "Linebreak({soft})"), - Self::Horizontal(kind) => write!(f, "Horizontal({kind:?})"), - Self::Text(text) => write!(f, "Text({text:?})"), - Self::Quote(double) => write!(f, "Quote({double})"), - Self::Inline(node) => { - f.write_str("Inline(")?; - node.fmt(f)?; - f.write_str(")") - } - Self::Parbreak => f.pad("Parbreak"), - Self::Colbreak => f.pad("Colbreak"), - Self::Vertical(kind) => write!(f, "Vertical({kind:?})"), - Self::Block(node) => { - f.write_str("Block(")?; - node.fmt(f)?; - f.write_str(")") - } - Self::List(item) => { - f.write_str("- ")?; - item.body.fmt(f) - } - Self::Enum(item) => { - if let Some(number) = item.number { - write!(f, "{}", number)?; - } - f.write_str(". ")?; - item.body.fmt(f) - } - Self::Pagebreak => f.pad("Pagebreak"), - Self::Page(page) => page.fmt(f), - Self::Show(node) => { - f.write_str("Show(")?; - node.fmt(f)?; - f.write_str(")") - } - Self::Styled(styled) => { - let (sub, map) = styled.as_ref(); - map.fmt(f)?; - sub.fmt(f) - } - Self::Sequence(seq) => f.debug_list().entries(seq.iter()).finish(), - } - } -} - -impl Add for Content { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - Self::Sequence(match (self, rhs) { - (Self::Sequence(mut lhs), Self::Sequence(rhs)) => { - let mutable = Arc::make_mut(&mut lhs); - match Arc::try_unwrap(rhs) { - Ok(vec) => mutable.extend(vec), - Err(rc) => mutable.extend(rc.iter().cloned()), - } - lhs - } - (Self::Sequence(mut lhs), rhs) => { - Arc::make_mut(&mut lhs).push(rhs); - lhs - } - (lhs, Self::Sequence(mut rhs)) => { - Arc::make_mut(&mut rhs).insert(0, lhs); - rhs - } - (lhs, rhs) => Arc::new(vec![lhs, rhs]), - }) - } -} - -impl AddAssign for Content { - fn add_assign(&mut self, rhs: Self) { - *self = std::mem::take(self) + rhs; - } -} - -impl Sum for Content { - fn sum>(iter: I) -> Self { - Self::sequence(iter.collect()) - } -} - -/// Builds a flow or page nodes from content. -struct Builder<'a> { - /// An arena where intermediate style chains are stored. - sya: &'a Arena>, - /// An arena where intermediate content resulting from show rules is stored. - tpa: &'a Arena, - /// The already built page runs. - pages: Option>, - /// The currently built list. - list: Option>, - /// The currently built flow. - flow: CollapsingBuilder<'a, FlowChild>, - /// The currently built paragraph. - par: CollapsingBuilder<'a, ParChild>, - /// Whether to keep the next page even if it is empty. - keep_next: bool, -} - -/// Builds an unordered or ordered list from items. -struct ListBuilder<'a> { - styles: StyleChain<'a>, - kind: ListKind, - items: Vec, - tight: bool, - staged: Vec<(&'a Content, StyleChain<'a>)>, -} - -impl<'a> Builder<'a> { - /// Prepare the builder. - fn new(sya: &'a Arena>, tpa: &'a Arena, top: bool) -> Self { - Self { - sya, - tpa, - pages: top.then(|| StyleVecBuilder::new()), - flow: CollapsingBuilder::new(), - list: None, - par: CollapsingBuilder::new(), - keep_next: true, - } - } - - /// Process content. - fn process( - &mut self, - ctx: &mut Context, - content: &'a Content, - styles: StyleChain<'a>, - ) -> TypResult<()> { - if let Some(builder) = &mut self.list { - match content { - Content::Space => { - builder.staged.push((content, styles)); - return Ok(()); - } - Content::Parbreak => { - builder.staged.push((content, styles)); - return Ok(()); - } - Content::List(item) if builder.kind == UNORDERED => { - builder.tight &= - builder.staged.iter().all(|&(t, _)| *t != Content::Parbreak); - builder.staged.clear(); - builder.items.push(item.clone()); - return Ok(()); - } - Content::Enum(item) if builder.kind == ORDERED => { - builder.tight &= - builder.staged.iter().all(|&(t, _)| *t != Content::Parbreak); - builder.staged.clear(); - builder.items.push(item.clone()); - return Ok(()); - } - _ => self.finish_list(ctx)?, - } - } - - match content { - Content::Space => { - self.par.weak(ParChild::Text(' '.into()), 0, styles); - } - Content::Linebreak(soft) => { - let c = if *soft { '\u{2028}' } else { '\n' }; - self.par.destructive(ParChild::Text(c.into()), styles); - } - Content::Horizontal(kind) => { - let child = ParChild::Spacing(*kind); - if kind.is_fractional() { - self.par.destructive(child, styles); - } else { - self.par.ignorant(child, styles); - } - } - Content::Quote(double) => { - self.par.supportive(ParChild::Quote(*double), styles); - } - Content::Text(text) => { - self.par.supportive(ParChild::Text(text.clone()), styles); - } - Content::Inline(node) => { - self.par.supportive(ParChild::Node(node.clone()), styles); - } - Content::Parbreak => { - self.finish_par(styles); - self.flow.weak(FlowChild::Parbreak, 1, styles); - } - Content::Colbreak => { - self.finish_par(styles); - self.flow.destructive(FlowChild::Colbreak, styles); - } - Content::Vertical(kind) => { - self.finish_par(styles); - let child = FlowChild::Spacing(*kind); - if kind.is_fractional() { - self.flow.destructive(child, styles); - } else { - self.flow.ignorant(child, styles); - } - } - Content::Block(node) => { - self.finish_par(styles); - let child = FlowChild::Node(node.clone()); - if node.is::() { - self.flow.ignorant(child, styles); - } else { - self.flow.supportive(child, styles); - } - self.finish_par(styles); - } - Content::List(item) => { - self.list = Some(ListBuilder { - styles, - kind: UNORDERED, - items: vec![item.clone()], - tight: true, - staged: vec![], - }); - } - Content::Enum(item) => { - self.list = Some(ListBuilder { - styles, - kind: ORDERED, - items: vec![item.clone()], - tight: true, - staged: vec![], - }); - } - Content::Pagebreak => { - self.finish_page(ctx, true, true, styles)?; - } - Content::Page(page) => { - self.finish_page(ctx, false, false, styles)?; - if let Some(pages) = &mut self.pages { - pages.push(page.clone(), styles); - } - } - Content::Show(node) => { - let id = node.id(); - let realized = match styles.realize(ctx, node)? { - Some(content) => content, - None => node.realize(ctx, styles)?, - }; - let content = node.finalize(ctx, styles, realized)?; - let stored = self.tpa.alloc(content); - self.process(ctx, stored, styles.unscoped(id))?; - } - Content::Styled(styled) => { - let (sub, map) = styled.as_ref(); - let stored = self.sya.alloc(styles); - let styles = map.chain(stored); - - let interruption = map.interruption(); - match interruption { - Some(Interruption::Page) => { - self.finish_page(ctx, false, true, styles)? - } - Some(Interruption::Par) => self.finish_par(styles), - None => {} - } - - self.process(ctx, sub, styles)?; - - match interruption { - Some(Interruption::Page) => { - self.finish_page(ctx, true, false, styles)? - } - Some(Interruption::Par) => self.finish_par(styles), - None => {} - } - } - Content::Sequence(seq) => { - for sub in seq.iter() { - self.process(ctx, sub, styles)?; - } - } - } - - Ok(()) - } - - /// Finish the currently built paragraph. - fn finish_par(&mut self, styles: StyleChain<'a>) { - let (mut par, shared) = std::mem::take(&mut self.par).finish(); - if !par.is_empty() { - // 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() - && par - .items() - .find_map(|child| match child { - ParChild::Spacing(_) => None, - ParChild::Text(_) | ParChild::Quote(_) => Some(true), - ParChild::Node(_) => Some(false), - }) - .unwrap_or_default() - && self - .flow - .items() - .rev() - .find_map(|child| match child { - FlowChild::Leading => None, - FlowChild::Parbreak => None, - FlowChild::Node(node) => Some(node.is::()), - FlowChild::Spacing(_) => Some(false), - FlowChild::Colbreak => Some(false), - }) - .unwrap_or_default() - { - par.push_front(ParChild::Spacing(indent.into())); - } - - let node = ParNode(par).pack(); - self.flow.supportive(FlowChild::Node(node), shared); - } - self.flow.weak(FlowChild::Leading, 0, styles); - } - - /// Finish the currently built list. - fn finish_list(&mut self, ctx: &mut Context) -> TypResult<()> { - let ListBuilder { styles, kind, items, tight, staged } = match self.list.take() { - Some(list) => list, - None => return Ok(()), - }; - - let content = match kind { - UNORDERED => Content::show(ListNode:: { start: 1, tight, items }), - ORDERED | _ => Content::show(ListNode:: { start: 1, tight, items }), - }; - - let stored = self.tpa.alloc(content); - self.process(ctx, stored, styles)?; - for (content, styles) in staged { - self.process(ctx, content, styles)?; - } - - Ok(()) - } - - /// Finish the currently built page run. - fn finish_page( - &mut self, - ctx: &mut Context, - keep_last: bool, - keep_next: bool, - styles: StyleChain<'a>, - ) -> TypResult<()> { - self.finish_list(ctx)?; - self.finish_par(styles); - if let Some(pages) = &mut self.pages { - let (flow, shared) = std::mem::take(&mut self.flow).finish(); - if !flow.is_empty() || (keep_last && self.keep_next) { - let styles = if flow.is_empty() { styles } else { shared }; - let node = PageNode(FlowNode(flow).pack()); - pages.push(node, styles); - } - } - self.keep_next = keep_next; - Ok(()) - } - - /// Finish everything. - fn finish(&mut self, ctx: &mut Context, styles: StyleChain<'a>) -> TypResult<()> { - self.finish_page(ctx, true, false, styles) - } -} diff --git a/src/eval/func.rs b/src/eval/func.rs index fcd19326..b2ecfe93 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -2,8 +2,9 @@ use std::fmt::{self, Debug, Formatter, Write}; use std::hash::{Hash, Hasher}; use std::sync::Arc; -use super::{Args, Content, Control, Eval, Scope, Scopes, StyleMap, Value}; +use super::{Args, Control, Eval, Scope, Scopes, Value}; use crate::diag::{StrResult, TypResult}; +use crate::model::{Content, StyleMap}; use crate::syntax::ast::Expr; use crate::syntax::Span; use crate::util::EcoString; diff --git a/src/eval/layout.rs b/src/eval/layout.rs deleted file mode 100644 index 117c269a..00000000 --- a/src/eval/layout.rs +++ /dev/null @@ -1,382 +0,0 @@ -//! Layouting infrastructure. - -use std::any::Any; -use std::fmt::{self, Debug, Formatter}; -use std::hash::Hash; -use std::sync::Arc; - -use super::{Barrier, RawAlign, RawLength, Resolve, StyleChain}; -use crate::diag::TypResult; -use crate::frame::{Element, Frame, Geometry}; -use crate::geom::{Align, Length, Paint, Point, Relative, Sides, Size, Spec, Stroke}; -use crate::library::graphics::MoveNode; -use crate::library::layout::{AlignNode, PadNode}; -use crate::util::Prehashed; -use crate::Context; - -/// A node that can be layouted into a sequence of regions. -/// -/// Layout return one frame per used region alongside constraints that define -/// whether the result is reusable in other regions. -pub trait Layout: 'static { - /// Layout this node into the given regions, producing constrained frames. - fn layout( - &self, - ctx: &mut Context, - regions: &Regions, - styles: StyleChain, - ) -> TypResult>>; - - /// Convert to a packed node. - fn pack(self) -> LayoutNode - where - Self: Debug + Hash + Sized + Sync + Send + 'static, - { - LayoutNode::new(self) - } -} - -/// A sequence of regions to layout into. -#[derive(Debug, Clone, Hash)] -pub struct Regions { - /// The (remaining) size of the first region. - pub first: Size, - /// The base size for relative sizing. - pub base: Size, - /// The height of followup regions. The width is the same for all regions. - pub backlog: Vec, - /// 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, - /// Whether nodes should expand to fill the regions instead of shrinking to - /// fit the content. - pub expand: Spec, -} - -impl Regions { - /// Create a new region sequence with exactly one region. - pub fn one(size: Size, base: Size, expand: Spec) -> Self { - Self { - first: size, - base, - backlog: vec![], - last: None, - expand, - } - } - - /// Create a new sequence of same-size regions that repeats indefinitely. - pub fn repeat(size: Size, base: Size, expand: Spec) -> Self { - Self { - first: size, - base, - backlog: vec![], - last: Some(size.y), - expand, - } - } - - /// Create new regions where all sizes are mapped with `f`. - /// - /// Note that since all regions must have the same width, the width returned - /// by `f` is ignored for the backlog and the final region. - pub fn map(&self, mut f: F) -> Self - where - F: FnMut(Size) -> Size, - { - let x = self.first.x; - Self { - first: f(self.first), - base: f(self.base), - backlog: self.backlog.iter().map(|&y| f(Size::new(x, y)).y).collect(), - last: self.last.map(|y| f(Size::new(x, y)).y), - expand: self.expand, - } - } - - /// Whether the first region is full and a region break is called for. - pub fn is_full(&self) -> bool { - Length::zero().fits(self.first.y) && !self.in_last() - } - - /// Whether the first region is the last usable region. - /// - /// If this is true, calling `next()` will have no effect. - pub fn in_last(&self) -> bool { - self.backlog.len() == 0 && self.last.map_or(true, |height| self.first.y == height) - } - - /// Advance to the next region if there is any. - pub fn next(&mut self) { - if let Some(height) = (!self.backlog.is_empty()) - .then(|| self.backlog.remove(0)) - .or(self.last) - { - self.first.y = height; - self.base.y = height; - } - } - - /// An iterator that returns the sizes of the first and all following - /// regions, equivalently to what would be produced by calling - /// [`next()`](Self::next) repeatedly until all regions are exhausted. - /// This iterater may be infinite. - pub fn iter(&self) -> impl Iterator + '_ { - let first = std::iter::once(self.first); - let backlog = self.backlog.iter(); - let last = self.last.iter().cycle(); - first.chain(backlog.chain(last).map(|&h| Size::new(self.first.x, h))) - } -} - -/// A type-erased layouting node with a precomputed hash. -#[derive(Clone, Hash)] -pub struct LayoutNode(Arc>); - -impl LayoutNode { - /// Pack any layoutable node. - pub fn new(node: T) -> Self - where - T: Layout + Debug + Hash + Sync + Send + 'static, - { - Self(Arc::new(Prehashed::new(node))) - } - - /// Check whether the contained node is a specific layout node. - pub fn is(&self) -> bool { - (**self.0).as_any().is::() - } - - /// A barrier for the node. - pub fn barrier(&self) -> Barrier { - (**self.0).barrier() - } - - /// Try to downcast to a specific layout node. - pub fn downcast(&self) -> Option<&T> - where - T: Layout + Debug + Hash + 'static, - { - (**self.0).as_any().downcast_ref() - } - - /// Force a size for this node. - pub fn sized(self, sizing: Spec>>) -> Self { - if sizing.any(Option::is_some) { - SizedNode { sizing, child: self }.pack() - } else { - self - } - } - - /// Fill the frames resulting from a node. - pub fn filled(self, fill: Paint) -> Self { - FillNode { fill, child: self }.pack() - } - - /// Stroke the frames resulting from a node. - pub fn stroked(self, stroke: Stroke) -> Self { - StrokeNode { stroke, child: self }.pack() - } - - /// Set alignments for this node. - pub fn aligned(self, aligns: Spec>) -> Self { - if aligns.any(Option::is_some) { - AlignNode { aligns, child: self }.pack() - } else { - self - } - } - - /// Pad this node at the sides. - pub fn padded(self, padding: Sides>) -> Self { - if !padding.left.is_zero() - || !padding.top.is_zero() - || !padding.right.is_zero() - || !padding.bottom.is_zero() - { - PadNode { padding, child: self }.pack() - } else { - self - } - } - - /// Transform this node's contents without affecting layout. - pub fn moved(self, delta: Spec>) -> Self { - if delta.any(|r| !r.is_zero()) { - MoveNode { delta, child: self }.pack() - } else { - self - } - } -} - -impl Layout for LayoutNode { - fn layout( - &self, - ctx: &mut Context, - regions: &Regions, - styles: StyleChain, - ) -> TypResult>> { - ctx.query((self, regions, styles), |ctx, (node, regions, styles)| { - node.0.layout(ctx, regions, node.barrier().chain(&styles)) - }) - .clone() - } - - fn pack(self) -> LayoutNode { - self - } -} - -impl Default for LayoutNode { - fn default() -> Self { - EmptyNode.pack() - } -} - -impl Debug for LayoutNode { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -impl PartialEq for LayoutNode { - fn eq(&self, other: &Self) -> bool { - self.0.eq(&other.0) - } -} - -trait Bounds: Layout + Debug + Sync + Send + 'static { - fn as_any(&self) -> &dyn Any; - fn barrier(&self) -> Barrier; -} - -impl Bounds for T -where - T: Layout + Debug + Hash + Sync + Send + 'static, -{ - fn as_any(&self) -> &dyn Any { - self - } - - fn barrier(&self) -> Barrier { - Barrier::new::() - } -} - -/// A layout node that produces an empty frame. -/// -/// The packed version of this is returned by [`PackedNode::default`]. -#[derive(Debug, Hash)] -struct EmptyNode; - -impl Layout for EmptyNode { - fn layout( - &self, - _: &mut Context, - regions: &Regions, - _: StyleChain, - ) -> TypResult>> { - Ok(vec![Arc::new(Frame::new( - regions.expand.select(regions.first, Size::zero()), - ))]) - } -} - -/// Fix the size of a node. -#[derive(Debug, Hash)] -struct SizedNode { - /// How to size the node horizontally and vertically. - sizing: Spec>>, - /// The node to be sized. - child: LayoutNode, -} - -impl Layout for SizedNode { - fn layout( - &self, - ctx: &mut Context, - regions: &Regions, - styles: StyleChain, - ) -> TypResult>> { - // The "pod" is the region into which the child will be layouted. - let pod = { - // Resolve the sizing to a concrete size. - let size = self - .sizing - .resolve(styles) - .zip(regions.base) - .map(|(s, b)| s.map(|v| v.relative_to(b))) - .unwrap_or(regions.first); - - // Select the appropriate base and expansion for the child depending - // on whether it is automatically or relatively sized. - let is_auto = self.sizing.map_is_none(); - let base = is_auto.select(regions.base, size); - let expand = regions.expand | !is_auto; - - Regions::one(size, base, expand) - }; - - // Layout the child. - let mut frames = self.child.layout(ctx, &pod, styles)?; - - // Ensure frame size matches regions size if expansion is on. - let frame = &mut frames[0]; - let target = regions.expand.select(regions.first, frame.size); - Arc::make_mut(frame).resize(target, Align::LEFT_TOP); - - Ok(frames) - } -} - -/// Fill the frames resulting from a node. -#[derive(Debug, Hash)] -struct FillNode { - /// How to fill the frames resulting from the `child`. - fill: Paint, - /// The node to fill. - child: LayoutNode, -} - -impl Layout for FillNode { - fn layout( - &self, - ctx: &mut Context, - regions: &Regions, - styles: StyleChain, - ) -> TypResult>> { - let mut frames = self.child.layout(ctx, regions, styles)?; - for frame in &mut frames { - let shape = Geometry::Rect(frame.size).filled(self.fill); - Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape)); - } - Ok(frames) - } -} - -/// Stroke the frames resulting from a node. -#[derive(Debug, Hash)] -struct StrokeNode { - /// How to stroke the frames resulting from the `child`. - stroke: Stroke, - /// The node to stroke. - child: LayoutNode, -} - -impl Layout for StrokeNode { - fn layout( - &self, - ctx: &mut Context, - regions: &Regions, - styles: StyleChain, - ) -> TypResult>> { - let mut frames = self.child.layout(ctx, regions, styles)?; - for frame in &mut frames { - let shape = Geometry::Rect(frame.size).stroked(self.stroke); - Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape)); - } - Ok(frames) - } -} diff --git a/src/eval/mod.rs b/src/eval/mod.rs index f77efe47..2839baff 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -6,38 +6,28 @@ mod array; mod dict; #[macro_use] mod value; -#[macro_use] -mod styles; + mod args; mod capture; -mod collapse; -mod content; mod control; mod func; -mod layout; pub mod methods; mod module; pub mod ops; mod raw; mod scope; -mod show; mod str; pub use self::str::*; pub use args::*; pub use array::*; pub use capture::*; -pub use collapse::*; -pub use content::*; pub use control::*; pub use dict::*; pub use func::*; -pub use layout::*; pub use module::*; pub use raw::*; pub use scope::*; -pub use show::*; -pub use styles::*; pub use value::*; use std::collections::BTreeMap; @@ -48,6 +38,7 @@ use unicode_segmentation::UnicodeSegmentation; use crate::diag::{At, StrResult, Trace, Tracepoint, TypResult}; use crate::geom::{Angle, Em, Fraction, Length, Ratio}; use crate::library; +use crate::model::{Content, StyleMap}; use crate::syntax::ast::*; use crate::syntax::{Span, Spanned}; use crate::util::EcoString; diff --git a/src/eval/module.rs b/src/eval/module.rs index 4460caec..d2b4c520 100644 --- a/src/eval/module.rs +++ b/src/eval/module.rs @@ -1,4 +1,5 @@ -use super::{Content, Scope}; +use super::Scope; +use crate::model::Content; use crate::source::{SourceId, SourceStore}; /// An evaluated module, ready for importing or layouting. diff --git a/src/eval/ops.rs b/src/eval/ops.rs index 70352039..832bd354 100644 --- a/src/eval/ops.rs +++ b/src/eval/ops.rs @@ -5,6 +5,7 @@ use std::cmp::Ordering; use super::{Dynamic, RawAlign, RawStroke, Smart, StrExt, Value}; use crate::diag::StrResult; use crate::geom::{Numeric, Spec, SpecAxis}; +use crate::model; use Value::*; /// Bail with a type mismatch error. @@ -20,8 +21,8 @@ pub fn join(lhs: Value, rhs: Value) -> StrResult { (a, None) => a, (None, b) => b, (Str(a), Str(b)) => Str(a + b), - (Str(a), Content(b)) => Content(super::Content::Text(a) + b), - (Content(a), Str(b)) => Content(a + super::Content::Text(b)), + (Str(a), Content(b)) => Content(model::Content::Text(a) + b), + (Content(a), Str(b)) => Content(a + model::Content::Text(b)), (Content(a), Content(b)) => Content(a + b), (Array(a), Array(b)) => Array(a + b), (Dict(a), Dict(b)) => Dict(a + b), @@ -86,8 +87,8 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult { (Content(a), None) => Content(a), (None, Content(b)) => Content(b), (Content(a), Content(b)) => Content(a + b), - (Content(a), Str(b)) => Content(a + super::Content::Text(b)), - (Str(a), Content(b)) => Content(super::Content::Text(a) + b), + (Content(a), Str(b)) => Content(a + model::Content::Text(b)), + (Str(a), Content(b)) => Content(model::Content::Text(a) + b), (Array(a), Array(b)) => Array(a + b), (Dict(a), Dict(b)) => Dict(a + b), diff --git a/src/eval/raw.rs b/src/eval/raw.rs index 6792c491..2d82fca8 100644 --- a/src/eval/raw.rs +++ b/src/eval/raw.rs @@ -2,11 +2,12 @@ use std::cmp::Ordering; use std::fmt::{self, Debug, Formatter}; use std::ops::{Add, Div, Mul, Neg}; -use super::{Fold, Resolve, Smart, StyleChain, Value}; +use super::{Smart, Value}; use crate::geom::{ Align, Em, Get, Length, Numeric, Paint, Relative, Spec, SpecAxis, Stroke, }; use crate::library::text::TextNode; +use crate::model::{Fold, Resolve, StyleChain}; /// The unresolved alignment representation. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] diff --git a/src/eval/show.rs b/src/eval/show.rs deleted file mode 100644 index c374c2df..00000000 --- a/src/eval/show.rs +++ /dev/null @@ -1,111 +0,0 @@ -use std::any::{Any, TypeId}; -use std::fmt::{self, Debug, Formatter}; -use std::hash::Hash; -use std::sync::Arc; - -use super::{Content, Dict, StyleChain}; -use crate::diag::TypResult; -use crate::util::Prehashed; -use crate::Context; - -/// A node that can be realized given some styles. -pub trait Show: 'static { - /// Encode this node into a dictionary. - fn encode(&self) -> Dict; - - /// The base recipe for this node that is executed if there is no - /// user-defined show rule. - fn realize(&self, ctx: &mut Context, styles: StyleChain) -> TypResult; - - /// Finalize this node given the realization of a base or user recipe. Use - /// this for effects that should work even in the face of a user-defined - /// show rule, for example: - /// - Application of general settable properties - /// - Attaching things like semantics to a heading - /// - /// Defaults to just the realized content. - #[allow(unused_variables)] - fn finalize( - &self, - ctx: &mut Context, - styles: StyleChain, - realized: Content, - ) -> TypResult { - Ok(realized) - } - - /// Convert to a packed show node. - fn pack(self) -> ShowNode - where - Self: Debug + Hash + Sized + Sync + Send + 'static, - { - ShowNode::new(self) - } -} - -/// A type-erased showable node with a precomputed hash. -#[derive(Clone, Hash)] -pub struct ShowNode(Arc>); - -impl ShowNode { - /// Pack any showable node. - pub fn new(node: T) -> Self - where - T: Show + Debug + Hash + Sync + Send + 'static, - { - Self(Arc::new(Prehashed::new(node))) - } - - /// The type id of this node. - pub fn id(&self) -> TypeId { - self.0.as_any().type_id() - } -} - -impl Show for ShowNode { - fn encode(&self) -> Dict { - self.0.encode() - } - - fn realize(&self, ctx: &mut Context, styles: StyleChain) -> TypResult { - self.0.realize(ctx, styles) - } - - fn finalize( - &self, - ctx: &mut Context, - styles: StyleChain, - realized: Content, - ) -> TypResult { - self.0.finalize(ctx, styles, realized) - } - - fn pack(self) -> ShowNode { - self - } -} - -impl Debug for ShowNode { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -impl PartialEq for ShowNode { - fn eq(&self, other: &Self) -> bool { - self.0.eq(&other.0) - } -} - -trait Bounds: Show + Debug + Sync + Send + 'static { - fn as_any(&self) -> &dyn Any; -} - -impl Bounds for T -where - T: Show + Debug + Hash + Sync + Send + 'static, -{ - fn as_any(&self) -> &dyn Any { - self - } -} diff --git a/src/eval/styles.rs b/src/eval/styles.rs deleted file mode 100644 index b666c85d..00000000 --- a/src/eval/styles.rs +++ /dev/null @@ -1,829 +0,0 @@ -use std::any::{Any, TypeId}; -use std::fmt::{self, Debug, Formatter}; -use std::hash::Hash; -use std::marker::PhantomData; -use std::sync::Arc; - -use super::{Args, Content, Func, Layout, Node, Show, ShowNode, Smart, Span, Value}; -use crate::diag::{At, TypResult}; -use crate::geom::{Numeric, Relative, Sides, Spec}; -use crate::library::layout::PageNode; -use crate::library::text::{FontFamily, ParNode, TextNode}; -use crate::util::Prehashed; -use crate::Context; - -/// A map of style properties. -#[derive(Default, Clone, PartialEq, Hash)] -pub struct StyleMap(Vec); - -impl StyleMap { - /// Create a new, empty style map. - pub fn new() -> Self { - Self::default() - } - - /// Whether this map contains no styles. - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - /// Create a style map from a single property-value pair. - pub fn with<'a, K: Key<'a>>(key: K, value: K::Value) -> Self { - let mut styles = Self::new(); - styles.set(key, value); - styles - } - - /// Set an inner value for a style property. - /// - /// If the property needs folding and the value is already contained in the - /// style map, `self` contributes the outer values and `value` is the inner - /// one. - pub fn set<'a, K: Key<'a>>(&mut self, key: K, value: K::Value) { - self.0.push(Entry::Property(Property::new(key, value))); - } - - /// Set an inner value for a style property if it is `Some(_)`. - pub fn set_opt<'a, K: Key<'a>>(&mut self, key: K, value: Option) { - if let Some(value) = value { - self.set(key, value); - } - } - - /// Set a font family composed of a preferred family and existing families - /// from a style chain. - pub fn set_family(&mut self, preferred: FontFamily, existing: StyleChain) { - self.set( - TextNode::FAMILY, - std::iter::once(preferred) - .chain(existing.get(TextNode::FAMILY).iter().cloned()) - .collect(), - ); - } - - /// Set a show rule recipe for a node. - pub fn set_recipe(&mut self, func: Func, span: Span) { - self.0.push(Entry::Recipe(Recipe::new::(func, span))); - } - - /// Whether the map contains a style property for the given key. - pub fn contains<'a, K: Key<'a>>(&self, _: K) -> bool { - self.0 - .iter() - .filter_map(|entry| entry.property()) - .any(|property| property.key == TypeId::of::()) - } - - /// Make `self` the first link of the `tail` chain. - /// - /// The resulting style chain contains styles from `self` as well as - /// `tail`. The ones from `self` take precedence over the ones from - /// `tail`. For folded properties `self` contributes the inner value. - pub fn chain<'a>(&'a self, tail: &'a StyleChain<'a>) -> StyleChain<'a> { - if self.is_empty() { - *tail - } else { - StyleChain { head: &self.0, tail: Some(tail) } - } - } - - /// Set an outer value for a style property. - /// - /// If the property needs folding and the value is already contained in the - /// style map, `self` contributes the inner values and `value` is the outer - /// one. - /// - /// Like [`chain`](Self::chain) or [`apply_map`](Self::apply_map), but with - /// only a single property. - pub fn apply<'a, K: Key<'a>>(&mut self, key: K, value: K::Value) { - self.0.insert(0, Entry::Property(Property::new(key, value))); - } - - /// Apply styles from `tail` in-place. The resulting style map is equivalent - /// to the style chain created by `self.chain(StyleChain::new(tail))`. - /// - /// This is useful over `chain` when you want to combine two maps, but you - /// still need an owned map without a lifetime. - pub fn apply_map(&mut self, tail: &Self) { - self.0.splice(0 .. 0, tail.0.iter().cloned()); - } - - /// Mark all contained properties as _scoped_. This means that they only - /// apply to the first descendant node (of their type) in the hierarchy and - /// not its children, too. This is used by [constructors](Node::construct). - pub fn scoped(mut self) -> Self { - for entry in &mut self.0 { - if let Entry::Property(property) = entry { - property.scoped = true; - } - } - self - } - - /// The highest-level kind of of structure the map interrupts. - pub fn interruption(&self) -> Option { - self.0 - .iter() - .filter_map(|entry| entry.property()) - .filter_map(|property| property.interruption()) - .max() - } -} - -impl Debug for StyleMap { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - for entry in self.0.iter().rev() { - writeln!(f, "{:?}", entry)?; - } - Ok(()) - } -} - -/// An entry for a single style property, recipe or barrier. -#[derive(Clone, PartialEq, Hash)] -enum Entry { - /// A style property originating from a set rule or constructor. - Property(Property), - /// A barrier for scoped styles. - Barrier(TypeId, &'static str), - /// A show rule recipe. - Recipe(Recipe), -} - -impl Entry { - /// If this is a property, return it. - fn property(&self) -> Option<&Property> { - match self { - Self::Property(property) => Some(property), - _ => None, - } - } - - /// If this is a recipe, return it. - fn recipe(&self) -> Option<&Recipe> { - match self { - Self::Recipe(recipe) => Some(recipe), - _ => None, - } - } -} - -impl Debug for Entry { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_str("#[")?; - match self { - Self::Property(property) => property.fmt(f)?, - Self::Recipe(recipe) => recipe.fmt(f)?, - Self::Barrier(_, name) => write!(f, "Barrier for {name}")?, - } - f.write_str("]") - } -} - -/// A style property originating from a set rule or constructor. -#[derive(Clone, Hash)] -struct Property { - /// The type id of the property's [key](Key). - key: TypeId, - /// The type id of the node the property belongs to. - node: TypeId, - /// The name of the property. - name: &'static str, - /// The property's value. - value: Arc>, - /// Whether the property should only affects the first node down the - /// hierarchy. Used by constructors. - scoped: bool, -} - -impl Property { - /// Create a new property from a key-value pair. - fn new<'a, K: Key<'a>>(_: K, value: K::Value) -> Self { - Self { - key: TypeId::of::(), - node: K::node(), - name: K::NAME, - value: Arc::new(Prehashed::new(value)), - scoped: false, - } - } - - /// What kind of structure the property interrupts. - fn interruption(&self) -> Option { - if self.is_of::() { - Some(Interruption::Page) - } else if self.is_of::() { - Some(Interruption::Par) - } else { - None - } - } - - /// Access the property's value if it is of the given key. - fn downcast<'a, K: Key<'a>>(&'a self) -> Option<&'a K::Value> { - if self.key == TypeId::of::() { - (**self.value).as_any().downcast_ref() - } else { - None - } - } - - /// Whether this property belongs to the node `T`. - fn is_of(&self) -> bool { - self.node == TypeId::of::() - } -} - -impl Debug for Property { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{} = {:?}", self.name, self.value)?; - if self.scoped { - write!(f, " [scoped]")?; - } - Ok(()) - } -} - -impl PartialEq for Property { - fn eq(&self, other: &Self) -> bool { - self.key == other.key - && self.value.eq(&other.value) - && self.scoped == other.scoped - } -} - -trait Bounds: Debug + Sync + Send + 'static { - fn as_any(&self) -> &dyn Any; -} - -impl Bounds for T -where - T: Debug + Sync + Send + 'static, -{ - fn as_any(&self) -> &dyn Any { - self - } -} - -/// Style property keys. -/// -/// This trait is not intended to be implemented manually, but rather through -/// the `#[node]` proc-macro. -pub trait Key<'a>: Copy + 'static { - /// The unfolded type which this property is stored as in a style map. For - /// example, this is [`Toggle`](crate::geom::Length) for the - /// [`STRONG`](TextNode::STRONG) property. - type Value: Debug + Clone + Hash + Sync + Send + 'static; - - /// The folded type of value that is returned when reading this property - /// from a style chain. For example, this is [`bool`] for the - /// [`STRONG`](TextNode::STRONG) property. For non-copy, non-folding - /// properties this is a reference type. - type Output; - - /// The name of the property, used for debug printing. - const NAME: &'static str; - - /// The type id of the node this property belongs to. - fn node() -> TypeId; - - /// Compute an output value from a sequence of values belong to this key, - /// folding if necessary. - fn get( - chain: StyleChain<'a>, - values: impl Iterator, - ) -> 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 Option { - type Output = Option; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|v| v.resolve(styles)) - } -} - -impl Resolve for Smart { - type Output = Smart; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|v| v.resolve(styles)) - } -} - -impl Resolve for Spec { - type Output = Spec; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|v| v.resolve(styles)) - } -} - -impl Resolve for Sides { - type Output = Sides; - - fn resolve(self, styles: StyleChain) -> Self::Output { - Sides { - left: self.left.resolve(styles), - right: self.right.resolve(styles), - top: self.top.resolve(styles), - bottom: self.bottom.resolve(styles), - } - } -} - -impl Resolve for Relative -where - T: Resolve + Numeric, - ::Output: Numeric, -{ - type Output = Relative<::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 Fold for Option -where - T: Fold, - T::Output: Default, -{ - type Output = Option; - - fn fold(self, outer: Self::Output) -> Self::Output { - self.map(|inner| inner.fold(outer.unwrap_or_default())) - } -} - -impl Fold for Smart -where - T: Fold, - T::Output: Default, -{ - type Output = Smart; - - fn fold(self, outer: Self::Output) -> Self::Output { - self.map(|inner| inner.fold(outer.unwrap_or_default())) - } -} - -/// A show rule recipe. -#[derive(Clone, PartialEq, Hash)] -struct Recipe { - /// The affected node. - node: TypeId, - /// The name of the affected node. - name: &'static str, - /// The function that defines the recipe. - func: Func, - /// The span to report all erros with. - span: Span, -} - -impl Recipe { - /// Create a new recipe for the node `T`. - fn new(func: Func, span: Span) -> Self { - Self { - node: TypeId::of::(), - name: std::any::type_name::(), - func, - span, - } - } -} - -impl Debug for Recipe { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "Recipe for {} from {:?}", self.name, self.span) - } -} - -/// A style chain barrier. -/// -/// Barriers interact with [scoped](StyleMap::scoped) styles: A scoped style -/// can still be read through a single barrier (the one of the node it -/// _should_ apply to), but a second barrier will make it invisible. -#[derive(Clone, PartialEq, Hash)] -pub struct Barrier(Entry); - -impl Barrier { - /// Create a new barrier for the layout node `T`. - pub fn new() -> Self { - Self(Entry::Barrier( - TypeId::of::(), - std::any::type_name::(), - )) - } - - /// Make this barrier the first link of the `tail` chain. - pub fn chain<'a>(&'a self, tail: &'a StyleChain) -> StyleChain<'a> { - // We have to store a full `Entry` enum inside the barrier because - // otherwise the `slice::from_ref` trick below won't work. - // Unfortunately, that also means we have to somehow extract the id - // here. - let id = match self.0 { - Entry::Barrier(id, _) => id, - _ => unreachable!(), - }; - - if tail - .entries() - .filter_map(Entry::property) - .any(|p| p.scoped && p.node == id) - { - StyleChain { - head: std::slice::from_ref(&self.0), - tail: Some(tail), - } - } else { - *tail - } - } -} - -impl Debug for Barrier { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -/// Determines whether a style could interrupt some composable structure. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub enum Interruption { - /// The style forces a paragraph break. - Par, - /// The style forces a page break. - Page, -} - -/// A chain of style maps, 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 -/// map, trying to find a match and then folding it with matches further up the -/// chain. -#[derive(Default, Clone, Copy, Hash)] -pub struct StyleChain<'a> { - /// The first link of this chain. - head: &'a [Entry], - /// The remaining links in the chain. - tail: Option<&'a Self>, -} - -impl<'a> StyleChain<'a> { - /// Create a new, empty style chain. - pub fn new() -> Self { - Self::default() - } - - /// Start a new style chain with a root map. - pub fn with_root(root: &'a StyleMap) -> Self { - Self { head: &root.0, tail: None } - } - - /// Get the output value of a style property. - /// - /// Returns the property's default value if no map in the chain contains an - /// entry for it. Also takes care of resolving and folding and returns - /// references where applicable. - pub fn get>(self, key: K) -> K::Output { - K::get(self, self.values(key)) - } - - /// Realize a node with a user recipe. - pub fn realize( - self, - ctx: &mut Context, - node: &ShowNode, - ) -> TypResult> { - let id = node.id(); - if let Some(recipe) = self - .entries() - .filter_map(Entry::recipe) - .find(|recipe| recipe.node == id) - { - let dict = node.encode(); - let args = Args::from_values(recipe.span, [Value::Dict(dict)]); - Ok(Some(recipe.func.call(ctx, args)?.cast().at(recipe.span)?)) - } else { - Ok(None) - } - } -} - -impl<'a> StyleChain<'a> { - /// Return the chain, but without the trailing scoped property for the given - /// `node`. This is a 90% hack fix for show node constructor scoping. - pub(super) fn unscoped(mut self, node: TypeId) -> Self { - while self - .head - .last() - .and_then(Entry::property) - .map_or(false, |p| p.scoped && p.node == node) - { - let len = self.head.len(); - self.head = &self.head[.. len - 1] - } - self - } - - /// Remove the last link from the chain. - fn pop(&mut self) { - *self = self.tail.copied().unwrap_or_default(); - } - - /// Build a style map from the suffix (all links beyond the `len`) of the - /// chain. - fn suffix(self, len: usize) -> StyleMap { - let mut suffix = StyleMap::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 - } - - /// Iterate over all values for the given property in the chain. - fn values>(self, _: K) -> Values<'a, K> { - Values { - entries: self.entries(), - depth: 0, - key: PhantomData, - } - } - - /// Iterate over the entries of the chain. - fn entries(self) -> Entries<'a> { - Entries { - inner: [].as_slice().iter(), - links: self.links(), - } - } - - /// Iterate over the links of the chain. - fn links(self) -> Links<'a> { - Links(Some(self)) - } -} - -impl Debug for StyleChain<'_> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - for entry in self.entries() { - writeln!(f, "{:?}", entry)?; - } - Ok(()) - } -} - -impl PartialEq for StyleChain<'_> { - fn eq(&self, other: &Self) -> bool { - let as_ptr = |s| s as *const _; - self.head.as_ptr() == other.head.as_ptr() - && self.head.len() == other.head.len() - && self.tail.map(as_ptr) == other.tail.map(as_ptr) - } -} - -/// An iterator over the values in a style chain. -struct Values<'a, K> { - entries: Entries<'a>, - depth: usize, - key: PhantomData, -} - -impl<'a, K: Key<'a>> Iterator for Values<'a, K> { - type Item = &'a K::Value; - - fn next(&mut self) -> Option { - while let Some(entry) = self.entries.next() { - match entry { - Entry::Property(property) => { - if let Some(value) = property.downcast::() { - if !property.scoped || self.depth <= 1 { - return Some(value); - } - } - } - Entry::Barrier(id, _) => { - self.depth += (*id == K::node()) as usize; - } - Entry::Recipe(_) => {} - } - } - - None - } -} - -/// An iterator over the entries in a style chain. -struct Entries<'a> { - inner: std::slice::Iter<'a, Entry>, - links: Links<'a>, -} - -impl<'a> Iterator for Entries<'a> { - type Item = &'a Entry; - - fn next(&mut self) -> Option { - loop { - if let Some(entry) = self.inner.next_back() { - return Some(entry); - } - - match self.links.next() { - Some(next) => self.inner = next.iter(), - None => return None, - } - } - } -} - -/// An iterator over the links of a style chain. -struct Links<'a>(Option>); - -impl<'a> Iterator for Links<'a> { - type Item = &'a [Entry]; - - fn next(&mut self) -> Option { - let StyleChain { head, tail } = self.0?; - self.0 = tail.copied(); - Some(head) - } -} - -/// A sequence of items with associated styles. -#[derive(Hash)] -pub struct StyleVec { - items: Vec, - maps: Vec<(StyleMap, usize)>, -} - -impl StyleVec { - /// Whether there are any items in the sequence. - pub fn is_empty(&self) -> bool { - self.items.is_empty() - } - - /// Number of items in the sequence. - pub fn len(&self) -> usize { - self.items.len() - } - - /// 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 maps(&self) -> impl Iterator { - self.maps.iter().map(|(map, _)| map) - } - - /// Iterate over the contained items. - pub fn items(&self) -> std::slice::Iter<'_, T> { - self.items.iter() - } - - /// Iterate over the contained items and associated style maps. - pub fn iter(&self) -> impl Iterator + '_ { - let styles = self - .maps - .iter() - .flat_map(|(map, count)| std::iter::repeat(map).take(*count)); - self.items().zip(styles) - } - - /// Insert an element in the front. The element will share the style of the - /// current first element. - /// - /// This method has no effect if the vector is empty. - pub fn push_front(&mut self, item: T) { - if !self.maps.is_empty() { - self.items.insert(0, item); - self.maps[0].1 += 1; - } - } -} - -impl Default for StyleVec { - fn default() -> Self { - Self { items: vec![], maps: vec![] } - } -} - -impl Debug for StyleVec { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_list() - .entries(self.iter().map(|(item, map)| { - crate::util::debug(|f| { - map.fmt(f)?; - item.fmt(f) - }) - })) - .finish() - } -} - -/// Assists in the construction of a [`StyleVec`]. -pub struct StyleVecBuilder<'a, T> { - items: Vec, - chains: Vec<(StyleChain<'a>, usize)>, -} - -impl<'a, T> StyleVecBuilder<'a, T> { - /// Create a new style-vec builder. - pub fn new() -> Self { - Self { items: vec![], chains: vec![] } - } - - /// Push a new item into the style vector. - pub fn push(&mut self, item: T, styles: StyleChain<'a>) { - self.items.push(item); - - if let Some((prev, count)) = self.chains.last_mut() { - if *prev == styles { - *count += 1; - return; - } - } - - self.chains.push((styles, 1)); - } - - /// Access the last item mutably and its chain by value. - pub fn last_mut(&mut self) -> Option<(&mut T, StyleChain<'a>)> { - let item = self.items.last_mut()?; - let chain = self.chains.last()?.0; - Some((item, chain)) - } - - /// Iterate over the contained items. - pub fn items(&self) -> std::slice::Iter<'_, T> { - self.items.iter() - } - - /// Finish building, returning a pair of two things: - /// - a style vector of items with the non-shared styles - /// - a shared prefix chain of styles that apply to all items - pub fn finish(self) -> (StyleVec, StyleChain<'a>) { - let mut iter = self.chains.iter(); - let mut trunk = match iter.next() { - Some(&(chain, _)) => chain, - None => return Default::default(), - }; - - let mut shared = trunk.links().count(); - for &(mut chain, _) in iter { - let len = chain.links().count(); - if len < shared { - for _ in 0 .. shared - len { - trunk.pop(); - } - shared = len; - } else if len > shared { - for _ in 0 .. len - shared { - chain.pop(); - } - } - - while shared > 0 && chain != trunk { - trunk.pop(); - chain.pop(); - shared -= 1; - } - } - - let maps = self - .chains - .into_iter() - .map(|(chain, count)| (chain.suffix(shared), count)) - .collect(); - - (StyleVec { items: self.items, maps }, trunk) - } -} - -impl<'a, T> Default for StyleVecBuilder<'a, T> { - fn default() -> Self { - Self::new() - } -} diff --git a/src/eval/value.rs b/src/eval/value.rs index 2ef26059..31287fa8 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -5,12 +5,13 @@ use std::hash::{Hash, Hasher}; use std::num::NonZeroUsize; use std::sync::Arc; -use super::{ops, Args, Array, Content, Dict, Func, Layout, LayoutNode, RawLength}; +use super::{ops, Args, Array, Dict, Func, RawLength}; use crate::diag::{with_alternative, StrResult}; use crate::geom::{ Angle, Color, Dir, Em, Fraction, Length, Paint, Ratio, Relative, RgbaColor, }; use crate::library::text::RawNode; +use crate::model::{Content, Layout, LayoutNode}; use crate::syntax::Spanned; use crate::util::EcoString; diff --git a/src/lib.rs b/src/lib.rs index 6dc52b67..0edaeb99 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,8 +7,8 @@ //! provided in the [AST] module. //! - **Evaluation:** 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 with the contents -//! of the module. The nodes of this tree are well structured and +//! and [content], a hierarchical, styled representation with the contents of +//! the module. The nodes of the content tree are well structured and //! order-independent and thus much better suited for layouting than the raw //! markup. //! - **Layouting:** Next, the tree is [layouted] into a portable version of the @@ -23,8 +23,8 @@ //! [AST]: syntax::ast //! [evaluate]: eval::Eval //! [module]: eval::Module -//! [content]: eval::Content -//! [layouted]: eval::Content::layout +//! [content]: model::Content +//! [layouted]: model::Content::layout //! [PDF]: export::pdf #![allow(clippy::len_without_is_empty)] @@ -45,6 +45,7 @@ pub mod frame; pub mod image; pub mod library; pub mod loading; +pub mod model; pub mod parse; pub mod source; pub mod syntax; @@ -57,11 +58,12 @@ use std::path::PathBuf; use std::sync::Arc; use crate::diag::TypResult; -use crate::eval::{Eval, Module, Scope, Scopes, StyleMap}; +use crate::eval::{Eval, Module, Scope, Scopes}; use crate::font::FontStore; use crate::frame::Frame; use crate::image::ImageStore; use crate::loading::Loader; +use crate::model::StyleMap; use crate::source::{SourceId, SourceStore}; /// The core context which holds the loader, configuration and cached artifacts. diff --git a/src/library/prelude.rs b/src/library/prelude.rs index a79f3c60..584b2ab2 100644 --- a/src/library/prelude.rs +++ b/src/library/prelude.rs @@ -9,12 +9,15 @@ pub use typst_macros::node; pub use crate::diag::{with_alternative, At, Error, StrResult, TypError, TypResult}; pub use crate::eval::{ - Arg, Args, Array, Cast, Content, Dict, Fold, Func, Key, Layout, LayoutNode, Node, - RawAlign, RawLength, RawStroke, Regions, Resolve, Scope, Show, ShowNode, Smart, - StyleChain, StyleMap, StyleVec, Value, + Arg, Args, Array, Cast, Dict, Func, Node, RawAlign, RawLength, RawStroke, Scope, + Smart, Value, }; pub use crate::frame::*; pub use crate::geom::*; +pub use crate::model::{ + Content, Fold, Key, Layout, LayoutNode, Regions, Resolve, Show, ShowNode, StyleChain, + StyleMap, StyleVec, +}; pub use crate::syntax::{Span, Spanned}; pub use crate::util::{EcoString, OptionExt}; pub use crate::Context; diff --git a/src/model/collapse.rs b/src/model/collapse.rs new file mode 100644 index 00000000..5e7a25f8 --- /dev/null +++ b/src/model/collapse.rs @@ -0,0 +1,100 @@ +use super::{StyleChain, StyleVec, StyleVecBuilder}; + +/// A wrapper around a [`StyleVecBuilder`] that allows to collapse items. +pub struct CollapsingBuilder<'a, T> { + builder: StyleVecBuilder<'a, T>, + staged: Vec<(T, StyleChain<'a>, Option)>, + last: Last, +} + +/// What the last non-ignorant item was. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +enum Last { + Weak, + Destructive, + Supportive, +} + +impl<'a, T> CollapsingBuilder<'a, T> { + /// Create a new style-vec builder. + pub fn new() -> Self { + Self { + builder: StyleVecBuilder::new(), + staged: vec![], + last: Last::Destructive, + } + } + + /// Can only exist when there is at least one supportive item to its left + /// and to its right, with no destructive items or weak items in between to + /// its left and no destructive items in between to its right. There may be + /// ignorant items in between in both directions. + pub fn weak(&mut self, item: T, strength: u8, styles: StyleChain<'a>) { + if self.last != Last::Destructive { + if self.last == Last::Weak { + if let Some(i) = self + .staged + .iter() + .position(|(.., prev)| prev.map_or(false, |p| p < strength)) + { + self.staged.remove(i); + } else { + return; + } + } + + self.staged.push((item, styles, Some(strength))); + self.last = Last::Weak; + } + } + + /// Forces nearby weak items to collapse. + pub fn destructive(&mut self, item: T, styles: StyleChain<'a>) { + self.flush(false); + self.push(item, styles); + self.last = Last::Destructive; + } + + /// Allows nearby weak items to exist. + pub fn supportive(&mut self, item: T, styles: StyleChain<'a>) { + self.flush(true); + self.push(item, styles); + self.last = Last::Supportive; + } + + /// Has no influence on other items. + pub fn ignorant(&mut self, item: T, styles: StyleChain<'a>) { + self.staged.push((item, styles, None)); + } + + /// Iterate over the contained items. + pub fn items(&self) -> impl DoubleEndedIterator { + self.builder.items().chain(self.staged.iter().map(|(item, ..)| item)) + } + + /// Return the finish style vec and the common prefix chain. + pub fn finish(mut self) -> (StyleVec, StyleChain<'a>) { + self.flush(false); + self.builder.finish() + } + + /// Push the staged items, filtering out weak items if `supportive` is false. + fn flush(&mut self, supportive: bool) { + for (item, styles, strength) in self.staged.drain(..) { + if supportive || strength.is_none() { + self.builder.push(item, styles); + } + } + } + + /// Push a new item into the style vector. + fn push(&mut self, item: T, styles: StyleChain<'a>) { + self.builder.push(item, styles); + } +} + +impl<'a, T> Default for CollapsingBuilder<'a, T> { + fn default() -> Self { + Self::new() + } +} diff --git a/src/model/content.rs b/src/model/content.rs new file mode 100644 index 00000000..2465f0e3 --- /dev/null +++ b/src/model/content.rs @@ -0,0 +1,604 @@ +use std::fmt::Debug; +use std::hash::Hash; +use std::iter::Sum; +use std::ops::{Add, AddAssign}; + +use typed_arena::Arena; + +use super::{ + CollapsingBuilder, Interruption, Key, Layout, LayoutNode, Show, ShowNode, StyleMap, + StyleVecBuilder, +}; +use crate::diag::StrResult; +use crate::library::layout::{FlowChild, FlowNode, PageNode, PlaceNode, Spacing}; +use crate::library::prelude::*; +use crate::library::structure::{ListItem, ListKind, ListNode, ORDERED, UNORDERED}; +use crate::library::text::{DecoNode, ParChild, ParNode, UNDERLINE}; +use crate::util::EcoString; + +/// Composable representation of styled content. +/// +/// This results from: +/// - anything written between square brackets in Typst +/// - any node constructor +/// +/// Content is represented as a tree of nodes. There are two nodes of special +/// interest: +/// +/// 1. A `Styled` node attaches a style map to other content. For example, a +/// single bold word could be represented as a `Styled(Text("Hello"), +/// [TextNode::STRONG: true])` node. +/// +/// 2. A `Sequence` node content combines other arbitrary content and is the +/// representation of a "flow" of other nodes. So, when you write `[Hi] + +/// [you]` in Typst, this type's [`Add`] implementation is invoked and the +/// two [`Text`](Self::Text) nodes are combined into a single +/// [`Sequence`](Self::Sequence) node. A sequence may contain nested +/// sequences. +#[derive(PartialEq, Clone, Hash)] +pub enum Content { + /// A word space. + Space, + /// A forced line break. If soft (`true`), the preceding line can still be + /// justified, if hard (`false`) not. + Linebreak(bool), + /// Horizontal spacing. + Horizontal(Spacing), + /// Plain text. + Text(EcoString), + /// A smart quote, may be single (`false`) or double (`true`). + Quote(bool), + /// An inline-level node. + Inline(LayoutNode), + /// A paragraph break. + Parbreak, + /// A column break. + Colbreak, + /// Vertical spacing. + Vertical(Spacing), + /// A block-level node. + Block(LayoutNode), + /// An item in an unordered list. + List(ListItem), + /// An item in an ordered list. + Enum(ListItem), + /// A page break. + Pagebreak, + /// A page node. + Page(PageNode), + /// A node that can be realized with styles. + Show(ShowNode), + /// Content with attached styles. + Styled(Arc<(Self, StyleMap)>), + /// A sequence of multiple nodes. + Sequence(Arc>), +} + +impl Content { + /// Create empty content. + pub fn new() -> Self { + Self::sequence(vec![]) + } + + /// Create content from an inline-level node. + pub fn inline(node: T) -> Self + where + T: Layout + Debug + Hash + Sync + Send + 'static, + { + Self::Inline(node.pack()) + } + + /// Create content from a block-level node. + pub fn block(node: T) -> Self + where + T: Layout + Debug + Hash + Sync + Send + 'static, + { + Self::Block(node.pack()) + } + + /// Create content from a showable node. + pub fn show(node: T) -> Self + where + T: Show + Debug + Hash + Sync + Send + 'static, + { + Self::Show(node.pack()) + } + + /// Create a new sequence nodes from multiples nodes. + pub fn sequence(seq: Vec) -> Self { + if seq.len() == 1 { + seq.into_iter().next().unwrap() + } else { + Self::Sequence(Arc::new(seq)) + } + } + + /// Repeat this content `n` times. + pub fn repeat(&self, n: i64) -> StrResult { + let count = usize::try_from(n) + .map_err(|_| format!("cannot repeat this content {} times", n))?; + + Ok(Self::sequence(vec![self.clone(); count])) + } + + /// Style this content with a single style property. + pub fn styled<'k, K: Key<'k>>(mut self, key: K, value: K::Value) -> Self { + if let Self::Styled(styled) = &mut self { + if let Some((_, map)) = Arc::get_mut(styled) { + map.apply(key, value); + return self; + } + } + + Self::Styled(Arc::new((self, StyleMap::with(key, value)))) + } + + /// Style this content with a full style map. + pub fn styled_with_map(mut self, styles: StyleMap) -> Self { + if styles.is_empty() { + return self; + } + + if let Self::Styled(styled) = &mut self { + if let Some((_, map)) = Arc::get_mut(styled) { + map.apply_map(&styles); + return self; + } + } + + Self::Styled(Arc::new((self, styles))) + } + + /// Underline this content. + pub fn underlined(self) -> Self { + Self::show(DecoNode::(self)) + } + + /// Return a node that is spaced apart at top and bottom. + pub fn spaced(self, above: Length, below: Length) -> Self { + if above.is_zero() && below.is_zero() { + return self; + } + + let mut seq = vec![]; + if !above.is_zero() { + seq.push(Content::Vertical(above.into())); + } + + seq.push(self); + + if !below.is_zero() { + seq.push(Content::Vertical(below.into())); + } + + Self::sequence(seq) + } + + /// Layout this content into a collection of pages. + pub fn layout(&self, ctx: &mut Context) -> TypResult>> { + let sya = Arena::new(); + let tpa = Arena::new(); + + let styles = ctx.styles.clone(); + let styles = StyleChain::with_root(&styles); + + let mut builder = Builder::new(&sya, &tpa, true); + builder.process(ctx, self, styles)?; + builder.finish(ctx, styles)?; + + let mut frames = vec![]; + let (pages, shared) = builder.pages.unwrap().finish(); + + for (page, map) in pages.iter() { + let number = 1 + frames.len(); + frames.extend(page.layout(ctx, number, map.chain(&shared))?); + } + + Ok(frames) + } +} + +impl Layout for Content { + fn layout( + &self, + ctx: &mut Context, + regions: &Regions, + styles: StyleChain, + ) -> TypResult>> { + let sya = Arena::new(); + let tpa = Arena::new(); + + let mut builder = Builder::new(&sya, &tpa, false); + builder.process(ctx, self, styles)?; + builder.finish(ctx, styles)?; + + let (flow, shared) = builder.flow.finish(); + FlowNode(flow).layout(ctx, regions, shared) + } + + fn pack(self) -> LayoutNode { + match self { + Content::Block(node) => node, + other => LayoutNode::new(other), + } + } +} + +impl Default for Content { + fn default() -> Self { + Self::new() + } +} + +impl Debug for Content { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Self::Space => f.pad("Space"), + Self::Linebreak(soft) => write!(f, "Linebreak({soft})"), + Self::Horizontal(kind) => write!(f, "Horizontal({kind:?})"), + Self::Text(text) => write!(f, "Text({text:?})"), + Self::Quote(double) => write!(f, "Quote({double})"), + Self::Inline(node) => { + f.write_str("Inline(")?; + node.fmt(f)?; + f.write_str(")") + } + Self::Parbreak => f.pad("Parbreak"), + Self::Colbreak => f.pad("Colbreak"), + Self::Vertical(kind) => write!(f, "Vertical({kind:?})"), + Self::Block(node) => { + f.write_str("Block(")?; + node.fmt(f)?; + f.write_str(")") + } + Self::List(item) => { + f.write_str("- ")?; + item.body.fmt(f) + } + Self::Enum(item) => { + if let Some(number) = item.number { + write!(f, "{}", number)?; + } + f.write_str(". ")?; + item.body.fmt(f) + } + Self::Pagebreak => f.pad("Pagebreak"), + Self::Page(page) => page.fmt(f), + Self::Show(node) => { + f.write_str("Show(")?; + node.fmt(f)?; + f.write_str(")") + } + Self::Styled(styled) => { + let (sub, map) = styled.as_ref(); + map.fmt(f)?; + sub.fmt(f) + } + Self::Sequence(seq) => f.debug_list().entries(seq.iter()).finish(), + } + } +} + +impl Add for Content { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self::Sequence(match (self, rhs) { + (Self::Sequence(mut lhs), Self::Sequence(rhs)) => { + let mutable = Arc::make_mut(&mut lhs); + match Arc::try_unwrap(rhs) { + Ok(vec) => mutable.extend(vec), + Err(rc) => mutable.extend(rc.iter().cloned()), + } + lhs + } + (Self::Sequence(mut lhs), rhs) => { + Arc::make_mut(&mut lhs).push(rhs); + lhs + } + (lhs, Self::Sequence(mut rhs)) => { + Arc::make_mut(&mut rhs).insert(0, lhs); + rhs + } + (lhs, rhs) => Arc::new(vec![lhs, rhs]), + }) + } +} + +impl AddAssign for Content { + fn add_assign(&mut self, rhs: Self) { + *self = std::mem::take(self) + rhs; + } +} + +impl Sum for Content { + fn sum>(iter: I) -> Self { + Self::sequence(iter.collect()) + } +} + +/// Builds a flow or page nodes from content. +struct Builder<'a> { + /// An arena where intermediate style chains are stored. + sya: &'a Arena>, + /// An arena where intermediate content resulting from show rules is stored. + tpa: &'a Arena, + /// The already built page runs. + pages: Option>, + /// The currently built list. + list: Option>, + /// The currently built flow. + flow: CollapsingBuilder<'a, FlowChild>, + /// The currently built paragraph. + par: CollapsingBuilder<'a, ParChild>, + /// Whether to keep the next page even if it is empty. + keep_next: bool, +} + +/// Builds an unordered or ordered list from items. +struct ListBuilder<'a> { + styles: StyleChain<'a>, + kind: ListKind, + items: Vec, + tight: bool, + staged: Vec<(&'a Content, StyleChain<'a>)>, +} + +impl<'a> Builder<'a> { + /// Prepare the builder. + fn new(sya: &'a Arena>, tpa: &'a Arena, top: bool) -> Self { + Self { + sya, + tpa, + pages: top.then(|| StyleVecBuilder::new()), + flow: CollapsingBuilder::new(), + list: None, + par: CollapsingBuilder::new(), + keep_next: true, + } + } + + /// Process content. + fn process( + &mut self, + ctx: &mut Context, + content: &'a Content, + styles: StyleChain<'a>, + ) -> TypResult<()> { + if let Some(builder) = &mut self.list { + match content { + Content::Space => { + builder.staged.push((content, styles)); + return Ok(()); + } + Content::Parbreak => { + builder.staged.push((content, styles)); + return Ok(()); + } + Content::List(item) if builder.kind == UNORDERED => { + builder.tight &= + builder.staged.iter().all(|&(t, _)| *t != Content::Parbreak); + builder.staged.clear(); + builder.items.push(item.clone()); + return Ok(()); + } + Content::Enum(item) if builder.kind == ORDERED => { + builder.tight &= + builder.staged.iter().all(|&(t, _)| *t != Content::Parbreak); + builder.staged.clear(); + builder.items.push(item.clone()); + return Ok(()); + } + _ => self.finish_list(ctx)?, + } + } + + match content { + Content::Space => { + self.par.weak(ParChild::Text(' '.into()), 0, styles); + } + Content::Linebreak(soft) => { + let c = if *soft { '\u{2028}' } else { '\n' }; + self.par.destructive(ParChild::Text(c.into()), styles); + } + Content::Horizontal(kind) => { + let child = ParChild::Spacing(*kind); + if kind.is_fractional() { + self.par.destructive(child, styles); + } else { + self.par.ignorant(child, styles); + } + } + Content::Quote(double) => { + self.par.supportive(ParChild::Quote(*double), styles); + } + Content::Text(text) => { + self.par.supportive(ParChild::Text(text.clone()), styles); + } + Content::Inline(node) => { + self.par.supportive(ParChild::Node(node.clone()), styles); + } + Content::Parbreak => { + self.finish_par(styles); + self.flow.weak(FlowChild::Parbreak, 1, styles); + } + Content::Colbreak => { + self.finish_par(styles); + self.flow.destructive(FlowChild::Colbreak, styles); + } + Content::Vertical(kind) => { + self.finish_par(styles); + let child = FlowChild::Spacing(*kind); + if kind.is_fractional() { + self.flow.destructive(child, styles); + } else { + self.flow.ignorant(child, styles); + } + } + Content::Block(node) => { + self.finish_par(styles); + let child = FlowChild::Node(node.clone()); + if node.is::() { + self.flow.ignorant(child, styles); + } else { + self.flow.supportive(child, styles); + } + self.finish_par(styles); + } + Content::List(item) => { + self.list = Some(ListBuilder { + styles, + kind: UNORDERED, + items: vec![item.clone()], + tight: true, + staged: vec![], + }); + } + Content::Enum(item) => { + self.list = Some(ListBuilder { + styles, + kind: ORDERED, + items: vec![item.clone()], + tight: true, + staged: vec![], + }); + } + Content::Pagebreak => { + self.finish_page(ctx, true, true, styles)?; + } + Content::Page(page) => { + self.finish_page(ctx, false, false, styles)?; + if let Some(pages) = &mut self.pages { + pages.push(page.clone(), styles); + } + } + Content::Show(node) => { + let id = node.id(); + let realized = match styles.realize(ctx, node)? { + Some(content) => content, + None => node.realize(ctx, styles)?, + }; + let content = node.finalize(ctx, styles, realized)?; + let stored = self.tpa.alloc(content); + self.process(ctx, stored, styles.unscoped(id))?; + } + Content::Styled(styled) => { + let (sub, map) = styled.as_ref(); + let stored = self.sya.alloc(styles); + let styles = map.chain(stored); + + let interruption = map.interruption(); + match interruption { + Some(Interruption::Page) => { + self.finish_page(ctx, false, true, styles)? + } + Some(Interruption::Par) => self.finish_par(styles), + None => {} + } + + self.process(ctx, sub, styles)?; + + match interruption { + Some(Interruption::Page) => { + self.finish_page(ctx, true, false, styles)? + } + Some(Interruption::Par) => self.finish_par(styles), + None => {} + } + } + Content::Sequence(seq) => { + for sub in seq.iter() { + self.process(ctx, sub, styles)?; + } + } + } + + Ok(()) + } + + /// Finish the currently built paragraph. + fn finish_par(&mut self, styles: StyleChain<'a>) { + let (mut par, shared) = std::mem::take(&mut self.par).finish(); + if !par.is_empty() { + // 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() + && par + .items() + .find_map(|child| match child { + ParChild::Spacing(_) => None, + ParChild::Text(_) | ParChild::Quote(_) => Some(true), + ParChild::Node(_) => Some(false), + }) + .unwrap_or_default() + && self + .flow + .items() + .rev() + .find_map(|child| match child { + FlowChild::Leading => None, + FlowChild::Parbreak => None, + FlowChild::Node(node) => Some(node.is::()), + FlowChild::Spacing(_) => Some(false), + FlowChild::Colbreak => Some(false), + }) + .unwrap_or_default() + { + par.push_front(ParChild::Spacing(indent.into())); + } + + let node = ParNode(par).pack(); + self.flow.supportive(FlowChild::Node(node), shared); + } + self.flow.weak(FlowChild::Leading, 0, styles); + } + + /// Finish the currently built list. + fn finish_list(&mut self, ctx: &mut Context) -> TypResult<()> { + let ListBuilder { styles, kind, items, tight, staged } = match self.list.take() { + Some(list) => list, + None => return Ok(()), + }; + + let content = match kind { + UNORDERED => Content::show(ListNode:: { start: 1, tight, items }), + ORDERED | _ => Content::show(ListNode:: { start: 1, tight, items }), + }; + + let stored = self.tpa.alloc(content); + self.process(ctx, stored, styles)?; + for (content, styles) in staged { + self.process(ctx, content, styles)?; + } + + Ok(()) + } + + /// Finish the currently built page run. + fn finish_page( + &mut self, + ctx: &mut Context, + keep_last: bool, + keep_next: bool, + styles: StyleChain<'a>, + ) -> TypResult<()> { + self.finish_list(ctx)?; + self.finish_par(styles); + if let Some(pages) = &mut self.pages { + let (flow, shared) = std::mem::take(&mut self.flow).finish(); + if !flow.is_empty() || (keep_last && self.keep_next) { + let styles = if flow.is_empty() { styles } else { shared }; + let node = PageNode(FlowNode(flow).pack()); + pages.push(node, styles); + } + } + self.keep_next = keep_next; + Ok(()) + } + + /// Finish everything. + fn finish(&mut self, ctx: &mut Context, styles: StyleChain<'a>) -> TypResult<()> { + self.finish_page(ctx, true, false, styles) + } +} diff --git a/src/model/layout.rs b/src/model/layout.rs new file mode 100644 index 00000000..da6290ae --- /dev/null +++ b/src/model/layout.rs @@ -0,0 +1,383 @@ +//! Layouting infrastructure. + +use std::any::Any; +use std::fmt::{self, Debug, Formatter}; +use std::hash::Hash; +use std::sync::Arc; + +use super::{Barrier, Resolve, StyleChain}; +use crate::diag::TypResult; +use crate::eval::{RawAlign, RawLength}; +use crate::frame::{Element, Frame, Geometry}; +use crate::geom::{Align, Length, Paint, Point, Relative, Sides, Size, Spec, Stroke}; +use crate::library::graphics::MoveNode; +use crate::library::layout::{AlignNode, PadNode}; +use crate::util::Prehashed; +use crate::Context; + +/// A node that can be layouted into a sequence of regions. +/// +/// Layout return one frame per used region alongside constraints that define +/// whether the result is reusable in other regions. +pub trait Layout: 'static { + /// Layout this node into the given regions, producing constrained frames. + fn layout( + &self, + ctx: &mut Context, + regions: &Regions, + styles: StyleChain, + ) -> TypResult>>; + + /// Convert to a packed node. + fn pack(self) -> LayoutNode + where + Self: Debug + Hash + Sized + Sync + Send + 'static, + { + LayoutNode::new(self) + } +} + +/// A sequence of regions to layout into. +#[derive(Debug, Clone, Hash)] +pub struct Regions { + /// The (remaining) size of the first region. + pub first: Size, + /// The base size for relative sizing. + pub base: Size, + /// The height of followup regions. The width is the same for all regions. + pub backlog: Vec, + /// 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, + /// Whether nodes should expand to fill the regions instead of shrinking to + /// fit the content. + pub expand: Spec, +} + +impl Regions { + /// Create a new region sequence with exactly one region. + pub fn one(size: Size, base: Size, expand: Spec) -> Self { + Self { + first: size, + base, + backlog: vec![], + last: None, + expand, + } + } + + /// Create a new sequence of same-size regions that repeats indefinitely. + pub fn repeat(size: Size, base: Size, expand: Spec) -> Self { + Self { + first: size, + base, + backlog: vec![], + last: Some(size.y), + expand, + } + } + + /// Create new regions where all sizes are mapped with `f`. + /// + /// Note that since all regions must have the same width, the width returned + /// by `f` is ignored for the backlog and the final region. + pub fn map(&self, mut f: F) -> Self + where + F: FnMut(Size) -> Size, + { + let x = self.first.x; + Self { + first: f(self.first), + base: f(self.base), + backlog: self.backlog.iter().map(|&y| f(Size::new(x, y)).y).collect(), + last: self.last.map(|y| f(Size::new(x, y)).y), + expand: self.expand, + } + } + + /// Whether the first region is full and a region break is called for. + pub fn is_full(&self) -> bool { + Length::zero().fits(self.first.y) && !self.in_last() + } + + /// Whether the first region is the last usable region. + /// + /// If this is true, calling `next()` will have no effect. + pub fn in_last(&self) -> bool { + self.backlog.len() == 0 && self.last.map_or(true, |height| self.first.y == height) + } + + /// Advance to the next region if there is any. + pub fn next(&mut self) { + if let Some(height) = (!self.backlog.is_empty()) + .then(|| self.backlog.remove(0)) + .or(self.last) + { + self.first.y = height; + self.base.y = height; + } + } + + /// An iterator that returns the sizes of the first and all following + /// regions, equivalently to what would be produced by calling + /// [`next()`](Self::next) repeatedly until all regions are exhausted. + /// This iterater may be infinite. + pub fn iter(&self) -> impl Iterator + '_ { + let first = std::iter::once(self.first); + let backlog = self.backlog.iter(); + let last = self.last.iter().cycle(); + first.chain(backlog.chain(last).map(|&h| Size::new(self.first.x, h))) + } +} + +/// A type-erased layouting node with a precomputed hash. +#[derive(Clone, Hash)] +pub struct LayoutNode(Arc>); + +impl LayoutNode { + /// Pack any layoutable node. + pub fn new(node: T) -> Self + where + T: Layout + Debug + Hash + Sync + Send + 'static, + { + Self(Arc::new(Prehashed::new(node))) + } + + /// Check whether the contained node is a specific layout node. + pub fn is(&self) -> bool { + (**self.0).as_any().is::() + } + + /// A barrier for the node. + pub fn barrier(&self) -> Barrier { + (**self.0).barrier() + } + + /// Try to downcast to a specific layout node. + pub fn downcast(&self) -> Option<&T> + where + T: Layout + Debug + Hash + 'static, + { + (**self.0).as_any().downcast_ref() + } + + /// Force a size for this node. + pub fn sized(self, sizing: Spec>>) -> Self { + if sizing.any(Option::is_some) { + SizedNode { sizing, child: self }.pack() + } else { + self + } + } + + /// Fill the frames resulting from a node. + pub fn filled(self, fill: Paint) -> Self { + FillNode { fill, child: self }.pack() + } + + /// Stroke the frames resulting from a node. + pub fn stroked(self, stroke: Stroke) -> Self { + StrokeNode { stroke, child: self }.pack() + } + + /// Set alignments for this node. + pub fn aligned(self, aligns: Spec>) -> Self { + if aligns.any(Option::is_some) { + AlignNode { aligns, child: self }.pack() + } else { + self + } + } + + /// Pad this node at the sides. + pub fn padded(self, padding: Sides>) -> Self { + if !padding.left.is_zero() + || !padding.top.is_zero() + || !padding.right.is_zero() + || !padding.bottom.is_zero() + { + PadNode { padding, child: self }.pack() + } else { + self + } + } + + /// Transform this node's contents without affecting layout. + pub fn moved(self, delta: Spec>) -> Self { + if delta.any(|r| !r.is_zero()) { + MoveNode { delta, child: self }.pack() + } else { + self + } + } +} + +impl Layout for LayoutNode { + fn layout( + &self, + ctx: &mut Context, + regions: &Regions, + styles: StyleChain, + ) -> TypResult>> { + ctx.query((self, regions, styles), |ctx, (node, regions, styles)| { + node.0.layout(ctx, regions, node.barrier().chain(&styles)) + }) + .clone() + } + + fn pack(self) -> LayoutNode { + self + } +} + +impl Default for LayoutNode { + fn default() -> Self { + EmptyNode.pack() + } +} + +impl Debug for LayoutNode { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl PartialEq for LayoutNode { + fn eq(&self, other: &Self) -> bool { + self.0.eq(&other.0) + } +} + +trait Bounds: Layout + Debug + Sync + Send + 'static { + fn as_any(&self) -> &dyn Any; + fn barrier(&self) -> Barrier; +} + +impl Bounds for T +where + T: Layout + Debug + Hash + Sync + Send + 'static, +{ + fn as_any(&self) -> &dyn Any { + self + } + + fn barrier(&self) -> Barrier { + Barrier::new::() + } +} + +/// A layout node that produces an empty frame. +/// +/// The packed version of this is returned by [`PackedNode::default`]. +#[derive(Debug, Hash)] +struct EmptyNode; + +impl Layout for EmptyNode { + fn layout( + &self, + _: &mut Context, + regions: &Regions, + _: StyleChain, + ) -> TypResult>> { + Ok(vec![Arc::new(Frame::new( + regions.expand.select(regions.first, Size::zero()), + ))]) + } +} + +/// Fix the size of a node. +#[derive(Debug, Hash)] +struct SizedNode { + /// How to size the node horizontally and vertically. + sizing: Spec>>, + /// The node to be sized. + child: LayoutNode, +} + +impl Layout for SizedNode { + fn layout( + &self, + ctx: &mut Context, + regions: &Regions, + styles: StyleChain, + ) -> TypResult>> { + // The "pod" is the region into which the child will be layouted. + let pod = { + // Resolve the sizing to a concrete size. + let size = self + .sizing + .resolve(styles) + .zip(regions.base) + .map(|(s, b)| s.map(|v| v.relative_to(b))) + .unwrap_or(regions.first); + + // Select the appropriate base and expansion for the child depending + // on whether it is automatically or relatively sized. + let is_auto = self.sizing.map_is_none(); + let base = is_auto.select(regions.base, size); + let expand = regions.expand | !is_auto; + + Regions::one(size, base, expand) + }; + + // Layout the child. + let mut frames = self.child.layout(ctx, &pod, styles)?; + + // Ensure frame size matches regions size if expansion is on. + let frame = &mut frames[0]; + let target = regions.expand.select(regions.first, frame.size); + Arc::make_mut(frame).resize(target, Align::LEFT_TOP); + + Ok(frames) + } +} + +/// Fill the frames resulting from a node. +#[derive(Debug, Hash)] +struct FillNode { + /// How to fill the frames resulting from the `child`. + fill: Paint, + /// The node to fill. + child: LayoutNode, +} + +impl Layout for FillNode { + fn layout( + &self, + ctx: &mut Context, + regions: &Regions, + styles: StyleChain, + ) -> TypResult>> { + let mut frames = self.child.layout(ctx, regions, styles)?; + for frame in &mut frames { + let shape = Geometry::Rect(frame.size).filled(self.fill); + Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape)); + } + Ok(frames) + } +} + +/// Stroke the frames resulting from a node. +#[derive(Debug, Hash)] +struct StrokeNode { + /// How to stroke the frames resulting from the `child`. + stroke: Stroke, + /// The node to stroke. + child: LayoutNode, +} + +impl Layout for StrokeNode { + fn layout( + &self, + ctx: &mut Context, + regions: &Regions, + styles: StyleChain, + ) -> TypResult>> { + let mut frames = self.child.layout(ctx, regions, styles)?; + for frame in &mut frames { + let shape = Geometry::Rect(frame.size).stroked(self.stroke); + Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape)); + } + Ok(frames) + } +} diff --git a/src/model/mod.rs b/src/model/mod.rs new file mode 100644 index 00000000..df39207f --- /dev/null +++ b/src/model/mod.rs @@ -0,0 +1,14 @@ +//! Structured representation of styled content. + +#[macro_use] +mod styles; +mod collapse; +mod content; +mod layout; +mod show; + +pub use collapse::*; +pub use content::*; +pub use layout::*; +pub use show::*; +pub use styles::*; diff --git a/src/model/show.rs b/src/model/show.rs new file mode 100644 index 00000000..5f76ba19 --- /dev/null +++ b/src/model/show.rs @@ -0,0 +1,112 @@ +use std::any::{Any, TypeId}; +use std::fmt::{self, Debug, Formatter}; +use std::hash::Hash; +use std::sync::Arc; + +use super::{Content, StyleChain}; +use crate::diag::TypResult; +use crate::eval::Dict; +use crate::util::Prehashed; +use crate::Context; + +/// A node that can be realized given some styles. +pub trait Show: 'static { + /// Encode this node into a dictionary. + fn encode(&self) -> Dict; + + /// The base recipe for this node that is executed if there is no + /// user-defined show rule. + fn realize(&self, ctx: &mut Context, styles: StyleChain) -> TypResult; + + /// Finalize this node given the realization of a base or user recipe. Use + /// this for effects that should work even in the face of a user-defined + /// show rule, for example: + /// - Application of general settable properties + /// - Attaching things like semantics to a heading + /// + /// Defaults to just the realized content. + #[allow(unused_variables)] + fn finalize( + &self, + ctx: &mut Context, + styles: StyleChain, + realized: Content, + ) -> TypResult { + Ok(realized) + } + + /// Convert to a packed show node. + fn pack(self) -> ShowNode + where + Self: Debug + Hash + Sized + Sync + Send + 'static, + { + ShowNode::new(self) + } +} + +/// A type-erased showable node with a precomputed hash. +#[derive(Clone, Hash)] +pub struct ShowNode(Arc>); + +impl ShowNode { + /// Pack any showable node. + pub fn new(node: T) -> Self + where + T: Show + Debug + Hash + Sync + Send + 'static, + { + Self(Arc::new(Prehashed::new(node))) + } + + /// The type id of this node. + pub fn id(&self) -> TypeId { + self.0.as_any().type_id() + } +} + +impl Show for ShowNode { + fn encode(&self) -> Dict { + self.0.encode() + } + + fn realize(&self, ctx: &mut Context, styles: StyleChain) -> TypResult { + self.0.realize(ctx, styles) + } + + fn finalize( + &self, + ctx: &mut Context, + styles: StyleChain, + realized: Content, + ) -> TypResult { + self.0.finalize(ctx, styles, realized) + } + + fn pack(self) -> ShowNode { + self + } +} + +impl Debug for ShowNode { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl PartialEq for ShowNode { + fn eq(&self, other: &Self) -> bool { + self.0.eq(&other.0) + } +} + +trait Bounds: Show + Debug + Sync + Send + 'static { + fn as_any(&self) -> &dyn Any; +} + +impl Bounds for T +where + T: Show + Debug + Hash + Sync + Send + 'static, +{ + fn as_any(&self) -> &dyn Any { + self + } +} diff --git a/src/model/styles.rs b/src/model/styles.rs new file mode 100644 index 00000000..2195568d --- /dev/null +++ b/src/model/styles.rs @@ -0,0 +1,831 @@ +use std::any::{Any, TypeId}; +use std::fmt::{self, Debug, Formatter}; +use std::hash::Hash; +use std::marker::PhantomData; +use std::sync::Arc; + +use super::{Content, Layout, Show, ShowNode}; +use crate::diag::{At, TypResult}; +use crate::eval::{Args, Func, Node, Smart, Value}; +use crate::geom::{Numeric, Relative, Sides, Spec}; +use crate::library::layout::PageNode; +use crate::library::text::{FontFamily, ParNode, TextNode}; +use crate::syntax::Span; +use crate::util::Prehashed; +use crate::Context; + +/// A map of style properties. +#[derive(Default, Clone, PartialEq, Hash)] +pub struct StyleMap(Vec); + +impl StyleMap { + /// Create a new, empty style map. + pub fn new() -> Self { + Self::default() + } + + /// Whether this map contains no styles. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Create a style map from a single property-value pair. + pub fn with<'a, K: Key<'a>>(key: K, value: K::Value) -> Self { + let mut styles = Self::new(); + styles.set(key, value); + styles + } + + /// Set an inner value for a style property. + /// + /// If the property needs folding and the value is already contained in the + /// style map, `self` contributes the outer values and `value` is the inner + /// one. + pub fn set<'a, K: Key<'a>>(&mut self, key: K, value: K::Value) { + self.0.push(Entry::Property(Property::new(key, value))); + } + + /// Set an inner value for a style property if it is `Some(_)`. + pub fn set_opt<'a, K: Key<'a>>(&mut self, key: K, value: Option) { + if let Some(value) = value { + self.set(key, value); + } + } + + /// Set a font family composed of a preferred family and existing families + /// from a style chain. + pub fn set_family(&mut self, preferred: FontFamily, existing: StyleChain) { + self.set( + TextNode::FAMILY, + std::iter::once(preferred) + .chain(existing.get(TextNode::FAMILY).iter().cloned()) + .collect(), + ); + } + + /// Set a show rule recipe for a node. + pub fn set_recipe(&mut self, func: Func, span: Span) { + self.0.push(Entry::Recipe(Recipe::new::(func, span))); + } + + /// Whether the map contains a style property for the given key. + pub fn contains<'a, K: Key<'a>>(&self, _: K) -> bool { + self.0 + .iter() + .filter_map(|entry| entry.property()) + .any(|property| property.key == TypeId::of::()) + } + + /// Make `self` the first link of the `tail` chain. + /// + /// The resulting style chain contains styles from `self` as well as + /// `tail`. The ones from `self` take precedence over the ones from + /// `tail`. For folded properties `self` contributes the inner value. + pub fn chain<'a>(&'a self, tail: &'a StyleChain<'a>) -> StyleChain<'a> { + if self.is_empty() { + *tail + } else { + StyleChain { head: &self.0, tail: Some(tail) } + } + } + + /// Set an outer value for a style property. + /// + /// If the property needs folding and the value is already contained in the + /// style map, `self` contributes the inner values and `value` is the outer + /// one. + /// + /// Like [`chain`](Self::chain) or [`apply_map`](Self::apply_map), but with + /// only a single property. + pub fn apply<'a, K: Key<'a>>(&mut self, key: K, value: K::Value) { + self.0.insert(0, Entry::Property(Property::new(key, value))); + } + + /// Apply styles from `tail` in-place. The resulting style map is equivalent + /// to the style chain created by `self.chain(StyleChain::new(tail))`. + /// + /// This is useful over `chain` when you want to combine two maps, but you + /// still need an owned map without a lifetime. + pub fn apply_map(&mut self, tail: &Self) { + self.0.splice(0 .. 0, tail.0.iter().cloned()); + } + + /// Mark all contained properties as _scoped_. This means that they only + /// apply to the first descendant node (of their type) in the hierarchy and + /// not its children, too. This is used by [constructors](Node::construct). + pub fn scoped(mut self) -> Self { + for entry in &mut self.0 { + if let Entry::Property(property) = entry { + property.scoped = true; + } + } + self + } + + /// The highest-level kind of of structure the map interrupts. + pub fn interruption(&self) -> Option { + self.0 + .iter() + .filter_map(|entry| entry.property()) + .filter_map(|property| property.interruption()) + .max() + } +} + +impl Debug for StyleMap { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + for entry in self.0.iter().rev() { + writeln!(f, "{:?}", entry)?; + } + Ok(()) + } +} + +/// An entry for a single style property, recipe or barrier. +#[derive(Clone, PartialEq, Hash)] +enum Entry { + /// A style property originating from a set rule or constructor. + Property(Property), + /// A barrier for scoped styles. + Barrier(TypeId, &'static str), + /// A show rule recipe. + Recipe(Recipe), +} + +impl Entry { + /// If this is a property, return it. + fn property(&self) -> Option<&Property> { + match self { + Self::Property(property) => Some(property), + _ => None, + } + } + + /// If this is a recipe, return it. + fn recipe(&self) -> Option<&Recipe> { + match self { + Self::Recipe(recipe) => Some(recipe), + _ => None, + } + } +} + +impl Debug for Entry { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.write_str("#[")?; + match self { + Self::Property(property) => property.fmt(f)?, + Self::Recipe(recipe) => recipe.fmt(f)?, + Self::Barrier(_, name) => write!(f, "Barrier for {name}")?, + } + f.write_str("]") + } +} + +/// A style property originating from a set rule or constructor. +#[derive(Clone, Hash)] +struct Property { + /// The type id of the property's [key](Key). + key: TypeId, + /// The type id of the node the property belongs to. + node: TypeId, + /// The name of the property. + name: &'static str, + /// The property's value. + value: Arc>, + /// Whether the property should only affects the first node down the + /// hierarchy. Used by constructors. + scoped: bool, +} + +impl Property { + /// Create a new property from a key-value pair. + fn new<'a, K: Key<'a>>(_: K, value: K::Value) -> Self { + Self { + key: TypeId::of::(), + node: K::node(), + name: K::NAME, + value: Arc::new(Prehashed::new(value)), + scoped: false, + } + } + + /// What kind of structure the property interrupts. + fn interruption(&self) -> Option { + if self.is_of::() { + Some(Interruption::Page) + } else if self.is_of::() { + Some(Interruption::Par) + } else { + None + } + } + + /// Access the property's value if it is of the given key. + fn downcast<'a, K: Key<'a>>(&'a self) -> Option<&'a K::Value> { + if self.key == TypeId::of::() { + (**self.value).as_any().downcast_ref() + } else { + None + } + } + + /// Whether this property belongs to the node `T`. + fn is_of(&self) -> bool { + self.node == TypeId::of::() + } +} + +impl Debug for Property { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{} = {:?}", self.name, self.value)?; + if self.scoped { + write!(f, " [scoped]")?; + } + Ok(()) + } +} + +impl PartialEq for Property { + fn eq(&self, other: &Self) -> bool { + self.key == other.key + && self.value.eq(&other.value) + && self.scoped == other.scoped + } +} + +trait Bounds: Debug + Sync + Send + 'static { + fn as_any(&self) -> &dyn Any; +} + +impl Bounds for T +where + T: Debug + Sync + Send + 'static, +{ + fn as_any(&self) -> &dyn Any { + self + } +} + +/// Style property keys. +/// +/// This trait is not intended to be implemented manually, but rather through +/// the `#[node]` proc-macro. +pub trait Key<'a>: Copy + 'static { + /// The unfolded type which this property is stored as in a style map. For + /// example, this is [`Toggle`](crate::geom::Length) for the + /// [`STRONG`](TextNode::STRONG) property. + type Value: Debug + Clone + Hash + Sync + Send + 'static; + + /// The folded type of value that is returned when reading this property + /// from a style chain. For example, this is [`bool`] for the + /// [`STRONG`](TextNode::STRONG) property. For non-copy, non-folding + /// properties this is a reference type. + type Output; + + /// The name of the property, used for debug printing. + const NAME: &'static str; + + /// The type id of the node this property belongs to. + fn node() -> TypeId; + + /// Compute an output value from a sequence of values belong to this key, + /// folding if necessary. + fn get( + chain: StyleChain<'a>, + values: impl Iterator, + ) -> 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 Option { + type Output = Option; + + fn resolve(self, styles: StyleChain) -> Self::Output { + self.map(|v| v.resolve(styles)) + } +} + +impl Resolve for Smart { + type Output = Smart; + + fn resolve(self, styles: StyleChain) -> Self::Output { + self.map(|v| v.resolve(styles)) + } +} + +impl Resolve for Spec { + type Output = Spec; + + fn resolve(self, styles: StyleChain) -> Self::Output { + self.map(|v| v.resolve(styles)) + } +} + +impl Resolve for Sides { + type Output = Sides; + + fn resolve(self, styles: StyleChain) -> Self::Output { + Sides { + left: self.left.resolve(styles), + right: self.right.resolve(styles), + top: self.top.resolve(styles), + bottom: self.bottom.resolve(styles), + } + } +} + +impl Resolve for Relative +where + T: Resolve + Numeric, + ::Output: Numeric, +{ + type Output = Relative<::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 Fold for Option +where + T: Fold, + T::Output: Default, +{ + type Output = Option; + + fn fold(self, outer: Self::Output) -> Self::Output { + self.map(|inner| inner.fold(outer.unwrap_or_default())) + } +} + +impl Fold for Smart +where + T: Fold, + T::Output: Default, +{ + type Output = Smart; + + fn fold(self, outer: Self::Output) -> Self::Output { + self.map(|inner| inner.fold(outer.unwrap_or_default())) + } +} + +/// A show rule recipe. +#[derive(Clone, PartialEq, Hash)] +struct Recipe { + /// The affected node. + node: TypeId, + /// The name of the affected node. + name: &'static str, + /// The function that defines the recipe. + func: Func, + /// The span to report all erros with. + span: Span, +} + +impl Recipe { + /// Create a new recipe for the node `T`. + fn new(func: Func, span: Span) -> Self { + Self { + node: TypeId::of::(), + name: std::any::type_name::(), + func, + span, + } + } +} + +impl Debug for Recipe { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "Recipe for {} from {:?}", self.name, self.span) + } +} + +/// A style chain barrier. +/// +/// Barriers interact with [scoped](StyleMap::scoped) styles: A scoped style +/// can still be read through a single barrier (the one of the node it +/// _should_ apply to), but a second barrier will make it invisible. +#[derive(Clone, PartialEq, Hash)] +pub struct Barrier(Entry); + +impl Barrier { + /// Create a new barrier for the layout node `T`. + pub fn new() -> Self { + Self(Entry::Barrier( + TypeId::of::(), + std::any::type_name::(), + )) + } + + /// Make this barrier the first link of the `tail` chain. + pub fn chain<'a>(&'a self, tail: &'a StyleChain) -> StyleChain<'a> { + // We have to store a full `Entry` enum inside the barrier because + // otherwise the `slice::from_ref` trick below won't work. + // Unfortunately, that also means we have to somehow extract the id + // here. + let id = match self.0 { + Entry::Barrier(id, _) => id, + _ => unreachable!(), + }; + + if tail + .entries() + .filter_map(Entry::property) + .any(|p| p.scoped && p.node == id) + { + StyleChain { + head: std::slice::from_ref(&self.0), + tail: Some(tail), + } + } else { + *tail + } + } +} + +impl Debug for Barrier { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +/// Determines whether a style could interrupt some composable structure. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum Interruption { + /// The style forces a paragraph break. + Par, + /// The style forces a page break. + Page, +} + +/// A chain of style maps, 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 +/// map, trying to find a match and then folding it with matches further up the +/// chain. +#[derive(Default, Clone, Copy, Hash)] +pub struct StyleChain<'a> { + /// The first link of this chain. + head: &'a [Entry], + /// The remaining links in the chain. + tail: Option<&'a Self>, +} + +impl<'a> StyleChain<'a> { + /// Create a new, empty style chain. + pub fn new() -> Self { + Self::default() + } + + /// Start a new style chain with a root map. + pub fn with_root(root: &'a StyleMap) -> Self { + Self { head: &root.0, tail: None } + } + + /// Get the output value of a style property. + /// + /// Returns the property's default value if no map in the chain contains an + /// entry for it. Also takes care of resolving and folding and returns + /// references where applicable. + pub fn get>(self, key: K) -> K::Output { + K::get(self, self.values(key)) + } + + /// Realize a node with a user recipe. + pub fn realize( + self, + ctx: &mut Context, + node: &ShowNode, + ) -> TypResult> { + let id = node.id(); + if let Some(recipe) = self + .entries() + .filter_map(Entry::recipe) + .find(|recipe| recipe.node == id) + { + let dict = node.encode(); + let args = Args::from_values(recipe.span, [Value::Dict(dict)]); + Ok(Some(recipe.func.call(ctx, args)?.cast().at(recipe.span)?)) + } else { + Ok(None) + } + } +} + +impl<'a> StyleChain<'a> { + /// Return the chain, but without the trailing scoped property for the given + /// `node`. This is a 90% hack fix for show node constructor scoping. + pub(super) fn unscoped(mut self, node: TypeId) -> Self { + while self + .head + .last() + .and_then(Entry::property) + .map_or(false, |p| p.scoped && p.node == node) + { + let len = self.head.len(); + self.head = &self.head[.. len - 1] + } + self + } + + /// Remove the last link from the chain. + fn pop(&mut self) { + *self = self.tail.copied().unwrap_or_default(); + } + + /// Build a style map from the suffix (all links beyond the `len`) of the + /// chain. + fn suffix(self, len: usize) -> StyleMap { + let mut suffix = StyleMap::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 + } + + /// Iterate over all values for the given property in the chain. + fn values>(self, _: K) -> Values<'a, K> { + Values { + entries: self.entries(), + depth: 0, + key: PhantomData, + } + } + + /// Iterate over the entries of the chain. + fn entries(self) -> Entries<'a> { + Entries { + inner: [].as_slice().iter(), + links: self.links(), + } + } + + /// Iterate over the links of the chain. + fn links(self) -> Links<'a> { + Links(Some(self)) + } +} + +impl Debug for StyleChain<'_> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + for entry in self.entries() { + writeln!(f, "{:?}", entry)?; + } + Ok(()) + } +} + +impl PartialEq for StyleChain<'_> { + fn eq(&self, other: &Self) -> bool { + let as_ptr = |s| s as *const _; + self.head.as_ptr() == other.head.as_ptr() + && self.head.len() == other.head.len() + && self.tail.map(as_ptr) == other.tail.map(as_ptr) + } +} + +/// An iterator over the values in a style chain. +struct Values<'a, K> { + entries: Entries<'a>, + depth: usize, + key: PhantomData, +} + +impl<'a, K: Key<'a>> Iterator for Values<'a, K> { + type Item = &'a K::Value; + + fn next(&mut self) -> Option { + while let Some(entry) = self.entries.next() { + match entry { + Entry::Property(property) => { + if let Some(value) = property.downcast::() { + if !property.scoped || self.depth <= 1 { + return Some(value); + } + } + } + Entry::Barrier(id, _) => { + self.depth += (*id == K::node()) as usize; + } + Entry::Recipe(_) => {} + } + } + + None + } +} + +/// An iterator over the entries in a style chain. +struct Entries<'a> { + inner: std::slice::Iter<'a, Entry>, + links: Links<'a>, +} + +impl<'a> Iterator for Entries<'a> { + type Item = &'a Entry; + + fn next(&mut self) -> Option { + loop { + if let Some(entry) = self.inner.next_back() { + return Some(entry); + } + + match self.links.next() { + Some(next) => self.inner = next.iter(), + None => return None, + } + } + } +} + +/// An iterator over the links of a style chain. +struct Links<'a>(Option>); + +impl<'a> Iterator for Links<'a> { + type Item = &'a [Entry]; + + fn next(&mut self) -> Option { + let StyleChain { head, tail } = self.0?; + self.0 = tail.copied(); + Some(head) + } +} + +/// A sequence of items with associated styles. +#[derive(Hash)] +pub struct StyleVec { + items: Vec, + maps: Vec<(StyleMap, usize)>, +} + +impl StyleVec { + /// Whether there are any items in the sequence. + pub fn is_empty(&self) -> bool { + self.items.is_empty() + } + + /// Number of items in the sequence. + pub fn len(&self) -> usize { + self.items.len() + } + + /// 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 maps(&self) -> impl Iterator { + self.maps.iter().map(|(map, _)| map) + } + + /// Iterate over the contained items. + pub fn items(&self) -> std::slice::Iter<'_, T> { + self.items.iter() + } + + /// Iterate over the contained items and associated style maps. + pub fn iter(&self) -> impl Iterator + '_ { + let styles = self + .maps + .iter() + .flat_map(|(map, count)| std::iter::repeat(map).take(*count)); + self.items().zip(styles) + } + + /// Insert an element in the front. The element will share the style of the + /// current first element. + /// + /// This method has no effect if the vector is empty. + pub fn push_front(&mut self, item: T) { + if !self.maps.is_empty() { + self.items.insert(0, item); + self.maps[0].1 += 1; + } + } +} + +impl Default for StyleVec { + fn default() -> Self { + Self { items: vec![], maps: vec![] } + } +} + +impl Debug for StyleVec { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_list() + .entries(self.iter().map(|(item, map)| { + crate::util::debug(|f| { + map.fmt(f)?; + item.fmt(f) + }) + })) + .finish() + } +} + +/// Assists in the construction of a [`StyleVec`]. +pub struct StyleVecBuilder<'a, T> { + items: Vec, + chains: Vec<(StyleChain<'a>, usize)>, +} + +impl<'a, T> StyleVecBuilder<'a, T> { + /// Create a new style-vec builder. + pub fn new() -> Self { + Self { items: vec![], chains: vec![] } + } + + /// Push a new item into the style vector. + pub fn push(&mut self, item: T, styles: StyleChain<'a>) { + self.items.push(item); + + if let Some((prev, count)) = self.chains.last_mut() { + if *prev == styles { + *count += 1; + return; + } + } + + self.chains.push((styles, 1)); + } + + /// Access the last item mutably and its chain by value. + pub fn last_mut(&mut self) -> Option<(&mut T, StyleChain<'a>)> { + let item = self.items.last_mut()?; + let chain = self.chains.last()?.0; + Some((item, chain)) + } + + /// Iterate over the contained items. + pub fn items(&self) -> std::slice::Iter<'_, T> { + self.items.iter() + } + + /// Finish building, returning a pair of two things: + /// - a style vector of items with the non-shared styles + /// - a shared prefix chain of styles that apply to all items + pub fn finish(self) -> (StyleVec, StyleChain<'a>) { + let mut iter = self.chains.iter(); + let mut trunk = match iter.next() { + Some(&(chain, _)) => chain, + None => return Default::default(), + }; + + let mut shared = trunk.links().count(); + for &(mut chain, _) in iter { + let len = chain.links().count(); + if len < shared { + for _ in 0 .. shared - len { + trunk.pop(); + } + shared = len; + } else if len > shared { + for _ in 0 .. len - shared { + chain.pop(); + } + } + + while shared > 0 && chain != trunk { + trunk.pop(); + chain.pop(); + shared -= 1; + } + } + + let maps = self + .chains + .into_iter() + .map(|(chain, count)| (chain.suffix(shared), count)) + .collect(); + + (StyleVec { items: self.items, maps }, trunk) + } +} + +impl<'a, T> Default for StyleVecBuilder<'a, T> { + fn default() -> Self { + Self::new() + } +} -- cgit v1.2.3