diff options
Diffstat (limited to 'src/library')
| -rw-r--r-- | src/library/deco.rs | 6 | ||||
| -rw-r--r-- | src/library/grid.rs | 3 | ||||
| -rw-r--r-- | src/library/image.rs | 95 | ||||
| -rw-r--r-- | src/library/pad.rs | 4 | ||||
| -rw-r--r-- | src/library/shape.rs | 29 | ||||
| -rw-r--r-- | src/library/sized.rs | 14 | ||||
| -rw-r--r-- | src/library/transform.rs | 2 |
7 files changed, 88 insertions, 65 deletions
diff --git a/src/library/deco.rs b/src/library/deco.rs index 6ef5a97b..2722fd68 100644 --- a/src/library/deco.rs +++ b/src/library/deco.rs @@ -102,9 +102,9 @@ pub enum LineKind { impl LineDecoration { /// Apply a line decoration to a all text elements in a frame. pub fn apply(&self, ctx: &LayoutContext, frame: &mut Frame) { - for i in 0 .. frame.children.len() { - let (pos, child) = &frame.children[i]; - if let FrameChild::Element(Element::Text(text)) = child { + for i in 0 .. frame.elements.len() { + let (pos, child) = &frame.elements[i]; + if let Element::Text(text) = child { let face = ctx.fonts.get(text.face_id); let metrics = match self.kind { LineKind::Underline => face.underline, diff --git a/src/library/grid.rs b/src/library/grid.rs index c9accffb..f247c7f4 100644 --- a/src/library/grid.rs +++ b/src/library/grid.rs @@ -167,7 +167,8 @@ impl<'a> GridLayouter<'a> { cols.pop(); rows.pop(); - // We use the regions only for auto row measurement and constraints. + // We use the regions for auto row measurement. Since at that moment, + // columns are already sized, we can enable horizontal expansion. let expand = regions.expand; regions.expand = Spec::new(true, false); diff --git a/src/library/image.rs b/src/library/image.rs index f93d4b54..ea9a050c 100644 --- a/src/library/image.rs +++ b/src/library/image.rs @@ -9,7 +9,9 @@ pub fn image(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { let path = args.expect::<Spanned<EcoString>>("path to image file")?; let width = args.named("width")?; let height = args.named("height")?; + let fit = args.named("fit")?.unwrap_or_default(); + // Load the image. let full = ctx.make_path(&path.v); let id = ctx.images.load(&full).map_err(|err| { Error::boxed(path.span, match err.kind() { @@ -18,10 +20,8 @@ pub fn image(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { }) })?; - Ok(Value::Template(Template::from_inline(move |_| ImageNode { - id, - width, - height, + Ok(Value::Template(Template::from_inline(move |_| { + ImageNode { id, fit }.pack().sized(width, height) }))) } @@ -30,10 +30,8 @@ pub fn image(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { pub struct ImageNode { /// The id of the image file. pub id: ImageId, - /// The fixed width, if any. - pub width: Option<Linear>, - /// The fixed height, if any. - pub height: Option<Linear>, + /// How the image should adjust itself to a given area. + pub fit: ImageFit, } impl Layout for ImageNode { @@ -42,36 +40,77 @@ impl Layout for ImageNode { ctx: &mut LayoutContext, regions: &Regions, ) -> Vec<Constrained<Rc<Frame>>> { + let &Regions { current, expand, .. } = regions; + let img = ctx.images.get(self.id); - let pixel_size = Spec::new(img.width() as f64, img.height() as f64); - let pixel_ratio = pixel_size.x / pixel_size.y; + let pixel_w = img.width() as f64; + let pixel_h = img.height() as f64; - let width = self.width.map(|w| w.resolve(regions.base.w)); - let height = self.height.map(|w| w.resolve(regions.base.h)); + let region_ratio = current.w / current.h; + let pixel_ratio = pixel_w / pixel_h; + let wide = region_ratio < pixel_ratio; - let mut cts = Constraints::new(regions.expand); - cts.set_base_if_linear(regions.base, Spec::new(self.width, self.height)); + // 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.w.safe_div(pixel_ratio)) + } else if current.h.is_finite() { + Size::new(current.h * pixel_ratio, current.h) + } else { + Size::new(Length::pt(pixel_w), Length::pt(pixel_h)) + }; - let size = match (width, height) { - (Some(width), Some(height)) => Size::new(width, height), - (Some(width), None) => Size::new(width, width / pixel_ratio), - (None, Some(height)) => Size::new(height * pixel_ratio, height), - (None, None) => { - cts.exact.x = Some(regions.current.w); - if regions.current.w.is_finite() { - // Fit to width. - Size::new(regions.current.w, regions.current.w / pixel_ratio) + // The actual size of the fitted image. + let size = match self.fit { + ImageFit::Contain | ImageFit::Cover => { + if wide == (self.fit == ImageFit::Contain) { + Size::new(canvas.w, canvas.w / pixel_ratio) } else { - // Unbounded width, we have to make up something, - // so it is 1pt per pixel. - pixel_size.map(Length::pt).to_size() + Size::new(canvas.h * pixel_ratio, canvas.h) } } + ImageFit::Stretch => canvas, }; - let mut frame = Frame::new(size, size.h); - frame.push(Point::zero(), Element::Image(self.id, size)); + // The position of the image so that it is centered in the canvas. + let mut frame = Frame::new(canvas, canvas.h); + frame.clips = self.fit == ImageFit::Cover; + frame.push( + Point::new((canvas.w - size.w) / 2.0, (canvas.h - size.h) / 2.0), + Element::Image(self.id, size), + ); + let mut cts = Constraints::new(regions.expand); + cts.exact = regions.current.to_spec().map(Some); vec![frame.constrain(cts)] } } + +/// How an image should adjust itself to a given area. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum ImageFit { + /// The image should be fully contained in the area. + Contain, + /// The image should completely cover the area. + Cover, + /// The image should be stretched so that it exactly fills the area. + Stretch, +} + +castable! { + ImageFit, + Expected: "string", + Value::Str(string) => match string.as_str() { + "contain" => Self::Contain, + "cover" => Self::Cover, + "stretch" => Self::Stretch, + _ => Err(r#"expected "contain", "cover" or "stretch""#)?, + }, +} + +impl Default for ImageFit { + fn default() -> Self { + Self::Contain + } +} diff --git a/src/library/pad.rs b/src/library/pad.rs index 1ec1b4a2..a1c8c6f9 100644 --- a/src/library/pad.rs +++ b/src/library/pad.rs @@ -46,9 +46,7 @@ impl Layout for PadNode { frames.iter_mut().zip(regions.iter()) { fn solve_axis(length: Length, padding: Linear) -> Length { - (length + padding.abs) - .div_finite(1.0 - padding.rel.get()) - .unwrap_or_default() + (length + padding.abs).safe_div(1.0 - padding.rel.get()) } // Solve for the size `padded` that satisfies (approximately): diff --git a/src/library/shape.rs b/src/library/shape.rs index 407d5974..dbd6eea7 100644 --- a/src/library/shape.rs +++ b/src/library/shape.rs @@ -1,7 +1,7 @@ use std::f64::consts::SQRT_2; use super::prelude::*; -use super::{PadNode, SizedNode}; +use super::PadNode; use crate::util::RcExt; /// `rect`: A rectangle with optional content. @@ -65,20 +65,13 @@ fn shape_impl( let fill = fill.unwrap_or(Color::Rgba(RgbaColor::gray(175))); Value::Template(Template::from_inline(move |style| { - let shape = Layout::pack(ShapeNode { + ShapeNode { kind, fill: Some(Paint::Color(fill)), child: body.as_ref().map(|body| body.pack(style)), - }); - - if width.is_some() || height.is_some() { - Layout::pack(SizedNode { - sizing: Spec::new(width, height), - child: shape, - }) - } else { - shape } + .pack() + .sized(width, height) })) } @@ -112,7 +105,7 @@ impl Layout for ShapeNode { ctx: &mut LayoutContext, regions: &Regions, ) -> Vec<Constrained<Rc<Frame>>> { - // Layout. + // Layout, either with or without child. let mut frame = if let Some(child) = &self.child { let mut node: &dyn Layout = child; @@ -141,15 +134,18 @@ impl Layout for ShapeNode { frames = node.layout(ctx, &pod); } - // Validate and set constraints. - assert_eq!(frames.len(), 1); + // Extract the frame. Rc::take(frames.into_iter().next().unwrap().item) } else { + // When there's no child, fill the area if expansion is on, + // otherwise fall back to a default size. let default = Length::pt(30.0); let size = Size::new( if regions.expand.x { regions.current.w } else { + // For rectangle and ellipse, the default shape is a bit + // wider than high. match self.kind { ShapeKind::Square | ShapeKind::Circle => default, ShapeKind::Rect | ShapeKind::Ellipse => 1.5 * default, @@ -161,7 +157,7 @@ impl Layout for ShapeNode { Frame::new(size, size.h) }; - // Add background shape if desired. + // Add background fill if desired. if let Some(fill) = self.fill { let (pos, geometry) = match self.kind { ShapeKind::Square | ShapeKind::Rect => { @@ -175,11 +171,10 @@ impl Layout for ShapeNode { frame.prepend(pos, Element::Geometry(geometry, fill)); } - // Generate tight constraints for now. + // Return tight constraints for now. let mut cts = Constraints::new(regions.expand); cts.exact = regions.current.to_spec().map(Some); cts.base = regions.base.to_spec().map(Some); - vec![frame.constrain(cts)] } } diff --git a/src/library/sized.rs b/src/library/sized.rs index 686d79b9..6394b0f4 100644 --- a/src/library/sized.rs +++ b/src/library/sized.rs @@ -6,12 +6,7 @@ pub fn box_(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { let height = args.named("height")?; let body: Template = args.find().unwrap_or_default(); Ok(Value::Template(Template::from_inline(move |style| { - let child = body.pack(style); - if width.is_some() || height.is_some() { - Layout::pack(SizedNode { sizing: Spec::new(width, height), child }) - } else { - child - } + body.pack(style).sized(width, height) }))) } @@ -21,12 +16,7 @@ pub fn block(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { let height = args.named("height")?; let body: Template = args.find().unwrap_or_default(); Ok(Value::Template(Template::from_block(move |style| { - let child = body.pack(style); - if width.is_some() || height.is_some() { - Layout::pack(SizedNode { sizing: Spec::new(width, height), child }) - } else { - child - } + body.pack(style).sized(width, height) }))) } diff --git a/src/library/transform.rs b/src/library/transform.rs index 85d65703..d0a27622 100644 --- a/src/library/transform.rs +++ b/src/library/transform.rs @@ -36,7 +36,7 @@ impl Layout for MoveNode { self.offset.y.map(|y| y.resolve(base.h)).unwrap_or_default(), ); - for (point, _) in &mut Rc::make_mut(frame).children { + for (point, _) in &mut Rc::make_mut(frame).elements { *point += offset; } } |
