Skip to content

[Feature]: Export to editable PPTX (native PowerPoint objects, not flat images) #194

@TTigger

Description

@TTigger

Problem

open-slide can export to static HTML and print-to-PDF, but there's no PowerPoint export. For audiences and orgs that live in PowerPoint / Keynote / Google Slides, a deck currently has to be hand-rebuilt before it can be handed off or co-edited.

The usual shortcut — screenshot each slide into a .pptx — is what most AI slide tools do, and it produces a deck where every slide is one flat image: you can't fix a typo, re-theme, or nudge a box. There's no way today to hand someone a .pptx where each block (text, image, shape) is an independent, editable object, the way a hand-built deck is.

Proposed solution

Add an "Export as PPTX" item to the existing Download menu (next to HTML/PDF), fully client-side, that turns each page into a real PowerPoint slide where every block is a native object:

  • text → native text boxes (per-run bold/italic/color/size/letter-spacing; <em>/<strong> become their own runs)
  • <img> → native pictures (embedded as base64)
  • filled / bordered / rounded boxes → native autoshapes
  • <table> → native tables
  • linear gradients → native vector gradient fills (recolorable in PowerPoint)
  • only un-mappable decorative graphics (inline SVG, blurred/blended overlays) fall back to a rasterized image of just that minimal subtree — never the whole slide

Mechanics: mount each page offscreen at the fixed 1920×1080 canvas (reusing the existing PDF-export lifecycle — wait for fonts + animations to settle), walk the DOM, read geometry/style via getBoundingClientRect / getComputedStyle, and emit one PptxGenJS object per node. 1920px @ 96dpi maps exactly to a 20″×11.25″ 16:9 slide, so positioning is precise. Gradients are post-patched into the OOXML with the already-bundled fflate (PptxGenJS can't emit gradients itself).

API: exportSlideAsPptx(slide, slideId, onProgress?), exported from @open-slide/core, wired into the slide Download dropdown using the existing progress toast.

I've prototyped this end-to-end and verified it across the demo decks — happy to open a PR (will link here).

Alternatives considered

  • Screenshot / PDF-rasterize each slide into a .pptx — simplest, but produces non-editable flat images (the exact thing we want to avoid).
  • python-pptx / server-side conversion — needs a backend; open-slide's export is fully client-side today.
  • Re-implementing layout into pptx from scratch — unnecessary: pages already render at a fixed 1920×1080 with absolute pixels, so we can read the resolved geometry directly instead of re-deriving layout.

Which package would this affect?

@open-slide/core (runtime, inspector, present mode, CLI)

Additional context

Dependencies: PptxGenJS (the only mature, browser-capable generator of editable pptx; its only dependency is JSZip) + html-to-image (raster fallback). Both are lazy-imported via dynamic import() — exactly like fflate in export-html.ts — so they stay out of the main bundle. The gradient post-patch reuses the existing fflate dependency.

Verified (structural, by unzipping the OOXML; all open in PowerPoint without a repair prompt):

Deck Native editable objects Images Notes
smoke test (controlled) 100% 0 gradient → vector gradFill
raycast-api (9 slides; gradients/SVG/images/blur) ~88% brand logos / blur only 165 native text runs
claude-code-intro (9 slides; full-page noise overlay) ~93% 1/slide (the noise overlay) rest native

Known limitations (intentional / honest):

  • Web fonts can't be embedded by PptxGenJS → substituted to system fonts (Aptos/Consolas/Georgia); text may re-wrap slightly.
  • flex/grid is baked to absolute coordinates (editable, but not reflow-aware).
  • radial/conic gradients fall back to a solid first stop (only linear is vectorized).
  • inline SVG / blur / mix-blend decorative graphics are rasterized (minimal subtree, resolution-capped).
  • transitions/animations are not exported — the final settled state is captured (same behaviour as the PDF export).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions