nmd_core/resource/
image_resource.rs

1use std::{fs::{self}, path::PathBuf, str::FromStr};
2
3use getset::{Getters, MutGetters, Setters};
4
5use crate::dossier;
6
7use super::{resource_reference::ResourceReference, source::Source, ResourceError};
8
9
10/// Image resource to manipulate images
11#[derive(Debug, Getters, MutGetters, Setters, Clone)]
12pub struct ImageResource {
13
14    #[getset(get = "pub", get_mut = "pub", set = "pub")]
15    src: Source,
16
17    #[getset(get = "pub", set = "pub")]
18    mime_type: Option<String>,
19
20    #[getset(get = "pub", set = "pub")]
21    id: Option<ResourceReference>,
22
23    #[getset(get = "pub", set = "pub")]
24    caption: Option<String>,
25
26    #[getset(get = "pub", set = "pub")]
27    style: Option<String>,
28}
29
30impl ImageResource {
31
32    pub fn new(src: Source, mime_type: Option<String>, id: Option<ResourceReference>, caption: Option<String>, style: Option<String>) -> Self {
33
34        Self {
35            src,
36            mime_type,
37            id,
38            caption,
39            style
40        }
41    }
42
43    pub fn inferring_id_if_not_set(mut self, document_name: &impl ToString) -> Result<Self, ResourceError> {
44
45        if self.id.is_none() {
46            
47            if let Some(ref caption) = self.caption {
48
49                self.id = Some(ResourceReference::of_internal_from_without_sharp(caption, Some(document_name))?)
50
51            } else {
52
53                match &self.src {
54                    Source::Remote { url } => self.id = Some(ResourceReference::of_url(url.as_str())?),
55                    Source::Local { path } => self.id = Some(ResourceReference::of_asset(path.to_string_lossy().to_string().as_str())?),
56                    Source::Base64String { base64: _ } | Source::Bytes { bytes: _ } => todo!(),     // TODO
57                }
58
59            }
60        }
61
62        Ok(self)
63    } 
64
65    /// Infer mime type from image path using `infer` lib.
66    /// If `src` is not a path error occurs.
67    /// 
68    /// `text/xml` is replaced by `image/svg+xml`
69    pub fn inferring_mime_type(mut self) -> Result<Self, ResourceError> {
70
71        match &self.src {
72            Source::Remote { url } => {
73                log::debug!("impossible to infer mime type of url: {}", url);
74            },
75            Source::Local { path } => {
76                let mime_type = infer::get_from_path(path)?;
77
78                if let Some(t) = mime_type {
79
80                    let mut mime_type = t.mime_type().to_string();
81
82                    // work-around svg+xml
83                    if mime_type.contains("text/xml") {
84                        mime_type = String::from("image/svg+xml");
85                    }
86
87                    self.set_mime_type(Some(mime_type));
88
89                    return Ok(self);
90
91                } else {
92                    return Err(ResourceError::InvalidResourceVerbose(format!("image {:?} mime type not found", self.src)));
93                }
94            },
95            Source::Base64String { base64: _ } | Source::Bytes { bytes: _ } => todo!(),
96        }
97
98        Ok(self)
99    }
100
101    /// Call `inferring_mime_type`, but if this returns an error nothing will be done
102    pub fn inferring_mime_type_or_nothing(self) -> Self {
103        let backup = self.clone();
104
105        match self.inferring_mime_type() {
106            Ok(ok) => ok,
107            Err(err) => {
108                log::warn!("wrong inferring MIME type of image ({:?}): {}", backup.src(), err.to_string());
109
110                backup
111            },
112        }
113    }
114
115    /// Elaborate `src` path if it is relative appending if it doesn't exist dossier `assets` directory
116    pub fn elaborating_relative_path_as_dossier_assets(mut self, base_location: &PathBuf) -> Self {
117
118        match &self.src {
119            Source::Local { path } => {
120
121                let mut base_location: PathBuf = base_location.clone();
122        
123                if !base_location.is_dir() {
124                    base_location = PathBuf::from(base_location.parent().unwrap());
125                }
126        
127                if path.is_relative() {
128
129                    let mut path = base_location.join(path);
130        
131                    if !path.exists() {
132        
133                        let image_file_name = path.file_name().unwrap();
134        
135                        path = base_location.join(dossier::ASSETS_DIR).join(dossier::IMAGES_DIR).join(image_file_name);
136        
137                        if !path.exists() {
138
139                            log::warn!("image src path {:?} not found also with images dossier assets path, try canonicalize path", path);
140
141                            if let Ok(src) = fs::canonicalize(path.clone()) {
142
143                                log::info!("canonicalizing ok: {:?} -> {:?}", path, src);
144
145                                path = src;
146
147                            } else {
148                                log::warn!("canonicalizing fails for {:?}", path);
149                            }
150                        }
151                    }
152        
153                    self.set_src(Source::Local { path });
154                }
155            },
156            _ => (),
157        }
158
159        self
160    }
161
162}
163
164impl FromStr for ImageResource {
165    type Err = ResourceError;
166
167    fn from_str(s: &str) -> Result<Self, Self::Err> {
168        Ok(Self::new(Source::from_str(s)?, None, None, None, None).inferring_mime_type_or_nothing())
169    }
170}
171
172#[cfg(test)]
173mod test {
174
175}