pdfspine
pptspine

PDF export

Render a .pptx deck to PDF with Presentation.to_pdf and save_pdf — one page per slide, font maps, degradation warnings, and the rendering features covered.

Presentation.to_pdf() renders a parsed deck to PDF: one page per slide, at the exact slide size (EMU → points, 12700 EMU per point), with every shape drawn at its absolute resolved position.

API

class Presentation:
    def to_pdf(self, *, font_map: dict[str, str] | None = None) -> bytes: ...
    def save_pdf(
        self, path: str | os.PathLike[str], *, font_map: dict[str, str] | None = None
    ) -> None: ...

Both take zero required arguments. to_pdf() returns the PDF as bytes; save_pdf(path) writes it to disk.

import pptspine

pres = pptspine.open("deck.pptx")

pdf_bytes = pres.to_pdf()            # -> bytes, starts with b"%PDF-"
pres.save_pdf("deck.pdf")           # writes the same bytes to a file

The page count always equals the slide count, and each page's rectangle equals the slide size in points — a 16:9 deck yields 960 × 540 pt pages, a 4:3 deck 720 × 540 pt:

pres = pptspine.open("widescreen.pptx")
assert pres.slide_size_points == (960.0, 540.0)
assert pres.to_pdf().startswith(b"%PDF-")   # page_count == slide_count

Fonts and the font_map

By default the renderer resolves each requested font family through a built-in substitution table (e.g. 宋体 → Songti SC) and embeds each used face once, subset to the glyphs actually drawn. Pass font_map to override family resolution — each entry maps a requested family name to either a font file path or another family name:

pdf = pres.to_pdf(font_map={"Calibri": "Helvetica"})

font_map is keyword-only and defaults to None.

Degradation warnings

When a font is substituted, or when a shape uses a preset the renderer does not yet draw, pptspine emits a Python warning through warnings.warn — one per unique kind, not one per shape. Export never raises for these; it degrades and warns. Suppress or capture them with the standard warnings module when rendering decks in bulk.

import warnings

with warnings.catch_warnings():
    warnings.simplefilter("ignore")
    pdf = pres.to_pdf()

What the renderer covers

FeatureSupport
Slide geometryOne page per slide at the exact slide size (EMU → pt).
Placeholder inheritanceph capture resolved up the slideLayout → slideMaster chain.
ThemeclrScheme + clrMap / clrMapOvr, fontScheme (incl. east-Asian), p:style fill / line / font references.
Shape transformsrot, flipH / flipV, avLst adjust values, dashed strokes, picture srcRect / stretch.
GroupsNested group affine remap: (child − chOff) · (ext / chExt) + off.
Text bodiesbodyPr anchor / insets / wrap and normAutofit font-scale. A stored fontScale is applied as stored; when normAutofit is on but no scale was stored, the text is recomputed to fit — fontScale steps down (95% → 25%) and lnSpcReduction escalates (0 → 10 → 20%) until the measured content height fits its box.
TablesAbsolute cell x from tblGrid, per-side borders, fills, cell margins, anchoring. Row height is content-adaptive — each row grows to max(declared height, measured content height) so long cell text is not clipped; rowSpan cells distribute their content height evenly across the rows they span.
BackgroundsSlide / layout / master bg solid and picture fills, bgRef via theme.
Text extractionToUnicode always written, so output is selectable and searchable.

Rendering is deterministic per font environment: the same deck and the same installed fonts always produce the same PDF bytes.

On main, unreleased

These export changes are merged on main after 0.5.1 and are not yet on PyPI:

  • Run-level spc, baseline and cap are parsed, inherited through txStyles / lstStyle, exposed in the run dict (char_spacing_pt / baseline / cap) and rendered. cap="small" is approximated as all caps with a one-time small-caps warning.
  • Line ends a:headEnd / a:tailEnd (triangle, stealth, diamond, oval, arrow; three sizes) are drawn on open outlines; unsupported kinds warn with line-end-degraded.
  • a:custGeom is no longer silently drawn as a rectangle: it is approximated by its bounding box (connectors as a straight line) with a custom-geometry-approximated warning.

Decks that use none of these render byte-identical PDFs.

On this page