mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-29 10:38:16 +08:00
feat: office-view 支持内嵌字体;数学公式;初步支持 textbox;修复 highlight 和加粗渲染不正确问题 (#6459)
* 初步支持 textbox * 开始实现内嵌字体 * 支持内嵌字体 * 避免 style 导致可能的 xss * 支持公式渲染 * 修复 highlight 和加粗渲染不正确问题 * 删掉不用的文件 * 修复编译报错
This commit is contained in:
parent
8314ba4909
commit
3941b21e7e
@ -5,3 +5,4 @@ npm/
|
||||
.git/
|
||||
.github/
|
||||
.vscode/
|
||||
.rollup.cache
|
||||
|
@ -18,14 +18,10 @@ order: 23
|
||||
- 列表
|
||||
- 注音
|
||||
- 链接
|
||||
- 文本框
|
||||
- 数学公式(依赖 MathML,需要比较新的浏览器,或者试试 [polyfill](https://github.com/w3c/mathml-polyfills))
|
||||
|
||||
不支持的功能:
|
||||
|
||||
- 分页符
|
||||
- 形状
|
||||
- 艺术字
|
||||
- 域
|
||||
- 对象
|
||||
不支持的功能:分页符、形状、艺术字、域、对象、目录
|
||||
|
||||
## 基本用法
|
||||
|
||||
@ -34,7 +30,8 @@ order: 23
|
||||
"type": "office-viewer",
|
||||
"src": "/examples/static/simple.docx",
|
||||
"wordOptions": {
|
||||
"padding": "8px"
|
||||
"padding": "8px",
|
||||
"ignoreWidth": false
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -50,25 +47,37 @@ order: 23
|
||||
"type": "office-viewer",
|
||||
"wordOptions": {
|
||||
"padding": "8px",
|
||||
"classPrefix": "docx"
|
||||
"ignoreWidth": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ----------------- | --------- | ------------- | ------------------------------------------ |
|
||||
| ----------------- | --------- | ------------- | ---------------------------------------------------------- |
|
||||
| classPrefix | `string` | 'docx-viewer' | 渲染的 class 类前缀 |
|
||||
| ignoreWidth | `boolean` | false | 忽略文档里的宽度设置,用于更好嵌入到页面里,但会减低还原度 |
|
||||
| padding | `string` | | 设置页面间距,忽略文档中的设置 |
|
||||
| bulletUseFont | `boolean` | true | 列表使用字体渲染,请参考下面的乱码说明 |
|
||||
| fontMapping | `object` | | 字体映射,是个键值对,用于替换文档中的字体 |
|
||||
| forceLineHeight | `string` | | 设置段落行高,忽略文档中的设置 |
|
||||
| padding | `string` | | 设置页面间距,忽略文档中的设置 |
|
||||
| enableReplaceText | `boolean` | true | 是否开启变量替换功能 |
|
||||
|
||||
### 关于渲染效果差异
|
||||
|
||||
目前的实现难以保证和本地 Word 渲染完全一致,会遇到以下问题:
|
||||
|
||||
1. 字体大小不一致
|
||||
1. 单元格宽度不一致,表格完全依赖浏览器渲染
|
||||
1. 分页显示,目前的渲染不会分页,而是内容有多长就有多高
|
||||
1. 分栏显示,这个是因为没有分页导致的,不限制高度没法分栏
|
||||
|
||||
如果追求完整效果打印,目前只能使用下载文件的方式用本地 Word 进行打印。
|
||||
|
||||
## 列表符号出现乱码问题
|
||||
|
||||
默认情况下列表左侧的符号使用字体渲染,这样能做到最接近 Word 渲染效果,但如果用户的系统中没有这些字体就会显示乱码,为了解决这个问题需要手动在 amis 渲染的页面里导入对应的字体,比如
|
||||
|
||||
```
|
||||
```html
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: Wingdings;
|
||||
|
@ -6,8 +6,13 @@ docx 渲染器,原理是将 docx 里的 xml 格式转成 html
|
||||
|
||||
相对于 Canvas 渲染,这个实现方案比较简单,最终页面也可以很方便复制,但无法保证和原始 docx 文件展现一致,因为有部分功能难以在 HTML 中实现,比如图文环绕效果。
|
||||
|
||||
## 还不支持的功能
|
||||
## 已知不支持的功能
|
||||
|
||||
- 分页符
|
||||
- 形状
|
||||
- 艺术字
|
||||
- 域
|
||||
- 对象
|
||||
- wmf,需要使用 https://github.com/SheetJS/js-wmf
|
||||
|
||||
## 参考资料
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {createWord} from './EmptyWord';
|
||||
import {mergeRun} from '../src/util/mergeRun';
|
||||
import {parseXML, buildXML} from '../src/util/xml';
|
||||
import {parseXML} from '../src/util/xml';
|
||||
|
||||
test('proofErr', async () => {
|
||||
const xmlDoc = parseXML(
|
||||
|
3
packages/office-viewer/__tests__/docx/dedocx/README.md
Normal file
3
packages/office-viewer/__tests__/docx/dedocx/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
来自 https://github.com/science-periodicals/dedocx
|
||||
|
||||
似乎很关注公式及引用相关的
|
BIN
packages/office-viewer/__tests__/docx/dedocx/broken/corrupt-zip.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/broken/corrupt-zip.docx
Executable file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/dedocx/broken/doc.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/broken/doc.docx
Executable file
Binary file not shown.
0
packages/office-viewer/src/openxml/word/AlternateContent.ts → packages/office-viewer/__tests__/docx/dedocx/broken/empty.docx
Normal file → Executable file
0
packages/office-viewer/src/openxml/word/AlternateContent.ts → packages/office-viewer/__tests__/docx/dedocx/broken/empty.docx
Normal file → Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/broken/excel.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/broken/excel.docx
Executable file
Binary file not shown.
28
packages/office-viewer/__tests__/docx/dedocx/broken/html.docx
Executable file
28
packages/office-viewer/__tests__/docx/dedocx/broken/html.docx
Executable file
File diff suppressed because one or more lines are too long
BIN
packages/office-viewer/__tests__/docx/dedocx/broken/malformed-document-xml.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/broken/malformed-document-xml.docx
Executable file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/dedocx/broken/no-document-xml.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/broken/no-document-xml.docx
Executable file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/dedocx/broken/png.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/broken/png.docx
Executable file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
BIN
packages/office-viewer/__tests__/docx/dedocx/broken/stupid name with spaces.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/broken/stupid name with spaces.docx
Executable file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/dedocx/citation-container-author.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/citation-container-author.docx
Executable file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/dedocx/code.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/code.docx
Executable file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/dedocx/custom-list.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/custom-list.docx
Executable file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/dedocx/doc-props.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/doc-props.docx
Executable file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/dedocx/equations.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/equations.docx
Executable file
Binary file not shown.
1
packages/office-viewer/__tests__/docx/dedocx/fake-test-docx.ds3.docx
Executable file
1
packages/office-viewer/__tests__/docx/dedocx/fake-test-docx.ds3.docx
Executable file
@ -0,0 +1 @@
|
||||
text inline
|
111
packages/office-viewer/__tests__/docx/dedocx/fields/fields-bib.xml
Executable file
111
packages/office-viewer/__tests__/docx/dedocx/fields/fields-bib.xml
Executable file
@ -0,0 +1,111 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 wp14">
|
||||
<w:body>
|
||||
<w:p w:rsidR="00A97433" w:rsidRPr="003741F5" w:rsidRDefault="00A97433" w:rsidP="00A97433">
|
||||
<w:pPr>
|
||||
<w:pStyle w:val="EndNoteBibliography"/>
|
||||
</w:pPr>
|
||||
<w:r w:rsidRPr="00E63F36">
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman"/>
|
||||
</w:rPr>
|
||||
<w:t xml:space="preserve">US is about 400,000 and over two million </w:t>
|
||||
</w:r>
|
||||
<w:r w:rsidR="007714DF" w:rsidRPr="00E63F36">
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman"/>
|
||||
</w:rPr>
|
||||
<w:t xml:space="preserve">people </w:t>
|
||||
</w:r>
|
||||
<w:r w:rsidR="00F21877" w:rsidRPr="00E63F36">
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman"/>
|
||||
</w:rPr>
|
||||
<w:t xml:space="preserve">are </w:t>
|
||||
</w:r>
|
||||
<w:r w:rsidRPr="00E63F36">
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman"/>
|
||||
</w:rPr>
|
||||
<w:fldChar w:fldCharType="begin"/>
|
||||
</w:r>
|
||||
<w:r w:rsidRPr="00E63F36">
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman"/>
|
||||
</w:rPr>
|
||||
<w:instrText xml:space="preserve"> ADDIN EN.REFLIST </w:instrText>
|
||||
</w:r>
|
||||
<w:r w:rsidRPr="00E63F36">
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman"/>
|
||||
</w:rPr>
|
||||
<w:fldChar w:fldCharType="separate"/>
|
||||
</w:r>
|
||||
<w:bookmarkStart w:id="0" w:name="_ENREF_1"/>
|
||||
<w:r w:rsidRPr="003741F5">
|
||||
<w:t>1.</w:t>
|
||||
</w:r>
|
||||
<w:r w:rsidRPr="003741F5">
|
||||
<w:tab/>
|
||||
<w:t>Orton SM, Herrera BM, Yee IM, et al. Sex ratio of multiple sclerosis in Canada: a longitudinal study. Lancet Neurol 2006;5:932-6.</w:t>
|
||||
</w:r>
|
||||
<w:bookmarkEnd w:id="0"/>
|
||||
</w:p>
|
||||
<w:p w:rsidR="00A97433" w:rsidRPr="003741F5" w:rsidRDefault="00A97433" w:rsidP="00A97433">
|
||||
<w:pPr>
|
||||
<w:pStyle w:val="EndNoteBibliography"/>
|
||||
</w:pPr>
|
||||
<w:bookmarkStart w:id="1" w:name="_ENREF_2"/>
|
||||
<w:r w:rsidRPr="003741F5">
|
||||
<w:t>2.</w:t>
|
||||
</w:r>
|
||||
<w:r w:rsidRPr="003741F5">
|
||||
<w:tab/>
|
||||
<w:t>Kister I, Chamot E, Salter AR, Cutter GR, Bacon TE, Herbert J. Disability in multiple sclerosis: a reference for patients and clinicians. Neurology 2013;80:1018-24.</w:t>
|
||||
</w:r>
|
||||
<w:bookmarkEnd w:id="1"/>
|
||||
</w:p>
|
||||
<w:p w:rsidR="00A97433" w:rsidRPr="003741F5" w:rsidRDefault="00A97433" w:rsidP="00A97433">
|
||||
<w:pPr>
|
||||
<w:pStyle w:val="EndNoteBibliography"/>
|
||||
</w:pPr>
|
||||
<w:bookmarkStart w:id="2" w:name="_ENREF_3"/>
|
||||
<w:r w:rsidRPr="003741F5">
|
||||
<w:t>3.</w:t>
|
||||
</w:r>
|
||||
<w:r w:rsidRPr="003741F5">
|
||||
<w:tab/>
|
||||
<w:t>Mayr WT, Pittock SJ, McClelland RL, Jorgensen NW, Noseworthy JH, Rodriguez M. Incidence and prevalence of multiple sclerosis in Olmsted County, Minnesota, 1985-2000. Neurology 2003;61:1373-7.</w:t>
|
||||
</w:r>
|
||||
<w:bookmarkEnd w:id="2"/>
|
||||
</w:p>
|
||||
<w:p w:rsidR="00A97433" w:rsidRPr="00E63F36" w:rsidRDefault="00A97433" w:rsidP="00A97433">
|
||||
<w:pPr>
|
||||
<w:pStyle w:val="Default"/>
|
||||
<w:spacing w:line="480" w:lineRule="auto"/>
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:cs="Times New Roman"/>
|
||||
<w:color w:val="auto"/>
|
||||
</w:rPr>
|
||||
</w:pPr>
|
||||
<w:r w:rsidRPr="00E63F36">
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman"/>
|
||||
</w:rPr>
|
||||
<w:fldChar w:fldCharType="end"/>
|
||||
</w:r>
|
||||
<w:r w:rsidR="000E1EAF" w:rsidRPr="00E63F36">
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman"/>
|
||||
</w:rPr>
|
||||
<w:t xml:space="preserve"> expected </w:t>
|
||||
</w:r>
|
||||
<w:r w:rsidR="00B22C62" w:rsidRPr="00E63F36">
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman"/>
|
||||
</w:rPr>
|
||||
<w:t>increase in the number of cases in future</w:t>
|
||||
</w:r>
|
||||
</w:p>
|
||||
</w:body>
|
||||
</w:document>
|
23
packages/office-viewer/__tests__/docx/dedocx/fields/fields-single.xml
Executable file
23
packages/office-viewer/__tests__/docx/dedocx/fields/fields-single.xml
Executable file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 wp14">
|
||||
<w:body>
|
||||
<w:p w:rsidR="00D26E0C" w:rsidRPr="00D26E0C" w:rsidRDefault="00D26E0C" w:rsidP="00D26E0C">
|
||||
<w:r w:rsidR="00C76D57">
|
||||
<w:fldChar w:fldCharType="begin"/>
|
||||
</w:r>
|
||||
<w:r w:rsidR="00C76D57">
|
||||
<w:instrText>HYPERLINK \l "_ENREF_95" \o "Kappos, 2010 #67"</w:instrText>
|
||||
</w:r>
|
||||
<w:r w:rsidR="00C76D57">
|
||||
<w:fldChar w:fldCharType="end"/>
|
||||
</w:r>
|
||||
<w:r w:rsidR="004C1AB8" w:rsidRPr="00E63F36">
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:cs="Times New Roman"/>
|
||||
<w:color w:val="auto"/>
|
||||
</w:rPr>
|
||||
<w:t xml:space="preserve"> </w:t>
|
||||
</w:r>
|
||||
</w:p>
|
||||
</w:body>
|
||||
</w:document>
|
101
packages/office-viewer/__tests__/docx/dedocx/fields/fields-three.xml
Executable file
101
packages/office-viewer/__tests__/docx/dedocx/fields/fields-three.xml
Executable file
@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 wp14">
|
||||
<w:body>
|
||||
<w:p w:rsidR="00D26E0C" w:rsidRPr="00D26E0C" w:rsidRDefault="00D26E0C" w:rsidP="00D26E0C">
|
||||
<w:proofErr w:type="gramEnd"/>
|
||||
<w:r w:rsidR="00C76D57">
|
||||
<w:fldChar w:fldCharType="begin"/>
|
||||
</w:r>
|
||||
<w:r w:rsidR="00C76D57">
|
||||
<w:instrText>HYPERLINK \l "_ENREF_95" \o "Kappos, 2010 #67"</w:instrText>
|
||||
</w:r>
|
||||
<w:r w:rsidR="00C76D57">
|
||||
<w:fldChar w:fldCharType="separate"/>
|
||||
</w:r>
|
||||
<w:r w:rsidR="00C76D57" w:rsidRPr="00E63F36">
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:cs="Times New Roman"/>
|
||||
<w:color w:val="auto"/>
|
||||
</w:rPr>
|
||||
<w:fldChar w:fldCharType="begin">
|
||||
<w:fldData xml:space="preserve">XXX</w:fldData>
|
||||
</w:fldChar>
|
||||
</w:r>
|
||||
<w:r w:rsidR="003741F5">
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:cs="Times New Roman"/>
|
||||
<w:color w:val="auto"/>
|
||||
</w:rPr>
|
||||
<w:instrText xml:space="preserve"> ADDIN EN.CITE </w:instrText>
|
||||
</w:r>
|
||||
<w:r w:rsidR="00C76D57">
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:cs="Times New Roman"/>
|
||||
<w:color w:val="auto"/>
|
||||
</w:rPr>
|
||||
<w:fldChar w:fldCharType="begin">
|
||||
<w:fldData xml:space="preserve">XXX</w:fldData>
|
||||
</w:fldChar>
|
||||
</w:r>
|
||||
<w:r w:rsidR="003741F5">
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:cs="Times New Roman"/>
|
||||
<w:color w:val="auto"/>
|
||||
</w:rPr>
|
||||
<w:instrText xml:space="preserve"> ADDIN EN.CITE.DATA </w:instrText>
|
||||
</w:r>
|
||||
<w:r w:rsidR="00C76D57">
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:cs="Times New Roman"/>
|
||||
<w:color w:val="auto"/>
|
||||
</w:rPr>
|
||||
</w:r>
|
||||
<w:r w:rsidR="00C76D57">
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:cs="Times New Roman"/>
|
||||
<w:color w:val="auto"/>
|
||||
</w:rPr>
|
||||
<w:fldChar w:fldCharType="end"/>
|
||||
</w:r>
|
||||
<w:r w:rsidR="00C76D57" w:rsidRPr="00E63F36">
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:cs="Times New Roman"/>
|
||||
<w:color w:val="auto"/>
|
||||
</w:rPr>
|
||||
</w:r>
|
||||
<w:r w:rsidR="00C76D57" w:rsidRPr="00E63F36">
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:cs="Times New Roman"/>
|
||||
<w:color w:val="auto"/>
|
||||
</w:rPr>
|
||||
<w:fldChar w:fldCharType="separate"/>
|
||||
</w:r>
|
||||
<w:r w:rsidR="003741F5" w:rsidRPr="00FA3B8B">
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:cs="Times New Roman"/>
|
||||
<w:noProof/>
|
||||
<w:color w:val="auto"/>
|
||||
<w:vertAlign w:val="superscript"/>
|
||||
</w:rPr>
|
||||
<w:t>95-98</w:t>
|
||||
</w:r>
|
||||
<w:r w:rsidR="00C76D57" w:rsidRPr="00E63F36">
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:cs="Times New Roman"/>
|
||||
<w:color w:val="auto"/>
|
||||
</w:rPr>
|
||||
<w:fldChar w:fldCharType="end"/>
|
||||
</w:r>
|
||||
<w:r w:rsidR="00C76D57">
|
||||
<w:fldChar w:fldCharType="end"/>
|
||||
</w:r>
|
||||
<w:r w:rsidR="004C1AB8" w:rsidRPr="00E63F36">
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:cs="Times New Roman"/>
|
||||
<w:color w:val="auto"/>
|
||||
</w:rPr>
|
||||
<w:t xml:space="preserve"> </w:t>
|
||||
</w:r>
|
||||
</w:p>
|
||||
</w:body>
|
||||
</w:document>
|
38
packages/office-viewer/__tests__/docx/dedocx/fields/fields-trivial-instr.xml
Executable file
38
packages/office-viewer/__tests__/docx/dedocx/fields/fields-trivial-instr.xml
Executable file
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 wp14">
|
||||
<w:body>
|
||||
<w:p w:rsidR="00D26E0C" w:rsidRPr="00D26E0C" w:rsidRDefault="00D26E0C" w:rsidP="00D26E0C">
|
||||
<w:r w:rsidR="00C76D57">
|
||||
<w:fldChar w:fldCharType="begin"/>
|
||||
</w:r>
|
||||
<w:r w:rsidR="00C76D57">
|
||||
<w:instrText>HYPERLINK \l "_ENREF_95" \o "Kappos, 2010 #67"</w:instrText>
|
||||
</w:r>
|
||||
<w:r w:rsidR="00C76D57">
|
||||
<w:fldChar w:fldCharType="begin"/>
|
||||
</w:r>
|
||||
<w:r w:rsidR="00C76D57">
|
||||
<w:instrText>HYPERLINK \l "_ENREF_2" \o "Kappos, 2010 #67"</w:instrText>
|
||||
</w:r>
|
||||
<w:r w:rsidR="00C76D57">
|
||||
<w:fldChar w:fldCharType="separate"/>
|
||||
</w:r>
|
||||
<w:r w:rsidR="00C76D57">
|
||||
<w:fldChar w:fldCharType="end"/>
|
||||
</w:r>
|
||||
<w:r w:rsidR="00C76D57">
|
||||
<w:fldChar w:fldCharType="separate"/>
|
||||
</w:r>
|
||||
<w:r w:rsidR="00C76D57">
|
||||
<w:fldChar w:fldCharType="end"/>
|
||||
</w:r>
|
||||
<w:r w:rsidR="004C1AB8" w:rsidRPr="00E63F36">
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:cs="Times New Roman"/>
|
||||
<w:color w:val="auto"/>
|
||||
</w:rPr>
|
||||
<w:t xml:space="preserve"> </w:t>
|
||||
</w:r>
|
||||
</w:p>
|
||||
</w:body>
|
||||
</w:document>
|
38
packages/office-viewer/__tests__/docx/dedocx/fields/fields-trivial.xml
Executable file
38
packages/office-viewer/__tests__/docx/dedocx/fields/fields-trivial.xml
Executable file
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 wp14">
|
||||
<w:body>
|
||||
<w:p w:rsidR="00D26E0C" w:rsidRPr="00D26E0C" w:rsidRDefault="00D26E0C" w:rsidP="00D26E0C">
|
||||
<w:r w:rsidR="00C76D57">
|
||||
<w:fldChar w:fldCharType="begin"/>
|
||||
</w:r>
|
||||
<w:r w:rsidR="00C76D57">
|
||||
<w:instrText>HYPERLINK \l "_ENREF_95" \o "Kappos, 2010 #67"</w:instrText>
|
||||
</w:r>
|
||||
<w:r w:rsidR="00C76D57">
|
||||
<w:fldChar w:fldCharType="separate"/>
|
||||
</w:r>
|
||||
<w:r w:rsidR="00C76D57">
|
||||
<w:fldChar w:fldCharType="begin"/>
|
||||
</w:r>
|
||||
<w:r w:rsidR="00C76D57">
|
||||
<w:instrText>HYPERLINK \l "_ENREF_2" \o "Kappos, 2010 #67"</w:instrText>
|
||||
</w:r>
|
||||
<w:r w:rsidR="00C76D57">
|
||||
<w:fldChar w:fldCharType="separate"/>
|
||||
</w:r>
|
||||
<w:r w:rsidR="00C76D57">
|
||||
<w:fldChar w:fldCharType="end"/>
|
||||
</w:r>
|
||||
<w:r w:rsidR="00C76D57">
|
||||
<w:fldChar w:fldCharType="end"/>
|
||||
</w:r>
|
||||
<w:r w:rsidR="004C1AB8" w:rsidRPr="00E63F36">
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:cs="Times New Roman"/>
|
||||
<w:color w:val="auto"/>
|
||||
</w:rPr>
|
||||
<w:t xml:space="preserve"> </w:t>
|
||||
</w:r>
|
||||
</w:p>
|
||||
</w:body>
|
||||
</w:document>
|
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/dedocx/footnotes-repeated.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/footnotes-repeated.docx
Executable file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/dedocx/footnotes.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/footnotes.docx
Executable file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/dedocx/full-tables.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/full-tables.docx
Executable file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/dedocx/inline-and-block-image-styles.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/inline-and-block-image-styles.docx
Executable file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/dedocx/list-of-citations.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/list-of-citations.docx
Executable file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/dedocx/list-test.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/list-test.docx
Executable file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/dedocx/lists.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/lists.docx
Executable file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/dedocx/minimum-test-document.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/minimum-test-document.docx
Executable file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/dedocx/picture-grid.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/picture-grid.docx
Executable file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/dedocx/playground.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/playground.docx
Executable file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/dedocx/runs.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/runs.docx
Executable file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/dedocx/section-links.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/section-links.docx
Executable file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/dedocx/symbols.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/symbols.docx
Executable file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/dedocx/tables-with-equations.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/tables-with-equations.docx
Executable file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/dedocx/tables.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/tables.docx
Executable file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/dedocx/textboxes.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/textboxes.docx
Executable file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/dedocx/tooltip.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/tooltip.docx
Executable file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/dedocx/uncaptioned-images.ds3.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/uncaptioned-images.ds3.docx
Executable file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/dedocx/undefined-bookmarks.docx
Executable file
BIN
packages/office-viewer/__tests__/docx/dedocx/undefined-bookmarks.docx
Executable file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/simple/bold.docx
Normal file
BIN
packages/office-viewer/__tests__/docx/simple/bold.docx
Normal file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/simple/em.docx
Normal file
BIN
packages/office-viewer/__tests__/docx/simple/em.docx
Normal file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/simple/embed-font.docx
Normal file
BIN
packages/office-viewer/__tests__/docx/simple/embed-font.docx
Normal file
Binary file not shown.
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/simple/highlight.docx
Normal file
BIN
packages/office-viewer/__tests__/docx/simple/highlight.docx
Normal file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/simple/image.docx
Normal file
BIN
packages/office-viewer/__tests__/docx/simple/image.docx
Normal file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/simple/link.docx
Normal file
BIN
packages/office-viewer/__tests__/docx/simple/link.docx
Normal file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/simple/math.docx
Normal file
BIN
packages/office-viewer/__tests__/docx/simple/math.docx
Normal file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/simple/shape-ellipse.docx
Normal file
BIN
packages/office-viewer/__tests__/docx/simple/shape-ellipse.docx
Normal file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/simple/textbox.docx
Normal file
BIN
packages/office-viewer/__tests__/docx/simple/textbox.docx
Normal file
Binary file not shown.
BIN
packages/office-viewer/__tests__/docx/simple/w.docx
Normal file
BIN
packages/office-viewer/__tests__/docx/simple/w.docx
Normal file
Binary file not shown.
@ -11,11 +11,17 @@ const testDir = '__tests__/docx';
|
||||
|
||||
const fileLists = {
|
||||
simple: [
|
||||
'helloworld.docx',
|
||||
'image.docx',
|
||||
'list.docx',
|
||||
'tableborder.docx',
|
||||
'tablestyle.docx',
|
||||
'pinyin.docx'
|
||||
'pinyin.docx',
|
||||
'em.docx',
|
||||
'w.docx',
|
||||
'textbox.docx',
|
||||
'embed-font.docx',
|
||||
'math.docx',
|
||||
'highlight.docx'
|
||||
],
|
||||
docx4j: [
|
||||
'ArialUnicodeMS.docx',
|
||||
|
@ -6,12 +6,12 @@ body {
|
||||
|
||||
@font-face {
|
||||
font-family: Wingdings;
|
||||
src: url(/examples/static/font/wingding.ttf);
|
||||
src: url(/examples/static/font/wingding.ttf) format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Symbol;
|
||||
src: url(/examples/static/font/symbol.ttf);
|
||||
src: url(/examples/static/font/symbol.ttf) format('truetype');
|
||||
}
|
||||
|
||||
/** 参考 bulma 的命名 */
|
||||
|
@ -49,6 +49,7 @@
|
||||
"@types/jest": "^28.1.0",
|
||||
"amis-formula": "^2.7.2",
|
||||
"jest": "^29.0.3",
|
||||
"fast-xml-parser": "4.1.3",
|
||||
"ts-jest": "^29.0.2",
|
||||
"ts-loader": "^9.2.3",
|
||||
"ts-node": "^10.4.0",
|
||||
|
@ -63,6 +63,47 @@ export function getAttrBoolean(
|
||||
return normalizeBoolean(element.getAttribute(attr), defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取属性值,转成数字
|
||||
*
|
||||
* @param attr 属性名
|
||||
* @param defaultValue 默认值
|
||||
* @returns 解析后的数字
|
||||
*/
|
||||
export function getAttrNumber(
|
||||
element: Element,
|
||||
attr: string,
|
||||
defaultValue: number = 0
|
||||
) {
|
||||
const value = element.getAttribute(attr);
|
||||
if (value) {
|
||||
return parseInt(value, 10);
|
||||
} else {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取百分比值,没测过
|
||||
* http://webapp.docx4java.org/OnlineDemo/ecma376/DrawingML/ST_Percentage.html
|
||||
* https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_ST_Percentage_topic_ID0EY3XNB.html#topic_ID0EY3XNB
|
||||
*
|
||||
* @returns 0-1 之间的小数
|
||||
*/
|
||||
export function getAttrPercentage(element: Element, attr: string) {
|
||||
const value = element.getAttribute(attr);
|
||||
|
||||
if (value) {
|
||||
const num = parseInt(value, 10);
|
||||
return num / 100000;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取属性的 hex 值
|
||||
*/
|
||||
export function getValHex(element: Element) {
|
||||
return parseInt(getVal(element) || '0', 16);
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import {FontTable} from './openxml/word/FontTable';
|
||||
/**
|
||||
* 总入口,它将包括所有 word 文档信息,后续渲染的时候依赖它来获取关联信息
|
||||
*/
|
||||
@ -10,7 +11,7 @@ import {parseTheme, Theme} from './openxml/Theme';
|
||||
import renderDocument from './render/renderDocument';
|
||||
import {blobToDataURL, downloadBlob} from './util/blob';
|
||||
import {Numbering} from './openxml/word/numbering/Numbering';
|
||||
import {appendChild} from './util/dom';
|
||||
import {appendChild, createElement} from './util/dom';
|
||||
import {renderStyle} from './render/renderStyle';
|
||||
import {mergeRun} from './util/mergeRun';
|
||||
import {WDocument} from './openxml/word/WDocument';
|
||||
@ -19,6 +20,8 @@ import {updateVariableText} from './render/renderRun';
|
||||
import ZipPackageParser from './package/ZipPackageParser';
|
||||
import {buildXML} from './util/xml';
|
||||
import {Paragraph} from './openxml/word/Paragraph';
|
||||
import {deobfuscate} from './openxml/word/Font';
|
||||
import {renderFont} from './render/renderFont';
|
||||
|
||||
/**
|
||||
* 渲染配置
|
||||
@ -29,9 +32,6 @@ export interface WordRenderOptions {
|
||||
*/
|
||||
classPrefix: string;
|
||||
|
||||
/** 图片是否使用 data url */
|
||||
imageDataURL: boolean;
|
||||
|
||||
/**
|
||||
* 列表使用字体渲染,需要自行引入 Windings 字体
|
||||
*/
|
||||
@ -86,18 +86,23 @@ export interface WordRenderOptions {
|
||||
* 强制行高,设置之后所有文本都使用这个行高,可以优化排版效果
|
||||
*/
|
||||
forceLineHeight?: string;
|
||||
|
||||
/**
|
||||
* 打印等待时间,单位毫秒,可能有的文档有很多图片,如果等待时间太短图片还没加载完,所以加这个配置项可控
|
||||
*/
|
||||
printWaitTime?: number;
|
||||
}
|
||||
|
||||
const defaultRenderOptions: WordRenderOptions = {
|
||||
imageDataURL: false,
|
||||
classPrefix: 'docx-viewer',
|
||||
inWrap: true,
|
||||
bulletUseFont: true,
|
||||
ignoreHeight: true,
|
||||
ignoreWidth: true,
|
||||
ignoreWidth: false,
|
||||
minLineHeight: 1.0,
|
||||
enableVar: false,
|
||||
debug: false
|
||||
debug: false,
|
||||
printWaitTime: 100
|
||||
};
|
||||
|
||||
export default class Word {
|
||||
@ -138,8 +143,21 @@ export default class Word {
|
||||
|
||||
renderOptions: WordRenderOptions;
|
||||
|
||||
/**
|
||||
* 全局关系表
|
||||
*/
|
||||
relationships: Record<string, Relationship>;
|
||||
|
||||
/**
|
||||
* 文档关系表
|
||||
*/
|
||||
documentRels: Record<string, Relationship>;
|
||||
|
||||
/**
|
||||
* 字体关系表
|
||||
*/
|
||||
fontTableRels: Record<string, Relationship>;
|
||||
|
||||
/**
|
||||
* 样式名映射,因为自定义样式名有可能不符合 css 命名规范,因此实际使用这个名字
|
||||
*/
|
||||
@ -150,6 +168,11 @@ export default class Word {
|
||||
*/
|
||||
styleIdNum: number = 0;
|
||||
|
||||
/**
|
||||
* 内置字体标
|
||||
*/
|
||||
fontTable?: FontTable;
|
||||
|
||||
/**
|
||||
* 渲染根节点
|
||||
*/
|
||||
@ -191,10 +214,12 @@ export default class Word {
|
||||
|
||||
// 这个必须在最前面,因为后面很多依赖它来查找文件的
|
||||
this.initContentType();
|
||||
// relation 需要排第二
|
||||
this.initRelation();
|
||||
|
||||
this.initTheme();
|
||||
this.initFontTable();
|
||||
this.initStyle();
|
||||
this.initRelation();
|
||||
this.initNumbering();
|
||||
|
||||
this.inited = true;
|
||||
@ -223,6 +248,20 @@ export default class Word {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析字体表
|
||||
*/
|
||||
initFontTable() {
|
||||
for (const override of this.conentTypes.overrides) {
|
||||
if (override.partName.startsWith('/word/fontTable.xml')) {
|
||||
this.fontTable = FontTable.fromXML(
|
||||
this,
|
||||
this.parser.getXML('/word/fontTable.xml')
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析关系
|
||||
*/
|
||||
@ -232,6 +271,8 @@ export default class Word {
|
||||
rels = parseRelationships(this.parser.getXML('/_rels/.rels'), 'root');
|
||||
}
|
||||
|
||||
this.relationships = rels;
|
||||
|
||||
let documentRels = {};
|
||||
if (this.parser.fileExists('/word/_rels/document.xml.rels')) {
|
||||
documentRels = parseRelationships(
|
||||
@ -239,7 +280,16 @@ export default class Word {
|
||||
'word'
|
||||
);
|
||||
}
|
||||
this.relationships = {...rels, ...documentRels};
|
||||
this.documentRels = documentRels;
|
||||
|
||||
let fontTableRels = {};
|
||||
if (this.parser.fileExists('/word/_rels/fontTable.xml.rels')) {
|
||||
fontTableRels = parseRelationships(
|
||||
this.parser.getXML('/word/_rels/fontTable.xml.rels'),
|
||||
'word'
|
||||
);
|
||||
}
|
||||
this.fontTableRels = fontTableRels;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -263,15 +313,35 @@ export default class Word {
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 id 获取关系
|
||||
* 获取全局关系
|
||||
*/
|
||||
getRelationship(id?: string) {
|
||||
if (id) {
|
||||
if (id && this.relationships) {
|
||||
return this.relationships[id];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文档对应的关系
|
||||
*/
|
||||
getDocumentRels(id?: string) {
|
||||
if (id && this.documentRels) {
|
||||
return this.documentRels[id];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字体对应的关系
|
||||
*/
|
||||
getFontTableRels(id?: string) {
|
||||
if (id && this.fontTableRels) {
|
||||
return this.fontTableRels[id];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 进行文本替换
|
||||
*/
|
||||
@ -288,7 +358,7 @@ export default class Word {
|
||||
/**
|
||||
* 加载图片
|
||||
*/
|
||||
loadImage(relation: Relationship): Promise<string> | null {
|
||||
loadImage(relation: Relationship): string | null {
|
||||
let path = relation.target;
|
||||
if (relation.part === 'word') {
|
||||
path = 'word/' + path;
|
||||
@ -296,13 +366,26 @@ export default class Word {
|
||||
|
||||
const data = this.parser.getFileByType(path, 'blob');
|
||||
if (data) {
|
||||
if (this.renderOptions.imageDataURL) {
|
||||
return blobToDataURL(data as Blob);
|
||||
} else {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
resolve(URL.createObjectURL(data as Blob));
|
||||
});
|
||||
return URL.createObjectURL(data as Blob);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
loadFont(rId: string, key: string) {
|
||||
const relation = this.getFontTableRels(rId);
|
||||
if (!relation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let path = relation.target;
|
||||
if (relation.part === 'word') {
|
||||
path = 'word/' + path;
|
||||
}
|
||||
|
||||
const data = this.parser.getFileByType(path, 'uint8array') as Uint8Array;
|
||||
if (data) {
|
||||
return URL.createObjectURL(new Blob([deobfuscate(data, key)]));
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -344,9 +427,9 @@ export default class Word {
|
||||
/**
|
||||
* 添加新样式,主要用于表格的单元格样式
|
||||
*/
|
||||
appendStyle(style: string) {
|
||||
const styleElement = document.createElement('style');
|
||||
styleElement.innerHTML = style;
|
||||
appendStyle(style: string = '') {
|
||||
const styleElement = createElement('style');
|
||||
styleElement.textContent = style;
|
||||
this.rootElement.appendChild(styleElement);
|
||||
}
|
||||
|
||||
@ -377,10 +460,18 @@ export default class Word {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取主题色,目前基于 css 变量实现,方便动态修改
|
||||
* 获取主题色
|
||||
*/
|
||||
getThemeColor(name: string) {
|
||||
return `var(--docx-${this.id}-theme-${name}-color)`;
|
||||
if (this.themes && this.themes.length > 0) {
|
||||
const theme = this.themes[0];
|
||||
const color = theme.themeElements?.clrScheme?.colors?.[name];
|
||||
if (color) {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
addClass(element: HTMLElement, className: string) {
|
||||
@ -436,7 +527,7 @@ export default class Word {
|
||||
iframe.focus();
|
||||
iframe.contentWindow?.print();
|
||||
iframe.parentNode?.removeChild(iframe);
|
||||
}, 100);
|
||||
}, this.renderOptions.printWaitTime || 100); // 需要等一下图片渲染
|
||||
window.focus();
|
||||
}
|
||||
|
||||
@ -472,6 +563,7 @@ export default class Word {
|
||||
}
|
||||
|
||||
appendChild(root, renderStyle(this));
|
||||
appendChild(root, renderFont(this.fontTable));
|
||||
appendChild(root, documentElement);
|
||||
}
|
||||
}
|
||||
|
@ -2,14 +2,17 @@
|
||||
* 解析共享样式 Style
|
||||
*/
|
||||
|
||||
import {parseTcPr} from '../parse/parseTcPr';
|
||||
import {getVal} from '../OpenXML';
|
||||
import Word from '../Word';
|
||||
import {ST_StyleType, ST_TblStyleOverrideType} from './Types';
|
||||
import {Paragraph, ParagraphPr} from './word/Paragraph';
|
||||
import {Run, RunPr} from './word/Run';
|
||||
import {Table, TablePr} from './word/Table';
|
||||
import {Tc, TcPr} from './word/table/Tc';
|
||||
import {TablePr} from './word/Table';
|
||||
import {TcPr} from './word/table/Tc';
|
||||
import {Tr, TrPr} from './word/table/Tr';
|
||||
import {parseTablePr} from '../parse/parseTablePr';
|
||||
import {parseTrPr} from '../parse/parseTrPr';
|
||||
|
||||
export interface CSSStyle {
|
||||
[key: string]: string;
|
||||
@ -94,15 +97,15 @@ function parseTblStylePr(word: Word, element: Element) {
|
||||
break;
|
||||
|
||||
case 'w:tblPr':
|
||||
style.tblPr = Table.parseTablePr(word, child);
|
||||
style.tblPr = parseTablePr(word, child);
|
||||
break;
|
||||
|
||||
case 'w:tcPr':
|
||||
style.tcPr = Tc.parseTcPr(word, child);
|
||||
style.tcPr = parseTcPr(word, child);
|
||||
break;
|
||||
|
||||
case 'w:trPr':
|
||||
style.trPr = Tr.parseTrPr(word, child);
|
||||
style.trPr = parseTrPr(word, child);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
* 主要参考 14.2.7 Theme Part
|
||||
*/
|
||||
|
||||
import {getAttrNumber, getAttrPercentage, getVal} from '../OpenXML';
|
||||
|
||||
// http://webapp.docx4java.org/OnlineDemo/ecma376/DrawingML/clrScheme.html
|
||||
class ClrScheme {
|
||||
name?: string;
|
||||
@ -21,8 +23,26 @@ function parseClrScheme(doc: Element | null): ClrScheme {
|
||||
const clrName = clr.nodeName.replace('a:', '');
|
||||
if (clrName === 'sysClr') {
|
||||
scheme.colors[colorName] = clr.getAttribute('lastClr') || '';
|
||||
} else if (clrName === 'srgbClr') {
|
||||
scheme.colors[colorName] = '#' + clr.getAttribute('val') || '';
|
||||
} else if (clrName === 'scrgbClr') {
|
||||
// https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_scrgbClr_topic_ID0EOOPJB.html
|
||||
// 没测过
|
||||
const r = getAttrPercentage(child, 'r') * 256;
|
||||
const g = getAttrPercentage(child, 'g') * 256;
|
||||
const b = getAttrPercentage(child, 'b') * 256;
|
||||
scheme.colors[colorName] = `rgb(${r}, ${g}, ${b})`;
|
||||
} else if (clrName === 'hslClr') {
|
||||
// https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_hslClr_topic_ID0EQ5FJB.html
|
||||
// 没测过
|
||||
const h = getAttrNumber(child, 'hue') / 60000;
|
||||
const s = getAttrPercentage(child, 'sat') * 100;
|
||||
const l = getAttrPercentage(child, 'lum') * 100;
|
||||
scheme.colors[colorName] = `hsl(${h}, ${s}%, ${l}%)`;
|
||||
} else if (clrName === 'prstClr') {
|
||||
scheme.colors[colorName] = getVal(child);
|
||||
} else {
|
||||
scheme.colors[colorName] = clr.getAttribute('val') || '';
|
||||
console.error('unknown clr name', clrName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
2068
packages/office-viewer/src/openxml/math/OMML2MML.XSL
Executable file
2068
packages/office-viewer/src/openxml/math/OMML2MML.XSL
Executable file
File diff suppressed because it is too large
Load Diff
10
packages/office-viewer/src/openxml/math/OMath.ts
Normal file
10
packages/office-viewer/src/openxml/math/OMath.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import Word from '../../Word';
|
||||
|
||||
export class OMath {
|
||||
element: Element;
|
||||
static fromXML(word: Word, element: Element): OMath {
|
||||
const math = new OMath();
|
||||
math.element = element;
|
||||
return math;
|
||||
}
|
||||
}
|
1
packages/office-viewer/src/openxml/math/README.md
Normal file
1
packages/office-viewer/src/openxml/math/README.md
Normal file
@ -0,0 +1 @@
|
||||
OMML2MML.XSL 文件来自安装 Windows 版本 Word 后 `C:\Program Files\Microsoft Office\root\Office16` 目录
|
11
packages/office-viewer/src/openxml/math/convertOOML.ts
Normal file
11
packages/office-viewer/src/openxml/math/convertOOML.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import {xsl} from './xsl';
|
||||
/**
|
||||
* 将 officel 的公式 xml 转成 mathml
|
||||
*/
|
||||
|
||||
export function convertOOXML(element: Element) {
|
||||
const xsltProcessor = new XSLTProcessor();
|
||||
xsltProcessor.importStylesheet(xsl);
|
||||
const fragment = xsltProcessor.transformToFragment(element, document);
|
||||
return fragment;
|
||||
}
|
2077
packages/office-viewer/src/openxml/math/xsl.ts
Normal file
2077
packages/office-viewer/src/openxml/math/xsl.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
||||
import {parseTable} from '../../parse/parseTable';
|
||||
import Word from '../../Word';
|
||||
import {Paragraph} from './Paragraph';
|
||||
import {Section, SectionChild, SectionPr} from './Section';
|
||||
import {Table} from './Table';
|
||||
|
||||
/**
|
||||
* body 类型定义
|
||||
@ -48,7 +48,7 @@ export class Body {
|
||||
break;
|
||||
|
||||
case 'w:tbl':
|
||||
const table = Table.fromXML(word, child);
|
||||
const table = parseTable(word, child);
|
||||
body.addChild(table);
|
||||
break;
|
||||
|
||||
|
@ -9,7 +9,7 @@ export class Break {
|
||||
/**
|
||||
* 目前也只支持这种
|
||||
*/
|
||||
type: ST_BrType = ST_BrType.textWrapping;
|
||||
type: ST_BrType = 'textWrapping';
|
||||
clear?: ST_BrClear;
|
||||
|
||||
static fromXML(word: Word, element: Element): Break {
|
||||
|
78
packages/office-viewer/src/openxml/word/Font.ts
Normal file
78
packages/office-viewer/src/openxml/word/Font.ts
Normal file
@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 内嵌字体
|
||||
* http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/Font%20Embedding.html
|
||||
*/
|
||||
|
||||
import {getVal} from '../../OpenXML';
|
||||
import Word from '../../Word';
|
||||
|
||||
/**
|
||||
* 这是来自 docxjs 里的代码,参考了规范 17.8.1 里的算法
|
||||
*/
|
||||
export function deobfuscate(data: Uint8Array, guidKey: string): Uint8Array {
|
||||
const len = 16;
|
||||
const trimmed = guidKey.replace(/{|}|-/g, '');
|
||||
const numbers = new Array(len);
|
||||
|
||||
for (let i = 0; i < len; i++)
|
||||
numbers[len - i - 1] = parseInt(trimmed.substr(i * 2, 2), 16);
|
||||
|
||||
for (let i = 0; i < 32; i++) data[i] = data[i] ^ numbers[i % len];
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export class Font {
|
||||
name: string;
|
||||
family: string;
|
||||
altName?: string;
|
||||
// 字体文件地址
|
||||
url?: string;
|
||||
|
||||
static fromXML(word: Word, element: Element): Font {
|
||||
const font = new Font();
|
||||
font.name = element.getAttribute('w:name') || '';
|
||||
|
||||
for (const child of element.children) {
|
||||
const tagName = child.tagName;
|
||||
switch (tagName) {
|
||||
case 'w:family':
|
||||
font.family = getVal(child);
|
||||
break;
|
||||
|
||||
case 'w:altName':
|
||||
font.altName = getVal(child);
|
||||
break;
|
||||
|
||||
case 'w:panose1':
|
||||
// 不知道是啥
|
||||
// http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/panose1.html
|
||||
break;
|
||||
|
||||
case 'w:charset':
|
||||
case 'w:sig':
|
||||
case 'w:pitch':
|
||||
// 用不上
|
||||
break;
|
||||
|
||||
case 'w:embedRegular':
|
||||
case 'w:embedBold':
|
||||
case 'w:embedItalic':
|
||||
case 'w:embedBoldItalic':
|
||||
case 'w:embedSystemFonts':
|
||||
case 'w:embedTrueTypeFonts':
|
||||
const id = child.getAttribute('r:id') || '';
|
||||
const fontKey = child.getAttribute('w:fontKey') || '';
|
||||
const fontURL = word.loadFont(id, fontKey);
|
||||
if (fontURL) {
|
||||
font.url = fontURL;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn('parse Font: Unknown key', tagName, child);
|
||||
}
|
||||
}
|
||||
return font;
|
||||
}
|
||||
}
|
20
packages/office-viewer/src/openxml/word/FontTable.ts
Normal file
20
packages/office-viewer/src/openxml/word/FontTable.ts
Normal file
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 解析和渲染字体
|
||||
*/
|
||||
|
||||
import Word from '../../Word';
|
||||
import {Font} from './Font';
|
||||
|
||||
export class FontTable {
|
||||
fonts: Font[] = [];
|
||||
|
||||
static fromXML(word: Word, doc: Document): FontTable {
|
||||
const fonts = Array.from(doc.getElementsByTagName('w:font'));
|
||||
const fontTable = new FontTable();
|
||||
for (const child of fonts) {
|
||||
const font = Font.fromXML(word, child);
|
||||
fontTable.fonts.push(font);
|
||||
}
|
||||
return fontTable;
|
||||
}
|
||||
}
|
@ -20,7 +20,7 @@ export class Hyperlink {
|
||||
|
||||
const rId = element.getAttribute('r:id');
|
||||
if (rId) {
|
||||
const rel = word.getRelationship(rId);
|
||||
const rel = word.getDocumentRels(rId);
|
||||
if (rel) {
|
||||
hyperlink.relation = rel;
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import {Run, RunPr} from './Run';
|
||||
import {Tab} from './Tab';
|
||||
import {SmartTag} from './SmartTag';
|
||||
import {FldSimple} from './FldSimple';
|
||||
import {OMath} from '../math/OMath';
|
||||
|
||||
/**
|
||||
* 这里简化了很多,如果能用 CSS 表示就直接用 CSS 表示
|
||||
@ -33,7 +34,8 @@ export type ParagraphChild =
|
||||
| BookmarkStart
|
||||
| Hyperlink
|
||||
| SmartTag
|
||||
| FldSimple;
|
||||
| FldSimple
|
||||
| OMath;
|
||||
// | SymbolRun
|
||||
// | PageBreak
|
||||
// | ColumnBreak
|
||||
@ -142,6 +144,10 @@ export class Paragraph {
|
||||
paragraph.fldSimples.push(FldSimple.fromXML(word, child));
|
||||
break;
|
||||
|
||||
case 'm:oMathPara':
|
||||
paragraph.addChild(OMath.fromXML(word, child));
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn('parse Paragraph: Unknown key', tagName, child);
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
import {Relationship} from '../../parse/parseRelationship';
|
||||
import Word from '../../Word';
|
||||
|
||||
export class Pict {
|
||||
imagedata?: Relationship;
|
||||
src?: string | null;
|
||||
|
||||
static fromXML(word: Word, element: Element): Pict | null {
|
||||
const pict = new Pict();
|
||||
@ -11,9 +10,9 @@ export class Pict {
|
||||
|
||||
if (imagedataElement) {
|
||||
const rId = imagedataElement.getAttribute('r:id') || '';
|
||||
const rel = word.getRelationship(rId);
|
||||
const rel = word.getDocumentRels(rId);
|
||||
if (rel) {
|
||||
pict.imagedata = rel;
|
||||
pict.src = word.loadImage(rel);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,6 +103,13 @@ export class Run {
|
||||
run.addChild(Sym.parseXML(child));
|
||||
break;
|
||||
|
||||
case 'mc:AlternateContent':
|
||||
const drawingChild = child.getElementsByTagName('w:drawing').item(0);
|
||||
if (drawingChild) {
|
||||
run.addChild(Drawing.fromXML(word, drawingChild));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn('parse Run: Unknown key', tagName, child);
|
||||
}
|
||||
|
@ -5,23 +5,10 @@
|
||||
*/
|
||||
|
||||
import {parseSize} from '../../parse/parseSize';
|
||||
import {
|
||||
ST_ChapterSep,
|
||||
ST_DocGrid,
|
||||
ST_LineNumberRestart,
|
||||
ST_NumberFormat,
|
||||
ST_PageBorderDisplay,
|
||||
ST_PageBorderOffset,
|
||||
ST_PageBorderZOrder,
|
||||
ST_PageOrientation,
|
||||
ST_SectionMark,
|
||||
ST_TextDirection
|
||||
} from '../Types';
|
||||
import {BorderOptions} from './Border';
|
||||
import {ST_PageOrientation} from '../Types';
|
||||
import {Hyperlink} from './Hyperlink';
|
||||
import {Paragraph} from './Paragraph';
|
||||
import {Table} from './Table';
|
||||
import {VerticalAlign} from './VerticalAlign';
|
||||
|
||||
export type PageSize = {
|
||||
width: string;
|
||||
|
@ -2,35 +2,24 @@
|
||||
* http://officeopenxml.com/WPtable.php
|
||||
*/
|
||||
|
||||
import {
|
||||
getAttrBoolean,
|
||||
getVal,
|
||||
getValBoolean,
|
||||
getValHex,
|
||||
getValNumber
|
||||
} from '../../OpenXML';
|
||||
import {parseBorder, parseBorders} from '../../parse/parseBorder';
|
||||
import {parseColorAttr, parseShdColor} from '../../parse/parseColor';
|
||||
import {addSize, LengthUsage, parseSize} from '../../parse/parseSize';
|
||||
import {parseSize} from '../../parse/parseSize';
|
||||
import Word from '../../Word';
|
||||
import {CSSStyle} from '../Style';
|
||||
import {
|
||||
CT_TblLook,
|
||||
ST_TblLayoutType,
|
||||
ST_TblStyleOverrideType,
|
||||
ST_TblWidth
|
||||
} from '../Types';
|
||||
import {
|
||||
parseCellMargin,
|
||||
parseInsideBorders,
|
||||
parseTblCellSpacing,
|
||||
parseTblWidth,
|
||||
Tc
|
||||
} from './table/Tc';
|
||||
import {Properties} from './properties/Properties';
|
||||
import {Tr} from './table/Tr';
|
||||
|
||||
export type CT_TblLookKey = keyof CT_TblLook;
|
||||
import {Properties} from './properties/Properties';
|
||||
import type {Tr} from './table/Tr';
|
||||
import {parseTablePr} from '../../parse/parseTablePr';
|
||||
import {Tc} from './table/Tc';
|
||||
import {parseTr} from '../../parse/parseTr';
|
||||
|
||||
export type TblLookKey =
|
||||
| 'firstRow'
|
||||
| 'firstRow'
|
||||
| 'lastRow'
|
||||
| 'firstColumn'
|
||||
| 'lastColumn'
|
||||
| 'noHBand'
|
||||
| 'noVBand';
|
||||
|
||||
export interface TablePr extends Properties {
|
||||
/**
|
||||
@ -54,7 +43,7 @@ export interface TablePr extends Properties {
|
||||
/**
|
||||
* 条件格式
|
||||
*/
|
||||
tblLook?: Record<CT_TblLookKey, boolean>;
|
||||
tblLook?: Record<TblLookKey, boolean>;
|
||||
|
||||
/**
|
||||
* 行间隔
|
||||
@ -67,244 +56,12 @@ export interface TablePr extends Properties {
|
||||
colBandSize?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 表格的 jc 需要使用 float 来实现
|
||||
* http://officeopenxml.com/WPtableAlignment.php
|
||||
*/
|
||||
function parseTblJc(element: Element, cssStyle: CSSStyle) {
|
||||
const val = getVal(element);
|
||||
switch (val) {
|
||||
case 'left':
|
||||
case 'start':
|
||||
// TODO: 会导致前面的文字掉下去,感觉还是不能支持这个功能
|
||||
// cssStyle['float'] = 'left';
|
||||
break;
|
||||
case 'right':
|
||||
case 'end':
|
||||
cssStyle['float'] = 'right';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 这个其实分左右,但目前只支持左,右可能是阿拉伯语?
|
||||
* http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/tblInd_2.html
|
||||
*/
|
||||
function parseTblInd(element: Element, style: CSSStyle) {
|
||||
const width = parseTblWidth(element);
|
||||
if (width) {
|
||||
style['margin-left'] = width;
|
||||
}
|
||||
}
|
||||
|
||||
function parseTblW(element: Element, style: CSSStyle) {
|
||||
const width = parseTblWidth(element);
|
||||
if (width) {
|
||||
style['width'] = width;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* http://officeopenxml.com/WPtableLayout.php
|
||||
*/
|
||||
function parseTblLayout(element: Element, style: CSSStyle) {
|
||||
const type = element.getAttribute('w:type') as ST_TblLayoutType;
|
||||
|
||||
if (type === ST_TblLayoutType.fixed) {
|
||||
style['table-layout'] = 'fixed';
|
||||
}
|
||||
}
|
||||
|
||||
interface GridCol {
|
||||
export interface GridCol {
|
||||
w: string;
|
||||
}
|
||||
|
||||
function parseTblGrid(element: Element) {
|
||||
const gridCol: GridCol[] = [];
|
||||
const gridColElements = element.getElementsByTagName('w:gridCol');
|
||||
for (const gridColElement of gridColElements) {
|
||||
const w = parseSize(gridColElement, 'w:w');
|
||||
gridCol.push({w});
|
||||
}
|
||||
return gridCol;
|
||||
}
|
||||
|
||||
// http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/ST_TblStyleOverrideType.html
|
||||
// val 是旧的格式
|
||||
function parseTblLook(child: Element) {
|
||||
const tblLook: Record<CT_TblLookKey, boolean> = {} as Record<
|
||||
CT_TblLookKey,
|
||||
boolean
|
||||
>;
|
||||
const tblLookVal = getValHex(child);
|
||||
if (getAttrBoolean(child, 'firstRow', false) || tblLookVal & 0x0020) {
|
||||
tblLook['firstRow'] = true;
|
||||
}
|
||||
if (getAttrBoolean(child, 'lastRow', false) || tblLookVal & 0x0040) {
|
||||
tblLook['lastRow'] = true;
|
||||
}
|
||||
if (getAttrBoolean(child, 'firstColumn', false) || tblLookVal & 0x0080) {
|
||||
tblLook['firstColumn'] = true;
|
||||
}
|
||||
if (getAttrBoolean(child, 'lastColumn', false) || tblLookVal & 0x0100) {
|
||||
tblLook['lastColumn'] = true;
|
||||
}
|
||||
if (getAttrBoolean(child, 'noHBand', false) || tblLookVal & 0x0200) {
|
||||
tblLook['noHBand'] = true;
|
||||
} else {
|
||||
tblLook['noHBand'] = false;
|
||||
}
|
||||
if (getAttrBoolean(child, 'noVBand', false) || tblLookVal & 0x0400) {
|
||||
tblLook['noVBand'] = true;
|
||||
} else {
|
||||
tblLook['noVBand'] = false;
|
||||
}
|
||||
|
||||
return tblLook;
|
||||
}
|
||||
|
||||
/**
|
||||
* http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/tblpPr.html
|
||||
* 只支持部分
|
||||
*/
|
||||
function parsetTlpPr(word: Word, child: Element, style: CSSStyle) {
|
||||
// 如果设置 padding 会导致绝对定位不准确,所以一旦设置就不支持
|
||||
if (typeof word.renderOptions.padding === 'undefined') {
|
||||
const tplpX = parseSize(child, 'w:tblpX');
|
||||
const tplpY = parseSize(child, 'w:tblpY');
|
||||
style.position = 'absolute';
|
||||
style.top = tplpY;
|
||||
style.left = tplpX;
|
||||
}
|
||||
|
||||
// 之前想用 float 来实现,但是会导致文字掉下去
|
||||
// const topFromText = parseSize(child, 'w:topFromText');
|
||||
// const bottomFromText = parseSize(child, 'w:bottomFromText');
|
||||
// const rightFromText = parseSize(child, 'w:rightFromText');
|
||||
// const leftFromText = parseSize(child, 'w:leftFromText');
|
||||
// style['float'] = 'left';
|
||||
// style['margin-bottom'] = addSize(style['margin-bottom'], bottomFromText);
|
||||
// style['margin-left'] = addSize(style['margin-left'], leftFromText);
|
||||
// style['margin-right'] = addSize(style['margin-right'], rightFromText);
|
||||
// style['margin-top'] = addSize(style['margin-top'], topFromText);
|
||||
}
|
||||
|
||||
export class Table {
|
||||
properties: TablePr = {};
|
||||
tblGrid: GridCol[] = [];
|
||||
trs: Tr[] = [];
|
||||
|
||||
static parseTablePr(word: Word, element: Element): TablePr {
|
||||
const properties: TablePr = {};
|
||||
|
||||
const tableStyle: CSSStyle = {};
|
||||
const tcStyle: CSSStyle = {};
|
||||
|
||||
properties.tblLook = {} as Record<CT_TblLookKey, boolean>;
|
||||
|
||||
properties.cssStyle = tableStyle;
|
||||
properties.tcCSSStyle = tcStyle;
|
||||
|
||||
for (const child of element.children) {
|
||||
const tagName = child.tagName;
|
||||
switch (tagName) {
|
||||
case 'w:tblBorders':
|
||||
parseBorders(word, child, tableStyle);
|
||||
properties.insideBorder = parseInsideBorders(word, child);
|
||||
break;
|
||||
|
||||
case 'w:tcBorders':
|
||||
parseBorders(word, child, tableStyle);
|
||||
break;
|
||||
|
||||
case 'w:tblInd':
|
||||
parseTblInd(child, tableStyle);
|
||||
break;
|
||||
|
||||
case 'w:jc':
|
||||
parseTblJc(child, tableStyle);
|
||||
break;
|
||||
|
||||
case 'w:tblCellMar':
|
||||
case 'w:tcMar':
|
||||
// http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/tblCellMar_1.html
|
||||
parseCellMargin(child, tcStyle);
|
||||
break;
|
||||
|
||||
case 'w:tblStyle':
|
||||
properties.pStyle = getVal(child);
|
||||
break;
|
||||
|
||||
case 'w:tblW':
|
||||
parseTblW(child, tableStyle);
|
||||
break;
|
||||
|
||||
case 'w:shd':
|
||||
// http://officeopenxml.com/WPtableShading.php
|
||||
tableStyle['background-color'] = parseShdColor(word, child);
|
||||
break;
|
||||
|
||||
case 'w:tblCaption':
|
||||
properties.tblCaption = getVal(child);
|
||||
break;
|
||||
|
||||
case 'w:tblCellSpacing':
|
||||
parseTblCellSpacing(child, tableStyle);
|
||||
break;
|
||||
|
||||
case 'w:tblLayout':
|
||||
parseTblLayout(child, tableStyle);
|
||||
break;
|
||||
|
||||
case 'w:tblLook':
|
||||
properties.tblLook = parseTblLook(child);
|
||||
break;
|
||||
|
||||
case 'w:tblStyleRowBandSize':
|
||||
properties.rowBandSize = getValNumber(child);
|
||||
break;
|
||||
|
||||
case 'w:tblStyleColBandSize':
|
||||
properties.colBandSize = getValNumber(child);
|
||||
break;
|
||||
|
||||
case 'w:tblpPr':
|
||||
parsetTlpPr(word, child, tableStyle);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn('parseTableProperties unknown tag', tagName, child);
|
||||
}
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
static fromXML(word: Word, element: Element): Table {
|
||||
const table = new Table();
|
||||
|
||||
// 用于计算列的跨行,这里记下前面的跨行情况
|
||||
const rowSpanMap: {[key: string]: Tc} = {};
|
||||
|
||||
for (const child of element.children) {
|
||||
const tagName = child.tagName;
|
||||
switch (tagName) {
|
||||
case 'w:tblPr':
|
||||
table.properties = Table.parseTablePr(word, child);
|
||||
break;
|
||||
|
||||
case 'w:tr':
|
||||
table.trs.push(Tr.fromXML(word, child, rowSpanMap));
|
||||
break;
|
||||
|
||||
case 'w:tblGrid':
|
||||
table.tblGrid = parseTblGrid(child);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn('Table.fromXML unknown tag', tagName, child);
|
||||
}
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
}
|
||||
|
@ -3,14 +3,16 @@ import Word from '../../../Word';
|
||||
|
||||
export class Blip {
|
||||
embled?: Relationship;
|
||||
src?: string | null;
|
||||
|
||||
static fromXML(word: Word, element: Element): Blip {
|
||||
const blip = new Blip();
|
||||
// 目前值支持 embed 这一种
|
||||
const embedId = element.getAttribute('r:embed') || '';
|
||||
const rel = word.getRelationship(embedId);
|
||||
const rel = word.getDocumentRels(embedId);
|
||||
if (rel) {
|
||||
blip.embled = rel;
|
||||
blip.src = word.loadImage(blip.embled);
|
||||
}
|
||||
|
||||
return blip;
|
||||
|
@ -1,13 +1,167 @@
|
||||
/**
|
||||
* 目前图片和 textbox 都会依赖这个
|
||||
*/
|
||||
|
||||
import {LengthUsage, convertLength} from './../../../parse/parseSize';
|
||||
import {CSSStyle} from './../../Style';
|
||||
|
||||
import {getAttrBoolean, getValBoolean} from '../../../OpenXML';
|
||||
import Word from '../../../Word';
|
||||
import {Pic} from './Pic';
|
||||
import {parseSize} from '../../../parse/parseSize';
|
||||
import {ST_RelFromH, ST_RelFromV} from '../../Types';
|
||||
import {WPS} from '../wps/WPS';
|
||||
|
||||
/**
|
||||
* drawing 在文档中的位置,目前有两种情况,child 和 anchor
|
||||
*/
|
||||
export enum Position {
|
||||
inline = 'inline',
|
||||
anchor = 'anchor'
|
||||
}
|
||||
|
||||
/**
|
||||
* 有大量属性不好通过 css 来实现
|
||||
* http://webapp.docx4java.org/OnlineDemo/ecma376/DrawingML/anchor_2.html
|
||||
*/
|
||||
export interface Anchor {
|
||||
simplePos: boolean;
|
||||
hidden?: boolean;
|
||||
}
|
||||
|
||||
function parseAnchor(element: Element): Anchor {
|
||||
const simplePos = getAttrBoolean(element, 'simplePos', false);
|
||||
const hidden = getAttrBoolean(element, 'hidden', false);
|
||||
return {
|
||||
simplePos,
|
||||
hidden
|
||||
};
|
||||
}
|
||||
|
||||
export class Drawing {
|
||||
pic: Pic;
|
||||
// 如果是图片,这里会有值
|
||||
pic?: Pic;
|
||||
// 主要用于文本框
|
||||
wps?: WPS;
|
||||
// drawing 的位置配置
|
||||
position: Position = Position.inline;
|
||||
// 如果是 anchor,描述具体配置
|
||||
anchor?: Anchor;
|
||||
// 外层容器样式
|
||||
containerStyle?: CSSStyle;
|
||||
|
||||
// 是否是相对容器的垂直高度,如果是的话需要将容器的 p 也设置为 relative
|
||||
relativeFromParagraph?: boolean;
|
||||
|
||||
static fromXML(word: Word, element: Element): Drawing | null {
|
||||
const drawing = new Drawing();
|
||||
const pic = element.querySelector('pic');
|
||||
drawing.pic = Pic.fromXML(word, pic);
|
||||
|
||||
const containerStyle: CSSStyle = {};
|
||||
drawing.containerStyle = containerStyle;
|
||||
|
||||
const position = element.firstElementChild;
|
||||
|
||||
if (position) {
|
||||
if (position.tagName === 'wp:anchor') {
|
||||
drawing.position = Position.anchor;
|
||||
drawing.anchor = parseAnchor(position);
|
||||
}
|
||||
|
||||
for (const child of position.children) {
|
||||
const tagName = child.tagName;
|
||||
switch (tagName) {
|
||||
case 'wp:simplePos':
|
||||
// 只有设置了 simplePos 才会生效
|
||||
// 据说 word 其实不支持这个属性,所以目前实现估计没啥用
|
||||
if (drawing.anchor?.simplePos) {
|
||||
containerStyle['position'] = 'absolute';
|
||||
containerStyle['x'] = parseSize(child, 'x', LengthUsage.Emu);
|
||||
containerStyle['y'] = parseSize(child, 'y', LengthUsage.Emu);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'wp:positionH':
|
||||
const relativeFromH = child.getAttribute(
|
||||
'relativeFrom'
|
||||
) as ST_RelFromH;
|
||||
if (relativeFromH === 'column' || relativeFromH === 'page') {
|
||||
const positionType = child.firstElementChild;
|
||||
if (positionType) {
|
||||
const positionTypeTagName = positionType.tagName;
|
||||
if (positionTypeTagName === 'wp:posOffset') {
|
||||
containerStyle['position'] = 'absolute';
|
||||
containerStyle['left'] = convertLength(
|
||||
positionType.innerHTML,
|
||||
LengthUsage.Emu
|
||||
);
|
||||
} else {
|
||||
console.warn('unsupport positionType', positionTypeTagName);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.warn('unsupport relativeFrom', relativeFromH);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'wp:positionV':
|
||||
const relativeFromV = child.getAttribute(
|
||||
'relativeFrom'
|
||||
) as ST_RelFromV;
|
||||
if (relativeFromV === 'paragraph' || relativeFromV === 'page') {
|
||||
if (relativeFromV === 'paragraph') {
|
||||
drawing.relativeFromParagraph = true;
|
||||
}
|
||||
const positionType = child.firstElementChild;
|
||||
if (positionType) {
|
||||
const positionTypeTagName = positionType.tagName;
|
||||
if (positionTypeTagName === 'wp:posOffset') {
|
||||
containerStyle['position'] = 'absolute';
|
||||
containerStyle['top'] = convertLength(
|
||||
positionType.innerHTML,
|
||||
LengthUsage.Emu
|
||||
);
|
||||
} else {
|
||||
console.warn('unsupport positionType', positionTypeTagName);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.warn('unsupport relativeFrom', relativeFromV);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'wp:docPr':
|
||||
// 和展现无关
|
||||
// http://webapp.docx4java.org/OnlineDemo/ecma376/DrawingML/docPr.html
|
||||
break;
|
||||
|
||||
case 'a:graphic':
|
||||
const graphicData = child.firstElementChild;
|
||||
const graphicDataChild = graphicData?.firstElementChild;
|
||||
|
||||
if (graphicDataChild) {
|
||||
const graphicDataChildTagName = graphicDataChild.tagName;
|
||||
|
||||
switch (graphicDataChildTagName) {
|
||||
case 'pic:pic':
|
||||
drawing.pic = Pic.fromXML(word, graphicDataChild);
|
||||
break;
|
||||
|
||||
case 'wps:wsp':
|
||||
drawing.wps = WPS.fromXML(word, graphicDataChild);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn('unknown graphicData child tag', tagName);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn('unknown tag', tagName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return drawing;
|
||||
}
|
||||
}
|
||||
|
17
packages/office-viewer/src/openxml/word/drawing/Geom.ts
Normal file
17
packages/office-viewer/src/openxml/word/drawing/Geom.ts
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 形状,目前大部分不支持
|
||||
*/
|
||||
import {ST_ShapeType} from '../../Types';
|
||||
import Word from '../../../Word';
|
||||
import {CSSStyle} from './../../Style';
|
||||
|
||||
export class Geom {
|
||||
type: ST_ShapeType;
|
||||
|
||||
static fromXML(word: Word, element: Element, style: CSSStyle): Geom {
|
||||
const geom = new Geom();
|
||||
geom.type = element.getAttribute('prst') as ST_ShapeType;
|
||||
// 后面得改成用 SVG 实现
|
||||
return geom;
|
||||
}
|
||||
}
|
@ -2,18 +2,131 @@
|
||||
* http://webapp.docx4java.org/OnlineDemo/ecma376/DrawingML/spPr_2.html
|
||||
*/
|
||||
|
||||
import {ST_PresetLineDashVal, ST_ShapeType} from '../../Types';
|
||||
import Word from '../../../Word';
|
||||
import {Transform} from './Transform';
|
||||
import {CSSStyle} from './../../Style';
|
||||
import {parseSize, LengthUsage} from '../../../parse/parseSize';
|
||||
import {Geom} from './Geom';
|
||||
|
||||
function prstDashToCSSBorderType(prstDash: ST_PresetLineDashVal) {
|
||||
let borderType = 'solid';
|
||||
switch (prstDash) {
|
||||
case 'dash':
|
||||
case 'dashDot':
|
||||
case 'lgDash':
|
||||
case 'lgDashDot':
|
||||
case 'lgDashDotDot':
|
||||
case 'sysDash':
|
||||
case 'sysDashDot':
|
||||
case 'sysDashDotDot':
|
||||
borderType = 'dashed';
|
||||
break;
|
||||
|
||||
case 'dot':
|
||||
case 'sysDot':
|
||||
borderType = 'dotted';
|
||||
break;
|
||||
}
|
||||
return borderType;
|
||||
}
|
||||
|
||||
function parseOutline(word: Word, element: Element, style: CSSStyle) {
|
||||
const borderWidth = parseSize(element, 'w', LengthUsage.Emu);
|
||||
style['border-width'] = borderWidth;
|
||||
style['border-style'] = 'solid';
|
||||
|
||||
for (const child of element.children) {
|
||||
const tagName = child.tagName;
|
||||
switch (tagName) {
|
||||
case 'a:solidFill':
|
||||
// 目前只支持 solidFill
|
||||
const colorChild = child.firstElementChild;
|
||||
if (colorChild) {
|
||||
const colorType = colorChild.tagName;
|
||||
switch (colorType) {
|
||||
case 'a:prstClr':
|
||||
const color = colorChild.getAttribute('val') || '';
|
||||
style['border-color'] = color;
|
||||
break;
|
||||
|
||||
case 'a:srgbClr':
|
||||
const rgbColor = colorChild.getAttribute('val') || '';
|
||||
style['border-color'] = '#' + rgbColor;
|
||||
|
||||
case 'a:schemeClr':
|
||||
const schemeClr = colorChild.getAttribute('val') || '';
|
||||
if (schemeClr) {
|
||||
style['border-color'] = word.getThemeColor(schemeClr);
|
||||
}
|
||||
|
||||
default:
|
||||
console.warn(
|
||||
'parseOutline: Unknown color type ',
|
||||
colorType,
|
||||
colorChild
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'a:noFill':
|
||||
style['border'] = 'none';
|
||||
break;
|
||||
|
||||
case 'a:round':
|
||||
// 瞎写的,规范里也没写是多少
|
||||
style['border-radius'] = '8%';
|
||||
break;
|
||||
|
||||
case 'a:prstDash':
|
||||
style['border-style'] = prstDashToCSSBorderType(
|
||||
child.getAttribute('val') as ST_PresetLineDashVal
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn('parseOutline: Unknown tag ', tagName, child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ShapePr {
|
||||
xfrm?: Transform;
|
||||
|
||||
// 内置图形
|
||||
prstGeom?: Geom;
|
||||
|
||||
// 主要是边框样式
|
||||
style?: CSSStyle;
|
||||
|
||||
static fromXML(word: Word, element?: Element | null): ShapePr {
|
||||
const shapePr = new ShapePr();
|
||||
const xfrm = element?.querySelector('xfrm');
|
||||
if (xfrm) {
|
||||
shapePr.xfrm = Transform.fromXML(word, xfrm);
|
||||
const style = {};
|
||||
shapePr.style = style;
|
||||
if (element) {
|
||||
for (const child of element.children) {
|
||||
const tagName = child.tagName;
|
||||
switch (tagName) {
|
||||
case 'a:xfrm':
|
||||
shapePr.xfrm = Transform.fromXML(word, child);
|
||||
break;
|
||||
|
||||
case 'a:prstGeom':
|
||||
shapePr.prstGeom = Geom.fromXML(word, child, style);
|
||||
break;
|
||||
|
||||
case 'a:ln':
|
||||
// http://officeopenxml.com/drwSp-outline.php
|
||||
parseOutline(word, child, style);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn('ShapePr: Unknown tag ', tagName, child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return shapePr;
|
||||
}
|
||||
}
|
||||
|
@ -13,8 +13,8 @@ export class Lvl {
|
||||
numFmt: ST_NumberFormat;
|
||||
lvlText: string = '%1.';
|
||||
isLgl: boolean = false;
|
||||
lvlJc: ST_Jc = ST_Jc.start;
|
||||
suff: ST_LevelSuffix = ST_LevelSuffix.space;
|
||||
lvlJc: ST_Jc = 'start';
|
||||
suff: ST_LevelSuffix = 'space';
|
||||
|
||||
pPr?: ParagraphPr;
|
||||
rPr?: RunPr;
|
||||
@ -43,6 +43,11 @@ export class Lvl {
|
||||
lvl.lvlJc = getVal(child) as ST_Jc;
|
||||
break;
|
||||
|
||||
case 'w:legacy':
|
||||
// 老的属性应该不需要支持了
|
||||
// http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/legacy.html
|
||||
break;
|
||||
|
||||
case 'w:pPr':
|
||||
lvl.pPr = Paragraph.parseParagraphPr(word, child);
|
||||
break;
|
||||
|
@ -1,13 +1,7 @@
|
||||
import {getValBoolean, getValNumber, getVal} from '../../../OpenXML';
|
||||
import Word from '../../../Word';
|
||||
import {CSSStyle} from '../../Style';
|
||||
import {Paragraph} from '../Paragraph';
|
||||
import {Table} from '../Table';
|
||||
import {LengthUsage, parseSize} from '../../../parse/parseSize';
|
||||
import {parseColorAttr, parseShdColor} from '../../../parse/parseColor';
|
||||
import {parseBorder, parseBorders} from '../../../parse/parseBorder';
|
||||
import {parseTextDirection} from '../../../parse/parseTextDirection';
|
||||
import {ST_Merge, ST_TblWidth, ST_VerticalJc} from '../../Types';
|
||||
import {ST_Merge} from '../../Types';
|
||||
|
||||
export interface TcPr {
|
||||
cssStyle?: CSSStyle;
|
||||
@ -32,104 +26,6 @@ export interface TcPr {
|
||||
|
||||
type TcChild = Paragraph | Table;
|
||||
|
||||
// http://officeopenxml.com/WPtableCellProperties-Margins.php
|
||||
export function parseCellMargin(element: Element, style: CSSStyle) {
|
||||
for (const child of element.children) {
|
||||
const tagName = child.tagName;
|
||||
switch (tagName) {
|
||||
case 'w:left':
|
||||
case 'w:start':
|
||||
style['padding-left'] = parseSize(child, 'w:w');
|
||||
break;
|
||||
|
||||
case 'w:right':
|
||||
case 'w:end':
|
||||
style['padding-right'] = parseSize(child, 'w:w');
|
||||
break;
|
||||
|
||||
case 'w:top':
|
||||
style['padding-top'] = parseSize(child, 'w:w');
|
||||
break;
|
||||
|
||||
case 'w:bottom':
|
||||
style['padding-bottom'] = parseSize(child, 'w:w');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseVAlign(element: Element, style: CSSStyle) {
|
||||
const vAlign = getVal(element) as ST_VerticalJc;
|
||||
|
||||
switch (vAlign) {
|
||||
case ST_VerticalJc.bottom:
|
||||
style['vertical-align'] = 'bottom';
|
||||
break;
|
||||
|
||||
case ST_VerticalJc.center:
|
||||
style['vertical-align'] = 'middle';
|
||||
break;
|
||||
|
||||
case ST_VerticalJc.top:
|
||||
style['vertical-align'] = 'top';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export function parseTblCellSpacing(element: Element, style: CSSStyle) {
|
||||
const width = parseTblWidth(element);
|
||||
if (width) {
|
||||
style['cell-spacing'] = width;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* parseBorders 不支持 insideH 和 insideV,所以单独支持一下
|
||||
* 实际显示时需要过滤掉第一列
|
||||
*/
|
||||
export function parseInsideBorders(word: Word, element: Element) {
|
||||
let H;
|
||||
const insideH = element.querySelector('insideH');
|
||||
if (insideH) {
|
||||
H = parseBorder(word, insideH);
|
||||
}
|
||||
|
||||
let V;
|
||||
const insideV = element.querySelector('insideV');
|
||||
if (insideV) {
|
||||
V = parseBorder(word, insideV);
|
||||
}
|
||||
|
||||
return {
|
||||
H,
|
||||
V
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* http://officeopenxml.com/WPtableWidth.php
|
||||
*/
|
||||
export function parseTblWidth(element: Element) {
|
||||
const type = element.getAttribute('w:type') as ST_TblWidth;
|
||||
if (!type || type === ST_TblWidth.dxa) {
|
||||
return parseSize(element, 'w:w');
|
||||
} else if (type === ST_TblWidth.pct) {
|
||||
return parseSize(element, 'w:w', LengthUsage.Percent);
|
||||
} else if (type === ST_TblWidth.auto) {
|
||||
return 'auto';
|
||||
} else {
|
||||
console.warn('parseTblWidth: ignore type', type, element);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function parseTcW(element: Element, style: CSSStyle) {
|
||||
const width = parseTblWidth(element);
|
||||
if (width) {
|
||||
style.width = width;
|
||||
}
|
||||
}
|
||||
|
||||
export class Tc {
|
||||
properties: TcPr = {};
|
||||
children: TcChild[] = [];
|
||||
@ -139,120 +35,4 @@ export class Tc {
|
||||
this.children.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
static parseTcPr(word: Word, element: Element) {
|
||||
const properties: TcPr = {};
|
||||
const style: CSSStyle = {};
|
||||
properties.cssStyle = style;
|
||||
|
||||
for (const child of element.children) {
|
||||
const tagName = child.tagName;
|
||||
switch (tagName) {
|
||||
case 'w:tcMar':
|
||||
parseCellMargin(child, style);
|
||||
break;
|
||||
|
||||
case 'w:shd':
|
||||
style['background-color'] = parseShdColor(word, child);
|
||||
break;
|
||||
|
||||
case 'w:tcW':
|
||||
parseTcW(child, style);
|
||||
break;
|
||||
|
||||
case 'w:noWrap':
|
||||
// http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/noWrap.html
|
||||
const noWrap = getValBoolean(child);
|
||||
if (noWrap) {
|
||||
style['white-space'] = 'nowrap';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'w:vAlign':
|
||||
parseVAlign(child, style);
|
||||
break;
|
||||
|
||||
case 'w:tcBorders':
|
||||
parseBorders(word, child, style);
|
||||
properties.insideBorder = parseInsideBorders(word, child);
|
||||
break;
|
||||
|
||||
case 'w:gridSpan':
|
||||
properties.gridSpan = getValNumber(child);
|
||||
break;
|
||||
|
||||
case 'w:vMerge':
|
||||
properties.vMerge = (getVal(child) as ST_Merge) || ST_Merge.continue;
|
||||
break;
|
||||
|
||||
case 'w:textDirection':
|
||||
parseTextDirection(child, style);
|
||||
break;
|
||||
|
||||
case 'w:cnfStyle':
|
||||
// 目前是自动计算的,所以不需要这个了
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn('parseTcPr: ignore', tagName, child);
|
||||
}
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
static fromXML(
|
||||
word: Word,
|
||||
element: Element,
|
||||
currentCol: {index: number},
|
||||
rowSpanMap: {[key: string]: Tc}
|
||||
): Tc | null {
|
||||
const tc = new Tc();
|
||||
|
||||
for (const child of element.children) {
|
||||
const tagName = child.tagName;
|
||||
switch (tagName) {
|
||||
case 'w:tcPr':
|
||||
tc.properties = Tc.parseTcPr(word, child);
|
||||
break;
|
||||
|
||||
case 'w:p':
|
||||
tc.add(Paragraph.fromXML(word, child));
|
||||
break;
|
||||
|
||||
case 'w:tbl':
|
||||
tc.add(Table.fromXML(word, child));
|
||||
break;
|
||||
}
|
||||
}
|
||||
const lastCol = rowSpanMap[currentCol.index];
|
||||
// 如果是 continue 意味着这个被合并了
|
||||
if (tc.properties.vMerge) {
|
||||
if (tc.properties.vMerge === ST_Merge.restart) {
|
||||
tc.properties.rowSpan = 1;
|
||||
rowSpanMap[currentCol.index] = tc;
|
||||
} else if (lastCol) {
|
||||
if (lastCol.properties && lastCol.properties.rowSpan) {
|
||||
lastCol.properties.rowSpan = lastCol.properties.rowSpan + 1;
|
||||
const colSpan = tc.properties.gridSpan || 1;
|
||||
currentCol.index += colSpan;
|
||||
return null;
|
||||
} else {
|
||||
console.warn(
|
||||
'Tc.fromXML: continue but not found lastCol',
|
||||
currentCol.index,
|
||||
tc,
|
||||
rowSpanMap
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
delete rowSpanMap[currentCol.index];
|
||||
}
|
||||
|
||||
const colSpan = tc.properties.gridSpan || 1;
|
||||
currentCol.index += colSpan;
|
||||
|
||||
return tc;
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,5 @@
|
||||
import {CSSStyle} from '../../Style';
|
||||
import {getVal, getValBoolean} from '../../../OpenXML';
|
||||
import {parseTblCellSpacing, Tc} from './Tc';
|
||||
import Word from '../../../Word';
|
||||
import {parseTrHeight} from '../../../parse/parseTrHeight';
|
||||
import {jcToTextAlign} from '../../../parse/jcToTextAlign';
|
||||
import {Table} from '../Table';
|
||||
import {Tc} from './Tc';
|
||||
|
||||
export interface TrPr {
|
||||
cssStyle?: CSSStyle;
|
||||
@ -18,93 +13,4 @@ export interface TrPr {
|
||||
export class Tr {
|
||||
properties: TrPr = {};
|
||||
tcs: Tc[] = [];
|
||||
|
||||
static parseTrPr(word: Word, element: Element): TrPr {
|
||||
const cssStyle: CSSStyle = {};
|
||||
const tcStyle: CSSStyle = {};
|
||||
|
||||
for (const child of element.children) {
|
||||
const tagName = child.tagName;
|
||||
switch (tagName) {
|
||||
case 'w:hidden':
|
||||
if (getValBoolean(child)) {
|
||||
cssStyle.display = 'none';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'w:trHeight':
|
||||
parseTrHeight(child, cssStyle);
|
||||
break;
|
||||
|
||||
case 'w:jc':
|
||||
cssStyle['text-align'] = jcToTextAlign(getVal(child));
|
||||
break;
|
||||
|
||||
case 'w:cantSplit':
|
||||
// 目前也不支持分页
|
||||
break;
|
||||
|
||||
case 'w:tblPrEx':
|
||||
// http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/tblPrEx_1.html
|
||||
const tablePr = Table.parseTablePr(word, child);
|
||||
Object.assign(cssStyle, tablePr.cssStyle);
|
||||
break;
|
||||
|
||||
case 'w:tblCellSpacing':
|
||||
parseTblCellSpacing(child, tcStyle);
|
||||
break;
|
||||
|
||||
case 'w:cnfStyle':
|
||||
// 目前是自动计算的,所以不需要这个了
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn(`Tr: Unknown tag `, tagName, child);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
cssStyle
|
||||
};
|
||||
}
|
||||
|
||||
static fromXML(
|
||||
word: Word,
|
||||
element: Element,
|
||||
rowSpanMap: {[key: string]: Tc}
|
||||
): Tr {
|
||||
const tr = new Tr();
|
||||
|
||||
// 做成对象是为了传递引用来修改
|
||||
const currentCol = {
|
||||
index: 0
|
||||
};
|
||||
|
||||
for (const child of element.children) {
|
||||
const tagName = child.tagName;
|
||||
switch (tagName) {
|
||||
case 'w:tc':
|
||||
const tc = Tc.fromXML(word, child, currentCol, rowSpanMap);
|
||||
if (tc) {
|
||||
tr.tcs.push(tc);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'w:trPr':
|
||||
tr.properties = Tr.parseTrPr(word, child);
|
||||
break;
|
||||
|
||||
case 'w:tblPrEx':
|
||||
// http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/tblPrEx_1.html
|
||||
const tablePr = Table.parseTablePr(word, child);
|
||||
Object.assign(tr.properties.cssStyle || {}, tablePr.cssStyle);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn(`Tr: Unknown tag `, tagName, child);
|
||||
}
|
||||
}
|
||||
|
||||
return tr;
|
||||
}
|
||||
}
|
||||
|
5
packages/office-viewer/src/openxml/word/wps/WPG.ts
Normal file
5
packages/office-viewer/src/openxml/word/wps/WPG.ts
Normal file
@ -0,0 +1,5 @@
|
||||
/**
|
||||
* 图形组,目前还不不支持
|
||||
*/
|
||||
|
||||
export class WPG {}
|
64
packages/office-viewer/src/openxml/word/wps/WPS.ts
Normal file
64
packages/office-viewer/src/openxml/word/wps/WPS.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import {Paragraph} from './../Paragraph';
|
||||
import {ShapePr} from './../drawing/ShapeProperties';
|
||||
/**
|
||||
* wps 指的是 wordprocessingShape,在 drawing 里 word 相关的 shape 定义
|
||||
* 目前主要是支持 textbox,
|
||||
*/
|
||||
|
||||
import Word from '../../../Word';
|
||||
import {Table} from '../Table';
|
||||
import {parseTable} from '../../../parse/parseTable';
|
||||
|
||||
export type TxbxContentChild = Paragraph | Table;
|
||||
|
||||
export class WPS {
|
||||
spPr?: ShapePr;
|
||||
txbxContent: TxbxContentChild[];
|
||||
|
||||
static fromXML(word: Word, element: Element) {
|
||||
const wps = new WPS();
|
||||
wps.txbxContent = [];
|
||||
|
||||
for (const child of element.children) {
|
||||
const tagName = child.tagName;
|
||||
switch (tagName) {
|
||||
case 'wps:cNvSpPr':
|
||||
// 和展现无关
|
||||
break;
|
||||
|
||||
case 'wps:spPr':
|
||||
wps.spPr = ShapePr.fromXML(word, child);
|
||||
break;
|
||||
|
||||
case 'wps:txbx':
|
||||
// 文本框内容
|
||||
// http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/txbxContent.html
|
||||
const txbxContent = child.firstElementChild;
|
||||
if (txbxContent) {
|
||||
for (const txbxContentChild of txbxContent.children) {
|
||||
const txbxContentTagName = txbxContentChild.tagName;
|
||||
switch (txbxContentTagName) {
|
||||
case 'w:p':
|
||||
wps.txbxContent.push(
|
||||
Paragraph.fromXML(word, txbxContentChild)
|
||||
);
|
||||
break;
|
||||
|
||||
case 'w:tbl':
|
||||
wps.txbxContent.push(parseTable(word, txbxContentChild));
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.warn('unknown wps:txbx', child);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn('WPS: Unknown tag ', tagName, child);
|
||||
}
|
||||
}
|
||||
|
||||
return wps;
|
||||
}
|
||||
}
|
@ -15,7 +15,10 @@ export interface PackageParser {
|
||||
/**
|
||||
* 根据类型读取文件
|
||||
*/
|
||||
getFileByType(filePath: string, type: 'string' | 'blob'): string | Blob;
|
||||
getFileByType(
|
||||
filePath: string,
|
||||
type: 'string' | 'blob' | 'uint8array'
|
||||
): string | Blob | Uint8Array;
|
||||
|
||||
/**
|
||||
* 文件是否存在
|
||||
|
@ -42,7 +42,7 @@ export default class ZipPackageParser implements PackageParser {
|
||||
/**
|
||||
* 根据类型读取文件
|
||||
*/
|
||||
getFileByType(filePath: string, type: 'string' | 'blob') {
|
||||
getFileByType(filePath: string, type: 'string' | 'blob' | 'uint8array') {
|
||||
filePath = filePath.startsWith('/') ? filePath.slice(1) : filePath;
|
||||
const file = this.zip[filePath];
|
||||
if (file) {
|
||||
@ -50,6 +50,8 @@ export default class ZipPackageParser implements PackageParser {
|
||||
return strFromU8(file);
|
||||
} else if (type === 'blob') {
|
||||
return new Blob([file]);
|
||||
} else if (type === 'uint8array') {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
throw new Error('file not found');
|
||||
|
@ -15,9 +15,9 @@ const DEFAULT_BORDER_COLOR = 'black';
|
||||
* 解析单个边框样式
|
||||
*/
|
||||
export function parseBorder(word: Word, element: Element) {
|
||||
const type = getVal(element);
|
||||
const type = getVal(element) as ST_Border;
|
||||
|
||||
if (type === ST_Border.nil || type === ST_Border.none) {
|
||||
if (type === 'nil' || type === 'none') {
|
||||
return 'none';
|
||||
}
|
||||
|
||||
@ -25,24 +25,24 @@ export function parseBorder(word: Word, element: Element) {
|
||||
|
||||
// 这里和 css 不完全一致,css 能表现的要少很多,也是导致展现效果难以一致的原因
|
||||
switch (type) {
|
||||
case ST_Border.dashed:
|
||||
case ST_Border.dashDotStroked:
|
||||
case ST_Border.dashSmallGap:
|
||||
case 'dashed':
|
||||
case 'dashDotStroked':
|
||||
case 'dashSmallGap':
|
||||
cssType = 'dashed';
|
||||
break;
|
||||
case ST_Border.dotDash:
|
||||
case ST_Border.dotDotDash:
|
||||
case ST_Border.dotted:
|
||||
case 'dotDash':
|
||||
case 'dotDotDash':
|
||||
case 'dotted':
|
||||
cssType = 'dotted';
|
||||
break;
|
||||
case ST_Border.double:
|
||||
case ST_Border.doubleWave:
|
||||
case 'double':
|
||||
case 'doubleWave':
|
||||
cssType = 'double';
|
||||
break;
|
||||
case ST_Border.inset:
|
||||
case 'inset':
|
||||
cssType = 'inset';
|
||||
break;
|
||||
case ST_Border.outset:
|
||||
case 'outset':
|
||||
cssType = 'outset';
|
||||
break;
|
||||
}
|
||||
|
28
packages/office-viewer/src/parse/parseCellMargin.ts
Normal file
28
packages/office-viewer/src/parse/parseCellMargin.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import {CSSStyle} from '../openxml/Style';
|
||||
import {parseSize} from './parseSize';
|
||||
|
||||
// http://officeopenxml.com/WPtableCellProperties-Margins.php
|
||||
export function parseCellMargin(element: Element, style: CSSStyle) {
|
||||
for (const child of element.children) {
|
||||
const tagName = child.tagName;
|
||||
switch (tagName) {
|
||||
case 'w:left':
|
||||
case 'w:start':
|
||||
style['padding-left'] = parseSize(child, 'w:w');
|
||||
break;
|
||||
|
||||
case 'w:right':
|
||||
case 'w:end':
|
||||
style['padding-right'] = parseSize(child, 'w:w');
|
||||
break;
|
||||
|
||||
case 'w:top':
|
||||
style['padding-top'] = parseSize(child, 'w:w');
|
||||
break;
|
||||
|
||||
case 'w:bottom':
|
||||
style['padding-bottom'] = parseSize(child, 'w:w');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -6,25 +6,27 @@ import {getVal} from '../OpenXML';
|
||||
import {ST_Shd} from '../openxml/Types';
|
||||
import Word from '../Word';
|
||||
|
||||
const knownColors = [
|
||||
'black',
|
||||
'blue',
|
||||
'cyan',
|
||||
'darkBlue',
|
||||
'darkCyan',
|
||||
'darkGray',
|
||||
'darkGreen',
|
||||
'darkMagenta',
|
||||
'darkRed',
|
||||
'darkYellow',
|
||||
'green',
|
||||
'lightGray',
|
||||
'magenta',
|
||||
'none',
|
||||
'red',
|
||||
'white',
|
||||
'yellow'
|
||||
];
|
||||
/**
|
||||
* css 里可以识别的颜色
|
||||
*/
|
||||
export const cssColors = ['black', 'blue', 'green', 'red', 'white', 'yellow'];
|
||||
|
||||
/**
|
||||
* 浏览器不支持的颜色名字转成 hex,其实这里不少 chrome 也支持,但是为了兼容性,还是转一下
|
||||
*/
|
||||
export const colorNameMap = {
|
||||
cyan: '#00FFFF',
|
||||
magenta: '#FF00FF',
|
||||
darkBlue: '#00008B',
|
||||
darkCyan: '#008B8B',
|
||||
darkGray: '#A9A9A9',
|
||||
darkGreen: '#006400',
|
||||
darkMagenta: '#800080',
|
||||
darkRed: '#8B0000',
|
||||
darkYellow: '#808000',
|
||||
lightGray: '#D3D3D3',
|
||||
none: 'transparent'
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
@ -44,8 +46,13 @@ export function parseColorAttr(
|
||||
if (color) {
|
||||
if (color == 'auto') {
|
||||
return autoColor;
|
||||
} else if (typeof color === 'string' && knownColors.includes(color)) {
|
||||
/**
|
||||
* css 里可以识别的颜色
|
||||
*/
|
||||
} else if (cssColors.includes(color)) {
|
||||
return color;
|
||||
} else if (color in colorNameMap) {
|
||||
return colorNameMap[color as keyof typeof colorNameMap];
|
||||
}
|
||||
|
||||
return `#${color}`;
|
||||
@ -71,51 +78,51 @@ export function parseShdColor(word: Word, element: Element) {
|
||||
|
||||
if (color.length === 6) {
|
||||
switch (val) {
|
||||
case ST_Shd.clear:
|
||||
case 'clear':
|
||||
return `#${color}`;
|
||||
case ST_Shd.pct10:
|
||||
case 'pct10':
|
||||
return colorPercent(color, 0.1);
|
||||
case ST_Shd.pct12:
|
||||
case 'pct12':
|
||||
return colorPercent(color, 0.125);
|
||||
case ST_Shd.pct15:
|
||||
case 'pct15':
|
||||
return colorPercent(color, 0.15);
|
||||
case ST_Shd.pct20:
|
||||
case 'pct20':
|
||||
return colorPercent(color, 0.2);
|
||||
case ST_Shd.pct25:
|
||||
case 'pct25':
|
||||
return colorPercent(color, 0.25);
|
||||
case ST_Shd.pct30:
|
||||
case 'pct30':
|
||||
return colorPercent(color, 0.3);
|
||||
case ST_Shd.pct35:
|
||||
case 'pct35':
|
||||
return colorPercent(color, 0.35);
|
||||
case ST_Shd.pct37:
|
||||
case 'pct37':
|
||||
return colorPercent(color, 0.375);
|
||||
case ST_Shd.pct40:
|
||||
case 'pct40':
|
||||
return colorPercent(color, 0.4);
|
||||
case ST_Shd.pct45:
|
||||
case 'pct45':
|
||||
return colorPercent(color, 0.45);
|
||||
case ST_Shd.pct5:
|
||||
case 'pct5':
|
||||
return colorPercent(color, 0.05);
|
||||
case ST_Shd.pct50:
|
||||
case 'pct50':
|
||||
return colorPercent(color, 0.5);
|
||||
case ST_Shd.pct55:
|
||||
case 'pct55':
|
||||
return colorPercent(color, 0.55);
|
||||
case ST_Shd.pct60:
|
||||
case 'pct60':
|
||||
return colorPercent(color, 0.6);
|
||||
case ST_Shd.pct65:
|
||||
case 'pct65':
|
||||
return colorPercent(color, 0.65);
|
||||
case ST_Shd.pct70:
|
||||
case 'pct70':
|
||||
return colorPercent(color, 0.7);
|
||||
case ST_Shd.pct75:
|
||||
case 'pct75':
|
||||
return colorPercent(color, 0.75);
|
||||
case ST_Shd.pct80:
|
||||
case 'pct80':
|
||||
return colorPercent(color, 0.8);
|
||||
case ST_Shd.pct85:
|
||||
case 'pct85':
|
||||
return colorPercent(color, 0.85);
|
||||
case ST_Shd.pct87:
|
||||
case 'pct87':
|
||||
return colorPercent(color, 0.87);
|
||||
case ST_Shd.pct90:
|
||||
case 'pct90':
|
||||
return colorPercent(color, 0.9);
|
||||
case ST_Shd.pct95:
|
||||
case 'pct95':
|
||||
return colorPercent(color, 0.95);
|
||||
|
||||
default:
|
||||
|
25
packages/office-viewer/src/parse/parseInsideBorders.ts
Normal file
25
packages/office-viewer/src/parse/parseInsideBorders.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import Word from '../Word';
|
||||
import {parseBorder} from './parseBorder';
|
||||
|
||||
/**
|
||||
* parseBorders 不支持 insideH 和 insideV,所以单独支持一下
|
||||
* 实际显示时需要过滤掉第一列
|
||||
*/
|
||||
export function parseInsideBorders(word: Word, element: Element) {
|
||||
let H;
|
||||
const insideH = element.querySelector('insideH');
|
||||
if (insideH) {
|
||||
H = parseBorder(word, insideH);
|
||||
}
|
||||
|
||||
let V;
|
||||
const insideV = element.querySelector('insideV');
|
||||
if (insideV) {
|
||||
V = parseBorder(word, insideV);
|
||||
}
|
||||
|
||||
return {
|
||||
H,
|
||||
V
|
||||
};
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
import {ST_TextAlignment} from './../openxml/Types';
|
||||
/**
|
||||
* 包括 rPr 及 pPr 的解析,参考了 docxjs 里的实现
|
||||
*/
|
||||
@ -6,14 +5,14 @@ import {ST_TextAlignment} from './../openxml/Types';
|
||||
import {LengthUsage} from './parseSize';
|
||||
import {CSSStyle} from '../openxml/Style';
|
||||
import Word from '../Word';
|
||||
import {getVal, getValBoolean} from '../OpenXML';
|
||||
import {getVal, getValBoolean, getValNumber} from '../OpenXML';
|
||||
import {parseBorder, parseBorders} from './parseBorder';
|
||||
import {parseColor, parseColorAttr, parseShdColor} from './parseColor';
|
||||
import {parseInd} from './parseInd';
|
||||
import {parseSize} from './parseSize';
|
||||
import {parseSpacing} from './parseSpacing';
|
||||
import {parseFont} from './parseFont';
|
||||
import {ST_VerticalAlignRun} from '../openxml/Types';
|
||||
import {ST_Em, ST_HighlightColor, ST_TextAlignment} from '../openxml/Types';
|
||||
import {parseTrHeight} from './parseTrHeight';
|
||||
import {jcToTextAlign} from './jcToTextAlign';
|
||||
import {parseTextDirection} from './parseTextDirection';
|
||||
@ -108,6 +107,35 @@ function parseFrame(element: Element, style: CSSStyle) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 em 转成 css 里对应的 text-emphasis 值
|
||||
* http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/ST_Em.html
|
||||
*/
|
||||
function convertEm(em: ST_Em, style: CSSStyle) {
|
||||
switch (em) {
|
||||
case 'dot':
|
||||
style['text-emphasis'] = 'filled';
|
||||
// 按规范里描述应该是在文字上面,但在 word 实际显示的时候是在下面
|
||||
// 怀疑是中文场景下做了特殊处理
|
||||
style['text-emphasis-position'] = 'under right';
|
||||
break;
|
||||
case 'comma':
|
||||
style['text-emphasis'] = 'filled sesame';
|
||||
break;
|
||||
|
||||
case 'circle':
|
||||
style['text-emphasis'] = 'open';
|
||||
break;
|
||||
|
||||
case 'underDot':
|
||||
style['text-emphasis'] = 'filled';
|
||||
style['text-emphasis-position'] = 'under right';
|
||||
break;
|
||||
case 'none':
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const HighLightColor = 'transparent';
|
||||
|
||||
/**
|
||||
@ -163,8 +191,8 @@ export function parsePr(word: Word, element: Element, type: 'r' | 'p' = 'p') {
|
||||
style['background-color'] = parseColorAttr(
|
||||
word,
|
||||
child,
|
||||
'w:fill',
|
||||
HighLightColor
|
||||
'w:val',
|
||||
'yellow'
|
||||
);
|
||||
break;
|
||||
|
||||
@ -172,9 +200,9 @@ export function parsePr(word: Word, element: Element, type: 'r' | 'p' = 'p') {
|
||||
// http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/vertAlign.html
|
||||
// 这个其实和 position 有冲突,但预计这两个同时出现的概率不高
|
||||
const vertAlign = getVal(child);
|
||||
if (vertAlign === ST_VerticalAlignRun.superscript) {
|
||||
if (vertAlign === 'superscript') {
|
||||
style['vertical-align'] = 'super';
|
||||
} else if (vertAlign === ST_VerticalAlignRun.subscript) {
|
||||
} else if (vertAlign === 'subscript') {
|
||||
style['vertical-align'] = 'sub';
|
||||
}
|
||||
break;
|
||||
@ -328,9 +356,9 @@ export function parsePr(word: Word, element: Element, type: 'r' | 'p' = 'p') {
|
||||
case 'w:textAlignment':
|
||||
// http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/textAlignment.html
|
||||
const alignment = getVal(child) as ST_TextAlignment;
|
||||
if (alignment === ST_TextAlignment.center) {
|
||||
if (alignment === 'center') {
|
||||
style['vertical-align'] = 'middle';
|
||||
} else if (alignment !== ST_TextAlignment.auto) {
|
||||
} else if (alignment !== 'auto') {
|
||||
style['vertical-align'] = alignment;
|
||||
}
|
||||
break;
|
||||
@ -366,6 +394,22 @@ export function parsePr(word: Word, element: Element, type: 'r' | 'p' = 'p') {
|
||||
// 支持不了
|
||||
break;
|
||||
|
||||
case 'w:em':
|
||||
convertEm(getVal(child) as ST_Em, style);
|
||||
break;
|
||||
|
||||
case 'w:w':
|
||||
// http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/w_1.html
|
||||
// 看起来应该是把文字拉伸
|
||||
const w = getValNumber(child);
|
||||
style['transform'] = `scaleX(${w / 100})`;
|
||||
style['display'] = 'inline-block'; // 需要这样才能生效
|
||||
break;
|
||||
|
||||
case 'w:webHidden':
|
||||
// 虽然是 web 渲染但希望是按 word 显示,所以先不处理了
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn('parsePr Unknown tagName', tagName, child);
|
||||
}
|
||||
|
45
packages/office-viewer/src/parse/parseTable.ts
Normal file
45
packages/office-viewer/src/parse/parseTable.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import {Tc} from '../openxml/word/table/Tc';
|
||||
import {GridCol, Table} from '../openxml/word/Table';
|
||||
import {parseTr} from './parseTr';
|
||||
import {parseTablePr} from './parseTablePr';
|
||||
import Word from '../Word';
|
||||
import {parseSize} from './parseSize';
|
||||
|
||||
function parseTblGrid(element: Element) {
|
||||
const gridCol: GridCol[] = [];
|
||||
const gridColElements = element.getElementsByTagName('w:gridCol');
|
||||
for (const gridColElement of gridColElements) {
|
||||
const w = parseSize(gridColElement, 'w:w');
|
||||
gridCol.push({w});
|
||||
}
|
||||
return gridCol;
|
||||
}
|
||||
|
||||
export function parseTable(word: Word, element: Element) {
|
||||
const table = new Table();
|
||||
|
||||
// 用于计算列的跨行,这里记下前面的跨行情况
|
||||
const rowSpanMap: {[key: string]: Tc} = {};
|
||||
|
||||
for (const child of element.children) {
|
||||
const tagName = child.tagName;
|
||||
switch (tagName) {
|
||||
case 'w:tblPr':
|
||||
table.properties = parseTablePr(word, child);
|
||||
break;
|
||||
|
||||
case 'w:tr':
|
||||
table.trs.push(parseTr(word, child, rowSpanMap));
|
||||
break;
|
||||
|
||||
case 'w:tblGrid':
|
||||
table.tblGrid = parseTblGrid(child);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn('Table.fromXML unknown tag', tagName, child);
|
||||
}
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
207
packages/office-viewer/src/parse/parseTablePr.ts
Normal file
207
packages/office-viewer/src/parse/parseTablePr.ts
Normal file
@ -0,0 +1,207 @@
|
||||
import {ST_TblLayoutType} from './../openxml/Types';
|
||||
|
||||
import {getAttrBoolean, getVal, getValHex, getValNumber} from '../OpenXML';
|
||||
import {CSSStyle} from '../openxml/Style';
|
||||
import {TablePr, TblLookKey} from '../openxml/word/Table';
|
||||
|
||||
import Word from '../Word';
|
||||
import {parseBorders} from './parseBorder';
|
||||
import {parseInsideBorders} from './parseInsideBorders';
|
||||
import {parseTblWidth} from './parseTblWidth';
|
||||
import {parseShdColor} from './parseColor';
|
||||
import {parseSize} from './parseSize';
|
||||
import {parseTblCellSpacing} from './parseTcPr';
|
||||
import {parseCellMargin} from './parseCellMargin';
|
||||
|
||||
/**
|
||||
* 表格的 jc 需要使用 float 来实现
|
||||
* http://officeopenxml.com/WPtableAlignment.php
|
||||
*/
|
||||
function parseTblJc(element: Element, cssStyle: CSSStyle) {
|
||||
const val = getVal(element);
|
||||
switch (val) {
|
||||
case 'left':
|
||||
case 'start':
|
||||
// TODO: 会导致前面的文字掉下去,感觉还是不能支持这个功能
|
||||
// cssStyle['float'] = 'left';
|
||||
break;
|
||||
case 'right':
|
||||
case 'end':
|
||||
cssStyle['float'] = 'right';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 这个其实分左右,但目前只支持左,右可能是阿拉伯语?
|
||||
* http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/tblInd_2.html
|
||||
*/
|
||||
function parseTblInd(element: Element, style: CSSStyle) {
|
||||
const width = parseTblWidth(element);
|
||||
if (width) {
|
||||
style['margin-left'] = width;
|
||||
}
|
||||
}
|
||||
|
||||
function parseTblW(element: Element, style: CSSStyle) {
|
||||
const width = parseTblWidth(element);
|
||||
if (width) {
|
||||
style['width'] = width;
|
||||
}
|
||||
}
|
||||
|
||||
// http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/ST_TblStyleOverrideType.html
|
||||
// val 是旧的格式
|
||||
function parseTblLook(child: Element) {
|
||||
const tblLook: Record<TblLookKey, boolean> = {} as Record<
|
||||
TblLookKey,
|
||||
boolean
|
||||
>;
|
||||
const tblLookVal = getValHex(child);
|
||||
if (getAttrBoolean(child, 'firstRow', false) || tblLookVal & 0x0020) {
|
||||
tblLook['firstRow'] = true;
|
||||
}
|
||||
if (getAttrBoolean(child, 'lastRow', false) || tblLookVal & 0x0040) {
|
||||
tblLook['lastRow'] = true;
|
||||
}
|
||||
if (getAttrBoolean(child, 'firstColumn', false) || tblLookVal & 0x0080) {
|
||||
tblLook['firstColumn'] = true;
|
||||
}
|
||||
if (getAttrBoolean(child, 'lastColumn', false) || tblLookVal & 0x0100) {
|
||||
tblLook['lastColumn'] = true;
|
||||
}
|
||||
if (getAttrBoolean(child, 'noHBand', false) || tblLookVal & 0x0200) {
|
||||
tblLook['noHBand'] = true;
|
||||
} else {
|
||||
tblLook['noHBand'] = false;
|
||||
}
|
||||
if (getAttrBoolean(child, 'noVBand', false) || tblLookVal & 0x0400) {
|
||||
tblLook['noVBand'] = true;
|
||||
} else {
|
||||
tblLook['noVBand'] = false;
|
||||
}
|
||||
|
||||
return tblLook;
|
||||
}
|
||||
|
||||
/**
|
||||
* http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/tblpPr.html
|
||||
* 只支持部分
|
||||
*/
|
||||
function parseTblpPr(word: Word, child: Element, style: CSSStyle) {
|
||||
// 如果设置 padding 会导致绝对定位不准确,所以一旦设置就不支持
|
||||
if (typeof word.renderOptions.padding === 'undefined') {
|
||||
const tplpX = parseSize(child, 'w:tblpX');
|
||||
const tplpY = parseSize(child, 'w:tblpY');
|
||||
style.position = 'absolute';
|
||||
style.top = tplpY;
|
||||
style.left = tplpX;
|
||||
}
|
||||
|
||||
// 之前想用 float 来实现,但是会导致文字掉下去
|
||||
// const topFromText = parseSize(child, 'w:topFromText');
|
||||
// const bottomFromText = parseSize(child, 'w:bottomFromText');
|
||||
// const rightFromText = parseSize(child, 'w:rightFromText');
|
||||
// const leftFromText = parseSize(child, 'w:leftFromText');
|
||||
// style['float'] = 'left';
|
||||
// style['margin-bottom'] = addSize(style['margin-bottom'], bottomFromText);
|
||||
// style['margin-left'] = addSize(style['margin-left'], leftFromText);
|
||||
// style['margin-right'] = addSize(style['margin-right'], rightFromText);
|
||||
// style['margin-top'] = addSize(style['margin-top'], topFromText);
|
||||
}
|
||||
|
||||
/**
|
||||
* http://officeopenxml.com/WPtableLayout.php
|
||||
*/
|
||||
function parseTblLayout(element: Element, style: CSSStyle) {
|
||||
const type = element.getAttribute('w:type') as ST_TblLayoutType;
|
||||
|
||||
if (type === 'fixed') {
|
||||
style['table-layout'] = 'fixed';
|
||||
}
|
||||
}
|
||||
|
||||
export function parseTablePr(word: Word, element: Element): TablePr {
|
||||
const properties: TablePr = {};
|
||||
|
||||
const tableStyle: CSSStyle = {};
|
||||
const tcStyle: CSSStyle = {};
|
||||
|
||||
properties.tblLook = {} as Record<TblLookKey, boolean>;
|
||||
|
||||
properties.cssStyle = tableStyle;
|
||||
properties.tcCSSStyle = tcStyle;
|
||||
|
||||
for (const child of element.children) {
|
||||
const tagName = child.tagName;
|
||||
switch (tagName) {
|
||||
case 'w:tblBorders':
|
||||
parseBorders(word, child, tableStyle);
|
||||
properties.insideBorder = parseInsideBorders(word, child);
|
||||
break;
|
||||
|
||||
case 'w:tcBorders':
|
||||
parseBorders(word, child, tableStyle);
|
||||
break;
|
||||
|
||||
case 'w:tblInd':
|
||||
parseTblInd(child, tableStyle);
|
||||
break;
|
||||
|
||||
case 'w:jc':
|
||||
parseTblJc(child, tableStyle);
|
||||
break;
|
||||
|
||||
case 'w:tblCellMar':
|
||||
case 'w:tcMar':
|
||||
// http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/tblCellMar_1.html
|
||||
parseCellMargin(child, tcStyle);
|
||||
break;
|
||||
|
||||
case 'w:tblStyle':
|
||||
properties.pStyle = getVal(child);
|
||||
break;
|
||||
|
||||
case 'w:tblW':
|
||||
parseTblW(child, tableStyle);
|
||||
break;
|
||||
|
||||
case 'w:shd':
|
||||
// http://officeopenxml.com/WPtableShading.php
|
||||
tableStyle['background-color'] = parseShdColor(word, child);
|
||||
break;
|
||||
|
||||
case 'w:tblCaption':
|
||||
properties.tblCaption = getVal(child);
|
||||
break;
|
||||
|
||||
case 'w:tblCellSpacing':
|
||||
parseTblCellSpacing(child, tableStyle);
|
||||
break;
|
||||
|
||||
case 'w:tblLayout':
|
||||
parseTblLayout(child, tableStyle);
|
||||
break;
|
||||
|
||||
case 'w:tblLook':
|
||||
properties.tblLook = parseTblLook(child);
|
||||
break;
|
||||
|
||||
case 'w:tblStyleRowBandSize':
|
||||
properties.rowBandSize = getValNumber(child);
|
||||
break;
|
||||
|
||||
case 'w:tblStyleColBandSize':
|
||||
properties.colBandSize = getValNumber(child);
|
||||
break;
|
||||
|
||||
case 'w:tblpPr':
|
||||
parseTblpPr(word, child, tableStyle);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn('parseTableProperties unknown tag', tagName, child);
|
||||
}
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
19
packages/office-viewer/src/parse/parseTblWidth.ts
Normal file
19
packages/office-viewer/src/parse/parseTblWidth.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import {ST_TblWidth} from './../openxml/Types';
|
||||
import {parseSize, LengthUsage} from './parseSize';
|
||||
|
||||
/**
|
||||
* http://officeopenxml.com/WPtableWidth.php
|
||||
*/
|
||||
export function parseTblWidth(element: Element) {
|
||||
const type = element.getAttribute('w:type') as ST_TblWidth;
|
||||
if (!type || type === 'dxa') {
|
||||
return parseSize(element, 'w:w');
|
||||
} else if (type === 'pct') {
|
||||
return parseSize(element, 'w:w', LengthUsage.Percent);
|
||||
} else if (type === 'auto') {
|
||||
return 'auto';
|
||||
} else {
|
||||
console.warn('parseTblWidth: ignore type', type, element);
|
||||
}
|
||||
return '';
|
||||
}
|
65
packages/office-viewer/src/parse/parseTc.ts
Normal file
65
packages/office-viewer/src/parse/parseTc.ts
Normal file
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 拆分出来主要是为了避免循环引用
|
||||
*/
|
||||
|
||||
import {Tc} from '../openxml/word/table/Tc';
|
||||
import Word from '../Word';
|
||||
import {parseTcPr} from './parseTcPr';
|
||||
|
||||
import {Paragraph} from '../openxml/word/Paragraph';
|
||||
import {parseTable} from './parseTable';
|
||||
|
||||
export function parseTc(
|
||||
word: Word,
|
||||
element: Element,
|
||||
currentCol: {index: number},
|
||||
rowSpanMap: {[key: string]: Tc}
|
||||
) {
|
||||
const tc = new Tc();
|
||||
|
||||
for (const child of element.children) {
|
||||
const tagName = child.tagName;
|
||||
switch (tagName) {
|
||||
case 'w:tcPr':
|
||||
tc.properties = parseTcPr(word, child);
|
||||
break;
|
||||
|
||||
case 'w:p':
|
||||
tc.add(Paragraph.fromXML(word, child));
|
||||
break;
|
||||
|
||||
case 'w:tbl':
|
||||
tc.add(parseTable(word, child));
|
||||
break;
|
||||
}
|
||||
}
|
||||
const lastCol = rowSpanMap[currentCol.index];
|
||||
// 如果是 continue 意味着这个被合并了
|
||||
if (tc.properties.vMerge) {
|
||||
if (tc.properties.vMerge === 'restart') {
|
||||
tc.properties.rowSpan = 1;
|
||||
rowSpanMap[currentCol.index] = tc;
|
||||
} else if (lastCol) {
|
||||
if (lastCol.properties && lastCol.properties.rowSpan) {
|
||||
lastCol.properties.rowSpan = lastCol.properties.rowSpan + 1;
|
||||
const colSpan = tc.properties.gridSpan || 1;
|
||||
currentCol.index += colSpan;
|
||||
return null;
|
||||
} else {
|
||||
console.warn(
|
||||
'Tc.fromXML: continue but not found lastCol',
|
||||
currentCol.index,
|
||||
tc,
|
||||
rowSpanMap
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
delete rowSpanMap[currentCol.index];
|
||||
}
|
||||
|
||||
const colSpan = tc.properties.gridSpan || 1;
|
||||
currentCol.index += colSpan;
|
||||
|
||||
return tc;
|
||||
}
|
105
packages/office-viewer/src/parse/parseTcPr.ts
Normal file
105
packages/office-viewer/src/parse/parseTcPr.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import {CSSStyle} from '../openxml/Style';
|
||||
import {TcPr} from '../openxml/word/table/Tc';
|
||||
import Word from '../Word';
|
||||
import {parseCellMargin} from './parseCellMargin';
|
||||
import {parseShdColor} from './parseColor';
|
||||
import {getVal, getValBoolean, getValNumber} from '../OpenXML';
|
||||
import {ST_Merge, ST_TblWidth, ST_VerticalJc} from '../openxml/Types';
|
||||
import {parseSize, LengthUsage} from './parseSize';
|
||||
import {parseBorders} from './parseBorder';
|
||||
import {parseTextDirection} from './parseTextDirection';
|
||||
import {parseTblWidth} from './parseTblWidth';
|
||||
import {parseInsideBorders} from './parseInsideBorders';
|
||||
|
||||
function parseVAlign(element: Element, style: CSSStyle) {
|
||||
const vAlign = getVal(element) as ST_VerticalJc;
|
||||
|
||||
switch (vAlign) {
|
||||
case 'bottom':
|
||||
style['vertical-align'] = 'bottom';
|
||||
break;
|
||||
|
||||
case 'center':
|
||||
style['vertical-align'] = 'middle';
|
||||
break;
|
||||
|
||||
case 'top':
|
||||
style['vertical-align'] = 'top';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export function parseTblCellSpacing(element: Element, style: CSSStyle) {
|
||||
const width = parseTblWidth(element);
|
||||
if (width) {
|
||||
style['cell-spacing'] = width;
|
||||
}
|
||||
}
|
||||
|
||||
function parseTcW(element: Element, style: CSSStyle) {
|
||||
const width = parseTblWidth(element);
|
||||
if (width) {
|
||||
style.width = width;
|
||||
}
|
||||
}
|
||||
|
||||
export function parseTcPr(word: Word, element: Element) {
|
||||
const properties: TcPr = {};
|
||||
const style: CSSStyle = {};
|
||||
properties.cssStyle = style;
|
||||
|
||||
for (const child of element.children) {
|
||||
const tagName = child.tagName;
|
||||
switch (tagName) {
|
||||
case 'w:tcMar':
|
||||
parseCellMargin(child, style);
|
||||
break;
|
||||
|
||||
case 'w:shd':
|
||||
style['background-color'] = parseShdColor(word, child);
|
||||
break;
|
||||
|
||||
case 'w:tcW':
|
||||
parseTcW(child, style);
|
||||
break;
|
||||
|
||||
case 'w:noWrap':
|
||||
// http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/noWrap.html
|
||||
const noWrap = getValBoolean(child);
|
||||
if (noWrap) {
|
||||
style['white-space'] = 'nowrap';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'w:vAlign':
|
||||
parseVAlign(child, style);
|
||||
break;
|
||||
|
||||
case 'w:tcBorders':
|
||||
parseBorders(word, child, style);
|
||||
properties.insideBorder = parseInsideBorders(word, child);
|
||||
break;
|
||||
|
||||
case 'w:gridSpan':
|
||||
properties.gridSpan = getValNumber(child);
|
||||
break;
|
||||
|
||||
case 'w:vMerge':
|
||||
properties.vMerge = (getVal(child) as ST_Merge) || 'continue';
|
||||
break;
|
||||
|
||||
case 'w:textDirection':
|
||||
parseTextDirection(child, style);
|
||||
break;
|
||||
|
||||
case 'w:cnfStyle':
|
||||
// 目前是自动计算的,所以不需要这个了
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn('parseTcPr: ignore', tagName, child);
|
||||
}
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user