summaryrefslogtreecommitdiff
path: root/src/library/image.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-11-16 21:32:29 +0100
committerLaurenz <laurmaedje@gmail.com>2021-11-16 21:32:29 +0100
commit9a800daa82833c57eee04e92c701ca9a05a67d3b (patch)
treea2c790f606037319393e9da3150bf58b44d4171d /src/library/image.rs
parent0cdf17216f47312f634d2dea8db237118ede72ce (diff)
Image fit modes
Diffstat (limited to 'src/library/image.rs')
-rw-r--r--src/library/image.rs95
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
+ }
+}