Refactoring

This commit is contained in:
Laurenz 2021-11-18 00:58:32 +01:00
parent d9c529347d
commit b2e6a29789
12 changed files with 104 additions and 76 deletions

View File

@ -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),
})
}
}

View File

@ -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)] }

View File

@ -23,11 +23,6 @@ impl<T> Spec<T> {
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<F, U>(self, mut f: F) -> Spec<U>
where

View File

@ -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<Linear>) -> 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 {

View File

@ -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);

View File

@ -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),
}

View File

@ -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, &regions).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, &regions).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, &regions).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::<Vec<_>>()
@ -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, &regions);
let frames = node.layout(ctx, &pod);
for (output, frame) in outputs.iter_mut().zip(frames) {
output.push_frame(pos, frame.item);
}

View File

@ -8,7 +8,6 @@ pub fn pad(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
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<Value> {
);
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<Linear>,
/// The child node whose sides to pad.
pub child: PackedNode,
/// The amount of padding.
pub padding: Sides<Linear>,
}
impl Layout for PadNode {
@ -37,43 +36,23 @@ impl Layout for PadNode {
regions: &Regions,
) -> Vec<Constrained<Rc<Frame>>> {
// Layout child into padded regions.
let mut frames = self.child.layout(
ctx,
&regions.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<Linear>) -> 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<Linear>) -> 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),
)
}

View File

@ -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;
}

View File

@ -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),
}

View File

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB