docx 458kb raw 217kb gzipped
pptx 574kb raw 253kb gzipped
xslx 601kb raw 269kb gzipped
I expected the Wasm bundles to be large and a lot more bigger than that for some reason.ChatGPT.com can benefit from using this library (or such a library) for rendering a preview of the file in a side panel on the right, instead of just giving me a download link to the outputted/transformed docx/pptx/xslx file.
For PPTX and DOCX, this solution is slightly worse than libreoffice conversion (this does not appear to output highlightable text, while PDF conversion does).
However, the XLSX preview BLEW my mind considering this was AI coded. Really good, even interactive!
Holy cow!!
So the tool is growing and maybe this would be interesting to have as the non LibreOffice dependent viewer...
The slightest misalignment of a paragraph means a line on page 27 of 120 now moved down by 2 pixels, screwing everything else out of alignment. Yes, plenty of companies pay Microsoft 365 subscriptions because of exactly this reason; it sounds ludicrous when you think they could just pay someone to replicate the formatting in a different suite a lot less than the subscription costs, but that's not how it works...
Does this work in Cloudflareβs workerd environment? Would be nice to have a cheap serverless render -> LLM (GLM-OCR / PaddleOCR) -> Markdown pipeline for the various MS Office formats.
It's 100% hallucinated.
This entire codebase β Rust parsers, TypeScript renderers, tests, and tooling β was implemented by Claude (Anthropic's AI assistant) through iterative prompting. No human-written application code exists in this repository.
A browser-based viewer for Office Open XML documents that renders to an HTML Canvas element.
The parsers are written in Rust and compiled to WebAssembly; the renderers use the Canvas 2D API.
Each format also exposes a headless engine (DocxDocument / XlsxWorkbook / PptxPresentation) that renders into any caller-supplied canvas, so you can compose your own UI β scroll views, thumbnail grids, master-detail panes β instead of being locked into the built-in viewer. See the Examples section in the Storybook demo.
| DOCX | XLSX | PPTX |
|---|---|---|
![]() |
![]() |
![]() |
npm install @silurus/ooxml
# or
pnpm add @silurus/ooxml
Bundler note: this package embeds
.wasmfiles. With Vite addvite-plugin-wasm; with webpack useexperiments.asyncWebAssembly.
Bundle size note: the package is ESM-only (
.mjs). npm's Unpacked Size sums all four entry bundles, including the opt-in math engine (MathJax + STIX Two Math, ~3 MB). What actually lands in your app is much smaller β import only the format you need (e.g.@silurus/ooxml/pptx). The math engine is a separate entry (@silurus/ooxml/math): it is bundled only if you import it and pass it to a viewer (see Rendering equations). Viewers that never receive amathengine β and all xlsx usage β tree-shake the ~3 MB away entirely.
import { DocxViewer } from '@silurus/ooxml/docx';
import { XlsxViewer } from '@silurus/ooxml/xlsx';
import { PptxViewer } from '@silurus/ooxml/pptx';
// DOCX β caller provides the <canvas>
const canvas = document.getElementById('docx-canvas') as HTMLCanvasElement;
const docx = new DocxViewer(canvas);
await docx.load('/document.docx');
docx.nextPage();
// XLSX β viewer manages its own <canvas> + tab bar
const container = document.getElementById('xlsx-container') as HTMLElement;
const xlsx = new XlsxViewer(container);
await xlsx.load('/workbook.xlsx');
// PPTX β caller provides the <canvas>
const canvas = document.getElementById('pptx-canvas') as HTMLCanvasElement;
const pptx = new PptxViewer(canvas);
await pptx.load('/deck.pptx');
pptx.nextSlide();
OMML equations (m:oMath / m:oMathPara) in .docx / .pptx are rendered with
MathJax + STIX Two Math.
That engine is ~3 MB, so it is opt-in: import the math engine from the separate
@silurus/ooxml/math entry and pass it to the viewer. Pass it and equations render;
omit it and the engine is referenced nowhere, so a bundler tree-shakes the ~3 MB
away entirely (equations are simply skipped). It is fully self-contained: no
network, no cross-origin requests.
import { DocxViewer } from '@silurus/ooxml/docx';
import { math } from '@silurus/ooxml/math';
const canvas = document.getElementById('docx-canvas') as HTMLCanvasElement;
const docx = new DocxViewer(canvas, { math }); // β equations now render
await docx.load('/paper-with-equations.docx');
The same math engine works for PptxViewer and the headless DocxDocument /
PptxPresentation APIs (which take math in their options). xlsx has no equation
support and never references the engine.
flowchart TB
subgraph build["π¦ Build-time (Rust β WebAssembly)"]
direction LR
docx_rs["packages/docx/parser/src/lib.rs"]
xlsx_rs["packages/xlsx/parser/src/lib.rs"]
pptx_rs["packages/pptx/parser/src/lib.rs"]
docx_rs -- wasm-pack --> docx_wasm["docx_parser.wasm"]
xlsx_rs -- wasm-pack --> xlsx_wasm["xlsx_parser.wasm"]
pptx_rs -- wasm-pack --> pptx_wasm["pptx_parser.wasm"]
end
subgraph browser["π Runtime (Browser)"]
subgraph core_pkg["@silurus/ooxml-core (shared primitives)"]
CORE["renderChart Β· resolveFill Β· applyStroke\nbuildCustomPath Β· autoResize Β· shared types"]
end
subgraph docx_pkg["@silurus/ooxml Β· docx"]
DV["DocxViewer"] --> DD["DocxDocument"]
DD --> DW["worker.ts\nγWeb Worker β parse onlyγ"]
DD --> DR["renderer.ts\nγCanvas 2D β main threadγ"]
end
subgraph xlsx_pkg["@silurus/ooxml Β· xlsx"]
XV["XlsxViewer"] --> XB["XlsxWorkbook"]
XB --> XW["worker.ts\nγWeb Worker β parse onlyγ"]
XB --> XR["renderer.ts\nγCanvas 2D β main threadγ"]
end
subgraph pptx_pkg["@silurus/ooxml Β· pptx"]
PV["PptxViewer"] --> PP["PptxPresentation"]
PP --> PW["worker.ts\nγWeb Worker β parse onlyγ"]
PP --> PR["renderer.ts\nγCanvas 2D β main threadγ"]
end
DR -. uses .-> CORE
XR -. uses .-> CORE
PR -. uses .-> CORE
end
docx_wasm --> DW
xlsx_wasm --> XW
pptx_wasm --> PW
DR --> canvas["<canvas>"]
XR --> canvas
PR --> canvas
All three formats follow the same shape: the worker parses the .docx / .xlsx / .pptx archive via WASM and posts a JSON model back to the main thread, where the renderer draws to the canvas. Rendering stays on the main thread so the canvas shares the document's FontFaceSet β an OffscreenCanvas in a worker has its own font registry and would silently fall back to a system font, producing subtly different text measurements (and wrap positions) from the installed theme webfonts. @silurus/ooxml-core holds the cross-format primitives that the three renderers all depend on: a unified chart renderer (bar / line / area / radar / waterfall), shape helpers (resolveFill, applyStroke, buildCustomPath, hexToRgba), the autoResize viewer utility, and the shared type definitions.
| File | Role |
|---|---|
packages/docx/parser/src/lib.rs |
Rust WASM parser β DOCX ZIP β Document JSON |
packages/xlsx/parser/src/lib.rs |
Rust WASM parser β XLSX ZIP β Workbook JSON |
packages/pptx/parser/src/lib.rs |
Rust WASM parser β PPTX ZIP β Presentation JSON |
packages/docx/src/renderer.ts |
Canvas 2D rendering engine with text layout (main thread) |
packages/xlsx/src/renderer.ts |
Canvas 2D rendering engine with virtual scroll (main thread) |
packages/pptx/src/renderer.ts |
Canvas 2D rendering engine (main thread) |
packages/*/src/worker.ts |
Web Worker: WASM init and parsing only (one per format) |
packages/*/src/viewer.ts |
Public Viewer API β canvas lifecycle, navigation |
packages/core/src/index.ts |
Cross-format primitives β chart renderer, shape helpers, autoResize, shared types |
// React 19.1 β vite-plugin-wasm required in vite.config.ts
import { useEffect, useRef, useState } from 'react';
import { PptxViewer } from '@silurus/ooxml/pptx';
export function PptxViewerComponent({ src }: { src: string }) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const viewerRef = useRef<PptxViewer | null>(null);
const [slide, setSlide] = useState({ current: 0, total: 0 });
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const viewer = new PptxViewer(canvas, {
onSlideChange: (i, total) => setSlide({ current: i, total }),
});
viewerRef.current = viewer;
viewer.load(src);
}, [src]);
return (
<div>
<canvas ref={canvasRef} style={{ width: 800 }} />
<button onClick={() => viewerRef.current?.prevSlide()}>βΉ Prev</button>
<span> {slide.current + 1} / {slide.total} </span>
<button onClick={() => viewerRef.current?.nextSlide()}>Next βΊ</button>
</div>
);
}
<!-- Vue 3.5 β useTemplateRef is a 3.5+ feature -->
<script setup lang="ts">
import { useTemplateRef, onMounted, ref } from 'vue';
import { PptxViewer } from '@silurus/ooxml/pptx';
const props = defineProps<{ src: string }>();
const canvas = useTemplateRef<HTMLCanvasElement>('canvas');
let viewer: PptxViewer | null = null;
const current = ref(0);
const total = ref(0);
onMounted(async () => {
viewer = new PptxViewer(canvas.value!, {
onSlideChange: (i, t) => { current.value = i; total.value = t; },
});
await viewer.load(props.src);
});
</script>
<template>
<div>
<canvas ref="canvas" style="width: 800px" />
<button @click="viewer?.prevSlide()">βΉ Prev</button>
<span> {{ current + 1 }} / {{ total }} </span>
<button @click="viewer?.nextSlide()">Next βΊ</button>
</div>
</template>
// Angular 19 β standalone component with signal-based state
import {
Component, ElementRef, viewChild,
signal, AfterViewInit,
} from '@angular/core';
import { PptxViewer } from '@silurus/ooxml/pptx';
@Component({
selector: 'app-pptx-viewer',
standalone: true,
template: `
<div>
<canvas #canvas style="width: 800px"></canvas>
<button (click)="prev()">βΉ Prev</button>
<span> {{ current() + 1 }} / {{ total() }} </span>
<button (click)="next()">Next βΊ</button>
</div>
`,
})
export class PptxViewerComponent implements AfterViewInit {
canvasEl = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');
current = signal(0);
total = signal(0);
private viewer?: PptxViewer;
ngAfterViewInit(): void {
this.viewer = new PptxViewer(this.canvasEl().nativeElement, {
onSlideChange: (i, t) => { this.current.set(i); this.total.set(t); },
});
this.viewer.load('/deck.pptx');
}
prev(): void { this.viewer?.prevSlide(); }
next(): void { this.viewer?.nextSlide(); }
}
Add
"allowSyntheticDefaultImports": trueand configure@angular-builders/custom-webpack(or useesbuildbuilder) with WASM support in your Angular workspace.
<!-- Svelte 5 β runes syntax ($props, $state) -->
<script lang="ts">
import { onMount } from 'svelte';
import { PptxViewer } from '@silurus/ooxml/pptx';
let { src }: { src: string } = $props();
let canvas: HTMLCanvasElement;
let viewer: PptxViewer;
let current = $state(0);
let total = $state(0);
onMount(async () => {
viewer = new PptxViewer(canvas, {
onSlideChange: (i, t) => { current = i; total = t; },
});
await viewer.load(src);
});
</script>
<div>
<canvas bind:this={canvas} style="width: 800px"></canvas>
<button onclick={() => viewer?.prevSlide()}>βΉ Prev</button>
<span> {current + 1} / {total} </span>
<button onclick={() => viewer?.nextSlide()}>Next βΊ</button>
</div>
// SolidJS 1.9
import { createSignal, onMount, onCleanup } from 'solid-js';
import { PptxViewer } from '@silurus/ooxml/pptx';
export function PptxViewerComponent(props: { src: string }) {
let canvasEl!: HTMLCanvasElement;
let viewer: PptxViewer | undefined;
const [current, setCurrent] = createSignal(0);
const [total, setTotal ] = createSignal(0);
onMount(async () => {
viewer = new PptxViewer(canvasEl, {
onSlideChange: (i, t) => { setCurrent(i); setTotal(t); },
});
await viewer.load(props.src);
});
onCleanup(() => { /* viewer?.destroy?.() */ });
return (
<div>
<canvas ref={canvasEl} style={{ width: '800px' }} />
<button onClick={() => viewer?.prevSlide()}>βΉ Prev</button>
<span> {current() + 1} / {total()} </span>
<button onClick={() => viewer?.nextSlide()}>Next βΊ</button>
</div>
);
}
// Qwik 2.0 β dynamic import to keep WASM out of SSR bundle
import { component$, useSignal, useVisibleTask$ } from '@builder.io/qwik';
import type { PptxViewer as PptxViewerType } from '@silurus/ooxml/pptx';
export const PptxViewerComponent = component$<{ src: string }>(({ src }) => {
const canvasRef = useSignal<HTMLCanvasElement>();
const current = useSignal(0);
const total = useSignal(0);
let viewer: PptxViewerType | undefined;
// useVisibleTask$ runs only in the browser, never during SSR
useVisibleTask$(async () => {
if (!canvasRef.value) return;
const { PptxViewer } = await import('@silurus/ooxml/pptx');
viewer = new PptxViewer(canvasRef.value, {
onSlideChange: (i, t) => { current.value = i; total.value = t; },
});
await viewer.load(src);
});
return (
<div>
<canvas ref={canvasRef} style={{ width: '800px' }} />
<button onClick$={() => viewer?.prevSlide()}>βΉ Prev</button>
<span> {current.value + 1} / {total.value} </span>
<button onClick$={() => viewer?.nextSlide()}>Next βΊ</button>
</div>
);
});
| Category | Feature | Status |
|---|---|---|
| Document | Page rendering | β |
| Page size and margins | β | |
| Headers / footers (default / first / even) | β | |
| Section breaks (continuous / nextPage / oddPage / evenPage) | β | |
| Text | Paragraphs | β |
| Bold, italic, underline, strikethrough | β | |
| Font family, size, color | β | |
| Hyperlinks | β | |
Superscript / subscript (w:vertAlign) |
β | |
Ruby annotations / furigana (w:ruby) |
β | |
| Formatting | Paragraph alignment (left/center/right/justify) | β |
| Line spacing (auto / atLeast / exact) | β | |
Line grid (w:docGrid, Β§17.6.5) |
β | |
| Margin collapsing between paragraphs | β | |
| Indents and tab stops | β | |
| Lists (bullet and numbered) | β | |
| Paragraph styles (Heading 1β9, Normal, custom) | β | |
Table style w:pPr cascade (Β§17.7.6) |
β | |
Table style borders / shading / banding (tblStylePr, cnfStyle, Β§17.4.7) |
β | |
| Table of contents (TOC field) β dot leaders, right-aligned page numbers | β | |
| keepNext / keepLines / widowControl | β | |
| Elements | Tables (with borders, fills, merges, banding, alignment) | β |
Math equations (OMML m:oMath / m:oMathPara, rendered via MathJax β opt-in @silurus/ooxml/math) |
β | |
| Images (inline and anchored, with text wrap) | β | |
| Text boxes / drawing shapes | β | |
| WMF / EMF metafile images (legacy vector) | β Not planned | |
| Advanced | Footnote / endnote reference markers | β |
Track changes (w:ins / w:del β author-coloured underline / strikethrough) |
β | |
| Comments / footnote bodies (parsed, not yet rendered inline) | β οΈ | |
| Mail merge fields | β Not planned | |
| Interaction | Text selection (transparent overlay, native copy) | β |
| Category | Feature | Status |
|---|---|---|
| Workbook | Multiple sheets, sheet names | β |
Sheet tab colors (<sheetPr><tabColor> β theme / tint / indexed / rgb) |
β | |
| Cells | Text, number, boolean, error values | β |
Formula results (from cached <v>) |
β | |
| Dates (ECMA-376 date format codes) | β | |
| Rich text (per-run formatting) | β | |
| Formatting | Bold, italic, underline (single / double / singleAccounting / doubleAccounting), strikethrough |
β |
Superscript / subscript (vertAlign) |
β | |
| Font family, size, color | β | |
| Cell background color (solid + gradient) | β | |
Pattern fills (gray125 / gray0625 / lightGray / mediumGray / darkGray and the 12 light* / dark* directional hatches) |
β | |
| Borders (thin, medium, thick, hair, double, dashed, dotted, dashDotDot, β¦) | β | |
Diagonal borders (diagonalUp / diagonalDown, single + double) |
β | |
| Horizontal / vertical alignment | β | |
| Text wrapping | β | |
Number formats (0.00, %, #,##0, custom date/time) |
β | |
| Structure | Merged cells | β |
| Frozen panes | β | |
| Row / column sizing (custom widths and heights) | β | |
| Hidden rows / columns | β | |
| Elements | Images (<xdr:twoCellAnchor>) |
β |
Drawing shapes / text boxes (xdr:sp, xdr:txBody) |
β | |
| Charts (bar, line, area, radar, scatter / bubble) | β | |
Chart markers (circle / square / diamond / triangle / x / plus / star / dot / dash, per-point <c:dPt> overrides) |
β | |
Chart data labels (<c:dLbl> per-point with CELLRANGE / VALUE / SERIESNAME / CATEGORYNAME field references, position l/r/t/b/ctr/outEnd) |
β | |
Chart error bars (<c:errBars> X/Y direction, cust / fixedVal / stdErr / stdDev / percentage, dashed/styled lines) |
β | |
Chart manual layout (<c:title><c:layout> and <c:plotArea><c:layout>) |
β | |
Sparklines (x14:sparklineGroup β line / column / win-loss, with markers and high/low/first/last/negative highlights) |
β | |
| Advanced | Conditional formatting (cellIs, colorScale, dataBar, iconSet, top10, aboveAverage) |
β |
| Slicers (static, Office 2010 extension) | β | |
| Pivot tables | β Not planned | |
| Data validation / comments | β Not planned | |
| Interaction | Cell selection (single / range / row / column / all) | β |
| Excel-style row / column header highlight on selection | β | |
| Shift+click to extend, Ctrl+C to copy as TSV | β | |
| Text selection inside cells (transparent overlay) | β | |
onSelectionChange callback, getCellAt(x, y) API |
β | |
Zoom slider (Excel-style, right of the tab bar, 10β400% with 100% centered; showZoomSlider option) |
β |
| Category | Feature | Status |
|---|---|---|
| Slides | Slide rendering | β |
| Slide layout / master inheritance | β | |
| Slide size (custom dimensions) | β | |
| Slide background (solid, gradient, image) | β | |
| Slide numbers | β | |
| Notes pages | β | |
| Animations / transitions | β Not planned | |
| Element types | Shapes (sp) |
β |
Pictures (pic) |
β | |
Groups (grpSp) with nested transforms |
β | |
Connectors (cxnSp) |
β | |
Tables (tbl in graphicFrame) |
β | |
| Charts (bar, line, area, radar, waterfall) | β | |
| Charts (pie, doughnut) | β | |
Charts (scatter β scatterStyle marker / line / smooth variants) |
β | |
Charts (bubble β bubbleSize per-point area scaling) |
β | |
| SmartArt | β | |
| OLE objects | β | |
| Video / audio (poster + interactive playback) | β | |
Ink / handwriting (p:contentPart, raster fallback) |
β | |
| Shape geometry | 130+ preset shapes (prstGeom) |
β |
Custom geometry (custGeom) on shapes and pictures (clipping) |
β | |
| Rotation and flip (flipH / flipV) | β | |
| 3D preset shapes | β | |
| Fills | Solid fill (solidFill) |
β |
Linear / radial gradient (gradFill) |
β | |
No fill (noFill) |
β | |
Pattern fill (pattFill) β 30 preset bitmaps incl. pct5βpct90 / horz / vert / cross / diag / grid / brick / check / trellis |
β | |
Image fill on shapes (blipFill in sp) |
β | |
| Strokes | Solid line color and width | β |
| Dash / dot styles | β | |
Arrow heads (headEnd / tailEnd) |
β | |
| Compound / double lines (`<a:ln cmpd="dbl | thinThick | |
| Shape effects | Drop shadow (outerShdw) |
β |
Glow (glow β radius + colour) |
β | |
Inner shadow (innerShdw β parsed; rendering follow-up) |
β οΈ | |
Soft edge (softEdge β parsed; rendering follow-up) |
β οΈ | |
Reflection (reflection β parsed; rendering follow-up) |
β οΈ | |
| Bevel / 3D extrusion | β | |
| Text β characters | Bold, italic, strikethrough (incl. dblStrike) |
β |
Underline styles (sng / dbl / dotted / dash / dashLong / dotDash / dotDotDash / wavy / wavyDbl and *Heavy variants) |
β | |
Per-run underline colour (uFill / uFillTx) |
β | |
| Font family, size, color | β | |
East Asian font (rPr > a:ea β separate typeface for CJK glyphs) |
β | |
Caps transform (all / small) |
β | |
Letter spacing (spc) |
β | |
| Superscript / subscript | β | |
Hyperlinks (hlinkClick β theme hlink colour + auto underline) |
β | |
Text shadow (rPr > effectLst > outerShdw) |
β | |
Text outline (rPr > a:ln) |
β | |
Math equations (OMML m:oMath / m:oMathPara, incl. a14:m / mc:AlternateContent; STIX Two Math via MathJax β opt-in @silurus/ooxml/math) |
β | |
| Text β paragraphs | Horizontal alignment (left / center / right / justify) | β |
| Vertical anchor (top / center / bottom) | β | |
Line spacing (spcPct, spcPts) |
β | |
| Space before / after paragraph | β | |
| Bullet points (character and auto-numbered) | β | |
| Tab stops | β | |
| Indent / margin | β | |
Vertical text (bodyPr@vert β vert / vert270 / eaVert) |
β | |
Right-to-left paragraph (pPr@rtl β Arabic / Hebrew default alignment + browser bidi) |
β | |
| Text β body | Text padding (insets) | β |
| normAutoFit (shrink to fit) | β | |
| spAutoFit (expand box; suppresses wrap when text fits in one line) | β | |
| Word wrap / no wrap | β | |
Multi-column text body (numCol / spcCol β balanced flow) |
β | |
Theme object-default inheritance (<a:objectDefaults><a:txDef|spDef> bodyPr fallback) |
β | |
| Tables | Cells, rows, columns | β |
| Cell merges (horizontal / vertical) | β | |
| Cell borders | β | |
| Cell fills (solid / gradient) | β | |
Cell diagonal lines (lnTlToBr / lnBlToTr) |
β | |
| Table theme styles (74 built-in PowerPoint presets) | β | |
| Theme | Scheme colors (dk1/lt1/accent1β6) | β |
Font scheme (+mj-lt, +mn-lt) |
β | |
| lumMod / lumOff / alpha transforms | β | |
| Interaction | Text selection (transparent overlay, native copy) | β |
A note on text selection. Across DOCX / PPTX / XLSX, text selection is currently implemented by rendering glyphs to the canvas while overlaying a transparent DOM layer that mirrors the canvas text positions for native browser selection. This dual-layer approach is a deliberate stop-gap: once the Canvas
drawElementAPI (proposed in WICG/html-in-canvas, currently in Chromium Origin Trial) ships across browsers, the project plans to migrate to a single DOM-as-source-of-truth pipeline where the canvas mirrors the DOM directly β eliminating the duplication while keeping z-order correctness and native selection / a11y.
packages/markdown/ β @silurus/ooxml-markdown and the ooxml-md CLI convert .pptx / .docx / .xlsx to GitHub-flavoured markdown via the workspace WASM parsers. Same projection used by the MCP server (~21Γ smaller than the raw XML on the demo deck, ~8% bigger than a flat-text extractor). Includes a node20-based GitHub Action for bulk repo-wide conversion.packages/node/ β Node-side parsers (@silurus/ooxml-node) exposing parsePptx / parseDocx / parseXlsx / parseXlsxAllSheets against the workspace WASM artifacts, with no DOM or Web Worker dependency. Useful for CI checks, headless rendering pipelines, and CLI tools. Includes an ooxml-thumbnail CLI (pptx-only first pass; requires skia-canvas).packages/vscode-extension/ β VS Code extension (ooxml-viewer) that registers CustomEditorProviders for .docx, .xlsx, and .pptx, and (opt-in) auto-installs and registers the ooxml-mcp-server so AI coding agents in the same window (Copilot Agent mode, Claude, β¦) can read those files via dedicated tools.packages/mcp-server/ β Rust MCP server (ooxml-mcp-server) exposing the parsers as tools for AI agents (Claude, Copilot, Codex, etc.). Provides structured queries (docx_get_structure, xlsx_get_cell_range, pptx_get_slide_structure, β¦) so agents can inspect OOXML files without shelling out to unzip. Prebuilt binaries are attached to each GitHub Release for macOS / Linux / Windows; the VS Code extension downloads them on demand.# Install dependencies
pnpm install
# Build all WASM parsers (requires Rust + wasm-pack)
pnpm build:wasm
# Start Storybook dev server (port 6006)
pnpm storybook
# Type-check all packages
pnpm typecheck
# Run visual regression tests (local only β not run in CI)
pnpm vrt
# Adopt the current rendering as the new reference baseline
UPDATE_REFS=1 pnpm vrt
# Build the library
pnpm build
cd packages/docx/parser && wasm-pack build --target web && cp pkg/docx_parser_bg.wasm pkg/docx_parser.js ../src/wasm/
cd packages/xlsx/parser && wasm-pack build --target web && cp pkg/xlsx_parser_bg.wasm pkg/xlsx_parser.js ../src/wasm/
cd packages/pptx/parser && wasm-pack build --target web && cp pkg/pptx_parser_bg.wasm pkg/pptx_parser.js ../src/wasm/
HTMLCanvasElement. No script, link, form, or other active content from the source file is executed or injected into the DOM.maxZipEntryBytes (bytes) β raise it for legitimate decks with large embedded media, lower it to tighten the budget for untrusted input:new PptxViewer(canvas, { maxZipEntryBytes: 64 * 1024 * 1024 }); // 64 MiB
Supported uniformly by DocxViewer, PptxViewer, and XlsxViewer. Zero / negative values fall back to the default.useGoogleFonts: true to the relevant Viewer / load(...) options β supported uniformly by DocxViewer, PptxViewer, and XlsxViewer. Enabling that option causes the end-user's browser to send an HTTP request (IP and User-Agent) to fonts.googleapis.com, which may have GDPR implications for your application β consider self-hosting the required fonts via @font-face instead.roxmltree, which does not resolve external entities (XXE-safe by default).MIT
Still, looks pretty; if it actually has proper testing, could close the gap. Code not being the hard part is a major impediment to good software coming out of these things.
If Microsoft canβt get consistent rendering of word docs between Word for Windows, Word for macOS and Office 365, I donβt like anyone elseβs chances.
Bit identical/pixel-faithful reproductions are easy to verifyβ¦
"oh yeah? Show me what you made, you can't, nobody can, it's all just AI psychosis"
"I made a pixel perfect Office document viewer"
"well... I wish you hadn't"
Yeah, it does.
https://ooxml.silurus.dev/storybook/?path=/story/docxviewer-...
I'm not familiar with this application, so perhaps I'm missing a step, and editing mode.
Well, yes, because it doesn't work.
> Bit identical/pixel-faithful reproductions are easy to verifyβ¦
And yet the prompter put so little effort in they couldn't even verify the software they prompted for does what it's supposed to.
The best developers are lazy.