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)) } /// Add vertical spacing above and below the node. 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) } }