mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-27 12:07:02 -06:00
Export: a group with a diagram becomes a graphicFrame naming ppt/diagrams data, layout, quickStyle and colors parts, plus the group's shapes as the cached drawing (dsp). The data model is built from the outline (doc, node, parTrans/sibTrans points and parOf links). A diagram read from a file keeps its own layout, quick style and colour parts until its layout is switched. Import: the outline is rebuilt from the data model's parOf tree, so PowerPoint diagrams get the Text Pane. Their shapes stay the file's drawing until the first outline edit (typed text isn't mapped back to nodes then). Co-authored-by: Cursor <cursoragent@cursor.com>
87 lines
2.9 KiB
Rust
87 lines
2.9 KiB
Rust
//! PPTX import and export: Office Open XML PresentationML / DrawingML, written from the public
|
|
//! ECMA-376 standard.
|
|
//!
|
|
//! [`import`] reads a package leniently (unknown parts are skipped, malformed parts are logged and
|
|
//! ignored, every size and depth is bounded) into the DeckCraft model; [`export`] writes a valid
|
|
//! package that PowerPoint opens without repair.
|
|
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
|
#![forbid(unsafe_code)]
|
|
|
|
mod custgeom;
|
|
mod dgm;
|
|
mod maps;
|
|
mod opc;
|
|
mod png;
|
|
pub(crate) mod read;
|
|
mod tables;
|
|
mod write;
|
|
mod xml;
|
|
|
|
use deckcraft_model::Presentation;
|
|
|
|
/// Extension URI of the PowerPoint 2010 section list in `presentation.xml`.
|
|
pub(crate) const SECTION_EXT_URI: &str = "{521415D9-36F7-43E2-AB2F-B90AF26B5E84}";
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum PptxError {
|
|
#[error("not a PPTX file: {0}")]
|
|
NotPptx(String),
|
|
#[error("unsupported: {0}")]
|
|
Unsupported(String),
|
|
#[error("write failed: {0}")]
|
|
Write(String),
|
|
#[error("malformed XML: {0}")]
|
|
Xml(String),
|
|
}
|
|
|
|
/// Does this look like an OOXML package (zip with `[Content_Types].xml`)?
|
|
///
|
|
/// The part is looked up in the zip's central directory, not in the leading bytes: writers may
|
|
/// store it anywhere in the archive (recent PowerPoint builds put it last, megabytes in).
|
|
pub fn sniff(bytes: &[u8]) -> bool {
|
|
if !bytes.starts_with(b"PK") {
|
|
return false;
|
|
}
|
|
zip::ZipArchive::new(std::io::Cursor::new(bytes)).map(|z| z.index_for_name("[Content_Types].xml").is_some()).unwrap_or(false)
|
|
}
|
|
|
|
/// Read a `.pptx` / `.potx` / `.ppsx` package.
|
|
pub fn import(bytes: &[u8]) -> Result<Presentation, PptxError> {
|
|
read::import(bytes)
|
|
}
|
|
|
|
/// Write a `.pptx` package.
|
|
pub fn export(p: &Presentation) -> Result<Vec<u8>, PptxError> {
|
|
write::export(p)
|
|
}
|
|
|
|
/// Content type for a file name's extension.
|
|
pub(crate) fn mime_for(name: &str) -> &'static str {
|
|
let ext = name.rsplit_once('.').map(|(_, e)| e.to_ascii_lowercase()).unwrap_or_default();
|
|
match ext.as_str() {
|
|
"png" => "image/png",
|
|
"jpg" | "jpeg" | "jpe" => "image/jpeg",
|
|
"gif" => "image/gif",
|
|
"bmp" => "image/bmp",
|
|
"tif" | "tiff" => "image/tiff",
|
|
"svg" => "image/svg+xml",
|
|
"emf" => "image/x-emf",
|
|
"wmf" => "image/x-wmf",
|
|
"webp" => "image/webp",
|
|
"mp4" | "m4v" => "video/mp4",
|
|
"mov" => "video/quicktime",
|
|
"wmv" => "video/x-ms-wmv",
|
|
"avi" => "video/x-msvideo",
|
|
"webm" => "video/webm",
|
|
"mp3" => "audio/mpeg",
|
|
"wav" => "audio/wav",
|
|
"m4a" => "audio/mp4",
|
|
"wma" => "audio/x-ms-wma",
|
|
"ogg" => "audio/ogg",
|
|
"flac" => "audio/flac",
|
|
"xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
"fntdata" => "application/x-fontdata",
|
|
_ => "application/octet-stream",
|
|
}
|
|
}
|