@web-ppt/viewer-core 0.5.0-beta.2 → 0.5.0

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/CHANGELOG.md CHANGED
@@ -4,6 +4,188 @@
4
4
 
5
5
  ## 未发布
6
6
 
7
+ ## 0.5.0 - 2026-10-29
8
+
9
+ 0.5.0 稳定版:`0.5.0-beta.7` 的全部内容转正到 `latest`,编辑线自此成为稳定能力——编辑文档模型与三层视图、模板/主题/母版/版式设计画布、图表数据与样式、批注、页面确保适合、SmartArt/OLE/墨迹内部编辑、高级渲染、PNG/SVG/批量 ZIP/可打印 HTML/图片 PDF 与实验性矢量 PDF/WebM 导出、原生 `.ppt` 生成保存、无来源富文本复制,以及 Agent `describe` 结构化读出与 `lint` 布局检查按需入口。相对 0.4.5 的逐项变化见下方 `0.5.0-beta.1` 至 `0.5.0-beta.7` 条目。
10
+
11
+ 破坏性变更核对:无 API 撤除;0.5 线新增能力均为增量或按需入口,唯一语义变化是 `elementFrameToSlideMatrix` / `elementParentToSlideMatrix` 改为沿渲染链求值(仅非等比组合内非 90° 整数倍旋转等场景结果不同,见 beta.7 条目)。
12
+
13
+ 安装:无版本号安装即得 0.5.0,八包必须同版本;`@next` 标签留给 0.6 线。
14
+
15
+ `@web-ppt/collab` 发布入口为 12.20KB gzip;排除 peer 的完整测试薄包为 14,333B gzip,两种口径分别核对。
16
+
17
+ Windows PowerPoint 真机验收经维护者决定延后,0.5.0 先行转正;四项自动门禁(含 LibreOffice 外部打开清单)全部通过,后续问题以 0.5.x 补丁版修复。
18
+
19
+ ## 0.5.0-beta.7 - 2026-10-26
20
+
21
+ ### 新增
22
+
23
+ - 新增按需 `@web-ppt/edit-core/lint` 七项布局检查与 `/lint/sampler` SVG 颜色采样协议:稳定元素定位、真实字体测量、显式资源错误、进度与取消,随包提供生成 Schema。
24
+ - 新增按需 `@web-ppt/edit-core/describe`:从当前 `EditDoc` 读出页面、设计来源、稳定元素 ID、页面外框和段落文字;支持完整 JSON 的字符 / token 预算及显式截断,随包发布由 TypeScript 类型生成的 JSON Schema。
25
+ - `presentationToVideo` 新增实验性 `videoOverlay`:导出时以 `<video>` 当前帧替代内嵌视频的海报帧,媒体起始时刻与音轨共用同一时间轴;默认关闭时产物不变。
26
+ - `savePpt` 原生写入首尾两色线性渐变(两端透明度分别保留)、十种预设虚线、五种默认尺寸箭头、图片四边矩形裁剪与形状名;图案填充、多色标或径向渐变、自定义虚线与非默认箭头尺寸仍以 `PptSaveError` 拒绝
27
+ - `@web-ppt/edit-core/ole` 新增 `setRun`(嵌入 DOCX 单个 run 改字,相邻格式保留)、`setCellStyle`(嵌入 XLSX 字体 / 填充 / 边框)与 `setCellRichText`(sharedStrings 富文本);`OleParagraph` 新增 `runs` 与表格行列 `table`,`OleCell` 新增 `styleIndex`、`style` 与 `richText`
28
+ - 实验性矢量 PDF 支持 dashed / dotted / double / wavy 文字装饰线,以及文字描边(描边轨在填色轨之下,等效 `paint-order:stroke`)。
29
+
30
+ ### 修复
31
+
32
+ - 编辑性能基准统一使用完整源码别名和同一个运行时,不再依赖尚未构建的 dist 或重复的会话 WeakMap;增加干净构建状态的真实回归。
33
+ - 组合同时带旋转与非等比缩放时不再拒绝解组:解组改按 `placeGroupChild` / `placeGroupFrame` 的渲染外框回填孩子 frame,旋转子元素的宽高按转角象限承接组缩放;非等比缩放下预设形状的渲染路径会固化为自由形状覆盖,保存写回 `custGeom`,箭头、圆角等依赖宽高比例的几何不再变形。只有需要改变旋转的框架对象(图表 / OLE 等)仍显式拒绝。
34
+ - 读取 `.ppt` 时线性渐变不再一律按竖向、前景色在上渲染:方向取自 fillAngle,首尾色按 fillAngle 的符号与 fillFocus 还原(与 LibreOffice 的 PowerPoint 兼容规则一致,焦点 40–60 为两侧对称渐变),终点透明度取自 fillBackOpacity;图片的 cropFrom* 裁剪与 wzName 形状名不再丢失。
35
+ - `presentationToVideo` 的 `videoOverlay`:内嵌视频加载失败或超时不再静默退回海报帧,改为经 `onWarning` 以稳定原因 `video-overlay-unavailable` 暴露(附页号与元素名),seek 超时或帧不可用报 `video-overlay-frame-unavailable`,导出均不中断;就绪条件改为首帧可画,避免只取到元数据时首帧停在海报上。未开启 `videoOverlay` 时产物不变。
36
+ - 修复 `.pptx` 媒体源解析只认经典 `a:audioFile` / `a:videoFile` 引用:PowerPoint 2010+ 内嵌 mp3 / mp4 等时把经典关系写成 `Target="NULL"` 外链、字节只挂在 `p14:media` 的 `r:embed` 上,此前 `MediaInfo.src` 为 `null`,player 模式不生成播放器、编辑生成保存会丢失媒体字节。现按经典引用 → `p14:media`(`r:link` 优先于 `r:embed`)逐个取第一个可解析目标:包内关系要求部件存在,外链关系要求是带 scheme 的 URL;正常文件的解析结果不变。
37
+ - 修正视频导出过期说明:`presentationToVideo` 已支持可选音轨;含内嵌媒体时 `mediaPosters: true` 在开启 `audio` / `videoOverlay` 时仍必需,拒绝信息与文档同步说明原因。
38
+ - 英语缩写中的弯撇号(`I’m`、`that’s`)不再按全角标点挤压半个 em,避免撇号与后一个字母重叠(#8)。中文句读的挤压规则不变。
39
+ - 对象动画「伸展」按 PowerPoint 子类型播放:水平展开(across)从中线向两侧长,自底部 / 顶部 / 左侧 / 右侧钉住对应边长出,不再一律从中心缩放并淡入;退场缩回同一条边。「挥舞」按 PowerPoint 的 `sin(2.5π·t)` 宽度(横向为高度)轨迹翻转并叠加淡入,中途会转到背面镜像,不再是一次 `rotateY`。
40
+ - 组合缩放只映射子元素外框:渲染时把 `chOff` / `chExt` 折算进子元素的位置、尺寸、路径与表格行列,不再对子树整体 `scale()`,描边、字号、内边距与效果半径保持物理尺寸(与 PowerPoint、LibreOffice 一致)。WPS 产出的归一化组合(子坐标空间不足 1px)不再出现千像素描边或逐字换行;普通缩放组合内的字号与描边也不再随组放大,非等比组合里的旋转子元素保持矩形。编辑器按同一份刚体外框工作:选择框、命中、框选、对齐 / 分布、吸附、裁剪、顶点与调节柄不再把非等比组合里的旋转子元素当成斜切的平行四边形;缩放与旋转在父组渲染坐标里计算后换算回模型,旋转跨过 45° 等边界时画面宽高保持不变(模型宽高随组缩放承接的轴向互换),粘贴进非等比组合同样按画面落位。`@web-ppt/core` 公开 `placeGroupChild`、`placeGroupFrame` 与 `unplaceGroupFrame`;`@web-ppt/edit-core` / `@web-ppt/editor` 公开 `renderedElement`、`renderedFrameToSlideMatrix`、`renderedContentToSlideMatrix`、`elementParentToRenderedParentMatrix`、`elementEditFrame`、`elementEditParentToSlideMatrix`、`slideToElementEditParentPoint` 与 `editFrameToModelFrame`(edit-core 另有 `groupEditChildrenToSlideMatrix`、`groupEditFrameToModelFrame`);`elementFrameToSlideMatrix` 与 `elementParentToSlideMatrix` 改为沿渲染链求值,只在非等比组合内的非 90° 整数倍旋转、或此类组合嵌套时结果不同(#7)。
41
+ - `wedgeRectCallout` / `wedgeRoundRectCallout` 按 ECMA-376 guide 求值:尾巴从尾尖所在一侧的边伸出,圆角取 `adj3`;此前底座固定在下边,尾尖朝上时尾巴穿过整个框只剩一条细线(#7 附件)。
42
+ - 非占位符形状 `p:style` 的 `fontRef` 颜色压过 presentation `defaultTextStyle` 的方案色,形状自身 `lstStyle` 与 `rPr` 仍优先;PowerPoint 默认形状里的白字不再渲染成黑字。
43
+ - 背景 `bgRef` 按 ST_StyleMatrixColumnIndex 取主题样式:`1001` 起对应 `bgFillStyleLst` 第 1 项、`1`–`999` 对应 `fillStyleLst`;此前 `idx="1001"` 被夹到列表末项,最常见的白色背景被画成主题第三档渐变。
44
+ - run 的字体栈只在文字含复杂文种(阿拉伯文、希伯来文、泰文、天城文等)时才纳入 `a:cs` 字体;此前拉丁字体缺席时 `cs`(如 WPS 常写的 Arial Black)会接管拉丁字母,标题被撑宽断行。新增公开函数 `textRunFontStack` 供编辑投影共用。
45
+ - 只写 `asvg:svgBlip`、`a:blip` 上没有位图兼容副本的图片(WPS 插入的图标)按 SVG 部件渲染,不再显示为不支持的对象。
46
+ - 有封面且源可用的音频不再叠加灰色播放标识(封面本身就是喇叭图标);`media: 'player'` 下音频不显示原生控件条,`Viewer` 点击封面播放 / 暂停,播放被拒绝时同样派发 `web-ppt-playback-error`。官网查看器改用 player 模式,放映中点击媒体不翻页。
47
+ - 等线缺席时西文改用合成字族:`local()` 引用系统 Arial 度量族,粗体 `size-adjust: 95.4%`、下划线 `74.6%`,只在页面出现等线时声明;`@web-ppt/fonts` 的替换路径对 Arimo 套用同一组参数。取代窄体回退与连续空格半字宽的处理;依据是 #8 附带的 PowerPoint 导出图逐词拟合,断行与填空线长度与 PowerPoint 一致。
48
+ - `Viewer` 执行动画时间线里的 `mediacall` 媒体调用(`playFrom` / `pause` / `stop` 等),`afterPrev` 等定时触发的音视频按时播放,切页或取消时暂停;`play()` 被浏览器拒绝时容器派发 `web-ppt-playback-error`。媒体调用步骤不再给图标留下强调动画的半透明终态(#7)。
49
+
50
+ `@web-ppt/collab` 发布入口为 12.20KB gzip;排除 peer 的完整测试薄包为 14,333B gzip,两种口径分别核对。
51
+
52
+ ## 0.5.0-beta.6 - 2026-10-10
53
+
54
+ ### 新增
55
+
56
+ - `presentationToVideo` 新增可选音轨:`audio: true` 时解析 timing 树媒体节点(`p:audio` 的音量 / 静音 / 循环 / 触发批次),按页时间轴确定性混音后以 Opus 48kHz 编码进 WebM 双轨;视频文件的 AAC 音轨同样提取混入,无音轨或解码失败经 `onWarning` 以稳定原因跳过、不阻断导出。无音频媒体时仍为无声视频,默认关闭时产物与上一版逐字节一致。公开 `WebmWriter` 音频轨与 `mixAudioClips` 混音核;`MediaInfo` 扩展 `spid` 与 `playback` 播放语义。
57
+ - 实验性矢量 PDF 的文字渐变原生支持(Pattern colorspace):任意轴线性渐变、同段多 run 各自分框(按字形墨迹范围)、下划线 / 删除线继承渐变。坐标口径经 MuPDF / poppler / CoreGraphics 三实现实测裁决;透明渐变因渲染层无输入语义暂按稳定原因回退。
58
+
59
+ ### 改进
60
+
61
+ - CJK 标点挤压补全 PowerPoint 完整规则:连续标点对、行首起始标点与行尾句读无条件收半格;断行判定仍按全角,与 PowerPoint 断行基线一致。
62
+ - 网络字体到货后的重排补齐数学布局缓存失效:`invalidateTextMeasureCaches` 公开导出,`Viewer.refresh()` 完整覆盖字体环境变化。
63
+ - 文档体系新增功能设计标准、分层依赖规范与 `.pptx` / `.ppt` 规范地图;依赖方向由 `check-architecture-boundary` 在 build 与 verify 机械审计。
64
+
65
+ ### 修复
66
+
67
+ - 修复矢量 PDF 跨页复用渐变 Pattern 时未在当前页注册资源导致的整段取色错误(单页导出不触发)。
68
+ - 修正 README 已知限制与能力矩阵的三处失真(EMF+ / OMML / 3D)及打开密码文件的能力描述。
69
+
70
+ `@web-ppt/collab` 发布入口为 12.20KB gzip;排除 peer 的完整测试薄包为 14,333B gzip,两种口径分别核对。
71
+
72
+ ## 0.5.0-beta.5 - 2026-09-10
73
+
74
+ ### 新增
75
+
76
+ - 高级文本生成保存与跨文稿复制支持公式、艺术字及已声明的高级文字效果;经典图表支持多级类别与共享工作簿同步,保留历史、恢复、协同和原生保存闭环。
77
+ - 新增实验性 `@web-ppt/core/pdf/vector` 及独立 browser 适配入口:支持范围内的文字可搜索,图形保留矢量,特殊效果按对象回退为图片并返回位置说明。
78
+ 编辑器导出窗口通过 Cordis 文件 / 字体服务提供入口,缺字体可定位并加载本机字体后重试;取消、换稿和退出等待实际资源消费结束。此版本不代表 005 整项验收完成;复杂文字效果、更多 SVG 组合、其他 PDF 阅读器及长文稿成本仍待收口。
79
+ - 新增 `@web-ppt/fonts/glyphs` 可选字体 Provider 及 HarfBuzz、Worker、FontFace 适配:验证原始容器与嵌入限制,返回定位字形、轮廓和 UTF-16 原文簇;整形器和字体字节由宿主提供。
80
+ 产品编辑器由 Cordis 文稿服务检查、应用和释放字体,提供中英文缺字诊断与本机替换;原始字体模式避免主线程重复解压,替换和安装失败会归还旧字体预算。当前字体资源用于预览及图片 / SVG / 实验性矢量 PDF 导出,不写入 PPTX。
81
+ - 可选 `@web-ppt/collab/migration` 入口为扩展字段保留带原 stamp 的 `set/del` 操作证据;来源元素或页面删除后仍可恢复。
82
+ 与默认入口共用协同会话和回滚,旧检查点缺少证据时保持未知;共享图表可按原操作裁决矛盾字段和删除,后续原操作可补齐缺失证据。
83
+ - 编辑核心可原子执行已裁决的扩展迁移凭据,保留来源操作、删除赢家和冷恢复语义;拒绝不完整来源及父子字段冲突。
84
+ 可选协同入口在远端接收和延迟消息消费时重新裁决,保留更晚本地操作;默认入口拒绝凭据,原始版本不会替换为运输消息的时钟。
85
+ 恢复日志在提交前准备声明,旧字段按原地址和顺序重放;解析器乱序释放不会复活旧实例。
86
+ - 包体积核对改为默认入口及全部相对静态分块逐文件 gzip 后求和,修正旧表漏算共享分块的问题;peer 与动态入口仍另计。
87
+ - 官网编辑产品层通过同一 Cordis 应用管理文稿、打开、新建、恢复、文件、业务工具与页面状态;页面插件持有模式、导航、历史和视口,入口仅保留启动及重试。
88
+ 新打开取消旧模板和图片选择,退出先停止页面与 SDK 编辑输入,再等待已提交文件交付,最后释放文稿、监听、观察器和弹窗;重建后命令不重复。发布 SDK 保持无框架依赖。
89
+ - 图片插入、替换和背景接口支持 `options.signal`;取消会释放待选入口,并在读取、解码及提交边界阻止迟到修改,
90
+ 避免应用退出等待文件交付期间旧工具继续写入文稿。
91
+ - 共享图表入口迁移独立数据区域的旧局部编辑,保留旧撤销历史、协同时钟及迟到消息;支持加载前复制页及
92
+ 多旧所有者/新旧数据的相等或不同字段合并。删除整张原页及中间副本后,幸存副本仍可编辑、保存和冷恢复。
93
+ 可选协同入口让复制快照继承可证明的原 set/del 版本,保留副本远端更新后的撤销重做,并裁决已加载共享入口的迟到复制。
94
+ 缺失原版本或同版本矛盾的字段、无来源记录及日志的旧裸模型、身份冲突仍保留只读,完整范围见迁移记录。
95
+
96
+ ### 验证与范围
97
+
98
+ - `@web-ppt/collab` 发布入口为 12.20KB gzip;包含相对静态分块。排除 peer 的完整测试薄包为 14,333B gzip,两种口径分别核对。
99
+ - 字体及 PDF 独立读取依赖固定版本并接入 CI / 发布环境;矢量 PDF 源码与产物契约纳入常规功能和一致性门禁。
100
+ - 保留 beta.4 的密码弹窗与 CI 稳定性修复。视频仍无音轨、内嵌视频合成和长尾格式扩展待后续;Windows PowerPoint 真机继续暂缓。
101
+ - 模块成果与下一步见[本轮交付说明](https://github.com/unStone/web-ppt/blob/master/docs/releases/0.5.0-beta.5.md)。
102
+
103
+ ## 0.5.0-beta.4 - 2026-09-09
104
+
105
+ ### 剩余功能收口
106
+
107
+ - 补齐四个产品入口的只读批注面板、PNG/SVG/ZIP 标记与打印正文;生成保存不再丢失批注,复制页独立分配批注部件和索引。
108
+ - 批注复制覆盖连续保存、撤销/重做、来源释放、外部补丁、恢复、最大索引及显式 Internal/未知 XML 扩展。
109
+ - 十页混合固件贯通七种原生现代布局、地图回退、经典数据、WAV/MP4、图片/立体和批注;新增包名产物契约及 0.8 功能完成度门禁。
110
+ - 新增按需图片效果和立体编辑,官网控件共用历史、恢复、协同与两条保存;双色调在 PNG/独立 SVG 中使用真实颜色。
111
+ - 官网与查看器遇到 ChartEx 自动按需启用原生解析,加载失败继续显示来源预览;SDK 默认行为不变。
112
+ - 新增可选画布读屏目录和 EditContext,真实 Chromium 覆盖预编辑、连续输入、撤销及加载失败回退。
113
+ - 修复经典图表系列标题与子节点顺序、零挤出重开及未加载扩展时双色调生成保存丢失。
114
+ - 补充混合内容保存旅程、公开 API 契约和迁移说明;Windows 真机按用户要求暂缓。
115
+
116
+ ### 新增
117
+
118
+ - 新增 `@web-ppt/core/chart-ex` 与显式解析 hook:树状图、旭日、直方图/Pareto、箱线、瀑布和漏斗输出原生 Schema;
119
+ 地图、歧义数据与不支持的来源保留 Office fallback。主题、母版与保存后继续沿用文稿打开时的解析配置。
120
+ SDK 默认入口不加载实现;官网按内容类型自动加载。其他类型真实 PPTX 和 Office 原生布局验收尚未完成。
121
+ - 官网增加中英文基础入口:三张页面输出静态英文镜像与双语 SEO,URL 优先于本机偏好和浏览器语言;
122
+ 原地切换保留文稿、选区和撤销历史,中文默认不下载英文词库。编辑页打开/保存/图片 ZIP、转换确认、
123
+ 模板选择、本机恢复、动态工具与无障碍文案均已接入;模板和恢复提示内可直接切换语言。
124
+ - 媒体按需入口支持 PCM WAV / MP4(含分片)、显式 HTTP(S) 外链、确定性音频图标和独立海报替换;
125
+ editor / React / Vue 和官网媒体工具转发同一 API,默认入口不加载媒体校验或图标。
126
+ 官网支持选中媒体试听、失效外链提示与按需媒体日志恢复,外链仅在点击播放时访问;切换文稿和关闭工具释放播放器。
127
+ - 经典图表新增按需数据编辑入口,支持系列、类别和数值增删改;稀疏覆盖贯通历史、恢复与协同,保存同时同步
128
+ 图表缓存和内嵌工作簿。官网数据检查器动态加载,默认入口不携带图表编辑实现。
129
+ - 主题、母版、版式与内置模板现已组成同一条公开编辑链:主题经 `exec`,母版与版式经 `execDesign`
130
+ 精确传播到依赖页,页面直设继续优先;三套模板共用历史、恢复、字段级协同与保存协议。
131
+ - 新增独立按需模板入口:极光、刊页、夜幕三套确定性配方各自物化主题、母版、五种常用版式和可编辑标题页;
132
+ 官网、无框架 editor 与 React/Vue 只转发同一目录,默认入口不加载配方或预览数据。
133
+ - 编辑模型补齐字符、自动编号和图片项目符号;结构化来源值、富文本剪贴板、格式刷、表格文字、恢复、协同
134
+ 以及补丁/生成保存使用同一套项目符号语义。
135
+ - 新增预设形状切换、DrawingML 调节值和独立 `@web-ppt/editor/adjustments` 拖柄入口;187 个预设中
136
+ 120 个含句柄的定义由固定规范源生成,未使用时不进入 editor 主包。
137
+ - 高级字符格式新增 17 种精确下划线、双删除线、高亮、字距、大小写与基线上下标;`ClearFormat` 删除
138
+ 来源直设并保留文字、动态字段、公式和链接身份。
139
+ - 新增按需入口 `@web-ppt/core/image-zip`:以稳定原页码命名批量导出动画终态 PNG,支持隐藏页策略、
140
+ 1–8 路有界并发、确定性 ZIP 元数据与逐页进度;任一页失败会携带页码原子拒绝,不返回半成品。
141
+ 它复用单页 data URI + `foreignObject` 光栅化与 `SecurityError` 原生文本回退,不引入第二套渲染器。
142
+ - 编辑模型、无框架 editor 与 React/Vue 类型入口补齐对象等距分布、替代文字、节管理和页面尺寸;四组能力
143
+ 贯通来源恢复、历史/恢复、字段级协同及保留型/生成式保存,页面尺寸采用不重排元素的“最大化”语义。
144
+ - 无框架 editor 新增仅触屏启用的细描边命中容差、双指缩放/平移和 500ms 长按上下文请求;导航结果经
145
+ adapter 与 React/Vue 公开 seam 交给宿主,不写模型、历史、恢复日志或保存内容,查看模式保留页面滚动。
146
+ - `EditorSession.toPresentation()` 公开当前编辑态只读投影,让官网可直接批量导出图片 ZIP,同时保持脏状态
147
+ 与保存点不变;资源生命周期仍由会话统一拥有。
148
+
149
+ ### 改进
150
+
151
+ - `@web-ppt/collab` 发布入口为 11.76KB gzip;契约测试使用的完整薄包在排除 peer 后为 14,288B gzip。
152
+
153
+ ### 修复
154
+
155
+ - 官网打开加密 `.pptx` / `.ppt` 时弹出密码对话框:错误密码可原地重试,正确后继续查看;core 暴露可识别的 `PasswordRequiredError`,避免界面依赖错误文案判断。
156
+ - 官网内联 CSS 后仅删除无引用产物,并等 Vite 生成预加载清单后再检查,避免共享样式缺失导致动态编辑模块打不开。
157
+ - 无封面媒体新增海报后可保存;兼容分支媒体继续保留整壳限制。生成保存不再重放旧海报关系。
158
+ - 公开 Patch 事件补齐图片资源闭包,修复接收端撤销回收后无法重做;提交及历史双向闭包超限在提交前原子拒绝,
159
+ 合并历史超限则保留独立撤销单元。
160
+ 无 OOXML 所属页的新建对象可恢复,仍被无来源海报引用的资源不可被远端删除。
161
+ - MC 兼容内容按声明命名空间与实际能力选择;真实 ChartEx 漏斗回退图恢复显示,编辑以完整兼容外壳为身份。
162
+ 移动、复制、层级、撤销与两条保存同步保留所有表示及原始数据;编辑会话保留兼容来源,原包释放后仍可生成保存,缺失或冲突的源依赖明确报错。
163
+ - 复制、外部补丁回放和恢复分配对象 ID 时计入未显示的兼容分支,避免新增形状撞 ID;OPC 写回不再因
164
+ Node Buffer 的切片共享内存而污染调用方输入。
165
+ - 0.7 集成验收打通“模板 → 主题 → 母版 → 版式 → 普通页面 → 保存重开”,并修复普通页面事务可绕过
166
+ 设计画布权限的问题;普通页面权限隔离现由唯一提交入口统一执行。
167
+ - LibreOffice 与 Windows PowerPoint 改为消费同一份 11 件 Office 清单;清单、产物字节和 PowerPoint
168
+ 报告均绑定当前提交,避免两套验收范围悄然分叉。
169
+ - 0.6 跨能力新建旅程打通表格、项目符号、预设形状、高级字符、等距分布、替代文字、节、页面尺寸、
170
+ 触屏与批量导出;恢复后分别走补丁和生成保存,再由 Chrome 与 LibreOffice 重开验证。
171
+ - 生成保存现在保留动态页码字段与替代文字;此前这些能力单独测试可用,但从空白文稿混合编辑后会拒绝
172
+ 动态字段或丢失 `cNvPr@title/@descr`。
173
+
174
+ ## 0.5.0-beta.3 - 2026-08-31
175
+
176
+ ### 新增
177
+
178
+ - `@web-ppt/edit-core` 新增稳定表格网格:任意位置插删行列、行高列宽、合并拆分与直接单元格格式都按
179
+ 稳定行列身份寻址,并通过公开 `queryTableGrid` 与纯数据命令进入历史、恢复和保存链路。
180
+ - 表格 tombstone 与完整合并真值让删除只改变可见投影;被合并覆盖的文字和格式休眠而不销毁,拆分、撤销及
181
+ 保存重开后仍恢复同一逻辑格。`@web-ppt/collab` 同步覆盖并发插删、合并和“至少保留一行一列”的确定性收敛;
182
+ 发布入口为 11.76KB gzip;契约测试使用的完整薄包在排除 peer 后为 14,288B gzip,两种口径不混用。
183
+
184
+ ### 改进
185
+
186
+ - 发布前一致性门禁统一核对版本、许可证、链接、断言数、快照数、包体积和发布包清单,并把 0.6 后续能力拆为
187
+ 可独立验收的 Wayfinder 票据。
188
+
7
189
  ## 0.5.0-beta.2 - 2026-08-30
8
190
 
9
191
  ### 新增
@@ -15,6 +197,7 @@
15
197
  - 新增可选的 `@web-ppt/collab`:字段级 LWW、分数序与确定性页序重物化把 `Editor` 补丁流接到
16
198
  可插拔 provider,支持离线幂等回放、remove-wins、远端历史 rebase 和 BroadcastChannel 双标签页。
17
199
  OOXML 元素身份由 provider 分配的副本 slot 精确分区;协同包不进入单机 `editor` 入口。
200
+ 发布入口为 10.70KB gzip;契约测试使用的完整薄包在排除 peer 后为 13,023B gzip,两种口径不混用。
18
201
 
19
202
  ### 改进
20
203
 
package/README.md CHANGED
@@ -20,6 +20,7 @@ const pres = await parse(file);
20
20
  const v = new Viewer(container, pres, { animate: true, autoAdvance: true });
21
21
 
22
22
  v.next(); // plays a pending animation batch, otherwise advances the slide
23
+ v.prev(); // rewinds one finished click, otherwise the previous slide at its end build
23
24
  v.finishAnimations(); // jump to this slide's end state
24
25
  v.setZoom(1.5);
25
26
  v.search('keyword'); // → array of matching slide indices
@@ -42,6 +43,8 @@ st.subscribe((change) => {
42
43
  if (change.transition) playTransition(prevEl, nextEl, change.transition);
43
44
  } else if (change.type === 'animation' && change.group) {
44
45
  playGroup(container, change.group);
46
+ } else if (change.type === 'animation' && change.settle) {
47
+ // repaint, then apply hiddenElementIds and the settled style of completed steps
45
48
  }
46
49
  });
47
50
 
@@ -0,0 +1,7 @@
1
+ import type { Slide } from '@web-ppt/core';
2
+ export interface CommentsPanel {
3
+ setSlide(slide: Pick<Slide, 'comments'> | null): void;
4
+ dispose(): void;
5
+ }
6
+ /** 只读目录独立加载;内容只进入文本节点,不能被当成宿主 HTML 或翻译词条。 */
7
+ export declare function createCommentsPanel(container: HTMLElement, emptyLabel?: string): CommentsPanel;
@@ -0,0 +1,26 @@
1
+ function x(l, u = "No comments on this slide") {
2
+ const n = l.ownerDocument, r = n.createElement("section"), a = n.createElement("ol"), o = n.createElement("p");
3
+ r.dataset.commentsPanel = "", o.dataset.commentsEmpty = "", o.textContent = u, a.style.paddingInlineStart = "28px", r.append(a, o), l.append(r);
4
+ let c = !1;
5
+ return {
6
+ setSlide(s) {
7
+ if (c) return;
8
+ a.replaceChildren();
9
+ const d = (s == null ? void 0 : s.comments) ?? [];
10
+ o.hidden = !!d.length, a.hidden = !d.length, d.forEach((t, i) => {
11
+ const e = n.createElement("li"), m = n.createElement("strong"), p = n.createElement("p");
12
+ if (e.dataset.commentId = t.id ?? `source:${i}`, t.parentId && (e.dataset.parentCommentId = t.parentId, e.style.marginInlineStart = "20px"), e.value = t.idx ?? i + 1, e.style.marginBlock = "16px", m.textContent = t.author, p.textContent = t.text, p.style.whiteSpace = "pre-wrap", p.style.overflowWrap = "anywhere", e.append(m), t.date) {
13
+ const h = n.createElement("time");
14
+ h.textContent = ` · ${t.date}`, e.append(h);
15
+ }
16
+ e.append(p), a.append(e);
17
+ });
18
+ },
19
+ dispose() {
20
+ c = !0, r.remove();
21
+ }
22
+ };
23
+ }
24
+ export {
25
+ x as createCommentsPanel
26
+ };
@@ -0,0 +1,16 @@
1
+ import type { AnimStep } from '@web-ppt/core';
2
+ interface Keyframes {
3
+ from: Keyframe;
4
+ to: Keyframe;
5
+ }
6
+ export declare function wipeClip(dir: string | undefined, hidden: boolean): string;
7
+ export declare function framesFor(step: AnimStep): Keyframes;
8
+ export interface PlayHandle {
9
+ /** 由本句柄创建的动画;宿主需要精确回收时无需扫描整棵 DOM。 */
10
+ readonly animations: readonly Animation[];
11
+ cancel(): void;
12
+ finished: Promise<void>;
13
+ }
14
+ /** 播放一批动画;返回可取消的句柄 */
15
+ export declare function playGroup(container: Element, group: AnimStep[]): PlayHandle;
16
+ export {};
package/dist/index.d.ts CHANGED
@@ -11,5 +11,6 @@ export { Viewer } from './viewer';
11
11
  export { foreignObjectScalesCorrectly, resetForeignObjectProbe } from './foreign-object';
12
12
  export type { ViewerOptions } from './viewer';
13
13
  export { autoAdvanceMs, framesFor, morphPairs, playGroup, playTransition, playTransitionControlled, transitionFrames, } from './playback';
14
+ export { settledDeclaration } from './settled';
14
15
  export { groupSteps, hiddenBefore, staticHidden } from '@web-ppt/core';
15
16
  export type { PlayHandle, TransitionPlayHandle } from './playback';
@@ -0,0 +1,9 @@
1
+ import type { AnimStep } from '@web-ppt/core';
2
+ type MediaCommand = NonNullable<AnimStep['mediaCommand']>;
3
+ export interface ScheduledMediaCommand {
4
+ finished: Promise<void>;
5
+ cancel(): void;
6
+ }
7
+ /** 媒体调用属于时间线行为;视觉动画结束不代表媒体已开始播放。 */
8
+ export declare function scheduleMediaCommand(target: Element, command: MediaCommand, startMs: number): ScheduledMediaCommand | null;
9
+ export {};
@@ -1,17 +1,6 @@
1
- import { type AnimStep, type Slide, type Transition } from '@web-ppt/core';
2
- interface Keyframes {
3
- from: Keyframe;
4
- to: Keyframe;
5
- }
6
- export declare function framesFor(step: AnimStep): Keyframes;
7
- export interface PlayHandle {
8
- /** 由本句柄创建的动画;宿主需要精确回收时无需扫描整棵 DOM。 */
9
- readonly animations: readonly Animation[];
10
- cancel(): void;
11
- finished: Promise<void>;
12
- }
13
- /** 播放一批动画;返回可取消的句柄 */
14
- export declare function playGroup(container: Element, group: AnimStep[]): PlayHandle;
1
+ import { type Slide, type Transition } from '@web-ppt/core';
2
+ export { framesFor, playGroup } from './element-playback';
3
+ export type { PlayHandle } from './element-playback';
15
4
  export declare function transitionFrames(t: Transition, incoming: boolean): Keyframe[];
16
5
  /**
17
6
  * morph:按 data-el(形状 id)在前后两页之间配对,配上的元素做几何补间,
@@ -39,4 +28,3 @@ export declare function playTransitionControlled(outgoing: HTMLElement | null, i
39
28
  export declare function playTransition(outgoing: HTMLElement | null, incoming: HTMLElement, t: Transition | undefined): Promise<void>;
40
29
  /** 幻灯片是否配置了自动换片 */
41
30
  export declare function autoAdvanceMs(slide: Slide): number | null;
42
- export {};
@@ -0,0 +1,33 @@
1
+ /**
2
+ * 百叶窗和盒状的裁剪窗口。
3
+ *
4
+ * 百分比必须相对形状的 fill-box。动画挂在 `data-el` 的 `<g>` 上,
5
+ * 不写 geometry box 时百分比相对整页视口,条带会铺到幻灯片而不是对象上。
6
+ *
7
+ * 百叶窗固定 6 条,每条从自己槽位的上沿或左沿展开,内容不缩放。
8
+ * 盒状只揭开、不缩放:`in` 从四边向中心收,`out` 从中心矩形向四边扩。
9
+ */
10
+ /** `vert` 是竖条,其余(含 horizontal)是横条。 */
11
+ export declare function blindsClip(dir: string | undefined, open: boolean): Keyframe;
12
+ /** 相对元素边框的百分比窗口。整框是 0,0,100,100。 */
13
+ export interface ClipRegion {
14
+ l: number;
15
+ t: number;
16
+ w: number;
17
+ h: number;
18
+ }
19
+ /**
20
+ * `polygon()` 只有一条轮廓,四边串起来会在中心拉出蝴蝶结。
21
+ * 向内用四块蒙版拼边框:上下两条通宽,左右两条通高。`out` 仍是中心矩形。
22
+ * 段落动画传入文字墨迹框,四边相对这段字而不是整块占位框。
23
+ */
24
+ export declare function boxInMask(open: boolean, region?: ClipRegion): Keyframe;
25
+ /** `out` 从窗口中心向四边扩大。 */
26
+ export declare function boxOutClip(open: boolean, region?: ClipRegion): string;
27
+ export declare function splitClip(dir: string | undefined, open: boolean): Keyframe;
28
+ export declare function checkerboardClip(dir: string | undefined, open: boolean): Keyframe;
29
+ export declare function randomBarsClip(dir: string | undefined, open: boolean | number): Keyframe;
30
+ export declare function stripsClip(dir: string | undefined, open: boolean | number): Keyframe;
31
+ export declare function irisClip(effect: 'circle' | 'diamond' | 'plus', dir: string | undefined, open: boolean | number): Keyframe;
32
+ export declare function wheelClip(spokes: string | undefined, open: boolean | number): Keyframe;
33
+ export declare function dissolveClip(open: boolean | number, seed: number): Keyframe;
@@ -0,0 +1,9 @@
1
+ import type { AnimStep } from '@web-ppt/core';
2
+ /**
3
+ * 一批点击结束时,元素上该留下的内联样式。
4
+ *
5
+ * 不倒放关键帧:播放层没有可逆时间轴,withPrev 和路径倒着播会对不齐。
6
+ * 入场的结束帧就是 SVG 本来的样子,退场靠隐藏集,所以这两类不写样式。
7
+ * 强调停在 fill 的结束帧上;路径停在采样折线的最后一点上。
8
+ */
9
+ export declare function settledDeclaration(step: AnimStep): Record<string, string> | null;
package/dist/state.d.ts CHANGED
@@ -24,12 +24,16 @@ export type StateChange =
24
24
  previous: number;
25
25
  transition?: Transition;
26
26
  }
27
- /** 动画批次推进。`group` 是本次应播放的这一批 */
27
+ /**
28
+ * 动画批次推进。`group` 是本次应播放的这一批。
29
+ * `settle` 表示光标退回了一批:UI 要重绘并摆到该光标的终态,不能再播一遍。
30
+ */
28
31
  | {
29
32
  type: 'animation';
30
33
  done: number;
31
34
  total: number;
32
35
  group: AnimStep[] | null;
36
+ settle?: boolean;
33
37
  } | {
34
38
  type: 'zoom';
35
39
  zoom: number;
@@ -55,6 +59,8 @@ export declare class PresentationState {
55
59
  get animationTotal(): number;
56
60
  get animationDone(): number;
57
61
  get hasPendingAnimation(): boolean;
62
+ /** 光标之前已经结束的步骤。终态样式按这个顺序覆盖。 */
63
+ get completedSteps(): readonly AnimStep[];
58
64
  /**
59
65
  * 当前应当隐藏的元素 id —— UI 据此设置可见性。
60
66
  *
@@ -75,6 +81,13 @@ export declare class PresentationState {
75
81
  goTo(i: number, direction?: 'forward' | 'backward'): boolean;
76
82
  /** 有待播动画时先播动画,否则翻页 */
77
83
  next(): void;
84
+ /**
85
+ * 放映时先退回一批已经播过的点击;本页还在开头,才去上一张可见页。
86
+ *
87
+ * 上一页要落在终态,否则入场又藏起来,没法继续往回退。
88
+ * 光标必须在发出 slide 之前拨到末批,不然会先画出开头那一帧。
89
+ * 浏览关掉动画时批次表是空的,这里仍是整页回退。
90
+ */
78
91
  prev(): void;
79
92
  setZoom(z: number): void;
80
93
  setAnimate(on: boolean): void;
@@ -0,0 +1,30 @@
1
+ import type { Presentation, Slide } from '@web-ppt/core';
2
+ import type { VideoOptions } from './timing';
3
+ type OverlayWarning = NonNullable<VideoOptions['onWarning']>;
4
+ /**
5
+ * 内嵌视频合成层:把 `<video>` 当前帧叠加到导出画布的媒体元素位置。
6
+ *
7
+ * 用 seek + drawImage 而非 WebCodecs 逐帧解码:导出本就是逐帧离线流程,
8
+ * seek 的异步等待不破坏确定性(同一媒体字节 + 同一时间戳 → 同一帧),
9
+ * 且浏览器 `<video>` 已覆盖全部容器 / 编码组合,免去手写 MP4 demux。
10
+ *
11
+ * 合成失败不阻断导出(调用方已经以 `mediaPosters` 显式接受海报呈现),
12
+ * 但每次退回海报都经 `onWarning` 以稳定 reason 暴露:
13
+ * - `video-overlay-unavailable`:加载失败、5s 内未取得首帧或时长无效,该视频全程保留海报帧
14
+ * - `video-overlay-frame-unavailable`:seek 超时或帧数据不可用,受影响的帧可能是海报或非目标时刻的画面
15
+ */
16
+ export declare class MediaOverlay {
17
+ private readonly warn;
18
+ private videos;
19
+ private disposed;
20
+ constructor(warn: OverlayWarning | undefined);
21
+ /** 预加载页面的视频媒体(每页一个列表;须在导出循环前调用) */
22
+ load(pres: Presentation, slide: Slide, slideNumber: number, groupStarts: number[]): Promise<void>;
23
+ /**
24
+ * 把当前时间轴的视频帧画到画布。在 SVG 光栅化之后调用,叠加在海报帧之上。
25
+ * scaleX/scaleY 把幻灯片坐标换算到导出画布坐标。
26
+ */
27
+ draw(context: CanvasRenderingContext2D, slideNumber: number, timelineMs: number, scaleX: number, scaleY: number): Promise<void>;
28
+ dispose(): void;
29
+ }
30
+ export {};
@@ -0,0 +1,31 @@
1
+ /**
2
+ * 确定性 PCM 混音核:把按时间轴排布的已解码音频片段混成一条交错浮点轨。
3
+ *
4
+ * 视频导出的时间轴是离线推进的(帧时间戳由 videoPlan 决定,不受实际耗时影响),
5
+ * 音轨必须同一性质——这里不做任何实时 API(OfflineAudioContext 的调度语义与
6
+ * 编解码图都不需要),只用纯函数计算:每输出样本是各片段在对应时刻取值的
7
+ * 加权和,重采样用确定性线性插值。解码(WAV 直读 / decodeAudioData)属于
8
+ * 接线层,本模块只吃浮点样本。
9
+ */
10
+ export interface AudioClip {
11
+ /** 已解码的交错样本,取值 [-1,1];声道数由 channels 声明 */
12
+ samples: Float32Array;
13
+ /** 片段采样率,正整数;与输出不一致时按线性插值重采样 */
14
+ sampleRate: number;
15
+ /** 片段声道数,只接受 1 或 2 */
16
+ channels: number;
17
+ /** 时间轴上的起始毫秒(由页序播放时机换算,>= 0) */
18
+ startMs: number;
19
+ /** 播放截止毫秒(相对时间轴原点);缺省为片段自然结束,循环时默认播到轴尾 */
20
+ endMs?: number;
21
+ loop?: boolean;
22
+ /** [0,1];0 即静音(仍参与求和,语义与浏览器媒体元素一致) */
23
+ volume?: number;
24
+ }
25
+ export interface MixOptions {
26
+ /** 输出采样率,默认 48000(Opus 编码标准速率) */
27
+ sampleRate?: number;
28
+ /** 输出声道数,默认 2 */
29
+ channels?: number;
30
+ }
31
+ export declare function mixAudioClips(clips: readonly AudioClip[], totalMs: number, options?: MixOptions): Float32Array;
@@ -0,0 +1,17 @@
1
+ import { type Presentation, type Slide, type AnimStep } from '@web-ppt/core';
2
+ /** 暂停动画后读取实际计算样式,把浏览器补间结果固化到单帧 SVG。 */
3
+ export declare class VideoScene {
4
+ private pres;
5
+ private showComments;
6
+ readonly host: HTMLDivElement;
7
+ readonly svg: SVGSVGElement;
8
+ private animations;
9
+ constructor(pres: Presentation, showComments: boolean);
10
+ page(slide: Slide, hidden: readonly number[]): Promise<SVGGElement>;
11
+ private pause;
12
+ group(page: SVGGElement, steps: AnimStep[]): Animation[];
13
+ transition(outgoing: SVGGElement, incoming: SVGGElement, slide: Slide): Animation[];
14
+ settle(animations: Animation[]): void;
15
+ draw(context: CanvasRenderingContext2D, width: number, height: number): Promise<void>;
16
+ dispose(): void;
17
+ }
@@ -0,0 +1,56 @@
1
+ import { type AnimStep, type Presentation, type Slide } from '@web-ppt/core';
2
+ export interface VideoOptions {
3
+ scale?: number;
4
+ fps?: number;
5
+ bitrate?: number;
6
+ skipHidden?: boolean;
7
+ showComments?: boolean;
8
+ animations?: boolean;
9
+ transitions?: boolean;
10
+ /** 没有自动换片时使用的停留时长,默认 3000 ms。 */
11
+ slideDurationMs?: number;
12
+ /** 自动触发每个点击批次前的停留,默认 700 ms。 */
13
+ clickDelayMs?: number;
14
+ /**
15
+ * 显式允许内嵌音视频以静态封面呈现;文稿含媒体时必须开启,否则导出直接拒绝。
16
+ * 开启 `audio` / `videoOverlay` 时同样必需:二者只覆盖带 timing 播放语义的媒体,失败时退回封面。
17
+ */
18
+ mediaPosters?: boolean;
19
+ /** 导出音轨:timing 媒体节点的音频混为 Opus;无音频媒体时仍为无声视频 */
20
+ audio?: boolean;
21
+ /** 导出时把内嵌视频的当前帧叠加到画面(替代海报帧);需要 timing 播放语义 */
22
+ videoOverlay?: boolean;
23
+ signal?: AbortSignal;
24
+ onProgress?: (value: {
25
+ completed: number;
26
+ total: number;
27
+ slideNumber: number;
28
+ }) => void;
29
+ /**
30
+ * 增强能力失败的显式通道(稳定 reason),不阻断导出;产品层可订阅展示:
31
+ * - `video-audio-decode-failed`:视频提轨解码失败,该视频不混入音轨
32
+ * - `video-overlay-unavailable`:内嵌视频加载失败或超时,全程保留海报帧
33
+ * - `video-overlay-frame-unavailable`:内嵌视频取帧失败(seek 超时等),部分帧可能为海报或非目标时刻画面
34
+ */
35
+ onWarning?: (reason: string, detail: {
36
+ slideNumber: number;
37
+ elementName: string;
38
+ }) => void;
39
+ }
40
+ export declare function groupDuration(steps: AnimStep[]): number;
41
+ export declare function videoPlan(pres: Presentation, options: VideoOptions): {
42
+ width: number;
43
+ height: number;
44
+ fps: number;
45
+ bitrate: number;
46
+ pages: {
47
+ slide: Slide;
48
+ slideNumber: number;
49
+ groups: AnimStep[][];
50
+ transition: number;
51
+ hold: number;
52
+ click: number;
53
+ }[];
54
+ total: number;
55
+ frames: (duration: number) => number;
56
+ };
@@ -0,0 +1,25 @@
1
+ /** WebM 的时间戳使用毫秒;帧的微秒时间来自编码器,与导出耗时无关。 */
2
+ export interface WebmAudioTrack {
3
+ sampleRate: number;
4
+ channels: 1 | 2;
5
+ }
6
+ export declare class WebmWriter {
7
+ private width;
8
+ private height;
9
+ private fps;
10
+ private codec;
11
+ private audio?;
12
+ private clusters;
13
+ private last;
14
+ private audioLast;
15
+ private audioBlocks;
16
+ private duration;
17
+ private length;
18
+ private closed;
19
+ constructor(width: number, height: number, fps: number, codec: 'VP8' | 'VP9', audio?: WebmAudioTrack | undefined);
20
+ private clusterFor;
21
+ addFrame(data: Uint8Array, timestamp: number, duration: number, key: boolean): void;
22
+ /** Opus 音频块:时间戳毫秒、流内严格递增;与视频块按簇交错 */
23
+ addAudio(data: Uint8Array, timestampMs: number): void;
24
+ finish(): Blob;
25
+ }
@@ -0,0 +1,10 @@
1
+ import { type Presentation } from '@web-ppt/core';
2
+ import { videoPlan, type VideoOptions } from './video/timing';
3
+ export type { VideoOptions };
4
+ export { videoPlan };
5
+ /**
6
+ * 浏览器固定时间轴导出;复用播放器补间,WebCodecs 编码 VP8/VP9。
7
+ * `audio` 开启时把 timing 媒体的音频混为 Opus 48kHz 音轨,`videoOverlay` 开启时把内嵌视频当前帧
8
+ * 叠加到画面;两者默认关闭,关闭时产物与无此选项时逐字节一致。增强能力失败经 `onWarning` 暴露。
9
+ */
10
+ export declare function presentationToVideo(pres: Presentation, options?: VideoOptions): Promise<Blob>;