//! Composable layouts. mod align; mod columns; mod container; #[path = "enum.rs"] mod enum_; mod flow; mod fragment; mod grid; mod hide; mod list; mod pad; mod page; mod par; mod place; mod regions; mod repeat; mod spacing; mod stack; mod table; mod terms; mod transform; pub use self::align::*; pub use self::columns::*; pub use self::container::*; pub use self::enum_::*; pub use self::flow::*; pub use self::fragment::*; pub use self::grid::*; pub use self::hide::*; pub use self::list::*; pub use self::pad::*; pub use self::page::*; pub use self::par::*; pub use self::place::*; pub use self::regions::*; pub use self::repeat::*; pub use self::spacing::*; pub use self::stack::*; pub use self::table::*; pub use self::terms::*; pub use self::transform::*; use std::mem; use typed_arena::Arena; use typst::diag::SourceResult; use typst::model::{ applicable, realize, Content, Node, SequenceNode, Style, StyleChain, StyleVecBuilder, StyledNode, }; use crate::math::{FormulaNode, LayoutMath}; use crate::meta::DocumentNode; use crate::prelude::*; use crate::shared::BehavedBuilder; use crate::text::{LinebreakNode, SmartQuoteNode, SpaceNode, TextNode}; use crate::visualize::{CircleNode, EllipseNode, ImageNode, RectNode, SquareNode}; /// Root-level layout. pub trait LayoutRoot { /// Layout into one frame per page. fn layout_root(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult; } impl LayoutRoot for Content { fn layout_root(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult { #[comemo::memoize] fn cached( node: &Content, world: Tracked, provider: TrackedMut, introspector: Tracked, styles: StyleChain, ) -> SourceResult { let mut vt = Vt { world, provider, introspector }; let scratch = Scratch::default(); let (realized, styles) = realize_root(&mut vt, &scratch, node, styles)?; realized .with::() .unwrap() .layout_root(&mut vt, styles) } cached( self, vt.world, TrackedMut::reborrow_mut(&mut vt.provider), vt.introspector, styles, ) } } /// Layout into regions. pub trait Layout { /// Layout into one frame per region. fn layout( &self, vt: &mut Vt, styles: StyleChain, regions: Regions, ) -> SourceResult; } impl Layout for Content { fn layout( &self, vt: &mut Vt, styles: StyleChain, regions: Regions, ) -> SourceResult { #[comemo::memoize] fn cached( node: &Content, world: Tracked, provider: TrackedMut, introspector: Tracked, styles: StyleChain, regions: Regions, ) -> SourceResult { let mut vt = Vt { world, provider, introspector }; let scratch = Scratch::default(); let (realized, styles) = realize_block(&mut vt, &scratch, node, styles)?; let barrier = Style::Barrier(realized.id()); let styles = styles.chain_one(&barrier); realized .with::() .unwrap() .layout(&mut vt, styles, regions) } cached( self, vt.world, TrackedMut::reborrow_mut(&mut vt.provider), vt.introspector, styles, regions, ) } } /// Realize into a node that is capable of root-level layout. fn realize_root<'a>( vt: &mut Vt, scratch: &'a Scratch<'a>, content: &'a Content, styles: StyleChain<'a>, ) -> SourceResult<(Content, StyleChain<'a>)> { if content.has::() && !applicable(content, styles) { return Ok((content.clone(), styles)); } let mut builder = Builder::new(vt, &scratch, true); builder.accept(content, styles)?; builder.interrupt_page(Some(styles))?; let (pages, shared) = builder.doc.unwrap().pages.finish(); Ok((DocumentNode::new(pages.to_vec()).pack(), shared)) } /// Realize into a node that is capable of block-level layout. fn realize_block<'a>( vt: &mut Vt, scratch: &'a Scratch<'a>, content: &'a Content, styles: StyleChain<'a>, ) -> SourceResult<(Content, StyleChain<'a>)> { if content.has::() && !content.is::() && !content.is::() && !content.is::() && !content.is::() && !content.is::() && !applicable(content, styles) { return Ok((content.clone(), styles)); } let mut builder = Builder::new(vt, &scratch, false); builder.accept(content, styles)?; builder.interrupt_par()?; let (children, shared) = builder.flow.0.finish(); Ok((FlowNode::new(children.to_vec()).pack(), shared)) } /// Builds a document or a flow node from content. struct Builder<'a, 'v, 't> { /// The virtual typesetter. vt: &'v mut Vt<'t>, /// Scratch arenas for building. scratch: &'a Scratch<'a>, /// The current document building state. doc: Option>, /// The current flow building state. flow: FlowBuilder<'a>, /// The current paragraph building state. par: ParBuilder<'a>, /// The current list building state. list: ListBuilder<'a>, } /// Temporary storage arenas for building. #[derive(Default)] struct Scratch<'a> { /// An arena where intermediate style chains are stored. styles: Arena>, /// An arena where intermediate content resulting from show rules is stored. content: Arena, maps: Arena, } impl<'a, 'v, 't> Builder<'a, 'v, 't> { fn new(vt: &'v mut Vt<'t>, scratch: &'a Scratch<'a>, top: bool) -> Self { Self { vt, scratch, doc: top.then(|| DocBuilder::default()), flow: FlowBuilder::default(), par: ParBuilder::default(), list: ListBuilder::default(), } } fn accept( &mut self, mut content: &'a Content, styles: StyleChain<'a>, ) -> SourceResult<()> { if content.has::() && !content.is::() { content = self.scratch.content.alloc(FormulaNode::new(content.clone()).pack()); } // Prepare only if this is the first application for this node. if let Some(node) = content.with::() { if !content.is_prepared() { let prepared = node.prepare(self.vt, content.clone().prepared(), styles)?; let stored = self.scratch.content.alloc(prepared); return self.accept(stored, styles); } } if let Some(styled) = content.to::() { return self.styled(styled, styles); } if let Some(seq) = content.to::() { for sub in seq.children() { let stored = self.scratch.content.alloc(sub); self.accept(stored, styles)?; } return Ok(()); } if let Some(realized) = realize(self.vt, content, styles)? { let stored = self.scratch.content.alloc(realized); return self.accept(stored, styles); } if self.list.accept(content, styles) { return Ok(()); } self.interrupt_list()?; if self.list.accept(content, styles) { return Ok(()); } if self.par.accept(content, styles) { return Ok(()); } self.interrupt_par()?; if self.flow.accept(content, styles) { return Ok(()); } let keep = content .to::() .map_or(false, |pagebreak| !pagebreak.weak()); self.interrupt_page(keep.then(|| styles))?; if let Some(doc) = &mut self.doc { if doc.accept(content, styles) { return Ok(()); } } if let Some(span) = content.span() { bail!(span, "not allowed here"); } Ok(()) } fn styled( &mut self, styled: &'a StyledNode, styles: StyleChain<'a>, ) -> SourceResult<()> { let map = self.scratch.maps.alloc(styled.map()); let stored = self.scratch.styles.alloc(styles); let content = self.scratch.content.alloc(styled.body()); let styles = stored.chain(map); self.interrupt_style(&map, None)?; self.accept(content, styles)?; self.interrupt_style(map, Some(styles))?; Ok(()) } fn interrupt_style( &mut self, map: &StyleMap, styles: Option>, ) -> SourceResult<()> { if let Some(Some(span)) = map.interruption::() { if self.doc.is_none() { bail!(span, "not allowed here"); } if styles.is_none() && (!self.flow.0.is_empty() || !self.par.0.is_empty() || !self.list.items.is_empty()) { bail!(span, "must appear before any content"); } } else if let Some(Some(span)) = map.interruption::() { if self.doc.is_none() { bail!(span, "not allowed here"); } self.interrupt_page(styles)?; } else if map.interruption::().is_some() || map.interruption::().is_some() { self.interrupt_par()?; } else if map.interruption::().is_some() || map.interruption::().is_some() || map.interruption::().is_some() { self.interrupt_list()?; } Ok(()) } fn interrupt_list(&mut self) -> SourceResult<()> { if !self.list.items.is_empty() { let staged = mem::take(&mut self.list.staged); let (list, styles) = mem::take(&mut self.list).finish(); let stored = self.scratch.content.alloc(list); self.accept(stored, styles)?; for (content, styles) in staged { self.accept(content, styles)?; } } Ok(()) } fn interrupt_par(&mut self) -> SourceResult<()> { self.interrupt_list()?; if !self.par.0.is_empty() { let (par, styles) = mem::take(&mut self.par).finish(); let stored = self.scratch.content.alloc(par); self.accept(stored, styles)?; } Ok(()) } fn interrupt_page(&mut self, styles: Option>) -> SourceResult<()> { self.interrupt_par()?; let Some(doc) = &mut self.doc else { return Ok(()) }; if !self.flow.0.is_empty() || (doc.keep_next && styles.is_some()) { let (flow, shared) = mem::take(&mut self.flow).0.finish(); let styles = if shared == StyleChain::default() { styles.unwrap() } else { shared }; let page = PageNode::new(FlowNode::new(flow.to_vec()).pack()).pack(); let stored = self.scratch.content.alloc(page); self.accept(stored, styles)?; } Ok(()) } } /// Accepts pagebreaks and pages. struct DocBuilder<'a> { /// The page runs built so far. pages: StyleVecBuilder<'a, Content>, /// Whether to keep a following page even if it is empty. keep_next: bool, } impl<'a> DocBuilder<'a> { fn accept(&mut self, content: &Content, styles: StyleChain<'a>) -> bool { if let Some(pagebreak) = content.to::() { self.keep_next = !pagebreak.weak(); return true; } if content.is::() { self.pages.push(content.clone(), styles); self.keep_next = false; return true; } false } } impl Default for DocBuilder<'_> { fn default() -> Self { Self { pages: StyleVecBuilder::new(), keep_next: true } } } /// Accepts flow content. #[derive(Default)] struct FlowBuilder<'a>(BehavedBuilder<'a>, bool); impl<'a> FlowBuilder<'a> { fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { if content.is::() { self.1 = true; return true; } let last_was_parbreak = self.1; self.1 = false; if content.is::() || content.is::() { self.0.push(content.clone(), styles); return true; } if content.has::() || content.is::() { let is_tight_list = if let Some(node) = content.to::() { node.tight() } else if let Some(node) = content.to::() { node.tight() } else if let Some(node) = content.to::() { node.tight() } else { false }; if !last_was_parbreak && is_tight_list { let leading = ParNode::leading_in(styles); let spacing = VNode::list_attach(leading.into()); self.0.push(spacing.pack(), styles); } let above = BlockNode::above_in(styles); let below = BlockNode::below_in(styles); self.0.push(above.clone().pack(), styles); self.0.push(content.clone(), styles); self.0.push(below.clone().pack(), styles); return true; } false } } /// Accepts paragraph content. #[derive(Default)] struct ParBuilder<'a>(BehavedBuilder<'a>); impl<'a> ParBuilder<'a> { fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { if content.is::() || content.is::() || content.is::() || content.is::() || content.is::() || content.to::().map_or(false, |node| !node.block()) || content.is::() { self.0.push(content.clone(), styles); return true; } false } fn finish(self) -> (Content, StyleChain<'a>) { let (children, shared) = self.0.finish(); (ParNode::new(children.to_vec()).pack(), shared) } } /// Accepts list / enum items, spaces, paragraph breaks. struct ListBuilder<'a> { /// The list items collected so far. items: StyleVecBuilder<'a, Content>, /// Whether the list contains no paragraph breaks. tight: bool, /// Trailing content for which it is unclear whether it is part of the list. staged: Vec<(&'a Content, StyleChain<'a>)>, } impl<'a> ListBuilder<'a> { fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { if !self.items.is_empty() && (content.is::() || content.is::()) { self.staged.push((content, styles)); return true; } if (content.is::() || content.is::() || content.is::()) && self .items .items() .next() .map_or(true, |first| first.id() == content.id()) { self.items.push(content.clone(), styles); self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::()); return true; } false } fn finish(self) -> (Content, StyleChain<'a>) { let (items, shared) = self.items.finish(); let item = items.items().next().unwrap(); let output = if item.is::() { ListNode::new( items .iter() .map(|(item, map)| { let item = item.to::().unwrap(); ListItem::new(item.body().styled_with_map(map.clone())) }) .collect::>(), ) .with_tight(self.tight) .pack() } else if item.is::() { EnumNode::new( items .iter() .map(|(item, map)| { let item = item.to::().unwrap(); EnumItem::new(item.body().styled_with_map(map.clone())) .with_number(item.number()) }) .collect::>(), ) .with_tight(self.tight) .pack() } else if item.is::() { TermsNode::new( items .iter() .map(|(item, map)| { let item = item.to::().unwrap(); TermItem::new( item.term().styled_with_map(map.clone()), item.description().styled_with_map(map.clone()), ) }) .collect::>(), ) .with_tight(self.tight) .pack() } else { unreachable!() }; (output, shared) } } impl Default for ListBuilder<'_> { fn default() -> Self { Self { items: StyleVecBuilder::default(), tight: true, staged: vec![], } } }