pptx-svg
v0.5.7
Published
PPTX ↔ SVG round-trip conversion library — browser & Node.js, zero dependencies
Maintainers
Readme
pptx-svg
PPTX and SVG round-trip conversion library. Runs in the browser and Node.js with zero dependencies.
Features
- PPTX to SVG: Convert PowerPoint slides to high-quality SVG
- SVG to PPTX: Edit SVG and export back to a valid .pptx file (lossless round-trip)
- Browser & Node.js: Runs client-side with no server, or server-side on Node.js 22+
- Zero dependencies: About 280KB Wasm binary, no npm dependencies
- Framework-agnostic: Works with React, Vue, Svelte, vanilla JS, Express, or any framework
Install
npm install pptx-svgQuick Start
import { PptxRenderer } from 'pptx-svg';
const renderer = new PptxRenderer();
await renderer.init(); // Wasm loaded automatically
const file = await fetch('presentation.pptx');
await renderer.loadPptx(await file.arrayBuffer());
const svgString = renderer.renderSlideSvg(0); // Slide 1 as SVG
document.getElementById('viewer').innerHTML = svgString;Node.js
import { readFileSync } from 'node:fs';
import { PptxRenderer } from 'pptx-svg';
const renderer = new PptxRenderer();
const wasmBytes = readFileSync('node_modules/pptx-svg/dist/main.wasm');
await renderer.init(wasmBytes); // Accepts Buffer / Uint8Array directly
const pptxBytes = readFileSync('presentation.pptx');
const pptxBuffer = pptxBytes.buffer.slice(
pptxBytes.byteOffset, pptxBytes.byteOffset + pptxBytes.byteLength
);
await renderer.loadPptx(pptxBuffer);
const svgString = renderer.renderSlideSvg(0);React
import { useEffect, useRef, useState } from 'react';
import { PptxRenderer } from 'pptx-svg';
function SlideViewer({ pptxBuffer }: { pptxBuffer: ArrayBuffer }) {
const [svg, setSvg] = useState('');
const rendererRef = useRef<PptxRenderer | null>(null);
useEffect(() => {
const renderer = new PptxRenderer();
rendererRef.current = renderer;
renderer.init()
.then(() => renderer.loadPptx(pptxBuffer))
.then(() => setSvg(renderer.renderSlideSvg(0)));
}, [pptxBuffer]);
return <div dangerouslySetInnerHTML={{ __html: svg }} />;
}Vanilla JS (no bundler)
<script type="importmap">
{ "imports": { "pptx-svg": "https://cdn.jsdelivr.net/npm/pptx-svg/dist/index.js" } }
</script>
<script type="module">
import { PptxRenderer } from 'pptx-svg';
const renderer = new PptxRenderer();
await renderer.init();
// ...
</script>See examples/ for complete working examples.
API Reference
PptxRenderer
import { PptxRenderer } from 'pptx-svg';
const renderer = new PptxRenderer(options?);Options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| measureText | (text, fontFace, fontSizePx) => number | Canvas 2D | Custom text width measurement. |
| fontFallbacks | Record<string, string[]> | (built-in) | Custom font fallback mappings. Merged with built-in defaults. |
| logLevel | 'silent' \| 'error' \| 'warn' \| 'info' \| 'debug' | 'error' | Console output verbosity. |
Methods:
| Method | Returns | Description |
|--------|---------|-------------|
| init(wasmSource?) | Promise<void> | Load the Wasm module. No arguments needed in browsers (auto-resolved). Pass URL, ArrayBuffer, or Uint8Array/Buffer (Node.js) to override. |
| loadPptx(buffer) | Promise<{ slideCount }> | Load a PPTX file from ArrayBuffer. |
| getSlideCount() | number | Number of slides. |
| isSlideHidden(idx) | boolean | Check if a slide is hidden (show="0"). |
| renderSlideSvg(idx) | string | Render slide as SVG string (0-indexed). |
| updateSlideFromSvg(idx, svg) | string | Update slide data from edited SVG. Returns "OK" or "ERROR:...". |
| getSlideOoxml(idx) | string | Get OOXML XML for a slide. |
| exportPptx() | Promise<ArrayBuffer> | Export as .pptx file with modifications applied. |
| getSlideXmlRaw(idx) | string | Raw slide XML (for debugging). |
| getEntryList() | string[] | All ZIP entry paths (for debugging). |
Shape-level Editing Methods:
| Method | Returns | Description |
|--------|---------|-------------|
| renderShapeSvg(slideIdx, shapeIdx) | string | Render a single shape as SVG fragment. |
| updateShapeTransform(slideIdx, shapeIdx, x, y, cx, cy, rot) | string | Update position/size/rotation (EMU). Returns re-rendered SVG. |
| updateShapeText(slideIdx, shapeIdx, paraIdx, runIdx, text) | string | Update text content. Returns re-rendered SVG. |
| updateShapeFill(slideIdx, shapeIdx, r, g, b) | string | Update solid fill color (0-255). Returns re-rendered SVG. |
| deleteShape(slideIdx, shapeIdx) | string | Delete a shape. Supports group children via composite index. |
| addShape(slideIdx, geomType, x, y, cx, cy, fillR, fillG, fillB) | string | Add a shape (rect/ellipse/roundRect/line). Returns OK:<index>. Fill -1 = none. |
| duplicateShape(slideIdx, shapeIdx, dxEmu?, dyEmu?) | string | Duplicate a shape with offset. Returns OK:<index>. |
| updateShapeGradientFill(slideIdx, shapeIdx, angle, stops) | string | Apply linear gradient. angle in 60000ths of degree. stops: [{pos,r,g,b}]. |
| addShapeText(slideIdx, shapeIdx, text, fontSize?, colorR?, colorG?, colorB?) | string | Add a text paragraph to a shape. fontSize in hundredths of a point (e.g. 1800 = 18pt). Returns OK:<paraIndex>. |
| updateShapeStroke(slideIdx, shapeIdx, r, g, b, widthEmu?, dash?) | string | Set stroke. Color -1 = remove. dash: dash/dot/etc. |
Text Editing Methods:
| Method | Returns | Description |
|--------|---------|-------------|
| addParagraph(slideIdx, shapeIdx, text, align?) | string | Add paragraph. align: l/ctr/r/just/"". Returns OK:<paraIndex>. |
| deleteParagraph(slideIdx, shapeIdx, paraIdx) | string | Delete a paragraph. Returns OK. |
| addRun(slideIdx, shapeIdx, paraIdx, text) | string | Add a text run. Returns OK:<runIndex>. |
| deleteRun(slideIdx, shapeIdx, paraIdx, runIdx) | string | Delete a text run. Returns OK. |
| updateTextRunStyle(slideIdx, shapeIdx, paraIdx, runIdx, bold?, italic?) | string | Bold/italic (1=on, 0=off, -1=no change). Returns re-rendered SVG. |
| updateTextRunFontSize(slideIdx, shapeIdx, paraIdx, runIdx, fontSize) | string | Font size in hundredths of pt (1800=18pt, 0=inherit). Returns re-rendered SVG. |
| updateTextRunColor(slideIdx, shapeIdx, paraIdx, runIdx, r, g, b) | string | Text color (0-255, r=-1 to inherit). Returns re-rendered SVG. |
| updateTextRunFont(slideIdx, shapeIdx, paraIdx, runIdx, fontFace?, eaFont?, csFont?) | string | Font family (""=no change). Returns re-rendered SVG. |
| updateParagraphAlign(slideIdx, shapeIdx, paraIdx, align) | string | Paragraph alignment. Returns re-rendered SVG. |
| updateTextRunDecoration(slideIdx, shapeIdx, paraIdx, runIdx, underline?, strike?, baseline?) | string | Underline/strike/super-subscript. Returns re-rendered SVG. |
All update* methods modify the cached SlideData in-place, mark the slide as modified for export, and return the re-rendered shape SVG. See docs/editing-guide.md for usage patterns.
Slide Management:
| Method | Returns | Description |
|--------|---------|-------------|
| addSlide(afterIdx?, sourceSlideIdx?) | Promise<{ slideCount, insertedIdx }> | Add a blank slide. afterIdx: insert after this index (-1 = beginning, omit = end). sourceSlideIdx: copy layout from this slide (default: last). |
| deleteSlide(slideIdx) | Promise<{ slideCount }> | Delete a slide (at least one must remain). |
| reorderSlides(newOrder) | Promise<{ slideCount }> | Reorder slides. newOrder[i] = old index for new position i. Must be a valid permutation. |
Slide management methods update presentation.xml, .rels, and [Content_Types].xml automatically. Changes are reflected in exportPptx().
Image Operations:
| Method | Returns | Description |
|--------|---------|-------------|
| addImage(slideIdx, imageData, mimeType, x, y, cx, cy) | string | Add picture shape. Handles media/rels/content-types. Returns OK:<shapeIdx>. |
| replaceImage(slideIdx, shapeIdx, imageData, mimeType) | string | Replace image of existing picture shape. Returns re-rendered SVG. |
| deleteImage(slideIdx, shapeIdx) | string | Delete picture shape and clean up orphaned media. Returns OK. |
Supported MIME types: image/png, image/jpeg, image/gif, image/bmp, image/tiff, image/svg+xml, image/x-emf, image/x-wmf.
Notes & Comments:
| Method | Returns | Description |
|--------|---------|-------------|
| getSlideNotes(idx) | string[] | Speaker notes as array of paragraph strings. |
| getSlideComments(idx) | SlideComment[] | Comments with text, author ID, date, and position. |
| getCommentAuthors() | CommentAuthor[] | All comment authors (id, name, initials). |
Notes and comments are automatically preserved in round-trip export.
Unit Conversion Helpers:
import { pxToEmu, emuToPx, ptToHundredths, hundredthsToPt, degreesToOoxml, ooxmlToDegrees } from 'pptx-svg';
pxToEmu(100) // 952500 EMU
emuToPx(914400) // 96 px
ptToHundredths(18) // 1800
hundredthsToPt(1800) // 18
degreesToOoxml(90) // 5400000
ooxmlToDegrees(5400000) // 90SVG DOM Helpers:
import { findShapeElement, getShapeTransform, getAllShapes, getSlideScale } from 'pptx-svg';
const shapes = getAllShapes(svgElement); // All shape <g> elements
const g = findShapeElement(svgElement, 0); // Shape by index
const transform = getShapeTransform(g); // { x, y, cx, cy, rot } in EMU
const scale = getSlideScale(svgElement); // EMU per SVG pixelSupported Features
Fully Supported
- Shapes: AutoShape (rect, ellipse, roundRect, line, ~154 preset geometries), custom geometry (
a:custGeom), connectors (straight/elbow/curved) - Text: Paragraphs, runs, bullets (char/auto/image), fonts (Latin/EA/CS/Symbol), bold/italic/underline/strikethrough, superscript/subscript, character spacing, kerning, capitalization, hyperlinks, tabs, RTL, justify (word-spacing distribution)
- Text body: Vertical alignment, margins, auto-fit, font scale, rotation, vertical text, multi-column, text warp (prstTxWarp)
- Fill: Solid color, gradient (linear/radial with stops), pattern (48 presets), image fill (stretch/tile/crop)
- Stroke: 11 dash patterns, 5 arrow types, line cap/join, compound lines, gradient/pattern stroke
- Effects: Outer shadow, inner shadow, preset shadow, glow, soft edge, reflection, blur, fill overlay (all via SVG filters)
- Images: PNG/JPEG/GIF/SVG, crop, alpha, brightness/contrast, duotone, color change
- Tables: Cell merge (grid span, row span), borders (including diagonal), margins, anchoring, table styles, conditional formatting (banded rows/cols, first/last row/col)
- Charts: Bar (clustered/stacked/percentStacked), Line, Pie, Doughnut, Scatter, Area, Radar, Bubble, Stock, Surface, OfPie (13 classic types) + Waterfall, Treemap, Sunburst, Histogram, Box & Whisker, Funnel (6 Office 2016+ cx:chart types), data labels, data points, trendlines, error bars, composite charts
- Group shapes: Recursive nesting with coordinate transforms
- Theme: 12 theme colors, font scheme, all color modifiers (tint, shade, saturation, luminance, etc.)
- Master/Layout inheritance: Placeholder inheritance,
p:clrMapOvr - Background: Solid, gradient, image, pattern backgrounds
- 3D: Data preservation for round-trip (bevel, extrusion, contour, material, camera, lighting)
- Placeholder auto content: Slide number, date, footer
- Speaker notes: Read via
getSlideNotes(), preserved in round-trip export - Comments: Read via
getSlideComments()/getCommentAuthors(), preserved in round-trip export - SmartArt: Fallback shapes from
mc:AlternateContentrendered;mc:Choice(DiagramML) preserved for round-trip - OLE / Embedded objects: Fallback image from
p:oleObjrendered; original XML preserved for round-trip - Media (video/audio): Poster frame image rendered; original XML preserved for round-trip
- EMF / WMF images: Converted to SVG at runtime via built-in converter
- Math equations (OMML
m:oMath): SVG rendering of fractions, radicals, integrals, matrices, accents, and operators; original XML preserved for round-trip
Supported with Limitations
- TIFF images - binary preserved for round-trip; not supported by browser
<img>in all browsers - Embedded fonts - binary preserved for round-trip; uses system font fallback for rendering
Data Preservation (no visual rendering)
- Animations (
p:timing) - preserved in round-trip export; static rendering only - Transitions (
p:transition) - preserved in round-trip export; static rendering only - Hidden slides - detected via
isSlideHidden()API;show="0"preserved in round-trip export
Out of Scope
- Macros / VBA - not supported for security reasons
SVG Output Format
The generated SVG embeds data-ooxml-* attributes that preserve all OOXML metadata for round-trip conversion. See docs/svg-specification.md for the complete attribute reference.
Architecture
[Browser / Node.js 22+]
PptxRenderer (TypeScript)
├── ZIP extraction (DecompressionStream)
├── ZIP building (CompressionStream + CRC-32)
└── FFI ─── WebAssembly GC (MoonBit)
├── XML parser
├── OOXML parser (types, theme, text, shapes, charts)
├── SVG renderer (shapes, text, fill, geometry, charts)
├── SVG parser (data-ooxml-* → SlideData)
└── OOXML serializer (SlideData → XML)Development
Prerequisites
- MoonBit toolchain
- Node.js 22+
Build
npm run build # Wasm + TypeScript + copy wasm to dist/Test
npm test # All tests (MoonBit unit + Node.js integration)
npm run test:moon # MoonBit unit tests only
npm run test:node # Node.js integration tests onlyBrowser Test
python3 -m http.server 8765 --directory .
# Open http://localhost:8765/web/index.htmlRelease
Releases are published to npm via GitHub Actions when a version tag is pushed:
# Update version in package.json, then:
git tag v0.1.0
git push origin v0.1.0Requires NPM_TOKEN secret configured in GitHub repository settings.
Contributing
- Fork the repository
- Create a feature branch
- Make changes following the existing code style
- Add MoonBit unit tests in
src/*/..._test.mbtand/or integration tests intest_fixtures/ - Run
npm run build && npm testto verify - Submit a pull request
