From 6b749b8b5389933baa1687a9e8ba0425d2f9b85c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=A4=9A=E7=9B=8A?= Date: Thu, 13 Apr 2023 21:11:02 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Word=20=E6=94=AF=E6=8C=81=E5=88=86?= =?UTF-8?q?=E9=A1=B5=E6=B8=B2=E6=9F=93=20(#6606)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 优化分页渲染的支持 * 补充一些注释 * 补充单元测试 * 优化单元测试,减少样式冗余 --- docs/zh-CN/components/office-viewer.md | 38 +- examples/static/page.docx | Bin 0 -> 14166 bytes .../amis-editor/src/plugin/OfficeViewer.tsx | 59 +- .../__tests__/docx/simple/break-page.xml | 1367 ++++++++++++ .../__snapshots__/all-shape1.test.ts.snap | 1423 ++++++++++++ .../__snapshots__/all-shape2.test.ts.snap | 1918 +++++++++++++++++ .../__snapshots__/alt-text.test.ts.snap | 26 + .../simple/__snapshots__/bold.test.ts.snap | 635 +----- .../simple/__snapshots__/br.test.ts.snap | 139 +- .../__snapshots__/break-page.test.ts.snap | 120 ++ .../simple/__snapshots__/cr.test.ts.snap | 30 + .../__snapshots__/drop-cap.test.ts.snap | 135 +- .../simple/__snapshots__/em.test.ts.snap | 201 +- .../__snapshots__/embed-font.test.ts.snap | 122 +- .../__snapshots__/hideMark.test.ts.snap | 328 +-- .../__snapshots__/highlight.test.ts.snap | 261 +-- .../simple/__snapshots__/image.test.ts.snap | 122 +- .../simple/__snapshots__/info.test.ts.snap | 1075 ++++----- .../simple/__snapshots__/link.test.ts.snap | 159 +- .../simple/__snapshots__/list.test.ts.snap | 402 +--- .../__snapshots__/noBreakHyphen.test.ts.snap | 178 +- .../simple/__snapshots__/pinyin.test.ts.snap | 173 +- .../simple/__snapshots__/sdt.test.ts.snap | 278 +-- .../simple/__snapshots__/svg.test.ts.snap | 127 +- .../simple/__snapshots__/sym.test.ts.snap | 125 +- .../__snapshots__/tableborder.test.ts.snap | 455 ++-- .../__snapshots__/tablestyle.test.ts.snap | 1820 +++++----------- .../textbox-background.test.ts.snap | 762 +------ .../textbox-behindDoc.test.ts.snap | 207 +- .../textbox-rotation.test.ts.snap | 171 +- .../__snapshots__/textbox-vert.test.ts.snap | 539 ++--- .../simple/__snapshots__/textbox.test.ts.snap | 281 +-- .../simple/__snapshots__/tooltip.test.ts.snap | 194 +- .../simple/__snapshots__/w.test.ts.snap | 112 +- .../__tests__/simple/all-shape1.test.ts | 5 + .../__tests__/simple/all-shape2.test.ts | 5 + .../__tests__/simple/alt-text.test.ts | 5 + .../__tests__/simple/break-page.test.ts | 5 + .../ooxml-viewer/__tests__/simple/cr.test.ts | 5 + .../ooxml-viewer/__tests__/snapShotTest.ts | 3 +- packages/ooxml-viewer/examples/app.ts | 79 +- .../ooxml-viewer/examples/static/css/app.css | 64 +- packages/ooxml-viewer/index.html | 13 +- packages/ooxml-viewer/src/Word.ts | 88 +- .../ooxml-viewer/src/openxml/word/Body.ts | 4 + packages/ooxml-viewer/src/openxml/word/Run.ts | 4 +- .../ooxml-viewer/src/openxml/word/Section.ts | 21 +- .../ooxml-viewer/src/render/renderBody.ts | 238 +- packages/ooxml-viewer/src/render/renderBr.ts | 6 +- .../ooxml-viewer/src/render/renderDocument.ts | 11 +- packages/ooxml-viewer/src/render/renderRun.ts | 2 +- .../ooxml-viewer/src/render/renderSection.ts | 41 +- .../ooxml-viewer/src/render/renderStyle.ts | 6 - packages/ooxml-viewer/src/util/dom.ts | 16 +- 54 files changed, 7830 insertions(+), 6773 deletions(-) create mode 100644 examples/static/page.docx create mode 100644 packages/ooxml-viewer/__tests__/docx/simple/break-page.xml create mode 100644 packages/ooxml-viewer/__tests__/simple/__snapshots__/all-shape1.test.ts.snap create mode 100644 packages/ooxml-viewer/__tests__/simple/__snapshots__/all-shape2.test.ts.snap create mode 100644 packages/ooxml-viewer/__tests__/simple/__snapshots__/alt-text.test.ts.snap create mode 100644 packages/ooxml-viewer/__tests__/simple/__snapshots__/break-page.test.ts.snap create mode 100644 packages/ooxml-viewer/__tests__/simple/__snapshots__/cr.test.ts.snap create mode 100644 packages/ooxml-viewer/__tests__/simple/all-shape1.test.ts create mode 100644 packages/ooxml-viewer/__tests__/simple/all-shape2.test.ts create mode 100644 packages/ooxml-viewer/__tests__/simple/alt-text.test.ts create mode 100644 packages/ooxml-viewer/__tests__/simple/break-page.test.ts create mode 100644 packages/ooxml-viewer/__tests__/simple/cr.test.ts diff --git a/docs/zh-CN/components/office-viewer.md b/docs/zh-CN/components/office-viewer.md index e11e40b57..2207a6f1d 100644 --- a/docs/zh-CN/components/office-viewer.md +++ b/docs/zh-CN/components/office-viewer.md @@ -21,8 +21,9 @@ order: 23 - 文本框 - 形状 - 数学公式(依赖 MathML,需要比较新的浏览器,或者试试 [polyfill](https://github.com/w3c/mathml-polyfills)) +- 分页渲染 -不支持的功能:分页符、艺术字、域、对象、目录 +不支持的功能:艺术字、域、对象、目录 ## 基本用法 @@ -63,14 +64,43 @@ order: 23 | forceLineHeight | `string` | | 设置段落行高,忽略文档中的设置 | | enableVar | `boolean` | true | 是否开启变量替换功能 | +#### 分页渲染 + +> 2.10.0 及以上版本 + +默认情况下 word 文档渲染使用流式布局,这样能更好融入到已有页面中,支持分栏显示,且展现上会和原先的文档有较大差异,如果希望能看起来更像桌面端的效果,可以通过 `page` 配置开启分页渲染 + +```schema: scope="body" +{ + "type": "office-viewer", + "id": "office-viewer-page", + "wordOptions": { + "page": true + }, + "src": "/examples/static/page.docx", +} +``` + +分页渲染的其它设置项 + +| 属性名 | 类型 | 默认值 | 说明 | +| ------------------ | --------- | --------- | ------------------------------------------------ | +| page | `boolean` | false | 是否开启分页渲染 | +| pageMarginBottom | `number` | 20 | 页面上下间距 | +| pageBackground | `string` | '#FFF' | 页面内背景色 | +| pageShadow | `boolean` | true | 是否显示阴影 | +| pageWrap | `boolean` | true | 是否显示页面包裹,开启这个后才能设置包裹的背景色 | +| pageWrap | `boolean` | true | 是否显示页面包裹 | +| pageWrapBackground | `string` | '#ECECEC' | 是否显示页面包裹 | +| zoom | `number` | | 缩放比例,取值 0-1 之间 | +| zoomFitWidth | `boolean` | false | 自适应宽度缩放,如果设置了 zoom 将不会生效 | + ### 关于渲染效果差异 目前的实现难以保证和本地 Word 渲染完全一致,会遇到以下问题: 1. 字体大小不一致 1. 单元格宽度不一致,表格完全依赖浏览器渲染 -1. 分页显示,目前的渲染不会分页,而是内容有多长就有多高 -1. 分栏显示,这个是因为没有分页导致的,不限制高度没法分栏 如果追求完整效果打印,目前只能使用下载文件的方式用本地 Word 进行打印。 @@ -78,7 +108,7 @@ order: 23 默认情况下列表左侧的符号使用字体渲染,这样能做到最接近 Word 渲染效果,但如果用户的系统中没有这些字体就会显示乱码,为了解决这个问题需要手动在 amis 渲染的页面里导入对应的字体,比如 -```html +```css - -
-
+
+

-

+ + - - - - 立项 - - - 依据 - -

-

+ - - - - 项目背景 - -

-
-
-
-
-
-

+

+

+ + + - -

-
-
-

- -


- -

-
-
-

- -

-
-
-

- -


- -

-
-
- + 项目背景 + +

+ + `; diff --git a/packages/ooxml-viewer/__tests__/simple/__snapshots__/br.test.ts.snap b/packages/ooxml-viewer/__tests__/simple/__snapshots__/br.test.ts.snap index 00907bd50..75ad2befe 100644 --- a/packages/ooxml-viewer/__tests__/simple/__snapshots__/br.test.ts.snap +++ b/packages/ooxml-viewer/__tests__/simple/__snapshots__/br.test.ts.snap @@ -1,123 +1,30 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`br 1`] = ` -
- - -
-
+
+

-

- - 换行 + 换行 + + +
+ + 工具 - -
- - 工具 - -
-

-
-
-
-
+ +

+ + `; diff --git a/packages/ooxml-viewer/__tests__/simple/__snapshots__/break-page.test.ts.snap b/packages/ooxml-viewer/__tests__/simple/__snapshots__/break-page.test.ts.snap new file mode 100644 index 000000000..128c8255a --- /dev/null +++ b/packages/ooxml-viewer/__tests__/simple/__snapshots__/break-page.test.ts.snap @@ -0,0 +1,120 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`break-page 1`] = ` +
+
+

+ + Word + + + 分栏的好处主要有: + +

+

+   +

+

+ + + + + 提高文档的阅读性:将版面分成多栏,可以提高文档的可读性,使版面显得更加生动活泼。 + +

+

+ + + + + 控制栏数、栏宽及栏间距:可以控制栏数、栏宽及栏间距,使版面更加美观。 + +

+

+ + + + + 设置分栏长度:可以很方便地设置分栏长度,使版面更加灵活。 + +

+

+   +

+

+ + 总之, + + + Word + + + 分栏可以提高文档的可读性和美观度,控制文档的页面布局和大小,方便用户查找和管理文档内容。 + +

+

+ +
+
+

+

+ +
+ + 第二页 + +
+

+
+
+`; diff --git a/packages/ooxml-viewer/__tests__/simple/__snapshots__/cr.test.ts.snap b/packages/ooxml-viewer/__tests__/simple/__snapshots__/cr.test.ts.snap new file mode 100644 index 000000000..925d5b1af --- /dev/null +++ b/packages/ooxml-viewer/__tests__/simple/__snapshots__/cr.test.ts.snap @@ -0,0 +1,30 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`cr 1`] = ` +
+
+

+ + 换行 + + +
+ + 工具 + +
+

+
+
+`; diff --git a/packages/ooxml-viewer/__tests__/simple/__snapshots__/drop-cap.test.ts.snap b/packages/ooxml-viewer/__tests__/simple/__snapshots__/drop-cap.test.ts.snap index fb0877578..a1bf3e9b9 100644 --- a/packages/ooxml-viewer/__tests__/simple/__snapshots__/drop-cap.test.ts.snap +++ b/packages/ooxml-viewer/__tests__/simple/__snapshots__/drop-cap.test.ts.snap @@ -1,119 +1,30 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`drop-cap 1`] = ` -
- - -
-
+
+

-

- - 首 - -

-

+

+

+ - - 字下沉 - -

-
-
-
-
+ 字下沉 + +

+ + `; diff --git a/packages/ooxml-viewer/__tests__/simple/__snapshots__/em.test.ts.snap b/packages/ooxml-viewer/__tests__/simple/__snapshots__/em.test.ts.snap index 5a0c327fb..a83736cf3 100644 --- a/packages/ooxml-viewer/__tests__/simple/__snapshots__/em.test.ts.snap +++ b/packages/ooxml-viewer/__tests__/simple/__snapshots__/em.test.ts.snap @@ -1,151 +1,62 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`em 1`] = ` -
- - -
-
+
+

-

- - 一边 - - - 欣 - - - 一边 - - - 欣 - - - 一边 - - - 欣 - - - 一边 - - - 欣 - -

-
-
-
-
+ 一边 + + + 欣 + + + 一边 + + + 欣 + + + 一边 + + + 欣 + + + 一边 + + + 欣 + +

+ + `; diff --git a/packages/ooxml-viewer/__tests__/simple/__snapshots__/embed-font.test.ts.snap b/packages/ooxml-viewer/__tests__/simple/__snapshots__/embed-font.test.ts.snap index ad92b217d..99b030094 100644 --- a/packages/ooxml-viewer/__tests__/simple/__snapshots__/embed-font.test.ts.snap +++ b/packages/ooxml-viewer/__tests__/simple/__snapshots__/embed-font.test.ts.snap @@ -1,114 +1,20 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`embed-font 1`] = ` -
- - -
-
+
+

-

- - Embed font - -

-
-
-
-
+ Embed font + +

+ + `; diff --git a/packages/ooxml-viewer/__tests__/simple/__snapshots__/hideMark.test.ts.snap b/packages/ooxml-viewer/__tests__/simple/__snapshots__/hideMark.test.ts.snap index ff9b02f41..4b7db9166 100644 --- a/packages/ooxml-viewer/__tests__/simple/__snapshots__/hideMark.test.ts.snap +++ b/packages/ooxml-viewer/__tests__/simple/__snapshots__/hideMark.test.ts.snap @@ -1,238 +1,112 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`hideMark 1`] = ` -
- - - -
-
+
+ -
- - - + - + - + - - - + + + + + + + + - + - + - - - - - - - -
+
+

-

-   -

-
+ +

-

-   -

-
+ +

-

-   -

-
+
+

+

+

+

+

+

+

-

-

+ +

-

-

+ +

-

-

-

-   -

-
-

-   -

-
-

-   -

-
-

-   -

-
-
-
-
+   +

+ + + + +

+   +

+ + `; diff --git a/packages/ooxml-viewer/__tests__/simple/__snapshots__/highlight.test.ts.snap b/packages/ooxml-viewer/__tests__/simple/__snapshots__/highlight.test.ts.snap index bba4b28d0..140ca159b 100644 --- a/packages/ooxml-viewer/__tests__/simple/__snapshots__/highlight.test.ts.snap +++ b/packages/ooxml-viewer/__tests__/simple/__snapshots__/highlight.test.ts.snap @@ -1,189 +1,100 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`highlight 1`] = ` -
- - -
-
+
+

-

- - 聚 - -

-

+

+

+ - - 聚 - -

-

+

+

+ - - 聚 - -

-

+

+

+ - - 聚 - -

-

+

+

+ - - 聚 - -

-

+

+

+ - - 聚 - -

-

+

+

+ - - 聚 - -

-

+

+

+ - - 聚 - -

-

+

+

+ - - 聚 - -

-
-
-
-
+ 聚 + +

+ + `; diff --git a/packages/ooxml-viewer/__tests__/simple/__snapshots__/image.test.ts.snap b/packages/ooxml-viewer/__tests__/simple/__snapshots__/image.test.ts.snap index 647b74f26..7c037361a 100644 --- a/packages/ooxml-viewer/__tests__/simple/__snapshots__/image.test.ts.snap +++ b/packages/ooxml-viewer/__tests__/simple/__snapshots__/image.test.ts.snap @@ -1,110 +1,26 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`image 1`] = ` -
- - -
-
+
+

-

- -

- -
- -

-
-
-
-
+ + + +

+ + `; diff --git a/packages/ooxml-viewer/__tests__/simple/__snapshots__/info.test.ts.snap b/packages/ooxml-viewer/__tests__/simple/__snapshots__/info.test.ts.snap index 9b456bafe..083a89e18 100644 --- a/packages/ooxml-viewer/__tests__/simple/__snapshots__/info.test.ts.snap +++ b/packages/ooxml-viewer/__tests__/simple/__snapshots__/info.test.ts.snap @@ -1,642 +1,523 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`info 1`] = ` -
- - - -
-
+
+

-

- - 员工人事资料卡 - -

-

+

+

+ - - 日期 - - - - - - 编号 - -

- + - - - + + + +
+ + 编号 + +

+ + + + - + - + + - + - - - - - + + + + - - - - + + + - - - + - + + + - - - + - + + + - - - + - + + + - - - + - + + + - - - + - + + + - + - - -
+

-

- - 姓 - - - - - - 名 - -

-
+ + + + + 名 + +

+
+

-

- - { - - - {name}} - -

-
+ + {name}} + +

+
+

-

+ 性 + + + + + + 别 + +

+
+

+ + 出 + + - - 性 - - - - - 别 - -

-
+ + 生 + + + + + + 日 + + + + + + 期 + +

+
+

-

- - 出 - - - - - - 生 - - - - - - 日 - - - - - - 期 - -

-
+

+

-

- - 相 - -

-

-   -

-

-   -

-

-   -

-

-   -

-

-   -

-

- - 片 - -

-
+

-

-   -

-
+

-

+

+   +

+

+   +

+

+ + 片 + +

+
+

+   +

+
+

+ + + + + 年 + + - - - - - 年 - - - - - - 月 - - - - - - 日 - - - -

-
-

+ - - 籍 - - - - - - 贯 - - - - -

-
-

+ + + + + 日 + + + + +

+
+

+ + 籍 + + + + + + 贯 + + - - - - - 省 - - - - - 市 - -

-
+

+
+

-

- - 现住址 - -

-
+ + 省 + + + + + + 市 + +

+
+

-

-   -

-
+

+
+

-

- - 身份证号码 - -

-
+
+

-

-   -

-
+

+
+

-

- - 手机号 - -

-
+
+

-

- - { - - - {phone}} - -

-
+

+
+

-

- - 邮箱 - -

-
+ + {phone}} + +

+
+

-

- - { - - - {email}} - -

-
+

+
+

-

- - 日期 - -

-
+ + {email}} + +

+
+

-

- - {{DATETOSTR(TODAY(), 'YYYY-MM-DD')}} - -

-
+

+
+

-

-   -

-
-

-   -

-

-   -

- -
- - + {{DATETOSTR(TODAY(), 'YYYY-MM-DD')}} + +

+
+

+   +

+
+

+   +

+

+   +

+
+
`; diff --git a/packages/ooxml-viewer/__tests__/simple/__snapshots__/link.test.ts.snap b/packages/ooxml-viewer/__tests__/simple/__snapshots__/link.test.ts.snap index 1d7019cdb..f03f15c4b 100644 --- a/packages/ooxml-viewer/__tests__/simple/__snapshots__/link.test.ts.snap +++ b/packages/ooxml-viewer/__tests__/simple/__snapshots__/link.test.ts.snap @@ -1,142 +1,31 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`link 1`] = ` -
+ a + + + mis + + +

+ + `; diff --git a/packages/ooxml-viewer/__tests__/simple/__snapshots__/list.test.ts.snap b/packages/ooxml-viewer/__tests__/simple/__snapshots__/list.test.ts.snap index 49d5165a5..6ab6f2ac5 100644 --- a/packages/ooxml-viewer/__tests__/simple/__snapshots__/list.test.ts.snap +++ b/packages/ooxml-viewer/__tests__/simple/__snapshots__/list.test.ts.snap @@ -1,325 +1,99 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`list 1`] = ` -
- - -
-
+
+

-

+ + + - - - - - 总体结论 - -

-

+

+

+ + + + - - - - - 整体情况 - -

-

+

+

+ + + + - - - - - 差异对比 - -

-

+

+

+ + + + - - - - - Doc - -

-

+

+

+ + + + - - - - - 总体结论 - -

-

+

+

+ + + + - - - - - Doc文档 - -

-

-   -

-
-
-
-
+ Doc文档 + +

+

+   +

+ + `; diff --git a/packages/ooxml-viewer/__tests__/simple/__snapshots__/noBreakHyphen.test.ts.snap b/packages/ooxml-viewer/__tests__/simple/__snapshots__/noBreakHyphen.test.ts.snap index 36f8d1f7f..20cceb84b 100644 --- a/packages/ooxml-viewer/__tests__/simple/__snapshots__/noBreakHyphen.test.ts.snap +++ b/packages/ooxml-viewer/__tests__/simple/__snapshots__/noBreakHyphen.test.ts.snap @@ -1,157 +1,39 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`noBreakHyphen 1`] = ` -
- -
-
+
+

-

- - Number of the form “999 + Number of the form “999 + + + + – - - - – - - - 99 - + + 99 - - - – - - - 9999”, where - + + + + – -

-
-
-
-
+ + 9999”, where + + +

+ + `; diff --git a/packages/ooxml-viewer/__tests__/simple/__snapshots__/pinyin.test.ts.snap b/packages/ooxml-viewer/__tests__/simple/__snapshots__/pinyin.test.ts.snap index 1a67187c9..ebf8995c6 100644 --- a/packages/ooxml-viewer/__tests__/simple/__snapshots__/pinyin.test.ts.snap +++ b/packages/ooxml-viewer/__tests__/simple/__snapshots__/pinyin.test.ts.snap @@ -1,145 +1,56 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`pinyin 1`] = ` -
- - -
-
+
+

-

- - + + + 拼 + + + - 拼 + pīn - - - - pīn - - - - - - - + + + + + + + + 音 + + + - 音 + yīn - - - - yīn - - - - - -

-
-
-
-
+ + + + +

+ + `; diff --git a/packages/ooxml-viewer/__tests__/simple/__snapshots__/sdt.test.ts.snap b/packages/ooxml-viewer/__tests__/simple/__snapshots__/sdt.test.ts.snap index acb429401..56cb7a2c5 100644 --- a/packages/ooxml-viewer/__tests__/simple/__snapshots__/sdt.test.ts.snap +++ b/packages/ooxml-viewer/__tests__/simple/__snapshots__/sdt.test.ts.snap @@ -1,204 +1,86 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`sdt 1`] = ` -
- -
-
+
+

-

- - start - - - - - - - - (sdt) - - - - - - - - - (embed sdt) - - - - - - - - - (Blank 2010, T. Bogich 2014) - - - - . - - - end - -

-
-
-
-
+ start + + + + + + + + (sdt) + + + + + + + + + (embed sdt) + + + + + + + + + (Blank 2010, T. Bogich 2014) + + + + . + + + end + +

+ + `; diff --git a/packages/ooxml-viewer/__tests__/simple/__snapshots__/svg.test.ts.snap b/packages/ooxml-viewer/__tests__/simple/__snapshots__/svg.test.ts.snap index 23d21fb73..f07f25e38 100644 --- a/packages/ooxml-viewer/__tests__/simple/__snapshots__/svg.test.ts.snap +++ b/packages/ooxml-viewer/__tests__/simple/__snapshots__/svg.test.ts.snap @@ -1,115 +1,26 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`svg 1`] = ` -
- - -
-
+
+

-

- -

- -
- -

-
-
-
-
+ +
+ +

+ + `; diff --git a/packages/ooxml-viewer/__tests__/simple/__snapshots__/sym.test.ts.snap b/packages/ooxml-viewer/__tests__/simple/__snapshots__/sym.test.ts.snap index 6393979a1..408c2e9de 100644 --- a/packages/ooxml-viewer/__tests__/simple/__snapshots__/sym.test.ts.snap +++ b/packages/ooxml-viewer/__tests__/simple/__snapshots__/sym.test.ts.snap @@ -1,118 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`sym 1`] = ` -
- - -
-
+
+

-

- -  - +  -

-
-
-
-
+ +

+ + `; diff --git a/packages/ooxml-viewer/__tests__/simple/__snapshots__/tableborder.test.ts.snap b/packages/ooxml-viewer/__tests__/simple/__snapshots__/tableborder.test.ts.snap index aa226d78f..e585c9d10 100644 --- a/packages/ooxml-viewer/__tests__/simple/__snapshots__/tableborder.test.ts.snap +++ b/packages/ooxml-viewer/__tests__/simple/__snapshots__/tableborder.test.ts.snap @@ -1,307 +1,188 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`tableborder 1`] = ` -
- - - -
-
+
+ -
- - - + - + - + - + - - - + + + - + - + - + - - -
+
+

-

- - 版本 - -

-
+

+
+

-

- - 注释 - -

-
+

+
+

-

- - 作者 - -

-
+

+
+

-

- - 时间 - -

-
+

+
+

-

-   -

-
+ +

-

- - ERP系统架构设计方案 - - - 初稿 - -

-
+ + 初稿 + +

+
+

-

- - nwind - -

-
+

+
+

-

- - 201 - - - 9 - - - - - - - 05 - - - - - - - 28 - -

-
-

-   -

-

-   -

-

-   -

-
-
-
-
+ 201 + + + 9 + + + - + + + 05 + + + - + + + 28 + +

+ + + + +

+   +

+

+   +

+

+   +

+ + `; diff --git a/packages/ooxml-viewer/__tests__/simple/__snapshots__/tablestyle.test.ts.snap b/packages/ooxml-viewer/__tests__/simple/__snapshots__/tablestyle.test.ts.snap index 2d48d7e72..3cd209a92 100644 --- a/packages/ooxml-viewer/__tests__/simple/__snapshots__/tablestyle.test.ts.snap +++ b/packages/ooxml-viewer/__tests__/simple/__snapshots__/tablestyle.test.ts.snap @@ -1,1356 +1,572 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`tablestyle 1`] = ` -
- - - - - - -
-
+
+ -
- - - + - + - + - + - - - + + + - + - + - + - - - + + + - + - + - + + +
+
+

-

-   -

-
+ +

-

-   -

-
+ +

-

-   -

-
+ +

-

-   -

-
+
+

-

-   -

-
+ +

-

-   -

-
+ +

-

-   -

-
+ +

-

-   -

-
+
+

-

-   -

-
+ +

-

-   -

-
+ + -
- - - + - + - + - - - + + + - + - + - - - + + + - + - + - - -
+
+

-

-   -

-
+ +

-

-   -

-
+ +

-

-   -

-
+
+

-

-   -

-
+ +

-

-   -

-
+ +

-

-   -

-
+
+

-

-   -

-
+ +

-

-   -

-
+ +

-

-   -

-
-

-

+
+

+ + +

-

-   -

- - - - + + + + +

-

-   -

- - + + +

-

-   -

- - + + +

-

-   -

- - + + +

-

-   -

- - - - -

-   -

- - - - + + +
+
+

+   +

+ + + + - + - + - + - + - - - + + + - + - + - + - + - - - + + + - + - + - + - + - - -
+

-

-   -

-
+ +

-

-   -

-
+ +

-

-   -

-
+ +

-

-   -

-
+ +

-

-   -

-
+
+

-

-   -

-
+ +

-

-   -

-
+ +

-

-   -

-
+ +

-

-   -

-
+ +

-

-   -

-
+
+

-

-   -

-
+ +

-

-   -

-
+ +

-

-   -

-
+ +

-

-   -

-
+ +

-

-   -

-
-

-   -

- - - - + + +
+
+

+   +

+ + + + - + - + - - - + + + - + - + - - - + + + - + - + - - -
+

-

-   -

-
+ +

-

-   -

-
+ +

-

-   -

-
+
+

-

-   -

-
+ +

-

-   -

-
+ +

-

-   -

-
+
+

-

-   -

-
+ +

-

-   -

-
+ +

-

-   -

-
-

-   -

-

-   -

-
-
-
-
+   +

+ + + + +

+   +

+

+   +

+ + `; diff --git a/packages/ooxml-viewer/__tests__/simple/__snapshots__/textbox-background.test.ts.snap b/packages/ooxml-viewer/__tests__/simple/__snapshots__/textbox-background.test.ts.snap index 478915803..a8f6db096 100644 --- a/packages/ooxml-viewer/__tests__/simple/__snapshots__/textbox-background.test.ts.snap +++ b/packages/ooxml-viewer/__tests__/simple/__snapshots__/textbox-background.test.ts.snap @@ -1,746 +1,32 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`textbox-background 1`] = ` -
- - -
-
+
+

-

- -

- - - -
- -

-
-
-
-
-
-

- -

-
-
-

- -


- -

-
-
-

- -

-
-
-

- -


- -

-
-
-
+ + + + +

+ + `; diff --git a/packages/ooxml-viewer/__tests__/simple/__snapshots__/textbox-behindDoc.test.ts.snap b/packages/ooxml-viewer/__tests__/simple/__snapshots__/textbox-behindDoc.test.ts.snap index b81e23e9b..ea6fd7e56 100644 --- a/packages/ooxml-viewer/__tests__/simple/__snapshots__/textbox-behindDoc.test.ts.snap +++ b/packages/ooxml-viewer/__tests__/simple/__snapshots__/textbox-behindDoc.test.ts.snap @@ -1,160 +1,71 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`textbox-behindDoc 1`] = ` -
- - -
-
+
+

-

- -

- + +

+ - - -

+ - - t - - - ext - -

-
- - +

+
+ + +
-
- - - -

-   -

-
- -

- -
- -
+ + +

+   +

+ +
+

+ + `; diff --git a/packages/ooxml-viewer/__tests__/simple/__snapshots__/textbox-rotation.test.ts.snap b/packages/ooxml-viewer/__tests__/simple/__snapshots__/textbox-rotation.test.ts.snap index a7bd69205..c8c122c69 100644 --- a/packages/ooxml-viewer/__tests__/simple/__snapshots__/textbox-rotation.test.ts.snap +++ b/packages/ooxml-viewer/__tests__/simple/__snapshots__/textbox-rotation.test.ts.snap @@ -1,140 +1,51 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`textbox-rotation 1`] = ` -
- - -
-
+
+

-

- -

- + +

+ - - -

+ - - Text - - - withou - - - t outline - -

-
- -

-
-
-
-
+ withou + + + t outline + +

+ + +

+ + `; diff --git a/packages/ooxml-viewer/__tests__/simple/__snapshots__/textbox-vert.test.ts.snap b/packages/ooxml-viewer/__tests__/simple/__snapshots__/textbox-vert.test.ts.snap index 148e5f966..9cf073c73 100644 --- a/packages/ooxml-viewer/__tests__/simple/__snapshots__/textbox-vert.test.ts.snap +++ b/packages/ooxml-viewer/__tests__/simple/__snapshots__/textbox-vert.test.ts.snap @@ -1,346 +1,253 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`text 1`] = ` -
- - -
-
+
+

-

- -

- + +

+ - - -

+ - - 合格证 - - - a - - - 270 - -

-
- - + + 270 + +

+
+ + +
-
- + +

+ - - -

+ - - 合格证 - - - 2 - - - 70 - -

-
- - + + 70 + +

+
+
+ +
-
- + +

+ - - -

+ - - 合格证 - - - 9 - - - 0 - -

-
- - + + 0 + +

+
+
+ +
-
- + +

+ - - -

- - 合格证 - -

-
- - +

+
+
+ +
-
- + +

+ - - -

- - 合格证 - -

-
- - +

+
+
+ +
-
- + +

+ - - -

- - 合格证 - -

-
- - +

+
+
+ +
-
- + +

+ - - -

- - 合格证 - -

-
- -

- -
- -
+ 合格证 +
+

+ + +

+ + `; diff --git a/packages/ooxml-viewer/__tests__/simple/__snapshots__/textbox.test.ts.snap b/packages/ooxml-viewer/__tests__/simple/__snapshots__/textbox.test.ts.snap index eb9c2245f..b2b2e3ea8 100644 --- a/packages/ooxml-viewer/__tests__/simple/__snapshots__/textbox.test.ts.snap +++ b/packages/ooxml-viewer/__tests__/simple/__snapshots__/textbox.test.ts.snap @@ -1,202 +1,113 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`textbox 1`] = ` -
- - -
-
+
+

-

- -

- + +

+ - - -

+ - - Text box - - - color - -

-
- - +

+
+ + +
-
- + +

+ - - -

- - Text box - -

-
- - +

+
+
+ +
-
- + +

+ - - -

+ - - Text - - - withou - - - t outline - -

-
- -

- -
- -
+ withou +
+ + t outline + +

+ + +

+ + `; diff --git a/packages/ooxml-viewer/__tests__/simple/__snapshots__/tooltip.test.ts.snap b/packages/ooxml-viewer/__tests__/simple/__snapshots__/tooltip.test.ts.snap index ee4961180..d2382f9ea 100644 --- a/packages/ooxml-viewer/__tests__/simple/__snapshots__/tooltip.test.ts.snap +++ b/packages/ooxml-viewer/__tests__/simple/__snapshots__/tooltip.test.ts.snap @@ -1,169 +1,45 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`tooltip 1`] = ` -
- - - -
+ . + +

+ + `; diff --git a/packages/ooxml-viewer/__tests__/simple/__snapshots__/w.test.ts.snap b/packages/ooxml-viewer/__tests__/simple/__snapshots__/w.test.ts.snap index 07ac06218..ac2025f91 100644 --- a/packages/ooxml-viewer/__tests__/simple/__snapshots__/w.test.ts.snap +++ b/packages/ooxml-viewer/__tests__/simple/__snapshots__/w.test.ts.snap @@ -1,104 +1,20 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`w 1`] = ` -
- - -
-
+
+

-

- - 中 - -

-
-
-
-
+ 中 + +

+ + `; diff --git a/packages/ooxml-viewer/__tests__/simple/all-shape1.test.ts b/packages/ooxml-viewer/__tests__/simple/all-shape1.test.ts new file mode 100644 index 000000000..9ea0f723a --- /dev/null +++ b/packages/ooxml-viewer/__tests__/simple/all-shape1.test.ts @@ -0,0 +1,5 @@ +import {snapShotTest} from '../snapShotTest'; + +test('all-shape-1', async () => { + snapShotTest('./docx/simple/all-shape-1.xml'); +}); diff --git a/packages/ooxml-viewer/__tests__/simple/all-shape2.test.ts b/packages/ooxml-viewer/__tests__/simple/all-shape2.test.ts new file mode 100644 index 000000000..ffd8d2cbb --- /dev/null +++ b/packages/ooxml-viewer/__tests__/simple/all-shape2.test.ts @@ -0,0 +1,5 @@ +import {snapShotTest} from '../snapShotTest'; + +test('all-shape-2', async () => { + snapShotTest('./docx/simple/all-shape-2.xml'); +}); diff --git a/packages/ooxml-viewer/__tests__/simple/alt-text.test.ts b/packages/ooxml-viewer/__tests__/simple/alt-text.test.ts new file mode 100644 index 000000000..8ab2e4cd7 --- /dev/null +++ b/packages/ooxml-viewer/__tests__/simple/alt-text.test.ts @@ -0,0 +1,5 @@ +import {snapShotTest} from '../snapShotTest'; + +test('alt-text', async () => { + snapShotTest('./docx/simple/alt-text.xml'); +}); diff --git a/packages/ooxml-viewer/__tests__/simple/break-page.test.ts b/packages/ooxml-viewer/__tests__/simple/break-page.test.ts new file mode 100644 index 000000000..ddbfa233d --- /dev/null +++ b/packages/ooxml-viewer/__tests__/simple/break-page.test.ts @@ -0,0 +1,5 @@ +import {snapShotTest} from '../snapShotTest'; + +test('break-page', async () => { + snapShotTest('./docx/simple/break-page.xml'); +}); diff --git a/packages/ooxml-viewer/__tests__/simple/cr.test.ts b/packages/ooxml-viewer/__tests__/simple/cr.test.ts new file mode 100644 index 000000000..a0e6f160a --- /dev/null +++ b/packages/ooxml-viewer/__tests__/simple/cr.test.ts @@ -0,0 +1,5 @@ +import {snapShotTest} from '../snapShotTest'; + +test('cr', async () => { + snapShotTest('./docx/simple/cr.xml'); +}); diff --git a/packages/ooxml-viewer/__tests__/snapShotTest.ts b/packages/ooxml-viewer/__tests__/snapShotTest.ts index d94e458a2..cd8dc5a6d 100644 --- a/packages/ooxml-viewer/__tests__/snapShotTest.ts +++ b/packages/ooxml-viewer/__tests__/snapShotTest.ts @@ -24,5 +24,6 @@ export async function snapShotTest(filePath: string) { const word = createWord(filePath, {}); await word.render(root); - expect(root).toMatchSnapshot(); + // 样式后续单独测试,不然太多冗余了 + expect(root.getElementsByTagName('article')[0]).toMatchSnapshot(); } diff --git a/packages/ooxml-viewer/examples/app.ts b/packages/ooxml-viewer/examples/app.ts index 4ae6096ea..a870b7dd1 100644 --- a/packages/ooxml-viewer/examples/app.ts +++ b/packages/ooxml-viewer/examples/app.ts @@ -30,7 +30,6 @@ const fileLists = { 'shadow.xml', 'shape-ellipse.xml', 'shape-custom.xml', - 'shape-custom.xml', 'all-shape-1.xml', 'all-shape-2.xml', 'tableborder.xml', @@ -64,47 +63,67 @@ const fileLists = { ] }; +// local storage 里的 key +const pageKey = 'page'; + +const page = !!localStorage.getItem(pageKey) || false; + +if (page) { + // 不知道为啥,大概是 vite 的问题 + setTimeout(() => { + const pageSwitch = document.getElementById( + 'switchPage' + )! as HTMLInputElement; + pageSwitch.checked = true; + }, 0); +} + +(window as any).switchPage = (checked: boolean) => { + if (checked) { + localStorage.setItem(pageKey, 'true'); + } else { + localStorage.removeItem(pageKey); + } + + location.reload(); +}; + /** * 生成左侧文件列表 */ -(function genFileList() { - const fileListElement = document.getElementById('fileList')!; - for (const dirName in fileLists) { - fileListElement.innerHTML += `

${dirName}

`; - const dir = dirName as keyof typeof fileLists; - for (const file of fileLists[dir]) { - const fileName = file.split('.')[0]; - fileListElement.innerHTML += `
${fileName}
`; - } - } - document.querySelectorAll('.file').forEach(file => { - file.addEventListener('click', elm => { - const fileName = (elm.target as Element).getAttribute('data-path')!; - history.pushState({fileName}, fileName, `?file=${fileName}`); - renderDocx(fileName); - }); +const fileListElement = document.getElementById('fileList')!; +for (const dirName in fileLists) { + fileListElement.innerHTML += `

${dirName}

`; + const dir = dirName as keyof typeof fileLists; + for (const file of fileLists[dir]) { + const fileName = file.split('.')[0]; + fileListElement.innerHTML += `
${fileName}
`; + } +} + +document.querySelectorAll('.file').forEach(file => { + file.addEventListener('click', elm => { + const fileName = (elm.target as Element).getAttribute('data-path')!; + history.pushState({fileName}, fileName, `?file=${fileName}`); + renderDocx(fileName); }); -})(); +}); const data = { var: 'amis' }; - -function replaceText(text: string) { - // 将 {{xxx}} 替换成 ${xxx},为啥要这样呢,因为输入 $ 可能会变成两段文本 - text = text.replace(/{{/g, '${').replace(/}}/g, '}'); - return text; -} +const renderOptions = { + debug: true, + page, + zoomFitWidth: true +}; async function renderDocx(fileName: string) { const filePath = `${testDir}/${fileName}`; const file = await (await fetch(filePath)).arrayBuffer(); let word: Word; - const renderOptions = { - debug: true - // replaceText - }; + if (filePath.endsWith('.xml')) { word = new Word(file, renderOptions, new XMLPackageParser()); } else { @@ -158,9 +177,9 @@ function renderWord(file: File) { const data = reader.result as ArrayBuffer; let word; if (file.name.endsWith('.xml')) { - word = new Word(data, {}, new XMLPackageParser()); + word = new Word(data, renderOptions, new XMLPackageParser()); } else { - word = new Word(data, {}); + word = new Word(data, renderOptions); } word.render(viewerElement); }; diff --git a/packages/ooxml-viewer/examples/static/css/app.css b/packages/ooxml-viewer/examples/static/css/app.css index 6f07227dd..4cdd26566 100644 --- a/packages/ooxml-viewer/examples/static/css/app.css +++ b/packages/ooxml-viewer/examples/static/css/app.css @@ -28,7 +28,7 @@ body { .file-list { flex: none; - width: 140px; + width: 100px; padding-top: 4px; padding-left: 4px; white-space: nowrap; @@ -43,3 +43,65 @@ body { .file:hover { color: hsl(217, 71%, 53%); } + +/* The switch - the box around the slider */ +.switch { + position: relative; + display: inline-block; + width: 30px; + height: 18px; +} + +/* Hide default HTML checkbox */ +.switch input { + opacity: 0; + width: 0; + height: 0; +} + +/* The slider */ +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + -webkit-transition: 0.4s; + transition: 0.4s; +} + +.slider:before { + position: absolute; + content: ''; + height: 16px; + width: 16px; + left: 1px; + bottom: 1px; + background-color: white; + -webkit-transition: 0.4s; + transition: 0.4s; +} + +input:checked + .slider { + background-color: #2196f3; +} + +input:focus + .slider { + box-shadow: 0 0 1px #2196f3; +} + +input:checked + .slider:before { + -webkit-transform: translateX(12px); + transform: translateX(12px); +} + +/* Rounded sliders */ +.slider.round { + border-radius: 34px; +} + +.slider.round:before { + border-radius: 50%; +} diff --git a/packages/ooxml-viewer/index.html b/packages/ooxml-viewer/index.html index 65dd575e5..623ba1c95 100644 --- a/packages/ooxml-viewer/index.html +++ b/packages/ooxml-viewer/index.html @@ -9,8 +9,19 @@
- + +

+ page + +

diff --git a/packages/ooxml-viewer/src/Word.ts b/packages/ooxml-viewer/src/Word.ts index e8fcfe76c..ddb1a34a7 100644 --- a/packages/ooxml-viewer/src/Word.ts +++ b/packages/ooxml-viewer/src/Word.ts @@ -42,11 +42,6 @@ export interface WordRenderOptions { */ bulletUseFont: boolean; - /** - * 是否包裹出页面效果 - */ - inWrap: boolean; - /** * 是否忽略文档宽度设置 */ @@ -104,18 +99,71 @@ export interface WordRenderOptions { * 打印等待时间,单位毫秒,可能有的文档有很多图片,如果等待时间太短图片还没加载完,所以加这个配置项可控 */ printWaitTime?: number; + + /** + * 是否使用分页的方式来渲染内容,使用这种方式还原度更高,但不支持打印功能 + * 设置后会自动将 ignoreHeight 和 ignoreWidth 设置为 false + */ + page?: boolean; + + /** + * 每页之间的间距 + */ + pageMarginBottom?: number; + + /** + * 页面背景色 + */ + pageBackground?: string; + + /** + * 是否显示页面阴影,只有在 page 为 true 的时候才生效 + */ + pageShadow?: boolean; + + /** + * 显示页面包裹效果,只有在 page 为 true 的时候才生效 + */ + pageWrap?: boolean; + + /** + * 页面包裹宽度 + */ + pageWrapPadding?: number; + + /** + * 页面包裹背景色 + */ + pageWrapBackground?: string; + + /** + * 缩放比例,取值 0-1 之间 + */ + zoom?: number; + + /** + * 自适应宽度,如果设置了 zoom,那么 zoom 优先级更高,这个设置只在 ignoreWidth 为 false 的时候生效 + */ + zoomFitWidth?: boolean; } const defaultRenderOptions: WordRenderOptions = { classPrefix: 'docx-viewer', - inWrap: true, + page: false, + pageWrap: true, bulletUseFont: true, ignoreHeight: true, ignoreWidth: false, minLineHeight: 1.0, enableVar: false, debug: false, + pageWrapPadding: 20, + pageMarginBottom: 20, + pageShadow: true, + pageBackground: '#FFFFFF', + pageWrapBackground: '#ECECEC', printWaitTime: 100, + zoomFitWidth: false, data: {}, evalVar: t => { return t; @@ -227,9 +275,19 @@ export default class Word { this.id = Word.globalId++; this.parser = parser; this.renderOptions = {...defaultRenderOptions, ...renderOptions}; + if (this.renderOptions.page) { + this.renderOptions.ignoreHeight = false; + this.renderOptions.ignoreWidth = false; + } } inited = false; + + /** + * 分页标记,如果为 true,那么在渲染的时候会强制分页 + */ + breakPage = false; + /** * 初始化一些公共资源,比如 */ @@ -578,7 +636,8 @@ export default class Word { document.body.appendChild(iframe); iframe.contentDocument?.write('
'); await this.render( - iframe.contentDocument?.getElementById('print') as HTMLElement + iframe.contentDocument?.getElementById('print') as HTMLElement, + {page: false, pageWrap: false} ); setTimeout(function () { iframe.focus(); @@ -592,10 +651,14 @@ export default class Word { * 渲染文档入口 * * @param root 渲染的根节点 + * @param renderOptionsOverride 临时覆盖某些渲选项 */ - async render(root: HTMLElement) { + async render( + root: HTMLElement, + renderOptionsOverride: Partial = {} + ) { this.init(); - const renderOptions = this.renderOptions; + const renderOptions = {...this.renderOptions, ...renderOptionsOverride}; const isDebug = renderOptions.debug; isDebug && console.log('init', this); @@ -614,10 +677,13 @@ export default class Word { const document = WDocument.fromXML(this, documentData); isDebug && console.log('document', document); - const documentElement = renderDocument(this, document); + + const documentElement = renderDocument(root, this, document, renderOptions); root.classList.add(this.getClassPrefix()); - if (renderOptions.inWrap) { + if (renderOptions.page && renderOptions.pageWrap) { root.classList.add(this.wrapClassName); + root.style.padding = `${renderOptions.pageWrapPadding || 0}px`; + root.style.background = renderOptions.pageWrapBackground || '#ECECEC'; } appendChild(root, renderStyle(this)); diff --git a/packages/ooxml-viewer/src/openxml/word/Body.ts b/packages/ooxml-viewer/src/openxml/word/Body.ts index 3122739cc..e270f2a47 100644 --- a/packages/ooxml-viewer/src/openxml/word/Body.ts +++ b/packages/ooxml-viewer/src/openxml/word/Body.ts @@ -62,6 +62,10 @@ export class Body { } } + // 过滤掉没内容的 section,一般是最后一个 + body.sections = body.sections.filter( + section => section.children.length > 0 + ); return body; } } diff --git a/packages/ooxml-viewer/src/openxml/word/Run.ts b/packages/ooxml-viewer/src/openxml/word/Run.ts index baf2a8fd0..a4b264bf0 100644 --- a/packages/ooxml-viewer/src/openxml/word/Run.ts +++ b/packages/ooxml-viewer/src/openxml/word/Run.ts @@ -104,7 +104,9 @@ export class Run { break; case 'w:lastRenderedPageBreak': - // 目前也不支持分页显示 + const pageBreak = new Break(); + pageBreak.type = 'page'; + run.addChild(pageBreak); break; case 'w:pict': diff --git a/packages/ooxml-viewer/src/openxml/word/Section.ts b/packages/ooxml-viewer/src/openxml/word/Section.ts index febd9255a..fbc509ad2 100644 --- a/packages/ooxml-viewer/src/openxml/word/Section.ts +++ b/packages/ooxml-viewer/src/openxml/word/Section.ts @@ -11,6 +11,7 @@ import {Hyperlink} from './Hyperlink'; import {Paragraph} from './Paragraph'; import {Table} from './Table'; import {Body} from './Body'; +import {getAttrNumber} from '../../OpenXML'; export type PageSize = { width: string; @@ -28,9 +29,12 @@ export type PageMargin = { gutter?: string; }; +/** + * 列设置,其实这里支持 + */ export interface Column { - width?: number; - space?: number; + num?: number; + space?: string; } export type SectionChild = Paragraph | Table | Hyperlink; @@ -38,6 +42,7 @@ export type SectionChild = Paragraph | Table | Hyperlink; export interface SectionPr { pageSize?: PageSize; pageMargin?: PageMargin; + cols?: Column; } export class Section { @@ -78,7 +83,6 @@ export class Section { const headerType = child.getAttribute('w:type'); const headerId = child.getAttribute('r:id'); // 目前只支持 default 且只支持背景图 - // TODO: 这里 rel 不对,需要用 "/word/_rels/header1.xml.rels,后面得想想怎么改 if (headerType === 'default' && headerId) { const headerRel = word.getDocumentRels(headerId); if (headerRel) { @@ -92,6 +96,17 @@ export class Section { } break; + case 'w:cols': + const cols: Column = {}; + const num = getAttrNumber(child, 'w:num', 1); + cols.num = num; + const space = parseSize(child, 'w:space'); + if (space) { + cols.space = space; + } + properties.cols = cols; + break; + default: break; } diff --git a/packages/ooxml-viewer/src/render/renderBody.ts b/packages/ooxml-viewer/src/render/renderBody.ts index b8296b0dd..bc670ed06 100644 --- a/packages/ooxml-viewer/src/render/renderBody.ts +++ b/packages/ooxml-viewer/src/render/renderBody.ts @@ -2,34 +2,246 @@ * 渲染 body 节点 */ -import {createElement, appendChild} from '../util/dom'; -import Word from '../Word'; +import {createElement, appendChild, removeChild} from '../util/dom'; +import Word, {WordRenderOptions} from '../Word'; import {Body} from '../openxml/word/Body'; import {Paragraph} from '../openxml/word/Paragraph'; import {Table} from '../openxml/word/Table'; -import {Hyperlink} from '../openxml/word/Hyperlink'; import renderParagraph from './renderParagraph'; import {renderSection} from './renderSection'; import renderTable from './renderTable'; +import {Section} from '../openxml/word/Section'; -export default function renderBody( +/** + * 判断是否需要创建一个新 section,包括强制分页和超出了 section 的高宽或宽度 + */ +function createNewSection( word: Word, - parent: HTMLElement, - body: Body + sectionEnd: SectionEnd, + child: HTMLElement ) { - for (const section of body.sections) { - const sectionEl = renderSection(word, section); - appendChild(parent, sectionEl); + // 支持插入分页符 + if (word.breakPage) { + word.breakPage = false; + return true; + } + const childBound = child.getBoundingClientRect(); + return ( + childBound.top + childBound.height > sectionEnd.bottom || + // 注意这里没有 + childBound.width,因为 width 一般都是 100% 导致容易超出 + childBound.left > sectionEnd.right + ); +} + +/** + * 添加到 section 里,如果超出了就创建一个新的 section + */ +function appendToSection( + word: Word, + renderOptions: WordRenderOptions, + bodyEl: HTMLElement, + sectionEl: HTMLElement, + sectionEnd: SectionEnd, + section: Section, + child: HTMLElement +) { + // 首先尝试写入 + appendChild(sectionEl, child); + + // 如果超出了就新建一个 section + if (createNewSection(word, sectionEnd, child)) { + const newChild = child.cloneNode(true) as HTMLElement; + removeChild(sectionEl, child); + let newSectionEl = renderSection(word, section, renderOptions); + appendChild(bodyEl, newSectionEl); + appendChild(newSectionEl, newChild); + sectionEnd = getSectionEnd(section, newSectionEl); + return {sectionEl: newSectionEl, sectionEnd}; + } + + return {sectionEl, sectionEnd}; +} + +type SectionEnd = { + bottom: number; + right: number; +}; + +/** + * 获取 section 结束的位置,也就是最后能放下子元素的位置 + */ +function getSectionEnd(section: Section, sectionEl: HTMLElement): SectionEnd { + const sectionBound = sectionEl.getBoundingClientRect(); + const pageMargin = section.properties.pageMargin; + let bottom = sectionBound.top + sectionBound.height; + if (pageMargin?.bottom) { + bottom = bottom - parseInt(pageMargin.bottom.replace('px', ''), 10); + } + let right = sectionBound.left + sectionBound.width; + if (pageMargin?.right) { + right = right - parseInt(pageMargin.right.replace('px', ''), 10); + } + return {bottom, right}; +} + +/** + * 获取缩放比例 + */ +function getTransform( + rootWidth: number, + section: Section, + renderOptions: WordRenderOptions +) { + const props = section.properties; + const pageSize = props.pageSize; + if (renderOptions.zoomFitWidth && !renderOptions.ignoreWidth) { + const pageWidth = pageSize?.width; + if (rootWidth && pageWidth) { + let pageWidthNum = parseInt(pageWidth.replace('px', ''), 10); + + if (props.pageMargin) { + const pageMargin = props.pageMargin; + pageWidthNum += pageMargin.left + ? parseInt(pageMargin.left.replace('px', ''), 10) + : 0; + pageWidthNum += pageMargin.right + ? parseInt(pageMargin.right.replace('px', ''), 10) + : 0; + } + const zoomWidth = rootWidth / pageWidthNum; + + return zoomWidth; + } + } + return 1; +} + +/** + * 分页渲染 + * @param isLastSection 是否是最后一节 + */ +function renderSectionInPage( + word: Word, + bodyEl: HTMLElement, + renderOptions: WordRenderOptions, + sectionEl: HTMLElement, + section: Section, + isLastSection: boolean +) { + // 如果不 setTimeout 取到的位置信息不对 + setTimeout(() => { + let sectionEnd = getSectionEnd(section, sectionEl); for (const child of section.children) { if (child instanceof Paragraph) { const p = renderParagraph(word, child); - appendChild(sectionEl, p); + const appendResult = appendToSection( + word, + renderOptions, + + bodyEl, + sectionEl, + sectionEnd, + section, + p + ); + sectionEl = appendResult.sectionEl; + sectionEnd = appendResult.sectionEnd; } else if (child instanceof Table) { - appendChild(sectionEl, renderTable(word, child)); + const table = renderTable(word, child); + const appendResult = appendToSection( + word, + renderOptions, + + bodyEl, + sectionEl, + sectionEnd, + section, + table + ); + sectionEl = appendResult.sectionEl; + sectionEnd = appendResult.sectionEnd; } else { console.warn('unknown child', child); } } - appendChild(parent, sectionEl); - } + + if (isLastSection) { + sectionEl.style.marginBottom = '0'; + } + }, 0); +} + +/** + * 渲染文档主体 + */ +export default function renderBody( + root: HTMLElement, + word: Word, + bodyEl: HTMLElement, + body: Body, + renderOptions: WordRenderOptions +) { + const page = renderOptions.page || false; + + const rootWidth = + root.getBoundingClientRect().width - + (renderOptions.pageWrapPadding || 0) * 2; + + const zooms: number[] = []; + + let index = 0; + const sections = body.sections; + const sectionLength = sections.length; + // 用于最后一个 section 不加 margin-bottom + let isLastSection = false; + for (const section of sections) { + zooms.push(getTransform(rootWidth, section, renderOptions)); + let sectionEl = renderSection(word, section, renderOptions); + appendChild(bodyEl, sectionEl); + + index = index + 1; + if (index === sectionLength) { + isLastSection = true; + } + if (page) { + renderSectionInPage( + word, + bodyEl, + + renderOptions, + sectionEl, + section, + isLastSection + ); + } else { + for (const child of section.children) { + if (child instanceof Paragraph) { + const p = renderParagraph(word, child); + appendChild(sectionEl, p); + } else if (child instanceof Table) { + const table = renderTable(word, child); + appendChild(sectionEl, table); + } else { + console.warn('unknown child', child); + } + } + } + } + + setTimeout(() => { + if (renderOptions.zoom) { + // 固定缩放 + bodyEl.style.transformOrigin = '0 0'; + bodyEl.style.transform = `scale(${renderOptions.zoom})`; + } else if ( + renderOptions.page && + renderOptions.zoomFitWidth && + !renderOptions.ignoreWidth + ) { + // 自适应宽度的缩放 + const minZoom = Math.min(...zooms); + bodyEl.style.transformOrigin = '0 0'; + bodyEl.style.transform = `scale(${minZoom})`; + } + }, 0); } diff --git a/packages/ooxml-viewer/src/render/renderBr.ts b/packages/ooxml-viewer/src/render/renderBr.ts index 4dfe802ca..07a3a8958 100644 --- a/packages/ooxml-viewer/src/render/renderBr.ts +++ b/packages/ooxml-viewer/src/render/renderBr.ts @@ -1,3 +1,4 @@ +import Word from '../Word'; import {Break} from '../openxml/word/Break'; import {createElement} from '../util/dom'; @@ -6,7 +7,10 @@ import {createElement} from '../util/dom'; * 其实还有 column 和 page,但目前还没实现分页渲染,所以目前先简单处理,后续再看看如何处理 * @returns 生成的 dom 结构 */ -export function renderBr(brak: Break) { +export function renderBr(word: Word, brak: Break) { + if (brak.type === 'page') { + word.breakPage = true; + } const br = createElement('br'); return br; } diff --git a/packages/ooxml-viewer/src/render/renderDocument.ts b/packages/ooxml-viewer/src/render/renderDocument.ts index 7bdfafd1a..93b9ecf39 100644 --- a/packages/ooxml-viewer/src/render/renderDocument.ts +++ b/packages/ooxml-viewer/src/render/renderDocument.ts @@ -1,5 +1,5 @@ import {createElement} from '../util/dom'; -import Word from '../Word'; +import Word, {WordRenderOptions} from '../Word'; import renderBody from './renderBody'; import {WDocument} from '../openxml/word/WDocument'; @@ -8,8 +8,13 @@ import {WDocument} from '../openxml/word/WDocument'; * 渲染 document 主要入口 * http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/document.html */ -export default function renderDocument(word: Word, document: WDocument) { +export default function renderDocument( + root: HTMLElement, + word: Word, + document: WDocument, + renderOptions: WordRenderOptions +) { const doc = createElement('article'); - renderBody(word, doc, document.body); + renderBody(root, word, doc, document.body, renderOptions); return doc; } diff --git a/packages/ooxml-viewer/src/render/renderRun.ts b/packages/ooxml-viewer/src/render/renderRun.ts index cc9e2ab25..f26ca5d2a 100644 --- a/packages/ooxml-viewer/src/render/renderRun.ts +++ b/packages/ooxml-viewer/src/render/renderRun.ts @@ -102,7 +102,7 @@ export default function renderRun( renderText(newSpan, word, child.text, paragraph); appendChild(span, newSpan); } else if (child instanceof Break) { - const br = renderBr(child); + const br = renderBr(word, child); appendChild(span, br); } else if (child instanceof Drawing) { appendChild(span, renderDrawing(word, child)); diff --git a/packages/ooxml-viewer/src/render/renderSection.ts b/packages/ooxml-viewer/src/render/renderSection.ts index 1e9ad7054..84d145e34 100644 --- a/packages/ooxml-viewer/src/render/renderSection.ts +++ b/packages/ooxml-viewer/src/render/renderSection.ts @@ -1,29 +1,49 @@ import {Section} from '../openxml/word/Section'; import {createElement} from '../util/dom'; -import Word from '../Word'; +import Word, {WordRenderOptions} from '../Word'; /** * 渲染「节」,在 word 中每个节都可以有自己的页边距和页面大小设置,但目前其实并没有实现分页展现,等后续再考虑 */ -export function renderSection(word: Word, section: Section) { +export function renderSection( + word: Word, + section: Section, + renderOptions: WordRenderOptions +) { const sectionEl = createElement('section') as HTMLElement; // 用于后续绝对定位 sectionEl.style.position = 'relative'; + + if (renderOptions.page) { + sectionEl.style.overflow = 'hidden'; + if (renderOptions.pageMarginBottom) { + sectionEl.style.marginBottom = renderOptions.pageMarginBottom + 'px'; + } + + if (renderOptions.pageShadow) { + sectionEl.style.boxShadow = '0 0 8px rgba(0, 0, 0, 0.5)'; + } + + if (renderOptions.pageBackground) { + sectionEl.style.background = renderOptions.pageBackground; + } + } + const props = section.properties; const pageSize = props.pageSize; if (pageSize) { - if (!word.renderOptions.ignoreWidth) { + if (!renderOptions.ignoreWidth) { sectionEl.style.width = pageSize.width; } - if (!word.renderOptions.ignoreHeight) { + if (!renderOptions.ignoreHeight) { sectionEl.style.height = pageSize.height; } } // 强制控制 padding - if (word.renderOptions.padding) { - sectionEl.style.padding = word.renderOptions.padding; + if (renderOptions.padding) { + sectionEl.style.padding = renderOptions.padding; } else { const pageMargin = props.pageMargin; if (pageMargin) { @@ -34,5 +54,14 @@ export function renderSection(word: Word, section: Section) { } } + if (props.cols) { + if (props.cols.num && props.cols.num > 1) { + sectionEl.style.columnCount = '' + props.cols.num; + if (props.cols.space) { + sectionEl.style.columnGap = props.cols.space; + } + } + } + return sectionEl; } diff --git a/packages/ooxml-viewer/src/render/renderStyle.ts b/packages/ooxml-viewer/src/render/renderStyle.ts index ae55746d0..11b97339d 100644 --- a/packages/ooxml-viewer/src/render/renderStyle.ts +++ b/packages/ooxml-viewer/src/render/renderStyle.ts @@ -27,13 +27,7 @@ function generateDefaultStyle(word: Word) { const classPrefix = word.getClassPrefix(); return ` - .${word.wrapClassName} { - } - - .${word.wrapClassName} > article > section { - background: white; - } /** docDefaults **/ diff --git a/packages/ooxml-viewer/src/util/dom.ts b/packages/ooxml-viewer/src/util/dom.ts index 69f9b1bd4..891a36201 100644 --- a/packages/ooxml-viewer/src/util/dom.ts +++ b/packages/ooxml-viewer/src/util/dom.ts @@ -47,13 +47,6 @@ export function createSVGElement(tagName: string): SVGElement { return document.createElementNS('http://www.w3.org/2000/svg', tagName); } -/** - * 创建片段 - */ -export function createDocumentFragment() { - return document.createDocumentFragment(); -} - /** * 添加子节点,会做一些判断避免报错 */ @@ -63,6 +56,15 @@ export function appendChild(parent: HTMLElement, child?: Node | null): void { } } +/** + * 删除子节点,会做一些判断避免报错 + */ +export function removeChild(parent: HTMLElement, child?: Node | null): void { + if (parent && child) { + parent.removeChild(child); + } +} + /** * 添加注释节点 */