feat: 实现 DOCX 可编辑字体嵌入
This commit is contained in:
+22
-2
@@ -232,6 +232,25 @@ Server Playwright 与 Desktop Electron 适配器,按主题 CSS SHA-256
|
||||
空白正文。下一步按 R2 字体嵌入、R3 PDF 与 DOCX 视觉差异引擎、R4 全主题
|
||||
深度回归的顺序推进。
|
||||
|
||||
阶段 12D-R2 已完成纯 Node.js DOCX 字体嵌入和代表主题验收。主题清单可
|
||||
声明本地或内置共享的 OTF、TTF、WOFF、WOFF2 字形;引擎使用
|
||||
`fontverter` 将 Web 字体转为 SFNT,解析字体内部名称、字重和 `fsType`,
|
||||
仅接受可安装或可编辑嵌入权限,再以稳定字体键生成 ODTTF 部件、字体表
|
||||
关系和内容类型。最终校验会解混淆字体并重新检查 SFNT、权限、字体族、
|
||||
关系、孤立部件和内容类型。WOFF2 WASM 解码已改为进程内串行调度,避免
|
||||
并发非重入导致批量字体损坏。内置主题统一前置共享字体 CSS,网页、PDF
|
||||
和 DOCX 使用同一字体资产,映射过程不按主题 ID 分支。
|
||||
|
||||
测试机未安装 Fandol 或 Open Sans。政企正式工作报告、政企红头标准文件
|
||||
和 Typora Github 分别成功嵌入 2、5、5 个字形面,Word 与 WPS 均能打开、
|
||||
显示和编辑;Word → WPS → Word 双向编辑 3/3 通过。完整 14 主题矩阵、
|
||||
全项目测试、类型检查和构建通过。视觉检查确认正式报告和 Github 在
|
||||
Word/WPS 均为 2 页;红头主题在 Word 为 2 页、WPS 为 3 页,版记被移到
|
||||
近空白末页,已冻结为 R3 视觉差异引擎的首个回归样例。WPS 默认另存还会
|
||||
移除嵌入字体,启用字体嵌入后可保留但会扩大互存文件;项目不会因 WPS
|
||||
写回器产生的 OOXML 顺序问题放宽自身严格门禁。完整记录见
|
||||
[DOCX 字体嵌入与代表主题验收](V0.6.0_DOCX_FONT_EMBEDDING.md)。
|
||||
|
||||
## 2. 已完成
|
||||
|
||||
### 2.1 项目骨架
|
||||
@@ -1168,8 +1187,9 @@ ECharts 第二阶段浏览器与 PDF 验证结果:
|
||||
- 阶段 12C:已完成通用 Pandoc 结构映射、封面真实分节、标题去重、
|
||||
表格内容区全宽、分页控制、OOXML 严格性和 14 主题真实 Word/WPS
|
||||
无修复打开门禁;
|
||||
- 阶段 12D:依次完成可分发字体嵌入、PDF 与 DOCX 自动视觉差异、14 套
|
||||
主题真实 Word/WPS 编辑互存和深度视觉回归;
|
||||
- 阶段 12D-R2:已完成可分发字体嵌入和三套代表主题真实 Word/WPS
|
||||
编辑互存;下一步 R3 建立 PDF 与 DOCX 自动视觉差异引擎,再由 R4 完成
|
||||
14 套主题深度视觉回归;
|
||||
- 阶段 13:完成 Word/WPS 双向互存、外部主题兼容、体积和正式发布验收。
|
||||
|
||||
每个阶段验收通过后创建一个独立提交,再进入下一阶段。当前阶段不得混入
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
# v0.6.0 DOCX 字体嵌入与代表主题验收
|
||||
|
||||
最后更新:2026-07-31
|
||||
|
||||
## 1. 目标与边界
|
||||
|
||||
阶段 12D-R2 解决 DOCX 在目标机器没有安装主题字体时发生字体回退的问题。
|
||||
实现必须同时满足:
|
||||
|
||||
- 纯 Node.js 运行,不增加 Python 环境;
|
||||
- 接受主题现有的 OTF、TTF、WOFF 和 WOFF2 资源;
|
||||
- 只嵌入明确声明且许可证位允许编辑的字体;
|
||||
- 使用 Word 原生嵌入字体部件,正文、标题、表格和代码仍可编辑;
|
||||
- Word 与 WPS 均可打开、编辑和互存;
|
||||
- 转换、关系、内容类型和字体部件继续通过严格 OOXML 校验;
|
||||
- 外部主题可以通过清单接入,不依赖主题 ID 分支。
|
||||
|
||||
字体采用完整字形嵌入,不做子集化。这样用户在 Word 或 WPS 中输入原文
|
||||
没有出现过的新字符时,仍可继续使用同一字体。代价是生成文件明显增大。
|
||||
|
||||
## 2. 通用实现
|
||||
|
||||
主题清单新增可选的 `docxFonts.faces`:
|
||||
|
||||
```json
|
||||
{
|
||||
"family": "FandolFang",
|
||||
"aliases": ["FangSong", "STFangsong"],
|
||||
"source": "theme-shared:official-fonts/FandolFang-Regular.woff2",
|
||||
"weight": 400,
|
||||
"style": "normal",
|
||||
"license": "GPL-3.0-or-later WITH Font-exception-2.0"
|
||||
}
|
||||
```
|
||||
|
||||
处理链路如下:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["主题字体清单"] --> B["受限资源读取"]
|
||||
B --> C["WOFF/WOFF2 转 SFNT"]
|
||||
C --> D["解析名称、字重与 fsType"]
|
||||
D --> E["许可证和体积门禁"]
|
||||
E --> F["ODTTF 混淆"]
|
||||
F --> G["写入 fontTable 与关系"]
|
||||
G --> H["重写 Word 字体引用"]
|
||||
H --> I["严格 OOXML 校验"]
|
||||
```
|
||||
|
||||
核心约束:
|
||||
|
||||
- 单主题最多 16 个字形面;
|
||||
- 单个源字体最多 8 MiB,源字体总量最多 32 MiB;
|
||||
- 单个解码后 SFNT 最多 16 MiB,总量最多 48 MiB;
|
||||
- 接受 `fsType=0x0000` 的可安装嵌入和 `fsType=0x0008` 的可编辑嵌入;
|
||||
- 拒绝受限、仅预览打印、位图嵌入及无效权限组合;
|
||||
- 字体键和 ODTTF 部件名由内容指纹稳定生成;
|
||||
- WOFF2 WASM 解码采用进程内串行调度,避免底层非重入导致批量损坏;
|
||||
- 同一字体内容按 SHA-256 合并并使用有界缓存;
|
||||
- 内置主题可引用 `_shared` 字体,本地主题不能越权引用内置共享资源;
|
||||
- 最终校验器会解混淆字体,重新检查 SFNT、内部字体族、权限位、关系和
|
||||
孤立部件。
|
||||
|
||||
`themes/_shared/official-fonts/fonts.css` 会统一前置到内置主题 CSS,使网页、
|
||||
PDF 和 DOCX 使用同一字体资产。字体选择和别名替换按字体族工作,没有
|
||||
主题 ID 判断。
|
||||
|
||||
## 3. 许可证边界
|
||||
|
||||
Fandol 字体:
|
||||
|
||||
- 使用仓库已有的 Fandol WOFF2;
|
||||
- 字体内 `fsType=0x0008`,允许可编辑嵌入;
|
||||
- 许可证为 GPL-3.0-or-later with Font Exception;
|
||||
- 完整许可证已位于
|
||||
`themes/_shared/official-fonts/LICENSE.txt`。
|
||||
|
||||
Open Sans:
|
||||
|
||||
- `typora-github` 当前包含的是 `v17` 历史字体文件;
|
||||
- 该批二进制的字体元数据和历史发行信息对应 Apache License 2.0;
|
||||
- 历史版权声明和完整许可证随字体放在
|
||||
`themes/typora-github/github/OPEN-SANS-LICENSE.txt`;
|
||||
- 当前上游 Open Sans 已迁移到 SIL Open Font License 1.1。未来替换字体
|
||||
二进制时必须重新核对许可证、版权声明和嵌入权限,不能沿用本阶段清单。
|
||||
|
||||
字体清单中的 `license` 是审计字段,不替代随包许可证文件。
|
||||
|
||||
## 4. 代表主题结果
|
||||
|
||||
测试机未安装 FandolSong、FandolFang、FandolHei、FandolKai 或
|
||||
Open Sans,能够真实检验嵌入字体而不是本机字体回退。
|
||||
|
||||
| 主题 | 嵌入字形面 | 原始 DOCX 大小 | Word 页数 | WPS 页数 |
|
||||
| --- | ---: | ---: | ---: | ---: |
|
||||
| 政企正式·工作报告 | 2 | 9,932,187 字节 | 2 | 2 |
|
||||
| 政企红头·标准文件 | 5 | 23,920,588 字节 | 2 | 3 |
|
||||
| Typora Github | 5 | 4,680,418 字节 | 2 | 2 |
|
||||
|
||||
三份文件均满足:
|
||||
|
||||
- 内置校验器确认嵌入字体部件、关系、内容类型和字体键完整;
|
||||
- Word 使用 `OpenNoRepairDialog` 正常打开并导出 PDF;
|
||||
- WPS 正常打开并导出 PDF;
|
||||
- 未发现方框、缺字或明显系统字体回退;
|
||||
- Word 追加编辑标记后,WPS 可以读取并继续编辑;
|
||||
- WPS 另存后,Word 仍可禁止修复打开;
|
||||
- 最终文件同时保留 Word 和 WPS 两段编辑文本。
|
||||
|
||||
14 套主题完整矩阵也已通过。未声明 `docxFonts` 的其余 11 套主题继续产生
|
||||
零嵌入字体,不会被共享 CSS 隐式扩大 DOCX。
|
||||
|
||||
## 5. 已知客户端差异
|
||||
|
||||
### 5.1 WPS 红头主题分页
|
||||
|
||||
同一份红头 DOCX 在 Word 中为 2 页,在 WPS 中为 3 页。WPS 将版记整体
|
||||
移动到近空白第 3 页。字体显示正确,内容没有丢失,但尚未达到跨客户端
|
||||
视觉一致门禁。
|
||||
|
||||
该问题进入阶段 12D-R3,由 PDF 与 DOCX 自动视觉差异引擎量化后处理。
|
||||
当前不通过主题 ID 特判或缩小正文来掩盖差异,避免在没有 PDF 基线的情况
|
||||
下破坏 Word 与 Chromium 的一致性。
|
||||
|
||||
### 5.2 WPS 互存策略
|
||||
|
||||
WPS 默认另存会移除嵌入字体,三份文件会缩小到约 20 KiB。设置文档属性
|
||||
“嵌入 TrueType 字体”并禁用子集后可以保留字体,但 WPS 会重新生成字体
|
||||
部件,互存文件增大到约 14~45 MiB。
|
||||
|
||||
WPS 还会重排 `styles.xml` 的 `uiPriority`,并可能删除未使用的
|
||||
`numbering.xml`。Microsoft Open XML SDK 会报告 WPS 写回文件自身产生的
|
||||
顺序问题,但 Word 仍能禁止修复打开。项目不会因此放宽对自身生成文件的
|
||||
严格 OOXML 门禁。
|
||||
|
||||
## 6. 验证结果
|
||||
|
||||
- `npm run verify:docx-themes`:14/14 主题通过;
|
||||
- `npm test`:全项目通过;
|
||||
- `npm run typecheck`:全项目通过;
|
||||
- `npm run build`:全项目通过;
|
||||
- `git diff --check`:通过;
|
||||
- Word 原始文档:3/3 禁止修复打开并导出 PDF;
|
||||
- WPS 原始文档:3/3 打开并导出 PDF;
|
||||
- Word → WPS → Word 双向编辑:3/3 通过;
|
||||
- Word/WPS 原始渲染:共 13 页逐页检查完成。
|
||||
|
||||
本地验收产物位于被 Git 忽略的:
|
||||
|
||||
```text
|
||||
output/docx-theme-matrix/
|
||||
```
|
||||
|
||||
## 7. 下一步
|
||||
|
||||
阶段 12D-R3 建立 PDF 与 DOCX 自动视觉差异引擎,优先量化:
|
||||
|
||||
- 页面数和分页边界;
|
||||
- 文本块位置和尺寸;
|
||||
- 字体、字号、颜色和字重;
|
||||
- 封面、版头、版记、表格及代码块边界;
|
||||
- Word、WPS 与 Chromium PDF 的差异。
|
||||
|
||||
红头主题的 WPS 版记溢出作为首个固定回归样例。
|
||||
Reference in New Issue
Block a user