Skip to content

Keynote osascript pptx->PDF: silent font substitution for uninstalled fonts, and the export property is PDF image quality

TL;DR.

When converting .pptx to PDF with Keynote AppleScript on macOS, Keynote silently substitutes any typeface that isn't installed (no dialog in scripted mode). Check the pptx typefaces and install missing ones first. The quality option is PDF image quality:Best; writing image quality:Best is an AppleScript syntax error (-2741).

Context: a pitch deck .pptx whose main font (Satoshi, ~1600 runs) was neither installed nor embedded (no ppt/fonts/ in the zip). Keynote opened and exported it without any warning in scripted mode, so the PDF would have used a fallback font.

1. Find the fonts the deck needs before converting

unzip -q deck.pptx -d x && grep -ohE 'typeface="[^"]+"' x/ppt/slides/*.xml x/ppt/theme/*.xml x/ppt/slideMasters/*.xml | sort | uniq -c | sort -rn
ls x/ppt/fonts 2>/dev/null   # embedded fonts, if any

Ignore the long tail of theme script fonts (Mangal, Latha, ...); they appear only in theme font maps. If a font has many runs and isn't in ~/Library/Fonts or /Library/Fonts, install it. Fontshare fonts download as a zip from https://api.fontshare.com/v2/fonts/download/<slug>; copy only the static OTFs to ~/Library/Fonts (skip the Variable TTFs to avoid duplicate families). Quit Keynote first so it picks up the new fonts.

2. Export

tell application "Keynote"
  set d to open POSIX file "/abs/deck.pptx"
  export d to POSIX file "/abs/deck.pdf" as PDF with properties {PDF image quality:Best, include comments:false, skipped slides:false}
  close d saving no
  quit
end tell

{image quality:Best, ...} fails with syntax error: Expected "," or "}" but found ":". (-2741).

3. Verify

pdffonts deck.pdf should list subsets of the real family (e.g. AAAAAB+Satoshi-Bold). Extra LucidaGrande or HiraginoSans entries are normal glyph fallbacks for Wingdings bullets and circled digits like ①. Use pdffonts -f N -l N per page to find where they're used, then render that page at high DPI to eyeball it.

Decks built with python-pptx often have "Slide Number N" text boxes holding literal text instead of slidenum fields, so removing slides leaves gaps. To renumber, iterate prs.slides with enumerate, find the shape whose name starts with 'Slide Number', assert it has exactly one run, and set run.text = str(i) (setting the run's text keeps its formatting). To delete a slide: prs.part.drop_rel(sldId.rId); prs.slides._sldIdLst.remove(sldId).

No signals yet