From f29207d999b9aa4fe4637556a507eb252246ecf8 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Mon, 12 Oct 2020 22:06:28 +0200 Subject: [PATCH] =?UTF-8?q?Strongly=20typed=20groups=20=F0=9F=91=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/eval/mod.rs | 176 +++++++++++++++++++++++++++---------------- src/library/align.rs | 8 +- src/library/boxed.rs | 11 +-- 3 files changed, 118 insertions(+), 77 deletions(-) diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 62ce6c202..71a097495 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -22,7 +22,7 @@ use fontdock::FontStyle; use crate::diag::Diag; use crate::diag::{Deco, Feedback, Pass}; -use crate::geom::{Gen, Length, Relative}; +use crate::geom::{Align, Dir, Gen, Length, Linear, Relative, Sides, Size}; use crate::layout::{ Document, Expansion, LayoutNode, Pad, Pages, Par, Softness, Spacing, Stack, Text, }; @@ -113,6 +113,87 @@ impl EvalContext { self.inner.push(node); } + /// Start a page group based on the active page state. + /// + /// If `hard` is false, empty page runs will be omitted from the output. + /// + /// This also starts an inner paragraph. + pub fn start_page_group(&mut self, hard: bool) { + self.start_group(PageGroup { + size: self.state.page.size, + padding: self.state.page.margins(), + dirs: self.state.dirs, + aligns: self.state.aligns, + hard, + }); + self.start_par_group(); + } + + /// End a page group and push it to the finished page runs. + /// + /// This also ends an inner paragraph. + pub fn end_page_group(&mut self) { + self.end_par_group(); + let (group, children) = self.end_group::(); + if group.hard || !children.is_empty() { + self.runs.push(Pages { + size: group.size, + child: LayoutNode::dynamic(Pad { + padding: group.padding, + child: LayoutNode::dynamic(Stack { + dirs: group.dirs, + aligns: group.aligns, + expansion: Gen::new(Expansion::Fill, Expansion::Fill), + children, + }), + }), + }) + } + } + + /// Start a content group. + /// + /// This also starts an inner paragraph. + pub fn start_content_group(&mut self) { + self.start_group(ContentGroup); + self.start_par_group(); + } + + /// End a content group and return the resulting nodes. + /// + /// This also ends an inner paragraph. + pub fn end_content_group(&mut self) -> Vec { + self.end_par_group(); + self.end_group::().1 + } + + /// Start a paragraph group based on the active text state. + pub fn start_par_group(&mut self) { + let em = self.state.font.font_size(); + self.start_group(ParGroup { + dirs: self.state.dirs, + aligns: self.state.aligns, + line_spacing: self.state.par.line_spacing.eval(em), + }); + } + + /// End a paragraph group and push it to its parent group if it's not empty. + pub fn end_par_group(&mut self) { + let (group, children) = self.end_group::(); + if !children.is_empty() { + // FIXME: This is a hack and should be superseded by something + // better. + let cross_expansion = Expansion::fill_if(self.groups.len() <= 1); + self.push(Par { + dirs: group.dirs, + aligns: group.aligns, + cross_expansion, + line_spacing: group.line_spacing, + children, + }); + } + } + /// Start a layouting group. /// /// All further calls to [`push`] will collect nodes for this group. @@ -121,7 +202,7 @@ impl EvalContext { /// /// [`push`]: #method.push /// [`end_group`]: #method.end_group - pub fn start_group(&mut self, meta: T) { + fn start_group(&mut self, meta: T) { self.groups.push((Box::new(meta), std::mem::take(&mut self.inner))); } @@ -130,7 +211,7 @@ impl EvalContext { /// This returns the stored metadata and the collected nodes. /// /// [`start_group`]: #method.start_group - pub fn end_group(&mut self) -> (T, Vec) { + fn end_group(&mut self) -> (T, Vec) { if let Some(&LayoutNode::Spacing(spacing)) = self.inner.last() { if spacing.softness == Softness::Soft { self.inner.pop(); @@ -142,69 +223,6 @@ impl EvalContext { (group, std::mem::replace(&mut self.inner, outer)) } - /// Start a page run group based on the active page state. - /// - /// If `hard` is false, empty page runs will be omitted from the output. - /// - /// This also starts an inner paragraph. - pub fn start_page_group(&mut self, hard: bool) { - let size = self.state.page.size; - let margins = self.state.page.margins(); - let dirs = self.state.dirs; - let aligns = self.state.aligns; - self.start_group((size, margins, dirs, aligns, hard)); - self.start_par_group(); - } - - /// End a page run group and push it to its parent group. - /// - /// This also ends an inner paragraph. - pub fn end_page_group(&mut self) { - self.end_par_group(); - let ((size, padding, dirs, aligns, hard), children) = self.end_group(); - let hard: bool = hard; - if hard || !children.is_empty() { - self.runs.push(Pages { - size, - child: LayoutNode::dynamic(Pad { - padding, - child: LayoutNode::dynamic(Stack { - dirs, - aligns, - expansion: Gen::new(Expansion::Fill, Expansion::Fill), - children, - }), - }), - }) - } - } - - /// Start a paragraph group based on the active text state. - pub fn start_par_group(&mut self) { - let dirs = self.state.dirs; - let em = self.state.font.font_size(); - let line_spacing = self.state.par.line_spacing.eval(em); - let aligns = self.state.aligns; - self.start_group((dirs, line_spacing, aligns)); - } - - /// End a paragraph group and push it to its parent group if its not empty. - pub fn end_par_group(&mut self) { - let ((dirs, line_spacing, aligns), children) = self.end_group(); - if !children.is_empty() { - // FIXME: This is a hack and should be superseded by constraints - // having min and max size. - let cross_expansion = Expansion::fill_if(self.groups.len() <= 1); - self.push(Par { - dirs, - aligns, - cross_expansion, - line_spacing, - children, - }); - } - } - /// Construct a text node from the given string based on the active text /// state. pub fn make_text_node(&self, text: String) -> Text { @@ -233,6 +251,25 @@ impl EvalContext { } } +/// A group for page runs. +struct PageGroup { + size: Size, + padding: Sides, + dirs: Gen, + aligns: Gen, + hard: bool, +} + +/// A group for generic content. +struct ContentGroup; + +/// A group for paragraphs. +struct ParGroup { + dirs: Gen, + aligns: Gen, + line_spacing: Length, +} + /// Evaluate an item. /// /// _Note_: Evaluation is not necessarily pure, it may change the active state. @@ -320,9 +357,16 @@ impl Eval for NodeRaw { families.list.insert(0, "monospace".to_string()); families.flatten(); + let em = ctx.state.font.font_size(); + let line_spacing = ctx.state.par.line_spacing.eval(em); + let mut children = vec![]; for line in &self.lines { children.push(LayoutNode::Text(ctx.make_text_node(line.clone()))); + children.push(LayoutNode::Spacing(Spacing { + amount: line_spacing, + softness: Softness::Hard, + })); } ctx.push(Stack { diff --git a/src/library/align.rs b/src/library/align.rs index d6b14692a..484756015 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -32,14 +32,14 @@ pub fn align(mut args: Args, ctx: &mut EvalContext) -> Value { .chain(hor.into_iter().map(|align| (Some(SpecAxis::Horizontal), align))) .chain(ver.into_iter().map(|align| (Some(SpecAxis::Vertical), align))); - let prev_main = ctx.state.aligns.main; - ctx.state.aligns = dedup_aligns(ctx, iter); - - if prev_main != ctx.state.aligns.main { + let aligns = dedup_aligns(ctx, iter); + if aligns.main != ctx.state.aligns.main { ctx.end_par_group(); ctx.start_par_group(); } + ctx.state.aligns = aligns; + if let Some(body) = body { body.eval(ctx); ctx.state = snapshot; diff --git a/src/library/boxed.rs b/src/library/boxed.rs index 0045b0bd4..24880998d 100644 --- a/src/library/boxed.rs +++ b/src/library/boxed.rs @@ -8,6 +8,8 @@ use crate::prelude::*; /// - `width`: The width of the box (length or relative to parent's width). /// - `height`: The height of the box (length or relative to parent's height). pub fn boxed(mut args: Args, ctx: &mut EvalContext) -> Value { + let snapshot = ctx.state.clone(); + let body = args.find::().unwrap_or_default(); let width = args.get::<_, Linear>(ctx, "width"); let height = args.get::<_, Linear>(ctx, "height"); @@ -16,13 +18,9 @@ pub fn boxed(mut args: Args, ctx: &mut EvalContext) -> Value { let dirs = ctx.state.dirs; let aligns = ctx.state.aligns; - let snapshot = ctx.state.clone(); - - ctx.start_group(()); - ctx.start_par_group(); + ctx.start_content_group(); body.eval(ctx); - ctx.end_par_group(); - let ((), children) = ctx.end_group(); + let children = ctx.end_content_group(); ctx.push(Fixed { width, @@ -40,6 +38,5 @@ pub fn boxed(mut args: Args, ctx: &mut EvalContext) -> Value { }); ctx.state = snapshot; - Value::None }