//! Embedded (OLE) objects and their preview pictures, as other versions of PowerPoint write them. use std::io::{Cursor, Read, Write}; use deckcraft_model::{ShapeKind, defaults}; fn png() -> Vec { let mut out = Cursor::new(vec![]); image::DynamicImage::ImageRgb8(image::RgbImage::from_pixel(4, 4, image::Rgb([10, 200, 30]))) .write_to(&mut out, image::ImageFormat::Png) .expect("png"); out.into_inner() } /// An exported deck with `parts` added (or replaced) and `edit` applied to every part's text. fn with_parts(parts: &[(&str, Vec)], edit: impl Fn(&str, String) -> String) -> Vec { let base = deckcraft_pptx::export(&defaults::new_presentation(None)).expect("export"); let mut z = zip::ZipArchive::new(Cursor::new(base)).expect("zip"); let mut out = zip::ZipWriter::new(Cursor::new(Vec::new())); let o = zip::write::SimpleFileOptions::default(); for i in 0..z.len() { let mut f = z.by_index(i).expect("entry"); let name = f.name().to_string(); if parts.iter().any(|(n, _)| *n == name) { continue; } let mut d = vec![]; f.read_to_end(&mut d).expect("read"); if name.ends_with(".xml") || name.ends_with(".rels") { d = edit(&name, String::from_utf8(d).expect("utf8")).into_bytes(); } out.start_file(name, o).expect("start"); out.write_all(&d).expect("write"); } for (name, data) in parts { out.start_file(*name, o).expect("start"); out.write_all(data).expect("write"); } out.finish().expect("finish").into_inner() } /// PowerPoint 2007 style: `p:oleObj spid` with no `p:pic`; the preview is the `v:imagedata` of the /// VML shape with that id in the slide's legacy drawing part. fn ole_deck(spid_in_vml: &str) -> Vec { let vml = format!( r#" "# ); let vml_rels = r#" "#; let frame = r#""#; with_parts( &[ ("ppt/drawings/vmlDrawing1.vml", vml.into_bytes()), ("ppt/drawings/_rels/vmlDrawing1.vml.rels", vml_rels.as_bytes().to_vec()), ("ppt/media/preview.png", png()), ("ppt/media/other.png", vec![1, 2, 3]), ("ppt/embeddings/oleObject1.bin", vec![0; 16]), ], |name, s| { if name == "ppt/slides/slide1.xml" { s.replacen("", &format!("{frame}"), 1) } else if name == "ppt/slides/_rels/slide1.xml.rels" { s.replacen( "", r#""#, 1, ) } else { s } }, ) } fn object_preview(bytes: &[u8]) -> Option> { let p = deckcraft_pptx::import(bytes).expect("import"); let s = p.slides[0].shapes.iter().find(|s| s.name == "Object 3").expect("object"); match &s.kind { ShapeKind::Opaque { preview, label, .. } => { assert_eq!(label, "PowerPoint.Slide.8"); preview.and_then(|m| p.media(m)).map(|m| m.data.to_vec()) } k => panic!("{k:?}"), } } #[test] fn an_old_ole_object_shows_its_vml_preview() { assert_eq!(object_preview(&ole_deck("_x0000_s681987")), Some(png()), "the VML shape with the object's spid"); } #[test] fn an_ole_object_without_a_matching_vml_shape_has_no_preview() { assert_eq!(object_preview(&ole_deck("_x0000_s1")), None); }