Merge PR #212 from justinjohn0306: PPTX: .potx and .ppsx declare a template and slide show main part (#210)

# Conflicts:
#	crates/pptx/tests/roundtrip.rs
This commit is contained in:
Brandon Thomas
2026-10-14 12:58:00 -04:00
4 changed files with 37 additions and 7 deletions
+7 -3
View File
@@ -129,10 +129,10 @@ fn open_bytes(s: &mut Session, p: &Value) -> Result<Value> {
Ok(json!({"document": i, "missingFonts": s.doc()?.missing_fonts}))
}
/// Encode the presentation in `format` (`deckcraft`, `pptx`, `outline`).
/// Encode the presentation in `format` (`deckcraft`, `pptx`, `potx`, `ppsx`, `outline`, `pdf`).
pub fn save_bytes(doc: &Presentation, format: &str) -> Result<Vec<u8>> {
match format {
"pptx" | "potx" | "ppsx" => deckcraft_pptx::export(doc).map_err(|e| EngineError::Other(e.to_string())),
f @ ("pptx" | "potx" | "ppsx") => deckcraft_pptx::export_as(doc, f).map_err(|e| EngineError::Other(e.to_string())),
"outline" | "txt" => Ok(deckcraft_format::slides_to_outline(doc).into_bytes()),
"pdf" => deckcraft_pdf::export(doc, &Default::default()).map_err(|e| EngineError::Other(e.to_string())),
_ => deckcraft_format::save(doc).map_err(|e| EngineError::Other(e.to_string())),
@@ -207,8 +207,12 @@ pub fn writable_format_for_path(path: &str) -> Option<&'static str> {
pub fn format_for_path(path: &str) -> &'static str {
let l = path.to_ascii_lowercase();
if l.ends_with(".pptx") || l.ends_with(".potx") || l.ends_with(".ppsx") {
if l.ends_with(".pptx") {
"pptx"
} else if l.ends_with(".potx") {
"potx"
} else if l.ends_with(".ppsx") {
"ppsx"
} else if l.ends_with(".png") {
"png"
} else if l.ends_with(".jpg") || l.ends_with(".jpeg") {
+12 -1
View File
@@ -60,7 +60,18 @@ pub fn import(bytes: &[u8]) -> Result<Presentation, PptxError> {
/// Write a `.pptx` package.
pub fn export(p: &Presentation) -> Result<Vec<u8>, PptxError> {
write::export(p)
write::export(p, write::CT_PRES)
}
/// Write a package for `ext`: `potx` (template) and `ppsx` (slide show) differ from `pptx` only in
/// the main part's content type.
pub fn export_as(p: &Presentation, ext: &str) -> Result<Vec<u8>, PptxError> {
let ct = match ext {
"potx" => write::CT_TEMPLATE,
"ppsx" => write::CT_SHOW,
_ => write::CT_PRES,
};
write::export(p, ct)
}
/// Content type for a file name's extension.
+5 -3
View File
@@ -19,7 +19,9 @@ use crate::PptxError;
use crate::opc::{self, NS_A, NS_P, NS_P14, NS_P188, NS_R, PackageOut, RelsOut};
use crate::xml::{A, W};
const CT_PRES: &str = "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml";
pub(crate) const CT_PRES: &str = "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml";
pub(crate) const CT_TEMPLATE: &str = "application/vnd.openxmlformats-officedocument.presentationml.template.main+xml";
pub(crate) const CT_SHOW: &str = "application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml";
const CT_SLIDE: &str = "application/vnd.openxmlformats-officedocument.presentationml.slide+xml";
const CT_LAYOUT: &str = "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml";
const CT_MASTER: &str = "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml";
@@ -418,7 +420,7 @@ fn sanitized(p: &Presentation) -> std::borrow::Cow<'_, Presentation> {
std::borrow::Cow::Owned(q)
}
pub fn export(p: &Presentation) -> Result<Vec<u8>, PptxError> {
pub fn export(p: &Presentation, main_ct: &str) -> Result<Vec<u8>, PptxError> {
let p = sanitized(p);
let p: &Presentation = &p;
let mut x = Exp {
@@ -575,7 +577,7 @@ pub fn export(p: &Presentation) -> Result<Vec<u8>, PptxError> {
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);
x.pkg.xml_part("ppt/presentation.xml", CT_PRES, pxml);
x.pkg.xml_part("ppt/presentation.xml", main_ct, pxml);
x.pkg.rels("ppt/presentation.xml", &pres.rels);
// Document properties and package relationships.
+13
View File
@@ -1036,3 +1036,16 @@ fn linked_pictures_are_written_with_r_link() {
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}");
}
/// #210: `.potx` and `.ppsx` were written with the `.pptx` main content type.
#[test]
fn templates_and_slide_shows_declare_their_own_main_content_type() {
let p = rich_deck();
for (ext, kind) in [("pptx", "presentation"), ("potx", "template"), ("ppsx", "slideshow")] {
let bytes = deckcraft_pptx::export_as(&p, ext).expect("export");
let mut z = zip::ZipArchive::new(std::io::Cursor::new(&bytes)).expect("zip");
let ct = std::io::read_to_string(z.by_name("[Content_Types].xml").expect("content types")).expect("read");
assert!(ct.contains(&format!("presentationml.{kind}.main+xml")), "{ext}: {ct}");
assert_eq!(deckcraft_pptx::import(&bytes).expect("import").slides.len(), p.slides.len(), "{ext} reads back");
}
}