pptxboss.dev

Rust

PowerPoint reading and verification in pure Rust.

pptxboss is a workspace of focused crates sharing one version: a clean-room reader built from ECMA-376 Parts 1 to 4 with its own ZIP, DEFLATE, XML and Open Packaging Conventions layers, a reader for the PowerPoint 97-2003 binary format built from MS-CFB, MS-PPT and MS-ODRAW into the same slide model, a verifier with 72 clause-numbered rules, and a deterministic writer. Safe Rust, no C dependencies, no bindings to another engine.

Install

Add only what you use.

pptxboss-core alone reads decks; pptxboss-check verifies them and pptxboss-write creates them. All three build on the core’s package layer.

shell
$ cargo add pptxboss-core
# optional: the verifier and the writer
$ cargo add pptxboss-check pptxboss-write

Quickstart

Open, read, report.

Document::open reads a .pptx or a legacy .ppt. Slides parse lazily; text_reporting returns the whole deck’s text with a report of everything skipped. Document is single-threaded; Document::seed() gives a Send + Sync handle from which any thread rebuilds a reader over the same archive, and map_slides spreads slides over every core unless with_threads caps the workers.

main.rs
use pptxboss_core::Document;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let doc = Document::open("deck.pptx")?;
    for slide in doc.slides() {
        let slide = slide?;
        println!("{}: {}", slide.number(), slide.title().unwrap_or_default());
        println!("{}", slide.text());
        if let Some(notes) = slide.notes_text()? {
            println!("notes: {notes}");
        }
    }
    let (text, report) = doc.text_reporting(&Default::default());
    for warning in report.warnings() {
        eprintln!("warning: {warning}");
    }
    println!("{text}");
    Ok(())
}

Verifier

Check a package against ECMA-376.

check_bytes, check_path and check run the 72 rules and return findings sorted most severe first, each with a stable code, a Severity, the clause it enforces, the part and a message. CheckOptions caps the number of findings and turns XML well-formedness and CRC verification on or off.

check.rs
let bytes = std::fs::read("deck.pptx")?;
let report = pptxboss_check::check_bytes(bytes, &Default::default())?;
for finding in &report.findings {
    println!("{:?} {} [{}] {}", finding.severity, finding.code, finding.clause, finding.message);
}
assert!(report.findings.is_empty());

Writer

Build a deck, read it back, verify it.

Presentation carries one master, four layouts and a theme; slides take titles, subtitles, bullets, paragraphs, text boxes, tables, pictures and notes, and the layout is inferred when not set. from_markdown builds the same from a Markdown string. Output is deterministic and passes the verifier with no findings.

write.rs
use pptxboss_write::{Metadata, Paragraph, Presentation, Rect, Slide, SlideSize};

let deck = Presentation::new()
    .size(SlideSize::WIDESCREEN)
    .metadata(Metadata { title: Some("Review".into()), ..Metadata::default() })
    .slide(Slide::title_slide("Quarterly review", Some("Q3 2026")))
    .slide(Slide::titled("Highlights").bullet("Revenue up").sub_bullet("in every region", 1).notes("Pause here"))
    .slide(Slide::titled("Free form")
        .text_box(Rect::inches(1.0, 1.5, 5.0, 1.0), vec![Paragraph::text("Bold claim").bold().size(28)])
        .picture(std::fs::read("chart.png")?, Rect::inches(7.0, 1.5, 5.0, 3.0))
        .table(Rect::inches(1.0, 4.0, 11.0, 2.0), vec![vec!["Region".into(), "Growth".into()], vec!["EMEA".into(), "12%".into()]], true));
deck.write_to("review.pptx")?;

// round trip: read it back and verify it
let bytes = deck.to_bytes()?;
let doc = pptxboss_core::Document::load(bytes.clone())?;
assert_eq!(doc.slide(1)?.text(), "Highlights\nRevenue up\nin every region");
assert!(pptxboss_check::check_bytes(bytes, &Default::default())?.findings.is_empty());

Design

What the crates promise.

  • Lenient reading: bytes before the archive, a missing central directory and broken content types and relationships are tolerated, the slide list is recovered from relationships when the presentation part does not list it, and every skip is reported.
  • Lazy slides and lazy media: opening a deck parses no slide, and image bytes are read only when asked for.
  • Two views of a package: Package is the raw Open Packaging Conventions view with defects included; Document is the lenient reader built on it.
  • Deterministic output from pptxboss-write: fixed timestamps, fixed part order, ids in insertion order.
  • Strict and Transitional namespaces both read, and mc:AlternateContent resolved per Part 3.