Skip to main content

ppt_rs/parts/
image.rs

1//! Image part
2//!
3//! Represents an image embedded in the presentation.
4
5use super::base::{ContentType, Part, PartType};
6use crate::exc::PptxError;
7use std::path::Path;
8
9/// Image part (ppt/media/imageN.ext)
10#[derive(Debug, Clone)]
11pub struct ImagePart {
12    path: String,
13    image_number: usize,
14    format: String,
15    data: Vec<u8>,
16    width: u32,
17    height: u32,
18}
19
20impl ImagePart {
21    /// Create a new image part
22    pub fn new(image_number: usize, format: &str, data: Vec<u8>) -> Self {
23        let format_lower = format.to_lowercase();
24        let ext = match format_lower.as_str() {
25            "jpeg" => "jpg",
26            other => other,
27        };
28
29        ImagePart {
30            path: format!("ppt/media/image{}.{}", image_number, ext),
31            image_number,
32            format: format_lower,
33            data,
34            width: 0,
35            height: 0,
36        }
37    }
38
39    /// Create from file path
40    pub fn from_file(image_number: usize, file_path: &str) -> Result<Self, PptxError> {
41        let data = std::fs::read(file_path)?;
42        let format = Path::new(file_path)
43            .extension()
44            .and_then(|e| e.to_str())
45            .unwrap_or("png")
46            .to_lowercase();
47
48        Ok(Self::new(image_number, &format, data))
49    }
50
51    /// Get image number
52    pub fn image_number(&self) -> usize {
53        self.image_number
54    }
55
56    /// Get format
57    pub fn format(&self) -> &str {
58        &self.format
59    }
60
61    /// Get image data
62    pub fn data(&self) -> &[u8] {
63        &self.data
64    }
65
66    /// Set dimensions
67    pub fn set_dimensions(&mut self, width: u32, height: u32) {
68        self.width = width;
69        self.height = height;
70    }
71
72    /// Get width
73    pub fn width(&self) -> u32 {
74        self.width
75    }
76
77    /// Get height
78    pub fn height(&self) -> u32 {
79        self.height
80    }
81
82    /// Get MIME type
83    pub fn mime_type(&self) -> &'static str {
84        match self.format.as_str() {
85            "png" => "image/png",
86            "jpg" | "jpeg" => "image/jpeg",
87            "gif" => "image/gif",
88            "bmp" => "image/bmp",
89            "tiff" | "tif" => "image/tiff",
90            _ => "application/octet-stream",
91        }
92    }
93
94    /// Get file extension
95    pub fn extension(&self) -> &str {
96        match self.format.as_str() {
97            "jpeg" => "jpg",
98            other => other,
99        }
100    }
101
102    /// Get relative path for relationships
103    pub fn rel_target(&self) -> String {
104        format!("../media/image{}.{}", self.image_number, self.extension())
105    }
106}
107
108impl Part for ImagePart {
109    fn path(&self) -> &str {
110        &self.path
111    }
112
113    fn part_type(&self) -> PartType {
114        PartType::Image
115    }
116
117    fn content_type(&self) -> ContentType {
118        ContentType::Image(self.format.clone())
119    }
120
121    fn to_xml(&self) -> Result<String, PptxError> {
122        // Images don't have XML content, they're binary
123        Err(PptxError::InvalidOperation(
124            "Images don't have XML content".to_string(),
125        ))
126    }
127
128    fn from_xml(_xml: &str) -> Result<Self, PptxError> {
129        Err(PptxError::InvalidOperation(
130            "Cannot create image from XML".to_string(),
131        ))
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn test_image_part_new() {
141        let data = vec![0x89, 0x50, 0x4E, 0x47]; // PNG header
142        let part = ImagePart::new(1, "png", data);
143
144        assert_eq!(part.image_number(), 1);
145        assert_eq!(part.format(), "png");
146        assert_eq!(part.path(), "ppt/media/image1.png");
147    }
148
149    #[test]
150    fn test_image_mime_type() {
151        let part = ImagePart::new(1, "jpeg", vec![]);
152        assert_eq!(part.mime_type(), "image/jpeg");
153
154        let part2 = ImagePart::new(2, "png", vec![]);
155        assert_eq!(part2.mime_type(), "image/png");
156    }
157
158    #[test]
159    fn test_image_rel_target() {
160        let part = ImagePart::new(3, "png", vec![]);
161        assert_eq!(part.rel_target(), "../media/image3.png");
162    }
163}