diff --git a/src/eval/function.rs b/src/eval/function.rs index c83d8b2b2..931a90a00 100644 --- a/src/eval/function.rs +++ b/src/eval/function.rs @@ -226,7 +226,3 @@ impl Debug for Arg { Debug::fmt(&self.value.v, f) } } - -dynamic! { - Args: "arguments", -} diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 6b28e5abe..a0c31e983 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -494,6 +494,10 @@ impl Eval for ClosureExpr { // Put the remaining arguments into the sink. if let Some(sink) = &sink { + dynamic! { + Args: "arguments", + } + ctx.scopes.def_mut(sink, args.take()); } diff --git a/src/eval/ops.rs b/src/eval/ops.rs index 6fac354d3..ede1230fa 100644 --- a/src/eval/ops.rs +++ b/src/eval/ops.rs @@ -3,7 +3,7 @@ use std::convert::TryFrom; use super::{Dynamic, Value}; use crate::diag::StrResult; -use crate::geom::{Align, Get, Spec}; +use crate::geom::{Align, Spec, SpecAxis}; use crate::util::EcoString; use Value::*; @@ -94,14 +94,18 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult { if let (Some(&a), Some(&b)) = (a.downcast::(), b.downcast::()) { - if a.axis() == b.axis() { - return Err(format!("cannot add two {:?} alignments", a.axis())); + dynamic! { + Spec: "2d alignment", } - let mut aligns = Spec::default(); - aligns.set(a.axis(), Some(a)); - aligns.set(b.axis(), Some(b)); - return Ok(Dyn(Dynamic::new(aligns))); + return if a.axis() != b.axis() { + Ok(Dyn(Dynamic::new(match a.axis() { + SpecAxis::Horizontal => Spec { x: a, y: b }, + SpecAxis::Vertical => Spec { x: b, y: a }, + }))) + } else { + Err(format!("cannot add two {:?} alignments", a.axis())) + }; } } diff --git a/src/export/pdf.rs b/src/export/pdf.rs index b9312c0a3..94386c995 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -266,8 +266,8 @@ impl<'a> PdfExporter<'a> { let mut page_writer = self.writer.page(page_id); page_writer.parent(page_tree_ref); - let w = page.size.w.to_f32(); - let h = page.size.h.to_f32(); + let w = page.size.x.to_f32(); + let h = page.size.y.to_f32(); page_writer.media_box(Rect::new(0.0, 0.0, w, h)); page_writer.contents(content_id); @@ -366,7 +366,7 @@ impl<'a> PageExporter<'a> { fn export(mut self, frame: &Frame) -> Page { // Make the coordinate system start at the top-left. - self.bottom = frame.size.h.to_f32(); + self.bottom = frame.size.y.to_f32(); self.content.transform([1.0, 0.0, 0.0, -1.0, 0.0, self.bottom]); self.write_frame(&frame); Page { @@ -397,8 +397,8 @@ impl<'a> PageExporter<'a> { self.transform(translation.pre_concat(group.transform)); if group.clips { - let w = group.frame.size.w.to_f32(); - let h = group.frame.size.h.to_f32(); + let w = group.frame.size.x.to_f32(); + let h = group.frame.size.y.to_f32(); self.content.move_to(0.0, 0.0); self.content.line_to(w, 0.0); self.content.line_to(w, h); @@ -471,8 +471,8 @@ impl<'a> PageExporter<'a> { match shape.geometry { Geometry::Rect(size) => { - let w = size.w.to_f32(); - let h = size.h.to_f32(); + let w = size.x.to_f32(); + let h = size.y.to_f32(); if w > 0.0 && h > 0.0 { self.content.rect(x, y, w, h); } @@ -533,8 +533,8 @@ impl<'a> PageExporter<'a> { fn write_image(&mut self, x: f32, y: f32, id: ImageId, size: Size) { self.image_map.insert(id); let name = format_eco!("Im{}", self.image_map.map(id)); - let w = size.w.to_f32(); - let h = size.h.to_f32(); + let w = size.x.to_f32(); + let h = size.y.to_f32(); self.content.save_state(); self.content.transform([w, 0.0, 0.0, -h, x, y + h]); self.content.x_object(Name(name.as_bytes())); @@ -550,8 +550,8 @@ impl<'a> PageExporter<'a> { // Compute the bounding box of the transformed link. for point in [ pos, - pos + Point::with_x(size.w), - pos + Point::with_y(size.h), + pos + Point::with_x(size.x), + pos + Point::with_y(size.y), pos + size.to_point(), ] { let t = point.transform(self.state.transform); diff --git a/src/frame.rs b/src/frame.rs index ec10fe964..4eea0578f 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -25,7 +25,7 @@ impl Frame { #[track_caller] pub fn new(size: Size) -> Self { assert!(size.is_finite()); - Self { size, baseline: size.h, elements: vec![] } + Self { size, baseline: size.y, elements: vec![] } } /// Add an element at a position in the background. @@ -58,13 +58,15 @@ impl Frame { /// Resize the frame to a new size, distributing new space according to the /// given alignments. pub fn resize(&mut self, new: Size, aligns: Spec) { - let offset = Point::new( - aligns.x.resolve(new.w - self.size.w), - aligns.y.resolve(new.h - self.size.h), - ); - self.size = new; - self.baseline += offset.y; - self.translate(offset); + if self.size != new { + let offset = Point::new( + aligns.x.resolve(new.x - self.size.x), + aligns.y.resolve(new.y - self.size.y), + ); + self.size = new; + self.baseline += offset.y; + self.translate(offset); + } } /// Move the contents of the frame by an offset. diff --git a/src/geom/gen.rs b/src/geom/gen.rs index 5232139b1..fc4c56e75 100644 --- a/src/geom/gen.rs +++ b/src/geom/gen.rs @@ -53,21 +53,6 @@ impl Gen { pub fn to_point(self, main: SpecAxis) -> Point { self.to_spec(main).to_point() } - - /// Convert to a size. - pub fn to_size(self, main: SpecAxis) -> Size { - self.to_spec(main).to_size() - } -} - -impl Gen> { - /// Unwrap the individual fields. - pub fn unwrap_or(self, other: Gen) -> Gen { - Gen { - cross: self.cross.unwrap_or(other.cross), - main: self.main.unwrap_or(other.main), - } - } } impl Get for Gen { diff --git a/src/geom/linear.rs b/src/geom/linear.rs index 4a0def2fe..77923c431 100644 --- a/src/geom/linear.rs +++ b/src/geom/linear.rs @@ -42,7 +42,7 @@ impl Linear { } /// Whether there is a linear component. - pub fn is_relative(&self) -> bool { + pub fn is_relative(self) -> bool { !self.rel.is_zero() } } diff --git a/src/geom/mod.rs b/src/geom/mod.rs index b0c75d25c..2f722f162 100644 --- a/src/geom/mod.rs +++ b/src/geom/mod.rs @@ -16,7 +16,6 @@ mod point; mod relative; mod scalar; mod sides; -mod size; mod spec; mod transform; @@ -34,7 +33,6 @@ pub use point::*; pub use relative::*; pub use scalar::*; pub use sides::*; -pub use size::*; pub use spec::*; pub use transform::*; diff --git a/src/geom/path.rs b/src/geom/path.rs index 20519cb3a..87e20dd12 100644 --- a/src/geom/path.rs +++ b/src/geom/path.rs @@ -26,9 +26,9 @@ impl Path { let point = Point::new; let mut path = Self::new(); path.move_to(point(z, z)); - path.line_to(point(size.w, z)); - path.line_to(point(size.w, size.h)); - path.line_to(point(z, size.h)); + path.line_to(point(size.x, z)); + path.line_to(point(size.x, size.y)); + path.line_to(point(z, size.y)); path.close_path(); path } @@ -37,8 +37,8 @@ impl Path { pub fn ellipse(size: Size) -> Self { // https://stackoverflow.com/a/2007782 let z = Length::zero(); - let rx = size.w / 2.0; - let ry = size.h / 2.0; + let rx = size.x / 2.0; + let ry = size.y / 2.0; let m = 0.551784; let mx = m * rx; let my = m * ry; diff --git a/src/geom/scalar.rs b/src/geom/scalar.rs index 6536e23ac..066246a9c 100644 --- a/src/geom/scalar.rs +++ b/src/geom/scalar.rs @@ -2,7 +2,7 @@ use super::*; /// A 64-bit float that implements `Eq`, `Ord` and `Hash`. /// -/// Panics if its `NaN` during any of those operations. +/// Panics if it's `NaN` during any of those operations. #[derive(Default, Copy, Clone, Serialize, Deserialize)] #[serde(transparent)] pub struct Scalar(pub f64); diff --git a/src/geom/sides.rs b/src/geom/sides.rs index 83407ad66..c0929fc71 100644 --- a/src/geom/sides.rs +++ b/src/geom/sides.rs @@ -33,11 +33,13 @@ impl Sides { } } -impl Sides { - /// A size with `left` and `right` summed into `width`, and `top` and - /// `bottom` summed into `height`. - pub fn size(self) -> Size { - Size::new(self.left + self.right, self.top + self.bottom) +impl Sides +where + T: Add, +{ + /// Sums up `left` and `right` into `x`, and `top` and `bottom` into `y`. + pub fn sum_by_axis(self) -> Spec { + Spec::new(self.left + self.right, self.top + self.bottom) } } @@ -45,10 +47,10 @@ impl Sides { /// Resolve the linear sides relative to the given `size`. pub fn resolve(self, size: Size) -> Sides { Sides { - left: self.left.resolve(size.w), - top: self.top.resolve(size.h), - right: self.right.resolve(size.w), - bottom: self.bottom.resolve(size.h), + left: self.left.resolve(size.x), + top: self.top.resolve(size.y), + right: self.right.resolve(size.x), + bottom: self.bottom.resolve(size.y), } } } diff --git a/src/geom/size.rs b/src/geom/size.rs deleted file mode 100644 index 1c0494255..000000000 --- a/src/geom/size.rs +++ /dev/null @@ -1,131 +0,0 @@ -use super::*; - -/// A size in 2D. -#[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] -pub struct Size { - /// The width. - pub w: Length, - /// The height. - pub h: Length, -} - -impl Size { - /// The zero size. - pub const fn zero() -> Self { - Self { w: Length::zero(), h: Length::zero() } - } - - /// Create a new size from width and height. - pub const fn new(w: Length, h: Length) -> Self { - Self { w, h } - } - - /// Create an instance with two equal components. - pub const fn splat(v: Length) -> Self { - Self { w: v, h: v } - } - - /// Whether the other size fits into this one (smaller width and height). - pub fn fits(self, other: Self) -> bool { - self.w.fits(other.w) && self.h.fits(other.h) - } - - /// Whether both components are finite. - pub fn is_finite(self) -> bool { - self.w.is_finite() && self.h.is_finite() - } - - /// Whether any of the two components is infinite. - pub fn is_infinite(self) -> bool { - self.w.is_infinite() || self.h.is_infinite() - } - - /// Convert to a point. - pub const fn to_point(self) -> Point { - Point::new(self.w, self.h) - } - - /// Convert to a Spec. - pub const fn to_spec(self) -> Spec { - Spec::new(self.w, self.h) - } - - /// Convert to the generic representation. - pub const fn to_gen(self, main: SpecAxis) -> Gen { - match main { - SpecAxis::Horizontal => Gen::new(self.h, self.w), - SpecAxis::Vertical => Gen::new(self.w, self.h), - } - } -} - -impl Get for Size { - type Component = Length; - - fn get(self, axis: SpecAxis) -> Length { - match axis { - SpecAxis::Horizontal => self.w, - SpecAxis::Vertical => self.h, - } - } - - fn get_mut(&mut self, axis: SpecAxis) -> &mut Length { - match axis { - SpecAxis::Horizontal => &mut self.w, - SpecAxis::Vertical => &mut self.h, - } - } -} - -impl Debug for Size { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "Size({:?}, {:?})", self.w, self.h) - } -} - -impl Neg for Size { - type Output = Self; - - fn neg(self) -> Self { - Self { w: -self.w, h: -self.h } - } -} - -impl Add for Size { - type Output = Self; - - fn add(self, other: Self) -> Self { - Self { w: self.w + other.w, h: self.h + other.h } - } -} - -sub_impl!(Size - Size -> Size); - -impl Mul for Size { - type Output = Self; - - fn mul(self, other: f64) -> Self { - Self { w: self.w * other, h: self.h * other } - } -} - -impl Mul for f64 { - type Output = Size; - - fn mul(self, other: Size) -> Size { - other * self - } -} - -impl Div for Size { - type Output = Self; - - fn div(self, other: f64) -> Self { - Self { w: self.w / other, h: self.h / other } - } -} - -assign_impl!(Size -= Size); -assign_impl!(Size += Size); -assign_impl!(Size *= f64); -assign_impl!(Size /= f64); diff --git a/src/geom/spec.rs b/src/geom/spec.rs index 608643d82..640d6231e 100644 --- a/src/geom/spec.rs +++ b/src/geom/spec.rs @@ -1,7 +1,10 @@ +use std::any::Any; +use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not}; + use super::*; /// A container with a horizontal and vertical component. -#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] pub struct Spec { /// The horizontal component. pub x: T, @@ -31,9 +34,18 @@ impl Spec { Spec { x: f(self.x), y: f(self.y) } } + /// Convert from `&Spec` to `Spec<&T>`. + pub fn as_ref(&self) -> Spec<&T> { + Spec { x: &self.x, y: &self.y } + } + + /// Convert from `&mut Spec` to `Spec<&mut T>`. + pub fn as_mut(&mut self) -> Spec<&mut T> { + Spec { x: &mut self.x, y: &mut self.y } + } + /// Zip two instances into an instance over a tuple. - pub fn zip(self, other: impl Into>) -> Spec<(T, U)> { - let other = other.into(); + pub fn zip(self, other: Spec) -> Spec<(T, U)> { Spec { x: (self.x, other.x), y: (self.y, other.y), @@ -56,6 +68,14 @@ impl Spec { f(&self.x) && f(&self.y) } + /// Filter the individual fields with a mask. + pub fn filter(self, mask: Spec) -> Spec> { + Spec { + x: if mask.x { Some(self.x) } else { None }, + y: if mask.y { Some(self.y) } else { None }, + } + } + /// Convert to the generic representation. pub fn to_gen(self, main: SpecAxis) -> Gen { match main { @@ -65,39 +85,6 @@ impl Spec { } } -impl From for Spec { - fn from(size: Size) -> Self { - size.to_spec() - } -} - -impl Spec { - /// The zero value. - pub fn zero() -> Self { - Self { x: Length::zero(), y: Length::zero() } - } - - /// Convert to a point. - pub fn to_point(self) -> Point { - Point::new(self.x, self.y) - } - - /// Convert to a size. - pub fn to_size(self) -> Size { - Size::new(self.x, self.y) - } -} - -impl Spec> { - /// Unwrap the individual fields. - pub fn unwrap_or(self, other: Spec) -> Spec { - Spec { - x: self.x.unwrap_or(other.x), - y: self.y.unwrap_or(other.y), - } - } -} - impl Get for Spec { type Component = T; @@ -116,9 +103,20 @@ impl Get for Spec { } } -impl Debug for Spec { +impl Debug for Spec +where + T: Debug + 'static, +{ fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "Spec({:?}, {:?})", self.x, self.y) + if let Spec { x: Some(x), y: Some(y) } = + self.as_ref().map(|v| (v as &dyn Any).downcast_ref::()) + { + write!(f, "{:?}-{:?}", x, y) + } else if (&self.x as &dyn Any).is::() { + write!(f, "Size({:?}, {:?})", self.x, self.y) + } else { + write!(f, "Spec({:?}, {:?})", self.x, self.y) + } } } @@ -159,3 +157,148 @@ impl Debug for SpecAxis { }) } } + +/// A size in 2D. +pub type Size = Spec; + +impl Size { + /// The zero value. + pub fn zero() -> Self { + Self { x: Length::zero(), y: Length::zero() } + } + + /// Whether the other size fits into this one (smaller width and height). + pub fn fits(self, other: Self) -> bool { + self.x.fits(other.x) && self.y.fits(other.y) + } + + /// Whether both components are finite. + pub fn is_finite(self) -> bool { + self.x.is_finite() && self.y.is_finite() + } + + /// Whether any of the two components is infinite. + pub fn is_infinite(self) -> bool { + self.x.is_infinite() || self.y.is_infinite() + } + + /// Convert to a point. + pub fn to_point(self) -> Point { + Point::new(self.x, self.y) + } +} + +impl Neg for Size { + type Output = Self; + + fn neg(self) -> Self { + Self { x: -self.x, y: -self.y } + } +} + +impl Add for Size { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self { x: self.x + other.x, y: self.y + other.y } + } +} + +sub_impl!(Size - Size -> Size); + +impl Mul for Size { + type Output = Self; + + fn mul(self, other: f64) -> Self { + Self { x: self.x * other, y: self.y * other } + } +} + +impl Mul for f64 { + type Output = Size; + + fn mul(self, other: Size) -> Size { + other * self + } +} + +impl Div for Size { + type Output = Self; + + fn div(self, other: f64) -> Self { + Self { x: self.x / other, y: self.y / other } + } +} + +assign_impl!(Size -= Size); +assign_impl!(Size += Size); +assign_impl!(Size *= f64); +assign_impl!(Size /= f64); + +impl Spec> { + /// Whether the individual fields are some. + pub fn map_is_some(&self) -> Spec { + self.as_ref().map(Option::is_some) + } + + /// Whether the individual fields are none. + pub fn map_is_none(&self) -> Spec { + self.as_ref().map(Option::is_none) + } + + /// Unwrap the individual fields. + pub fn unwrap_or(self, other: Spec) -> Spec { + Spec { + x: self.x.unwrap_or(other.x), + y: self.y.unwrap_or(other.y), + } + } +} + +impl Spec { + /// Select `t.x` if `self.x` is true and `f.x` otherwise and same for `y`. + pub fn select(self, t: Spec, f: Spec) -> Spec { + Spec { + x: if self.x { t.x } else { f.x }, + y: if self.y { t.y } else { f.y }, + } + } +} + +impl Not for Spec { + type Output = Self; + + fn not(self) -> Self::Output { + Self { x: !self.x, y: !self.y } + } +} + +impl BitOr for Spec { + type Output = Self; + + fn bitor(self, rhs: Self) -> Self::Output { + Self { x: self.x | rhs.x, y: self.y | rhs.y } + } +} + +impl BitAnd for Spec { + type Output = Self; + + fn bitand(self, rhs: Self) -> Self::Output { + Self { x: self.x & rhs.x, y: self.y & rhs.y } + } +} + +impl BitOrAssign for Spec { + fn bitor_assign(&mut self, rhs: Self) { + self.x |= rhs.x; + self.y |= rhs.y; + } +} + +impl BitAndAssign for Spec { + fn bitand_assign(&mut self, rhs: Self) { + self.x &= rhs.x; + self.y &= rhs.y; + } +} diff --git a/src/layout/constraints.rs b/src/layout/constraints.rs index b72254d7b..0d772cead 100644 --- a/src/layout/constraints.rs +++ b/src/layout/constraints.rs @@ -2,7 +2,7 @@ use std::rc::Rc; use super::Regions; use crate::frame::Frame; -use crate::geom::{Length, Linear, Size, Spec}; +use crate::geom::{Length, Size, Spec}; /// Constrain a frame with constraints. pub trait Constrain { @@ -65,8 +65,8 @@ impl Constraints { Self { min: Spec::default(), max: Spec::default(), - exact: regions.current.to_spec().map(Some), - base: regions.base.to_spec().map(Some), + exact: regions.current.map(Some), + base: regions.base.map(Some), expand: regions.expand, } } @@ -80,18 +80,6 @@ impl Constraints { && verify(self.exact, current, Length::approx_eq) && verify(self.base, base, Length::approx_eq) } - - /// Set the appropriate base constraints for linear width and height sizing. - pub fn set_base_if_linear(&mut self, base: Size, sizing: Spec>) { - // The full sizes need to be equal if there is a relative component in - // the sizes. - if sizing.x.map_or(false, |l| l.is_relative()) { - self.base.x = Some(base.w); - } - if sizing.y.map_or(false, |l| l.is_relative()) { - self.base.y = Some(base.h); - } - } } /// Verify a single constraint. diff --git a/src/layout/regions.rs b/src/layout/regions.rs index 0ef925435..66e6da175 100644 --- a/src/layout/regions.rs +++ b/src/layout/regions.rs @@ -51,7 +51,7 @@ impl Regions { /// Whether the current region is full and a region break is called for. pub fn is_full(&self) -> bool { - Length::zero().fits(self.current.h) && !self.in_last() + Length::zero().fits(self.current.y) && !self.in_last() } /// Whether `current` is the last usable region. diff --git a/src/library/align.rs b/src/library/align.rs index 6f079b7bb..7ad5a2d4b 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -29,10 +29,9 @@ impl Layout for AlignNode { ctx: &mut LayoutContext, regions: &Regions, ) -> Vec>> { - // Along axes with specified alignment, the child doesn't need to expand. + // The child only needs to expand along an axis if there's no alignment. let mut pod = regions.clone(); - pod.expand.x &= self.aligns.x.is_none(); - pod.expand.y &= self.aligns.y.is_none(); + pod.expand &= self.aligns.map_is_none(); // Layout the child. let mut frames = self.child.layout(ctx, &pod); @@ -40,23 +39,17 @@ impl Layout for AlignNode { for (Constrained { item: frame, cts }, (current, base)) in frames.iter_mut().zip(regions.iter()) { - // The possibly larger size in which we align the frame. - let new = Size::new( - if regions.expand.x { current.w } else { frame.size.w }, - if regions.expand.y { current.h } else { frame.size.h }, - ); - - let aligns = self.aligns.unwrap_or(Spec::new(Align::Left, Align::Top)); - Rc::make_mut(frame).resize(new, aligns); + // Align in the target size. The target size depends on whether we + // should expand. + let target = regions.expand.select(current, frame.size); + let default = Spec::new(Align::Left, Align::Top); + let aligns = self.aligns.unwrap_or(default); + Rc::make_mut(frame).resize(target, aligns); // Set constraints. cts.expand = regions.expand; - cts.base.x.and_set(Some(base.w)); - cts.base.y.and_set(Some(base.h)); - cts.exact = Spec::new( - regions.expand.x.then(|| current.w), - regions.expand.y.then(|| current.h), - ); + cts.base = base.filter(cts.base.map_is_some()); + cts.exact = current.filter(regions.expand); } frames diff --git a/src/library/flow.rs b/src/library/flow.rs index d30bce09c..e105c596a 100644 --- a/src/library/flow.rs +++ b/src/library/flow.rs @@ -157,10 +157,10 @@ impl<'a> FlowLayouter<'a> { /// Layout absolute spacing. fn layout_absolute(&mut self, amount: Linear) { // Resolve the linear, limiting it to the remaining available space. - let resolved = amount.resolve(self.full.h); - let limited = resolved.min(self.regions.current.h); - self.regions.current.h -= limited; - self.used.h += limited; + let resolved = amount.resolve(self.full.y); + let limited = resolved.min(self.regions.current.y); + self.regions.current.y -= limited; + self.used.y += limited; self.items.push(FlowItem::Absolute(resolved)); } @@ -195,9 +195,9 @@ impl<'a> FlowLayouter<'a> { for (i, frame) in frames.into_iter().enumerate() { // Grow our size, shrink the region and save the frame for later. let size = frame.item.size; - self.used.h += size.h; - self.used.w.set_max(size.w); - self.regions.current.h -= size.h; + self.used.y += size.y; + self.used.x.set_max(size.x); + self.regions.current.y -= size.y; self.items.push(FlowItem::Frame(frame.item, aligns)); if i + 1 < len { @@ -210,16 +210,13 @@ impl<'a> FlowLayouter<'a> { fn finish_region(&mut self) { // Determine the size of the flow in this region dependening on whether // the region expands. - let mut size = Size::new( - if self.expand.x { self.full.w } else { self.used.w }, - if self.expand.y { self.full.h } else { self.used.h }, - ); + let mut size = self.expand.select(self.full, self.used); // Account for fractional spacing in the size calculation. - let remaining = self.full.h - self.used.h; - if self.fr.get() > 0.0 && self.full.h.is_finite() { - self.used.h = self.full.h; - size.h = self.full.h; + let remaining = self.full.y - self.used.y; + if self.fr.get() > 0.0 && self.full.y.is_finite() { + self.used.y = self.full.y; + size.y = self.full.y; } let mut output = Frame::new(size); @@ -243,10 +240,10 @@ impl<'a> FlowLayouter<'a> { ruler = ruler.max(aligns.y); // Align horizontally and vertically. - let x = aligns.x.resolve(size.w - frame.size.w); - let y = before + ruler.resolve(size.h - self.used.h); + let x = aligns.x.resolve(size.x - frame.size.x); + let y = before + ruler.resolve(size.y - self.used.y); let pos = Point::new(x, y); - before += frame.size.h; + before += frame.size.y; // The baseline of the flow is that of the first frame. if first { @@ -261,8 +258,8 @@ impl<'a> FlowLayouter<'a> { // Generate tight constraints for now. let mut cts = Constraints::new(self.expand); - cts.exact = self.full.to_spec().map(Some); - cts.base = self.regions.base.to_spec().map(Some); + cts.exact = self.full.map(Some); + cts.base = self.regions.base.map(Some); // Advance to the next region. self.regions.next(); diff --git a/src/library/grid.rs b/src/library/grid.rs index 09bb3b3ba..9dd156dac 100644 --- a/src/library/grid.rs +++ b/src/library/grid.rs @@ -178,7 +178,7 @@ impl<'a> GridLayouter<'a> { rcols: vec![Length::zero(); cols.len()], cols, rows, - full: regions.current.h, + full: regions.current.y, regions, used: Size::zero(), fr: Fractional::zero(), @@ -220,7 +220,7 @@ impl<'a> GridLayouter<'a> { case = Case::Fitting; } TrackSizing::Linear(v) => { - let resolved = v.resolve(self.regions.base.w); + let resolved = v.resolve(self.regions.base.x); *rcol = resolved; linear += resolved; } @@ -232,7 +232,7 @@ impl<'a> GridLayouter<'a> { } // Size that is not used by fixed-size columns. - let available = self.regions.current.w - linear; + let available = self.regions.current.x - linear; if available >= Length::zero() { // Determine size of auto columns. let (auto, count) = self.measure_auto_columns(ctx, available); @@ -254,18 +254,18 @@ impl<'a> GridLayouter<'a> { } // Children could depend on base. - self.cts.base = self.regions.base.to_spec().map(Some); + self.cts.base = self.regions.base.map(Some); // Set constraints depending on the case we hit. match case { Case::PurelyLinear => {} - Case::Fitting => self.cts.min.x = Some(self.used.w), - Case::Exact => self.cts.exact.x = Some(self.regions.current.w), + Case::Fitting => self.cts.min.x = Some(self.used.x), + Case::Exact => self.cts.exact.x = Some(self.regions.current.x), Case::Overflowing => self.cts.max.x = Some(linear), } // Sum up the resolved column sizes once here. - self.used.w = self.rcols.iter().sum(); + self.used.x = self.rcols.iter().sum(); } /// Measure the size that is available to auto columns. @@ -287,7 +287,7 @@ impl<'a> GridLayouter<'a> { let mut resolved = Length::zero(); 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 size = Size::new(available, self.regions.base.y); let mut pod = Regions::one(size, self.regions.base, Spec::splat(false)); @@ -295,11 +295,11 @@ impl<'a> GridLayouter<'a> { // base, for auto it's already correct and for fr we could // only guess anyway. if let TrackSizing::Linear(v) = self.rows[y] { - pod.base.h = v.resolve(self.regions.base.h); + pod.base.y = v.resolve(self.regions.base.y); } let frame = node.layout(ctx, &pod).remove(0).item; - resolved.set_max(frame.size.w); + resolved.set_max(frame.size.x); } } @@ -382,17 +382,15 @@ 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) { + // All widths should be `rcol` except the base for auto columns. 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. + pod.mutate(|size| size.x = rcol); if self.cols[x] == TrackSizing::Auto { - pod.base.w = self.regions.base.w; + pod.base.x = self.regions.base.x; } let mut sizes = - node.layout(ctx, &pod).into_iter().map(|frame| frame.item.size.h); + node.layout(ctx, &pod).into_iter().map(|frame| frame.item.size.y); // For each region, we want to know the maximum height any // column requires. @@ -425,7 +423,7 @@ impl<'a> GridLayouter<'a> { for (target, (current, _)) in resolved[.. len - 1].iter_mut().zip(self.regions.iter()) { - target.set_max(current.h); + target.set_max(current.y); } } @@ -444,13 +442,13 @@ impl<'a> GridLayouter<'a> { /// Layout a row with linear height. Such a row cannot break across multiple /// regions, but it may force a region break. fn layout_linear_row(&mut self, ctx: &mut LayoutContext, v: Linear, y: usize) { - let resolved = v.resolve(self.regions.base.h); + let resolved = v.resolve(self.regions.base.y); let frame = self.layout_single_row(ctx, resolved, y); // Skip to fitting region. - let height = frame.size.h; - while !self.regions.current.h.fits(height) && !self.regions.in_last() { - self.cts.max.y = Some(self.used.h + height); + let height = frame.size.y; + while !self.regions.current.y.fits(height) && !self.regions.in_last() { + self.cts.max.y = Some(self.used.y + height); self.finish_region(ctx); // Don't skip multiple regions for gutter and don't push a row. @@ -469,21 +467,18 @@ impl<'a> GridLayouter<'a> { height: Length, y: usize, ) -> Frame { - let mut output = Frame::new(Size::new(self.used.w, height)); + let mut output = Frame::new(Size::new(self.used.x, height)); let mut pos = Point::zero(); for (x, &rcol) in self.rcols.iter().enumerate() { if let Some(node) = self.cell(x, y) { let size = Size::new(rcol, height); - // Set the base to the size for non-auto rows. - let mut base = self.regions.base; - if self.cols[x] != TrackSizing::Auto { - base.w = size.w; - } - if self.rows[y] != TrackSizing::Auto { - base.h = size.h; - } + // Set the base to the region's base for auto rows and to the + // size for linear and fractional rows. + let base = Spec::new(self.cols[x], self.rows[y]) + .map(|s| s == TrackSizing::Auto) + .select(self.regions.base, size); let pod = Regions::one(size, base, Spec::splat(true)); let frame = node.layout(ctx, &pod).remove(0); @@ -506,15 +501,15 @@ impl<'a> GridLayouter<'a> { // Prepare frames. let mut outputs: Vec<_> = heights .iter() - .map(|&h| Frame::new(Size::new(self.used.w, h))) + .map(|&h| Frame::new(Size::new(self.used.x, h))) .collect(); // Prepare regions. - let size = Size::new(self.used.w, heights[0]); + let size = Size::new(self.used.x, heights[0]); let mut pod = Regions::one(size, self.regions.base, Spec::splat(true)); pod.backlog = heights[1 ..] .iter() - .map(|&h| Size::new(self.used.w, h)) + .map(|&h| Size::new(self.used.x, h)) .collect::>() .into_iter(); @@ -522,12 +517,10 @@ 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) { - pod.mutate(|size| size.w = rcol); - - // Set the horizontal base back to the parent region's base for - // auto columns. + // All widths should be `rcol` except the base for auto columns. + pod.mutate(|size| size.x = rcol); if self.cols[x] == TrackSizing::Auto { - pod.base.w = self.regions.base.w; + pod.base.x = self.regions.base.x; } // Push the layouted frames into the individual output frames. @@ -545,8 +538,8 @@ impl<'a> GridLayouter<'a> { /// Push a row frame into the current region. fn push_row(&mut self, frame: Frame) { - self.regions.current.h -= frame.size.h; - self.used.h += frame.size.h; + self.regions.current.y -= frame.size.y; + self.used.y += frame.size.y; self.lrows.push(Row::Frame(frame)); } @@ -556,10 +549,10 @@ impl<'a> GridLayouter<'a> { // there are fr rows. let mut size = self.used; if self.fr.get() > 0.0 && self.full.is_finite() { - size.h = self.full; + size.y = self.full; self.cts.exact.y = Some(self.full); } else { - self.cts.min.y = Some(size.h); + self.cts.min.y = Some(size.y); } // The frame for the region. @@ -571,20 +564,20 @@ impl<'a> GridLayouter<'a> { let frame = match row { Row::Frame(frame) => frame, Row::Fr(v, y) => { - let remaining = self.full - self.used.h; + let remaining = self.full - self.used.y; let height = v.resolve(self.fr, remaining); self.layout_single_row(ctx, height, y) } }; - let height = frame.size.h; + let height = frame.size.y; output.merge_frame(pos, frame); pos.y += height; } self.regions.next(); - self.full = self.regions.current.h; - self.used.h = Length::zero(); + self.full = self.regions.current.y; + self.used.y = Length::zero(); self.fr = Fractional::zero(); self.finished.push(output.constrain(self.cts)); self.cts = Constraints::new(self.expand); diff --git a/src/library/image.rs b/src/library/image.rs index 185d033a9..92580f6e9 100644 --- a/src/library/image.rs +++ b/src/library/image.rs @@ -47,16 +47,16 @@ impl Layout for ImageNode { let pxh = img.height() as f64; let pixel_ratio = pxw / pxh; - let current_ratio = current.w / current.h; + let current_ratio = current.x / current.y; let wide = pixel_ratio > current_ratio; // The space into which the image will be placed according to its fit. let canvas = if expand.x && expand.y { current - } else if expand.x || (wide && current.w.is_finite()) { - Size::new(current.w, current.h.min(current.w.safe_div(pixel_ratio))) - } else if current.h.is_finite() { - Size::new(current.w.min(current.h * pixel_ratio), current.h) + } else if expand.x || (wide && current.x.is_finite()) { + Size::new(current.x, current.y.min(current.x.safe_div(pixel_ratio))) + } else if current.y.is_finite() { + Size::new(current.x.min(current.y * pixel_ratio), current.y) } else { Size::new(Length::pt(pxw), Length::pt(pxh)) }; @@ -65,9 +65,9 @@ impl Layout for ImageNode { let size = match self.fit { ImageFit::Contain | ImageFit::Cover => { if wide == (self.fit == ImageFit::Contain) { - Size::new(canvas.w, canvas.w / pixel_ratio) + Size::new(canvas.x, canvas.x / pixel_ratio) } else { - Size::new(canvas.h * pixel_ratio, canvas.h) + Size::new(canvas.y * pixel_ratio, canvas.y) } } ImageFit::Stretch => canvas, diff --git a/src/library/mod.rs b/src/library/mod.rs index 4d730a7ec..c953a76e3 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -143,15 +143,6 @@ dynamic! { Align: "alignment", } -dynamic! { - Spec>: "2d alignment", - @align: Align => { - let mut aligns = Spec::default(); - aligns.set(align.axis(), Some(*align)); - aligns - }, -} - dynamic! { FontFamily: "font family", Value::Str(string) => Self::Named(string.to_lowercase()), @@ -162,3 +153,15 @@ castable! { Expected: "color", Value::Color(color) => Paint::Solid(color), } + +castable! { + Spec>, + Expected: "1d or 2d alignment", + @align: Align => { + let mut aligns = Spec::default(); + aligns.set(align.axis(), Some(*align)); + aligns + }, + @aligns: Spec => aligns.map(Some), + +} diff --git a/src/library/pad.rs b/src/library/pad.rs index 7604af400..ce7f41506 100644 --- a/src/library/pad.rs +++ b/src/library/pad.rs @@ -54,29 +54,17 @@ impl Layout for PadNode { 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)); - cts.exact.y.and_set(Some(current.h)); - cts.base.x.and_set(Some(base.w)); - cts.base.y.and_set(Some(base.h)); - - // Also set base constraints if the padding is relative. - if self.padding.left.is_relative() || self.padding.right.is_relative() { - cts.base.x = Some(base.w); - } - - if self.padding.top.is_relative() || self.padding.bottom.is_relative() { - cts.base.y = Some(base.h); - } + // Set exact and base constraints if the child had them. Also set + // base if our padding is relative. + let is_rel = self.padding.sum_by_axis().map(Linear::is_relative); + cts.exact = current.filter(cts.exact.map_is_some()); + cts.base = base.filter(is_rel | cts.base.map_is_some()); // 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; - } + spec.as_mut() + .zip(padding.sum_by_axis()) + .map(|(s, p)| s.as_mut().map(|v| *v += p)); } } @@ -86,7 +74,7 @@ impl Layout for PadNode { /// Shrink a size by padding relative to the size itself. fn shrink(size: Size, padding: Sides) -> Size { - size - padding.resolve(size).size() + size - padding.resolve(size).sum_by_axis() } /// Grow a size by padding relative to the grown size. @@ -109,12 +97,6 @@ fn shrink(size: Size, padding: Sides) -> Size { /// <=> (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), - ) + size.zip(padding.sum_by_axis()) + .map(|(s, p)| (s + p.abs).safe_div(1.0 - p.rel.get())) } diff --git a/src/library/page.rs b/src/library/page.rs index 1ac21facf..0289401ae 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -30,16 +30,16 @@ pub fn page(ctx: &mut EvalContext, args: &mut Args) -> TypResult { if let Some(width) = width { page.class = PaperClass::Custom; - page.size.w = width; + page.size.x = width; } if flip.unwrap_or(false) { - std::mem::swap(&mut page.size.w, &mut page.size.h); + std::mem::swap(&mut page.size.x, &mut page.size.y); } if let Some(height) = height { page.class = PaperClass::Custom; - page.size.h = height; + page.size.y = height; } if let Some(margins) = margins { @@ -95,7 +95,7 @@ impl PageNode { pub fn layout(&self, ctx: &mut LayoutContext) -> Vec> { // When one of the lengths is infinite the page fits its content along // that axis. - let expand = self.size.to_spec().map(Length::is_finite); + let expand = self.size.map(Length::is_finite); let regions = Regions::repeat(self.size, self.size, expand); // Layout the child. diff --git a/src/library/par.rs b/src/library/par.rs index e09e4ad27..5f900dff3 100644 --- a/src/library/par.rs +++ b/src/library/par.rs @@ -208,7 +208,7 @@ impl<'a> ParLayouter<'a> { for (range, child) in par.ranges().zip(&par.children) { match *child { ParChild::Spacing(Spacing::Linear(v)) => { - let resolved = v.resolve(regions.current.w); + let resolved = v.resolve(regions.current.x); items.push(ParItem::Absolute(resolved)); ranges.push(range); } @@ -230,7 +230,7 @@ impl<'a> ParLayouter<'a> { } } ParChild::Node(ref node) => { - let size = Size::new(regions.current.w, regions.base.h); + let size = Size::new(regions.current.x, regions.base.y); let expand = Spec::splat(false); let pod = Regions::one(size, regions.base, expand); let frame = node.layout(ctx, &pod).remove(0); @@ -292,26 +292,26 @@ impl<'a> ParLayouter<'a> { // fit the line will yield the same line break. Therefore, // the width of the region must not fit the width of the // tried line. - if !stack.regions.current.w.fits(line.size.w) { - stack.cts.max.x.set_min(line.size.w); + if !stack.regions.current.x.fits(line.size.x) { + stack.cts.max.x.set_min(line.size.x); } // Same as above, but for height. - if !stack.regions.current.h.fits(line.size.h) { - let too_large = stack.size.h + self.leading + line.size.h; + if !stack.regions.current.y.fits(line.size.y) { + let too_large = stack.size.y + self.leading + line.size.y; stack.cts.max.y.set_min(too_large); } stack.push(last_line); - stack.cts.min.y = Some(stack.size.h); + stack.cts.min.y = Some(stack.size.y); start = last_end; line = LineLayout::new(ctx, &self, start .. end); } } // If the line does not fit vertically, we start a new region. - while !stack.regions.current.h.fits(line.size.h) { + while !stack.regions.current.y.fits(line.size.y) { if stack.regions.in_last() { stack.overflowing = true; break; @@ -320,7 +320,7 @@ impl<'a> ParLayouter<'a> { // Again, the line must not fit. It would if the space taken up // plus the line height would fit, therefore the constraint // below. - let too_large = stack.size.h + self.leading + line.size.h; + let too_large = stack.size.y + self.leading + line.size.y; stack.cts.max.y.set_min(too_large); stack.finish_region(ctx); @@ -329,7 +329,7 @@ impl<'a> ParLayouter<'a> { // If the line does not fit horizontally or we have a mandatory // line break (i.e. due to "\n"), we push the line into the // stack. - if mandatory || !stack.regions.current.w.fits(line.size.w) { + if mandatory || !stack.regions.current.x.fits(line.size.x) { start = end; last = None; @@ -339,23 +339,23 @@ impl<'a> ParLayouter<'a> { // paragraph, we want to force an empty line. if mandatory && end == self.bidi.text.len() { let line = LineLayout::new(ctx, &self, end .. end); - if stack.regions.current.h.fits(line.size.h) { + if stack.regions.current.y.fits(line.size.y) { stack.push(line); } } - stack.cts.min.y = Some(stack.size.h); + stack.cts.min.y = Some(stack.size.y); } else { // Otherwise, the line fits both horizontally and vertically // and we remember it. - stack.cts.min.x.set_max(line.size.w); + stack.cts.min.x.set_max(line.size.x); last = Some((line, end)); } } if let Some((line, _)) = last { stack.push(line); - stack.cts.min.y = Some(stack.size.h); + stack.cts.min.y = Some(stack.size.y); } stack.finish(ctx) @@ -467,9 +467,9 @@ impl<'a> LineLayout<'a> { ParItem::Fractional(v) => fr += v, ParItem::Text(ShapedText { size, baseline, .. }) | ParItem::Frame(Frame { size, baseline, .. }) => { - width += size.w; + width += size.x; top.set_max(baseline); - bottom.set_max(size.h - baseline); + bottom.set_max(size.y - baseline); } } } @@ -489,8 +489,8 @@ impl<'a> LineLayout<'a> { /// Build the line's frame. fn build(&self, ctx: &LayoutContext, width: Length) -> Frame { - let size = Size::new(self.size.w.max(width), self.size.h); - let remaining = size.w - self.size.w; + let size = Size::new(self.size.x.max(width), self.size.y); + let remaining = size.x - self.size.x; let mut output = Frame::new(size); let mut offset = Length::zero(); @@ -507,7 +507,7 @@ impl<'a> LineLayout<'a> { let x = offset + self.par.align.resolve(remaining); let y = self.baseline - frame.baseline; - offset += frame.size.w; + offset += frame.size.x; // Add to the line's frame. output.merge_frame(Point::new(x, y), frame); @@ -602,12 +602,12 @@ impl<'a> LineStack<'a> { /// Push a new line into the stack. fn push(&mut self, line: LineLayout<'a>) { - self.regions.current.h -= line.size.h + self.leading; + self.regions.current.y -= line.size.y + self.leading; - self.size.w.set_max(line.size.w); - self.size.h += line.size.h; + self.size.x.set_max(line.size.x); + self.size.y += line.size.y; if !self.lines.is_empty() { - self.size.h += self.leading; + self.size.y += self.leading; } self.fractional |= !line.fr.is_zero(); @@ -617,14 +617,14 @@ impl<'a> LineStack<'a> { /// Finish the frame for one region. fn finish_region(&mut self, ctx: &LayoutContext) { if self.regions.expand.x || self.fractional { - self.size.w = self.regions.current.w; - self.cts.exact.x = Some(self.regions.current.w); + self.size.x = self.regions.current.x; + self.cts.exact.x = Some(self.regions.current.x); } if self.overflowing { self.cts.min.y = None; self.cts.max.y = None; - self.cts.exact = self.full.to_spec().map(Some); + self.cts.exact = self.full.map(Some); } let mut output = Frame::new(self.size); @@ -632,7 +632,7 @@ impl<'a> LineStack<'a> { let mut first = true; for line in self.lines.drain(..) { - let frame = line.build(ctx, self.size.w); + let frame = line.build(ctx, self.size.x); let pos = Point::with_y(offset); if first { @@ -640,7 +640,7 @@ impl<'a> LineStack<'a> { first = false; } - offset += frame.size.h + self.leading; + offset += frame.size.y + self.leading; output.merge_frame(pos, frame); } @@ -648,7 +648,7 @@ impl<'a> LineStack<'a> { self.regions.next(); self.full = self.regions.current; self.cts = Constraints::new(self.regions.expand); - self.cts.base = self.regions.base.to_spec().map(Some); + self.cts.base = self.regions.base.map(Some); self.size = Size::zero(); } diff --git a/src/library/shape.rs b/src/library/shape.rs index 36e25b3c3..208ca2a39 100644 --- a/src/library/shape.rs +++ b/src/library/shape.rs @@ -138,8 +138,8 @@ impl Layout for ShapeNode { // the result is really a square or circle. let size = frames[0].item.size; let mut pod = regions.clone(); - pod.current.w = size.w.max(size.h).min(pod.current.w); - pod.current.h = pod.current.w; + pod.current.x = size.x.max(size.y).min(pod.current.x); + pod.current.y = pod.current.x; pod.expand = Spec::splat(true); frames = node.layout(ctx, &pod); } @@ -153,7 +153,7 @@ impl Layout for ShapeNode { let default = Length::pt(30.0); let mut size = Size::new( if regions.expand.x { - regions.current.w + regions.current.x } else { // For rectangle and ellipse, the default shape is a bit // wider than high. @@ -162,16 +162,16 @@ impl Layout for ShapeNode { ShapeKind::Rect | ShapeKind::Ellipse => 1.5 * default, } }, - if regions.expand.y { regions.current.h } else { default }, + if regions.expand.y { regions.current.y } else { default }, ); // Don't overflow the region. - size.w = size.w.min(regions.current.w); - size.h = size.h.min(regions.current.h); + size.x = size.x.min(regions.current.x); + size.y = size.y.min(regions.current.y); if matches!(self.kind, ShapeKind::Square | ShapeKind::Circle) { - size.w = size.w.min(size.h); - size.h = size.w; + size.x = size.x.min(size.y); + size.y = size.x; } Frame::new(size) @@ -194,11 +194,7 @@ impl Layout for ShapeNode { } // Ensure frame size matches regions size if expansion is on. - let expand = regions.expand; - frame.size = Size::new( - if expand.x { regions.current.w } else { frame.size.w }, - if expand.y { regions.current.h } else { frame.size.h }, - ); + frame.size = regions.expand.select(regions.current, frame.size); // Return tight constraints for now. vec![frame.constrain(Constraints::tight(regions))] diff --git a/src/library/sized.rs b/src/library/sized.rs index df150143d..9b2cdf227 100644 --- a/src/library/sized.rs +++ b/src/library/sized.rs @@ -35,50 +35,36 @@ impl Layout for SizedNode { ctx: &mut LayoutContext, regions: &Regions, ) -> Vec>> { - // Generate constraints. - let mut cts = Constraints::new(regions.expand); - cts.set_base_if_linear(regions.base, self.sizing); - - // Set tight exact and base constraints if the child is - // automatically sized since we don't know what the child might do. - if self.sizing.x.is_none() { - cts.exact.x = Some(regions.current.w); - cts.base.x = Some(regions.base.w); - } - - // Same here. - if self.sizing.y.is_none() { - cts.exact.y = Some(regions.current.h); - cts.base.y = Some(regions.base.h); - } - - // Resolve width and height relative to the region's base. - let width = self.sizing.x.map(|w| w.resolve(regions.base.w)); - let height = self.sizing.y.map(|h| h.resolve(regions.base.h)); + let is_auto = self.sizing.map_is_none(); + let is_rel = self.sizing.map(|s| s.map_or(false, Linear::is_relative)); // The "pod" is the region into which the child will be layouted. let pod = { - let size = Size::new( - width.unwrap_or(regions.current.w), - height.unwrap_or(regions.current.h), - ); + // Resolve the sizing to a concrete size. + let size = self + .sizing + .zip(regions.base) + .map(|(s, b)| s.map(|v| v.resolve(b))) + .unwrap_or(regions.current); - let base = Size::new( - if width.is_some() { size.w } else { regions.base.w }, - if height.is_some() { size.h } else { regions.base.h }, - ); + // Select the appropriate base and expansion for the child depending + // on whether it is automatically or linearly sized. + let base = is_auto.select(regions.base, size); + let expand = regions.expand | !is_auto; - let expand = Spec::new( - width.is_some() || regions.expand.x, - height.is_some() || regions.expand.y, - ); - - // TODO: Allow multiple regions if only width is set. Regions::one(size, base, expand) }; let mut frames = self.child.layout(ctx, &pod); - frames[0].cts = cts; + + // Set base & exact constraints if the child is automatically sized + // since we don't know what the child might do. Also set base if our + // sizing is relative. + let frame = &mut frames[0]; + frame.cts = Constraints::new(regions.expand); + frame.cts.exact = regions.current.filter(is_auto); + frame.cts.base = regions.base.filter(is_auto | is_rel); + frames } } diff --git a/src/library/stack.rs b/src/library/stack.rs index a6878bd68..91f1ef622 100644 --- a/src/library/stack.rs +++ b/src/library/stack.rs @@ -128,7 +128,6 @@ impl<'a> StackLayouter<'a> { let expand = regions.expand; let full = regions.current; - // Disable expansion along the block axis for children. let mut regions = regions.clone(); regions.expand.set(axis, false); @@ -210,11 +209,8 @@ impl<'a> StackLayouter<'a> { fn finish_region(&mut self) { // Determine the size of the stack in this region dependening on whether // the region expands. - let used = self.used.to_size(self.axis); - let mut size = Size::new( - if self.expand.x { self.full.w } else { used.w }, - if self.expand.y { self.full.h } else { used.h }, - ); + let used = self.used.to_spec(self.axis); + let mut size = self.expand.select(self.full, used); // Expand fully if there are fr spacings. let full = self.full.get(self.axis); @@ -263,8 +259,8 @@ impl<'a> StackLayouter<'a> { // Generate tight constraints for now. let mut cts = Constraints::new(self.expand); - cts.exact = self.full.to_spec().map(Some); - cts.base = self.regions.base.to_spec().map(Some); + cts.exact = self.full.map(Some); + cts.base = self.regions.base.map(Some); // Advance to the next region. self.regions.next(); diff --git a/src/library/transform.rs b/src/library/transform.rs index 207a80988..c8b486666 100644 --- a/src/library/transform.rs +++ b/src/library/transform.rs @@ -57,8 +57,7 @@ impl Layout for TransformNode { let mut frames = self.child.layout(ctx, regions); for Constrained { item: frame, .. } in frames.iter_mut() { - let x = self.origin.x.resolve(frame.size.w); - let y = self.origin.y.resolve(frame.size.h); + let Spec { x, y } = self.origin.zip(frame.size).map(|(o, s)| o.resolve(s)); let transform = Transform::translation(x, y) .pre_concat(self.transform) .pre_concat(Transform::translation(-x, -y)); diff --git a/src/util/mod.rs b/src/util/mod.rs index 8b74aed68..6fc1fb59c 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -27,9 +27,6 @@ impl BoolExt for bool { /// Additional methods for options. pub trait OptionExt { - /// Replace `self` with `other` if `self` is `Some`. - fn and_set(&mut self, other: Option); - /// Sets `other` as the value if `self` is `None` or if it contains a value /// larger than `other`. fn set_min(&mut self, other: T) @@ -44,12 +41,6 @@ pub trait OptionExt { } impl OptionExt for Option { - fn and_set(&mut self, other: Option) { - if self.is_some() { - *self = other; - } - } - fn set_min(&mut self, other: T) where T: Ord, diff --git a/tests/typeset.rs b/tests/typeset.rs index 289cf7660..8f8556c4c 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -387,8 +387,8 @@ fn print_error(source: &SourceFile, line: usize, error: &Error) { fn draw(ctx: &Context, frames: &[Rc], dpp: f32) -> sk::Pixmap { let pad = Length::pt(5.0); - let width = 2.0 * pad + frames.iter().map(|l| l.size.w).max().unwrap_or_default(); - let height = pad + frames.iter().map(|l| l.size.h + pad).sum::(); + let width = 2.0 * pad + frames.iter().map(|l| l.size.x).max().unwrap_or_default(); + let height = pad + frames.iter().map(|l| l.size.y + pad).sum::(); let pxw = (dpp * width.to_f32()) as u32; let pxh = (dpp * height.to_f32()) as u32; @@ -414,13 +414,13 @@ fn draw(ctx: &Context, frames: &[Rc], dpp: f32) -> sk::Pixmap { let mut background = sk::Paint::default(); background.set_color(sk::Color::WHITE); - let w = frame.size.w.to_f32(); - let h = frame.size.h.to_f32(); + let w = frame.size.x.to_f32(); + let h = frame.size.y.to_f32(); let rect = sk::Rect::from_xywh(0.0, 0.0, w, h).unwrap(); canvas.fill_rect(rect, &background, ts, None); draw_frame(&mut canvas, ts, &mask, ctx, frame); - ts = ts.pre_translate(0.0, (frame.size.h + pad).to_f32()); + ts = ts.pre_translate(0.0, (frame.size.y + pad).to_f32()); } canvas @@ -469,8 +469,8 @@ fn draw_group( ) { let ts = ts.pre_concat(convert_typst_transform(group.transform)); if group.clips { - let w = group.frame.size.w.to_f32(); - let h = group.frame.size.h.to_f32(); + let w = group.frame.size.x.to_f32(); + let h = group.frame.size.y.to_f32(); let rect = sk::Rect::from_xywh(0.0, 0.0, w, h).unwrap(); let path = sk::PathBuilder::from_rect(rect).transform(ts).unwrap(); let rule = sk::FillRule::default(); @@ -565,8 +565,8 @@ fn draw_shape( ) { let path = match shape.geometry { Geometry::Rect(size) => { - let w = size.w.to_f32(); - let h = size.h.to_f32(); + let w = size.x.to_f32(); + let h = size.y.to_f32(); let rect = sk::Rect::from_xywh(0.0, 0.0, w, h).unwrap(); sk::PathBuilder::from_rect(rect) } @@ -613,8 +613,8 @@ fn draw_image( *dest = sk::ColorU8::from_rgba(r, g, b, a).premultiply(); } - let view_width = size.w.to_f32(); - let view_height = size.h.to_f32(); + let view_width = size.x.to_f32(); + let view_height = size.y.to_f32(); let scale_x = view_width as f32 / pixmap.width() as f32; let scale_y = view_height as f32 / pixmap.height() as f32;