use super::*; use smallvec::smallvec; pub fn layout_tree(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>, flex: FlexLayouter, style: TextStyle, } impl<'a, 'p> TreeLayouter<'a, 'p> { /// Create a new syntax tree layouter. fn new(ctx: LayoutContext<'a, 'p>) -> TreeLayouter<'a, 'p> { TreeLayouter { flex: FlexLayouter::new(FlexContext { flex_spacing: flex_spacing(&ctx.text_style), spaces: ctx.spaces.clone(), axes: ctx.axes, expand: ctx.expand, }), style: ctx.text_style.clone(), ctx, } } fn layout(&mut self, tree: &SyntaxTree) -> LayoutResult<()> { for node in &tree.nodes { match &node.val { Node::Text(text) => self.layout_text(text)?, Node::Space => self.layout_space(), Node::Newline => self.layout_paragraph()?, Node::ToggleItalics => self.style.toggle_class(FontClass::Italic), Node::ToggleBold => self.style.toggle_class(FontClass::Bold), Node::ToggleMonospace => self.style.toggle_class(FontClass::Monospace), 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, })?; Ok(self.flex.add(layout)) } fn layout_space(&mut self) { self.flex.add_primary_space(word_spacing(&self.style), SpaceKind::Soft); } fn layout_paragraph(&mut self) -> LayoutResult<()> { self.flex.add_secondary_space(paragraph_spacing(&self.style), SpaceKind::Soft) } fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> { let spaces = self.flex.remaining(); let mut axes = self.ctx.axes.expanding(false); axes.secondary.alignment = Alignment::Origin; let commands = func.body.val.layout(LayoutContext { loader: self.ctx.loader, top_level: false, text_style: &self.style, page_style: self.ctx.page_style, spaces, axes, expand: false, })?; for command in commands { self.execute(command)?; } Ok(()) } fn execute(&mut self, command: Command) -> LayoutResult<()> { match command { Command::LayoutTree(tree) => self.layout(tree)?, Command::Add(layout) => self.flex.add(layout), Command::AddMultiple(layouts) => self.flex.add_multiple(layouts), Command::AddPrimarySpace(space) => self.flex.add_primary_space(space, SpaceKind::Hard), Command::AddSecondarySpace(space) => self.flex.add_secondary_space(space, SpaceKind::Hard)?, Command::FinishLine => self.flex.add_break(), Command::FinishRun => { self.flex.finish_run()?; }, Command::FinishSpace => self.flex.finish_space(true)?, Command::BreakParagraph => self.layout_paragraph()?, Command::SetTextStyle(style) => self.style = style, Command::SetPageStyle(style) => { if !self.ctx.top_level { lerr!("page style cannot only be altered in the top-level context"); } self.ctx.page_style = style; self.flex.set_spaces(smallvec![ LayoutSpace { dimensions: style.dimensions, padding: style.margins, } ], true); }, Command::SetAxes(axes) => { self.flex.set_axes(axes); self.ctx.axes = axes; } } Ok(()) } fn finish(self) -> LayoutResult { self.flex.finish() } } fn word_spacing(style: &TextStyle) -> Size { style.word_spacing * style.font_size } fn flex_spacing(style: &TextStyle) -> Size { (style.line_spacing - 1.0) * style.font_size } fn paragraph_spacing(style: &TextStyle) -> Size { (style.paragraph_spacing - 1.0) * style.font_size }