How PPTX Export Works in OpenMAIC Using the pptxgenjs Library

OpenMAIC generates PowerPoint files through a three-layer pipeline that prepares slide data, resolves media assets to embed-ready sources, and constructs the final PPTX using the pptxgenjs API, exposed via the useExportPPTX() React hook.

The export implementation lives in src/lib/export/use-export-pptx.ts and converts internal slide models into standard Office Open XML binaries. This system bridges OpenMAIC's canvas-based editor with the pptxgenjs library, handling everything from text styling to media embedding while preventing concurrent export collisions.

The Three-Layer PPTX Export Architecture

The export flow in src/lib/export/use-export-pptx.ts abstracts complexity across three distinct logical layers, each handling a specific phase of the generation process.

Data Preparation Layer

Before construction begins, the pipeline validates the integrity of the slide model and media references. The system calls derivePptxMediaReferenceSet (lines 37‑50) to scan all slides and scenes, producing a manifest of every image, video, and audio asset required by the presentation. Immediately after, assertPptxMediaReferenceParity (lines 73‑86) validates that the derived manifest aligns with the actual layout, ensuring no broken references reach the final output.

Asset Resolution Layer

Once the manifest is validated, resolvePptxEmbeddableSrc (lines 10‑28) converts opaque media references into concrete, embed-ready data. This function implements a fallback hierarchy:

  1. Stored‑bytes lookup: For opaque references, resolveStoredBytes fetches raw binary data from the backend and converts it to a data‑URL via blobToDataUrl.
  2. Task resolution: If a generation task exists, lookupMediaTask retrieves the concrete URL from the media task store.
  3. Renderable fallback: renderableMediaUrl provides the final string for embedding, returning an empty string if the asset is unavailable.

PPTX Construction Layer

The buildPptxBlob function (lines 96‑210) instantiates a pptxgen object and iterates through the slide list. For each canvas element—text, shapes, images, charts, tables, or LaTeX—it constructs a matching pptxgenjs options object (e.g., TextProps, ImageProps, ShapeProps) and calls the appropriate add* method. Finally, it executes pptx.write({outputType:'blob'}) to produce the downloadable Blob.

The Export Hook: useExportPPTX()

The React hook useExportPPTX() provides the UI-facing interface for triggering exports. Located in the same file (lines 99‑170), it reads the current stage state, canvas dimensions, and viewport ratio, then calculates conversion factors:

  • ratioPx2Inch: Converts canvas pixels to inches for pptxgenjs positioning (96 DPI baseline).
  • ratioPx2Pt: Converts pixels to points for font sizing.

The hook wraps buildPptxBlob in withExportGuard, a concurrent-operation preventer that disables the export button during generation and displays toast notifications upon completion. The resulting blob is passed to file‑saver's saveAs function to trigger the browser download.

// src/lib/export/use-export-pptx.ts – export hook
export function useExportPPTX() {
  const [exporting, setExporting] = useState(false);
  const { t } = useI18n();
  
  const scenes = useStageStore(s => s.scenes);
  const stage = useStageStore(s => s.stage);
  const viewportSize = useCanvasStore.use.viewportSize();
  const viewportRatio = useCanvasStore.use.viewportRatio();

  const ratioPx2Inch = 96 * (viewportSize / 960);
  const ratioPx2Pt = (96 / 72) * (viewportSize / 960);

  const slideScenes = scenes.filter(s => s.content.type === 'slide');
  const slides = slideScenes.map(s => (s.content as SlideContent).canvas);

  const exportPPTX = useCallback(() => {
    withExportGuard(async () => {
      const fileName = stage?.name ?? 'slides';
      const blob = await buildPptxBlob(
        slides, slideScenes,
        viewportRatio, viewportSize,
        ratioPx2Inch, ratioPx2Pt,
        stage?.id,
      );
      saveAs(blob, `${fileName}.pptx`);
      toast.success(t('export.exportSuccess'));
    });
  }, [/* deps */]);
}

Mapping OpenMAIC Elements to pptxgenjs

The buildPptxBlob function implements a type-specific adapter pattern, converting OpenMAIC's internal element format into pptxgenjs-compatible configuration objects using helpers like formatColor, formatHTML, formatPoints, getShadowOption, getOutlineOption, and getLinkOption.

Element Type pptxgenjs Method Key Conversion Details
Text slide.addText() HTML content is parsed through formatHTML into TextProps slices, extracting font families, colors, alignment, bullets, shadows, and outlines.
Image slide.addImage() Source URL is resolved via resolvePptxEmbeddableSrc; transformations like flipH, rotate, and clipping regions are applied directly to the ImageProps.
Shape slide.addShape('custGeom') Special shapes generate an SVG via svg2Base64; generic paths use toPoints to convert SVG path syntax into pptxgenjs point arrays.
Line slide.addShape('custGeom') Path data is converted to points; styling includes color, width, dash patterns, and arrowheads.
Chart slide.addChart() Series data is formatted as pptxgen.TableRow[] with theme colors, axis labels, and legend positioning.
Table slide.addTable() Cells map to pptxgen.TableCell[] with support for colspan, rowspan, background fills, and theme integration.
LaTeX slide.addFormula() (primary) or slide.addImage() (fallback) LaTeX strings convert to OMML via latexToOmml; if conversion fails, an SVG render is base64-encoded and inserted as an image.
Video/Audio slide.addMedia() Media URLs resolve to base64 embeddings; videos include a generated poster frame captured from the first frame.

Media Resolution Workflow

When buildPptxBlob encounters a media element, it delegates to the resolution pipeline to ensure all assets are self-contained within the PPTX file. The async function resolvePptxEmbeddableSrc handles three resolution strategies:

async function resolvePptxEmbeddableSrc(ref, task, stageId?) {
  if (!ref) return '';
  if (!isConcreteMediaAddress(ref)) {
    const stored = await resolveStoredBytes(ref, {...});
    if (stored) return blobToDataUrl(stored);
  }
  const tasks = useMediaGenerationStore.getState().tasks;
  const effectiveTask = task ?? lookupMediaTask(tasks, ref, stageId);
  return renderableMediaUrl(
    resolvePptxMediaBinding(ref, effectiveTask).resolution
  ) ?? '';
}

This ensures that whether an asset is stored as a binary blob, generated by an async task, or referenced by concrete URL, it resolves to a string that pptxgenjs can embed directly into the Open XML package.

Resource Pack Integration

OpenMAIC extends the PPTX export to support resource packs—ZIP archives containing both the presentation and interactive HTML scenes. The buildResourcePackZip function (lines 54‑94 in the same file) first inlines assets for interactive pages using inlineHtmlAssets, then conditionally appends the PPTX blob via opts.getPptxBlob. This decouples the presentation builder from the archive assembler, allowing the same buildPptxBlob logic to be reused across standalone downloads and bundled exports.

Implementation Examples

Programmatic Export Without React

For server-side scripts or non-React contexts, import buildPptxBlob directly:

import { buildPptxBlob } from '@/lib/export/use-export-pptx';
import { saveAs } from 'file-saver';

async function exportPptx(slides, slideScenes) {
  const viewportSize = 960;
  const viewportRatio = 0.75; // 4:3
  const ratioPx2Inch = 96 * (viewportSize / 960);
  const ratioPx2Pt = (96 / 72) * (viewportSize / 960);

  const blob = await buildPptxBlob(
    slides,
    slideScenes,
    viewportRatio,
    viewportSize,
    ratioPx2Inch,
    ratioPx2Pt,
    undefined // optional stageId
  );

  saveAs(blob, 'presentation.pptx');
}

Adding Custom Shape Support

To extend the exporter with a new shape type, modify the shape handling branch in buildPptxBlob (around line 89‑100):

if (el.type === 'shape' && el.special) {
  // Custom star shape handler
  if (el.customKind === 'star') {
    const starSvg = generateStarSvg(el);
    const data = svg2Base64(starSvg);
    pptxSlide.addImage({ 
      data, 
      x: el.x, 
      y: el.y, 
      w: el.w, 
      h: el.h 
    });
    continue;
  }
  // Existing special shape handling...
}

Summary

  • Three-layer pipeline: Data preparation (derivePptxMediaReferenceSet), asset resolution (resolvePptxEmbeddableSrc), and PPTX construction (buildPptxBlob) ensure reliable exports.
  • Media handling: The system resolves opaque references via stored bytes or task lookups, guaranteeing all images and media are embedded as base64 or data URLs.
  • Element mapping: Each canvas element type maps to a specific pptxgenjs method with dedicated conversion utilities for styling, positioning, and formatting.
  • React integration: useExportPPTX() provides a guarded, toast-enabled interface that prevents concurrent exports and handles browser downloads via file-saver.
  • Extensibility: The modular design supports resource pack bundling and custom shape injection without modifying core pptxgenjs logic.

Frequently Asked Questions

How does OpenMAIC handle missing or broken media references during PPTX export?

The assertPptxMediaReferenceParity function (lines 73‑86) validates that every media reference in the slide manifest corresponds to a resolvable asset before construction begins. If resolution fails during resolvePptxEmbeddableSrc, the function returns an empty string, and pptxgenjs skips that element, ensuring the export completes without crashing.

Can I customize the slide dimensions or aspect ratio when exporting to PPTX?

Yes. The useExportPPTX hook calculates ratioPx2Inch and ratioPx2Pt based on viewportSize and viewportRatio from the canvas store. These ratios are passed to buildPptxBlob, which applies them to all positioning and sizing calculations, allowing dynamic support for 4:3, 16:9, or custom canvas dimensions.

What happens if a user triggers multiple export operations simultaneously?

The withExportGuard wrapper maintains a exportingRef boolean that prevents concurrent execution. If a user clicks the export button while a generation is in progress, the guard blocks the second call and typically shows a loading state or toast notification, protecting against race conditions and memory leaks.

How are LaTeX mathematical expressions rendered in the exported PowerPoint?

OpenMAIC first attempts to convert LaTeX to Office Math Markup Language (OMML) using latexToOmml and inserts it via slide.addFormula(). If the conversion fails or the environment lacks MathML support, the system falls back to rendering the expression as an SVG, converting it to base64 via svg2Base64, and embedding it as an image using slide.addImage().

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →