easypdf_core/model/
image_data.rs1#[derive(Clone, Debug, PartialEq)]
19pub struct ImageData {
20 format: ImageFormat,
22 bytes: Option<Vec<u8>>,
24 caption: Option<String>,
26 alt_text: Option<String>,
28 width_pt: Option<f64>,
30 height_pt: Option<f64>,
32}
33
34impl ImageData {
35 #[must_use]
37 pub const fn new(format: ImageFormat) -> Self {
38 Self {
39 format,
40 bytes: None,
41 caption: None,
42 alt_text: None,
43 width_pt: None,
44 height_pt: None,
45 }
46 }
47
48 #[must_use]
50 pub fn with_bytes(mut self, bytes: Vec<u8>) -> Self {
51 self.bytes = Some(bytes);
52 self
53 }
54
55 #[must_use]
57 pub fn with_caption(mut self, caption: impl Into<String>) -> Self {
58 self.caption = Some(caption.into());
59 self
60 }
61
62 #[must_use]
64 pub fn with_alt_text(mut self, alt: impl Into<String>) -> Self {
65 self.alt_text = Some(alt.into());
66 self
67 }
68
69 #[must_use]
71 pub const fn with_dimensions(mut self, width_pt: f64, height_pt: f64) -> Self {
72 self.width_pt = Some(width_pt);
73 self.height_pt = Some(height_pt);
74 self
75 }
76
77 #[must_use]
79 pub const fn format(&self) -> ImageFormat {
80 self.format
81 }
82
83 #[must_use]
85 pub fn bytes(&self) -> Option<&[u8]> {
86 self.bytes.as_deref()
87 }
88
89 #[must_use]
91 pub fn caption(&self) -> Option<&str> {
92 self.caption.as_deref()
93 }
94
95 #[must_use]
97 pub fn alt_text(&self) -> Option<&str> {
98 self.alt_text.as_deref()
99 }
100
101 #[must_use]
103 pub const fn width_pt(&self) -> Option<f64> {
104 self.width_pt
105 }
106
107 #[must_use]
109 pub const fn height_pt(&self) -> Option<f64> {
110 self.height_pt
111 }
112}
113
114#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
116#[non_exhaustive]
117pub enum ImageFormat {
118 Png,
120 Jpeg,
122 Gif,
124 Bmp,
126 Tiff,
128 Svg,
130 Unknown,
132}
133
134impl ImageFormat {
135 #[must_use]
137 pub const fn extension(self) -> &'static str {
138 match self {
139 Self::Png => "png",
140 Self::Jpeg => "jpg",
141 Self::Gif => "gif",
142 Self::Bmp => "bmp",
143 Self::Tiff => "tiff",
144 Self::Svg => "svg",
145 Self::Unknown => "bin",
146 }
147 }
148}
149
150#[cfg(test)]
151#[allow(clippy::uninlined_format_args, clippy::float_cmp)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn new_creates_with_format_only() {
157 let data = ImageData::new(ImageFormat::Png);
158 assert_eq!(data.format(), ImageFormat::Png);
159 assert!(data.bytes().is_none());
160 assert!(data.caption().is_none());
161 assert!(data.alt_text().is_none());
162 assert!(data.width_pt().is_none());
163 assert!(data.height_pt().is_none());
164 }
165
166 #[test]
167 fn with_bytes_sets_bytes() {
168 let data = ImageData::new(ImageFormat::Jpeg).with_bytes(vec![0xFF, 0xD8, 0xFF]);
169 assert_eq!(data.bytes(), Some([0xFF, 0xD8, 0xFF].as_slice()));
170 }
171
172 #[test]
173 fn with_caption_sets_caption() {
174 let data = ImageData::new(ImageFormat::Png).with_caption("Logo");
175 assert_eq!(data.caption(), Some("Logo"));
176 }
177
178 #[test]
179 fn with_alt_text_sets_alt_text() {
180 let data = ImageData::new(ImageFormat::Gif).with_alt_text("A cat");
181 assert_eq!(data.alt_text(), Some("A cat"));
182 }
183
184 #[test]
185 fn with_dimensions_sets_both() {
186 let data = ImageData::new(ImageFormat::Bmp).with_dimensions(200.0, 100.0);
187 assert_eq!(data.width_pt(), Some(200.0));
188 assert_eq!(data.height_pt(), Some(100.0));
189 }
190
191 #[test]
192 fn builder_chaining_sets_all_fields() {
193 let data = ImageData::new(ImageFormat::Svg)
194 .with_bytes(vec![1, 2, 3])
195 .with_caption("SVG Icon")
196 .with_alt_text("icon")
197 .with_dimensions(64.0, 64.0);
198 assert_eq!(data.format(), ImageFormat::Svg);
199 assert_eq!(data.bytes(), Some([1, 2, 3].as_slice()));
200 assert_eq!(data.caption(), Some("SVG Icon"));
201 assert_eq!(data.alt_text(), Some("icon"));
202 assert_eq!(data.width_pt(), Some(64.0));
203 assert_eq!(data.height_pt(), Some(64.0));
204 }
205
206 #[test]
207 fn clone_preserves_all_fields() {
208 let data = ImageData::new(ImageFormat::Tiff)
209 .with_bytes(vec![10, 20])
210 .with_caption("Photo")
211 .with_dimensions(800.0, 600.0);
212 let cloned = data.clone();
213 assert_eq!(data, cloned);
214 }
215
216 #[test]
217 fn partial_eq_works() {
218 let a = ImageData::new(ImageFormat::Png).with_caption("A");
219 let b = ImageData::new(ImageFormat::Png).with_caption("A");
220 let c = ImageData::new(ImageFormat::Png).with_caption("B");
221 assert_eq!(a, b);
222 assert_ne!(a, c);
223 }
224
225 #[test]
226 fn extension_png() {
227 assert_eq!(ImageFormat::Png.extension(), "png");
228 }
229
230 #[test]
231 fn extension_jpeg() {
232 assert_eq!(ImageFormat::Jpeg.extension(), "jpg");
233 }
234
235 #[test]
236 fn extension_gif() {
237 assert_eq!(ImageFormat::Gif.extension(), "gif");
238 }
239
240 #[test]
241 fn extension_bmp() {
242 assert_eq!(ImageFormat::Bmp.extension(), "bmp");
243 }
244
245 #[test]
246 fn extension_tiff() {
247 assert_eq!(ImageFormat::Tiff.extension(), "tiff");
248 }
249
250 #[test]
251 fn extension_svg() {
252 assert_eq!(ImageFormat::Svg.extension(), "svg");
253 }
254
255 #[test]
256 fn extension_unknown() {
257 assert_eq!(ImageFormat::Unknown.extension(), "bin");
258 }
259
260 #[test]
261 fn image_format_eq() {
262 assert_eq!(ImageFormat::Png, ImageFormat::Png);
263 assert_ne!(ImageFormat::Png, ImageFormat::Jpeg);
264 }
265}