No server · No framework · Your files stay in your browser

Render PowerPoint in your browser

Open .pptx / .ppt without a backend or an Office installation.
Built-in OOXML, CFB/OfficeArt and EMF/WMF/PICT parsers. The only runtime dependency is fflate.

npm i @web-ppt/core

Running right on this page

This viewer uses the published npm package @web-ppt/core. Drop in your own file. It will not be uploaded anywhere.

Drop to render
— / —
— / —
Loading… Download

Parsing and rendering happen on your device. No network request sends your file elsewhere.

Fidelity & performance

Fidelity needs a reference

Compare with actual LibreOffice rendering as ground truth. Theme color shade/tint must be calculated in linear RGB:

TransformLibreOfficeDirect sRGB scalingLinear RGB
shade 20%33,56,97 Δ69 Δ8
tint 60%176,187,222 Δ37 Δ1

Snapshots detect changes, not results that were wrong from the start. That requires an external reference.

Performance

Measured in a browser: 210 slides / 11280 elements.

Lazy first view (parse + render)42ms
Full parse376ms (1.8ms/slide)
Render one slide0.09ms
JS heap40MB (0.19MB/slide)
OptimizationEffects
Lazy parsing (default)9× faster first view
Worker parsingNo main-thread blocking
Virtualized thumbnailsRender 7 of 210 thumbnails

Feature matrix

Each format has its own parser. Both use the same rendering schema.

Features.pptx.ppt
Preset geometryAll 187 standard presetsFull MSOSPT set
Custom geometrycustGeom + formula evaluation + arcTopVertices / pSegmentInfo
FillSolid / gradient / image / tile / pattern / theme color transformsSolid / gradient / image
StrokeDashes / arrows / caps / joinsDashes / arrows
EffectsOuter shadow / inner shadow / glow / soft edges / reflectionIgnored
3D effectsExtrusion / bevel / contour / material / cameraIgnored
Style inheritanceMaster → layout → placeholder → paragraph → runTxMasterStyle → shape
TextFull text + 15 WordArt warpsSize / color / bold, italic, underline / alignment / bullets
ImagesCrop / shape crop / opacity / grayscalePictures stream + DEFLATE
EMF / WMF / PICTBuilt-in GDI / QuickDraw interpreter → SVGSame (PICT from Mac PowerPoint)
TablestableStyles / banding / merges / bordersTable properties + grid reconstruction
Charts13 types · secondary axes · 3DVia embedded EMF preview
Transitions41 types (including p14 extensions and per-element morph)Via SSSlideInfoAtom
Element animationsEntrance / exit / emphasis / motion paths, grouped by clickSame
SmartArtCached drawing part; data-driven layout when no cache exists—
MediaPoster + play indicator; optional native player—
Ink · comments · sectionsSupported—
Embedded OLE objectsEmbedded p:pic preview / legacy VML snapshot—
Hidden slidessld@show="0"SSSlideInfoAtom
OMML equationsFractions / radicals / scripts / operators / matrices / delimiters—
Encrypted filesStandard AES-ECB / Agile AES-CBCRC4 CryptoAPI (40/56/128 bit)
Notes · hyperlinksSupportedSupported

Get started

Render a slide

import { parse, renderSlideToSvg } from '@web-ppt/core';

const pres = await parse(file);   // File | Blob | ArrayBuffer
el.innerHTML = renderSlideToSvg(pres, pres.slides[0]);

Complete viewer

import { Viewer } from '@web-ppt/viewer-core';

const v = new Viewer(box, pres, { animate: true });
v.next();          // Play animations before advancing
v.setZoom(1.5);
await v.exportPng(2);

Connect your own UI

import { PresentationState } from '@web-ppt/viewer-core';

// DOM-free state machine for React / Vue
const st = new PresentationState(pres);
st.subscribe(setState);
st.next(); st.search('keyword');

Keep the main thread free

import { parseInWorker } from '@web-ppt/core';

const w = new Worker(
  new URL('@web-ppt/core/worker', import.meta.url),
  { type: 'module' });
const pres = await parseInWorker(w, buf);

Export

await slideToPng(pres, slide, 2);        // → Blob
await slideToSvgFile(pres, slide);       // → string
await presentationToPrintableHtml(pres); // Print to PDF in the browser

Load only what you need

// Inject chart parsers and metafile decoders through hooks
setChartParser(myChartParser);
setMetafileDecoder(undefined);  // Save ~15KB gzip

Architecture

Parsing and rendering are independent. The renderer knows the schema, not file formats.

Detect formats by signaturePK → pptx; D0CF11E0 → ppt. No filename extension required.
Two text rendering pathsPreview uses foreignObject (selectable text); export uses native <text> with built-in measurement, wrapping and columns. Chrome may taint canvases containing foreignObject in SVG, preventing toBlob.
Lazy parsingParse each slide on first access: 376ms → 42ms for the first view of 210 slides.

Hard cases

These are real bugs we encountered, not invented examples. Each pair shows a naive approach on the left and this engine rendering the same .pptx.

Combining marks

OMML m:acc@chr stores combining code points such as U+20D7. They belong to the preceding character; rendering them alone can produce dotted circles or misplaced accents.

v⃗·xy̅
Naive approach
This engine

Stretchable delimiters

Delimiters cannot simply scale around the baseline. A matrix is centered on the math axis, with asymmetric extents above and below the baseline. Uniform scaling makes the bottom edge extend too far.

() ab cd
Naive approach
This engine

Fraction bar alignment

A fraction bar belongs on the math axis (roughly half the x-height), not the baseline. Nested fractions expose this difference.

1 1+ 1 x
Naive approach
This engine

Zero-area strokes

The callout leader is a three-point open subpath. SVG fill closes it into a solid wedge. Traversing forward and then back adds two crossings, preserving the fill under evenodd while keeping the stroke visible.

Naive approach
This engine

Overlapping evenodd shapes

A cloud made from overlapping ellipses develops holes under evenodd because overlapping regions have an even crossing count. Use a single closed contour.

Naive approach
This engine

Inner contour direction

To cut a hole in a solid shape, the inner contour must run in reverse. With the same direction, nonzero does not produce a hole; switching to evenodd affects other self-intersections.

Naive approach
This engine

The corpus contains 242 public Apache POI test files. Excluding 22 deliberately corrupt fuzzing samples, the success rate is 210/220; the remaining 10 are truncated or empty files, PowerPoint 95/4.0, or encrypted files with unknown passwords.

Packages

PackagesPurposeDependenciesSize
@web-ppt/coreFramework-free parsing, rendering and exportfflate131.63KB gzip
@web-ppt/edit-coreEditing model, history and incremental save@web-ppt/core185.70KB gzip
@web-ppt/editorFramework-free editing session and three-layer DOMcore + edit-core + viewer-core59.27KB gzip
@web-ppt/collabOptional field-level LWW collaboration@web-ppt/edit-core12.20KB gzip
@web-ppt/reactThin React components and hooks@web-ppt/editor + React1.12KB gzip
@web-ppt/vueThin Vue components and composables@web-ppt/editor + Vue1.34KB gzip
@web-ppt/viewer-coreHeadless state machine + playback@web-ppt/core13.07KB gzip
@web-ppt/fontsOptional font substitution and on-demand loading@web-ppt/core2.69KB gzip

Sizes sum the gzipped default entry and all relative static chunks, excluding peers and dynamic entries.

Framework adapters and collaboration are separate optional packages. Parsing, viewing and local editing do not load unused features.