summaryrefslogtreecommitdiff
path: root/src/library/image.rs
blob: d9c0a0a7daa6eac3c353f6899fa95e0cdf1b8bb4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
use ::image::GenericImageView;

use super::*;
use crate::env::{ImageResource, ResourceId};
use crate::layout::*;

/// `image`: Insert an image.
///
/// Supports PNG and JPEG files.
///
/// # Positional arguments
/// - Path to image file: of type `string`.
pub fn image(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
    let path = args.require::<Spanned<String>>(ctx, "path to image file");
    let width = args.get(ctx, "width");
    let height = args.get(ctx, "height");

    Value::template("image", move |ctx| {
        if let Some(path) = &path {
            let loaded = ctx.env.resources.load(&path.v, ImageResource::parse);
            if let Some((res, img)) = loaded {
                let dimensions = img.buf.dimensions();
                ctx.push(NodeImage {
                    res,
                    dimensions,
                    width,
                    height,
                    aligns: ctx.state.aligns,
                });
            } else {
                ctx.diag(error!(path.span, "failed to load image"));
            }
        }
    })
}

/// An image node.
#[derive(Debug, Clone, PartialEq)]
struct NodeImage {
    /// How to align this image node in its parent.
    aligns: LayoutAligns,
    /// The resource id of the image file.
    res: ResourceId,
    /// The pixel dimensions of the image.
    dimensions: (u32, u32),
    /// The fixed width, if any.
    width: Option<Linear>,
    /// The fixed height, if any.
    height: Option<Linear>,
}

impl Layout for NodeImage {
    fn layout(&self, _: &mut LayoutContext, areas: &Areas) -> Layouted {
        let Areas { current, full, .. } = areas;

        let pixel_width = self.dimensions.0 as f64;
        let pixel_height = self.dimensions.1 as f64;
        let pixel_ratio = pixel_width / pixel_height;

        let width = self.width.map(|w| w.resolve(full.width));
        let height = self.height.map(|w| w.resolve(full.height));

        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) => {
                let ratio = current.width / current.height;
                if ratio < pixel_ratio && current.width.is_finite() {
                    Size::new(current.width, current.width / pixel_ratio)
                } else if current.height.is_finite() {
                    // TODO: Fix issue with line spacing.
                    Size::new(current.height * pixel_ratio, current.height)
                } else {
                    // Totally unbounded area, we have to make up something.
                    Size::new(Length::pt(pixel_width), Length::pt(pixel_height))
                }
            }
        };

        let mut frame = Frame::new(size);
        frame.push(Point::ZERO, Element::Image(Image { res: self.res, size }));

        Layouted::Frame(frame, self.aligns)
    }
}

impl From<NodeImage> for NodeAny {
    fn from(image: NodeImage) -> Self {
        Self::new(image)
    }
}