Render Office documents in the browser with WebAssembly
Browser-based, read-only DOCX/XLSX/PPTX viewer with Rust/WASM parsers and Canvas 2D rendering - fast, bundle-conscious, and built entirely by AI agents.
0.86.1Add to Favorites
Why it matters
Enable developers to display Word, Excel, and PowerPoint files directly in web applications without server-side conversion or third-party APIs, using Rust-compiled WebAssembly parsers and Canvas 2D rendering for full client-side document viewing.
Outcomes
What it gets done
Parse DOCX, XLSX, and PPTX files in the browser using WebAssembly-compiled Rust parsers
Render Office documents to HTML Canvas with accurate text layout, charts, and formatting
Provide headless rendering engines for custom UI composition beyond built-in viewers
Optionally render mathematical equations with MathJax while keeping the engine tree-shakeable
Source
Get it from source
Spark does not host a copy of it.
Open sourceReports
Agent outcome reports
No reports yet
Overview
Silurus/ooxml
Office Open XML Viewer is a browser-based, read-only viewer for DOCX, XLSX, and PPTX documents, rendering to HTML Canvas via Rust parsers compiled to WebAssembly. It exposes both built-in viewer UIs and headless engines for custom UI, with opt-in equation rendering (MathJax), chart/TIFF extras, and off-main-thread worker rendering, and states its entire codebase is written by AI coding agents against the ECMA-376/ISO-29500 spec. Use it to display real Office documents in a web app or VS Code extension without server-side conversion or full editing support - editing, mutation, and round-trip saving are explicitly out of scope by design.
What it does
Office Open XML Viewer (@silurus/ooxml) is a browser-based, read-only viewer for DOCX, XLSX, and PPTX documents that renders to an HTML Canvas. Its parsers are written in Rust and compiled to WebAssembly; rendering runs through the Canvas 2D API. Each format also exposes a headless engine (DocxDocument, XlsxWorkbook, PptxPresentation) that renders into a caller-supplied canvas, so you can build your own scroll views, thumbnail grids, or master-detail panes instead of using the built-in viewer UI. Notably, the project states its entire codebase - Rust parsers, TypeScript renderers, tests, and tooling - is written by AI coding agents (primarily Claude and Codex) through iterative prompting against the ECMA-376/ISO-29500 specification, with no human-written application code.
When to use - and when NOT to
Use it to render OOXML documents in a web app or VS Code extension without a server round-trip, when editing is not required - the project is deliberately scoped to read-only viewing (selection, copy, search, hit testing, and annotation stay in scope; editing, mutation APIs, and saving/round-tripping are explicitly out of scope, with editing-focused forks welcomed instead). Bring your own canvas for DocxViewer/PptxViewer/XlsxSheetViewer, or let XlsxViewer manage its own canvas plus a tab bar. It works zero-config with webpack 5, Next.js, and Vite 8; Vite 7's dev server and esbuild-based builders (including the Angular CLI) need a small manual step (excluding the package from dependency optimization, or copying the .wasm asset and passing a wasmUrl) because they don't process the new URL(...)-based WASM asset reference the same way production builds do.
Inputs and outputs
Input is a .docx/.xlsx/.pptx file (a URL string or an ArrayBuffer), loaded via .load() on a viewer or headless engine; XlsxSheetViewer additionally accepts delimited text (CSV/TSV/pipe-delimited) as a lightweight, type-agnostic preview convenience, not a general tabular model. Output is a rendered page, sheet, or slide painted to canvas, plus a read-only, serializable selection context (getSelectionContext()) - bounded populated cells, retained formulas and display text - intended for handing an Excel selection to an AI/MCP request. Off-main-thread mode (mode: 'worker') parses and renders inside a Web Worker and hands the main thread a ready ImageBitmap instead, trading a small per-frame transfer cost for a main thread that stays free for scrolling and input.
Integrations
npm install @silurus/ooxml
Equation rendering (OMML via MathJax + STIX Two Math, across DOCX/PPTX/XLSX) and chart/format extras (ChartEx, 3-D charts, Region Maps, TIFF) are all opt-in, separately imported modules injected once at load time - each stays out of the default bundle graph unless a document actually needs it, and the ~3 MB math engine is fetched on demand only the first time a document contains an equation. The Region Map renderer works fully offline against a pinned Natural Earth dataset with no network calls. DocxScrollViewer and PptxScrollViewer render an entire document as one continuously scrolling, PDF-reader-style surface (taking a container div rather than a canvas) for a different UX than the page-at-a-time default viewers. Live runnable examples exist for React, Vue, Svelte, and Solid.
Who it's for
Web and VS Code extension developers who need to display real Office documents in the browser without a server-side conversion step, and specifically ones who need it fast and bundle-conscious (opt-in modules, worker-mode rendering, framework-agnostic canvas APIs) rather than a full-featured editor - anyone needing actual editing or round-trip saving should look elsewhere, since that is explicitly out of scope here. License details are in the project's LICENSE file in its GitHub repository. Two features are explicitly out of scope: mail merge fields in Word documents are not planned, and Excel pivot tables render their saved state read-only, without refresh, recalculation, filtering, or interactivity.
Source README
Why this project exists - a note from the author
OOXML's behavior is defined by a written specification (ECMA-376 / ISO-29500), and there is a clear answer to compare against: Word, Excel, and PowerPoint themselves. In principle, anyone with enough patience could have built a faithful viewer - the spec says what to implement, and the Office applications show whether you got it right.
In practice, it didn't happen. For more than a decade, no free, open-source library reached a rendering quality good enough for real use. There are a few commercial libraries with decent fidelity (and editing support), but their pricing makes them hard to adopt casually. I think the reason is simply cost: the specification is huge, and reading and implementing it faithfully takes far more effort than volunteers can afford.
Generative AI changed that. A viewer is an unusually good fit for AI-driven iterative development ("vibe coding"): there is a spec to read and a correct output to aim for, so the work comes down to interpreting the specification and refining the rendering until it matches. Limiting the scope to viewing also avoids the most serious risk an Office library can carry - corrupting a user's files.
So I'm building this library with AI coding agents, spec-first, and keeping it free to use. For some documents it already reproduces the desktop Office applications more faithfully than commercial libraries - and sometimes even the official Microsoft 365 web apps.
Office Open XML Viewer
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 live framework examples for runnable React, Vue, Svelte, and Solid projects.
Project scope: read-only viewing
This project is intentionally a read-only viewer. Editing interfaces, mutation APIs, editable or lossless document models, saving / round-tripping, and partial editing of DOCX, XLSX, or PPTX files are out of scope. Read-only interactions such as selection, copy, search, shape IDs, hit testing, and annotation or external-tool integrations remain in scope, and editing-focused forks are welcome. See #496 for the rationale.
| DOCX | XLSX | PPTX |
|---|---|---|
npm install @silurus/ooxml
# or
pnpm add @silurus/ooxml
Bundler note: the Rust parsers ship as real
.wasmasset files next to the
JavaScript, referenced with the standardnew URL('…', import.meta.url)form
and fetched (streaming-compiled) at load time. Verified to work with zero
config: webpack 5, Next.js (Turbopack, dev and build), Vite 8
(dev and build), Vite 7 production builds, and a plain<script type="module">with no bundler at all. Two setups need a hand:
- Vite 7 dev server: the dependency optimizer rewrites the asset reference
into its own cache path and the load fails (fixed in Vite 8). AddoptimizeDeps: { exclude: ['@silurus/ooxml'] }to yourvite.config-
production builds are unaffected.- esbuild / Angular CLI (whose application builder is esbuild-based):
new URLasset references are not processed
(esbuild#795). Copy the.wasminto your served output and point the viewer at it with thewasmUrlload option. For Angular CLI, copy the required*_parser_bg.wasmasset fromnode_modules/@silurus/ooxml/distinto the
served output and pass its public URL to the viewer.
wasmUrlalso serves the parser WASM from a CDN or any path you control:new DocxViewer(canvas, { wasmUrl: 'https://cdn.example.com/docx_parser_bg.wasm' });
Bundle size note: the package is ESM-only (
.mjs). npm's Unpacked Size
includes every entry and optional asset, so it is not the size of a
format-specific application graph. See the current production measurements on
the stable Bundle size page. Import
only the format and optional renderer entries your application needs. In main
mode, MathJax, ChartEx, 3-D, Region Map and TIFF implementations remain outside the
graph unless imported; the separately loaded worker asset is self-contained.
Quick Start
import { DocxViewer } from '@silurus/ooxml/docx';
import { XlsxSheetViewer, XlsxViewer, XlsxWorkbook } from '@silurus/ooxml/xlsx';
import { PptxViewer } from '@silurus/ooxml/pptx';
// DOCX — caller provides the <canvas>
const docxCanvas = document.getElementById('docx-canvas') as HTMLCanvasElement;
const docx = new DocxViewer(docxCanvas);
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');
xlsx.setSelection('B2:D5'); // A1 strings describe geometry; the normalized upper-left is ActiveCell
// Excel keeps Selection and ActiveCell separate. Use structured state when the
// active cell or Shift-extension anchor is not the area's upper-left cell.
xlsx.setSelection({
areas: [{ kind: 'cells', top: 2, left: 2, bottom: 5, right: 4 }],
activeAreaIndex: 0,
activeCell: { row: 3, col: 3 },
extensionAnchor: { row: 2, col: 2 },
});
// Read-only, serializable context for an AI/MCP request. Populated cells are
// bounded and detached; formulas and Viewer-formatted display text are retained.
const context = xlsx.getSelectionContext({
maxCells: 1_000,
maxTextCharacters: 1_048_576,
});
// XLSX active-sheet surface only — caller provides the <canvas>
const sheetCanvas = document.getElementById('xlsx-canvas') as HTMLCanvasElement;
const sheet = new XlsxSheetViewer(sheetCanvas);
await sheet.load('/workbook.xlsx');
await sheet.goToSheet(1);
// Share one parsed workbook across independently scrollable sheet canvases,
// including canvases created in same-origin popup windows.
const workbook = await XlsxWorkbook.load('/workbook.xlsx');
const firstCanvas = document.getElementById('xlsx-first-canvas') as HTMLCanvasElement;
const secondCanvas = document.getElementById('xlsx-second-canvas') as HTMLCanvasElement;
const firstSheet = XlsxSheetViewer.fromWorkbook(firstCanvas, workbook);
const secondSheet = XlsxSheetViewer.fromWorkbook(secondCanvas, workbook);
await Promise.all([
firstSheet.goToSheet(0),
secondSheet.goToSheet(1),
]);
// Each viewer borrows the workbook; the caller closes it after the viewers.
firstSheet.destroy();
secondSheet.destroy();
workbook.destroy();
// PPTX — caller provides the <canvas>
const pptxCanvas = document.getElementById('pptx-canvas') as HTMLCanvasElement;
const pptx = new PptxViewer(pptxCanvas);
await pptx.load('/deck.pptx');
pptx.nextSlide();
XlsxSheetViewer also offers a deliberately small delimited-text preview convenience
that reuses the Excel-style sheet surface. It has the same source and ownership
lifecycle as XLSX loading: a string is fetched as a URL, an ArrayBuffer is
caller-owned, a successful reload replaces and disposes the previous source,
and destroy() releases the active source. There is no format auto-detection or
general-purpose tabular model, and XlsxWorkbook / XlsxViewer remain
OOXML-only. Every delimited field stays text, so leading zeroes, long identifiers,
dates, and =... values are displayed without type or formula inference.
This convenience covers delimiter-based text regardless of filename extension;
fixed-width PRN, DIF, and SYLK remain separate formats and are not parsed here.
const csvFile = document.querySelector('input[type=file]') as HTMLInputElement;
const csvBytes = await (csvFile.files?.[0] as File).arrayBuffer();
await sheet.load(csvBytes, { format: 'csv' });
// String sources are URLs, just like sheet.load('/workbook.xlsx').
await sheet.load('/export.tsv', { format: 'tsv', encoding: 'windows-1252' });
// Extensions such as .txt, .dat, and .psv do not imply one delimiter.
await sheet.load('/report.dat', { format: 'delimited-text', delimiter: '|' });
Rendering equations
OMML equations (m:oMath / m:oMathPara) in .docx, .pptx and .xlsx 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 asset is not fetched or evaluated (equations are simply skipped;
the on-demand render-worker asset retains a small loader). When you do pass it, the ~3 MB engine ships
as a standalone asset file next to the bundle rather than an inline data URL, and is
fetched on demand - only the first time a document actually contains an equation, so
equation-free documents never pay for it. It is fully self-contained: served from your own
origin, 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 every viewer (DocxViewer, PptxViewer,XlsxViewer) and every headless engine (DocxDocument, PptxPresentation,XlsxWorkbook). You inject it once where you create the object - the viewer
constructor or the .load() options - and every render reuses it; it is never a
per-render argument. (Excel stores "Insert > Equation" as OMML inside the shared
DrawingML <xdr:txBody> grammar, so XlsxViewer renders equations embedded in
shapes / text boxes the same way.)
Custom Google Fonts CSS service
When useGoogleFonts is enabled, set googleFontsCssOrigin to use a regional
mirror or an internal Google Fonts-compatible CSS service. The option works for
DOCX, XLSX, and PPTX engines and self-loading viewers, including worker and
progressive loading:
await DocxDocument.load(data, {
useGoogleFonts: true,
googleFontsCssOrigin: 'https://fonts.internal.example',
});
Supply an HTTP(S) origin only, without a path, query, fragment, or credentials.
Built-in stylesheet paths and family queries are preserved. Relative font-file
URLs in the returned CSS resolve against the stylesheet's final URL, including
after redirects. A fully internal deployment must therefore host or proxy the
font files as well as the CSS and return URLs reachable by the browser. Configure
CORS and your application's connect-src and font-src CSP directives for the
selected CSS and font hosts. Failures retain the existing system-font fallback
and do not retry the public Google Fonts service.
Optional rendering modules
Classic DrawingML 2-D chart families are included in every format entry.
Microsoft ChartEx, model-space 3-D charts and offline country-level Region Maps
are separate entries. TIFF decoding is also a separate entry shared by DOCX,
XLSX and PPTX. Inject the modules once in the same load options object asmath; omitting one keeps its main-mode implementation out of the ordinary
format graph. The built-in modules work in both main and worker modes. Without chartEx,
ChartEx families show the standard unsupported-chart placeholder. WithoutthreeD, 3-D chart groups
fall back to their canonical 2-D family. Without regionMap, Region Maps show
the standard unsupported-chart placeholder. Without tiff, recognized TIFF
parts report a render error instead of relying on the browser decoder or being
silently omitted. The code-size boundary applies to
the default main-mode application graph. The separately loaded render-worker
asset stays self-contained for broad bundler compatibility and therefore
contains its built-in optional renderer implementations.
import { XlsxViewer } from '@silurus/ooxml/xlsx';
import { threeD } from '@silurus/ooxml/three-d';
import { regionMap } from '@silurus/ooxml/region-map';
import { chartEx } from '@silurus/ooxml/chart-ex';
import { tiff } from '@silurus/ooxml/tiff';
const container = document.getElementById('xlsx-container') as HTMLElement;
const workbookViewer = new XlsxViewer(container, {
threeD,
regionMap,
chartEx,
tiff,
mode: 'worker',
});
await workbookViewer.load('/workbook-with-advanced-charts.xlsx');
The Region Map renderer is deterministic and network-free. It uses a pinned
Natural Earth country dataset, supports authored world projections and
two/three-stop value ramps, and fails closed for cached identities or
sub-country/view-specific layouts that the bounded offline model cannot yet
represent safely. The specification/Office evidence boundary for automatic
chart behavior is documented in
Chart compatibility evidence and scope.
The bounded TIFF 6.0 codec decodes the first IFD/page of stripped, top-left
images in the uncompressed bilevel, 8-bit grayscale, RGB, RGBA and process-CMYK
classes, plus 1-bit CCITT Group 4. Unsupported or malformed first-page classes
fail with a diagnostic render error. It is not a general-purpose TIFF library,
but as a small
by-product the same tiff.render() method can provide a simple preview of a
supported standalone TIFF file. Its optional TiffRenderOptions argument can
request a bounded display-sized bitmap; the Office viewers supply those targets
automatically for layout-backed image paints. Try Yours and the VS Code extension enable
every first-party optional module; library applications can choose only the entries they need.
See Production decisions for the full
module list and Bundle size for current
measurements.
Off-main-thread rendering
By default the headless engines parse in a worker but render on the main thread.
Pass mode: 'worker' to .load() to normally parse and render inside a Web
Worker - the main thread presents the returned ImageBitmap via abitmaprenderer context, keeping it free for scrolling and input. It requiresWorker + OffscreenCanvas.
import { PptxPresentation } from '@silurus/ooxml/pptx';
// Render entirely inside a Web Worker — the main thread only paints bitmaps.
const pres = await PptxPresentation.load('/deck.pptx', { mode: 'worker' });
const canvas = document.getElementById('pptx-canvas') as HTMLCanvasElement;
const bitmap = await pres.renderSlideToBitmap(0, { width: 960, dpr: window.devicePixelRatio });
const ctx = canvas.getContext('bitmaprenderer') as ImageBitmapRenderingContext;
ctx.transferFromImageBitmap(bitmap); // consumes the bitmap
The *ToBitmap method exists on all three engines -PptxPresentation.renderSlideToBitmap(slideIndex, opts),DocxDocument.renderPageToBitmap(pageIndex, opts), andXlsxWorkbook.renderViewportToBitmap(sheetIndex, viewport, opts) (the xlsx
variant requires opts.width and opts.height, since a worker has no DOM
element to measure). They work in both modes - in main mode they render to
an internal OffscreenCanvas - so you can write mode-agnostic code.
Notes:
- The returned
ImageBitmapis owned by the caller:transferFromImageBitmap
consumes it, or callbitmap.close()when done. - The canvas-target methods (
renderSlide(canvas),renderPage(canvas),renderViewport(canvas)) are unavailable in worker mode - use the*ToBitmap
variants instead. - The built-in math, ChartEx, 3-D chart, and Region Map renderers work in both
modes through the samemath,chartEx,threeD, andregionMapoptions.
Custom renderer objects are main-realm code and therefore use the feature's
documented fallback inmode: 'worker'. - A DOCX document that requires browser-only OpenType vertical-glyph selection
automatically uses effective main mode for correct shaping. Readdocument.modeafter loading when your integration needs to observe this
fallback. - Trade-off: worker mode keeps the main thread responsive, but each frame is
transferred back as anImageBitmap, so a single render can be marginally
slower thanmode: 'main'. Choose it for non-blocking UI, not raw speed.
Continuous scroll viewers
DocxScrollViewer and PptxScrollViewer render the whole document as one
vertically-scrolling, PDF-reader-style surface instead of a single page/slide at
a time. Unlike DocxViewer / PptxViewer (which take a <canvas>), the scroll
viewers take a container <div> - they own the scroll host, virtualize the
page/slide list (only the visible window plus a small overscan is mounted), and
recycle canvases as you scroll.
import { DocxScrollViewer } from '@silurus/ooxml/docx';
const container = document.getElementById('docx-scroll') as HTMLElement;
const viewer = new DocxScrollViewer(container);
await viewer.load('/document.docx');
// viewer.scrollToPage(3);
// viewer.pageCount, viewer.topVisiblePage
import { PptxScrollViewer } from '@silurus/ooxml/pptx';
const container = document.getElementById('pptx-scroll') as HTMLElement;
const viewer = new PptxScrollViewer(container);
await viewer.load('/deck.pptx');
// viewer.scrollToSlide(2);
// viewer.slideCount, viewer.topVisibleSlide
The container must have a bounded height (e.g. height: 100vh or a flex child)
so the viewer can size its scroll host to it. Base zoom fits the widest available
DOCX page, or the PPTX slide width, to the container and re-fits on resize. A
progressively loaded DOCX re-fits if a wider page appears; a 0-width container
defers layout until it has width. Call destroy() to tear down (a self-loaded
engine is destroyed with it; a borrowed one is not - see below).
Pass refitOnResize: false when the viewport must not determine the document's
physical display size. An explicit pre-load setScale(1) then keeps the same
authored font size at roughly the same on-screen size for portrait pages,
landscape pages, and slides; users can still zoom or call fitWidth() /fitPage() explicitly.
Desk appearance. The viewer paints each page/slide on its own white canvas
with a soft drop shadow, over a transparent "desk". Style the desk and the sheet
gaps without any wrapper CSS:
const viewer = new DocxScrollViewer(container, {
background: '#f3f4f6', // the desk behind / between pages
gap: 24, // vertical gap between pages
paddingTop: 32, // desk padding above the first page
pageShadow: '0 0 0 1px #c8ccd0', // crisp 1px "border" look (box-shadow never shifts layout)
// pageShadow: false, // flat pages, no shadow
});
paddingBottom, paddingLeft and paddingRight each default to gap, so the
sheet sits inside a uniform desk margin; pass 0 for a flush edge.
Zoom. Ctrl/⌘ + mouse-wheel (and trackpad pinch) zooms the surface;
bare-wheel still scrolls natively. Zoom is flicker-free - a rapid gesture shows a
CSS preview and settles into a crisp re-render when it pauses. Bounds are the
absolute scale factors zoomMin / zoomMax (default 0.1 / 4), andsetScale(scale) sets it programmatically. When fitting needs a scale belowzoomMin, that fitted scale becomes the effective minimum so users can zoom in
and still return to the original fit. Pass enableZoom: false to disable.
Text selection and find. Pass enableTextSelection: true to overlay a
transparent, selectable text layer per page/slide for native copy. It works in
both mode: 'main' and mode: 'worker'; worker rendering returns the retained
text-run geometry beside each bitmap. findText(), findNext(), findPrev(),
and clearFind() search the complete document, including virtualized pages or
slides outside the mounted window. Set findHighlightColors: { match, active }
on any viewer to override the two overlay backgrounds with CSS colors; use an
alpha color when the canvas text should remain visible through the highlight.
Selection context for AI/MCP. Every Viewer exposes one read-only query,getSelectionContext(), for handing the user's current focus to an external
assistant. The result is a detached, JSON-serializable snapshot discriminated byformat and kind; it never exposes a mutable document model or sends data over
the network. Text, run locators, and populated XLSX cells have hard resource
limits. Check truncated / truncationReasons before building a prompt.
const docx = new DocxViewer(docxCanvas, {
enableTextSelection: true,
enableElementSelection: true,
onSelectionContextChange(context) {
// kind === 'text' or 'element': selected text or a clicked drawing
updateAskAiButton(context);
},
});
const pptx = new PptxViewer(pptxCanvas, {
enableTextSelection: true,
enableElementSelection: true,
onSelectionContextChange(context) {
// Text selection wins while it exists; otherwise a slide-element click
// yields kind === 'element' with compact bounds, provenance and content.
updateAskAiButton(context);
},
});
const spreadsheet = new XlsxViewer(container, {
enableElementSelection: true,
onSelectionContextChange(context) {
updateAskAiButton(context);
},
onContextMenu: async ({ originalEvent, getContext }) => {
// Browser-menu control must happen synchronously, before the first await.
originalEvent.preventDefault();
const { clientX, clientY } = originalEvent;
const context = await getContext();
openContextMenu({ clientX, clientY, context });
},
});
const spreadsheetContext = spreadsheet.getSelectionContext({ maxCells: 1_000 });
// kind === 'range': selection state, values and formulas.
// kind === 'element': a clicked chart, picture or shape.
All three formats expose the same onSelectionContextChange(context) handoff;
callers may instead query on demand. enableElementSelection is an independent,
explicit opt-in because it enables object selection and draws a non-editable outline
around the focused object; adding the callback alone never enables object
hit-testing. XLSX separately retainsonSelectionStateChange for canonical UI state such as ActiveCell and multiple
areas. Its context callback is frame-coalesced so drag selection does not build
one snapshot per pointer event.onContextMenu is also common to every Viewer. It receives the real browser
event synchronously so the host can call preventDefault(), plus a clearly
asynchronous getContext() lookup for the right-click target. The lookup starts
on the first call and is memoized. Omitting the callback installs no listener and leaves
the native browser menu unchanged.DocxDocument.getElementContextAt() andPptxPresentation.getElementContextAt() provide the identical compact element
query for custom/headless page or slide surfaces in both modes. PPTX
element provenance is limited to master | layout | slide; editor tree indexes,
archive paths, save/round-trip handles, and mutation APIs are deliberately absent.
When switching on kind, retain a default branch so a future read-only focus kind
can be added without changing the transport envelope.
See the selection-context guide for the complete
contract, resource bounds, PPTX hit-testing semantics, and extension policy.
Hyperlinks. For DOCX/PPTX the link hit regions live on the text-selection
overlay, so hyperlink interaction requires enableTextSelection: true; when that
overlay is enabled, links are interactive by default. XLSX hit-tests cells
directly, so links are interactive out of the box. An external link opens in a
new tab (scheme-sanitized to http / https / mailto / tel, noopener),
and an internal target navigates within the document (docx bookmark, pptx slide
jump, xlsx defined name or cell reference). XLSX references may switch sheets
and then scroll the destination cell into view; a range navigates to its first
cell. Pass onHyperlinkClick(target) to take over the click yourself.
Pass enableHyperlinks: false to disable hyperlink interactivity entirely - no
hit-testing, no pointer cursor over links, no default navigation, andonHyperlinkClick is never called; links still render as authored but are inert.
This applies to every viewer that supports hyperlinks (DocxViewer,DocxScrollViewer, PptxViewer, PptxScrollViewer, XlsxViewer,XlsxSheetViewer).
Choose one loading mode. For the normal one-view case, construct a Viewer and
call viewer.load(source): the Viewer owns the parsed engine, may replace it on a
later load, and destroys it during teardown. For master-detail, multi-pane, or
multi-window UIs, create the Viewer from an already-loaded headless engine so
every view shares one parse. The two modes are mutually exclusive: on a
Viewer returned by a from*() factory, load() is unsupported, the engine's
own mode wins, and destroy() leaves the borrowed engine intact - the caller
owns its lifecycle.
| Use case | Acquisition | Engine owner |
|---|---|---|
| One Viewer | new Viewer(target) then viewer.load(source) |
Viewer |
| Shared parse | Engine.load(source) then Viewer.fromDocument / fromPresentation / fromWorkbook(...) |
Caller |
import { DocxDocument, DocxScrollViewer, DocxViewer } from '@silurus/ooxml/docx';
const document = await DocxDocument.load('/document.docx'); // parse once
const scroll = DocxScrollViewer.fromDocument(container, document);
const page = DocxViewer.fromDocument(canvas, document);
await page.goToPage(0);
// ...also drive a thumbnail grid or more panes from the same document.
page.destroy();
scroll.destroy(); // the borrowed document is NOT destroyed — you own it
document.destroy(); // release it yourself when every pane is gone
PptxViewer and PptxScrollViewer use fromPresentation(...); XlsxViewer
and XlsxSheetViewer use fromWorkbook(...).
Progressive DOCX layout
For a large DOCX file, set progressiveLayout: true to resolve load() when the
opening pages are paintable while the same paginator continues in the background.
It is available on DocxViewer, DocxScrollViewer, and DocxDocument.load() in
both render modes. Pair it with mode: 'worker' when the remaining layout and
paint work should also stay off the UI thread.
import { DocxScrollViewer } from '@silurus/ooxml/docx';
const container = document.querySelector('#document') as HTMLElement;
const pager = document.querySelector('#pager') as HTMLElement;
const viewer = new DocxScrollViewer(container, {
progressiveLayout: true,
mode: 'worker',
onVisiblePageChange(pageIndex, availablePages, layoutComplete) {
pager.textContent =
`Page ${pageIndex + 1} of ${availablePages}${layoutComplete ? '' : '…'}`;
pager.setAttribute('aria-busy', String(!layoutComplete));
},
});
await viewer.load('/document.docx');
// Print, export, and final-page-count UI need the converged layout.
await viewer.waitUntilLayoutComplete();
console.log('Final page count:', viewer.pageCount);
While layoutComplete is false, pageCount and the callback's total argument
mean pages available so far, not the final total. The page callbacks fire again
when that count grows, even if the visible page does not change. AwaitwaitUntilLayoutComplete() before printing, exporting, or snapshotting a final count.
In-document NUMPAGES fields are repainted with their authoritative value after
pagination converges. See the progressive layout guide
and DOCX API reference.
Progressive PPTX layout
PPTX exposes the same progressive lifecycle on PptxViewer,PptxScrollViewer, and PptxPresentation.load():
import { PptxScrollViewer } from '@silurus/ooxml/pptx';
const viewer = new PptxScrollViewer(container, {
progressiveLayout: true,
mode: 'worker',
onVisibleSlideChange(slideIndex, slideCount, layoutComplete) {
pager.textContent = `Slide ${slideIndex + 1} of ${slideCount}`;
pager.setAttribute('aria-busy', String(!layoutComplete));
},
});
await viewer.load('/presentation.pptx');
await viewer.waitUntilLayoutComplete();
Unlike DOCX pagination, a PPTX bootstrap already provides the final slide list
and uniform dimensions. slideCount and the ScrollViewer's scroll extent are
therefore stable from first paint; availableSlideCount grows as the paintable
opening prefix is prepared. Scrolling ahead shows a loading state without
changing the scrollbar length. See the PPTX progressive layout guide
and PPTX API reference.
For presentations, enableMediaPlayback: true makes embedded audio and video
interactive inside the real viewport plus mediaOverscan slides. Other mounted
slides remain static and selectable, avoiding offscreen media blobs and
animation loops.
Both viewers also expose relayout() (force a re-fit when the container resizes
in a way a ResizeObserver cannot see - e.g. a late web-font load),onVisiblePageChange (fires when the top-most visible page, provisional DOCX
page count, or completion state changes), onVisibleSlideChange (fires when the
top-most visible slide or PPTX completion state changes; its slide count is
final from first paint), and onError (async per-page render failures are routed
here instead of crashing the scroll loop). The parse/render knobs from the
headless engines (mode, useGoogleFonts, googleFontsCssOrigin, cjkFallback, resourceLimits, the deprecatedmaxZipEntryBytes alias, math, dpr) are accepted too.
CJK fallback region
All document engines and viewers accept cjkFallback: 'auto' | 'sc' | 'tc' | 'hk' | 'jp' | 'kr'.
It chooses the regional fallback for ambiguous Han text; authored fonts and
recognized document font regions retain priority.
const viewer = new DocxViewer(canvas, { cjkFallback: 'sc' });
Omitting the option is equivalent to 'auto': at load time, resolve the first
usable CJK language from <html lang>, then navigator.languages in order,
then navigator.language. If none is available, use 'jp'. For Chinese,
explicit Hans/Hant maps to SC/TC and takes precedence over region; without an
explicit script, HK/MO maps to HK and TW maps to TC. Bare zh maps to SC.
Japanese and Korean map to JP and KR.
The resolved preference is shared with workers and remains fixed for that load.
No migration is required. Ambiguous Han text can now use different regional
glyphs according to the host language. Set an explicit region for reproducible
output, including in Node. This option does not enable Google Fonts; useuseGoogleFonts: true or provide local/self-hosted fallback fonts as usual.
HK retains the existing sans-only webfont support. This option covers document
text, spreadsheet cells/shapes, and slide text; embedded chart and equation
renderers retain their own font policies. XLSX automatic script inference uses
the shared-string table; inline cell strings and shape text do not contribute
to that workbook-level inference.
Markdown export
Every headless engine can produce a best-effort, text-focused GitHub-flavoured
markdown projection for LLM ingestion, full-text search, or diffing. Explicit
headings, lists, and tables are preserved where available, but visual layout,
fonts, positioning, and inferred relationships between shapes are intentionally
dropped. Treat the result as full-text extraction, not an authoritative semantic
or reading-order representation.
Review comments are kept out of the document body and collected in a final## Review comments appendix. Comment text is quoted, replies use nested quotes,
and only reliable locations (such as a slide number or worksheet cell) are
reported. Speaker notes remain separate from review comments.
The projection is compiled into each format's existing parser WASM; there is no
separate markdown WASM to load. toMarkdown() works in both mode: 'main' andmode: 'worker' (it runs off the archive opened at load()):
import { DocxDocument } from '@silurus/ooxml/docx';
const doc = await DocxDocument.load('/document.docx');
const md = await doc.toMarkdown();
PptxPresentation.toMarkdown() (title slides → # headings, body → nested
bullets, speaker notes kept with their slide) and XlsxWorkbook.toMarkdown()
(each sheet → a ## SheetName pipe table) are the twins.
The repository also contains a low-level adapter and CLI for workspace tooling.
They are internal implementation utilities, not separately published packages;
installed applications should use the format model's toMarkdown() method.
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)"]
VIEWER["DOCX / XLSX / PPTX Viewer"] --> ENGINE["format headless engine"]
ENGINE -->|mode: main| PARSE["parser worker\n〈WASM parse〉"]
PARSE --> MODEL["validated document model"]
MODEL --> MAIN["layout + Canvas paint\n〈main thread〉"]
ENGINE -->|mode: worker| RENDER_WORKER["render-worker.ts\n〈WASM parse + layout + paint〉"]
RENDER_WORKER --> BITMAP["ImageBitmap"]
MAIN --> CANVAS["<canvas>"]
BITMAP --> CANVAS
CORE["@silurus/ooxml-core\nshared layout and paint primitives"]
MAIN -. uses .-> CORE
RENDER_WORKER -. uses .-> CORE
end
docx_wasm --> PARSE
xlsx_wasm --> PARSE
pptx_wasm --> PARSE
docx_wasm --> RENDER_WORKER
xlsx_wasm --> RENDER_WORKER
pptx_wasm --> RENDER_WORKER
All three formats use the same public mode boundary. In the default main mode,
WASM parsing runs in a Worker and the validated model is laid out and painted on
the main thread. In worker mode, parsing, layout and Canvas paint run in a
render Worker, which returns an ImageBitmap for presentation. Built-in optional
renderers use the same injection options in both modes. @silurus/ooxml-core
owns the layout, paint and type primitives shared by DOCX, XLSX and PPTX.
Key files
| 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 |
DOCX Canvas 2D rendering and text layout |
packages/xlsx/src/renderer.ts |
XLSX Canvas 2D viewport rendering |
packages/pptx/src/renderer.ts |
PPTX Canvas 2D rendering |
packages/*/src/worker.ts |
Main-mode parser Worker entry |
packages/*/src/render-worker.ts |
Worker-mode parser, layout and Canvas paint entry |
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 |
Framework Examples
Runnable TypeScript projects are available for
React,
Vue,
Svelte, and
Solid. Each guide embeds the
complete StackBlitz project and supports selecting a local DOCX, XLSX, or PPTX
file without uploading it.
Feature Support
Word (.docx)
| Category | Feature | Status |
|---|---|---|
| Document | Page rendering | ✅ |
| Page size and margins | ✅ | |
| Headers / footers (default / first / even) | ✅ | |
| Section breaks (continuous / nextPage / oddPage / evenPage) | ✅ | |
Page borders (w:pgBorders, §17.6.10 - standard line styles, offsetFrom / display / zOrder; art borders not yet supported) |
✅ | |
Line numbering (w:lnNumType, §17.6.8) |
✅ | |
Section vertical alignment (w:vAlign, §17.6.22) |
✅ | |
| 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 / distribute - CJK both/distribute spread by inter-character pitch, §17.18.44) |
✅ |
| Line spacing (auto / atLeast / exact) | ✅ | |
Document grid (w:docGrid, §17.6.5 - line pitch + East Asian character grid / 字詰め) |
✅ | |
| Margin collapsing between paragraphs | ✅ | |
| Indents and tab stops | ✅ | |
Multi-column section layout (w:cols, §17.6.4 - newspaper-flow columns; full-width floats span all columns) |
✅ | |
Lists (bullet and numbered, multi-level %N markers §17.9.11) |
✅ | |
| 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 | ✅ | |
Right-to-left text - UAX#9 bidi, w:bidi / w:rtl, complex-script formatting (w:szCs / w:bCs / rFonts@cs, §17.3.2.26), RTL lists and indents |
✅ | |
Japanese kinsoku line breaking (w:kinsoku, §17.15.1.58 - 行頭/行末禁則) |
✅ | |
| Vertical writing (縦書き - UAX#50 vertical glyph forms, 縦中横 tate-chu-yoko runs, 、。 upper-right positioning; §17.3.2 vertical text) | ✅ | |
| Elements | Tables (with borders, fills, merges, banding, alignment) | ✅ |
Table auto-layout by preferred widths (w:tblLayout autofit, §17.4.52; min content width) |
✅ | |
Table indent (w:tblInd, §17.4.50) |
✅ | |
Right-to-left table column order (w:bidiVisual, §17.4.1) |
✅ | |
Charts (embedded DrawingML c:chart - bar / line / area / pie / doughnut / radar / scatter, via the shared core chart renderer; data labels honour dLblPos, §21.2.2.48) |
✅ | |
| ChartEx (waterfall / histogram / Pareto / funnel / box & whisker / treemap / sunburst) | ✅ opt-in | |
Math equations (OMML m:oMath / m:oMathPara, rendered via MathJax - opt-in @silurus/ooxml/math) |
✅ | |
| Images (inline and anchored, with text wrap and adaptive display-sized decoding for oversized rasters) | ✅ | |
TIFF images (opt-in @silurus/ooxml/tiff; bounded bilevel, grayscale, RGB(A), process-CMYK and CCITT Group 4) |
✅ | |
SVG images (asvg:svgBlip MS-2016 extension - vector drawn from the embedded .svg, raster fallback) |
✅ | |
Text boxes / drawing shapes (inline and anchored wps:wsp / wps:txbx, including solid, gradient, and image fills; a:prstGeom - 186 preset geometries via the shared engine; connector arrow heads headEnd / tailEnd (§20.1.8.3) and prstDash dash patterns (§20.1.8.48)). Text-box paragraphs run through the same line-layout engine as body text, so kinsoku 行頭/行末禁則 (§17.15.1.58-60), UAX#9 bidi (w:bidi, §17.3.1.6), justification (§17.18.44) and tab stops (§17.3.1.37) all apply inside a box |
✅ | |
| WMF and EMF metafile images (legacy vector, incl. inside text boxes) - rasterized via a built-in player: window→viewport mapping (MS-EMF map modes, world transform), pens/brushes, poly/rect/ellipse, text-out, path clipping, and embedded DIB blits | ✅ | |
| Legacy VML content - positioned shapes, text boxes, image previews, and authored text wrapping | ✅ | |
OLE embedded objects (w:object - the baked VML v:imagedata preview is drawn; the embedded app is not run) |
✅ | |
| Advanced | Footnotes - reference markers + bottom-of-page bodies with separator rule, numbered (w:footnoteReference / w:footnoteRef, §17.11) |
✅ |
Endnotes - reference markers + bodies at document end (w:endnoteReference, §17.11) |
✅ | |
Page-number formats (w:pgNumType restart / format §17.6.12; PAGE \* switches - decimal / roman / letter / hex / ordinal-dash / hebrew2 / koreanLegal, §17.18.59) |
✅ | |
Field date/time pictures (TIME / DATE field \@ format, §17.16.5.72 / .16) |
✅ | |
w:snapToGrid opt-out of the document grid (§17.3.1.32) |
✅ | |
Track changes (§17.13.5 w:ins / w:del / w:moveFrom / w:moveTo) - the default render is the FINAL state (deletions and moved-away text hidden); the opt-in markup view (showTrackedChanges) draws author-coloured underline / strikethrough plus margin change bars, and body-story revision records are available as data |
✅ | |
Comments (§17.13.4) - opt-in margin balloons (comments: true): commented ranges tinted, threaded replies via commentsExtended.xml, resolved threads hidden, click-to-select stacking; also available as data (doc.comments, doc.commentAnchorRanges()) |
✅ | |
Markdown export (DocxDocument.toMarkdown() - headings, lists, tables, footnotes / comments) |
✅ | |
| Mail merge fields | ❌ Not planned | |
| Interaction | Text selection, including table-cell text (transparent overlay, native copy) | ✅ |
Bounded read-only text/element context (getSelectionContext(), page/source locators, element selection, AI/MCP callback) |
✅ | |
In-document find (findText / findNext / findPrev / clearFind - full-text search, all hits highlighted, each match tagged with its page) |
✅ | |
Runtime zoom (getScale / setScale / fitWidth / fitPage) |
✅ | |
Clickable hyperlinks (overlay hit-test, onHyperlinkClick; internal bookmark / anchor navigation) |
✅ | |
Continuous scroll viewer (DocxScrollViewer - virtualized page list, desk background / shadow, Ctrl/⌘+wheel zoom, borrowed-engine factory) |
✅ | |
| Loading | Password-protected files ([MS-OFFCRYPTO] Agile Encryption - load(bytes, { password }), decrypted client-side via WebCrypto; legacy Standard / Extensible encryption → typed unsupported-encryption) |
✅ |
Excel (.xlsx)
| 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) | ✅ | |
East-Asian furigana (<rPh> §18.4.6 + <phoneticPr> §18.4.3 - drawn when a cell opts in via ph="1"; row-level <row ph> inheritance) |
✅ | |
| 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 | ✅ | |
| Japanese kinsoku line breaking in wrapped cells (行頭/行末禁則, shared core engine) | ✅ | |
Number formats (0.00, %, #,##0, custom date/time) |
✅ | |
| Structure | Merged cells | ✅ |
Right-to-left sheets (sheetView rightToLeft, §18.3.1.87 - mirrored grid, headers, selection, scroll) |
✅ | |
| Frozen panes | ✅ | |
| Row / column sizing (custom widths and heights) | ✅ | |
| Hidden rows / columns | ✅ | |
Row / column outline grouping (outlineLevel / collapsed §18.3.1.73 / .13, <outlinePr> - gutter brackets, +/− collapse, numbered level buttons; view-only) |
✅ | |
| Elements | Images (<xdr:twoCellAnchor>, with adaptive display-sized decoding for oversized rasters) |
✅ |
TIFF images (opt-in @silurus/ooxml/tiff; bounded bilevel, grayscale, RGB(A), process-CMYK and CCITT Group 4) |
✅ | |
OLE embedded objects (<oleObjects> - the legacy VML v:imagedata preview keyed by oleObject@shapeId is drawn; an image-typed objectPr target is preferred when present, and the embedded app is not run) |
✅ | |
SVG images (asvg:svgBlip MS-2016 extension - vector drawn from the embedded .svg, raster fallback) |
✅ | |
Drawing shapes / text boxes (xdr:sp, xdr:txBody - 186 preset geometries via the shared engine, with avLst adjust handles) |
✅ | |
Math equations in shapes (OMML m:oMath / m:oMathPara in xdr:txBody, incl. a14:m / mc:AlternateContent; rendered via MathJax - opt-in @silurus/ooxml/math) |
✅ | |
| Charts (bar, line, area, pie, doughnut, radar, scatter / bubble) | ✅ | |
| ChartEx (waterfall / histogram / Pareto / funnel / box & whisker / treemap / sunburst) | ✅ opt-in | |
Chart markers (circle / square / diamond / triangle / x / plus / star / dot / dash, per-point <c:dPt> overrides; markers-only scatter series draw a marker legend key) |
✅ | |
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 (saved worksheet output renders unchanged; read-only metadata is exposed. Refresh, recalculation, filtering, restructuring, and interactivity are unsupported) | ⚠️ Partial | |
Cell comments / notes (classic xl/commentsN.xml + Office-365 threaded comments - red triangle indicator + author / text via the worksheet model; pointer or keyboard users can open the popup, with a polite screen-reader status) |
✅ | |
Data validation (rules via the worksheet model; list-type dropdown arrow on the selected cell whose click opens a panel showing the allowed values - read-only) |
✅ | |
Markdown export (XlsxWorkbook.toMarkdown() - each sheet as a ## SheetName pipe table) |
✅ | |
| Interaction | Cell selection (single / range / row / column / all / multiple areas; setSelection('B2:D5') or canonical structured state) |
✅ |
| Excel-style row / column header highlight on selection | ✅ | |
| Shift+click to extend, Ctrl/⌘+drag to add another area, Ctrl+C to copy as TSV | ✅ | |
| Text selection inside cells (transparent overlay) | ✅ | |
onSelectionStateChange, bounded range/element getSelectionContext() / copySelection(), chart/picture/shape selection, getCellAt(x, y) |
✅ | |
Zoom slider (Excel-style, right of the tab bar, 10-400% with 100% centered; showZoomSlider option) |
✅ | |
| Ctrl/⌘ + mouse-wheel and trackpad-pinch zoom (in addition to the slider) | ✅ | |
Runtime fit / zoom API (fitWidth / fitPage / getScale / setScale, in addition to the slider) |
✅ | |
In-document find (findText / findNext / findPrev / clearFind - matches tagged with sheet + cell) |
✅ | |
Clickable hyperlinks (onHyperlinkClick; internal defined-name / sheet-and-cell navigation, ranges use the first cell) |
✅ | |
Drag-to-resize columns / rows by dragging header borders (resizable option, default on) - view-only: changes the on-screen view only and never modifies the loaded file |
✅ | |
Customizable cell-selection color (selectionColor option, setSelectionColor()) |
✅ | |
| Loading | Password-protected files ([MS-OFFCRYPTO] Agile Encryption - load(bytes, { password }), decrypted client-side via WebCrypto; legacy Standard / Extensible encryption → typed unsupported-encryption) |
✅ |
PowerPoint (.pptx)
| Category | Feature | Status |
|---|---|---|
| Slides | Slide renderin... |
FAQ
Common questions
Discussion
Questions & comments · 0
Sign In Sign in to leave a comment.