@file-viewer/doc 3.1.2 → 3.2.1
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.en.md +3 -35
- package/README.md +3 -265
- package/dist/core/binary.d.ts +0 -5
- package/dist/core/binary.js +0 -5
- package/dist/core/cfb.d.ts +0 -5
- package/dist/core/cfb.js +0 -5
- package/dist/core/inflate.d.ts +0 -8
- package/dist/core/inflate.js +0 -10
- package/dist/msdoc/constants.js +1 -1
- package/dist/msdoc/fkp.js +0 -1
- package/dist/msdoc/html-word.js +0 -2
- package/dist/msdoc/objects.d.ts +0 -11
- package/dist/msdoc/objects.js +0 -31
- package/dist/msdoc/officeart.d.ts +0 -6
- package/dist/msdoc/officeart.js +0 -6
- package/dist/msdoc/parser.d.ts +1 -6
- package/dist/msdoc/parser.js +75 -83
- package/dist/msdoc/properties.js +0 -6
- package/dist/msdoc/styles.js +0 -1
- package/dist/msdoc/vector.js +0 -1
- package/dist/render/fonts.d.ts +2 -1
- package/dist/render/fonts.js +60 -0
- package/dist/render/html.d.ts +0 -10
- package/dist/render/html.js +65 -22
- package/dist/types.d.ts +1 -4
- package/dist/viewer.d.ts +0 -9
- package/dist/viewer.js +0 -11
- package/dist/worker-client.d.ts +0 -4
- package/dist/worker-client.js +0 -4
- package/package.json +59 -20
package/README.en.md
CHANGED
|
@@ -1,39 +1,7 @@
|
|
|
1
1
|
# @file-viewer/doc
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Runtime package version 3.2.1.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Usage and API documentation: https://github.com/flyfish-dev/docjs
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
npm install @file-viewer/doc
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Historical imports from `msdoc-viewer` remain supported through the compatibility alias package.
|
|
12
|
-
|
|
13
|
-
## Usage
|
|
14
|
-
|
|
15
|
-
```ts
|
|
16
|
-
import { parseMsDoc, renderMsDoc } from '@file-viewer/doc'
|
|
17
|
-
|
|
18
|
-
const parsed = parseMsDoc(arrayBuffer)
|
|
19
|
-
const rendered = renderMsDoc(parsed)
|
|
20
|
-
|
|
21
|
-
container.innerHTML = `<style>${rendered.css}</style><div class="msdoc-root">${rendered.html}</div>`
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
External links and linked images are blocked by default. Trusted integrations
|
|
25
|
-
can opt in separately with `externalLinkPolicy: 'allow'` and
|
|
26
|
-
`externalResourcePolicy: 'allow'`; embedded images and bookmarks keep working.
|
|
27
|
-
|
|
28
|
-
The package also exports `parseMsDocToHtml`, `mountMsDoc`, `createMsDocViewer`, and `MsDocWorkerClient`.
|
|
29
|
-
|
|
30
|
-
## Scope
|
|
31
|
-
|
|
32
|
-
- OLE/CFB container parsing
|
|
33
|
-
- WordDocument, 0Table/1Table, CLX, FKP, CHPX, PAPX, STSH, and font table parsing
|
|
34
|
-
- Paragraph, character, table, image, OLE attachment, and basic field-code output
|
|
35
|
-
- Embedded picture extraction via MS-DOC PICF / OfficeArt BLIP records (PNG, JPEG, DIB, TIFF), with EMF/WMF → SVG conversion for browser preview
|
|
36
|
-
- Browser-native HTML/CSS rendering without server-side conversion
|
|
37
|
-
- Optional module worker entry at `@file-viewer/doc/worker`
|
|
38
|
-
|
|
39
|
-
This package intentionally tracks the old public `msdoc-viewer@0.2.0` code line and continues to harden that public MS-DOC path. Newer private document-engine work is not used here.
|
|
7
|
+
The package's own license is MIT. Included third-party notices apply to their respective components.
|
package/README.md
CHANGED
|
@@ -1,269 +1,7 @@
|
|
|
1
1
|
# @file-viewer/doc
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Runtime package version 3.2.1.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
> 本包基于 npm 已公开的 `msdoc-viewer@0.2.0` 旧版源码维护,不包含新版私有文档引擎实现。
|
|
5
|
+
Usage and API documentation: https://github.com/flyfish-dev/docjs
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
- **整仓 TypeScript 重构**:源码、公开 API、Worker 协议、解析 AST 全部使用 TypeScript,并开启 `strict` 模式
|
|
11
|
-
- **完整声明文件产物**:构建后输出 `dist/*.d.ts`,便于社区阅读、联想、二次开发
|
|
12
|
-
- **纯前端方案**:不依赖 LibreOffice、COM、ActiveX 或服务器端转换
|
|
13
|
-
- **按 MS-DOC 主链路实现**:CFB/OLE → FIB → CLX/Piece Table → FKP → PAPX/CHPX/TAPX → STSH → HTML
|
|
14
|
-
- **可扩展架构**:把二进制读取、属性解码、样式继承、表格布局、渲染层完全拆分
|
|
15
|
-
- **支持 Web Worker**:避免大文档解析阻塞 UI 线程
|
|
16
|
-
|
|
17
|
-
## 当前能力
|
|
18
|
-
|
|
19
|
-
已经覆盖的主要能力:
|
|
20
|
-
|
|
21
|
-
- CFB/OLE 容器读取
|
|
22
|
-
- FIB(`FibBase` / `FibRgLw` / `FibRgFcLcb`)解析
|
|
23
|
-
- CLX / Piece Table 文本恢复
|
|
24
|
-
- FKP(PAPX / CHPX)属性读取
|
|
25
|
-
- STSH 样式表解析与样式继承
|
|
26
|
-
- 字体表读取
|
|
27
|
-
- 常见字符样式:粗体、斜体、下划线、删除线、字号、颜色、高亮、大小写、上下标、字体切换
|
|
28
|
-
- 常见段落样式:对齐、缩进、段前后距、行距、分页控制、边框
|
|
29
|
-
- 表格:`sprmTDefTable`、单元格宽度、横向/纵向合并、边框、垂直对齐、nowrap、fitText
|
|
30
|
-
- 图片:按 MS-DOC PICF / OfficeArt BLIP 规范提取内嵌 PNG、JPEG、DIB、TIFF,并支持 EMF/WMF 转 SVG 预览
|
|
31
|
-
- OLE / ObjectPool 附件提取
|
|
32
|
-
- 域代码的基础处理(例如超链接)
|
|
33
|
-
- 浏览器 Viewer 与 Worker Client
|
|
34
|
-
|
|
35
|
-
## 工程结构
|
|
36
|
-
|
|
37
|
-
```text
|
|
38
|
-
src/
|
|
39
|
-
core/
|
|
40
|
-
binary.ts # 二进制读取器
|
|
41
|
-
cfb.ts # CFB/OLE 容器解析
|
|
42
|
-
inflate.ts # OfficeArt 压缩 metafile 的 zlib/deflate 解压
|
|
43
|
-
utils.ts # 字节、编码、范围等底层工具
|
|
44
|
-
msdoc/
|
|
45
|
-
fib.ts # FIB 读取
|
|
46
|
-
clx.ts # CLX / Piece Table
|
|
47
|
-
fkp.ts # FKP 页读取
|
|
48
|
-
sprm.ts # SPRM 操作数解码
|
|
49
|
-
properties.ts # PAPX / CHPX / TAPX 到状态对象的归并
|
|
50
|
-
styles.ts # STSH / 样式继承
|
|
51
|
-
fonts.ts # 字体表
|
|
52
|
-
officeart.ts # OfficeArt 内联 shape / BLIP 辅助解析
|
|
53
|
-
objects.ts # PICF / OfficeArt 图片与 OLE / ObjectPool
|
|
54
|
-
vector.ts # EMF/WMF 转 SVG
|
|
55
|
-
parser.ts # 主解析流程,输出 AST
|
|
56
|
-
render/
|
|
57
|
-
html.ts # AST -> HTML/CSS
|
|
58
|
-
types.ts # 完整类型声明与公共接口
|
|
59
|
-
viewer.ts # DOM Viewer
|
|
60
|
-
worker-client.ts # Worker 客户端
|
|
61
|
-
worker.ts # Worker 入口
|
|
62
|
-
index.ts # 公共导出
|
|
63
|
-
|
|
64
|
-
demo/
|
|
65
|
-
index.html
|
|
66
|
-
|
|
67
|
-
test/
|
|
68
|
-
smoke.mjs
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
## 安装
|
|
72
|
-
|
|
73
|
-
```bash
|
|
74
|
-
npm install @file-viewer/doc
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
历史 `msdoc-viewer` 包名会通过兼容别名继续导出同一套 API。
|
|
78
|
-
|
|
79
|
-
## 仓库开发与构建
|
|
80
|
-
|
|
81
|
-
```bash
|
|
82
|
-
pnpm install
|
|
83
|
-
pnpm --filter @file-viewer/doc type-check
|
|
84
|
-
pnpm --filter @file-viewer/doc build
|
|
85
|
-
pnpm --filter @file-viewer/doc verify:github-34
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
构建产物输出到 `dist/`:
|
|
89
|
-
|
|
90
|
-
- `dist/index.js`
|
|
91
|
-
- `dist/index.d.ts`
|
|
92
|
-
- `dist/worker.js`
|
|
93
|
-
- 其余模块的 JS / d.ts
|
|
94
|
-
|
|
95
|
-
## 快速开始
|
|
96
|
-
|
|
97
|
-
### 1)直接解析并渲染
|
|
98
|
-
|
|
99
|
-
```ts
|
|
100
|
-
import { parseMsDoc, renderMsDoc } from '@file-viewer/doc';
|
|
101
|
-
|
|
102
|
-
const buffer = await file.arrayBuffer();
|
|
103
|
-
const parsed = parseMsDoc(buffer);
|
|
104
|
-
const rendered = renderMsDoc(parsed);
|
|
105
|
-
|
|
106
|
-
document.getElementById('app')!.innerHTML = `
|
|
107
|
-
<style>${rendered.css}</style>
|
|
108
|
-
<div class="msdoc-root">${rendered.html}</div>
|
|
109
|
-
`;
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
外部链接和关联图片默认阻断。确需访问可信外部资源时,应分别显式设置
|
|
113
|
-
`externalLinkPolicy: 'allow'` 和 `externalResourcePolicy: 'allow'`;内嵌图片及书签不受影响。
|
|
114
|
-
|
|
115
|
-
### 2)使用内置 Viewer
|
|
116
|
-
|
|
117
|
-
```ts
|
|
118
|
-
import { createMsDocViewer } from '@file-viewer/doc';
|
|
119
|
-
|
|
120
|
-
const container = document.getElementById('app')!;
|
|
121
|
-
const viewer = createMsDocViewer(container);
|
|
122
|
-
|
|
123
|
-
await viewer.load(file);
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### 3)使用 Worker 提升体验
|
|
127
|
-
|
|
128
|
-
```ts
|
|
129
|
-
import { createMsDocViewer, MsDocWorkerClient } from '@file-viewer/doc';
|
|
130
|
-
|
|
131
|
-
const workerClient = MsDocWorkerClient.create(
|
|
132
|
-
new URL('./node_modules/@file-viewer/doc/dist/worker.js', import.meta.url)
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
const viewer = createMsDocViewer(document.getElementById('app')!, {
|
|
136
|
-
workerClient,
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
await viewer.load(file);
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
## API 概览
|
|
143
|
-
|
|
144
|
-
### `parseMsDoc(input, options?)`
|
|
145
|
-
|
|
146
|
-
把 `.doc` 解析成结构化结果:
|
|
147
|
-
|
|
148
|
-
```ts
|
|
149
|
-
import type { MsDocParseResult } from '@file-viewer/doc';
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
核心字段:
|
|
153
|
-
|
|
154
|
-
- `warnings`: 解析期告警
|
|
155
|
-
- `meta`: 文档元数据与统计
|
|
156
|
-
- `fonts`: 字体表摘要
|
|
157
|
-
- `styles`: 样式表摘要
|
|
158
|
-
- `blocks`: 结构化块级 AST(段落、表格、附件)
|
|
159
|
-
- `assets`: 图片 / 附件资产
|
|
160
|
-
|
|
161
|
-
### `renderMsDoc(parsed, options?)`
|
|
162
|
-
|
|
163
|
-
把 AST 渲染为:
|
|
164
|
-
|
|
165
|
-
- `html`
|
|
166
|
-
- `css`
|
|
167
|
-
- `warnings`
|
|
168
|
-
- `meta`
|
|
169
|
-
- `assets`
|
|
170
|
-
- `parsed`
|
|
171
|
-
|
|
172
|
-
### `parseMsDocToHtml(input, options?)`
|
|
173
|
-
|
|
174
|
-
一步完成解析与渲染。
|
|
175
|
-
|
|
176
|
-
### `createMsDocViewer(container, config?)`
|
|
177
|
-
|
|
178
|
-
返回一个面向浏览器的 viewer:
|
|
179
|
-
|
|
180
|
-
- `load(input, options?)`
|
|
181
|
-
- `mount(rendered)`
|
|
182
|
-
- `clear()`
|
|
183
|
-
- `destroy()`
|
|
184
|
-
- `value`
|
|
185
|
-
|
|
186
|
-
### `MsDocWorkerClient`
|
|
187
|
-
|
|
188
|
-
为 Worker 提供强类型 RPC 包装:
|
|
189
|
-
|
|
190
|
-
- `parse(input, options?)`
|
|
191
|
-
- `render(parsed, options?)`
|
|
192
|
-
- `parseToHtml(input, options?)`
|
|
193
|
-
- `destroy()`
|
|
194
|
-
|
|
195
|
-
## 关键类型
|
|
196
|
-
|
|
197
|
-
所有公共类型都从根入口导出,例如:
|
|
198
|
-
|
|
199
|
-
```ts
|
|
200
|
-
import type {
|
|
201
|
-
MsDocParseResult,
|
|
202
|
-
MsDocRenderResult,
|
|
203
|
-
MsDocBlock,
|
|
204
|
-
ParagraphBlock,
|
|
205
|
-
TableBlock,
|
|
206
|
-
MsDocAsset,
|
|
207
|
-
MsDocParseOptions,
|
|
208
|
-
MsDocRenderOptions,
|
|
209
|
-
MsDocViewer,
|
|
210
|
-
} from '@file-viewer/doc';
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
这意味着:
|
|
214
|
-
|
|
215
|
-
- 社区开发者可以直接消费 `AST` 与 `asset` 数据自行渲染
|
|
216
|
-
- 也可以在现有渲染器之外实现 Markdown、Canvas、PDF 等输出层
|
|
217
|
-
- Worker 主线程通信协议也有完整类型约束,避免“隐式 any 消息格式”问题
|
|
218
|
-
|
|
219
|
-
## 严格类型与可维护性说明
|
|
220
|
-
|
|
221
|
-
这次重构的重点不只是把文件后缀改成 `.ts`,而是把整条主链路都显式类型化:
|
|
222
|
-
|
|
223
|
-
- 二进制结构:CFB/FIB/CLX/FKP
|
|
224
|
-
- 样式模型:字符、段落、表格状态对象
|
|
225
|
-
- 中间 AST:段落、行内节点、表格、附件
|
|
226
|
-
- 渲染结果:HTML/CSS/资产
|
|
227
|
-
- Viewer / Worker 协议
|
|
228
|
-
|
|
229
|
-
同时给关键模块补充了注释,尤其是:
|
|
230
|
-
|
|
231
|
-
- `BinaryReader` 的边界读取行为
|
|
232
|
-
- Worker 请求/响应通道
|
|
233
|
-
- parser 主流程
|
|
234
|
-
- 样式与属性归并逻辑
|
|
235
|
-
|
|
236
|
-
## 本地验证
|
|
237
|
-
|
|
238
|
-
```bash
|
|
239
|
-
npm run smoke
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
这个命令会:
|
|
243
|
-
|
|
244
|
-
1. 先运行 TypeScript 构建
|
|
245
|
-
2. 再读取 `test/test.doc`
|
|
246
|
-
3. 输出 `test/rendered-sample.html`
|
|
247
|
-
|
|
248
|
-
如果你要手工打开 demo:
|
|
249
|
-
|
|
250
|
-
```bash
|
|
251
|
-
npm run build
|
|
252
|
-
# 然后用任意静态服务器打开 demo/index.html
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
## 已知边界
|
|
256
|
-
|
|
257
|
-
MS-DOC 是历史包袱很重的二进制格式,虽然主链路已经完整重构,但以下场景仍建议用更多真实样本持续压测:
|
|
258
|
-
|
|
259
|
-
- 极端复杂的嵌套表格
|
|
260
|
-
- 非常规域代码组合
|
|
261
|
-
- 少见的 OLE 嵌入形式
|
|
262
|
-
- 某些兼容模式下的边角 sprm 变体
|
|
263
|
-
- 仅保存外部路径、未嵌入字节的链接图片(会给出占位提示,无法在离线环境加载本地盘路径)
|
|
264
|
-
|
|
265
|
-
这些场景并不影响当前的整体架构,后续继续扩展时可直接在现有 TS 类型体系上增量补强。
|
|
266
|
-
|
|
267
|
-
## 许可证
|
|
268
|
-
|
|
269
|
-
MIT
|
|
7
|
+
The package's own license is MIT. Included third-party notices apply to their respective components.
|
package/dist/core/binary.d.ts
CHANGED
|
@@ -2,11 +2,6 @@ import type { BinaryInput } from '../types.js';
|
|
|
2
2
|
type ReaderMethod = ((offset: number) => number) & {
|
|
3
3
|
byteWidth?: number;
|
|
4
4
|
};
|
|
5
|
-
/**
|
|
6
|
-
* Lightweight little-endian binary reader used across the MS-DOC parser.
|
|
7
|
-
* The implementation intentionally returns neutral defaults for out-of-range
|
|
8
|
-
* reads so upper layers can stay resilient to slightly malformed documents.
|
|
9
|
-
*/
|
|
10
5
|
export declare class BinaryReader {
|
|
11
6
|
readonly bytes: Uint8Array;
|
|
12
7
|
readonly view: DataView;
|
package/dist/core/binary.js
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { textDecoder } from './utils.js';
|
|
2
2
|
const utf16leDecoder = textDecoder('utf-16le');
|
|
3
3
|
const latin1Decoder = typeof TextDecoder !== 'undefined' ? textDecoder('windows-1252') : textDecoder('latin1');
|
|
4
|
-
/**
|
|
5
|
-
* Lightweight little-endian binary reader used across the MS-DOC parser.
|
|
6
|
-
* The implementation intentionally returns neutral defaults for out-of-range
|
|
7
|
-
* reads so upper layers can stay resilient to slightly malformed documents.
|
|
8
|
-
*/
|
|
9
4
|
export class BinaryReader {
|
|
10
5
|
bytes;
|
|
11
6
|
view;
|
package/dist/core/cfb.d.ts
CHANGED
|
@@ -1,7 +1,2 @@
|
|
|
1
1
|
import type { BinaryInput, ParsedCFB } from '../types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Parses the CFB/OLE container that wraps a legacy `.doc` file.
|
|
4
|
-
* The returned object exposes stream helpers so higher layers can focus on
|
|
5
|
-
* Word-specific structures instead of low-level sector navigation.
|
|
6
|
-
*/
|
|
7
2
|
export declare function parseCFB(input: BinaryInput, _options?: Record<string, unknown>): ParsedCFB;
|
package/dist/core/cfb.js
CHANGED
|
@@ -105,11 +105,6 @@ function buildPathMap(entries) {
|
|
|
105
105
|
function normalizeName(name) {
|
|
106
106
|
return name.replace(/\u0000+$/, '');
|
|
107
107
|
}
|
|
108
|
-
/**
|
|
109
|
-
* Parses the CFB/OLE container that wraps a legacy `.doc` file.
|
|
110
|
-
* The returned object exposes stream helpers so higher layers can focus on
|
|
111
|
-
* Word-specific structures instead of low-level sector navigation.
|
|
112
|
-
*/
|
|
113
108
|
export function parseCFB(input, _options = {}) {
|
|
114
109
|
const bytes = toUint8Array(input);
|
|
115
110
|
const reader = new BinaryReader(bytes);
|
package/dist/core/inflate.d.ts
CHANGED
|
@@ -1,9 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Small RFC1950/zlib + RFC1951/DEFLATE inflater.
|
|
3
|
-
*
|
|
4
|
-
* OfficeArt stores EMF/WMF BLIPs behind OfficeArtMetafileHeader.compression=0x00
|
|
5
|
-
* using the zlib wrapper defined by RFC1950. Keeping this inflater dependency-free
|
|
6
|
-
* preserves the browser-only build while still allowing the synchronous MS-DOC
|
|
7
|
-
* parser to expose browser-displayable SVG assets.
|
|
8
|
-
*/
|
|
9
1
|
export declare function inflateZlib(bytes: Uint8Array, expectedLength?: number): Uint8Array | null;
|
package/dist/core/inflate.js
CHANGED
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Small RFC1950/zlib + RFC1951/DEFLATE inflater.
|
|
3
|
-
*
|
|
4
|
-
* OfficeArt stores EMF/WMF BLIPs behind OfficeArtMetafileHeader.compression=0x00
|
|
5
|
-
* using the zlib wrapper defined by RFC1950. Keeping this inflater dependency-free
|
|
6
|
-
* preserves the browser-only build while still allowing the synchronous MS-DOC
|
|
7
|
-
* parser to expose browser-displayable SVG assets.
|
|
8
|
-
*/
|
|
9
1
|
class InflateError extends Error {
|
|
10
2
|
name = 'InflateError';
|
|
11
3
|
}
|
|
@@ -81,8 +73,6 @@ class ByteSink {
|
|
|
81
73
|
toUint8Array(expectedLength) {
|
|
82
74
|
const out = this.buffer.slice(0, this.length);
|
|
83
75
|
if (expectedLength != null && expectedLength >= 0 && out.length !== expectedLength) {
|
|
84
|
-
// The OfficeArt header is advisory in some legacy files. Do not fail the
|
|
85
|
-
// parse because a producer rounded the saved size differently.
|
|
86
76
|
return out;
|
|
87
77
|
}
|
|
88
78
|
return out;
|
package/dist/msdoc/constants.js
CHANGED
package/dist/msdoc/fkp.js
CHANGED
|
@@ -170,7 +170,6 @@ export function readPapxProperties(wordBytes, offset, dataBytes) {
|
|
|
170
170
|
bodyStart = offset + 2;
|
|
171
171
|
}
|
|
172
172
|
else {
|
|
173
|
-
// PapxInFkp.cb counts 2-byte words; the payload occupies (2 * cb) - 1 bytes.
|
|
174
173
|
bodySize = cb * 2 - 1;
|
|
175
174
|
bodyStart = offset + 1;
|
|
176
175
|
}
|
package/dist/msdoc/html-word.js
CHANGED
|
@@ -216,8 +216,6 @@ function extractParagraphs(html) {
|
|
|
216
216
|
}
|
|
217
217
|
cursor = tagEnd + 1;
|
|
218
218
|
}
|
|
219
|
-
// Malformed HTML saved by old Word versions is common. Preserve its visible
|
|
220
|
-
// tail as a paragraph without restarting a regex search at every opening tag.
|
|
221
219
|
if (active) {
|
|
222
220
|
appendParagraph(active.attributes, html.slice(active.contentStart));
|
|
223
221
|
}
|
package/dist/msdoc/objects.d.ts
CHANGED
|
@@ -12,18 +12,7 @@ export declare function detectImageSegment(bytes: Uint8Array): {
|
|
|
12
12
|
start: number;
|
|
13
13
|
end: number;
|
|
14
14
|
} | null;
|
|
15
|
-
/**
|
|
16
|
-
* OfficeArt records all start with the common 8-byte OfficeArtRecordHeader.
|
|
17
|
-
* Parsing this explicitly lets us walk inline shape containers instead of guessing
|
|
18
|
-
* image payloads by magic bytes, which was the root cause of the broken image output.
|
|
19
|
-
*/
|
|
20
15
|
export declare function parseOfficeArtRecordHeader(bytes: Uint8Array, offset: number): OfficeArtRecordHeader | null;
|
|
21
|
-
/**
|
|
22
|
-
* Resolves a picture character (U+0001 + sprmCPicLocation) to an HTML-friendly asset.
|
|
23
|
-
* The happy path is: PICF -> optional linked picture name -> OfficeArtInlineSpContainer
|
|
24
|
-
* -> OfficeArtBlip*. When no structured BLIP can be found we still fall back to a
|
|
25
|
-
* signature scan so slightly malformed files remain usable.
|
|
26
|
-
*/
|
|
27
16
|
export declare function extractPictureAsset(dataStreamBytes: Uint8Array, pictureOffset: number | null | undefined, options?: MsDocParseOptions): ImageAsset | null;
|
|
28
17
|
export declare function extractFbseImageAsset(drawingBytes: Uint8Array, offset: number, header?: OfficeArtRecordHeader | null): ImageAsset | null;
|
|
29
18
|
export declare function parseOle10Native(streamBytes: Uint8Array): {
|
package/dist/msdoc/objects.js
CHANGED
|
@@ -182,9 +182,6 @@ function parsePicfHeader(bytes) {
|
|
|
182
182
|
if (bytes.length < PICF_HEADER_SIZE)
|
|
183
183
|
return null;
|
|
184
184
|
const reader = new BinaryReader(bytes);
|
|
185
|
-
// PICMID stores the intended display goal size in twips plus mx/my scaling
|
|
186
|
-
// factors (tenths of a percent). Word uses these values for inline picture
|
|
187
|
-
// layout, including images that live in header/footer stories.
|
|
188
185
|
const dxaGoalTwips = reader.i16(28);
|
|
189
186
|
const dyaGoalTwips = reader.i16(30);
|
|
190
187
|
const scaleXPermille = reader.u16(32);
|
|
@@ -209,11 +206,6 @@ function parsePicfHeader(bytes) {
|
|
|
209
206
|
displayHeightTwips,
|
|
210
207
|
};
|
|
211
208
|
}
|
|
212
|
-
/**
|
|
213
|
-
* PICFAndOfficeArtData can optionally carry a length-prefixed ANSI path right after
|
|
214
|
-
* the 68-byte PICF header. The spec ties this to MM_SHAPEFILE, but real-world files
|
|
215
|
-
* are not always perfectly spec-compliant, so we also accept clearly path-like data.
|
|
216
|
-
*/
|
|
217
209
|
function readOptionalPictureName(pictureChunk, picf) {
|
|
218
210
|
const offset = Math.min(picf.cbHeader || PICF_HEADER_SIZE, pictureChunk.length);
|
|
219
211
|
if (offset >= pictureChunk.length)
|
|
@@ -231,11 +223,6 @@ function readOptionalPictureName(pictureChunk, picf) {
|
|
|
231
223
|
}
|
|
232
224
|
return { value, nextOffset: offset + 1 + length };
|
|
233
225
|
}
|
|
234
|
-
/**
|
|
235
|
-
* OfficeArt records all start with the common 8-byte OfficeArtRecordHeader.
|
|
236
|
-
* Parsing this explicitly lets us walk inline shape containers instead of guessing
|
|
237
|
-
* image payloads by magic bytes, which was the root cause of the broken image output.
|
|
238
|
-
*/
|
|
239
226
|
export function parseOfficeArtRecordHeader(bytes, offset) {
|
|
240
227
|
if (offset < 0 || offset + 8 > bytes.length)
|
|
241
228
|
return null;
|
|
@@ -300,11 +287,6 @@ function dibToBmp(dibBytes) {
|
|
|
300
287
|
out.set(dibBytes, 14);
|
|
301
288
|
return out;
|
|
302
289
|
}
|
|
303
|
-
/**
|
|
304
|
-
* OfficeArtBStoreContainerFileBlock can wrap an OfficeArtFBSE record instead of a
|
|
305
|
-
* BLIP atom directly. In that case the actual image payload sits in the FBSE
|
|
306
|
-
* embeddedBlip field after the fixed 36-byte FBSE header and optional nameData.
|
|
307
|
-
*/
|
|
308
290
|
function extractEmbeddedBlipFromFbse(bytes, offset, header) {
|
|
309
291
|
const payloadOffset = offset + 8;
|
|
310
292
|
const payloadEnd = payloadOffset + header.recLen;
|
|
@@ -448,12 +430,6 @@ function collectOfficeArtCandidates(bytes, offset, end, out) {
|
|
|
448
430
|
cursor = next;
|
|
449
431
|
}
|
|
450
432
|
}
|
|
451
|
-
/**
|
|
452
|
-
* The `picture` field of PICFAndOfficeArtData is an OfficeArtInlineSpContainer.
|
|
453
|
-
* We recursively walk nested OfficeArt containers until we reach actual BLIP atoms
|
|
454
|
-
* (PNG/JPEG/DIB/TIFF/EMF/WMF/PICT) and then extract the payload according to the
|
|
455
|
-
* BLIP record-specific layout from MS-ODRAW.
|
|
456
|
-
*/
|
|
457
433
|
function findOfficeArtCandidates(pictureChunk, startOffset) {
|
|
458
434
|
if (startOffset < 0 || startOffset + 8 > pictureChunk.length)
|
|
459
435
|
return [];
|
|
@@ -543,12 +519,6 @@ function createImageAsset(candidate, pictureOffset, picf, linkedPath, inlineShap
|
|
|
543
519
|
meta,
|
|
544
520
|
};
|
|
545
521
|
}
|
|
546
|
-
/**
|
|
547
|
-
* Resolves a picture character (U+0001 + sprmCPicLocation) to an HTML-friendly asset.
|
|
548
|
-
* The happy path is: PICF -> optional linked picture name -> OfficeArtInlineSpContainer
|
|
549
|
-
* -> OfficeArtBlip*. When no structured BLIP can be found we still fall back to a
|
|
550
|
-
* signature scan so slightly malformed files remain usable.
|
|
551
|
-
*/
|
|
552
522
|
export function extractPictureAsset(dataStreamBytes, pictureOffset, options = {}) {
|
|
553
523
|
if (!dataStreamBytes || pictureOffset == null || pictureOffset < 0 || pictureOffset + PICF_HEADER_SIZE > dataStreamBytes.length)
|
|
554
524
|
return null;
|
|
@@ -712,7 +682,6 @@ export function parseOle10Native(streamBytes) {
|
|
|
712
682
|
}
|
|
713
683
|
}
|
|
714
684
|
catch {
|
|
715
|
-
// try next variant
|
|
716
685
|
}
|
|
717
686
|
}
|
|
718
687
|
return null;
|
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
export declare function detectMimeFromBlipType(blipType: number): string | null;
|
|
2
|
-
/**
|
|
3
|
-
* Inline picture characters often carry a miniature OfficeArt container that
|
|
4
|
-
* points at the BLIP used by the picture. We only need a compact subset of the
|
|
5
|
-
* shape metadata so the higher-level parser can annotate extracted assets with
|
|
6
|
-
* source shape ids and BLIP indices.
|
|
7
|
-
*/
|
|
8
2
|
export declare function parseInlineOfficeArtShape(bytes: Uint8Array, startOffset?: number): {
|
|
9
3
|
shapeId: number;
|
|
10
4
|
blipIndex?: number;
|
package/dist/msdoc/officeart.js
CHANGED
|
@@ -125,12 +125,6 @@ function scanRecords(bytes, start, end, visitor) {
|
|
|
125
125
|
}
|
|
126
126
|
return false;
|
|
127
127
|
}
|
|
128
|
-
/**
|
|
129
|
-
* Inline picture characters often carry a miniature OfficeArt container that
|
|
130
|
-
* points at the BLIP used by the picture. We only need a compact subset of the
|
|
131
|
-
* shape metadata so the higher-level parser can annotate extracted assets with
|
|
132
|
-
* source shape ids and BLIP indices.
|
|
133
|
-
*/
|
|
134
128
|
export function parseInlineOfficeArtShape(bytes, startOffset = 0) {
|
|
135
129
|
let result = null;
|
|
136
130
|
scanRecords(bytes, startOffset, bytes.length, (offset, header) => {
|
package/dist/msdoc/parser.d.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import type { MsDocParseOptions, MsDocParseResult, ParagraphModel, TableRowBlock } from '../types.js';
|
|
2
2
|
export declare function finalizeTableGrid(rows: TableRowBlock[]): void;
|
|
3
3
|
export declare function normalizeRowEndOnlyTables(paragraphs: ParagraphModel[]): void;
|
|
4
|
-
|
|
5
|
-
* Main MS-DOC entry point.
|
|
6
|
-
* It parses the OLE container, restores text through the piece table, resolves
|
|
7
|
-
* paragraph/character/table properties, and finally produces a normalized AST
|
|
8
|
-
* that the HTML renderer can consume.
|
|
9
|
-
*/
|
|
4
|
+
export declare function buildBlocks(paragraphs: ParagraphModel[]): MsDocParseResult['blocks'];
|
|
10
5
|
export declare function parseMsDoc(input: ArrayBuffer | Uint8Array | ArrayBufferView, options?: MsDocParseOptions): MsDocParseResult;
|
package/dist/msdoc/parser.js
CHANGED
|
@@ -337,7 +337,6 @@ function buildParagraphModel(range, paragraphText, styles, fonts, chpxRuns, reso
|
|
|
337
337
|
const resolveFont = (fontId) => fonts.byIndex(fontId);
|
|
338
338
|
const segments = buildCharSegments(range, paragraphText, chpxRuns, styles, baseCharProps, resolveFont, chpxCursor, range.storyKind === 'textbox' || range.storyKind === 'header-textbox');
|
|
339
339
|
const inlines = buildInlineNodes(segments, resolveAsset);
|
|
340
|
-
// The terminator has its own CHPX run and is not part of paragraphText.
|
|
341
340
|
const markState = range.terminator === DOC_CONTROL.paragraph
|
|
342
341
|
? buildCharSegments({ ...range, cpStart: range.cpEnd - 1 }, range.terminator, chpxRuns, styles, baseCharProps, resolveFont, { index: chpxCursor.index })[0]?.state
|
|
343
342
|
: undefined;
|
|
@@ -440,10 +439,6 @@ export function finalizeTableGrid(rows) {
|
|
|
440
439
|
}
|
|
441
440
|
}
|
|
442
441
|
}
|
|
443
|
-
// HTML tables need one shared column grid even when Word stores a different
|
|
444
|
-
// TDefTable boundary list per row (usually because cells are merged). Map
|
|
445
|
-
// every visible cell back onto the union of physical boundaries so colspan
|
|
446
|
-
// preserves the original geometry instead of squeezing later row cells.
|
|
447
442
|
const boundaries = Array.from(new Set(rows.flatMap((row) => row.cells.flatMap((cell) => {
|
|
448
443
|
const left = cell.meta?.leftBoundary;
|
|
449
444
|
const right = cell.meta?.rightBoundary;
|
|
@@ -468,8 +463,6 @@ export function finalizeTableGrid(rows) {
|
|
|
468
463
|
function resolveTableGridBorders(rows) {
|
|
469
464
|
const hasOwnBorder = (borders, side) => (Object.prototype.hasOwnProperty.call(borders, side) && (() => {
|
|
470
465
|
const border = borders[side];
|
|
471
|
-
// An all-zero BRC is an empty cell-level value and still inherits the
|
|
472
|
-
// table edge/inside border. NilBrc is the explicit "no border" value.
|
|
473
466
|
return Boolean(border?.nil || border?.borderType);
|
|
474
467
|
})());
|
|
475
468
|
rows.forEach((row, rowIndex) => {
|
|
@@ -557,11 +550,6 @@ export function normalizeRowEndOnlyTables(paragraphs) {
|
|
|
557
550
|
}
|
|
558
551
|
if (remainingCells > 0)
|
|
559
552
|
continue;
|
|
560
|
-
// Some Word/WPS producers store all table properties only on the row-end
|
|
561
|
-
// PAPX. Paragraph marks inside a cell then look like ordinary paragraphs.
|
|
562
|
-
// When the immediately preceding row has the same outer grid and table
|
|
563
|
-
// border identity, everything between the two row terminators belongs to
|
|
564
|
-
// the current row, including leading paragraphs in its first cell.
|
|
565
553
|
let previousRowEndIndex = cursor;
|
|
566
554
|
while (previousRowEndIndex >= 0
|
|
567
555
|
&& !paragraphs[previousRowEndIndex].paraState.tableRowEnd
|
|
@@ -581,87 +569,91 @@ export function normalizeRowEndOnlyTables(paragraphs) {
|
|
|
581
569
|
rowParagraphs.forEach((paragraph) => inheritRowEndTableState(paragraph, rowEnd));
|
|
582
570
|
}
|
|
583
571
|
}
|
|
584
|
-
function
|
|
585
|
-
const rows = [];
|
|
586
|
-
let pendingRow = { cells: [] };
|
|
587
|
-
let pendingCellParagraphs = [];
|
|
588
|
-
for (const paragraph of tableParagraphs) {
|
|
589
|
-
pendingCellParagraphs.push(paragraph);
|
|
590
|
-
if (paragraph.terminator === DOC_CONTROL.cellMark) {
|
|
591
|
-
pendingRow.cells.push({
|
|
592
|
-
id: uniqueId('cell'),
|
|
593
|
-
paragraphs: pendingCellParagraphs.map(paragraphToBlock),
|
|
594
|
-
meta: null,
|
|
595
|
-
});
|
|
596
|
-
pendingCellParagraphs = [];
|
|
597
|
-
if (paragraph.paraState.tableRowEnd || paragraph.paraState.innerTableRowEnd) {
|
|
598
|
-
const cellDefs = applyTableStateToCells(paragraph.tableState);
|
|
599
|
-
while (cellDefs.length &&
|
|
600
|
-
pendingRow.cells.length > cellDefs.length &&
|
|
601
|
-
pendingRow.cells[pendingRow.cells.length - 1].paragraphs.every((block) => !block.text && !(block.inlines || []).length)) {
|
|
602
|
-
pendingRow.cells.pop();
|
|
603
|
-
}
|
|
604
|
-
pendingRow.cells.forEach((cell, index) => {
|
|
605
|
-
cell.meta = cellDefs[index] || { index };
|
|
606
|
-
});
|
|
607
|
-
const gridWidthTwips = cellDefs.length
|
|
608
|
-
? ((cellDefs[cellDefs.length - 1].rightBoundary || 0) - (cellDefs[0].leftBoundary || 0))
|
|
609
|
-
: 0;
|
|
610
|
-
rows.push({
|
|
611
|
-
id: uniqueId('row'),
|
|
612
|
-
cells: pendingRow.cells,
|
|
613
|
-
state: paragraph.tableState,
|
|
614
|
-
gridWidthTwips,
|
|
615
|
-
});
|
|
616
|
-
pendingRow = { cells: [] };
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
if (pendingCellParagraphs.length) {
|
|
621
|
-
pendingRow.cells.push({ id: uniqueId('cell'), paragraphs: pendingCellParagraphs.map(paragraphToBlock), meta: null });
|
|
622
|
-
}
|
|
623
|
-
if (pendingRow.cells.length) {
|
|
624
|
-
rows.push({ id: uniqueId('row'), cells: pendingRow.cells, state: tableParagraphs[0]?.tableState || tablePropsToState([]), gridWidthTwips: 0 });
|
|
625
|
-
}
|
|
626
|
-
finalizeTableGrid(rows);
|
|
627
|
-
resolveTableGridBorders(rows);
|
|
628
|
-
const gridWidthTwips = rows.find((row) => row.gridWidthTwips)?.gridWidthTwips || 0;
|
|
629
|
-
const depth = Math.max(...tableParagraphs.map((paragraph) => getTableDepth(paragraph.paraState)), 1);
|
|
630
|
-
return {
|
|
631
|
-
type: 'table',
|
|
632
|
-
id: uniqueId('table'),
|
|
633
|
-
depth,
|
|
634
|
-
rows,
|
|
635
|
-
state: rows[0]?.state || tablePropsToState([]),
|
|
636
|
-
gridWidthTwips,
|
|
637
|
-
};
|
|
638
|
-
}
|
|
639
|
-
function buildBlocks(paragraphs) {
|
|
572
|
+
export function buildBlocks(paragraphs) {
|
|
640
573
|
const blocks = [];
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
574
|
+
const stack = [];
|
|
575
|
+
const finishCell = (pending) => {
|
|
576
|
+
pending.cells.push({
|
|
577
|
+
id: uniqueId('cell'),
|
|
578
|
+
paragraphs: pending.content.filter((block) => block.type === 'paragraph'),
|
|
579
|
+
blocks: pending.content,
|
|
580
|
+
meta: null,
|
|
581
|
+
});
|
|
582
|
+
pending.content = [];
|
|
583
|
+
};
|
|
584
|
+
const finishRow = (pending, state = pending.lastState) => {
|
|
585
|
+
if (pending.content.length)
|
|
586
|
+
finishCell(pending);
|
|
587
|
+
const definitions = applyTableStateToCells(state);
|
|
588
|
+
while (pending.cells.length < definitions.length)
|
|
589
|
+
finishCell(pending);
|
|
590
|
+
if (!pending.cells.length)
|
|
591
|
+
finishCell(pending);
|
|
592
|
+
pending.cells.forEach((cell, index) => { cell.meta = definitions[index] || { index }; });
|
|
593
|
+
pending.table.rows.push({
|
|
594
|
+
id: uniqueId('row'),
|
|
595
|
+
cells: pending.cells,
|
|
596
|
+
state,
|
|
597
|
+
gridWidthTwips: definitions.length
|
|
598
|
+
? (definitions[definitions.length - 1].rightBoundary || 0) - (definitions[0].leftBoundary || 0)
|
|
599
|
+
: 0,
|
|
600
|
+
});
|
|
601
|
+
pending.cells = [];
|
|
602
|
+
};
|
|
603
|
+
const finishTable = () => {
|
|
604
|
+
const pending = stack.pop();
|
|
605
|
+
if (pending.content.length || pending.cells.length)
|
|
606
|
+
finishRow(pending);
|
|
607
|
+
const table = pending.table;
|
|
608
|
+
finalizeTableGrid(table.rows);
|
|
609
|
+
resolveTableGridBorders(table.rows);
|
|
610
|
+
table.state = table.rows[0]?.state || pending.lastState;
|
|
611
|
+
table.gridWidthTwips = table.rows.find(row => row.gridWidthTwips)?.gridWidthTwips || 0;
|
|
612
|
+
const parent = stack[stack.length - 1];
|
|
613
|
+
if (parent)
|
|
614
|
+
parent.content.push(table);
|
|
615
|
+
else
|
|
616
|
+
blocks.push(table);
|
|
617
|
+
};
|
|
618
|
+
for (const paragraph of paragraphs) {
|
|
644
619
|
const depth = getTableDepth(paragraph.paraState);
|
|
620
|
+
if (stack.length && stack[0].storyKind !== paragraph.storyKind) {
|
|
621
|
+
while (stack.length)
|
|
622
|
+
finishTable();
|
|
623
|
+
}
|
|
624
|
+
while (stack.length && stack[stack.length - 1].table.depth > depth)
|
|
625
|
+
finishTable();
|
|
645
626
|
if (depth <= 0) {
|
|
646
627
|
blocks.push(paragraphToBlock(paragraph));
|
|
647
|
-
index += 1;
|
|
648
628
|
continue;
|
|
649
629
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
630
|
+
let pending = stack[stack.length - 1];
|
|
631
|
+
if (!pending || pending.table.depth < depth) {
|
|
632
|
+
pending = {
|
|
633
|
+
table: { type: 'table', id: uniqueId('table'), depth, rows: [], state: paragraph.tableState, gridWidthTwips: 0 },
|
|
634
|
+
cells: [], content: [], lastState: paragraph.tableState, storyKind: paragraph.storyKind,
|
|
635
|
+
};
|
|
636
|
+
stack.push(pending);
|
|
637
|
+
}
|
|
638
|
+
pending.lastState = paragraph.tableState;
|
|
639
|
+
const rowEnd = depth > 1 ? paragraph.paraState.innerTableRowEnd : paragraph.paraState.tableRowEnd;
|
|
640
|
+
if (rowEnd) {
|
|
641
|
+
if (paragraph.text || paragraph.inlines.length)
|
|
642
|
+
pending.content.push(paragraphToBlock(paragraph));
|
|
643
|
+
finishRow(pending, paragraph.tableState);
|
|
644
|
+
continue;
|
|
654
645
|
}
|
|
655
|
-
|
|
646
|
+
pending.content.push(paragraphToBlock(paragraph));
|
|
647
|
+
const cellEnd = depth > 1
|
|
648
|
+
? paragraph.paraState.innerTableCell
|
|
649
|
+
: paragraph.terminator === DOC_CONTROL.cellMark;
|
|
650
|
+
if (cellEnd)
|
|
651
|
+
finishCell(pending);
|
|
656
652
|
}
|
|
653
|
+
while (stack.length)
|
|
654
|
+
finishTable();
|
|
657
655
|
return blocks;
|
|
658
656
|
}
|
|
659
|
-
/**
|
|
660
|
-
* Main MS-DOC entry point.
|
|
661
|
-
* It parses the OLE container, restores text through the piece table, resolves
|
|
662
|
-
* paragraph/character/table properties, and finally produces a normalized AST
|
|
663
|
-
* that the HTML renderer can consume.
|
|
664
|
-
*/
|
|
665
657
|
export function parseMsDoc(input, options = {}) {
|
|
666
658
|
const warnings = [];
|
|
667
659
|
const cfb = parseCFB(input, options);
|
package/dist/msdoc/properties.js
CHANGED
|
@@ -155,7 +155,6 @@ export function paraPropsToState(properties) {
|
|
|
155
155
|
break;
|
|
156
156
|
case 'lineSpacing': {
|
|
157
157
|
state.lineSpacing = prop.value || 0;
|
|
158
|
-
// LSPD's second short distinguishes 1/240-line multiples from signed twips.
|
|
159
158
|
const bytes = prop.operandBytes;
|
|
160
159
|
const absolute = bytes && bytes.length >= 4
|
|
161
160
|
? bytes[2] === 0 && bytes[3] === 0
|
|
@@ -375,9 +374,6 @@ export function applyTableStateToCells(tableState) {
|
|
|
375
374
|
for (const op of tableState.operations || []) {
|
|
376
375
|
switch (op.name) {
|
|
377
376
|
case 'insertCells': {
|
|
378
|
-
// Word commonly emits TDefTable for legacy readers and TInsert for
|
|
379
|
-
// readers that process sprmPTableProps. They are alternative cell
|
|
380
|
-
// definitions, not cumulative instructions.
|
|
381
377
|
if (def?.cells?.length)
|
|
382
378
|
break;
|
|
383
379
|
const value = op.value;
|
|
@@ -429,8 +425,6 @@ export function applyTableStateToCells(tableState) {
|
|
|
429
425
|
rangeApply(cells, op.value.range, (cell) => {
|
|
430
426
|
const value = op.value;
|
|
431
427
|
cell.ftsWidth = value.ftsWidth;
|
|
432
|
-
// ftsNil and ftsAuto carry no usable preferred width. Preserve the
|
|
433
|
-
// TDefTable boundary width instead of collapsing the cell to zero.
|
|
434
428
|
if ((value.ftsWidth === 2 || value.ftsWidth === 3) && value.width != null) {
|
|
435
429
|
cell.width = value.width;
|
|
436
430
|
}
|
package/dist/msdoc/styles.js
CHANGED
package/dist/msdoc/vector.js
CHANGED
|
@@ -1104,7 +1104,6 @@ function parseEmf(bytes) {
|
|
|
1104
1104
|
}
|
|
1105
1105
|
case EMF_RECORD.EMR_EXTCREATEFONTINDIRECTW: {
|
|
1106
1106
|
const index = record.u32(8);
|
|
1107
|
-
// LOGFONTW starts at offset 12: lfHeight(+0), ..., lfFaceName(+28).
|
|
1108
1107
|
const height = Math.abs(record.i32(12));
|
|
1109
1108
|
const escapement = record.i32(20);
|
|
1110
1109
|
const weight = record.i32(28);
|
package/dist/render/fonts.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { CharState } from '../types.js';
|
|
1
|
+
import type { CharState, FontInfo } from '../types.js';
|
|
2
|
+
export declare function msDocFontFallbacks(family: string, info?: FontInfo): readonly string[];
|
|
2
3
|
export declare function textFontRuns(text: string, style: CharState): Array<{
|
|
3
4
|
text: string;
|
|
4
5
|
fontFamily?: string;
|
package/dist/render/fonts.js
CHANGED
|
@@ -1,5 +1,65 @@
|
|
|
1
1
|
const EAST_ASIAN = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Script=Bopomofo}\u3000-\u303f\uff00-\uffef]/u;
|
|
2
2
|
const INHERIT_FONT = /[\p{Mark}\u200c\u200d]/u;
|
|
3
|
+
function cjkFontFallbacks(family) {
|
|
4
|
+
switch (family.toLowerCase().replace(/[ _-]/g, '')) {
|
|
5
|
+
case '宋体':
|
|
6
|
+
case '新宋体':
|
|
7
|
+
case 'simsun':
|
|
8
|
+
case 'nsimsun':
|
|
9
|
+
return ['SimSun', 'Songti SC', 'Noto Serif CJK SC', 'serif'];
|
|
10
|
+
case '仿宋':
|
|
11
|
+
case '仿宋gb2312':
|
|
12
|
+
case 'fangsong':
|
|
13
|
+
case 'fangsonggb2312':
|
|
14
|
+
return ['FangSong', 'STFangsong', 'Songti SC', 'Noto Serif CJK SC', 'serif'];
|
|
15
|
+
case '楷体':
|
|
16
|
+
case '楷体gb2312':
|
|
17
|
+
case 'kaiti':
|
|
18
|
+
case 'kaitigb2312':
|
|
19
|
+
return ['KaiTi', 'STKaiti', 'Kaiti SC', 'Songti SC', 'Noto Serif CJK SC', 'serif'];
|
|
20
|
+
case '黑体':
|
|
21
|
+
case 'simhei':
|
|
22
|
+
return ['SimHei', 'Heiti SC', 'Noto Sans CJK SC', 'sans-serif'];
|
|
23
|
+
case '微软雅黑':
|
|
24
|
+
case 'microsoftyahei':
|
|
25
|
+
return ['Microsoft YaHei', 'PingFang SC', 'Noto Sans CJK SC', 'sans-serif'];
|
|
26
|
+
}
|
|
27
|
+
const normalized = family.normalize('NFKC').toLowerCase();
|
|
28
|
+
if (/fangsong|仿宋/.test(normalized))
|
|
29
|
+
return ['STFangsong', 'Songti SC', 'Noto Serif CJK SC', 'serif'];
|
|
30
|
+
if (/simsun|nsimsun|宋体|宋體|songti/.test(normalized))
|
|
31
|
+
return ['Songti SC', 'Songti TC', 'Noto Serif CJK SC', 'serif'];
|
|
32
|
+
if (/kaiti|楷体|楷體/.test(normalized))
|
|
33
|
+
return ['Kaiti SC', 'STKaiti', 'Noto Serif CJK SC', 'serif'];
|
|
34
|
+
if (/mingliu|細明體|新細明體/.test(normalized))
|
|
35
|
+
return ['Songti TC', 'Noto Serif CJK TC', 'serif'];
|
|
36
|
+
if (/mincho|明朝/.test(normalized))
|
|
37
|
+
return ['Hiragino Mincho ProN', 'Yu Mincho', 'Noto Serif CJK JP', 'serif'];
|
|
38
|
+
if (/simhei|黑体|黑體|heiti|dengxian|等线|等線|yahei/.test(normalized))
|
|
39
|
+
return ['PingFang SC', 'Microsoft YaHei', 'Noto Sans CJK SC', 'sans-serif'];
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
export function msDocFontFallbacks(family, info) {
|
|
43
|
+
const alternate = info?.altName && info.altName !== family ? [info.altName] : [];
|
|
44
|
+
const cjk = cjkFontFallbacks(family);
|
|
45
|
+
if (cjk)
|
|
46
|
+
return [...alternate, ...cjk];
|
|
47
|
+
const genericByFamily = {
|
|
48
|
+
1: 'serif', 2: 'sans-serif', 3: 'monospace', 4: 'cursive', 5: 'fantasy',
|
|
49
|
+
};
|
|
50
|
+
const familyClass = ((info?.ffid ?? 0) >>> 4) & 7;
|
|
51
|
+
let generic = genericByFamily[familyClass];
|
|
52
|
+
if (!generic && info?.panose?.[0] === 2) {
|
|
53
|
+
const serif = info.panose[1];
|
|
54
|
+
if (info.panose[3] === 9)
|
|
55
|
+
generic = 'monospace';
|
|
56
|
+
else if (serif >= 2 && serif <= 10)
|
|
57
|
+
generic = 'serif';
|
|
58
|
+
else if (serif >= 11 && serif <= 15)
|
|
59
|
+
generic = 'sans-serif';
|
|
60
|
+
}
|
|
61
|
+
return [...alternate, generic ?? 'sans-serif'];
|
|
62
|
+
}
|
|
3
63
|
export function textFontRuns(text, style) {
|
|
4
64
|
if (!text)
|
|
5
65
|
return [];
|
package/dist/render/html.d.ts
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
import type { MsDocParseResult, MsDocRenderOptions, MsDocRenderResult } from '../types.js';
|
|
2
2
|
type ExternalLinkPolicy = NonNullable<MsDocRenderOptions['externalLinkPolicy']>;
|
|
3
|
-
/**
|
|
4
|
-
* Normalizes a document-provided hyperlink before it reaches generated HTML.
|
|
5
|
-
* Internal bookmarks always remain available. External navigation is opt-in
|
|
6
|
-
* and is still restricted to browser-safe schemes and relative paths.
|
|
7
|
-
*/
|
|
8
3
|
export declare function sanitizeMsDocLinkHref(href: string | undefined | null, policy?: ExternalLinkPolicy): string | null;
|
|
9
4
|
export declare function defaultMsDocCss(): string;
|
|
10
|
-
/**
|
|
11
|
-
* Converts the parsed AST into HTML and a companion CSS string.
|
|
12
|
-
* Keeping rendering separate from parsing makes it easier for downstream apps
|
|
13
|
-
* to customize styles or consume the AST directly.
|
|
14
|
-
*/
|
|
15
5
|
export declare function renderMsDoc(parsed: MsDocParseResult, options?: MsDocRenderOptions): MsDocRenderResult;
|
|
16
6
|
export {};
|
package/dist/render/html.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { escapeHtml, slugify, twipsToPx } from '../core/utils.js';
|
|
2
2
|
import { HIGHLIGHT_COLORS } from '../msdoc/constants.js';
|
|
3
3
|
import { cssTextAlign, cssUnderline, cssVerticalAlign } from '../msdoc/properties.js';
|
|
4
|
-
import { textFontRuns } from './fonts.js';
|
|
4
|
+
import { msDocFontFallbacks, textFontRuns } from './fonts.js';
|
|
5
5
|
const COLOR_INDEX_MAP = {
|
|
6
6
|
1: '#000000',
|
|
7
7
|
2: '#0000ff',
|
|
@@ -34,11 +34,10 @@ function quoteCssString(value) {
|
|
|
34
34
|
.replace(/[\u0000-\u001f\u007f-\u009f]/g, ' ');
|
|
35
35
|
return `'${escaped}'`;
|
|
36
36
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
*/
|
|
37
|
+
function fontFamilyToCss(family, context) {
|
|
38
|
+
const fallbacks = msDocFontFallbacks(family, context.fonts.get(family));
|
|
39
|
+
return [quoteCssString(family), ...fallbacks.map((name, index) => index === fallbacks.length - 1 ? name : quoteCssString(name))].join(',');
|
|
40
|
+
}
|
|
42
41
|
export function sanitizeMsDocLinkHref(href, policy = 'block') {
|
|
43
42
|
const value = String(href ?? '')
|
|
44
43
|
.replace(/[\u0000-\u0020\u007f-\u009f]/g, '')
|
|
@@ -160,7 +159,6 @@ function paragraphStyleToCss(paraState) {
|
|
|
160
159
|
}
|
|
161
160
|
else {
|
|
162
161
|
const multiple = Math.abs(paraState.lineSpacing) / 240;
|
|
163
|
-
// A font's single line includes its metrics and leading, not just a 1em box.
|
|
164
162
|
style['line-height'] = multiple === 1 ? 'normal' : String(multiple);
|
|
165
163
|
}
|
|
166
164
|
}
|
|
@@ -203,7 +201,7 @@ function buildUnderlineStyle(underline) {
|
|
|
203
201
|
css['text-decoration-style'] = 'solid';
|
|
204
202
|
return css;
|
|
205
203
|
}
|
|
206
|
-
function inlineStyleToCss(styleState) {
|
|
204
|
+
function inlineStyleToCss(styleState, context) {
|
|
207
205
|
const style = {};
|
|
208
206
|
if (styleState.bold || styleState.boldBi)
|
|
209
207
|
style['font-weight'] = '700';
|
|
@@ -215,7 +213,7 @@ function inlineStyleToCss(styleState) {
|
|
|
215
213
|
if (styleState.fontSizeHalfPoints)
|
|
216
214
|
style['font-size'] = `${styleState.fontSizeHalfPoints / 2}pt`;
|
|
217
215
|
if (styleState.fontFamily)
|
|
218
|
-
style['font-family'] =
|
|
216
|
+
style['font-family'] = fontFamilyToCss(styleState.fontFamily, context);
|
|
219
217
|
if (styleState.colorIndex && COLOR_INDEX_MAP[styleState.colorIndex])
|
|
220
218
|
style.color = COLOR_INDEX_MAP[styleState.colorIndex];
|
|
221
219
|
const highlightIndex = typeof styleState.highlight === 'number' ? styleState.highlight : styleState.highlight?.index;
|
|
@@ -253,10 +251,10 @@ function renderTextNode(node, context) {
|
|
|
253
251
|
const text = escapeHtml(run.text);
|
|
254
252
|
if (!run.fontFamily || run.fontFamily === node.style.fontFamily)
|
|
255
253
|
return text;
|
|
256
|
-
const font = styleObjectToCss({ 'font-family':
|
|
254
|
+
const font = styleObjectToCss({ 'font-family': fontFamilyToCss(run.fontFamily, context) });
|
|
257
255
|
return `<span style="${font}">${text}</span>`;
|
|
258
256
|
}).join('');
|
|
259
|
-
const inlineStyle = inlineStyleToCss(node.style);
|
|
257
|
+
const inlineStyle = inlineStyleToCss(node.style, context);
|
|
260
258
|
inlineStyle['white-space'] = 'break-spaces';
|
|
261
259
|
const revision = context.reviewMode === 'all'
|
|
262
260
|
? node.style.revisionDeleted ? 'delete' : node.style.revisionInserted ? 'insert' : null
|
|
@@ -373,7 +371,7 @@ function sanitizeAssetHref(href, context) {
|
|
|
373
371
|
function renderImageNode(node, context) {
|
|
374
372
|
const allowGeneratedVector = node.asset.meta?.vectorConverted === true;
|
|
375
373
|
const src = sanitizeImageSource(node.asset.sourceUrl, context.externalResourcePolicy, allowGeneratedVector) || sanitizeImageSource(node.asset.dataUrl, context.externalResourcePolicy, allowGeneratedVector);
|
|
376
|
-
const baseStyle = inlineStyleToCss(node.style);
|
|
374
|
+
const baseStyle = inlineStyleToCss(node.style, context);
|
|
377
375
|
const displaySize = inlineImageDisplaySizePx(node.asset);
|
|
378
376
|
if (displaySize.widthPx || displaySize.heightPx) {
|
|
379
377
|
applyInlineImageDisplaySize(baseStyle, node.asset);
|
|
@@ -432,7 +430,6 @@ function reviewParagraphs(paragraphs, context) {
|
|
|
432
430
|
const previous = result[result.length - 1];
|
|
433
431
|
if (previous && previous.storyKind === paragraph.storyKind
|
|
434
432
|
&& hiddenRevision(previous.paragraphMark, context)) {
|
|
435
|
-
// The surviving paragraph mark owns the merged paragraph's formatting.
|
|
436
433
|
result[result.length - 1] = {
|
|
437
434
|
...paragraph,
|
|
438
435
|
inlines: [...previous.inlines, ...paragraph.inlines],
|
|
@@ -508,7 +505,15 @@ function tableStyle(block) {
|
|
|
508
505
|
else
|
|
509
506
|
style.width = '100%';
|
|
510
507
|
const marginLeft = twipsToPx(block.state?.leftIndent);
|
|
511
|
-
if (
|
|
508
|
+
if (block.state?.alignment === 1) {
|
|
509
|
+
style['margin-inline-start'] = 'auto';
|
|
510
|
+
style['margin-inline-end'] = 'auto';
|
|
511
|
+
}
|
|
512
|
+
else if (block.state?.alignment === 2) {
|
|
513
|
+
style['margin-inline-start'] = 'auto';
|
|
514
|
+
style['margin-inline-end'] = '0';
|
|
515
|
+
}
|
|
516
|
+
else if (marginLeft)
|
|
512
517
|
style['margin-inline-start'] = `${marginLeft}px`;
|
|
513
518
|
if (block.state?.rtl)
|
|
514
519
|
style.direction = 'rtl';
|
|
@@ -531,6 +536,48 @@ function tableStyle(block) {
|
|
|
531
536
|
style['table-layout'] = 'fixed';
|
|
532
537
|
return style;
|
|
533
538
|
}
|
|
539
|
+
function renderTableColumns(block) {
|
|
540
|
+
const boundaries = new Set();
|
|
541
|
+
for (const row of block.rows)
|
|
542
|
+
for (const cell of row.cells) {
|
|
543
|
+
const left = cell.meta?.leftBoundary;
|
|
544
|
+
const right = cell.meta?.rightBoundary;
|
|
545
|
+
if (Number.isFinite(left) && Number.isFinite(right) && right > left) {
|
|
546
|
+
boundaries.add(left);
|
|
547
|
+
boundaries.add(right);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
const sorted = [...boundaries].sort((a, b) => a - b);
|
|
551
|
+
if (sorted.length < 2)
|
|
552
|
+
return '';
|
|
553
|
+
return `<colgroup>${sorted.slice(1).map((right, i) => `<col style="width:${twipsToPx(right - sorted[i])}px">`).join('')}</colgroup>`;
|
|
554
|
+
}
|
|
555
|
+
function renderCellBody(cell, context) {
|
|
556
|
+
const parts = [];
|
|
557
|
+
let paragraphs = [];
|
|
558
|
+
const wrapText = (body) => cell.meta?.textFlow === 5
|
|
559
|
+
? `<div class="msdoc-cell-vertical" style="writing-mode:vertical-rl;text-orientation:upright;inline-size:max-content;block-size:max-content;margin:0 auto">${body}</div>`
|
|
560
|
+
: body;
|
|
561
|
+
const flush = () => {
|
|
562
|
+
if (!paragraphs.length)
|
|
563
|
+
return;
|
|
564
|
+
const body = reviewParagraphs(paragraphs, context)
|
|
565
|
+
.map(paragraph => renderParagraphBlock(paragraph, context, { inline: true })).join('');
|
|
566
|
+
if (body)
|
|
567
|
+
parts.push(wrapText(body));
|
|
568
|
+
paragraphs = [];
|
|
569
|
+
};
|
|
570
|
+
for (const block of cell.blocks ?? cell.paragraphs) {
|
|
571
|
+
if (block.type === 'paragraph')
|
|
572
|
+
paragraphs.push(block);
|
|
573
|
+
else {
|
|
574
|
+
flush();
|
|
575
|
+
parts.push(renderTableBlock(block, context));
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
flush();
|
|
579
|
+
return parts.join('') || wrapText('<div class="msdoc-paragraph"><br></div>');
|
|
580
|
+
}
|
|
534
581
|
function renderTableBlock(block, context) {
|
|
535
582
|
const rows = block.rows.map((row) => {
|
|
536
583
|
const rowHeight = row.state?.rowHeight ? twipsToPx(Math.abs(row.state.rowHeight)) : null;
|
|
@@ -544,14 +591,13 @@ function renderTableBlock(block, context) {
|
|
|
544
591
|
if ((cell.rowspan ?? 1) > 1)
|
|
545
592
|
attrs.push(` rowspan="${cell.rowspan}"`);
|
|
546
593
|
const style = styleObjectToCss(cellStyle(cell));
|
|
547
|
-
const body =
|
|
548
|
-
.map((paragraph) => renderParagraphBlock(paragraph, context, { inline: true })).join('');
|
|
594
|
+
const body = renderCellBody(cell, context);
|
|
549
595
|
return `<td class="msdoc-cell"${attrs.join('')}${style ? ` style="${style}"` : ''}>${body || '<div class="msdoc-paragraph"><br></div>'}</td>`;
|
|
550
596
|
})
|
|
551
597
|
.join('');
|
|
552
598
|
return `<tr class="msdoc-row"${rowStyle}>${cells}</tr>`;
|
|
553
599
|
}).join('');
|
|
554
|
-
return `<table class="msdoc-table msdoc-table-depth-${block.depth}" style="${styleObjectToCss(tableStyle(block))}"
|
|
600
|
+
return `<table class="msdoc-table msdoc-table-depth-${block.depth}" style="${styleObjectToCss(tableStyle(block))}">${renderTableColumns(block)}<tbody>${rows}</tbody></table>`;
|
|
555
601
|
}
|
|
556
602
|
function renderAttachmentsBlock(block) {
|
|
557
603
|
const items = block.items.map((item) => {
|
|
@@ -572,6 +618,7 @@ export function defaultMsDocCss() {
|
|
|
572
618
|
.msdoc-line-at-least span,.msdoc-line-at-least ins,.msdoc-line-at-least del{line-height:normal}
|
|
573
619
|
.msdoc-paragraph:last-child{margin-bottom:0}
|
|
574
620
|
.msdoc-table{margin:12px 0;border-collapse:collapse;border-spacing:0;max-width:100%}
|
|
621
|
+
.msdoc-cell>.msdoc-table{margin-top:0;margin-bottom:0}
|
|
575
622
|
.msdoc-cell{padding:6px 8px;vertical-align:top;word-break:break-word;overflow-wrap:anywhere}
|
|
576
623
|
.msdoc-link{color:#1a73e8;text-decoration:none}
|
|
577
624
|
.msdoc-link:hover{text-decoration:underline}
|
|
@@ -583,17 +630,13 @@ export function defaultMsDocCss() {
|
|
|
583
630
|
.msdoc-page-break{display:block;height:0;border-top:1px dashed #cbd5e1;margin:16px 0}
|
|
584
631
|
`;
|
|
585
632
|
}
|
|
586
|
-
/**
|
|
587
|
-
* Converts the parsed AST into HTML and a companion CSS string.
|
|
588
|
-
* Keeping rendering separate from parsing makes it easier for downstream apps
|
|
589
|
-
* to customize styles or consume the AST directly.
|
|
590
|
-
*/
|
|
591
633
|
export function renderMsDoc(parsed, options = {}) {
|
|
592
634
|
const css = options.css ?? defaultMsDocCss();
|
|
593
635
|
const context = {
|
|
594
636
|
reviewMode: options.reviewMode ?? 'all',
|
|
595
637
|
externalLinkPolicy: options.externalLinkPolicy ?? 'block',
|
|
596
638
|
externalResourcePolicy: options.externalResourcePolicy ?? 'block',
|
|
639
|
+
fonts: new Map((parsed.fonts ?? []).flatMap(font => [font.name, font.altName].filter(Boolean).map(name => [name, font]))),
|
|
597
640
|
};
|
|
598
641
|
const parts = [];
|
|
599
642
|
let paragraphs = [];
|
package/dist/types.d.ts
CHANGED
|
@@ -482,7 +482,6 @@ export interface OfficeArtBlipMeta {
|
|
|
482
482
|
blipIndex?: number;
|
|
483
483
|
sourceShapeId?: number;
|
|
484
484
|
}
|
|
485
|
-
/** PICF / OfficeArt BLIP / linked-picture metadata used by the HTML renderer. */
|
|
486
485
|
export interface ImageAssetMeta extends PicfSummary, OfficeArtBlipMeta {
|
|
487
486
|
linkedPath?: string;
|
|
488
487
|
sourceKind?: 'embedded' | 'linked' | 'fallback';
|
|
@@ -582,6 +581,7 @@ export interface ParagraphBlock {
|
|
|
582
581
|
export interface TableCellBlock {
|
|
583
582
|
id: string;
|
|
584
583
|
paragraphs: ParagraphBlock[];
|
|
584
|
+
blocks?: Array<ParagraphBlock | TableBlock>;
|
|
585
585
|
meta: TableCellMeta | null;
|
|
586
586
|
colIndex?: number;
|
|
587
587
|
colspan?: number;
|
|
@@ -632,11 +632,8 @@ export interface MsDocParseOptions {
|
|
|
632
632
|
}
|
|
633
633
|
export interface MsDocRenderOptions {
|
|
634
634
|
css?: string;
|
|
635
|
-
/** Preserve insertion/deletion marks by default; explicitly select final or original text. */
|
|
636
635
|
reviewMode?: 'all' | 'final' | 'original';
|
|
637
|
-
/** External links are blocked by default; internal document bookmarks remain active. */
|
|
638
636
|
externalLinkPolicy?: 'allow' | 'block';
|
|
639
|
-
/** Linked image resources are blocked by default; embedded data/blob images remain available. */
|
|
640
637
|
externalResourcePolicy?: 'allow' | 'block';
|
|
641
638
|
}
|
|
642
639
|
export interface MsDocMeta {
|
package/dist/viewer.d.ts
CHANGED
|
@@ -1,16 +1,7 @@
|
|
|
1
1
|
import type { Config } from 'dompurify';
|
|
2
2
|
import type { MsDocParseToHtmlOptions, MsDocRenderResult, MsDocViewer, MsDocViewerConfig, ViewerInput } from './types.js';
|
|
3
|
-
/**
|
|
4
|
-
* Shared DOC/RTF DOMPurify boundary. A per-window purifier and Trusted Types
|
|
5
|
-
* policy avoid duplicate policy creation when several Word formats are opened
|
|
6
|
-
* in the same host document.
|
|
7
|
-
*/
|
|
8
3
|
export declare function sanitizeFileViewerDocumentMarkup(html: string, targetWindow: Window, config: Config): unknown | null;
|
|
9
4
|
export declare function sanitizeMsDocHtml(html: string, targetWindow: Window): DocumentFragment;
|
|
10
5
|
export declare function mountMsDoc(container: HTMLElement, rendered: MsDocRenderResult): HTMLElement;
|
|
11
6
|
export declare function parseMsDocToHtml(input: ViewerInput, options?: MsDocParseToHtmlOptions): Promise<MsDocRenderResult>;
|
|
12
|
-
/**
|
|
13
|
-
* Small DOM-oriented helper that keeps browser integration trivial.
|
|
14
|
-
* Apps can either use it directly or consume the lower-level parse/render APIs.
|
|
15
|
-
*/
|
|
16
7
|
export declare function createMsDocViewer(container: HTMLElement, config?: MsDocViewerConfig): MsDocViewer;
|
package/dist/viewer.js
CHANGED
|
@@ -32,11 +32,6 @@ const getDocumentTrustedTypesPolicy = (targetWindow) => {
|
|
|
32
32
|
return null;
|
|
33
33
|
}
|
|
34
34
|
};
|
|
35
|
-
/**
|
|
36
|
-
* Shared DOC/RTF DOMPurify boundary. A per-window purifier and Trusted Types
|
|
37
|
-
* policy avoid duplicate policy creation when several Word formats are opened
|
|
38
|
-
* in the same host document.
|
|
39
|
-
*/
|
|
40
35
|
export function sanitizeFileViewerDocumentMarkup(html, targetWindow, config) {
|
|
41
36
|
const purifier = getDocumentPurifier(targetWindow);
|
|
42
37
|
if (!purifier)
|
|
@@ -49,8 +44,6 @@ export function sanitizeFileViewerDocumentMarkup(html, targetWindow, config) {
|
|
|
49
44
|
});
|
|
50
45
|
}
|
|
51
46
|
catch {
|
|
52
|
-
// If a host CSP forbids this policy name, callers fall back to a text node
|
|
53
|
-
// rather than mounting unsanitized markup or failing the whole preview.
|
|
54
47
|
return null;
|
|
55
48
|
}
|
|
56
49
|
}
|
|
@@ -112,10 +105,6 @@ export async function parseMsDocToHtml(input, options = {}) {
|
|
|
112
105
|
const parsed = parseMsDoc(buffer, options.parseOptions || {});
|
|
113
106
|
return renderMsDoc(parsed, options.renderOptions || {});
|
|
114
107
|
}
|
|
115
|
-
/**
|
|
116
|
-
* Small DOM-oriented helper that keeps browser integration trivial.
|
|
117
|
-
* Apps can either use it directly or consume the lower-level parse/render APIs.
|
|
118
|
-
*/
|
|
119
108
|
export function createMsDocViewer(container, config = {}) {
|
|
120
109
|
let current = null;
|
|
121
110
|
return {
|
package/dist/worker-client.d.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
import type { MsDocParseOptions, MsDocParseResult, MsDocParseToHtmlOptions, MsDocRenderOptions, MsDocRenderResult, MsDocWorkerClientLike, WorkerLike } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Thin typed wrapper around a module worker so applications can offload
|
|
4
|
-
* expensive binary parsing without having to manage message correlation.
|
|
5
|
-
*/
|
|
6
2
|
export declare class MsDocWorkerClient implements MsDocWorkerClientLike {
|
|
7
3
|
private readonly worker;
|
|
8
4
|
private readonly pending;
|
package/dist/worker-client.js
CHANGED
|
@@ -8,10 +8,6 @@ function toArrayBuffer(input) {
|
|
|
8
8
|
}
|
|
9
9
|
throw new TypeError('Expected ArrayBuffer or TypedArray');
|
|
10
10
|
}
|
|
11
|
-
/**
|
|
12
|
-
* Thin typed wrapper around a module worker so applications can offload
|
|
13
|
-
* expensive binary parsing without having to manage message correlation.
|
|
14
|
-
*/
|
|
15
11
|
export class MsDocWorkerClient {
|
|
16
12
|
worker;
|
|
17
13
|
pending = new Map();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@file-viewer/doc",
|
|
3
|
-
"version": "3.1
|
|
3
|
+
"version": "3.2.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Browser-native legacy MS-DOC (.doc) parser and HTML renderer extracted from the public msdoc-viewer 0.2.0 line.",
|
|
@@ -27,8 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"repository": {
|
|
29
29
|
"type": "git",
|
|
30
|
-
"url": "git+https://github.com/flyfish-dev/file-viewer.git"
|
|
31
|
-
"directory": "packages/renderers/doc"
|
|
30
|
+
"url": "git+https://github.com/flyfish-dev/file-viewer.git"
|
|
32
31
|
},
|
|
33
32
|
"homepage": "https://github.com/flyfish-dev/docjs",
|
|
34
33
|
"bugs": {
|
|
@@ -56,26 +55,66 @@
|
|
|
56
55
|
"./package.json": "./package.json"
|
|
57
56
|
},
|
|
58
57
|
"files": [
|
|
59
|
-
"dist",
|
|
60
|
-
"
|
|
58
|
+
"dist/core/binary.d.ts",
|
|
59
|
+
"dist/core/binary.js",
|
|
60
|
+
"dist/core/cfb.d.ts",
|
|
61
|
+
"dist/core/cfb.js",
|
|
62
|
+
"dist/core/inflate.d.ts",
|
|
63
|
+
"dist/core/inflate.js",
|
|
64
|
+
"dist/core/utils.d.ts",
|
|
65
|
+
"dist/core/utils.js",
|
|
66
|
+
"dist/index.d.ts",
|
|
67
|
+
"dist/index.js",
|
|
68
|
+
"dist/msdoc/clx.d.ts",
|
|
69
|
+
"dist/msdoc/clx.js",
|
|
70
|
+
"dist/msdoc/constants.d.ts",
|
|
71
|
+
"dist/msdoc/constants.js",
|
|
72
|
+
"dist/msdoc/fib.d.ts",
|
|
73
|
+
"dist/msdoc/fib.js",
|
|
74
|
+
"dist/msdoc/fkp.d.ts",
|
|
75
|
+
"dist/msdoc/fkp.js",
|
|
76
|
+
"dist/msdoc/fonts.d.ts",
|
|
77
|
+
"dist/msdoc/fonts.js",
|
|
78
|
+
"dist/msdoc/html-word.d.ts",
|
|
79
|
+
"dist/msdoc/html-word.js",
|
|
80
|
+
"dist/msdoc/objects.d.ts",
|
|
81
|
+
"dist/msdoc/objects.js",
|
|
82
|
+
"dist/msdoc/officeart.d.ts",
|
|
83
|
+
"dist/msdoc/officeart.js",
|
|
84
|
+
"dist/msdoc/parser.d.ts",
|
|
85
|
+
"dist/msdoc/parser.js",
|
|
86
|
+
"dist/msdoc/properties.d.ts",
|
|
87
|
+
"dist/msdoc/properties.js",
|
|
88
|
+
"dist/msdoc/sprm.d.ts",
|
|
89
|
+
"dist/msdoc/sprm.js",
|
|
90
|
+
"dist/msdoc/styles.d.ts",
|
|
91
|
+
"dist/msdoc/styles.js",
|
|
92
|
+
"dist/msdoc/vector.d.ts",
|
|
93
|
+
"dist/msdoc/vector.js",
|
|
94
|
+
"dist/render/fonts.d.ts",
|
|
95
|
+
"dist/render/fonts.js",
|
|
96
|
+
"dist/render/html.d.ts",
|
|
97
|
+
"dist/render/html.js",
|
|
98
|
+
"dist/types.d.ts",
|
|
99
|
+
"dist/types.js",
|
|
100
|
+
"dist/viewer.d.ts",
|
|
101
|
+
"dist/viewer.js",
|
|
102
|
+
"dist/worker-client.d.ts",
|
|
103
|
+
"dist/worker-client.js",
|
|
104
|
+
"dist/worker.d.ts",
|
|
105
|
+
"dist/worker.js",
|
|
106
|
+
"LICENSE",
|
|
61
107
|
"README.en.md",
|
|
62
|
-
"
|
|
108
|
+
"README.md"
|
|
63
109
|
],
|
|
64
110
|
"dependencies": {
|
|
65
|
-
"dompurify": "3.4.
|
|
66
|
-
},
|
|
67
|
-
"devDependencies": {
|
|
68
|
-
"typescript": "^6.0.3"
|
|
111
|
+
"dompurify": "3.4.16"
|
|
69
112
|
},
|
|
70
113
|
"license": "MIT",
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
"verify:github-87": "pnpm build && node scripts/verify-github-87.mjs",
|
|
77
|
-
"verify:github-171": "pnpm build && node scripts/verify-github-171.mjs",
|
|
78
|
-
"verify:github-192": "pnpm build && node scripts/verify-github-192.mjs",
|
|
79
|
-
"verify:github-236": "pnpm build && node scripts/verify-github-236.mjs"
|
|
114
|
+
"fileViewer": {
|
|
115
|
+
"distribution": {
|
|
116
|
+
"schemaVersion": 1,
|
|
117
|
+
"mode": "runtime-only"
|
|
118
|
+
}
|
|
80
119
|
}
|
|
81
|
-
}
|
|
120
|
+
}
|