summaryrefslogtreecommitdiff
path: root/src/layout/tree.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2020-08-02 22:05:49 +0200
committerLaurenz <laurmaedje@gmail.com>2020-08-02 22:05:49 +0200
commit266d457292e7461d448f9141030028ea68b573d1 (patch)
treeff3ff3cc289d34040db421b6a7faa1f2aa402b05 /src/layout/tree.rs
parentcbbc46215fe0a0ad8a50e991ec442890b8eadc0a (diff)
Refactor model into tree 🛒
Diffstat (limited to 'src/layout/tree.rs')
-rw-r--r--src/layout/tree.rs223
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,
+ );
+ }
+}