diff options
| author | Laurenz <laurmaedje@gmail.com> | 2020-08-02 22:05:49 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2020-08-02 22:05:49 +0200 |
| commit | 266d457292e7461d448f9141030028ea68b573d1 (patch) | |
| tree | ff3ff3cc289d34040db421b6a7faa1f2aa402b05 /src/layout/tree.rs | |
| parent | cbbc46215fe0a0ad8a50e991ec442890b8eadc0a (diff) | |
Refactor model into tree 🛒
Diffstat (limited to 'src/layout/tree.rs')
| -rw-r--r-- | src/layout/tree.rs | 223 |
1 files changed, 223 insertions, 0 deletions
diff --git a/src/layout/tree.rs b/src/layout/tree.rs new file mode 100644 index 00000000..44c59211 --- /dev/null +++ b/src/layout/tree.rs @@ -0,0 +1,223 @@ +//! The tree layouter layouts trees (i.e. +//! [syntax trees](crate::syntax::SyntaxTree) and [functions](crate::func)) +//! by executing commands issued by the trees. + +use crate::{Pass, Feedback, DynFuture}; +use crate::style::LayoutStyle; +use crate::syntax::decoration::Decoration; +use crate::syntax::tree::{SyntaxTree, SyntaxNode, DynamicNode}; +use crate::syntax::span::{Span, Spanned}; +use super::line::{LineLayouter, LineContext}; +use super::text::{layout_text, TextContext}; +use super::*; + +/// Performs the tree layouting. +#[derive(Debug)] +pub struct TreeLayouter<'a> { + ctx: LayoutContext<'a>, + layouter: LineLayouter, + style: LayoutStyle, + feedback: Feedback, +} + +impl<'a> TreeLayouter<'a> { + /// Create a new tree layouter. + pub fn new(ctx: LayoutContext<'a>) -> TreeLayouter<'a> { + TreeLayouter { + layouter: LineLayouter::new(LineContext { + spaces: ctx.spaces.clone(), + axes: ctx.axes, + align: ctx.align, + repeat: ctx.repeat, + line_spacing: ctx.style.text.line_spacing(), + }), + style: ctx.style.clone(), + ctx, + feedback: Feedback::new(), + } + } + + /// Layout a syntax tree by directly processing the nodes instead of using + /// the command based architecture. + pub async fn layout_tree(&mut self, tree: &SyntaxTree) { + for node in tree { + self.layout_node(node).await; + } + } + + pub async fn layout_node(&mut self, node: &Spanned<SyntaxNode>) { + let decorate = |this: &mut TreeLayouter, deco| { + this.feedback.decorations.push(Spanned::new(deco, node.span)); + }; + + match &node.v { + SyntaxNode::Space => self.layout_space(), + SyntaxNode::Parbreak => self.layout_paragraph(), + SyntaxNode::Linebreak => self.layouter.finish_line(), + + SyntaxNode::Text(text) => { + if self.style.text.italic { + decorate(self, Decoration::Italic); + } + + if self.style.text.bolder { + decorate(self, Decoration::Bold); + } + + self.layout_text(text).await; + } + + SyntaxNode::ToggleItalic => { + self.style.text.italic = !self.style.text.italic; + decorate(self, Decoration::Italic); + } + + SyntaxNode::ToggleBolder => { + self.style.text.bolder = !self.style.text.bolder; + decorate(self, Decoration::Bold); + } + + SyntaxNode::Raw(lines) => { + // TODO: Make this more efficient. + let fallback = self.style.text.fallback.clone(); + self.style.text.fallback.list_mut().insert(0, "monospace".to_string()); + self.style.text.fallback.flatten(); + + // Layout the first line. + let mut iter = lines.iter(); + if let Some(line) = iter.next() { + self.layout_text(line).await; + } + + // Put a newline before each following line. + for line in iter { + self.layouter.finish_line(); + self.layout_text(line).await; + } + + self.style.text.fallback = fallback; + } + + SyntaxNode::Dyn(dynamic) => { + self.layout_dyn(Spanned::new(dynamic.as_ref(), node.span)).await; + } + } + } + + /// Layout a node into this layouting process. + pub async fn layout_dyn(&mut self, dynamic: Spanned<&dyn DynamicNode>) { + // Execute the tree's layout function which generates the commands. + let layouted = dynamic.v.layout(LayoutContext { + style: &self.style, + spaces: self.layouter.remaining(), + nested: true, + .. self.ctx + }).await; + + // Add the errors generated by the tree to the error list. + self.feedback.extend_offset(layouted.feedback, dynamic.span.start); + + for command in layouted.output { + self.execute_command(command, dynamic.span).await; + } + } + + /// Compute the finished list of boxes. + pub fn finish(self) -> Pass<MultiLayout> { + Pass::new(self.layouter.finish(), self.feedback) + } + + /// Execute a command issued by a tree. When the command is errorful, the + /// given span is stored with the error. + fn execute_command<'r>( + &'r mut self, + command: Command<'r>, + tree_span: Span, + ) -> DynFuture<'r, ()> { Box::pin(async move { + use Command::*; + + match command { + LayoutSyntaxTree(tree) => self.layout_tree(tree).await, + + Add(layout) => self.layouter.add(layout), + AddMultiple(layouts) => self.layouter.add_multiple(layouts), + AddSpacing(space, kind, axis) => match axis { + Primary => self.layouter.add_primary_spacing(space, kind), + Secondary => self.layouter.add_secondary_spacing(space, kind), + } + + BreakLine => self.layouter.finish_line(), + BreakParagraph => self.layout_paragraph(), + BreakPage => { + if self.ctx.nested { + error!( + @self.feedback, tree_span, + "page break cannot be issued from nested context", + ); + } else { + 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!( + @self.feedback, tree_span, + "page style cannot be changed from nested context", + ); + } else { + self.style.page = style; + + // The line layouter has no idea of page styles and thus we + // need to recompute the layouting space resulting of the + // new page style and update it within the layouter. + let margins = style.margins(); + self.ctx.base = style.size.unpadded(margins); + self.layouter.set_spaces(vec![ + LayoutSpace { + size: style.size, + padding: margins, + expansion: LayoutExpansion::new(true, true), + } + ], true); + } + } + + SetAlignment(align) => self.ctx.align = align, + SetAxes(axes) => { + self.layouter.set_axes(axes); + self.ctx.axes = axes; + } + } + }) } + + /// Layout a continous piece of text and add it to the line layouter. + async fn layout_text(&mut self, text: &str) { + self.layouter.add(layout_text(text, TextContext { + loader: &self.ctx.loader, + style: &self.style.text, + axes: self.ctx.axes, + align: self.ctx.align, + }).await) + } + + /// Add the spacing for a syntactic space node. + fn layout_space(&mut self) { + self.layouter.add_primary_spacing( + self.style.text.word_spacing(), + SpacingKind::WORD, + ); + } + + /// Finish the paragraph and add paragraph spacing. + fn layout_paragraph(&mut self) { + self.layouter.add_secondary_spacing( + self.style.text.paragraph_spacing(), + SpacingKind::PARAGRAPH, + ); + } +} |
