wild_doc_script_image/
lib.rs

1use std::{path::PathBuf, sync::Arc};
2
3use image::{imageops::FilterType, GenericImageView, RgbaImage};
4use parking_lot::Mutex;
5use wild_doc_script::{
6    anyhow::Result, async_trait, IncludeAdaptor, Stack, WildDocScript, WildDocValue,
7};
8
9pub struct WdImage {}
10
11fn var2u32(value: &str, stack: &Stack) -> Option<u32> {
12    if value.starts_with("$") {
13        let value = unsafe { std::str::from_utf8_unchecked(&value.as_bytes()[1..]) };
14        if let Some(WildDocValue::Number(v)) = stack.get(&Arc::new(value.into())) {
15            if let Some(v) = v.as_u64() {
16                return Some(v as u32);
17            }
18        }
19    } else {
20        if let Ok(v) = value.parse::<u32>() {
21            return Some(v);
22        }
23    }
24    None
25}
26
27#[async_trait(?Send)]
28impl<I: IncludeAdaptor + Send> WildDocScript<I> for WdImage {
29    fn new(_: Arc<Mutex<I>>, _: PathBuf, _: &Stack) -> Result<Self>
30    where
31        Self: Sized,
32    {
33        Ok(Self {})
34    }
35
36    async fn evaluate_module(&mut self, _: &str, _: &str, _: &Stack) -> Result<()> {
37        Ok(())
38    }
39
40    async fn eval(&mut self, code: &str, stack: &Stack) -> Result<WildDocValue> {
41        let splited: Vec<&str> = code.split("?").collect();
42        if let Some(image_var) = splited.get(0) {
43            if let Some(WildDocValue::Binary(image)) = stack.get(&Arc::new(image_var.to_string())) {
44                if let Some(param) = splited.get(1) {
45                    if let Ok(mut image) = image::load_from_memory(image) {
46                        let mut w = None;
47                        let mut h = None;
48                        let mut mode: Option<&str> = None;
49                        for p in param.split("&").into_iter() {
50                            let pp: Vec<&str> = p.split("=").collect();
51                            if pp.len() == 2 {
52                                let value = unsafe { pp.get_unchecked(1) };
53                                match unsafe { pp.get_unchecked(0).as_ref() } {
54                                    "w" => {
55                                        w = var2u32(value, stack);
56                                    }
57                                    "h" => {
58                                        h = var2u32(value, stack);
59                                    }
60                                    "m" => {
61                                        if value.starts_with("$") {
62                                            if let Some(WildDocValue::String(v)) =
63                                                stack.get(&Arc::new(
64                                                    unsafe {
65                                                        std::str::from_utf8_unchecked(
66                                                            &value.as_bytes()[1..],
67                                                        )
68                                                    }
69                                                    .into(),
70                                                ))
71                                            {
72                                                mode = Some(v);
73                                            }
74                                        } else {
75                                            mode = Some(value);
76                                        }
77                                    }
78                                    _ => {}
79                                }
80                            }
81                        }
82                        if w.is_some() && h.is_none() {
83                            let r = image.width() as f32 / w.unwrap() as f32;
84                            h = Some((image.height() as f32 * r) as u32);
85                        } else if w.is_none() && h.is_some() {
86                            let r = image.height() as f32 / h.unwrap() as f32;
87                            w = Some((image.width() as f32 * r) as u32);
88                        }
89                        if let (Some(w), Some(h)) = (w, h) {
90                            image = match mode {
91                                Some("fit") => image.resize(w, h, FilterType::Triangle),
92                                Some("cover") => image.resize_to_fill(w, h, FilterType::Triangle),
93                                Some("contain") => {
94                                    let resized = image.resize(w, h, FilterType::Triangle);
95                                    let resized_w = resized.width();
96                                    let resized_h = resized.height();
97                                    if resized_w != w || resized_h != h {
98                                        let mut image = RgbaImage::new(w, h);
99                                        let offset_x = (w - resized_w) / 2;
100                                        let offset_y = (h - resized_h) / 2;
101                                        for y in 0..resized_h {
102                                            for x in 0..resized_w {
103                                                image.put_pixel(
104                                                    x + offset_x,
105                                                    y + offset_y,
106                                                    resized.get_pixel(x, y),
107                                                );
108                                            }
109                                        }
110                                        image.into()
111                                    } else {
112                                        resized
113                                    }
114                                }
115                                Some("crop") => image.crop_imm(
116                                    (image.width() - w) / 2,
117                                    (image.height() - h) / 2,
118                                    w,
119                                    h,
120                                ),
121                                _ => image.resize_exact(w, h, FilterType::Triangle),
122                            };
123                        }
124                        let mut buffer = std::io::Cursor::new(vec![]);
125                        let _ = image.write_to(&mut buffer, image::ImageOutputFormat::WebP);
126                        return Ok(WildDocValue::Binary(buffer.into_inner()));
127                    }
128                } else {
129                    return Ok(WildDocValue::Binary(image.clone()));
130                }
131            }
132        }
133        Ok(WildDocValue::Null)
134    }
135}