Office-open-xml-viewer: 将Office XML文档渲染到HTML Canvas的查看器

Hacker News Top 工具

摘要

一个基于浏览器的Office Open XML文档(DOCX,XLSX,PPTX)查看器,渲染到HTML Canvas,使用Rust/WASM解析器和TypeScript渲染器构建,由Anthropic的Claude AI生成。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/06/08 03:19

yukiyokotani/office-open-xml-viewer 来源:https://github.com/yukiyokotani/office-open-xml-viewer > 整个代码库(包括 Rust 解析器、TypeScript 渲染器、测试和工具)均由 Claude(https://claude.ai)(Anthropic 的 AI 助手)通过迭代提示实现。此仓库中不存在任何人类编写的应用程序代码。 # office-open-xml-viewer npm 版本(https://www.npmjs.com/package/@silurus/ooxml) npm 下载量(https://www.npmjs.com/package/@silurus/ooxml) VS Code 市场(https://marketplace.visualstudio.com/items?itemName=silurus.office-open-xml-viewer) VS Code 安装次数(https://marketplace.visualstudio.com/items?itemName=silurus.office-open-xml-viewer) 许可证 演示(Storybook)(https://ooxml.silurus.dev) 一个基于浏览器的 Office Open XML 文档查看器,可将文档渲染到 HTML Canvas 元素上。解析器使用 Rust 编写并编译为 WebAssembly;渲染器使用 Canvas 2D API。每个格式还公开了一个无头引擎(DocxDocument / XlsxWorkbook / PptxPresentation),可渲染到任意调用方提供的画布中,因此您可以自行构建界面——如滚动视图、缩略图网格、主从面板——无需局限于内置查看器。请参见 Storybook 演示中的“示例”部分(https://ooxml.silurus.dev)。 | DOCX | XLSX | PPTX | |:—:|:—:|:—:| | docx | xlsx | pptx | bash npm install @silurus/ooxml # 或 pnpm add @silurus/ooxml > 打包工具注意事项:此包内嵌了 .wasm 文件。使用 Vite 时添加 vite-plugin-wasm(https://github.com/Menci/vite-plugin-wasm);使用 webpack 时启用 experiments.asyncWebAssembly(https://webpack.js.org/configuration/experiments/)。 > 包大小注意事项:此包仅为 ESM(.mjs)。npm 的 Unpacked Size 汇总了四个入口包的总和,其中包括 可选 数学引擎(MathJax + STIX Two Math,约 3 MB)。实际进入您应用的体积要小得多——只需导入您需要的格式(例如 @silurus/ooxml/pptx)。数学引擎是一个 单独的入口@silurus/ooxml/math):仅当您导入并将其传递给查看器时 才会被打包(参见 渲染公式)。从未接收 math 引擎的查看器——以及所有 xlsx 使用场景——会通过 tree-shaking 完全剔除约 3 MB 的体积。 — ## 快速开始 typescript import { DocxViewer } from '@silurus/ooxml/docx'; import { XlsxViewer } from '@silurus/ooxml/xlsx'; import { PptxViewer } from '@silurus/ooxml/pptx'; // DOCX — 调用方提供画布 const canvas = document.getElementById('docx-canvas') as HTMLCanvasElement; const docx = new DocxViewer(canvas); await docx.load('/document.docx'); docx.nextPage(); // XLSX — 查看器自己管理画布和标签栏 const container = document.getElementById('xlsx-container') as HTMLElement; const xlsx = new XlsxViewer(container); await xlsx.load('/workbook.xlsx'); // PPTX — 调用方提供画布 const canvas = document.getElementById('pptx-canvas') as HTMLCanvasElement; const pptx = new PptxViewer(canvas); await pptx.load('/deck.pptx'); pptx.nextSlide(); ### 渲染公式 .docx / .pptx 中的 OMML 公式(m:oMath / m:oMathPara)使用 MathJax(https://www.mathjax.org/)+ STIX Two Math(https://github.com/stipub/stixfonts)渲染。该引擎约 3 MB,因此是 可选的:从单独的 @silurus/ooxml/math 入口导入 math 引擎,然后传递给查看器。传递后即可渲染公式;省略该引擎则在任何地方都不会引用它,因此打包工具会 完全 tree-shake 掉约 3 MB(公式会被直接跳过)。它完全自包含:无需网络、无需跨域请求。 typescript 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 }); // ← 现在公式可以渲染 await docx.load('/paper-with-equations.docx'); 同一个 math 引擎也适用于 PptxViewer 以及无头的 DocxDocument / PptxPresentation API(它们在选项中接受 math)。xlsx 不支持公式,且从未引用该引擎。 — 架构图 mermaid flowchart TB subgraph build["🦀 构建时(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["🌐 运行时(浏览器)"] subgraph core_pkg["@silurus/ooxml-core(共享原语)"] 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 — 仅解析〉"] DD --> DR["renderer.ts\n〈Canvas 2D — 主线程〉"] end subgraph xlsx_pkg["@silurus/ooxml · xlsx"] XV["XlsxViewer"] --> XB["XlsxWorkbook"] XB --> XW["worker.ts\n〈Web Worker — 仅解析〉"] XB --> XR["renderer.ts\n〈Canvas 2D — 主线程〉"] end subgraph pptx_pkg["@silurus/ooxml · pptx"] PV["PptxViewer"] --> PP["PptxPresentation"] PP --> PW["worker.ts\n〈Web Worker — 仅解析〉"] PP --> PR["renderer.ts\n〈Canvas 2D — 主线程〉"] 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 所有三种格式都遵循相同结构:worker 通过 WASM 解析 .docx / .xlsx / .pptx 压缩包,并将 JSON 模型发送回主线程,渲染器在主线程上绘制画布。渲染保留在主线程上,以便画布与文档共享 FontFaceSet——worker 中的 OffscreenCanvas 有自己的字体注册表,会静默回退到系统字体,从而产生与已安装的主题 Web 字体不同的文本测量结果(以及换行位置)。 @silurus/ooxml-core 包含三个渲染器都依赖的跨格式原语:统一的图表渲染器(柱状图/折线图/面积图/雷达图/瀑布图)、形状辅助函数(resolveFillapplyStrokebuildCustomPathhexToRgba)、autoResize 查看器工具以及共享的类型定义。 ### 关键文件 | 文件 | 作用 | |——|——| | packages/docx/parser/src/lib.rs | Rust WASM 解析器 — DOCX ZIP → Document JSON | | packages/xlsx/parser/src/lib.rs | Rust WASM 解析器 — XLSX ZIP → Workbook JSON | | packages/pptx/parser/src/lib.rs | Rust WASM 解析器 — PPTX ZIP → Presentation JSON | | packages/docx/src/renderer.ts | Canvas 2D 渲染引擎,包含文本布局(主线程) | | packages/xlsx/src/renderer.ts | Canvas 2D 渲染引擎,包含虚拟滚动(主线程) | | packages/pptx/src/renderer.ts | Canvas 2D 渲染引擎(主线程) | | packages/*/src/worker.ts | Web Worker:仅 WASM 初始化和解析(每种格式一个) | | packages/*/src/viewer.ts | 公开的 Viewer API — 画布生命周期、导航 | | packages/core/src/index.ts | 跨格式原语 — 图表渲染器、形状辅助函数、autoResize、共享类型 | — ## 框架示例 React 19 tsx // React 19.1 — 需在 vite.config.ts 中添加 vite-plugin-wasm 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 ( viewerRef.current?.prevSlide()}>‹ 上一页 {slide.current + 1} / {slide.total} viewerRef.current?.nextSlide()}>下一页 › ); } Vue 3.5 vue 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); }); Angular 19 typescript // Angular 19 — 独立组件,基于信号的 state import { Component, ElementRef, viewChild, signal, AfterViewInit, } from '@angular/core'; import { PptxViewer } from '@silurus/ooxml/pptx'; @Component({ selector: 'app-pptx-viewer', standalone: true, template: ` ‹ 上一页 {{ current() + 1 }} / {{ total() }} 下一页 › `, }) export class PptxViewerComponent implements AfterViewInit { canvasEl = viewChild.required>('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(); } } > 在 Angular 工作区中添加 "allowSyntheticDefaultImports": true,并配置 @angular-builders/custom-webpack(或使用 esbuild 构建器)以支持 WASM。 Svelte 5 svelte 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); }); viewer?.prevSlide()}>‹ 上一页 {current + 1} / {total} viewer?.nextSlide()}>下一页 › SolidJS 1.9 tsx // 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 ( viewer?.prevSlide()}>‹ 上一页 {current() + 1} / {total()} viewer?.nextSlide()}>下一页 › ); } Qwik 2 tsx // Qwik 2.0 — 动态导入以确保 WASM 不进入 SSR 包 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(); const current = useSignal(0); const total = useSignal(0); let viewer: PptxViewerType | undefined; // useVisibleTask$ 仅在浏览器中运行,绝不会在 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 ( viewer?.prevSlide()}>‹ 上一页 {current.value + 1} / {total.value} viewer?.nextSlide()}>下一页 › ); }); — ## 功能支持 ### Word (.docx) | 类别 | 功能 | 状态 | |——|——|——| | 文档 | 页面渲染 | ✅ | | | 页面大小和边距 | ✅ | | | 页眉/页脚(默认/首页/奇偶页) | ✅ | | | 分节符(连续/下一页/奇数页/偶数页) | ✅ | | 文本 | 段落 | ✅ | | | 加粗、斜体、下划线、删除线 | ✅ | | | 字体族、字号、颜色 | ✅ | | | 超链接 | ✅ | | | 上标/下标(w:vertAlign) | ✅ | | | 注音/振假名(w:ruby) | ✅ | | 格式 | 段落对齐(左/中/右/两端对齐) | ✅ | | | 行间距(自动/至少/固定) | ✅ | | | 行网格(w:docGrid,§17.6.5) | ✅ | | | 段落间边距折叠 | ✅ | | | 缩进和制表位 | ✅ | | | 列表(项目符号和编号) | ✅ | | | 段落样式(标题 1–9、正文、自定义) | ✅ | | | 表格样式 w:pPr 级联(§17.7.6) | ✅ | | | 表格样式边框/底纹/斑马条纹(tblStylePrcnfStyle,§17.4.7) | ✅ | | | 目录(TOC 域)— 点前导符、右对齐页码 | ✅ | | | keepNext / keepLines / widowControl | ✅ | | 元素 | 表格(含边框、填充、合并、斑马纹、对齐) | ✅ | | | 数学公式(OMML m:oMath / m:oMathPara,通过 MathJax 渲染 — 可选 @silurus/ooxml/math) | ✅ | | | 图片(内联和锚定,带文字环绕) | ✅ | | | 文本框/绘图形状 | ✅ | | | WMF / EMF 图元文件(旧式矢量) | ❌ 不计划 | | 高级 | 脚注/尾注引用标记 | ✅ | | | 修订标记(w:ins / w:del — 作者颜色下划线/删除线) | ✅ | | | 批注/脚注正文(已解析,尚未内联渲染) | ⚠️ | | | 邮件合并域 | ❌ 不计划 | | 交互 | 文本选择(透明覆盖层,原生复制) | ✅ | — ### Excel (.xlsx) | 类别 | 功能 | 状态 | |——|——|——| | 工作簿 | 多个工作表、工作表名称 | ✅ | | | 工作表标签颜色(— theme / tint / indexed / rgb) | ✅ | | **单元格** | 文本、数字、布尔值、错误值 | ✅ | | | 公式结果(来自缓存的) | ✅ | | | 日期(ECMA-376 日期格式代码) | ✅ | | | 富文本(逐段格式化) | ✅ | | 格式 | 加粗、斜体、下划线(single / double / singleAccounting / doubleAccounting)、删除线 | ✅ | | | 上标/下标(vertAlign) | ✅ | | | 字体族、字号、颜色 | ✅ | | | 单元格背景色(纯色 + 渐变) | ✅ | | | 图案填充(gray125 / gray0625 / lightGray / mediumGray / darkGray 以及 12 种 light* / dark* 方向斜线) | ✅ | | | 边框(细线、中等、粗线、发线、双线、虚线、点线、点画线……) | ✅ | | | 对角线边框(diagonalUp / diagonalDown,单线 + 双线) | ✅ | | | 水平/垂直对齐 | ✅ | | | 文本换行 | ✅ | | | 数字格式(0.00%#,##0、自定义日期/时间) | ✅ | | 结构 | 合并单元格 | ✅ | | | 冻结窗格 | ✅ | | | 行/列尺寸(自定义宽度和高度) | ✅ | | | 隐藏行/列 | ✅ | | 元素 | 图片() | ✅ | | | 绘图形状/文本框(`xdr:sp`、`xdr:txBody`) | ✅ | | | 图表(柱状图、折线图、面积图、雷达图、散点图/气泡图) | ✅ | | | 图表标记(圆/方/菱形/三角形/x/加号/星/点/短横线,支持逐点 覆盖) | ✅ | | | 图表数据标签( 逐点,支持 CELLRANGE / VALUE / SERIESNAME / CATEGORYNAME 字段引用,位置 `l`/`r`/`t`/`b`/`ctr`/`outEnd`) | ✅ | | | 图表误差线( X/Y 方向,cust / fixedVal / stdErr / stdDev / percentage,虚线/样式线条) | ✅ | | | 图表手动布局() | ✅ | | | 迷你图(x14:sparklineGroup — 折线图/柱状图/胜负图,支持标记和高点/低点/首点/尾点/负点高亮) | ✅ | | 高级 | 条件格式(cellIscolorScaledataBariconSettop10aboveAverage) | ✅ | | | 切片器(静态,Office 2010 扩展) | ✅ | | | 数据透视表 | ❌ 不计划 | | | 数据验证/批注 | ❌ 不计划 | | 交互 | 单元格选择(单个/区域/整行/整列/全部) | ✅ | | | Excel 风格的行/列标题选中高亮 | ✅ | | | Shift+点击扩展选择,Ctrl+C 复制为 TSV | ✅ | | | 单元格内文本选择(透明覆盖层) | ✅ | | | onSelectionChange 回调,getCellAt(x, y) API | ✅ | | | 缩放滑块(Excel 风格,位于标签栏右侧,10–400%,100%居中;showZoomSlider 选项) | ✅ | — ### PowerPoint (.pptx) | 类别 | 功能 | 状态 | |——|——|——| | 幻灯片 | 幻灯片渲染 | ✅ | | | 幻灯片布局/母版继承 | ✅ | | | 幻灯片尺寸(自定义尺寸……) | ✅ | | | 背景(纯色、渐变、图片、图案) | ✅ | | | 过渡效果(淡入淡出、推入、擦除、分割、随机条形、形状、遮盖、揭开、闪光、溶解、棋盘格、新闻快报、加号、涡旋、梳理、缩放) | ✅ | | | 逐段动画(段落动画:出现、淡入、飞入、浮入、劈裂、擦除、形状、轮子、随机效果;“按段落”变化) | ✅ | | | 幻灯片备注(已解析,可通过 Slide.notes 获取) | ✅ | | | 批注(已解析,可通过 Slide.comments 获取) | ✅ | | 形状 | 矩形、椭圆、线条、箭头、自由形状、连接符 | ✅ | | | 文本框及其内部文本、填充、轮廓、阴影、旋转 | ✅ | | | 表格(带边框、填充、合并单元格、文本) | ✅ | | | 图片(`` — JPEG、PNG、GIF、SVG、TIFF) | ✅ | | | 图表(柱状图、折线图、面积图、雷达图、饼图、散点图/气泡图、圆环图) | ✅ | | | 图表标记(圆/方/菱形/三角形/x/加号/星/点/短横线,支持逐点覆盖) | ✅ |

相似文章

Canvas 中的 HTML 演示

Hacker News Top

来自 Chrome DevRel 团队的 CSS 和 Web UI 演示合集,包括 Canvas 中的 HTML 演示。