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]);
No server · No framework · Your files stay 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/coreThis viewer uses the published npm package @web-ppt/core. Drop in your own file. It will not be uploaded anywhere.
Parsing and rendering happen on your device. No network request sends your file elsewhere.
Compare with actual LibreOffice rendering as ground truth. Theme color shade/tint must be calculated in linear RGB:
| Transform | LibreOffice | Direct sRGB scaling | Linear 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.
Measured in a browser: 210 slides / 11280 elements.
| Lazy first view (parse + render) | 42ms |
| Full parse | 376ms (1.8ms/slide) |
| Render one slide | 0.09ms |
| JS heap | 40MB (0.19MB/slide) |
| Optimization | Effects |
|---|---|
| Lazy parsing (default) | 9× faster first view |
| Worker parsing | No main-thread blocking |
| Virtualized thumbnails | Render 7 of 210 thumbnails |
Each format has its own parser. Both use the same rendering schema.
| Features | .pptx | .ppt |
|---|---|---|
| Preset geometry | All 187 standard presets | Full MSOSPT set |
| Custom geometry | custGeom + formula evaluation + arcTo | pVertices / pSegmentInfo |
| Fill | Solid / gradient / image / tile / pattern / theme color transforms | Solid / gradient / image |
| Stroke | Dashes / arrows / caps / joins | Dashes / arrows |
| Effects | Outer shadow / inner shadow / glow / soft edges / reflection | Ignored |
| 3D effects | Extrusion / bevel / contour / material / camera | Ignored |
| Style inheritance | Master → layout → placeholder → paragraph → run | TxMasterStyle → shape |
| Text | Full text + 15 WordArt warps | Size / color / bold, italic, underline / alignment / bullets |
| Images | Crop / shape crop / opacity / grayscale | Pictures stream + DEFLATE |
| EMF / WMF / PICT | Built-in GDI / QuickDraw interpreter → SVG | Same (PICT from Mac PowerPoint) |
| Tables | tableStyles / banding / merges / borders | Table properties + grid reconstruction |
| Charts | 13 types · secondary axes · 3D | Via embedded EMF preview |
| Transitions | 41 types (including p14 extensions and per-element morph) | Via SSSlideInfoAtom |
| Element animations | Entrance / exit / emphasis / motion paths, grouped by click | Same |
| SmartArt | Cached drawing part; data-driven layout when no cache exists | — |
| Media | Poster + play indicator; optional native player | — |
| Ink · comments · sections | Supported | — |
| Embedded OLE objects | Embedded p:pic preview / legacy VML snapshot | — |
| Hidden slides | sld@show="0" | SSSlideInfoAtom |
| OMML equations | Fractions / radicals / scripts / operators / matrices / delimiters | — |
| Encrypted files | Standard AES-ECB / Agile AES-CBC | RC4 CryptoAPI (40/56/128 bit) |
| Notes · hyperlinks | Supported | Supported |
import { parse, renderSlideToSvg } from '@web-ppt/core';
const pres = await parse(file); // File | Blob | ArrayBuffer
el.innerHTML = renderSlideToSvg(pres, pres.slides[0]);
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);
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');
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);
await slideToPng(pres, slide, 2); // → Blob
await slideToSvgFile(pres, slide); // → string
await presentationToPrintableHtml(pres); // Print to PDF in the browser
// Inject chart parsers and metafile decoders through hooks
setChartParser(myChartParser);
setMetafileDecoder(undefined); // Save ~15KB gzip
Parsing and rendering are independent. The renderer knows the schema, not file formats.
PK → pptx; D0CF11E0 → ppt. No filename extension required.foreignObject (selectable text); export uses native <text> with built-in measurement, wrapping and columns. Chrome may taint canvases containing foreignObject in SVG, preventing toBlob.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.
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.
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.
A fraction bar belongs on the math axis (roughly half the x-height), not the baseline. Nested fractions expose this difference.
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.
A cloud made from overlapping ellipses develops holes under evenodd because overlapping regions have an even crossing count. Use a single closed contour.
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.
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 | Purpose | Dependencies | Size |
|---|---|---|---|
@web-ppt/core | Framework-free parsing, rendering and export | fflate | 131.63KB gzip |
@web-ppt/edit-core | Editing model, history and incremental save | @web-ppt/core | 185.70KB gzip |
@web-ppt/editor | Framework-free editing session and three-layer DOM | core + edit-core + viewer-core | 59.27KB gzip |
@web-ppt/collab | Optional field-level LWW collaboration | @web-ppt/edit-core | 12.20KB gzip |
@web-ppt/react | Thin React components and hooks | @web-ppt/editor + React | 1.12KB gzip |
@web-ppt/vue | Thin Vue components and composables | @web-ppt/editor + Vue | 1.34KB gzip |
@web-ppt/viewer-core | Headless state machine + playback | @web-ppt/core | 13.07KB gzip |
@web-ppt/fonts | Optional font substitution and on-demand loading | @web-ppt/core | 2.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.