diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-11-16 21:32:29 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-11-16 21:32:29 +0100 |
| commit | 9a800daa82833c57eee04e92c701ca9a05a67d3b (patch) | |
| tree | a2c790f606037319393e9da3150bf58b44d4171d /src/library/image.rs | |
| parent | 0cdf17216f47312f634d2dea8db237118ede72ce (diff) | |
Image fit modes
Diffstat (limited to 'src/library/image.rs')
| -rw-r--r-- | src/library/image.rs | 95 |
1 files changed, 67 insertions, 28 deletions
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 + } +} |
