Home تقنية Silurus/ooxml: Pixel-faithful Office documents, rendered in the browser | itg-ar.com

Silurus/ooxml: Pixel-faithful Office documents, rendered in the browser | itg-ar.com

2
0
Silurus/ooxml: Pixel-faithful Office documents, rendered in the browser | itg-ar.com

Silurus/ooxml: Pixel-faithful Office documents, rendered in the browser

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.

Demo (Storybook)
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.

npm install @silurus/ooxml
# or
pnpm add @silurus/ooxml

Bundler note: this package embeds .wasm files. With Vite add vite-plugin-wasm; with webpack use experiments.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 a math engine — 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
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 + tab bar
const container = document.getElementById(‘xlsx-container’) as HTMLElement;
const xlsx = new XlsxViewer(container);
await xlsx.load(‘/workbook.xlsx’);

// PPTX — caller provides the
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.

Architecture diagram

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(““)
XR –> canvas
PR –> canvas

Loading

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
// 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(null);
const viewerRef = useRef(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 (