diff --git a/src/eval/template.rs b/src/eval/template.rs index 6c1223cb1..7801dc799 100644 --- a/src/eval/template.rs +++ b/src/eval/template.rs @@ -9,7 +9,7 @@ use crate::diag::StrResult; use crate::geom::{Align, Dir, GenAxis, Length, Linear, Sides, Size}; use crate::layout::{Layout, PackedNode}; use crate::library::{ - Decoration, DocumentNode, FlowChild, FlowNode, PadNode, PageNode, ParChild, ParNode, + Decoration, DocumentNode, FlowChild, FlowNode, PageNode, ParChild, ParNode, PlacedNode, Spacing, }; use crate::style::Style; @@ -400,7 +400,7 @@ impl PageBuilder { let Self { size, padding, hard } = self; (!child.children.is_empty() || (keep && hard)).then(|| PageNode { size, - child: PadNode { padding, child: child.pack() }.pack(), + child: child.pack().padded(padding), }) } } diff --git a/src/frame.rs b/src/frame.rs index 94234ae9c..9feb69595 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -64,6 +64,13 @@ impl Frame { } } + /// Move all elements in the frame by an offset. + pub fn translate(&mut self, offset: Point) { + for (point, _) in &mut self.elements { + *point += offset; + } + } + /// An iterator over all non-frame elements in this and nested frames. pub fn elements(&self) -> Elements { Elements { stack: vec![(0, Point::zero(), self)] } diff --git a/src/geom/spec.rs b/src/geom/spec.rs index 3b49b54a1..82bedf9e3 100644 --- a/src/geom/spec.rs +++ b/src/geom/spec.rs @@ -23,11 +23,6 @@ impl Spec { Self { x: v.clone(), y: v } } - /// Borrows the individual fields. - pub fn as_ref(&self) -> Spec<&T> { - Spec { x: &self.x, y: &self.y } - } - /// Maps the individual fields with `f`. pub fn map(self, mut f: F) -> Spec where diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 3ac327228..8f46c049a 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -17,9 +17,9 @@ use std::rc::Rc; use crate::font::FontStore; use crate::frame::Frame; -use crate::geom::{Align, Linear, Spec}; +use crate::geom::{Align, Linear, Sides, Spec}; use crate::image::ImageStore; -use crate::library::{AlignNode, DocumentNode, MoveNode, SizedNode}; +use crate::library::{AlignNode, DocumentNode, MoveNode, PadNode, SizedNode}; use crate::Context; /// Layout a document node into a collection of frames. @@ -129,6 +129,19 @@ impl PackedNode { self } } + + /// Pad this node at the sides. + pub fn padded(self, padding: Sides) -> Self { + if !padding.left.is_zero() + || !padding.top.is_zero() + || !padding.right.is_zero() + || !padding.bottom.is_zero() + { + PadNode { child: self, padding }.pack() + } else { + self + } + } } impl Layout for PackedNode { diff --git a/src/library/align.rs b/src/library/align.rs index 19c52f987..5aeef5430 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -49,7 +49,6 @@ impl Layout for AlignNode { pod.expand.y &= self.aligns.y.is_none(); let mut frames = self.child.layout(ctx, &pod); - for (Constrained { item: frame, cts }, (current, _)) in frames.iter_mut().zip(regions.iter()) { @@ -67,10 +66,7 @@ impl Layout for AlignNode { let frame = Rc::make_mut(frame); frame.size = canvas; frame.baseline += offset.y; - - for (point, _) in &mut frame.elements { - *point += offset; - } + frame.translate(offset); cts.expand = regions.expand; cts.exact = current.to_spec().map(Some); diff --git a/src/library/flow.rs b/src/library/flow.rs index 185e60bb0..5271eca93 100644 --- a/src/library/flow.rs +++ b/src/library/flow.rs @@ -61,7 +61,7 @@ impl Layout for FlowNode { pub enum FlowChild { /// Vertical spacing between other children. Spacing(Spacing), - /// A node and how to align it in the flow. + /// An arbitrary node. Node(PackedNode), } diff --git a/src/library/grid.rs b/src/library/grid.rs index 347132e1a..6bd723888 100644 --- a/src/library/grid.rs +++ b/src/library/grid.rs @@ -288,17 +288,17 @@ impl<'a> GridLayouter<'a> { for y in 0 .. self.rows.len() { if let Some(node) = self.cell(x, y) { let size = Size::new(available, self.regions.base.h); - let mut regions = + let mut pod = Regions::one(size, self.regions.base, Spec::splat(false)); // For linear rows, we can already resolve the correct // base, for auto it's already correct and for fr we could // only guess anyway. if let TrackSizing::Linear(v) = self.rows[y] { - regions.base.h = v.resolve(self.regions.base.h); + pod.base.h = v.resolve(self.regions.base.h); } - let frame = node.layout(ctx, ®ions).remove(0).item; + let frame = node.layout(ctx, &pod).remove(0).item; resolved.set_max(frame.size.w); } } @@ -376,17 +376,17 @@ impl<'a> GridLayouter<'a> { // Determine the size for each region of the row. for (x, &rcol) in self.rcols.iter().enumerate() { if let Some(node) = self.cell(x, y) { - let mut regions = self.regions.clone(); - regions.mutate(|size| size.w = rcol); + let mut pod = self.regions.clone(); + pod.mutate(|size| size.w = rcol); // Set the horizontal base back to the parent region's base for // auto columns. if self.cols[x] == TrackSizing::Auto { - regions.base.w = self.regions.base.w; + pod.base.w = self.regions.base.w; } let mut sizes = - node.layout(ctx, ®ions).into_iter().map(|frame| frame.item.size.h); + node.layout(ctx, &pod).into_iter().map(|frame| frame.item.size.h); for (target, size) in resolved.iter_mut().zip(&mut sizes) { target.set_max(size); @@ -475,8 +475,8 @@ impl<'a> GridLayouter<'a> { base.h = size.h; } - let regions = Regions::one(size, base, Spec::splat(true)); - let frame = node.layout(ctx, ®ions).remove(0); + let pod = Regions::one(size, base, Spec::splat(true)); + let frame = node.layout(ctx, &pod).remove(0); output.push_frame(pos, frame.item); } @@ -501,8 +501,8 @@ impl<'a> GridLayouter<'a> { // Prepare regions. let size = Size::new(self.used.w, resolved[0]); - let mut regions = Regions::one(size, self.regions.base, Spec::splat(true)); - regions.backlog = resolved[1 ..] + let mut pod = Regions::one(size, self.regions.base, Spec::splat(true)); + pod.backlog = resolved[1 ..] .iter() .map(|&h| Size::new(self.used.w, h)) .collect::>() @@ -512,16 +512,16 @@ impl<'a> GridLayouter<'a> { let mut pos = Point::zero(); for (x, &rcol) in self.rcols.iter().enumerate() { if let Some(node) = self.cell(x, y) { - regions.mutate(|size| size.w = rcol); + pod.mutate(|size| size.w = rcol); // Set the horizontal base back to the parent region's base for // auto columns. if self.cols[x] == TrackSizing::Auto { - regions.base.w = self.regions.base.w; + pod.base.w = self.regions.base.w; } // Push the layouted frames into the individual output frames. - let frames = node.layout(ctx, ®ions); + let frames = node.layout(ctx, &pod); for (output, frame) in outputs.iter_mut().zip(frames) { output.push_frame(pos, frame.item); } diff --git a/src/library/pad.rs b/src/library/pad.rs index a1c8c6f90..829272cca 100644 --- a/src/library/pad.rs +++ b/src/library/pad.rs @@ -8,7 +8,6 @@ pub fn pad(_: &mut EvalContext, args: &mut Args) -> TypResult { let right = args.named("right")?; let bottom = args.named("bottom")?; let body: Template = args.expect("body")?; - let padding = Sides::new( left.or(all).unwrap_or_default(), top.or(all).unwrap_or_default(), @@ -17,17 +16,17 @@ pub fn pad(_: &mut EvalContext, args: &mut Args) -> TypResult { ); Ok(Value::Template(Template::from_inline(move |style| { - PadNode { padding, child: body.pack(style) } + body.pack(style).padded(padding) }))) } /// A node that adds padding to its child. #[derive(Debug, Hash)] pub struct PadNode { - /// The amount of padding. - pub padding: Sides, /// The child node whose sides to pad. pub child: PackedNode, + /// The amount of padding. + pub padding: Sides, } impl Layout for PadNode { @@ -37,43 +36,23 @@ impl Layout for PadNode { regions: &Regions, ) -> Vec>> { // Layout child into padded regions. - let mut frames = self.child.layout( - ctx, - ®ions.map(|size| size - self.padding.resolve(size).size()), - ); + let pod = regions.map(|size| shrink(size, self.padding)); + let mut frames = self.child.layout(ctx, &pod); for (Constrained { item: frame, cts }, (current, base)) in frames.iter_mut().zip(regions.iter()) { - fn solve_axis(length: Length, padding: Linear) -> Length { - (length + padding.abs).safe_div(1.0 - padding.rel.get()) - } - - // Solve for the size `padded` that satisfies (approximately): - // `padded - padding.resolve(padded).size() == size` - let padded = Size::new( - solve_axis(frame.size.w, self.padding.left + self.padding.right), - solve_axis(frame.size.h, self.padding.top + self.padding.bottom), - ); - + // Apply the padding inversely such that the grown size padded + // yields the frame's size. + let padded = grow(frame.size, self.padding); let padding = self.padding.resolve(padded); - let origin = Point::new(padding.left, padding.top); + let offset = Point::new(padding.left, padding.top); - // Create a new larger frame and place the child's frame inside it. - let empty = Frame::new(padded, frame.baseline + origin.y); - let prev = std::mem::replace(frame, Rc::new(empty)); - let new = Rc::make_mut(frame); - new.push_frame(origin, prev); - - // Inflate min and max contraints by the padding. - for spec in [&mut cts.min, &mut cts.max] { - if let Some(x) = spec.x.as_mut() { - *x += padding.size().w; - } - if let Some(y) = spec.y.as_mut() { - *y += padding.size().h; - } - } + // Grow the frame and translate everything in the frame inwards. + let frame = Rc::make_mut(frame); + frame.size = padded; + frame.baseline += offset.y; + frame.translate(offset); // Set exact and base constraints if the child had them. cts.exact.x.and_set(Some(current.w)); @@ -89,8 +68,53 @@ impl Layout for PadNode { if self.padding.top.is_relative() || self.padding.bottom.is_relative() { cts.base.y = Some(base.h); } + + // Inflate min and max contraints by the padding. + for spec in [&mut cts.min, &mut cts.max] { + if let Some(x) = spec.x.as_mut() { + *x += padding.size().w; + } + if let Some(y) = spec.y.as_mut() { + *y += padding.size().h; + } + } } frames } } + +/// Shrink a size by padding relative to the size itself. +fn shrink(size: Size, padding: Sides) -> Size { + size - padding.resolve(size).size() +} + +/// Grow a size by padding relative to the grown size. +/// This is the inverse operation to `shrink()`. +/// +/// For the horizontal axis the derivation looks as follows. +/// (Vertical axis is analogous.) +/// +/// Let w be the grown target width, +/// s be given width, +/// l be the left padding, +/// r be the right padding, +/// p = l + r. +/// +/// We want that: w - l.resolve(w) - r.resolve(w) = s +/// +/// Thus: w - l.resolve(w) - r.resolve(w) = s +/// <=> w - p.resolve(w) = s +/// <=> w - p.rel * w - p.abs = s +/// <=> (1 - p.rel) * w = s + p.abs +/// <=> w = (s + p.abs) / (1 - p.rel) +fn grow(size: Size, padding: Sides) -> Size { + fn solve_axis(length: Length, padding: Linear) -> Length { + (length + padding.abs).safe_div(1.0 - padding.rel.get()) + } + + Size::new( + solve_axis(size.w, padding.left + padding.right), + solve_axis(size.h, padding.top + padding.bottom), + ) +} diff --git a/src/library/shape.rs b/src/library/shape.rs index 5d9b41526..7c543958b 100644 --- a/src/library/shape.rs +++ b/src/library/shape.rs @@ -1,7 +1,6 @@ use std::f64::consts::SQRT_2; use super::prelude::*; -use super::PadNode; use crate::util::RcExt; /// `rect`: A rectangle with optional content. @@ -113,10 +112,8 @@ impl Layout for ShapeNode { if matches!(self.kind, ShapeKind::Circle | ShapeKind::Ellipse) { // Padding with this ratio ensures that a rectangular child fits // perfectly into a circle / an ellipse. - storage = PadNode { - padding: Sides::splat(Relative::new(0.5 - SQRT_2 / 4.0).into()), - child: child.clone(), - }; + let ratio = Relative::new(0.5 - SQRT_2 / 4.0); + storage = child.clone().padded(Sides::splat(ratio.into())); node = &storage; } diff --git a/src/library/stack.rs b/src/library/stack.rs index cc02592f4..3d91bec71 100644 --- a/src/library/stack.rs +++ b/src/library/stack.rs @@ -75,7 +75,7 @@ impl Layout for StackNode { pub enum StackChild { /// Spacing between other nodes. Spacing(Spacing), - /// Any block node and how to align it in the stack. + /// An arbitrary node. Node(PackedNode), } diff --git a/src/library/transform.rs b/src/library/transform.rs index ef9caf2e8..107523584 100644 --- a/src/library/transform.rs +++ b/src/library/transform.rs @@ -30,14 +30,10 @@ impl Layout for MoveNode { for (Constrained { item: frame, .. }, (_, base)) in frames.iter_mut().zip(regions.iter()) { - let offset = Point::new( + Rc::make_mut(frame).translate(Point::new( self.offset.x.map(|x| x.resolve(base.w)).unwrap_or_default(), self.offset.y.map(|y| y.resolve(base.h)).unwrap_or_default(), - ); - - for (point, _) in &mut Rc::make_mut(frame).elements { - *point += offset; - } + )); } frames diff --git a/tests/ref/coma.png b/tests/ref/coma.png index 04356991b..d4c6c3def 100644 Binary files a/tests/ref/coma.png and b/tests/ref/coma.png differ