diff options
Diffstat (limited to 'src/model/content.rs')
| -rw-r--r-- | src/model/content.rs | 604 |
1 files changed, 604 insertions, 0 deletions
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<Vec<Self>>), +} + +impl Content { + /// Create empty content. + pub fn new() -> Self { + Self::sequence(vec![]) + } + + /// Create content from an inline-level node. + pub fn inline<T>(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<T>(node: T) -> Self + where + T: Layout + Debug + Hash + Sync + Send + 'static, + { + Self::Block(node.pack()) + } + + /// Create content from a showable node. + pub fn show<T>(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>) -> 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<Self> { + 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::<UNDERLINE>(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<Vec<Arc<Frame>>> { + 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<Vec<Arc<Frame>>> { + 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<I: Iterator<Item = Self>>(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<StyleChain<'a>>, + /// An arena where intermediate content resulting from show rules is stored. + tpa: &'a Arena<Content>, + /// The already built page runs. + pages: Option<StyleVecBuilder<'a, PageNode>>, + /// The currently built list. + list: Option<ListBuilder<'a>>, + /// 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<ListItem>, + tight: bool, + staged: Vec<(&'a Content, StyleChain<'a>)>, +} + +impl<'a> Builder<'a> { + /// Prepare the builder. + fn new(sya: &'a Arena<StyleChain<'a>>, tpa: &'a Arena<Content>, 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::<PlaceNode>() { + 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::<ParNode>()), + 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::<UNORDERED> { start: 1, tight, items }), + ORDERED | _ => Content::show(ListNode::<ORDERED> { 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) + } +} |
