summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2020-10-12 22:06:28 +0200
committerLaurenz <laurmaedje@gmail.com>2020-10-12 22:06:28 +0200
commitf29207d999b9aa4fe4637556a507eb252246ecf8 (patch)
tree02d934b30f20e05406edf04ccb221526eb0a7cf9
parentdd4a4545a6b72e48cde5d2483fac5e4e76f6047f (diff)
Strongly typed groups 👔
-rw-r--r--src/eval/mod.rs154
-rw-r--r--src/library/align.rs8
-rw-r--r--src/library/boxed.rs11
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
}