# docx-viewer 使用文档

本文档说明如何运行静态 demo、在网页中集成 DOCX 渲染器、使用公开 API、配置渲染选项，以及维护 GitHub Pages 自动部署。

## 1. 在线 Demo

在线地址：

https://wybaby168.github.io/docx-viewer/

demo 支持三种常用方式：

- 通过文件选择器上传本地 `.docx` 文件。
- 将 `.docx` 文件拖拽到预览区域。
- 从 `Test Documents` 下拉菜单加载仓库内置样例。

上传或加载 DOCX 后，demo 会把 ZIP 解包和 OOXML 解析放到 `demo/docx-worker.js` 里的 Web Worker 执行。主线程只接收可渲染的文档快照、创建 DOM，并在图片、字体、图表、SmartArt、墨迹或 alternate chunk 需要时异步向 worker 请求资源，因此大文档解析期间页面仍可响应。

worker 会优先使用浏览器原生 `DOMParser`；如果某些 worker 环境没有暴露该 API，则仅在 worker 内从 esm.sh 加载 `@xmldom/xmldom` 作为 XML 解析 fallback。

DOM 插入完成后，demo 会继续等待 `awaitRenderedLayout()`。这样动态分页完成后才会更新缩略图和 `Ready` 状态，避免长文档只显示初始几页。

demo 还会设置 `ignoreLastRenderedPageBreak: false`，优先使用 Microsoft Word 已写入的 `lastRenderedPageBreak` 分页标记。worker 检测到这些标记后，demo 会跳过该文档的动态重分页，直接使用已存分页，避免只显示初始少量页面，也避免浏览器端分页异常膨胀。

公开 demo 默认关闭 `debug`，避免长文档把控制台刷满非致命的 OOXML 兼容提示。

页面顶部的 `Rendered snapshots` 会打开 `rendered-docx-effect.html`，它是一个静态渲染快照页，用于快速查看 `tests/render-test/**/result.html` 的视觉效果。

## 2. 目录说明

关键文件如下：

- `index.html`：交互式静态 demo 页面。
- `dist/docx-preview.js`：UMD 浏览器构建，加载后暴露为 `window.docx`。
- `dist/docx-preview.mjs`：ES module 构建，适合现代浏览器或打包工具。
- `dist/docx-preview.d.ts`：公开 API 的 TypeScript 类型声明。
- `demo/docx-preprocessor.js`：可选 DOCX 预处理参考脚本，可在自定义页面中按需使用。
- `demo/docx-worker.js`：demo 使用的 Web Worker，负责在后台解析 DOCX。
- `demo/docx-worker-client.js`：主线程代理，负责调用 worker、重建可渲染文档对象并按需加载资源。
- `demo/thumbnail.example.js`：demo 使用的缩略图示例脚本。
- `tests/render-test/**/document.docx`：demo 下拉菜单使用的 DOCX 样例文件。
- `scripts/build-pages.mjs`：把 GitHub Pages 所需静态资源复制到 `_site/`。
- `scripts/check-pages.mjs`：部署前校验 `_site/` 中的关键文件。

## 3. 本地运行

```bash
git clone https://github.com/wybaby168/docx-viewer.git
cd docx-viewer
npm install
```

构建静态 demo：

```bash
npm run build:pages
npm run check:pages
```

构建结果会生成在 `_site/`。建议用任意静态服务器访问：

```bash
npx serve _site
```

也可以直接打开 `index.html`，但部分浏览器会限制本地文件环境中的 `fetch()`，导致内置 DOCX 样例无法加载。使用 HTTP 静态服务器更稳定。

Web Worker 解析也建议在 HTTP 静态服务器下测试，避免不同浏览器对 `file://` worker、`fetch()` 或跨源脚本加载的限制。

如果需要验证某个已由同源静态服务器暴露的 DOCX，可以使用 `?src=/path/to/document.docx` 直接加载。

## 4. 普通浏览器脚本集成

如果项目没有打包工具，可以直接使用 UMD 构建。

```html
<script src="https://unpkg.com/jszip/dist/jszip.min.js"></script>
<script src="./dist/docx-preview.js"></script>

<input id="file" type="file" accept=".docx" />
<div id="viewer"></div>

<script>
  const input = document.getElementById("file");
  const viewer = document.getElementById("viewer");

  input.addEventListener("change", async () => {
    const file = input.files[0];
    if (!file) return;

    viewer.innerHTML = "";

    await docx.renderAsync(file, viewer, null, {
      className: "docx",
      inWrapper: true,
      breakPages: true,
      renderHeaders: true,
      renderFooters: true,
      renderFootnotes: true,
      renderEndnotes: true,
      strictWordCompatibility: true
    });
  });
</script>
```

注意事项：

- 必须先加载 `JSZip`，再加载 `dist/docx-preview.js`。
- UMD 构建会把 API 挂到 `window.docx`。
- 第一个参数可以是 `Blob`、`ArrayBuffer`、`Uint8Array`，或其他 `JSZip.loadAsync` 支持的输入。
- 如果文档较大，参考 demo 的 `demo/docx-worker-client.js` 和 `demo/docx-worker.js`，把 `parseAsync` 放到 Web Worker 中执行，再在主线程调用 `renderDocument` 渲染 DOM。

## 5. ES Module 集成

现代浏览器或前端工程可以使用 ESM 构建。

```html
<div id="viewer"></div>

<script type="module">
  import { renderAsync } from "./dist/docx-preview.mjs";

  const response = await fetch("./tests/render-test/text/document.docx");
  const data = await response.blob();

  await renderAsync(data, document.getElementById("viewer"), null, {
    breakPages: true,
    strictWordCompatibility: true
  });
</script>
```

如果要在其他项目中直接依赖这个 GitHub 仓库，可以安装：

```bash
npm install github:wybaby168/docx-viewer
```

然后在代码中引用：

```ts
import { renderAsync } from "@wybaby168/docx-viewer";
```

## 6. 核心 API

### renderAsync

```ts
renderAsync(
  document: Blob | ArrayBuffer | Uint8Array,
  bodyContainer: HTMLElement,
  styleContainer?: HTMLElement,
  options?: Partial<Options>
): Promise<WordDocument>
```

大多数场景优先使用 `renderAsync`。它会解析 DOCX，并把生成的 HTML 渲染到 `bodyContainer`。

`styleContainer` 是可选参数。如果不传或传 `null`，生成的样式会插入到 `bodyContainer`。如果希望样式和滚动内容分离，可以传入独立的样式容器。

### parseAsync

```ts
parseAsync(
  document: Blob | ArrayBuffer | Uint8Array,
  options?: Partial<Options>
): Promise<WordDocument>
```

把 DOCX 解析成内部的 `WordDocument` 对象。适合需要在渲染前检查或修改文档结构的高级场景。

### renderDocument

```ts
renderDocument(
  wordDocument: WordDocument,
  options?: Partial<Options>
): Promise<Node[]>
```

把已经解析好的文档渲染成 DOM 节点。它比 `renderAsync` 更底层。

### awaitRenderedLayout

```ts
awaitRenderedLayout(
  container: HTMLElement,
  options?: Partial<Options>
): Promise<LayoutSnapshot>
```

等待图片、字体、浏览器布局帧以及可选分页过程完成，然后返回布局快照。

### collectLayoutSnapshot

```ts
collectLayoutSnapshot(
  container: HTMLElement,
  options?: Partial<Options>
): LayoutSnapshot
```

从已经渲染好的容器中收集页数、溢出页、字段值、锚点、浮动对象和未加载媒体等信息。

## 7. 渲染选项

常用选项如下：

| 选项 | 默认值 | 说明 |
| --- | --- | --- |
| `className` | `"docx"` | 生成样式使用的 CSS 类名前缀。 |
| `inWrapper` | `true` | 是否用 `.docx-wrapper` 包裹渲染页面。 |
| `hideWrapperOnPrint` | `false` | 打印时隐藏外层 wrapper 样式。 |
| `ignoreWidth` | `false` | 忽略 DOCX 页面宽度。 |
| `ignoreHeight` | `false` | 忽略 DOCX 页面高度。 |
| `ignoreFonts` | `false` | 禁用嵌入字体渲染。 |
| `breakPages` | `true` | 尽可能渲染分页。 |
| `ignoreLastRenderedPageBreak` | `true` | 忽略 Word 生成的 `<w:lastRenderedPageBreak/>` 标记。 |
| `strictWordCompatibility` | `true` | 启用更接近 Word 的分页和布局兼容逻辑。 |
| `paginationTolerance` | `2` | 判定页面溢出时允许的像素误差。 |
| `maxDynamicPaginationPasses` | `1000` | 动态分页循环的安全上限。 |
| `awaitLayout` | `false` | 让 `renderAsync` 等待布局相关异步任务后再 resolve。 |
| `experimental` | `false` | 启用实验性解析或渲染路径。 |
| `trimXmlDeclaration` | `true` | 解析 XML 前移除 XML declaration。 |
| `useBase64URL` | `false` | 媒体资源使用 base64 URL，而不是 object URL。 |
| `renderChanges` | `false` | 渲染插入、删除等修订内容。 |
| `renderHeaders` | `true` | 渲染页眉。 |
| `renderFooters` | `true` | 渲染页脚。 |
| `renderFootnotes` | `true` | 渲染脚注。 |
| `renderEndnotes` | `true` | 渲染尾注。 |
| `renderComments` | `false` | 在支持的情况下渲染批注。 |
| `renderAltChunks` | `true` | 渲染 alternate HTML chunks。 |
| `debug` | `false` | 输出额外调试信息。 |

生产环境建议从下面的配置开始：

```ts
const options = {
  breakPages: true,
  strictWordCompatibility: true,
  renderHeaders: true,
  renderFooters: true,
  renderFootnotes: true,
  renderEndnotes: true,
  renderComments: false,
  awaitLayout: true
};
```

## 8. 加载远程 DOCX

加载远程文件时，先用 `fetch` 取回 `Blob` 或 `ArrayBuffer`。

```ts
const response = await fetch("https://example.com/report.docx");

if (!response.ok) {
  throw new Error(`Failed to load DOCX: ${response.status}`);
}

const blob = await response.blob();
await renderAsync(blob, document.getElementById("viewer"));
```

远程服务器必须允许浏览器跨域访问，也就是要配置正确的 CORS 响应头。

## 9. 渲染多个文档

渲染多个文档时，建议每个文档使用独立容器，或者在重新渲染前清空容器。

```ts
async function renderInto(container, file) {
  container.innerHTML = "";
  await docx.renderAsync(file, container, null, {
    className: "docx",
    breakPages: true
  });
}
```

如果多个文档同时显示在同一个页面上，建议给每个文档设置不同的 `className`，避免生成的样式相互影响。

## 10. GitHub Pages 自动部署

仓库使用 `.github/workflows/pages.yml` 自动部署静态 demo。

触发方式：

- 每次 push 到 `main` 自动部署。
- 也可以在 GitHub Actions 页面手动运行 `Deploy static demo` workflow。

部署流程：

1. Checkout 仓库。
2. 使用 `npm ci` 安装依赖。
3. 执行 `npm run build:pages` 生成 `_site/`。
4. 执行 `npm run check:pages` 校验静态资源。
5. 使用 `actions/upload-pages-artifact` 上传 `_site/`。
6. 使用 `actions/deploy-pages` 发布到 GitHub Pages。

部署地址：

https://wybaby168.github.io/docx-viewer/

## 11. 常见问题

页面空白：

- 打开浏览器控制台，检查脚本或 DOCX 请求是否失败。
- 使用 UMD 构建时，确认 `JSZip` 在 `dist/docx-preview.js` 之前加载。
- 尽量通过 HTTP 静态服务器访问，而不是直接打开本地文件。

内置样例加载失败：

- 确认部署产物中存在 `tests/render-test/<name>/document.docx`。
- 本地执行 `npm run build:pages` 和 `npm run check:pages`。

多个文档样式互相污染：

- 每个文档使用独立容器。
- 给每个文档设置不同的 `className`。

分页效果和 Microsoft Word 不完全一致：

- 保持 `breakPages: true`。
- 如果源 DOCX 包含 Word 生成的分页标记，可以尝试 `ignoreLastRenderedPageBreak: false`。
- 读取布局信息前使用 `awaitLayout: true`。
