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.warnone 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 this page