@silurus/ooxml 0.75.3 → 0.75.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -254
- package/dist/{document-pull-client-C3s6Y0eT.js → document-pull-client-BcNYhoAG.js} +3920 -3701
- package/dist/{docx-C4sCu6Z1.js → docx-DjF8FgoP.js} +3 -3
- package/dist/docx.mjs +1 -1
- package/dist/docx_parser_bg.wasm +0 -0
- package/dist/{duotone-bitmap-by-path-D4mAC2FR.js → duotone-bitmap-by-path-CWsSJkJu.js} +1 -1
- package/dist/index.mjs +3 -3
- package/dist/{line-distribute-BHeDP03S.js → line-distribute-B8sqT8kC.js} +1 -1
- package/dist/{line-metrics-EFT-W2Au.js → line-metrics-D9gqQWHl.js} +286 -279
- package/dist/node.mjs +3 -3
- package/dist/{pptx-DQqAbp3d.js → pptx-C17xP8AV.js} +3 -3
- package/dist/{pptx-Bjf_CgIh.js → pptx-Dl8tOSmP.js} +1 -1
- package/dist/pptx.mjs +2 -2
- package/dist/pptx_parser_bg.wasm +0 -0
- package/dist/{render-BcebVCOq.js → render-CalC7GX4.js} +2 -2
- package/dist/{render-worker-host-vEEGCzB3.js → render-worker-host-BG3a7brz.js} +1 -1
- package/dist/render-worker-host-Cyv5Hkbh.js +27 -0
- package/dist/{renderer-C3LQln2P.js → renderer-B9R6F_QY.js} +39 -39
- package/dist/types/docx.d.ts +80 -14
- package/dist/types/index.d.ts +1100 -1069
- package/dist/types/math.d.ts +1 -1
- package/dist/types/node.d.ts +744 -713
- package/dist/types/pptx.d.ts +800 -800
- package/dist/types/xlsx.d.ts +11 -13
- package/dist/{xlsx-Ca8qcow5.js → xlsx-BaH9irS0.js} +141 -141
- package/dist/xlsx.mjs +1 -1
- package/package.json +3 -1
- package/dist/render-worker-host-8wfcbD3f.js +0 -27
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ So I'm building this library with AI coding agents, spec-first, and keeping it f
|
|
|
30
30
|
|
|
31
31
|
A browser-based viewer for Office Open XML documents that renders to an HTML Canvas element.
|
|
32
32
|
The parsers are written in Rust and compiled to WebAssembly; the renderers use the Canvas 2D API.
|
|
33
|
-
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
|
|
33
|
+
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](https://ooxml.silurus.dev/frameworks/) for runnable React, Vue, Svelte, and Solid projects.
|
|
34
34
|
|
|
35
35
|
## Project scope: read-only viewing
|
|
36
36
|
|
|
@@ -61,8 +61,9 @@ pnpm add @silurus/ooxml
|
|
|
61
61
|
> `new URL` asset references are not processed
|
|
62
62
|
> ([esbuild#795](https://github.com/evanw/esbuild/issues/795)). Copy the
|
|
63
63
|
> `.wasm` into your served output and point the viewer at it with the
|
|
64
|
-
> `wasmUrl` load option
|
|
65
|
-
>
|
|
64
|
+
> `wasmUrl` load option. For Angular CLI, copy the required
|
|
65
|
+
> `*_parser_bg.wasm` asset from `node_modules/@silurus/ooxml/dist` into the
|
|
66
|
+
> served output and pass its public URL to the viewer.
|
|
66
67
|
>
|
|
67
68
|
> `wasmUrl` also serves the parser WASM from a CDN or any path you control:
|
|
68
69
|
>
|
|
@@ -394,256 +395,13 @@ All three formats follow the same shape: the worker parses the `.docx` / `.xlsx`
|
|
|
394
395
|
|
|
395
396
|
## Framework Examples
|
|
396
397
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
export function PptxViewerComponent({ src }: { src: string }) {
|
|
406
|
-
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
407
|
-
const viewerRef = useRef<PptxViewer | null>(null);
|
|
408
|
-
const [slide, setSlide] = useState({ current: 0, total: 0 });
|
|
409
|
-
|
|
410
|
-
useEffect(() => {
|
|
411
|
-
const canvas = canvasRef.current;
|
|
412
|
-
if (!canvas) return;
|
|
413
|
-
|
|
414
|
-
const viewer = new PptxViewer(canvas, {
|
|
415
|
-
onSlideChange: (i, total) => setSlide({ current: i, total }),
|
|
416
|
-
});
|
|
417
|
-
viewerRef.current = viewer;
|
|
418
|
-
viewer.load(src);
|
|
419
|
-
}, [src]);
|
|
420
|
-
|
|
421
|
-
return (
|
|
422
|
-
<div>
|
|
423
|
-
<canvas ref={canvasRef} style={{ width: 800 }} />
|
|
424
|
-
<button onClick={() => viewerRef.current?.prevSlide()}>‹ Prev</button>
|
|
425
|
-
<span> {slide.current + 1} / {slide.total} </span>
|
|
426
|
-
<button onClick={() => viewerRef.current?.nextSlide()}>Next ›</button>
|
|
427
|
-
</div>
|
|
428
|
-
);
|
|
429
|
-
}
|
|
430
|
-
```
|
|
431
|
-
|
|
432
|
-
</details>
|
|
433
|
-
|
|
434
|
-
<details>
|
|
435
|
-
<summary><strong>Vue 3.5</strong></summary>
|
|
436
|
-
|
|
437
|
-
```vue
|
|
438
|
-
<!-- Vue 3.5 — useTemplateRef is a 3.5+ feature -->
|
|
439
|
-
<script setup lang="ts">
|
|
440
|
-
import { useTemplateRef, onMounted, ref } from 'vue';
|
|
441
|
-
import { PptxViewer } from '@silurus/ooxml/pptx';
|
|
442
|
-
|
|
443
|
-
const props = defineProps<{ src: string }>();
|
|
444
|
-
|
|
445
|
-
const canvas = useTemplateRef<HTMLCanvasElement>('canvas');
|
|
446
|
-
let viewer: PptxViewer | null = null;
|
|
447
|
-
const current = ref(0);
|
|
448
|
-
const total = ref(0);
|
|
449
|
-
|
|
450
|
-
onMounted(async () => {
|
|
451
|
-
viewer = new PptxViewer(canvas.value as HTMLCanvasElement, {
|
|
452
|
-
onSlideChange: (i, t) => { current.value = i; total.value = t; },
|
|
453
|
-
});
|
|
454
|
-
await viewer.load(props.src);
|
|
455
|
-
});
|
|
456
|
-
</script>
|
|
457
|
-
|
|
458
|
-
<template>
|
|
459
|
-
<div>
|
|
460
|
-
<canvas ref="canvas" style="width: 800px" />
|
|
461
|
-
<button @click="viewer?.prevSlide()">‹ Prev</button>
|
|
462
|
-
<span> {{ current + 1 }} / {{ total }} </span>
|
|
463
|
-
<button @click="viewer?.nextSlide()">Next ›</button>
|
|
464
|
-
</div>
|
|
465
|
-
</template>
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
</details>
|
|
469
|
-
|
|
470
|
-
<details>
|
|
471
|
-
<summary><strong>Angular 19</strong></summary>
|
|
472
|
-
|
|
473
|
-
The Angular CLI's esbuild-based builder does not process the `new URL('…', import.meta.url)`
|
|
474
|
-
asset reference the parsers use ([angular-cli#22388](https://github.com/angular/angular-cli/issues/22388)),
|
|
475
|
-
so the `.wasm` never reaches the build output — and under `ng serve` the dependency
|
|
476
|
-
optimizer additionally rewrites the reference into its own cache path. **Both steps
|
|
477
|
-
below are required** (the asset copy alone fixes only production builds; `ng serve`
|
|
478
|
-
still 404s without `wasmUrl`):
|
|
479
|
-
|
|
480
|
-
```jsonc
|
|
481
|
-
// angular.json — copy the parser WASM into the served root
|
|
482
|
-
// (restart `ng serve` after editing this file)
|
|
483
|
-
"architect": {
|
|
484
|
-
"build": {
|
|
485
|
-
"options": {
|
|
486
|
-
"assets": [
|
|
487
|
-
{ "glob": "*_parser_bg.wasm", "input": "node_modules/@silurus/ooxml/dist", "output": "/" },
|
|
488
|
-
{ "glob": "**/*", "input": "public" }
|
|
489
|
-
]
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
```
|
|
494
|
-
|
|
495
|
-
```typescript
|
|
496
|
-
// Angular 19 — standalone component with signal-based state
|
|
497
|
-
import {
|
|
498
|
-
Component, ElementRef, viewChild,
|
|
499
|
-
signal, AfterViewInit,
|
|
500
|
-
} from '@angular/core';
|
|
501
|
-
import { PptxViewer } from '@silurus/ooxml/pptx';
|
|
502
|
-
|
|
503
|
-
@Component({
|
|
504
|
-
selector: 'app-pptx-viewer',
|
|
505
|
-
standalone: true,
|
|
506
|
-
template: `
|
|
507
|
-
<div>
|
|
508
|
-
<canvas #canvas style="width: 800px"></canvas>
|
|
509
|
-
<button (click)="prev()">‹ Prev</button>
|
|
510
|
-
<span> {{ current() + 1 }} / {{ total() }} </span>
|
|
511
|
-
<button (click)="next()">Next ›</button>
|
|
512
|
-
</div>
|
|
513
|
-
`,
|
|
514
|
-
})
|
|
515
|
-
export class PptxViewerComponent implements AfterViewInit {
|
|
516
|
-
canvasEl = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');
|
|
517
|
-
current = signal(0);
|
|
518
|
-
total = signal(0);
|
|
519
|
-
private viewer?: PptxViewer;
|
|
520
|
-
|
|
521
|
-
ngAfterViewInit(): void {
|
|
522
|
-
this.viewer = new PptxViewer(this.canvasEl().nativeElement, {
|
|
523
|
-
wasmUrl: '/pptx_parser_bg.wasm',
|
|
524
|
-
onSlideChange: (i, t) => { this.current.set(i); this.total.set(t); },
|
|
525
|
-
});
|
|
526
|
-
this.viewer.load('/deck.pptx');
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
prev(): void { this.viewer?.prevSlide(); }
|
|
530
|
-
next(): void { this.viewer?.nextSlide(); }
|
|
531
|
-
}
|
|
532
|
-
```
|
|
533
|
-
|
|
534
|
-
> The `*_parser_bg.wasm` glob copies all three parsers; narrow it to
|
|
535
|
-
> `pptx_parser_bg.wasm` if you only use one format. If you deploy under a
|
|
536
|
-
> non-root `base href`, adjust `wasmUrl` so it resolves under your base (a
|
|
537
|
-
> relative `wasmUrl` is resolved against the document URL).
|
|
538
|
-
|
|
539
|
-
</details>
|
|
540
|
-
|
|
541
|
-
<details>
|
|
542
|
-
<summary><strong>Svelte 5</strong></summary>
|
|
543
|
-
|
|
544
|
-
```svelte
|
|
545
|
-
<!-- Svelte 5 — runes syntax ($props, $state) -->
|
|
546
|
-
<script lang="ts">
|
|
547
|
-
import { onMount } from 'svelte';
|
|
548
|
-
import { PptxViewer } from '@silurus/ooxml/pptx';
|
|
549
|
-
|
|
550
|
-
let { src }: { src: string } = $props();
|
|
551
|
-
|
|
552
|
-
let canvas: HTMLCanvasElement;
|
|
553
|
-
let viewer: PptxViewer;
|
|
554
|
-
let current = $state(0);
|
|
555
|
-
let total = $state(0);
|
|
556
|
-
|
|
557
|
-
onMount(async () => {
|
|
558
|
-
viewer = new PptxViewer(canvas, {
|
|
559
|
-
onSlideChange: (i, t) => { current = i; total = t; },
|
|
560
|
-
});
|
|
561
|
-
await viewer.load(src);
|
|
562
|
-
});
|
|
563
|
-
</script>
|
|
564
|
-
|
|
565
|
-
<div>
|
|
566
|
-
<canvas bind:this={canvas} style="width: 800px"></canvas>
|
|
567
|
-
<button onclick={() => viewer?.prevSlide()}>‹ Prev</button>
|
|
568
|
-
<span> {current + 1} / {total} </span>
|
|
569
|
-
<button onclick={() => viewer?.nextSlide()}>Next ›</button>
|
|
570
|
-
</div>
|
|
571
|
-
```
|
|
572
|
-
|
|
573
|
-
</details>
|
|
574
|
-
|
|
575
|
-
<details>
|
|
576
|
-
<summary><strong>SolidJS 1.9</strong></summary>
|
|
577
|
-
|
|
578
|
-
```tsx
|
|
579
|
-
// SolidJS 1.9
|
|
580
|
-
import { createSignal, onMount, onCleanup } from 'solid-js';
|
|
581
|
-
import { PptxViewer } from '@silurus/ooxml/pptx';
|
|
582
|
-
|
|
583
|
-
export function PptxViewerComponent(props: { src: string }) {
|
|
584
|
-
let canvasEl!: HTMLCanvasElement;
|
|
585
|
-
let viewer: PptxViewer | undefined;
|
|
586
|
-
const [current, setCurrent] = createSignal(0);
|
|
587
|
-
const [total, setTotal ] = createSignal(0);
|
|
588
|
-
|
|
589
|
-
onMount(async () => {
|
|
590
|
-
viewer = new PptxViewer(canvasEl, {
|
|
591
|
-
onSlideChange: (i, t) => { setCurrent(i); setTotal(t); },
|
|
592
|
-
});
|
|
593
|
-
await viewer.load(props.src);
|
|
594
|
-
});
|
|
595
|
-
|
|
596
|
-
onCleanup(() => { /* viewer?.destroy?.() */ });
|
|
597
|
-
|
|
598
|
-
return (
|
|
599
|
-
<div>
|
|
600
|
-
<canvas ref={canvasEl} style={{ width: '800px' }} />
|
|
601
|
-
<button onClick={() => viewer?.prevSlide()}>‹ Prev</button>
|
|
602
|
-
<span> {current() + 1} / {total()} </span>
|
|
603
|
-
<button onClick={() => viewer?.nextSlide()}>Next ›</button>
|
|
604
|
-
</div>
|
|
605
|
-
);
|
|
606
|
-
}
|
|
607
|
-
```
|
|
608
|
-
|
|
609
|
-
</details>
|
|
610
|
-
|
|
611
|
-
<details>
|
|
612
|
-
<summary><strong>Qwik 2</strong></summary>
|
|
613
|
-
|
|
614
|
-
```tsx
|
|
615
|
-
// Qwik 2.0 — dynamic import to keep WASM out of SSR bundle
|
|
616
|
-
import { component$, useSignal, useVisibleTask$ } from '@builder.io/qwik';
|
|
617
|
-
import type { PptxViewer as PptxViewerType } from '@silurus/ooxml/pptx';
|
|
618
|
-
|
|
619
|
-
export const PptxViewerComponent = component$<{ src: string }>(({ src }) => {
|
|
620
|
-
const canvasRef = useSignal<HTMLCanvasElement>();
|
|
621
|
-
const current = useSignal(0);
|
|
622
|
-
const total = useSignal(0);
|
|
623
|
-
let viewer: PptxViewerType | undefined;
|
|
624
|
-
|
|
625
|
-
// useVisibleTask$ runs only in the browser, never during SSR
|
|
626
|
-
useVisibleTask$(async () => {
|
|
627
|
-
if (!canvasRef.value) return;
|
|
628
|
-
const { PptxViewer } = await import('@silurus/ooxml/pptx');
|
|
629
|
-
viewer = new PptxViewer(canvasRef.value, {
|
|
630
|
-
onSlideChange: (i, t) => { current.value = i; total.value = t; },
|
|
631
|
-
});
|
|
632
|
-
await viewer.load(src);
|
|
633
|
-
});
|
|
634
|
-
|
|
635
|
-
return (
|
|
636
|
-
<div>
|
|
637
|
-
<canvas ref={canvasRef} style={{ width: '800px' }} />
|
|
638
|
-
<button onClick$={() => viewer?.prevSlide()}>‹ Prev</button>
|
|
639
|
-
<span> {current.value + 1} / {total.value} </span>
|
|
640
|
-
<button onClick$={() => viewer?.nextSlide()}>Next ›</button>
|
|
641
|
-
</div>
|
|
642
|
-
);
|
|
643
|
-
});
|
|
644
|
-
```
|
|
645
|
-
|
|
646
|
-
</details>
|
|
398
|
+
Runnable TypeScript projects are available for
|
|
399
|
+
[React](https://ooxml.silurus.dev/frameworks/react/),
|
|
400
|
+
[Vue](https://ooxml.silurus.dev/frameworks/vue/),
|
|
401
|
+
[Svelte](https://ooxml.silurus.dev/frameworks/svelte/), and
|
|
402
|
+
[Solid](https://ooxml.silurus.dev/frameworks/solid/). Each guide embeds the
|
|
403
|
+
complete StackBlitz project and supports selecting a local DOCX, XLSX, or PPTX
|
|
404
|
+
file without uploading it.
|
|
647
405
|
|
|
648
406
|
---
|
|
649
407
|
|
|
@@ -689,7 +447,7 @@ export const PptxViewerComponent = component$<{ src: string }>(({ src }) => {
|
|
|
689
447
|
| | Math equations (OMML `m:oMath` / `m:oMathPara`, rendered via MathJax — opt-in `@silurus/ooxml/math`) | ✅ |
|
|
690
448
|
| | Images (inline and anchored, with text wrap) | ✅ |
|
|
691
449
|
| | SVG images (`asvg:svgBlip` MS-2016 extension — vector drawn from the embedded `.svg`, raster fallback) | ✅ |
|
|
692
|
-
| | Text boxes / drawing shapes (`wps:txbx`, `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 | ✅ |
|
|
450
|
+
| | 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 | ✅ |
|
|
693
451
|
| | 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 | ✅ |
|
|
694
452
|
| | OLE embedded objects (`w:object` — the baked VML `v:imagedata` preview is drawn; the embedded app is not run) | ✅ |
|
|
695
453
|
| **Advanced** | Footnotes — reference markers + bottom-of-page bodies with separator rule, numbered (`w:footnoteReference` / `w:footnoteRef`, §17.11) | ✅ |
|