diff options
| author | Laurenz <laurmaedje@gmail.com> | 2020-10-12 22:06:28 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2020-10-12 22:06:28 +0200 |
| commit | f29207d999b9aa4fe4637556a507eb252246ecf8 (patch) | |
| tree | 02d934b30f20e05406edf04ccb221526eb0a7cf9 | |
| parent | dd4a4545a6b72e48cde5d2483fac5e4e76f6047f (diff) | |
Strongly typed groups 👔
| -rw-r--r-- | src/eval/mod.rs | 154 | ||||
| -rw-r--r-- | src/library/align.rs | 8 | ||||
| -rw-r--r-- | src/library/boxed.rs | 11 |
3 files changed, 107 insertions, 66 deletions
diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 62ce6c20..71a09749 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,64 +113,36 @@ impl EvalContext { self.inner.push(node); } - /// Start a layouting group. - /// - /// All further calls to [`push`] will collect nodes for this group. - /// The given metadata will be returned alongside the collected nodes - /// in a matching call to [`end_group`]. - /// - /// [`push`]: #method.push - /// [`end_group`]: #method.end_group - pub fn start_group<T: 'static>(&mut self, meta: T) { - self.groups.push((Box::new(meta), std::mem::take(&mut self.inner))); - } - - /// End a layouting group started with [`start_group`]. - /// - /// This returns the stored metadata and the collected nodes. - /// - /// [`start_group`]: #method.start_group - pub fn end_group<T: 'static>(&mut self) -> (T, Vec<LayoutNode>) { - if let Some(&LayoutNode::Spacing(spacing)) = self.inner.last() { - if spacing.softness == Softness::Soft { - self.inner.pop(); - } - } - - let (any, outer) = self.groups.pop().expect("no pushed group"); - let group = *any.downcast::<T>().expect("bad group type"); - (group, std::mem::replace(&mut self.inner, outer)) - } - - /// Start a page run group based on the active page state. + /// 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) { - 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_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 run group and push it to its parent 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 ((size, padding, dirs, aligns, hard), children) = self.end_group(); - let hard: bool = hard; - if hard || !children.is_empty() { + let (group, children) = self.end_group::<PageGroup>(); + if group.hard || !children.is_empty() { self.runs.push(Pages { - size, + size: group.size, child: LayoutNode::dynamic(Pad { - padding, + padding: group.padding, child: LayoutNode::dynamic(Stack { - dirs, - aligns, + dirs: group.dirs, + aligns: group.aligns, expansion: Gen::new(Expansion::Fill, Expansion::Fill), children, }), @@ -179,32 +151,78 @@ impl EvalContext { } } + /// 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<LayoutNode> { + self.end_par_group(); + self.end_group::<ContentGroup>().1 + } + /// 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)); + 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 its not empty. + /// End a paragraph group and push it to its parent group if it's not empty. pub fn end_par_group(&mut self) { - let ((dirs, line_spacing, aligns), children) = self.end_group(); + let (group, children) = self.end_group::<ParGroup>(); if !children.is_empty() { - // FIXME: This is a hack and should be superseded by constraints - // having min and max size. + // 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, - aligns, + dirs: group.dirs, + aligns: group.aligns, cross_expansion, - line_spacing, + line_spacing: group.line_spacing, children, }); } } + /// Start a layouting group. + /// + /// All further calls to [`push`] will collect nodes for this group. + /// The given metadata will be returned alongside the collected nodes + /// in a matching call to [`end_group`]. + /// + /// [`push`]: #method.push + /// [`end_group`]: #method.end_group + fn start_group<T: 'static>(&mut self, meta: T) { + self.groups.push((Box::new(meta), std::mem::take(&mut self.inner))); + } + + /// End a layouting group started with [`start_group`]. + /// + /// This returns the stored metadata and the collected nodes. + /// + /// [`start_group`]: #method.start_group + fn end_group<T: 'static>(&mut self) -> (T, Vec<LayoutNode>) { + if let Some(&LayoutNode::Spacing(spacing)) = self.inner.last() { + if spacing.softness == Softness::Soft { + self.inner.pop(); + } + } + + let (any, outer) = self.groups.pop().expect("no pushed group"); + let group = *any.downcast::<T>().expect("bad group type"); + (group, std::mem::replace(&mut self.inner, outer)) + } + /// 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<Linear>, + dirs: Gen<Dir>, + aligns: Gen<Align>, + hard: bool, +} + +/// A group for generic content. +struct ContentGroup; + +/// A group for paragraphs. +struct ParGroup { + dirs: Gen<Dir>, + aligns: Gen<Align>, + 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 d6b14692..48475601 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 0045b0bd..24880998 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::<SynTree>().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 } |
