1use fop_types::{Length, Result};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum ImageFormat {
10 PNG,
12
13 JPEG,
15
16 Unknown,
18}
19
20#[derive(Debug, Clone)]
22pub struct ImageInfo {
23 pub format: ImageFormat,
25
26 pub width_px: u32,
28
29 pub height_px: u32,
31
32 pub bits_per_component: u8,
34
35 pub color_space: String,
37
38 pub data: Vec<u8>,
40}
41
42impl ImageInfo {
43 pub fn from_bytes(data: &[u8]) -> Result<Self> {
45 let format = Self::detect_format(data)?;
47
48 match format {
49 ImageFormat::PNG => Self::parse_png(data),
50 ImageFormat::JPEG => Self::parse_jpeg(data),
51 ImageFormat::Unknown => Err(fop_types::FopError::Generic(
52 "Unknown image format".to_string(),
53 )),
54 }
55 }
56
57 fn detect_format(data: &[u8]) -> Result<ImageFormat> {
59 if data.len() < 8 {
60 return Ok(ImageFormat::Unknown);
61 }
62
63 if data[0..8] == [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A] {
65 return Ok(ImageFormat::PNG);
66 }
67
68 if data.len() >= 3 && data[0..3] == [0xFF, 0xD8, 0xFF] {
70 return Ok(ImageFormat::JPEG);
71 }
72
73 Ok(ImageFormat::Unknown)
74 }
75
76 fn parse_png(data: &[u8]) -> Result<Self> {
78 use std::io::Cursor;
79 let decoder = png::Decoder::new(Cursor::new(data));
81 let reader = decoder
82 .read_info()
83 .map_err(|e| fop_types::FopError::Generic(format!("PNG decode error: {}", e)))?;
84
85 let info = reader.info();
86 let width_px = info.width;
87 let height_px = info.height;
88 let color_type = info.color_type;
89
90 let color_space = match color_type {
92 png::ColorType::Rgb | png::ColorType::Rgba => "DeviceRGB",
93 png::ColorType::Grayscale | png::ColorType::GrayscaleAlpha => "DeviceGray",
94 png::ColorType::Indexed => {
95 return Err(fop_types::FopError::Generic(
96 "Indexed PNG images are not supported".to_string(),
97 ))
98 }
99 };
100
101 Ok(Self {
102 format: ImageFormat::PNG,
103 width_px,
104 height_px,
105 bits_per_component: 8,
106 color_space: color_space.to_string(),
107 data: data.to_vec(),
108 })
109 }
110
111 fn parse_jpeg(data: &[u8]) -> Result<Self> {
113 use jpeg_decoder::Decoder;
114 use std::io::Cursor;
115
116 let mut decoder = Decoder::new(Cursor::new(data));
117 decoder.read_info().map_err(|e| {
118 fop_types::FopError::Generic(format!("Failed to read JPEG info: {}", e))
119 })?;
120
121 let metadata = decoder.info().ok_or_else(|| {
122 fop_types::FopError::Generic("JPEG decoder info not available".to_string())
123 })?;
124
125 let color_space = match metadata.pixel_format {
126 jpeg_decoder::PixelFormat::L8 => "DeviceGray",
127 jpeg_decoder::PixelFormat::L16 => "DeviceGray",
128 jpeg_decoder::PixelFormat::RGB24 => "DeviceRGB",
129 jpeg_decoder::PixelFormat::CMYK32 => "DeviceCMYK",
130 };
131
132 Ok(Self {
133 format: ImageFormat::JPEG,
134 width_px: metadata.width as u32,
135 height_px: metadata.height as u32,
136 bits_per_component: 8,
137 color_space: color_space.to_string(),
138 data: data.to_vec(),
139 })
140 }
141
142 pub fn calculate_display_size(
144 &self,
145 max_width: Length,
146 max_height: Length,
147 ) -> (Length, Length) {
148 let aspect_ratio = self.width_px as f64 / self.height_px as f64;
149
150 let width_fit = max_width;
152 let height_for_width = Length::from_pt(width_fit.to_pt() / aspect_ratio);
153
154 if height_for_width <= max_height {
155 return (width_fit, height_for_width);
156 }
157
158 let height_fit = max_height;
160 let width_for_height = Length::from_pt(height_fit.to_pt() * aspect_ratio);
161
162 (width_for_height, height_fit)
163 }
164
165 pub fn dpi_at_size(&self, display_width: Length) -> f64 {
167 72.0 * self.width_px as f64 / display_width.to_pt()
168 }
169}
170
171#[derive(Debug, Clone)]
173pub struct ImagePlacement {
174 pub x: Length,
176
177 pub y: Length,
179
180 pub width: Length,
182
183 pub height: Length,
185
186 pub image: ImageInfo,
188}
189
190impl ImagePlacement {
191 pub fn new(x: Length, y: Length, width: Length, height: Length, image: ImageInfo) -> Self {
193 Self {
194 x,
195 y,
196 width,
197 height,
198 image,
199 }
200 }
201
202 pub fn auto_size(
204 x: Length,
205 y: Length,
206 max_width: Length,
207 max_height: Length,
208 image: ImageInfo,
209 ) -> Self {
210 let (width, height) = image.calculate_display_size(max_width, max_height);
211 Self {
212 x,
213 y,
214 width,
215 height,
216 image,
217 }
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 #[test]
226 fn test_detect_png() {
227 let png_header = vec![0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
228 let format = ImageInfo::detect_format(&png_header).expect("test: should succeed");
229 assert_eq!(format, ImageFormat::PNG);
230 }
231
232 #[test]
233 fn test_detect_jpeg() {
234 let jpeg_header = vec![0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46]; let format = ImageInfo::detect_format(&jpeg_header).expect("test: should succeed");
236 assert_eq!(format, ImageFormat::JPEG);
237 }
238
239 #[test]
240 fn test_detect_unknown() {
241 let unknown = vec![0x00, 0x00, 0x00, 0x00];
242 let format = ImageInfo::detect_format(&unknown).expect("test: should succeed");
243 assert_eq!(format, ImageFormat::Unknown);
244 }
245
246 #[test]
247 fn test_aspect_ratio_fit_by_width() {
248 let image = ImageInfo {
249 format: ImageFormat::PNG,
250 width_px: 200,
251 height_px: 100, bits_per_component: 8,
253 color_space: "DeviceRGB".to_string(),
254 data: Vec::new(),
255 };
256
257 let (w, h) = image.calculate_display_size(Length::from_pt(100.0), Length::from_pt(100.0));
258
259 assert_eq!(w, Length::from_pt(100.0));
261 assert_eq!(h, Length::from_pt(50.0));
262 }
263
264 #[test]
265 fn test_aspect_ratio_fit_by_height() {
266 let image = ImageInfo {
267 format: ImageFormat::PNG,
268 width_px: 100,
269 height_px: 200, bits_per_component: 8,
271 color_space: "DeviceRGB".to_string(),
272 data: Vec::new(),
273 };
274
275 let (w, h) = image.calculate_display_size(Length::from_pt(100.0), Length::from_pt(100.0));
276
277 assert_eq!(w, Length::from_pt(50.0));
279 assert_eq!(h, Length::from_pt(100.0));
280 }
281
282 #[test]
283 fn test_dpi_calculation() {
284 let image = ImageInfo {
285 format: ImageFormat::PNG,
286 width_px: 720,
287 height_px: 720,
288 bits_per_component: 8,
289 color_space: "DeviceRGB".to_string(),
290 data: Vec::new(),
291 };
292
293 let dpi = image.dpi_at_size(Length::from_pt(72.0));
295 assert_eq!(dpi, 720.0);
296 }
297
298 #[test]
299 fn test_image_placement() {
300 let image = ImageInfo {
301 format: ImageFormat::JPEG,
302 width_px: 100,
303 height_px: 100,
304 bits_per_component: 8,
305 color_space: "DeviceRGB".to_string(),
306 data: Vec::new(),
307 };
308
309 let placement = ImagePlacement::new(
310 Length::from_pt(10.0),
311 Length::from_pt(20.0),
312 Length::from_pt(50.0),
313 Length::from_pt(50.0),
314 image,
315 );
316
317 assert_eq!(placement.x, Length::from_pt(10.0));
318 assert_eq!(placement.width, Length::from_pt(50.0));
319 }
320
321 #[test]
322 fn test_auto_size_placement() {
323 let image = ImageInfo {
324 format: ImageFormat::PNG,
325 width_px: 200,
326 height_px: 100,
327 bits_per_component: 8,
328 color_space: "DeviceRGB".to_string(),
329 data: Vec::new(),
330 };
331
332 let placement = ImagePlacement::auto_size(
333 Length::ZERO,
334 Length::ZERO,
335 Length::from_pt(100.0),
336 Length::from_pt(100.0),
337 image,
338 );
339
340 assert_eq!(placement.width, Length::from_pt(100.0));
341 assert_eq!(placement.height, Length::from_pt(50.0));
342 }
343}