1use crate::error::Result;
4use crate::parts::presentation::PresentationPart;
5use crate::slide::Slides;
6use std::io::{Read, Seek, Write};
7use super::{dimensions, open, save};
8
9pub struct Presentation {
14 part: PresentationPart,
15 package: crate::opc::package::Package,
17}
18
19impl Presentation {
20 pub fn new() -> Result<Self> {
22 let part = PresentationPart::new()?;
23 let package = crate::opc::package::Package::new();
24 Ok(Self { part, package })
25 }
26
27 pub fn open<R: Read + Seek>(reader: R) -> Result<Self> {
29 use crate::opc::package::Package;
30
31 let package = Package::open(reader)?;
33
34 let part = open::open_from_package(&package)?;
36
37 let package = Package::new(); Ok(Self { part, package })
40 }
41
42 pub fn save<W: Write + Seek>(&mut self, writer: W) -> Result<()> {
44 save::save(&mut self.part, &mut self.package, writer)
45 }
46
47 pub fn save_to_file<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<()> {
49 use std::io::Cursor;
50 let mut cursor = Cursor::new(Vec::new());
51 self.save(&mut cursor)?;
52 let data = cursor.into_inner();
53 std::fs::write(path, data)?;
54 Ok(())
55 }
56
57 pub fn slides(&mut self) -> Slides {
59 Slides::new(&mut self.part)
60 }
61
62 pub fn package_mut(&mut self) -> &mut crate::opc::package::Package {
65 &mut self.package
66 }
67
68 pub fn part(&self) -> &PresentationPart {
70 &self.part
71 }
72
73 pub fn part_mut(&mut self) -> &mut PresentationPart {
75 &mut self.part
76 }
77
78 pub fn core_properties(&self) -> Result<crate::parts::coreprops::CorePropertiesPart> {
80 self.part.core_properties()
81 }
82
83 pub fn slide_width(&self) -> Option<u32> {
85 dimensions::slide_width(&self.part)
86 }
87
88 pub fn set_slide_width(&mut self, width: u32) -> Result<()> {
90 dimensions::set_slide_width(&mut self.part, width)
91 }
92
93 pub fn slide_height(&self) -> Option<u32> {
95 dimensions::slide_height(&self.part)
96 }
97
98 pub fn set_slide_height(&mut self, height: u32) -> Result<()> {
100 dimensions::set_slide_height(&mut self.part, height)
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use std::io::Cursor;
108
109 #[test]
110 fn test_presentation_new() {
111 let prs = Presentation::new();
112 assert!(prs.is_ok());
113 let prs = prs.unwrap();
114 assert_eq!(prs.slide_width(), Some(9144000));
115 assert_eq!(prs.slide_height(), Some(6858000));
116 }
117
118 #[test]
119 fn test_presentation_save_to_writer() {
120 let mut prs = Presentation::new().unwrap();
121 let mut cursor = Cursor::new(Vec::new());
122 let result = prs.save(&mut cursor);
123 assert!(result.is_ok());
124
125 let data = cursor.into_inner();
127 assert!(!data.is_empty());
128
129 let cursor = Cursor::new(&data);
131 let archive = zip::ZipArchive::new(cursor);
132 assert!(archive.is_ok());
133 }
134
135 #[test]
136 fn test_presentation_save_to_file() {
137 let mut prs = Presentation::new().unwrap();
138 let test_path = "test_output/test_save.pptx";
139
140 std::fs::create_dir_all("test_output").ok();
142
143 let result = prs.save_to_file(test_path);
144 assert!(result.is_ok());
145
146 assert!(std::path::Path::new(test_path).exists());
148
149 let file = std::fs::File::open(test_path);
151 assert!(file.is_ok());
152 let archive = zip::ZipArchive::new(file.unwrap());
153 assert!(archive.is_ok());
154
155 std::fs::remove_file(test_path).ok();
157 }
158
159 #[test]
160 fn test_presentation_save_contains_content_types() {
161 let mut prs = Presentation::new().unwrap();
162 let mut cursor = Cursor::new(Vec::new());
163 prs.save(&mut cursor).unwrap();
164
165 let data = cursor.into_inner();
166 let cursor = Cursor::new(&data);
167 let mut archive = zip::ZipArchive::new(cursor).unwrap();
168
169 let content_types = archive.by_name("[Content_Types].xml");
171 assert!(content_types.is_ok());
172
173 let mut content_types_file = content_types.unwrap();
174 let mut content = String::new();
175 std::io::Read::read_to_string(&mut content_types_file, &mut content).unwrap();
176 assert!(content.contains("Types"));
177 assert!(content.contains("application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"));
178 }
179
180 #[test]
181 fn test_presentation_save_contains_presentation_xml() {
182 let mut prs = Presentation::new().unwrap();
183 let mut cursor = Cursor::new(Vec::new());
184 prs.save(&mut cursor).unwrap();
185
186 let data = cursor.into_inner();
187 let cursor = Cursor::new(&data);
188 let mut archive = zip::ZipArchive::new(cursor).unwrap();
189
190 let presentation_xml = archive.by_name("ppt/presentation.xml");
192 assert!(presentation_xml.is_ok());
193
194 let mut presentation_file = presentation_xml.unwrap();
195 let mut content = String::new();
196 std::io::Read::read_to_string(&mut presentation_file, &mut content).unwrap();
197 assert!(content.contains("presentation"));
198 assert!(content.contains("sldIdLst"));
199 assert!(content.contains("sldSz"));
200 }
201
202 #[test]
203 fn test_presentation_save_contains_relationships() {
204 let mut prs = Presentation::new().unwrap();
205 let mut cursor = Cursor::new(Vec::new());
206 prs.save(&mut cursor).unwrap();
207
208 let data = cursor.into_inner();
209 let cursor = Cursor::new(&data);
210 let mut archive = zip::ZipArchive::new(cursor).unwrap();
211
212 let rels = archive.by_name("_rels/.rels");
214 assert!(rels.is_ok());
215
216 let mut rels_file = rels.unwrap();
217 let mut content = String::new();
218 std::io::Read::read_to_string(&mut rels_file, &mut content).unwrap();
219 assert!(content.contains("Relationships"));
220 assert!(content.contains("ppt/presentation.xml"));
221 }
222
223 #[test]
224 fn test_presentation_slide_dimensions() {
225 let prs = Presentation::new().unwrap();
226 assert_eq!(prs.slide_width(), Some(9144000));
227 assert_eq!(prs.slide_height(), Some(6858000));
228
229 let mut prs = Presentation::new().unwrap();
231 assert!(prs.set_slide_width(10000000).is_ok());
232 assert!(prs.set_slide_height(8000000).is_ok());
233 }
234
235 #[test]
236 fn test_presentation_slides() {
237 let mut prs = Presentation::new().unwrap();
238 let mut slides = prs.slides();
239 assert_eq!(slides.len(), 0);
241 }
242
243 #[test]
244 fn test_presentation_save_with_slides() {
245 use crate::parts::slide::SlidePart;
246 use crate::opc::packuri::PackURI;
247 use crate::opc::part::Part;
248
249 let mut prs = Presentation::new().unwrap();
250 let slide_part = SlidePart::new(PackURI::new("/ppt/slides/slide1.xml").unwrap(), prs.part() as &dyn Part).unwrap();
252
253 fn add_slide_helper(
255 prs: &mut Presentation,
256 slide_part: &SlidePart,
257 ) -> Result<()> {
258 unsafe {
260 let prs_ptr = prs as *mut Presentation;
261 let slides = &mut (*prs_ptr).part;
262 let package = &mut (*prs_ptr).package;
263 let mut slides_collection = crate::slide::Slides::new(slides);
264 slides_collection.add_slide(slide_part, package)?;
265 }
266 Ok(())
267 }
268
269 add_slide_helper(&mut prs, &slide_part).unwrap();
270
271 {
273 let slides = prs.slides();
274 assert_eq!(slides.len(), 1);
275 }
276
277 let test_path = "test_output/test_save_with_slides.pptx";
279 std::fs::create_dir_all("test_output").ok();
280 let result = prs.save_to_file(test_path);
281 assert!(result.is_ok());
282
283 assert!(std::path::Path::new(test_path).exists());
285 let file = std::fs::File::open(test_path).unwrap();
286 let mut archive = zip::ZipArchive::new(file).unwrap();
287
288 let slide_xml = archive.by_name("ppt/slides/slide1.xml");
290 assert!(slide_xml.is_ok());
291
292 std::fs::remove_file(test_path).ok();
294 }
295
296 #[test]
297 fn test_presentation_save_with_images() {
298 use crate::parts::slide::SlidePart;
299 use crate::opc::packuri::PackURI;
300 use crate::opc::part::Part;
301
302 let mut prs = Presentation::new().unwrap();
303 let slide_part = SlidePart::new(PackURI::new("/ppt/slides/slide1.xml").unwrap(), prs.part() as &dyn Part).unwrap();
305
306 let png_data = vec![
308 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
309 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
310 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
311 0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x77, 0x53, 0xDE,
312 0x00, 0x00, 0x00, 0x0A, 0x49, 0x44, 0x41, 0x54,
313 0x78, 0x9C, 0x63, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00, 0x01,
314 0x0D, 0x0A, 0x2D, 0xB4,
315 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44,
316 0xAE, 0x42, 0x60, 0x82,
317 ];
318
319 fn add_slide_and_image_helper(
321 prs: &mut Presentation,
322 slide_part: &SlidePart,
323 png_data: Vec<u8>,
324 ) -> Result<()> {
325 unsafe {
327 let prs_ptr = prs as *mut Presentation;
328 let slides = &mut (*prs_ptr).part;
329 let package = &mut (*prs_ptr).package;
330 let mut slides_collection = crate::slide::Slides::new(slides);
331 let mut slide = slides_collection.add_slide(slide_part, package)?;
332 slide.add_image(png_data, "png", package)?;
333 }
335 Ok(())
336 }
337
338 add_slide_and_image_helper(&mut prs, &slide_part, png_data).unwrap();
339
340 let test_path = "test_output/test_save_with_images.pptx";
342 std::fs::create_dir_all("test_output").ok();
343 let result = prs.save_to_file(test_path);
344 assert!(result.is_ok());
345
346 assert!(std::path::Path::new(test_path).exists());
348 let file = std::fs::File::open(test_path).unwrap();
349 let mut archive = zip::ZipArchive::new(file).unwrap();
350
351 {
353 let image_file = archive.by_name("ppt/media/image1.png");
354 assert!(image_file.is_ok());
355 }
356
357 {
359 let slide_xml = archive.by_name("ppt/slides/slide1.xml");
360 assert!(slide_xml.is_ok());
361 let mut slide_content = String::new();
362 std::io::Read::read_to_string(&mut slide_xml.unwrap(), &mut slide_content).unwrap();
363 assert!(slide_content.contains("image1.png"));
364 }
365
366 std::fs::remove_file(test_path).ok();
368 }
369
370 #[test]
371 fn test_presentation_package_mut() {
372 let mut prs = Presentation::new().unwrap();
373 let package = prs.package_mut();
374 assert_eq!(package.iter_parts().count(), 0);
375 }
376
377 #[test]
378 fn test_presentation_save_collects_all_parts() {
379 use crate::parts::slide::SlidePart;
380 use crate::opc::packuri::PackURI;
381 use crate::opc::part::Part;
382
383 let mut prs = Presentation::new().unwrap();
384 let slide_part = SlidePart::new(PackURI::new("/ppt/slides/slide1.xml").unwrap(), prs.part() as &dyn Part).unwrap();
386
387 fn add_slide_and_image_helper(
389 prs: &mut Presentation,
390 slide_part: &SlidePart,
391 png_data: Vec<u8>,
392 ) -> Result<()> {
393 unsafe {
395 let prs_ptr = prs as *mut Presentation;
396 let slides = &mut (*prs_ptr).part;
397 let package = &mut (*prs_ptr).package;
398 let mut slides_collection = crate::slide::Slides::new(slides);
399 let mut slide = slides_collection.add_slide(slide_part, package)?;
400 slide.add_image(png_data, "png", package)?;
401 }
403 Ok(())
404 }
405
406 let png_data = vec![0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
407 add_slide_and_image_helper(&mut prs, &slide_part, png_data).unwrap();
408
409 let mut cursor = std::io::Cursor::new(Vec::new());
411 prs.save(&mut cursor).unwrap();
412
413 let data = cursor.into_inner();
414 let cursor = std::io::Cursor::new(&data);
415 let mut archive = zip::ZipArchive::new(cursor).unwrap();
416
417 assert!(archive.by_name("ppt/presentation.xml").is_ok());
419
420 assert!(archive.by_name("ppt/slides/slide1.xml").is_ok());
422
423 assert!(archive.by_name("ppt/media/image1.png").is_ok());
425
426 assert!(archive.by_name("ppt/_rels/presentation.xml.rels").is_ok());
428 assert!(archive.by_name("ppt/slides/_rels/slide1.xml.rels").is_ok());
429 }
430}
431