diff --git a/src/geom/spec.rs b/src/geom/spec.rs index 16d63959b..5d89c894f 100644 --- a/src/geom/spec.rs +++ b/src/geom/spec.rs @@ -85,6 +85,27 @@ impl Spec { } } +impl Spec +where + T: Ord, +{ + /// The component-wise minimum of this and another instance. + pub fn min(self, other: Self) -> Self { + Self { + x: self.x.min(other.x), + y: self.y.min(other.y), + } + } + + /// The component-wise minimum of this and another instance. + pub fn max(self, other: Self) -> Self { + Self { + x: self.x.max(other.x), + y: self.y.max(other.y), + } + } +} + impl Get for Spec { type Component = T; diff --git a/src/library/align.rs b/src/library/align.rs index a2881ccea..189203690 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -61,7 +61,7 @@ impl Layout for AlignNode { // Set constraints. cts.expand = regions.expand; cts.base = base.filter(cts.base.map_is_some()); - cts.exact = current.filter(regions.expand); + cts.exact = current.filter(regions.expand | cts.exact.map_is_some()); } frames diff --git a/src/library/flow.rs b/src/library/flow.rs index 587b5a808..9494d6c06 100644 --- a/src/library/flow.rs +++ b/src/library/flow.rs @@ -132,10 +132,6 @@ impl<'a> FlowLayouter<'a> { /// Layout all children. fn layout(mut self, ctx: &mut LayoutContext) -> Vec>> { for child in self.children { - if self.regions.is_full() { - self.finish_region(); - } - match *child { FlowChild::Spacing(Spacing::Linear(v)) => { self.layout_absolute(v); @@ -145,6 +141,10 @@ impl<'a> FlowLayouter<'a> { self.fr += v; } FlowChild::Node(ref node) => { + if self.regions.is_full() { + self.finish_region(); + } + self.layout_node(ctx, node); } } diff --git a/src/library/grid.rs b/src/library/grid.rs index 9dd156dac..7a9d88c37 100644 --- a/src/library/grid.rs +++ b/src/library/grid.rs @@ -552,7 +552,7 @@ impl<'a> GridLayouter<'a> { size.y = self.full; self.cts.exact.y = Some(self.full); } else { - self.cts.min.y = Some(size.y); + self.cts.min.y = Some(size.y.min(self.full)); } // The frame for the region. @@ -575,11 +575,12 @@ impl<'a> GridLayouter<'a> { pos.y += height; } + self.cts.base = self.regions.base.map(Some); + self.finished.push(output.constrain(self.cts)); self.regions.next(); 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 a20f38565..7ef45d6d2 100644 --- a/src/library/image.rs +++ b/src/library/image.rs @@ -40,34 +40,34 @@ impl Layout for ImageNode { ctx: &mut LayoutContext, regions: &Regions, ) -> Vec>> { - let &Regions { current, expand, .. } = regions; - let img = ctx.images.get(self.id); let pxw = img.width() as f64; let pxh = img.height() as f64; + let px_ratio = pxw / pxh; - let pixel_ratio = pxw / pxh; + // Find out whether the image is wider or taller than the target size. + let current = regions.current; let current_ratio = current.x / current.y; - let wide = pixel_ratio > current_ratio; + let wide = px_ratio > current_ratio; // The space into which the image will be placed according to its fit. - let target = if expand.x && expand.y { + let target = if regions.expand.x && regions.expand.y { current - } else if expand.x || (wide && current.x.is_finite()) { - Size::new(current.x, current.y.min(current.x.safe_div(pixel_ratio))) + } else if regions.expand.x || (wide && current.x.is_finite()) { + Size::new(current.x, current.y.min(current.x.safe_div(px_ratio))) } else if current.y.is_finite() { - Size::new(current.x.min(current.y * pixel_ratio), current.y) + Size::new(current.x.min(current.y * px_ratio), current.y) } else { Size::new(Length::pt(pxw), Length::pt(pxh)) }; // The actual size of the fitted image. - let size = match self.fit { + let fitted = match self.fit { ImageFit::Contain | ImageFit::Cover => { if wide == (self.fit == ImageFit::Contain) { - Size::new(target.x, target.x / pixel_ratio) + Size::new(target.x, target.x / px_ratio) } else { - Size::new(target.y * pixel_ratio, target.y) + Size::new(target.y * px_ratio, target.y) } } ImageFit::Stretch => target, @@ -76,8 +76,8 @@ impl Layout for ImageNode { // First, place the image in a frame of exactly its size and then resize // the frame to the target size, center aligning the image in the // process. - let mut frame = Frame::new(size); - frame.push(Point::zero(), Element::Image(self.id, size)); + let mut frame = Frame::new(fitted); + frame.push(Point::zero(), Element::Image(self.id, fitted)); frame.resize(target, Align::CENTER_HORIZON); // Create a clipping group if the fit mode is "cover". diff --git a/src/library/par.rs b/src/library/par.rs index 5f900dff3..c29e5f9ed 100644 --- a/src/library/par.rs +++ b/src/library/par.rs @@ -231,8 +231,7 @@ impl<'a> ParLayouter<'a> { } ParChild::Node(ref node) => { let size = Size::new(regions.current.x, regions.base.y); - let expand = Spec::splat(false); - let pod = Regions::one(size, regions.base, expand); + let pod = Regions::one(size, regions.base, Spec::splat(false)); let frame = node.layout(ctx, &pod).remove(0); items.push(ParItem::Frame(Rc::take(frame.item))); ranges.push(range); @@ -288,25 +287,31 @@ impl<'a> ParLayouter<'a> { // line cannot be broken up further. if !stack.regions.current.fits(line.size) { if let Some((last_line, last_end)) = last.take() { + let fits = + stack.regions.current.zip(line.size).map(|(c, s)| c.fits(s)); + // Since the new line try did not fit, no region that would // 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.x.fits(line.size.x) { + if !fits.x { stack.cts.max.x.set_min(line.size.x); } // Same as above, but for height. - if !stack.regions.current.y.fits(line.size.y) { + if !fits.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.y); - start = last_end; - line = LineLayout::new(ctx, &self, start .. end); + // Don't start new lines at every opportunity when we are + // overflowing. + if !stack.overflowing || !fits.x { + stack.push(last_line); + stack.cts.min.y = Some(stack.size.y); + start = last_end; + line = LineLayout::new(ctx, &self, start .. end); + } } } @@ -322,7 +327,6 @@ impl<'a> ParLayouter<'a> { // below. let too_large = stack.size.y + self.leading + line.size.y; stack.cts.max.y.set_min(too_large); - stack.finish_region(ctx); } @@ -644,12 +648,12 @@ impl<'a> LineStack<'a> { output.merge_frame(pos, frame); } + self.cts.base = self.regions.base.map(Some); self.finished.push(output.constrain(self.cts)); self.regions.next(); self.full = self.regions.current; - self.cts = Constraints::new(self.regions.expand); - self.cts.base = self.regions.base.map(Some); self.size = Size::zero(); + self.cts = Constraints::new(self.regions.expand); } /// Finish the last region and return the built frames. diff --git a/src/library/placed.rs b/src/library/placed.rs index 1ae515309..722e0035f 100644 --- a/src/library/placed.rs +++ b/src/library/placed.rs @@ -43,7 +43,8 @@ impl Layout for PlacedNode { // The pod is the base area of the region because for absolute // placement we don't really care about the already used area (current). let pod = { - let expand = if out_of_flow { Spec::splat(true) } else { regions.expand }; + let finite = regions.base.map(Length::is_finite); + let expand = finite & (regions.expand | out_of_flow); Regions::one(regions.base, regions.base, expand) }; diff --git a/src/library/shape.rs b/src/library/shape.rs index 208ca2a39..2f2f9686d 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 crate::util::RcExt; /// `rect`: A rectangle with optional content. pub fn rect(_: &mut EvalContext, args: &mut Args) -> TypResult { @@ -68,7 +67,13 @@ fn shape_impl( }; // Shorthand for padding. - let padding = Sides::splat(args.named("padding")?.unwrap_or_default()); + let mut padding = args.named::("padding")?.unwrap_or_default(); + + // Padding with this ratio ensures that a rectangular child fits + // perfectly into a circle / an ellipse. + if kind.is_round() { + padding.rel += Relative::new(0.5 - SQRT_2 / 4.0); + } // The shape's contents. let body = args.find::