use smallvec::smallvec; use crate::func::Command; use crate::syntax::{SyntaxTree, Node, FuncCall}; use crate::style::TextStyle; use super::*; /// Layout a syntax tree into a multibox. pub fn layout(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult { let mut layouter = TreeLayouter::new(ctx); layouter.layout(tree)?; layouter.finish() } #[derive(Debug, Clone)] struct TreeLayouter<'a, 'p> { ctx: LayoutContext<'a, 'p>, layouter: LineLayouter, style: LayoutStyle, } impl<'a, 'p> TreeLayouter<'a, 'p> { /// Create a new syntax tree layouter. fn new(ctx: LayoutContext<'a, 'p>) -> TreeLayouter<'a, 'p> { TreeLayouter { layouter: LineLayouter::new(LineContext { spaces: ctx.spaces.clone(), axes: ctx.axes, alignment: ctx.alignment, repeat: ctx.repeat, debug: ctx.debug, line_spacing: ctx.style.text.line_spacing(), }), style: ctx.style.clone(), ctx, } } fn layout(&mut self, tree: &SyntaxTree) -> LayoutResult<()> { for node in &tree.nodes { match &node.v { Node::Text(text) => self.layout_text(text)?, Node::Space => self.layout_space(), Node::Newline => self.layout_paragraph()?, Node::ToggleItalics => self.style.text.variant.style.toggle(), Node::ToggleBolder => { self.style.text.variant.weight.0 += 300 * if self.style.text.bolder { -1 } else { 1 }; self.style.text.bolder = !self.style.text.bolder; } Node::ToggleMonospace => { let list = &mut self.style.text.fallback.list; match list.get(0).map(|s| s.as_str()) { Some("monospace") => { list.remove(0); }, _ => list.insert(0, "monospace".to_string()), } } Node::Func(func) => self.layout_func(func)?, } } Ok(()) } fn layout_text(&mut self, text: &str) -> LayoutResult<()> { let layout = layout_text(text, TextContext { loader: &self.ctx.loader, style: &self.style.text, axes: self.ctx.axes, alignment: self.ctx.alignment, })?; self.layouter.add(layout) } fn layout_space(&mut self) { self.layouter.add_primary_spacing(self.style.text.word_spacing(), WORD_KIND); } fn layout_paragraph(&mut self) -> LayoutResult<()> { self.layouter.add_secondary_spacing(self.style.text.paragraph_spacing(), PARAGRAPH_KIND) } fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> { let commands = func.0.layout(LayoutContext { style: &self.style, spaces: self.layouter.remaining(), nested: true, debug: false, .. self.ctx })?; for command in commands { self.execute(command)?; } Ok(()) } fn execute(&mut self, command: Command) -> LayoutResult<()> { use Command::*; match command { LayoutTree(tree) => self.layout(tree)?, Add(layout) => self.layouter.add(layout)?, AddMultiple(layouts) => self.layouter.add_multiple(layouts)?, SpacingFunc(space, kind, axis) => match axis { Primary => self.layouter.add_primary_spacing(space, kind), Secondary => self.layouter.add_secondary_spacing(space, kind)?, } FinishLine => self.layouter.finish_line()?, FinishSpace => self.layouter.finish_space(true)?, BreakParagraph => self.layout_paragraph()?, BreakPage => { if self.ctx.nested { error!("page break cannot be issued from nested context"); } self.layouter.finish_space(true)? } SetTextStyle(style) => { self.layouter.set_line_spacing(style.line_spacing()); self.style.text = style; } SetPageStyle(style) => { if self.ctx.nested { error!("page style cannot be altered in nested context"); } self.style.page = style; let margins = style.margins(); self.ctx.base = style.dimensions.unpadded(margins); self.layouter.set_spaces(smallvec![ LayoutSpace { dimensions: style.dimensions, padding: margins, expansion: LayoutExpansion::new(true, true), } ], true); } SetAlignment(alignment) => self.ctx.alignment = alignment, SetAxes(axes) => { self.layouter.set_axes(axes); self.ctx.axes = axes; } } Ok(()) } fn finish(self) -> LayoutResult { self.layouter.finish() } }