Paper Office
paper-pptx

Guide

How to use the APIs paper-pptx adds, grouped as perceive, edit, compose, and verify, plus the error taxonomy.

This page summarizes the APIs paper-pptx adds to python-pptx. The generated API reference covers the complete public API, and the comparison page describes deliberate behavior changes at package boundaries.

Safety contract

Paper's guarded mutating APIs either complete or refuse atomically. An operation that cannot proceed safely raises a typed refusal and restores the presentation's package state. Programmer mistakes (a bad type, an out-of-range index) raise plain ValueError or TypeError.

The refusal hierarchy in pptx.errors:

  • PaperRefusal, the base class
  • PackageLimitError: archive has no unambiguous reading or is unsafe to expand
  • AmbiguousTargetError: addressing matches more than one target
  • TargetNotFoundError: addressing matches nothing
  • StaleAnchorError (subclass of TargetNotFoundError): the block at an anchor no longer matches its content fingerprint
  • UnsupportedStructureError: structure the API cannot operate on safely
  • BoundaryViolationError: the operation would cross a boundary it promised to stay inside
  • RelationshipPolicyError: the relationship graph cannot be honored under the requested policy

Perceive: pptx.inspect

Read-only resolution of what a deck renders, through the placeholder, layout, master, and theme inheritance walk. Each value reports its sources. Unsupported values (gradients, East Asian typefaces, style modulations) report resolved=False.

effective_font(run) -> EffectiveFont            # also run.effective_font()
effective_paragraph_format(paragraph) -> EffectiveParagraphFormat
effective_shape_format(shape) -> EffectiveShapeFormat
inspect_text(slide) -> TextInspection
inspect_deck(prs) -> DeckManifest
content_hash(text) -> str
run = prs.slides[0].shapes.title.text_frame.paragraphs[0].runs[0]
font = run.effective_font()
if font.size.resolved:
    print(font.size.value_pt)           # e.g. 36.0
    for step in font.size.provenance:
        if step.supplied:
            print("supplied by", step.level)   # e.g. "master txStyles titleStyle lvl1"

Each EffectiveValue carries value, value_pt, resolved, and the ordered provenance chain. inspect_text() traverses top-level shapes, groups recursively to any depth, and table cells in a pinned order. Every TextBlock carries a structural BlockAnchor with a full content fingerprint for stale-safe edits. Regions the library cannot read are counted as blind regions. inspect_deck() returns a deterministic, JSON-friendly DeckManifest (slides, shapes, z-order, geometry, placeholder bindings, autofit state, tables, charts, images, masters).

Edit: pptx.edit and inherited-class methods

Text replacement

replace_text(prs, find, replace, *, include_notes=False) -> ReplaceResult
replace_text_at(prs, anchor, find, replace) -> ReplaceResult
refind(prs, anchor) -> BlockAnchor

Deck-wide and formatting-preserving. Matches do not cross paragraph, line-break, or field boundaries. Replacement text inherits the formatting of the run where the match starts. The full traversal is materialized before the first write, so a refusal leaves the deck unchanged. replace_text_at locates the structural container and then verifies its content fingerprint. It raises StaleAnchorError when that content is stale; refind is the recovery path.

Slide lifecycle (Slides)

prs.slides.clone(source, *, after=None, policy=None) -> Slide
prs.slides.delete(slide) -> None
prs.slides.move(slide, to_index) -> None
prs.slides.reorder(new_order) -> None      # exact permutation required

clone is a policy-governed deep copy: charts are deep-copied with their embedded workbooks and style parts, notes are deep-copied and re-linked, and media is shared (tunable via SlideClonePolicy). Relationship types that cannot be cloned safely (OLE objects, ActiveX controls) raise RelationshipPolicyError before anything changes. delete purges section and custom-show references and does not strand orphaned parts.

Bullets, autofit, notes

paragraph.bullet.set_character("•", font_name=None, size_percent=None)
paragraph.bullet.set_numbered("arabicPeriod", start_at=1)
paragraph.bullet.set_none()

text_frame.font_scale               # e.g. 62.5 (normAutofit), else None
text_frame.line_space_reduction     # e.g. 20.0
text_frame.normalize_autofit(min_font_size=None, resolve=False)

slide.read_notes_text() -> str      # does not create a notes part
slide.replace_notes_text(text)      # edits only an existing notes body

Bullets write a:buChar, a:buAutoNum, or a:buNone markup with hanging-indent geometry. normalize_autofit() bakes the rendered sizes into explicit formatting before disabling autofit. A run whose size cannot be resolved refuses unless resolve=True walks the effective-style chain, and the walk refuses what it cannot resolve.

Shape and table operations

shapes.shape_by_name(name); shapes.picture_by_name(name)
shapes.table_by_name(name); shapes.chart_by_name(name)
shapes.add_copy(shape) -> Shape
shapes.delete(shape); shapes.move(shape, to_index)

table.insert_row(after, *, copy_format_from=None) -> _Row
table.insert_column(after, *, width=None, copy_format_from=None) -> _Column
table.delete_row(row_idx); table.delete_column(col_idx)
cell.extend_merge(other_cell) -> None

picture.replace_image(image_file, *, allow_format_change=False)
chart.replace_data_safe(categories, series, *, number_format=None)

The *_by_name lookups recurse into groups, return the right object type, and raise AmbiguousTargetError on duplicate names. Table operations guard merged regions cell by cell; an operation refuses only when it would split a merge. replace_image() swaps the image relationship and leaves position, size, rotation, and crop untouched; when two pictures share one image part, only the target changes. replace_data_safe() validates the chart and workbook structure before writing, refuses shared chart parts and unsupported chart families (XY, bubble, stock, surface, radar, 3-D, multi-plot combos), and supports workbook-less charts.

Compose: pptx.compose, pptx.rebind, pptx.hf

Cross-deck import

prs.import_slide(source_prs, slide, *, mode, position=None,
                 notes=True, section=None, section_id=None,
                 target_layout=None, placeholder_map="auto") -> ImportReport
prs.append_deck(source_prs, *, mode, notes=True) -> tuple[ImportReport, ...]

mode is required and has no default:

  • "adopt_theme": rebind to a destination layout. Orphaned placeholders bake from their source-resolved look, and run_shifts reports each appearance change.
  • "keep_appearance": transplant the source layout, master, and theme chain, fingerprint-deduplicated, so ten slides from one source share one master.
  • "bake": freeze resolved formatting into the slide.

The source deck is never mutated (byte-tested). Media copies; charts deep-copy with workbooks; comments drop and the report says so. Unsafe relationship types raise RelationshipPolicyError before the first write. Automatic layout and placeholder matches must be unique. Use target_layout or a partial placeholder_map to resolve ambiguity. section selects a unique exact name; section_id selects an exact stored GUID.

report = prs.import_slide(source, 0, mode="adopt_theme")
for shift in report.run_shifts:
    print(shift.text, shift.before["name"]["value"], "->", shift.after["name"]["value"])

Layout rebind

slide.rebind_layout(target_layout, *, placeholder_map="auto",
                    orphan_policy="refuse") -> RebindReport

Moves a slide to another layout in the same package under explicit placeholder and orphan policies. It runs the effective-value resolver before and after, and reports every run whose resolved appearance changed.

Footers and fields

prs.apply_footers(*, footer=None, slide_number=False, date_format=None,
                  fixed_date=None, skip_title_slides=False, now=None) -> None
slide.apply_footers(...)                    # per-slide variant

Reproduces PowerPoint's Insert → Header & Footer behavior with a:fld slide-number and date fields that renumber on reorder, bound to the layout's footer placeholders, applied idempotently. date_format takes the ISO/IEC 29500 tokens ("datetime1" through "datetime13"). fixed_date writes a literal string. Passing both raises ValueError.

Verify: pptx.diff and pptx.package

diff_decks(path_a, path_b, *, detail="structure") -> DeckDiff

Slides are matched by permanent slide ID, so a reorder reports as a move rather than a delete plus an add. Top-level shapes match by shape ID and compatible kind. detail levels: "structure" (slide add/remove/move, shape add/remove, geometry, table dimensions, image replacement), "text" (adds canonical text-region, chart-data, and notes deltas), "full" (adds per-run effective-value and bullet shifts; expensive). ID matching serves decks derived from a common ancestor; independently authored decks are out of scope.

xml_equivalent(a, b) -> bool
diff_package(path_a, path_b) -> PackageDiff
patch_save(original_path, document, out_path) -> PackageDiff

xml_equivalent compares canonicalized XML and treats meaningful whitespace, including a trailing space inside a run, as content. patch_save serializes the deck, then restores the original bytes of every semantically identical member: a one-line edit to a sixty-slide deck diffs as one part, a no-op round trip is byte-identical, entry order and timestamps are deterministic, and the write is atomic.

Report objects

These public operations return typed, versioned, JSON-friendly reports:

ObjectReturned bySchema
TextInspectioninspect_textpaper-text-inspection v3
DeckManifestinspect_deckpaper-deck-manifest v1
EffectiveFonteffective_fontpaper-effective-font v2
ReplaceResultreplace_text, replace_text_atpaper-replace-result v2
ImportReportimport_slide, append_deckpaper-import-report v3
RebindReportrebind_layoutpaper-rebind-report v1
DeckDiffdiff_deckspaper-deck-diff v5
PackageDiffdiff_package, patch_savepaper-package-diff v1

On this page