Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

pptxboss reads, verifies and creates PowerPoint .pptx files, and reads legacy .ppt files through the same API. It is a clean-room implementation of ECMA-376 (Office Open XML) in safe Rust, with no compression or XML dependency. The same core is used by the command line, the Rust crates and the Python extension. The PowerPoint 97-2003 binary format is read from the MS-CFB, MS-PPT and MS-ODRAW specifications into the same slide model, so text, notes, titles and pictures are read from a .ppt the same way.

Leniency

Real decks are damaged. The reader tolerates bytes before the archive, a missing central directory, and broken content types and relationships, recovers the slide list from relationships when the presentation part does not list it, and skips what it cannot read instead of refusing. Every skip is reported: the CLI prints it to stderr as a warning: line, and text_reporting returns it with the text.

Two views of a package

Package is the raw Open Packaging Conventions view: every item, content type and relationship exactly as written, defects included. Document is the lenient reader built on it. The verifier reads both, which is how it can report the defects the reader works around.

Scope

Text extraction with paragraphs, line breaks, fields, tables, groups, charts and diagrams; Markdown output; speaker notes; comments, threaded comments included; sections; core and application properties; titles; pictures with their image parts; embedded objects; hyperlinks; alternative text; slide structure as a shape tree; a verifier with 72 clause-numbered rules; deck creation with titles, bullets, paragraphs, text boxes, tables, pictures and notes; Markdown to slides. Rendering slides to images is out of scope.

Where to go next

Installation covers the wheel, the binary and the crates; the Quickstart shows the CLI, Python and Rust doing real work. The guide then takes one task per chapter:

  • Extracting text: slide text, reading order, slide selection, warnings for what was skipped.
  • Notes, tables and structure: speaker notes, tables, the shape tree, titles and hyperlinks.
  • Extracting images: each slide's pictures and the bytes of their image parts, read only when asked for.
  • Verifying a deck: the clause-numbered rules, their codes and severities.
  • Creating decks: titles, bullets, text boxes, tables, pictures and notes from the CLI and Rust.
  • Markdown to slides: headings become slides, list items bullets and Notes: lines speaker notes.

The reference section holds the CLI reference, the Python API, the Rust crates, the verifier rules and the list of limitations.

pptxboss is dual-licensed under MIT or Apache-2.0, at your option.

Installation

Python

pip install pptxboss

Wheels are built for Linux x86_64 and macOS arm64 against the stable ABI for Python 3.12 and later. Other platforms build from the sdist with a Rust toolchain present.

Command line

cargo install pptxboss-cli

Coding agents get a bundled skill: pptxboss skill install writes it into ./.claude/skills/pptxboss/, and pptxboss skill install -g into the home directory.

Rust crates

[dependencies]
pptxboss-core = "0.2"    # reading
pptxboss-check = "0.2"   # verifying
pptxboss-write = "0.2"   # creating

From source

git clone https://github.com/4thel00z/pptxboss
cd pptxboss
cargo test --workspace
python -m venv .venv && . .venv/bin/activate
pip install maturin pytest pyyaml
maturin develop && pytest

Quickstart

CLI

pptxboss info deck.pptx
pptxboss text --notes deck.pptx
pptxboss check deck.pptx
pptxboss create md out.pptx slides.md

Python

import pptxboss

doc = pptxboss.Document("deck.pptx")
for slide in doc:
    print(slide.number, slide.title)
    print(slide.text())
print(pptxboss.check("deck.pptx"))

Rust

use pptxboss_core::Document;

let doc = Document::open("deck.pptx")?;
for slide in doc.slides() {
    let slide = slide?;
    println!("{}: {}", slide.number(), slide.text());
}

Extracting text

What you get

Shapes in z-order, which ECMA-376 makes both the paint order and the reading order (19.3.1.45). Each shape's paragraphs come one per line; a line break inside a paragraph stays a line break; field text (slide numbers, dates) is included as cached. Tables give one line per row with tab-separated cells, merged-away cells omitted. Groups are descended. Date, footer, header and slide-number placeholders are left out by default, as are hidden shapes. Text is never inherited from a layout or master, so an empty placeholder is empty.

CLI

pptxboss text deck.pptx                 # slides separated by a blank line
pptxboss text --headings deck.pptx      # --- slide N --- before each slide
pptxboss text --notes deck.pptx         # speaker notes after each slide
pptxboss text --furniture deck.pptx     # include date/footer/slide-number text
pptxboss text --skip-hidden deck.pptx   # leave out slides marked hidden
pptxboss text --json deck.pptx          # [{"number": 1, "text": "..."}, ...]
pptxboss text --slides 2-4,7 deck.pptx  # only those slides, in that order

Anything the reader skipped is printed to stderr as warning: lines; the exit code stays 0.

Python

doc = pptxboss.Document("deck.pptx")
doc.text()                                  # whole deck
doc.text(notes=True, hidden_slides=False)
doc.slide_texts()                           # one string per slide, in parallel
doc.slide_texts(indexes=[2, 0])             # chosen slides, zero-based, in that order
text, warnings = doc.text_reporting()       # what was skipped, one line each
text, report = doc.extract(indexes=[0])     # the same with an ExtractReport
doc[3].text(furniture=True)
doc[3].paragraphs()                         # every paragraph incl. table cells

Rust

use pptxboss_core::{Document, TextOptions};

let doc = Document::open("deck.pptx")?;
let (text, report) = doc.text_reporting(&TextOptions { notes: true, ..TextOptions::default() });
for warning in report.warnings() {
    eprintln!("{warning}");
}

Document::map_slides runs a closure over every slide across the available cores, or across the cap set with Document::with_threads; slide_texts and text_reporting are built on it. map_slides_at, slide_texts_at and markdown_at take a list of zero-based indices instead and keep the written order, which is what --slides uses.

What is skipped, and how it is reported

A slide whose part is missing or malformed gives an empty string and an entry in ExtractReport::failed_slides. Graphic frames whose type the reader does not know (anything but tables, charts, diagrams and embedded objects) are counted in unknown_graphics with their URI. Elements in unknown namespaces inside a shape tree are skipped and counted. Hidden slides left out because of the options are counted separately and do not make the report incomplete. ExtractReport::is_complete is true when nothing was dropped for a reason other than the options.

Encodings

Parts are UTF-8 or UTF-16; a UTF-16 part (byte order mark or <? in either byte order) is transcoded before parsing, and the verifier notes it. Text uses the _xHHHH_ escape convention for control characters, which the reader decodes.

Charts and diagrams

A chart gives its title and a table of its cached data: a header of series names, then one row per category with each series' value, cells separated like table cells. Series with differing categories (scatter and bubble charts) become one row each. A diagram (SmartArt) gives one line per node in tree order. Both are on by default and switched off with TextOptions::charts and TextOptions::diagrams (--no-charts, --no-diagrams). Slide::charts and Slide::diagrams return the data structured.

Markdown

Document::markdown and pptxboss markdown render the deck: a ## heading per slide (the title, or Slide N), bullets with their levels, plain paragraphs, bold, italic and links, GFM tables, ![alt](part) images, a **Chart: title** table per chart, diagram outlines, embedded objects as italic labels, and speaker notes and comments as block quotes when asked. Slides are separated by a rule. Paragraphs that inherit their bullet from the list style are bullets inside body placeholders and plain text elsewhere.

Comments and alternative text

TextOptions::comments appends each slide's comments after its text and notes, one line each: [comment] Author: text, replies indented as [reply]. TextOptions::alt_text emits the descr of shapes that have no text of their own (pictures, charts, diagrams, embedded objects). Both are off by default so extracted text stays paragraph-for-paragraph comparable with the slide content. Slide::comments returns the same comments structured, with author, initials and date.

Notes, tables and structure

Speaker notes

Notes are stored in a notes slide part linked from the slide. The reader takes the body placeholder of that part; when there is none, every text shape other than the slide image and the date, footer and slide-number placeholders.

pptxboss text --notes deck.pptx
doc[1].notes()          # str or None
slide.notes_text()?     // Option<String>

Tables

for rows in slide.tables():
    for row in rows:
        print(row)          # cell texts, merged-away cells omitted

tables() gives cell text only. Spans and merges are on the table shape:

for shape in slide.shapes():
    if shape.kind == "table":
        for row in shape.table.rows:
            [(cell.text, cell.grid_span, cell.row_span, cell.is_origin) for cell in row.cells]

In Rust a table is Content::Table(Table) with column_widths and rows of Cell { body, grid_span, row_span, h_merge, v_merge }. Cell::is_origin is false for cells merged into another.

The shape tree

for shape in slide.shapes():
    shape.kind          # text, picture, table, group, chart, diagram, ole, connector, content_part, unknown
    shape.id, shape.name, shape.hidden, shape.placeholder, shape.placeholder_index
    shape.text          # for text shapes
    shape.frame         # (x, y, cx, cy) in EMU when the shape has a transform
    shape.children      # for groups

A text shape's paragraphs holds Paragraph objects with their runs, each with bold, italic, size, hyperlink and the other run properties as written; Slide.paragraphs() stays a flat list of strings.

In Rust, SlideContent::shapes holds the top-level Shape values and SlideContent::walk() yields every shape in document order, descending into groups. Shape::placeholder holds the placeholder kind and index; Shape::is_title is true for title and centered-title placeholders.

Titles

Slide::title() returns the first title placeholder's text. Decks that put their titles in plain text boxes have no title placeholder, and the info listing shows (no title) for them.

Runs hold the relationship id of a click hyperlink in RunProps::hyperlink; Slide::hyperlink_target(rel_id) resolves it to an external URL or an internal part name.

Extracting images

Pictures are p:pic shapes whose a:blip names an image part through a relationship. Embedded object previews count too.

for image in slide.images():
    image.shape_id, image.rel_id, image.part, image.content_type, image.external
    data = slide.image_bytes(image)        # bytes of the image part
for image in slide.images()? {
    let bytes = slide.image_bytes(&image)?;
}

Image bytes are read only when asked for, so a deck's media costs nothing until then. Linked images (r:link) resolve to an external target and have no bytes in the package. Embedded objects (p:oleObj) are listed by Slide::objects with their progId and part, and Slide::object_bytes reads them. Charts and diagrams are not rendered; Slide::charts and Slide::diagrams give their text and data.

Verifying a deck

pptxboss check runs 72 structural rules from ECMA-376 Parts 1 and 2 over a package: the ZIP container, part names, content types, relationships, required parts, id ranges and uniqueness, XML well-formedness, namespace consistency and core properties. There is no schema validation; the rules are the constraints the specification states in prose.

CLI

pptxboss check deck.pptx
pptxboss check --quiet deck.pptx        # errors only
pptxboss check --json deck.pptx
pptxboss check --no-crc deck.pptx       # skip CRC-32 of XML parts
pptxboss rules                          # every rule

Exit code 0 when no errors were found, 1 when errors were found, 2 when the file could not be opened at all. Warnings alone exit 0.

Each finding prints as severity CODE part (location): message [clause].

Python

for finding in pptxboss.check("deck.pptx"):
    finding.severity, finding.code, finding.clause, finding.part, finding.location, finding.message
report = pptxboss.check_report("deck.pptx")   # .findings, .parts_checked, .truncated, .is_clean
pptxboss.rules()

Rust

use pptxboss_check::{check_path, CheckOptions};

let report = check_path("deck.pptx", &CheckOptions::default())?;
for finding in &report.findings {
    println!("{finding}");
}

Severity policy

A violation of a "shall" is an error. A "should", an inconsistency PowerPoint itself tolerates, or a policy the specification leaves open is a warning. Anything merely notable is info. Decks saved by PowerPoint pass with no findings.

Limitations

The verifier reads every XML part once. It does not validate against the XSD schemas, does not check DrawingML value ranges beyond slide size and ids, and does not inspect chart, diagram or embedded object parts.

Creating decks

pptxboss-write builds decks with one master, four layouts (title, title and content, title only, blank), a theme, and slides made of titles, subtitles, bullet lists, paragraphs, text boxes, tables, pictures and speaker notes. Output is deterministic: fixed timestamps, fixed part order, ids in insertion order. Every deck it writes reads back through the core and passes the verifier with no findings.

CLI

pptxboss create blank out.pptx --slides 3
pptxboss create text out.pptx --title "Hello" --bullet "one" --bullet "two" --notes "say hi"
pptxboss create md out.pptx slides.md

Rust

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")?;

The layout is inferred when not set: a title with body text uses Title and Content, a title alone uses Title Only, no title uses Blank.

Round trip

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());

Limitations

Pictures must be PNG, JPEG, GIF, BMP or TIFF; their box is given explicitly. Tables use one built-in style and equal column widths. There is no chart, diagram or embedded object creation, and no editing of existing decks.

Markdown to slides

pptxboss create md out.pptx slides.md
cat slides.md | pptxboss create md out.pptx -

The converter is line-oriented:

MarkdownResult
# Titlea title slide; the next paragraph becomes its subtitle
## Title, ### Titlea new content slide
---, ***a new untitled slide
- item, * item, + item, 1. item, 1) itema bullet; two spaces of indentation per level
Notes: textspeaker notes for the current slide until a blank line
fenced codeplain paragraphs
any other linea body paragraph without a bullet

Inline **bold**, *italic*, _emphasis_ and backtick markers are stripped; underscores inside words stay.

let deck = pptxboss_write::from_markdown(&markdown).font("Arial");
deck.write_to("out.pptx")?;

CLI reference

Every command takes a path to a .pptx (or .ppt, except check). Warnings go to stderr as warning: lines; errors as error: lines. Exit code 1 means the input could not be read, 2 means bad usage, except for check, which uses 1 for findings at error severity and 2 for an unreadable file.

--threads N, before or after the subcommand, caps the worker threads used to parse slides; the default is every core, or the value of PPTXBOSS_THREADS when that is set. --threads 1 keeps everything on the calling thread, which is also the default for a legacy .ppt deck.

--slides RANGE on info, text and markdown picks slides by their one-based number: a comma-separated list of numbers and low-high ranges such as 1-3,7, printed in the written order, duplicates kept. info still reports the whole deck's counts and lists only the picked slides; text --json and --headings use the real slide numbers. A number the deck does not have, or 0, is a usage error with exit code 2: error: --slides 9: slide 9 out of range (deck has 3 slides).

pptxboss info FILE [--json] [--slides RANGE]

Format (pptx or ppt), slide count, presentation part, slide size in EMU and inches with its declared type, master count, part count, the core properties that are set (title, subject, creator, modified by, created, modified, application), one section: line per section with its slide numbers, then one line per slide: number, title or (no title), and flags among hidden, notes, pictures, tables, objects, comments. A slide that fails to parse shows (unreadable: reason).

pptxboss text FILE [--slides RANGE] [--notes] [--comments] [--alt-text] [--no-charts] [--no-diagrams] [--furniture] [--hidden-shapes] [--skip-hidden] [--headings] [--json]

Slide text, slides separated by a blank line; empty slides are skipped unless --headings or --json is given. --comments appends [comment] Author: text lines (replies as [reply]) after a slide's text and notes; --alt-text adds the alternative text of pictures and other shapes that have no text. Chart data and diagram text are included unless --no-charts or --no-diagrams is given.

pptxboss markdown FILE [--slides RANGE] [--notes] [--comments] [--skip-hidden] [--hidden-shapes] [--furniture] [--no-headings] [--no-images]

The deck as Markdown on stdout: a ## Title heading per slide, bullets, paragraphs, GFM tables, images, chart tables and diagram outlines, slides separated by a rule; --notes and --comments add block quotes. Warnings go to stderr as for text.

pptxboss check FILE [--json] [--quiet] [--max-findings N] [--no-crc]

Findings sorted most severe first, then a summary line FILE: ok|not ok: E error(s), W warning(s), P part(s) checked.

pptxboss rules [--json]

Every rule: severity, code, clause, summary.

pptxboss create blank OUT [--slides N] [--standard]

pptxboss create text OUT --title T [--bullet B]... [--notes N] [--standard]

pptxboss create md OUT INPUT [--standard] [--font F]

INPUT may be - for standard input.

pptxboss skill install [-g|--global], pptxboss skill show

Installs the bundled agent skill into ./.claude/skills/pptxboss/SKILL.md, or with -g into ~/.claude/skills/pptxboss/SKILL.md, overwriting an earlier install. show prints it.

Python reference

The .pyi stubs shipped in the package give the exact signatures.

Document(path=None, *, data=None, threads=None)

threads caps the workers whole-deck calls use; None or 0 means every core (or PPTXBOSS_THREADS), 1 stays on the calling thread. Read back as threads. format is "pptx" or "ppt"; a legacy deck has no properties, sections, comments or charts, check() refuses it, and by default it stays on the calling thread.

slide_count, path, presentation_part, slide_size, slide_size_type, defects; len(), indexing with negative indexes, iteration; slide(i), slides(), titles(), text(...), text_reporting(...), extract(...), slide_texts(...), markdown(...), core_properties(), app_properties(), sections(), comment_authors(), presentation(), package(). Text methods take notes, furniture, hidden_shapes, hidden_slides, alt_text, comments, charts, diagrams; markdown takes headings, notes, comments, hidden_slides, hidden_shapes, furniture, images. extract, slide_texts and markdown also take indexes, a list of zero-based slide indexes (negatives count from the end) read in the written order, which is what the CLI's --slides uses. text_reporting returns (text, warnings); extract returns (text, ExtractReport).

Slide

index, number, part, hidden, name, title, warnings; text(furniture=, hidden_shapes=, alt_text=, charts=, diagrams=), markdown(...), paragraphs(), notes(), comments(), tables(), shapes(), images(), image_bytes(image), charts(), diagrams(), embedded_objects(), object_bytes(object), hyperlink(rel_id), notes_part(), comments_part(), layout_part().

ExtractReport

What extract skipped: failed_slides, failed_notes, failed_comments, failed_frames as (index, error) pairs, hidden_slides_skipped, unknown_graphics, unknown_graphic_uris, unknown_elements; is_complete is true when nothing was dropped for a reason other than the options, and warnings gives the same lines as text_reporting.

Chart, ChartSeries, Diagram

A chart's shape_id, title, kinds (e.g. barChart), axis titles and series, each with name, categories and values as written. A diagram's shape_id and items, (level, text) tuples depth-first.

CoreProperties, AppProperties

Every field of docProps/core.xml as optional text (title, subject, creator, keywords, description, last_modified_by, revision, created, modified, last_printed, category, content_status, language, identifier, version); docProps/app.xml as application, app_version, company, manager, template, presentation_format, the counts slides, notes, hidden_slides, words, paragraphs, total_time, and titles_of_parts.

Section

name, slides (zero-based slide indexes).

Presentation, SlideId, MasterId

The parsed presentation part: slides and masters as (id, rel_id) records, notes_master and handout_master relationship ids, slide_size, slide_size_type, notes_size, first_slide_num, rtl.

DocumentDefects

located (relationship, content_type, conventional_path or legacy_stream), unresolved_slides as (position, rel_id) pairs, slides_recovered_from_rels.

CommentAuthor

id, name, initials.

Package

The raw package, from Document.package() or Package(path=None, *, data=None): parts() as Part records (name, content_type, size, compressed_size), has(name), content_type(name), read(name, raw=False) (UTF-16 XML comes back as UTF-8 unless raw), rels(source="/") as Relationship records (id, type, target, external), resolve(source, rel_id), content_types() with defaults and overrides dicts, and defects, a PackageDefects record: content_types_missing, content_types_case, content_types_error, content_types_unreadable, invalid_names, collisions, derivable, directories, incomplete_pieces.

Comment

author, initials, date, text, reply.

EmbeddedObject

shape_id, prog_id, rel_id, part, content_type, external.

Shape

id, name, kind, hidden, placeholder, placeholder_index, text, description, hyperlink, frame, rotation, image_rel, rows, table, paragraphs, children, is_title. rows is cell text only; table is the grid with spans and merges; paragraphs is the text with its runs.

Table, Row, Cell

A table's column_widths (EMU) and rows; a row's height and cells; a cell's text, paragraphs, grid_span, row_span, h_merge, v_merge and is_origin, false for a cell merged into another.

Paragraph, Run

A paragraph's text, level, bullet (inherited, none, char, auto_number, picture), bullet_char, number_scheme, number_start and runs. A run's kind (text, line_break, field), field, text, and the formatting as written, None where not set: bold, italic, underline, strike, size in hundredths of a point, hyperlink (a relationship id for Slide.hyperlink), lang, typeface.

Image

shape_id, rel_id, part, content_type, external.

check(path=None, *, data=None, max_findings=1000, xml_well_formed=True, verify_crc=True) -> list[Finding]

check_report(...) -> CheckReport

Same arguments; returns findings, parts_checked, truncated (true when max_findings cut the list short), errors and warnings counts, is_clean and codes.

Finding

code, severity, clause, part, location, message.

rules() -> list[Rule]

code, severity, clause, summary.

PptxError

Raised for any processing error. ValueError for bad arguments, IndexError for slide indexes out of range.

Threading

Documents and slides are frozen and usable from any thread. Calls that read the archive release the GIL and run on a private Document rebuilt over the same archive, so calls from different threads run in parallel. Whole-deck calls (slides(), titles(), text(), slide_texts(), text_reporting()) use up to the thread count set at construction.

Rust reference

CrateStart here
pptxboss-coreDocument::open, Document::slide, Slide::text, Document::map_slides, Document::markdown, Document::core_properties, Document::sections, Slide::comments, Slide::charts, Slide::diagrams, Slide::objects; Package for the raw view; zip::Archive, inflate, xml::Reader, opc, model, chart, diagram, markdown; cfb::Compound and ppt::LegacyDeck behind .ppt files (Document::legacy)
pptxboss-checkcheck, check_path, check_bytes, rules, Finding, Severity, CheckOptions
pptxboss-writePresentation, Slide, Paragraph, Rect, SlideSize, Layout, from_markdown
pptxboss-clithe binary

Document is single-threaded; Document::seed() gives a Send + Sync handle from which any thread rebuilds a Document over the same archive with private caches. Package works the same way. Document::map_slides spreads slides over every core unless Document::with_threads (or set_threads, or the PPTXBOSS_THREADS variable) caps the workers; the seed keeps the cap. A legacy .ppt deck stays on the calling thread unless a cap is set explicitly, because spreading them costs more than it saves.

Every error type is a thiserror enum per crate: pptxboss_core::Error, pptxboss_write::Error.

Verifier rules

pptxboss rules prints the current list with severities and clauses. The codes are stable. Families:

PrefixCoversClauses
ZIPcontainer records, compression methods, flags, header consistency, CRC-32, duplicate items, directoriesPart 2 7.3 and Annex B
OPCpart name grammar, equivalence, derivability, the content types streamPart 2 6.2.2, 7.2.3, 7.3.7
CTYDefault and Override elements, media type syntax, every part typedPart 2 6.2.3, 7.2.3
RELRelationships parts, ids, targets, reachabilityPart 2 6.5
PKGthe package relationships: one presentation, core properties, thumbnailsPart 1 13.3.6, 15.2; Part 2 8.2
XMLwell-formedness, encodings, Strict versus Transitional mixingPart 2 6.2.5; Part 4 7
PMLpresentation and slide-family structure, id ranges and uniqueness, required relationshipsPart 1 13.3, 19.2, 19.3, 19.7, 20.1.2.2.8
CPRcore propertiesPart 2 8

Limitations

  • Password-protected files (encrypted packages, encrypted .ppt) are detected and refused, not decrypted.
  • Legacy .ppt decks: text, titles, notes, hidden flags, slide size and pictures are read, and the file is read whole into memory; tables, charts, comments and document properties of the binary format are not read, and the verifier does not cover it. PowerPoint 95 files are refused.
  • Charts give their cached title, series, categories and values; nothing is recomputed from the embedded workbook. Extended charts (cx:) inside mc:AlternateContent fall back to their picture, since the reader does not claim to understand their versioned namespaces. Embedded objects are listed with their bytes, not interpreted.
  • Text formatting beyond bold, italic, underline, strike, size, language, typeface and hyperlinks is not modelled.
  • Placeholder inheritance of formatting from layouts and masters is not resolved; text is never inherited by design.
  • No rendering of slides to images.
  • The verifier does not validate against the XSD schemas.
  • The writer embeds PNG, JPEG, GIF, BMP and TIFF pictures only, creates no charts or diagrams, and does not edit existing decks.