Round-trip tables, charts, animation, media, sections and masters

An owned corpus of those deck kinds survives open, save and reopen. The
check found the handout master — printed header, footer, date and slide
number — was dropped on save, so import and export now keep that part.
This commit is contained in:
Nikita Rakitin committed 2026-10-13 23:08:31 +03:00
1 parent 457406a7a5
commit 034b312a64
8 files changed
+699 -24

No files matched your search

+31 -2
View File
@@ -24,6 +24,7 @@ const CT_SLIDE: &str = "application/vnd.openxmlformats-officedocument.presentati
const CT_LAYOUT: &str = "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml";
const CT_MASTER: &str = "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml";
const CT_NOTES_MASTER: &str = "application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml";
const CT_HANDOUT_MASTER: &str = "application/vnd.openxmlformats-officedocument.presentationml.handoutMaster+xml";
const CT_NOTES: &str = "application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml";
const CT_THEME: &str = "application/vnd.openxmlformats-officedocument.theme+xml";
const CT_PRES_PROPS: &str = "application/vnd.openxmlformats-officedocument.presentationml.presProps+xml";
@@ -459,6 +460,29 @@ pub fn export(p: &Presentation) -> Result<Vec<u8>, PptxError> {
notes_master_rid = Some(pres.rels.add("notesMaster", &opc::relative("ppt/presentation.xml", notes_master), false));
}
// Handout master. Its header, footer, date and slide-number text live on this part; a deck
// that has never had one does not gain an empty master on save.
let handout_path = "ppt/handoutMasters/handoutMaster1.xml";
let mut handout_rid = None;
if let Some(hm) = p.handout_master.as_ref() {
let tindex = p.masters.len() + 1 + usize::from(notes_master_rid.is_some());
let tname = format!("ppt/theme/theme{tindex}.xml");
let mut ho = Out::new(handout_path);
ho.rels.add("theme", &opc::relative(handout_path, &tname), false);
let mut to = Out::new(&tname);
let txml = theme::theme_xml(&mut x, &mut to, &hm.theme);
x.pkg.xml_part(&tname, CT_THEME, txml);
x.pkg.rels(&tname, &to.rels);
let mut w = W::new();
root_open(&mut w, "p:handoutMaster", A::new());
csld(&mut w, &mut x, &mut ho, &hm.name, hm.background.as_ref(), &hm.shapes);
clr_map(&mut w, &hm.color_map);
w.close("p:handoutMaster");
x.pkg.xml_part(handout_path, CT_HANDOUT_MASTER, w.finish());
x.pkg.rels(handout_path, &ho.rels);
handout_rid = Some(pres.rels.add("handoutMaster", &opc::relative("ppt/presentation.xml", handout_path), false));
}
// Comment authors.
let mut authors: Vec<(String, String, u32)> = vec![];
let mut n_comments = 0u32;
@@ -514,7 +538,7 @@ pub fn export(p: &Presentation) -> Result<Vec<u8>, PptxError> {
x.pkg.xml_part("ppt/viewProps.xml", CT_VIEW_PROPS, view_props_xml());
let ts = crate::tables::table_styles_xml(&x.table_styles, &p.table_styles);
x.pkg.xml_part("ppt/tableStyles.xml", CT_TABLE_STYLES, ts);
let pxml = presentation_xml(&mut x, &mut pres, &master_rids, notes_master_rid.as_deref(), &slide_rids);
let pxml = presentation_xml(&mut x, &mut pres, &master_rids, notes_master_rid.as_deref(), handout_rid.as_deref(), &slide_rids);
x.pkg.xml_part("ppt/presentation.xml", CT_PRES, pxml);
x.pkg.rels("ppt/presentation.xml", &pres.rels);
@@ -665,7 +689,7 @@ fn section_slides(p: &Presentation) -> Vec<(String, Vec<usize>)> {
out
}
fn presentation_xml(x: &mut Exp, o: &mut Out, masters: &[(u64, String)], notes: Option<&str>, slides: &[String]) -> Vec<u8> {
fn presentation_xml(x: &mut Exp, o: &mut Out, masters: &[(u64, String)], notes: Option<&str>, handout: Option<&str>, slides: &[String]) -> Vec<u8> {
let p = x.p;
let mut w = W::new();
let first = (p.first_slide_number != 1).then_some(p.first_slide_number.min(9999));
@@ -680,6 +704,11 @@ fn presentation_xml(x: &mut Exp, o: &mut Out, masters: &[(u64, String)], notes:
w.empty("p:notesMasterId", A::new().a("r:id", n));
w.close("p:notesMasterIdLst");
}
if let Some(h) = handout {
w.open0("p:handoutMasterIdLst");
w.empty("p:handoutMasterId", A::new().a("r:id", h));
w.close("p:handoutMasterIdLst");
}
if !slides.is_empty() {
w.open0("p:sldIdLst");
for (i, rid) in slides.iter().enumerate() {