# How PPTX Export Works in OpenMAIC Using the pptxgenjs Library

> Discover how OpenMAIC uses the pptxgenjs library to export PPTX files. Learn about the three layer pipeline that prepares slides, resolves media, and constructs your presentation.

- Repository: [MAIC/OpenMAIC](https://github.com/THU-MAIC/OpenMAIC)
- Tags: how-to-guide
- Published: 2026-09-13

---

**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`](https://github.com/THU-MAIC/OpenMAIC/blob/main/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`](https://github.com/THU-MAIC/OpenMAIC/blob/main/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.

```typescript
// 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:

```typescript
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:

```typescript
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):

```typescript
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()`.