渲染一页
import { parse, renderSlideToSvg } from '@web-ppt/core';
const pres = await parse(file); // File | Blob | ArrayBuffer
el.innerHTML = renderSlideToSvg(pres, pres.slides[0]);
零服务端 · 零框架 · 文件不出浏览器
打开 .pptx / .ppt 不需要任何后端,也不用装 Office。
自研 OOXML、CFB/OfficeArt、EMF/WMF/PICT 解析,唯一运行时依赖是 fflate。
npm i @web-ppt/core下面这个查看器用的就是 npm 上的 @web-ppt/core。拖一个自己的文件进来——它不会被上传到任何地方。
解析与渲染全部发生在你的设备上,没有任何网络请求把文件送出去。
两种格式各自走完全独立的解析链路,渲染层共用同一份 Schema。
| 能力 | .pptx | .ppt |
|---|---|---|
| 预设几何 | 187 个预设(规范全集) | MSOSPT 全表 |
| 自定义几何 | custGeom + 公式求值 + arcTo | pVertices / pSegmentInfo |
| 填充 | 纯色 / 渐变 / 图片 / 平铺 / 图案 / 主题色变换 | 纯色 / 渐变 / 图片 |
| 描边 | 虚线 / 箭头 / 端点 / 连接 | 虚线 / 箭头 |
| 效果 | 外阴影 / 内阴影 / 发光 / 柔化 / 倒影 | 忽略 |
| 立体 3D | 挤出 / 斜角 / 轮廓 / 材质 / 视角 | 忽略 |
| 样式继承 | 母版 → 版式 → 占位符 → 段落 → run | TxMasterStyle → 形状 |
| 文本 | 完整 + 15 种艺术字变形 | 字号 / 颜色 / 粗斜下划线 / 对齐 / 项目符号 |
| 图片 | 裁剪 / 裁进形状 / 透明度 / 灰度 | Pictures 流 + DEFLATE |
| EMF / WMF / PICT | 自研 GDI / QuickDraw 解释器 → SVG | 同左(PICT 为 Mac 版所存) |
| 表格 | tableStyles / 条纹 / 合并 / 边框 | 表格属性 + 网格还原 |
| 图表 | 13 类 · 次坐标轴 · 3D | 经内嵌 EMF 预览 |
| 切换效果 | 41 种(含 p14 扩展与 morph 逐元素形变) | 经 SSSlideInfoAtom |
| 元素动画 | 入场 / 退场 / 强调 / 运动路径,按点击分批 | 同上 |
| SmartArt | 缓存 drawing part;无缓存时按数据模型自行排布 | — |
| 媒体 | 封面帧 + 播放标识,可选嵌真实播放器 | — |
| 墨迹 · 评论 · 节 | 支持 | — |
| OLE 嵌入对象 | 内嵌 p:pic 预览 / 旧式 VML 快照 | — |
| 隐藏页 | sld@show="0" | SSSlideInfoAtom |
| 数学公式 OMML | 分式 / 根式 / 上下标 / 大算符 / 矩阵 / 定界符 | — |
| 加密文档 | 标准 AES-ECB / 敏捷 AES-CBC | RC4 CryptoAPI(40/56/128 位) |
| 备注 · 超链接 | 支持 | 支持 |
import { parse, renderSlideToSvg } from '@web-ppt/core';
const pres = await parse(file); // File | Blob | ArrayBuffer
el.innerHTML = renderSlideToSvg(pres, pres.slides[0]);
import { Viewer } from '@web-ppt/viewer-core';
const v = new Viewer(box, pres, { animate: true });
v.next(); // 先播动画,播完才翻页
v.setZoom(1.5);
await v.exportPng(2);
import { PresentationState } from '@web-ppt/viewer-core';
// 无 DOM 的状态机,React / Vue 直接驱动
const st = new PresentationState(pres);
st.subscribe(setState);
st.next(); st.search('关键词');
import { parseInWorker } from '@web-ppt/core';
const w = new Worker(
new URL('@web-ppt/core/worker', import.meta.url),
{ type: 'module' });
const pres = await parseInWorker(w, buf);
await slideToPng(pres, slide, 2); // → Blob
await slideToSvgFile(pres, slide); // → string
await presentationToPrintableHtml(pres); // 浏览器打印即 PDF
// 图表解析器与图元文件解码器都经 hook 注入
setChartParser(myChartParser);
setMetafileDecoder(undefined); // 省 ~15KB gzip
解析层与渲染层完全解耦——渲染层只认 Schema,不认文件格式。
PK → pptx,D0CF11E0 → ppt。调用方不需要关心扩展名。foreignObject(文本可选中);导出用原生 <text> + 自实现测量断行与分栏——Chrome 会把含 foreignObject 的 SVG 判为污染画布,无法 toBlob。拿 LibreOffice 的实际渲染做 ground truth 逐档比对。主题色 shade/tint 必须在线性 RGB 里算:
| 档位 | LibreOffice | sRGB 直乘 | 线性 RGB |
|---|---|---|---|
| shade 20% | 33,56,97 | Δ69 | Δ8 |
| tint 60% | 176,187,222 | Δ37 | Δ1 |
快照测试只能挡住"变了",挡不住"一开始就错了"——所以要有外部真值。
浏览器实测,210 页 / 11280 元素:
| 惰性首屏(解析 + 首页渲染) | 42ms |
| 全量解析 | 376ms(1.8ms/页) |
| 单页渲染 | 0.09ms |
| JS 堆 | 40MB(0.19MB/页) |
| 优化 | 效果 |
|---|---|
| 惰性解析(默认) | 首屏 9× |
| Worker 解析 | 主线程零阻塞 |
| 缩略图虚拟化 | 210 页只渲 7 个 |
下面每一格都是实际踩过的坑,不是编出来的教学例子。左边是天真做法的结果,右边是本引擎实时渲染的同一份 .pptx。
OMML 的 m:acc@chr 存的是组合码位(U+20D7 等)。它本该附着在前一个字符上,单独当一个字形画出来,浏览器要么画成点状圈里的符号、要么位置漂移。
括号按内容高度拉伸时不能绕基线等比放大。矩阵整体压在数学轴上、上下不对称,等比放大会让括号下缘冲出内容一大截。
分数线要压在数学轴(约 x 高度的一半)上,不是压在基线上。单层分式看不出差别,一嵌套就整个塌下去。
标注引线是三点开放子路径。SVG 填充会把它自动闭合,于是引线被补成一块实心楔形。正确做法是正向走一遍再反向走回来——穿越数 +2,在 evenodd 下不改变填充,描边却照样可见。
云形若用一堆重叠的椭圆拼,在 evenodd 填充规则下重叠处穿越数为偶,会被挖成洞。必须收敛成单条闭合轮廓。
要在实心图形里挖洞,内圈必须反向绕。绕向相同时 nonzero 下洞根本不出现,换 evenodd 又会牵连别处的自交图形。
语料来自 Apache POI 的 242 个公开测试文件。排除 22 个故意损坏的模糊测试样本后,真实文件成功率 210/220;余下 10 个是截断文件、空文件、PowerPoint 95/4.0 与未知口令的加密文件。
| 包 | 作用 | 依赖 | 体积 |
|---|---|---|---|
@web-ppt/core | 解析 / 渲染 / 导出,无框架 | fflate | 84KB gzip |
@web-ppt/viewer-core | headless 状态机 + 播放层 | @web-ppt/core | 6.8KB gzip |
想接 React / Vue / Svelte,直接驱动 PresentationState,不需要等官方封装。