mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-24 10:37:03 -06:00
Merge PR #728 from midasmatterhorn: pptx: write linked pictures with r:link so PowerPoint resolves external images
This commit is contained in:
@@ -95,6 +95,9 @@ pub fn fill(w: &mut W, x: &mut Exp, o: &mut Out, f: &Fill) {
|
||||
|
||||
/// `a:blipFill` / `p:blipFill`.
|
||||
pub fn blip_fill(w: &mut W, x: &mut Exp, o: &mut Out, tag: &str, pf: &PictureFill) {
|
||||
// An image that lives outside the package is a linked picture: `a:blip` names it through
|
||||
// `r:link`; `r:embed` may only reference a part inside the package.
|
||||
let linked = x.p.media(pf.media).is_some_and(|m| m.data.is_empty() && m.link.is_some());
|
||||
let rid = x.media_rel(o, pf.media, "image").unwrap_or_else(|| x.placeholder_rel(o));
|
||||
w.open(tag, A::new().a("rotWithShape", if pf.upright { "0" } else { "1" }));
|
||||
let adj = &pf.adjust;
|
||||
@@ -106,7 +109,7 @@ pub fn blip_fill(w: &mut W, x: &mut Exp, o: &mut Out, tag: &str, pf: &PictureFil
|
||||
|| adj.clear_color.is_some()
|
||||
|| adj.bilevel.is_some()
|
||||
|| adj.alpha_inv;
|
||||
let a = A::new().a("r:embed", &rid);
|
||||
let a = if linked { A::new().a("r:link", &rid) } else { A::new().a("r:embed", &rid) };
|
||||
if has_kids {
|
||||
w.open("a:blip", a);
|
||||
if let Some(c) = adj.clear_color {
|
||||
@@ -521,7 +524,12 @@ pub fn ppr(w: &mut W, x: &mut Exp, o: &mut Out, tag: &str, level: Option<u8>, p:
|
||||
Some(Bullet::Picture { media }) => match x.media_rel(o, *media, "image") {
|
||||
Some(rid) => {
|
||||
w.open0("a:buBlip");
|
||||
w.empty("a:blip", A::new().a("r:embed", rid));
|
||||
let a = if x.p.media(*media).is_some_and(|m| m.data.is_empty() && m.link.is_some()) {
|
||||
A::new().a("r:link", &rid)
|
||||
} else {
|
||||
A::new().a("r:embed", &rid)
|
||||
};
|
||||
w.empty("a:blip", a);
|
||||
w.close("a:buBlip");
|
||||
}
|
||||
None => w.empty("a:buChar", A::new().a("char", "•")),
|
||||
|
||||
@@ -991,3 +991,31 @@ fn transition_defaults_read_like_powerpoint() {
|
||||
assert_eq!((t.kind.as_str(), t.option.as_str(), t.duration_ms), (*kind, *option, 2000), "{el}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn linked_pictures_are_written_with_r_link() {
|
||||
// A picture whose bytes live outside the package must be referenced with `r:link`; `r:embed`
|
||||
// may only name a part inside the package (PowerPoint won't load the linked image otherwise).
|
||||
let mut p = defaults::new_presentation(None);
|
||||
let media = deckcraft_model::MediaId(5000);
|
||||
p.media.push(deckcraft_model::MediaItem {
|
||||
id: media,
|
||||
name: "linked.png".into(),
|
||||
content_type: "image/png".into(),
|
||||
data: Arc::new(Vec::new()),
|
||||
link: Some("file:///tmp/linked.png".into()),
|
||||
});
|
||||
let mut pic = shape(&mut p, "Linked", 100.0, 100.0, 200.0, 150.0);
|
||||
pic.kind = ShapeKind::Picture { fill: PictureFill { media, ..Default::default() } };
|
||||
Arc::make_mut(&mut p.slides[0]).shapes.push(pic);
|
||||
|
||||
let bytes = deckcraft_pptx::export(&p).expect("export");
|
||||
let mut zip = zip::ZipArchive::new(std::io::Cursor::new(bytes)).expect("zip");
|
||||
let mut xml = String::new();
|
||||
std::io::Read::read_to_string(&mut zip.by_name("ppt/slides/slide1.xml").expect("slide"), &mut xml).expect("read");
|
||||
assert!(xml.contains(r#"r:link="rId"#), "{xml}");
|
||||
assert!(!xml.contains(r#"r:embed="rId"#), "{xml}");
|
||||
let mut rels = String::new();
|
||||
std::io::Read::read_to_string(&mut zip.by_name("ppt/slides/_rels/slide1.xml.rels").expect("rels"), &mut rels).expect("read");
|
||||
assert!(rels.contains(r#"Target="file:///tmp/linked.png" TargetMode="External""#), "{rels}");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user