mirror of
https://gitee.com/baidu/amis.git
synced 2024-12-02 03:58:07 +08:00
feat: office viewer 支持显示形状 (#6585)
* shape 支持初步 * 完善 shape 的支持 * 支持自定义 shape * arcTo 换算法 * 修正 arc 圆形的计算 * curvedRightArrow 暂时不支持 * 修复编译报错 * 更新 snapshot * 增加动作配置 * 新增 api 的时候也触发变化
This commit is contained in:
parent
d13faa6933
commit
48a67a5673
@ -19,9 +19,10 @@ order: 23
|
||||
- 注音
|
||||
- 链接
|
||||
- 文本框
|
||||
- 形状
|
||||
- 数学公式(依赖 MathML,需要比较新的浏览器,或者试试 [polyfill](https://github.com/w3c/mathml-polyfills))
|
||||
|
||||
不支持的功能:分页符、形状、艺术字、域、对象、目录
|
||||
不支持的功能:分页符、艺术字、域、对象、目录
|
||||
|
||||
## 基本用法
|
||||
|
||||
|
@ -674,6 +674,11 @@ export function isApiOutdated(
|
||||
return false;
|
||||
}
|
||||
|
||||
// 通常是编辑器里加了属性,一开始没值,后来有了
|
||||
if (prevApi === undefined && !nextApi !== undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
nextApi = normalizeApi(nextApi);
|
||||
|
||||
if (nextApi.autoRefresh === false) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {registerEditorPlugin} from 'amis-editor-core';
|
||||
import {RendererPluginAction, registerEditorPlugin} from 'amis-editor-core';
|
||||
import {BaseEventContext, BasePlugin} from 'amis-editor-core';
|
||||
import {defaultValue, getSchemaTpl} from 'amis-editor-core';
|
||||
|
||||
@ -27,6 +27,19 @@ export class OfficeViewerPlugin extends BasePlugin {
|
||||
|
||||
panelJustify = true;
|
||||
|
||||
actions: RendererPluginAction[] = [
|
||||
{
|
||||
actionType: 'print',
|
||||
actionLabel: '打印',
|
||||
description: '打印文档'
|
||||
},
|
||||
{
|
||||
actionType: 'saveAs',
|
||||
actionLabel: '下载',
|
||||
description: '下载文档'
|
||||
}
|
||||
];
|
||||
|
||||
panelBodyCreator = (context: BaseEventContext) => {
|
||||
return [
|
||||
getSchemaTpl('tabs', [
|
||||
@ -41,9 +54,18 @@ export class OfficeViewerPlugin extends BasePlugin {
|
||||
name: 'src',
|
||||
type: 'input-text',
|
||||
label: '文档地址'
|
||||
}),
|
||||
|
||||
getSchemaTpl('switch', {
|
||||
type: 'switch',
|
||||
label: '是否渲染',
|
||||
name: 'display',
|
||||
pipeIn: defaultValue(true),
|
||||
inline: true
|
||||
})
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Word 渲染配置',
|
||||
collapsed: true,
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -78,7 +78,7 @@
|
||||
xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape"
|
||||
mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh wp14">
|
||||
<w:body>
|
||||
<w:p w14:paraId="17EBEE4B" w14:textId="5840C45A" w:rsidR="003456C4"
|
||||
<w:p w14:paraId="17EBEE4B" w14:textId="5713407F" w:rsidR="003456C4"
|
||||
w:rsidRDefault="003456C4">
|
||||
<w:r>
|
||||
<w:rPr>
|
||||
@ -89,16 +89,16 @@
|
||||
<w:drawing>
|
||||
<wp:anchor distT="0" distB="0" distL="114300" distR="114300" simplePos="0"
|
||||
relativeHeight="251659264" behindDoc="0" locked="0" layoutInCell="1"
|
||||
allowOverlap="1" wp14:anchorId="47B983CA" wp14:editId="3301C715">
|
||||
allowOverlap="1" wp14:anchorId="47B983CA" wp14:editId="189E243A">
|
||||
<wp:simplePos x="0" y="0" />
|
||||
<wp:positionH relativeFrom="column">
|
||||
<wp:posOffset>0</wp:posOffset>
|
||||
<wp:posOffset>-1108</wp:posOffset>
|
||||
</wp:positionH>
|
||||
<wp:positionV relativeFrom="paragraph">
|
||||
<wp:posOffset>5715</wp:posOffset>
|
||||
<wp:posOffset>9526</wp:posOffset>
|
||||
</wp:positionV>
|
||||
<wp:extent cx="832485" cy="832485" />
|
||||
<wp:effectExtent l="9525" t="9525" r="15240" b="15240" />
|
||||
<wp:extent cx="832485" cy="649694" />
|
||||
<wp:effectExtent l="12700" t="12700" r="18415" b="10795" />
|
||||
<wp:wrapNone />
|
||||
<wp:docPr id="12" name="椭圆 12" />
|
||||
<wp:cNvGraphicFramePr />
|
||||
@ -109,8 +109,8 @@
|
||||
<wps:cNvSpPr />
|
||||
<wps:spPr>
|
||||
<a:xfrm>
|
||||
<a:off x="4650105" y="6876415" />
|
||||
<a:ext cx="832485" cy="832485" />
|
||||
<a:off x="0" y="0" />
|
||||
<a:ext cx="832485" cy="649694" />
|
||||
</a:xfrm>
|
||||
<a:prstGeom prst="ellipse">
|
||||
<a:avLst />
|
||||
@ -148,14 +148,17 @@
|
||||
</wps:wsp>
|
||||
</a:graphicData>
|
||||
</a:graphic>
|
||||
<wp14:sizeRelV relativeFrom="margin">
|
||||
<wp14:pctHeight>0</wp14:pctHeight>
|
||||
</wp14:sizeRelV>
|
||||
</wp:anchor>
|
||||
</w:drawing>
|
||||
</mc:Choice>
|
||||
<mc:Fallback>
|
||||
<w:pict>
|
||||
<v:oval w14:anchorId="099597AD" id="椭圆 12" o:spid="_x0000_s1026"
|
||||
style="position:absolute;left:0;text-align:left;margin-left:0;margin-top:.45pt;width:65.55pt;height:65.55pt;z-index:251659264;visibility:visible;mso-wrap-style:square;mso-wrap-distance-left:9pt;mso-wrap-distance-top:0;mso-wrap-distance-right:9pt;mso-wrap-distance-bottom:0;mso-position-horizontal:absolute;mso-position-horizontal-relative:text;mso-position-vertical:absolute;mso-position-vertical-relative:text;v-text-anchor:middle"
|
||||
o:gfxdata="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
"
|
||||
<v:oval w14:anchorId="407A8DBD" id="椭圆 12" o:spid="_x0000_s1026"
|
||||
style="position:absolute;left:0;text-align:left;margin-left:-.1pt;margin-top:.75pt;width:65.55pt;height:51.15pt;z-index:251659264;visibility:visible;mso-wrap-style:square;mso-height-percent:0;mso-wrap-distance-left:9pt;mso-wrap-distance-top:0;mso-wrap-distance-right:9pt;mso-wrap-distance-bottom:0;mso-position-horizontal:absolute;mso-position-horizontal-relative:text;mso-position-vertical:absolute;mso-position-vertical-relative:text;mso-height-percent:0;mso-height-relative:margin;v-text-anchor:middle"
|
||||
o:gfxdata="UEsDBBQABgAIAAAAIQC2gziS/gAAAOEBAAATAAAAW0NvbnRlbnRfVHlwZXNdLnhtbJSRQU7DMBBF
90jcwfIWJU67QAgl6YK0S0CoHGBkTxKLZGx5TGhvj5O2G0SRWNoz/78nu9wcxkFMGNg6quQqL6RA
0s5Y6ir5vt9lD1JwBDIwOMJKHpHlpr69KfdHjyxSmriSfYz+USnWPY7AufNIadK6MEJMx9ApD/oD
OlTrorhX2lFEilmcO2RdNtjC5xDF9pCuTyYBB5bi6bQ4syoJ3g9WQ0ymaiLzg5KdCXlKLjvcW893
SUOqXwnz5DrgnHtJTxOsQfEKIT7DmDSUCaxw7Rqn8787ZsmRM9e2VmPeBN4uqYvTtW7jvijg9N/y
JsXecLq0q+WD6m8AAAD//wMAUEsDBBQABgAIAAAAIQA4/SH/1gAAAJQBAAALAAAAX3JlbHMvLnJl
bHOkkMFqwzAMhu+DvYPRfXGawxijTi+j0GvpHsDYimMaW0Yy2fr2M4PBMnrbUb/Q94l/f/hMi1qR
JVI2sOt6UJgd+ZiDgffL8ekFlFSbvV0oo4EbChzGx4f9GRdb25HMsYhqlCwG5lrLq9biZkxWOiqY
22YiTra2kYMu1l1tQD30/bPm3wwYN0x18gb45AdQl1tp5j/sFB2T0FQ7R0nTNEV3j6o9feQzro1i
OWA14Fm+Q8a1a8+Bvu/d/dMb2JY5uiPbhG/ktn4cqGU/er3pcvwCAAD//wMAUEsDBBQABgAIAAAA
IQBJzeembgIAADEFAAAOAAAAZHJzL2Uyb0RvYy54bWysVE2PGjEMvVfqf4hy785AgQLaYYVYbVUJ
dVG3Vc8hkzCRMnGaBAb66+tkPkDtqoeqc8g4sf1sP8e5fzjXmpyE8wpMQUd3OSXCcCiVORT029en
d3NKfGCmZBqMKOhFePqwevvmvrFLMYYKdCkcQRDjl40taBWCXWaZ55Womb8DKwwqJbiaBdy6Q1Y6
1iB6rbNxns+yBlxpHXDhPZ4+tkq6SvhSCh6epfQiEF1QzC2k1aV1H9dsdc+WB8dspXiXBvuHLGqm
DAYdoB5ZYOTo1B9QteIOPMhwx6HOQErFRaoBqxnlv1XzUjErUi1IjrcDTf7/wfLPpxe7c0hDY/3S
oxirOEtXxz/mR86JrMtAljgHwvFw/n48mU8p4aiaTRazxSSSmV2drfPho4CaRKGgQmtlfSyHLdlp
60Nr3VvFYwNPSuvUEm1Ig/dpkU/z5OFBqzJqo513h/1GO3Ji2NVNHr8u9o0ZZqINJnStK0nhokXE
0OaLkESVWMm4jRCvnBhgGefChFGrqlgp2mjT22C9Ryo7AUZkiVkO2B1Ab9mC9NgtA519dBXpxg7O
Xel/cx48UmQwYXCulQH3WmUaq+oit/Y9SS01kaU9lJedIw7aefGWPyls4pb5sGMOBwRHCYc+POMi
NWCnoJMoqcD9fO082uO9RS0lDQ5cQf2PI3OCEv3J4I1ejCaTOKFpM5l+GOPG3Wr2txpzrDeA3R/h
82J5EqN90L0oHdTf8W1Yx6ioYoZj7ILy4PrNJrQPAb4uXKzXyQyn0rKwNS+WR/DIqoH1MYBU6cJe
2elYw7lM7e/ekDj4t/tkdX3pVr8AAAD//wMAUEsDBBQABgAIAAAAIQBnIN5n3gAAAAwBAAAPAAAA
ZHJzL2Rvd25yZXYueG1sTE/NTsMwDL4j8Q6RkbigLWETW+maTgjEDtzYeICsMW1E45Qk28qeHu8E
F8v2Z38/1Xr0vThiTC6QhvupAoHUBOuo1fCxe50UIFI2ZE0fCDX8YIJ1fX1VmdKGE73jcZtbwSSU
SqOhy3kopUxNh96kaRiQGPsM0ZvMY2yljebE5L6XM6UW0htHrNCZAZ87bL62B6/BL9/Uzp0j3i1V
cy7cN20WG9L69mZ8WXF5WoHIOOa/D7hkYP9Qs7F9OJBNotcwmfEhrx9AXNC5egSx50bNC5B1Jf+H
qH8BAAD//wMAUEsBAi0AFAAGAAgAAAAhALaDOJL+AAAA4QEAABMAAAAAAAAAAAAAAAAAAAAAAFtD
b250ZW50X1R5cGVzXS54bWxQSwECLQAUAAYACAAAACEAOP0h/9YAAACUAQAACwAAAAAAAAAAAAAA
AAAvAQAAX3JlbHMvLnJlbHNQSwECLQAUAAYACAAAACEASc3npm4CAAAxBQAADgAAAAAAAAAAAAAA
AAAuAgAAZHJzL2Uyb0RvYy54bWxQSwECLQAUAAYACAAAACEAZyDeZ94AAAAMAQAADwAAAAAAAAAA
AAAAAADIBAAAZHJzL2Rvd25yZXYueG1sUEsFBgAAAAAEAAQA8wAAANMFAAAAAA==
"
|
||||
filled="f" strokecolor="#c00000" strokeweight="1.5pt">
|
||||
<v:stroke joinstyle="miter" />
|
||||
</v:oval>
|
||||
@ -494,9 +497,9 @@
|
||||
xmlns:sl="http://schemas.openxmlformats.org/schemaLibrary/2006/main"
|
||||
mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh">
|
||||
<w:zoom w:percent="120" />
|
||||
<w:doNotDisplayPageBoundaries />
|
||||
<w:bordersDoNotSurroundHeader />
|
||||
<w:bordersDoNotSurroundFooter />
|
||||
<w:proofState w:spelling="clean" w:grammar="clean" />
|
||||
<w:defaultTabStop w:val="420" />
|
||||
<w:drawingGridVerticalSpacing w:val="156" />
|
||||
<w:displayHorizontalDrawingGridEvery w:val="0" />
|
||||
@ -526,7 +529,11 @@
|
||||
<w:rsids>
|
||||
<w:rsidRoot w:val="003456C4" />
|
||||
<w:rsid w:val="003456C4" />
|
||||
<w:rsid w:val="008B0168" />
|
||||
<w:rsid w:val="0097768D" />
|
||||
<w:rsid w:val="00C406BE" />
|
||||
<w:rsid w:val="00D127B0" />
|
||||
<w:rsid w:val="00E010D9" />
|
||||
</w:rsids>
|
||||
<m:mathPr>
|
||||
<m:mathFont m:val="Cambria Math" />
|
||||
@ -546,6 +553,12 @@
|
||||
w:accent1="accent1" w:accent2="accent2" w:accent3="accent3" w:accent4="accent4"
|
||||
w:accent5="accent5" w:accent6="accent6" w:hyperlink="hyperlink"
|
||||
w:followedHyperlink="followedHyperlink" />
|
||||
<w:shapeDefaults>
|
||||
<o:shapedefaults v:ext="edit" spidmax="1026" />
|
||||
<o:shapelayout v:ext="edit">
|
||||
<o:idmap v:ext="edit" data="1" />
|
||||
</o:shapelayout>
|
||||
</w:shapeDefaults>
|
||||
<w:decimalSymbol w:val="." />
|
||||
<w:listSeparator w:val="," />
|
||||
<w14:docId w14:val="0C40186C" />
|
||||
@ -568,9 +581,9 @@
|
||||
<cp:keywords />
|
||||
<dc:description />
|
||||
<cp:lastModifiedBy>administrator</cp:lastModifiedBy>
|
||||
<cp:revision>2</cp:revision>
|
||||
<cp:revision>6</cp:revision>
|
||||
<dcterms:created xsi:type="dcterms:W3CDTF">2023-03-28T09:22:00Z</dcterms:created>
|
||||
<dcterms:modified xsi:type="dcterms:W3CDTF">2023-03-28T09:22:00Z</dcterms:modified>
|
||||
<dcterms:modified xsi:type="dcterms:W3CDTF">2023-04-10T10:44:00Z</dcterms:modified>
|
||||
</cp:coreProperties>
|
||||
</pkg:xmlData>
|
||||
</pkg:part>
|
||||
@ -1115,7 +1128,7 @@
|
||||
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"
|
||||
xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
|
||||
<Template>Normal.dotm</Template>
|
||||
<TotalTime>0</TotalTime>
|
||||
<TotalTime>2</TotalTime>
|
||||
<Pages>1</Pages>
|
||||
<Words>0</Words>
|
||||
<Characters>1</Characters>
|
||||
|
1165
packages/ooxml-viewer/__tests__/docx/simple/shape-rightArrow.xml
Normal file
1165
packages/ooxml-viewer/__tests__/docx/simple/shape-rightArrow.xml
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
1203
packages/ooxml-viewer/__tests__/docx/unsupport/arrow.xml
Normal file
1203
packages/ooxml-viewer/__tests__/docx/unsupport/arrow.xml
Normal file
File diff suppressed because it is too large
Load Diff
@ -676,8 +676,19 @@ border-right: 0.67px solid black;
|
||||
class="docx-viewer-0-r"
|
||||
>
|
||||
<div
|
||||
style="z-index: 251664384; position: absolute; left: 482.39px; top: -145.60px; width: 341.99px; height: 1205.97px; background-color: rgb(245, 122, 21); column-count: 1;"
|
||||
/>
|
||||
style="z-index: 251664384; position: absolute; left: 482.39px; top: -145.60px; width: 341.99px; height: 1205.97px; column-count: 1;"
|
||||
>
|
||||
<svg
|
||||
height="1205.97px"
|
||||
style="overflow: visible"
|
||||
width="341.99px"
|
||||
>
|
||||
<path
|
||||
d="M 0 0 L 341.99 6.184461538461538 L 341.99 1191.5395897435897 L 87.9974269005848 1205.97 L 0 0 Z"
|
||||
fill="#F57A15"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</span>
|
||||
</p>
|
||||
</section>
|
||||
|
@ -97,8 +97,20 @@ padding-right: 7.20px;
|
||||
class="docx-viewer-0-r"
|
||||
>
|
||||
<div
|
||||
style="z-index: 251659264; position: absolute; left: 144.00px; top: 77.80px; width: 122.79px; height: 53.58px; column-count: 1; background-color: rgb(255, 255, 255); border-width: 0.67px; border-style: solid;"
|
||||
style="z-index: 251659264; position: absolute; left: 144.00px; top: 77.80px; width: 122.79px; height: 53.58px; column-count: 1;"
|
||||
>
|
||||
<svg
|
||||
height="53.58px"
|
||||
style="overflow: visible"
|
||||
width="122.79px"
|
||||
>
|
||||
<path
|
||||
d="M 0 0 L 122.79 0 L 122.79 53.58 L 0 53.58 Z"
|
||||
fill="#FFFFFF"
|
||||
stroke="black"
|
||||
stroke-width="0.67px"
|
||||
/>
|
||||
</svg>
|
||||
<p
|
||||
class="docx-viewer-0-p"
|
||||
>
|
||||
@ -119,8 +131,18 @@ padding-right: 7.20px;
|
||||
class="docx-viewer-0-r"
|
||||
>
|
||||
<div
|
||||
style="z-index: 251658239; position: absolute; left: 122.79px; top: 64.41px; width: 164.09px; height: 84.84px; background-color: rgb(112, 173, 71); column-count: 1; border-style: solid;"
|
||||
style="z-index: 251658239; position: absolute; left: 122.79px; top: 64.41px; width: 164.09px; height: 84.84px; column-count: 1;"
|
||||
>
|
||||
<svg
|
||||
height="84.84px"
|
||||
style="overflow: visible"
|
||||
width="164.09px"
|
||||
>
|
||||
<path
|
||||
d="M 0 0 L 164.09 0 L 164.09 84.84 L 0 84.84 Z"
|
||||
fill="#70AD47"
|
||||
/>
|
||||
</svg>
|
||||
<p
|
||||
class="docx-viewer-0-p"
|
||||
>
|
||||
|
@ -97,8 +97,18 @@ padding-right: 7.20px;
|
||||
class="docx-viewer-0-r"
|
||||
>
|
||||
<div
|
||||
style="z-index: 251661312; position: absolute; left: 214.63px; top: 36.07px; width: 155.16px; height: 82.60px; column-count: 1; background-color: rgb(255, 255, 255); transform: rotate(90deg);"
|
||||
style="z-index: 251661312; position: absolute; left: 214.63px; top: 36.07px; width: 155.16px; height: 82.60px; column-count: 1; transform: rotate(90deg);"
|
||||
>
|
||||
<svg
|
||||
height="82.6px"
|
||||
style="overflow: visible"
|
||||
width="155.16px"
|
||||
>
|
||||
<path
|
||||
d="M 0 0 L 155.16 0 L 155.16 82.6 L 0 82.6 Z"
|
||||
fill="#FFFFFF"
|
||||
/>
|
||||
</svg>
|
||||
<p
|
||||
class="docx-viewer-0-p"
|
||||
>
|
||||
|
@ -101,8 +101,18 @@ padding-right: 7.20px;
|
||||
class="docx-viewer-0-r"
|
||||
>
|
||||
<div
|
||||
style="z-index: 251671552; position: absolute; left: 10.05px; top: 439.47px; width: 233.30px; height: 158.46px; background-color: rgb(23, 41, 72); column-count: 1;"
|
||||
style="z-index: 251671552; position: absolute; left: 10.05px; top: 439.47px; width: 233.30px; height: 158.46px; column-count: 1;"
|
||||
>
|
||||
<svg
|
||||
height="158.46px"
|
||||
style="overflow: visible"
|
||||
width="233.3px"
|
||||
>
|
||||
<path
|
||||
d="M 0 0 L 233.3 0 L 233.3 158.46 L 0 158.46 Z"
|
||||
fill="#172948"
|
||||
/>
|
||||
</svg>
|
||||
<p
|
||||
class="docx-viewer-0-p"
|
||||
style="text-align: center;"
|
||||
@ -132,8 +142,18 @@ padding-right: 7.20px;
|
||||
class="docx-viewer-0-r"
|
||||
>
|
||||
<div
|
||||
style="z-index: 251669504; position: absolute; left: 279.06px; top: 249.71px; width: 195.33px; height: 189.76px; background-color: rgb(23, 41, 72); writing-mode: vertical-rl; text-orientation: mixed; column-count: 1;"
|
||||
style="z-index: 251669504; position: absolute; left: 279.06px; top: 249.71px; width: 195.33px; height: 189.76px; writing-mode: vertical-rl; text-orientation: mixed; column-count: 1;"
|
||||
>
|
||||
<svg
|
||||
height="189.76px"
|
||||
style="overflow: visible"
|
||||
width="195.33px"
|
||||
>
|
||||
<path
|
||||
d="M 0 0 L 195.33 0 L 195.33 189.76 L 0 189.76 Z"
|
||||
fill="#172948"
|
||||
/>
|
||||
</svg>
|
||||
<p
|
||||
class="docx-viewer-0-p"
|
||||
style="text-align: center;"
|
||||
@ -163,8 +183,18 @@ padding-right: 7.20px;
|
||||
class="docx-viewer-0-r"
|
||||
>
|
||||
<div
|
||||
style="z-index: 251667456; position: absolute; left: 16.00px; top: 174.55px; width: 195.33px; height: 158.51px; background-color: rgb(23, 41, 72); writing-mode: vertical-rl; text-orientation: sideways; column-count: 1;"
|
||||
style="z-index: 251667456; position: absolute; left: 16.00px; top: 174.55px; width: 195.33px; height: 158.51px; writing-mode: vertical-rl; text-orientation: sideways; column-count: 1;"
|
||||
>
|
||||
<svg
|
||||
height="158.51px"
|
||||
style="overflow: visible"
|
||||
width="195.33px"
|
||||
>
|
||||
<path
|
||||
d="M 0 0 L 195.33 0 L 195.33 158.51 L 0 158.51 Z"
|
||||
fill="#172948"
|
||||
/>
|
||||
</svg>
|
||||
<p
|
||||
class="docx-viewer-0-p"
|
||||
style="text-align: center;"
|
||||
@ -194,8 +224,18 @@ padding-right: 7.20px;
|
||||
class="docx-viewer-0-r"
|
||||
>
|
||||
<div
|
||||
style="z-index: 251659264; position: absolute; left: 279.06px; top: 28.68px; width: 195.33px; height: 158.51px; background-color: rgb(23, 41, 72); writing-mode: vertical-rl; text-orientation: mixed; column-count: 1;"
|
||||
style="z-index: 251659264; position: absolute; left: 279.06px; top: 28.68px; width: 195.33px; height: 158.51px; writing-mode: vertical-rl; text-orientation: mixed; column-count: 1;"
|
||||
>
|
||||
<svg
|
||||
height="158.51px"
|
||||
style="overflow: visible"
|
||||
width="195.33px"
|
||||
>
|
||||
<path
|
||||
d="M 0 0 L 195.33 0 L 195.33 158.51 L 0 158.51 Z"
|
||||
fill="#172948"
|
||||
/>
|
||||
</svg>
|
||||
<p
|
||||
class="docx-viewer-0-p"
|
||||
style="text-align: center;"
|
||||
@ -213,8 +253,18 @@ padding-right: 7.20px;
|
||||
class="docx-viewer-0-r"
|
||||
>
|
||||
<div
|
||||
style="z-index: 251665408; position: absolute; left: 16.00px; top: 20.46px; width: 195.34px; height: 70.32px; background-color: rgb(23, 41, 72); column-count: 1;"
|
||||
style="z-index: 251665408; position: absolute; left: 16.00px; top: 20.46px; width: 195.34px; height: 70.32px; column-count: 1;"
|
||||
>
|
||||
<svg
|
||||
height="70.32px"
|
||||
style="overflow: visible"
|
||||
width="195.34px"
|
||||
>
|
||||
<path
|
||||
d="M 0 0 L 195.34 0 L 195.34 70.32 L 0 70.32 Z"
|
||||
fill="#172948"
|
||||
/>
|
||||
</svg>
|
||||
<p
|
||||
class="docx-viewer-0-p"
|
||||
style="text-align: center;"
|
||||
@ -232,8 +282,18 @@ padding-right: 7.20px;
|
||||
class="docx-viewer-0-r"
|
||||
>
|
||||
<div
|
||||
style="z-index: 251663360; position: absolute; left: 16.00px; top: -75.53px; width: 195.34px; height: 70.32px; background-color: rgb(23, 41, 72); column-count: 1;"
|
||||
style="z-index: 251663360; position: absolute; left: 16.00px; top: -75.53px; width: 195.34px; height: 70.32px; column-count: 1;"
|
||||
>
|
||||
<svg
|
||||
height="70.32px"
|
||||
style="overflow: visible"
|
||||
width="195.34px"
|
||||
>
|
||||
<path
|
||||
d="M 0 0 L 195.34 0 L 195.34 70.32 L 0 70.32 Z"
|
||||
fill="#172948"
|
||||
/>
|
||||
</svg>
|
||||
<p
|
||||
class="docx-viewer-0-p"
|
||||
style="text-align: center;"
|
||||
@ -251,8 +311,18 @@ padding-right: 7.20px;
|
||||
class="docx-viewer-0-r"
|
||||
>
|
||||
<div
|
||||
style="z-index: 251661312; position: absolute; left: 16.00px; top: -75.53px; width: 195.34px; height: 70.32px; background-color: rgb(23, 41, 72); column-count: 1;"
|
||||
style="z-index: 251661312; position: absolute; left: 16.00px; top: -75.53px; width: 195.34px; height: 70.32px; column-count: 1;"
|
||||
>
|
||||
<svg
|
||||
height="70.32px"
|
||||
style="overflow: visible"
|
||||
width="195.34px"
|
||||
>
|
||||
<path
|
||||
d="M 0 0 L 195.34 0 L 195.34 70.32 L 0 70.32 Z"
|
||||
fill="#172948"
|
||||
/>
|
||||
</svg>
|
||||
<p
|
||||
class="docx-viewer-0-p"
|
||||
style="text-align: center;"
|
||||
|
@ -97,8 +97,19 @@ padding-right: 7.20px;
|
||||
class="docx-viewer-0-r"
|
||||
>
|
||||
<div
|
||||
style="z-index: 251663360; position: absolute; left: 329.67px; top: 137.67px; width: 155.16px; height: 82.60px; background-color: rgb(255, 255, 255); column-count: 1; border-style: solid; border-color: #ed7d31;"
|
||||
style="z-index: 251663360; position: absolute; left: 329.67px; top: 137.67px; width: 155.16px; height: 82.60px; column-count: 1;"
|
||||
>
|
||||
<svg
|
||||
height="82.6px"
|
||||
style="overflow: visible"
|
||||
width="155.16px"
|
||||
>
|
||||
<path
|
||||
d="M 0 0 L 155.16 0 L 155.16 82.6 L 0 82.6 Z"
|
||||
fill="#FFFFFF"
|
||||
stroke="#ED7D31"
|
||||
/>
|
||||
</svg>
|
||||
<p
|
||||
class="docx-viewer-0-p"
|
||||
>
|
||||
@ -119,8 +130,20 @@ padding-right: 7.20px;
|
||||
class="docx-viewer-0-r"
|
||||
>
|
||||
<div
|
||||
style="z-index: 251659264; position: absolute; left: 47.94px; top: 139.17px; width: 155.16px; height: 82.60px; column-count: 1; background-color: rgb(255, 255, 255); border-width: 0.67px; border-style: solid;"
|
||||
style="z-index: 251659264; position: absolute; left: 47.94px; top: 139.17px; width: 155.16px; height: 82.60px; column-count: 1;"
|
||||
>
|
||||
<svg
|
||||
height="82.6px"
|
||||
style="overflow: visible"
|
||||
width="155.16px"
|
||||
>
|
||||
<path
|
||||
d="M 0 0 L 155.16 0 L 155.16 82.6 L 0 82.6 Z"
|
||||
fill="#FFFFFF"
|
||||
stroke="black"
|
||||
stroke-width="0.67px"
|
||||
/>
|
||||
</svg>
|
||||
<p
|
||||
class="docx-viewer-0-p"
|
||||
>
|
||||
@ -136,8 +159,18 @@ padding-right: 7.20px;
|
||||
class="docx-viewer-0-r"
|
||||
>
|
||||
<div
|
||||
style="z-index: 251661312; position: absolute; left: 214.63px; top: 36.07px; width: 155.16px; height: 82.60px; column-count: 1; background-color: rgb(255, 255, 255);"
|
||||
style="z-index: 251661312; position: absolute; left: 214.63px; top: 36.07px; width: 155.16px; height: 82.60px; column-count: 1;"
|
||||
>
|
||||
<svg
|
||||
height="82.6px"
|
||||
style="overflow: visible"
|
||||
width="155.16px"
|
||||
>
|
||||
<path
|
||||
d="M 0 0 L 155.16 0 L 155.16 82.6 L 0 82.6 Z"
|
||||
fill="#FFFFFF"
|
||||
/>
|
||||
</svg>
|
||||
<p
|
||||
class="docx-viewer-0-p"
|
||||
>
|
||||
|
@ -28,6 +28,11 @@ const fileLists = {
|
||||
'svg.xml',
|
||||
'sym.xml',
|
||||
'shadow.xml',
|
||||
'shape-ellipse.xml',
|
||||
'shape-custom.xml',
|
||||
'shape-custom.xml',
|
||||
'all-shape-1.xml',
|
||||
'all-shape-2.xml',
|
||||
'tableborder.xml',
|
||||
'tablestyle.xml',
|
||||
'textbox.xml',
|
||||
|
14
packages/ooxml-viewer/examples/presetShape.html
Normal file
14
packages/ooxml-viewer/examples/presetShape.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Preset Shape</title>
|
||||
<link rel="stylesheet" href="./static/css/app.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="shapes"></div>
|
||||
|
||||
<script type="module" src="./presetShape.ts"></script>
|
||||
</body>
|
||||
</html>
|
32
packages/ooxml-viewer/examples/presetShape.ts
Normal file
32
packages/ooxml-viewer/examples/presetShape.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import {presetShape} from '../src/openxml/word/drawing/presetShape';
|
||||
import {shapeToSVG} from '../src/openxml/word/drawing/svg/shapeToSVG';
|
||||
|
||||
const container = document.getElementById('shapes')! as HTMLElement;
|
||||
|
||||
for (const shapeName in presetShape) {
|
||||
// 临时测试用
|
||||
if (shapeName !== 'smileyFace') {
|
||||
// continue;
|
||||
}
|
||||
const shape = presetShape[shapeName];
|
||||
console.log('render shape', shapeName);
|
||||
const svg = shapeToSVG(shape, [], {}, 100, 100, {
|
||||
fillColor: '#4472C4'
|
||||
});
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.style.display = 'inline-block';
|
||||
div.style.margin = '12px 40px';
|
||||
div.style.width = '100px';
|
||||
div.style.height = '120px';
|
||||
div.style.boxSizing = 'border-box';
|
||||
div.style.verticalAlign = 'top';
|
||||
div.style.textAlign = 'center';
|
||||
|
||||
div.appendChild(svg);
|
||||
const p = document.createElement('p');
|
||||
|
||||
p.innerText = shapeName;
|
||||
div.appendChild(p);
|
||||
container.appendChild(div);
|
||||
}
|
@ -14,7 +14,8 @@
|
||||
"coverage": "jest --coverage",
|
||||
"declaration": "tsc --project tsconfig-for-declaration.json --allowJs --declaration --emitDeclarationOnly --declarationDir ./lib --rootDir ./src",
|
||||
"clean-dist": "rimraf lib/** esm/**",
|
||||
"xsd2ts": "cd tools && ts-node --transpileOnly xsd2ts.ts"
|
||||
"xsd2ts": "cd tools && ts-node --transpileOnly xsd2ts.ts",
|
||||
"conver-preset": "cd tools && ts-node --transpileOnly converDrawingML.ts"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
|
@ -6,7 +6,12 @@
|
||||
* 获取 w:val 的值
|
||||
*/
|
||||
export function getVal(element: Element) {
|
||||
return element.getAttribute('w:val') || element.getAttribute('w14:val') || '';
|
||||
return (
|
||||
element.getAttribute('w:val') ||
|
||||
element.getAttribute('w14:val') ||
|
||||
element.getAttribute('val') ||
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
17
packages/ooxml-viewer/src/openxml/word/drawing/CustomGeom.ts
Normal file
17
packages/ooxml-viewer/src/openxml/word/drawing/CustomGeom.ts
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 自定义形状
|
||||
*/
|
||||
|
||||
import Word from '../../../Word';
|
||||
import {parseShape} from '../../../parse/parseShape';
|
||||
import {Shape} from './Shape';
|
||||
|
||||
export class CustomGeom {
|
||||
shape: Shape;
|
||||
|
||||
static fromXML(word: Word, element: Element): CustomGeom {
|
||||
const geom = new CustomGeom();
|
||||
geom.shape = parseShape(element);
|
||||
return geom;
|
||||
}
|
||||
}
|
@ -1,17 +1,28 @@
|
||||
/**
|
||||
* 形状,目前大部分不支持
|
||||
* 形状
|
||||
*/
|
||||
import {ST_ShapeType} from '../../Types';
|
||||
import Word from '../../../Word';
|
||||
import {CSSStyle} from './../../Style';
|
||||
import {parseShapeGuide} from '../../../parse/parseShape';
|
||||
import {ShapeGuide} from './Shape';
|
||||
|
||||
export class Geom {
|
||||
type: ST_ShapeType;
|
||||
prst: ST_ShapeType;
|
||||
avLst?: ShapeGuide[];
|
||||
|
||||
static fromXML(word: Word, element: Element, style: CSSStyle): Geom {
|
||||
static fromXML(word: Word, element: Element): Geom {
|
||||
const geom = new Geom();
|
||||
geom.type = element.getAttribute('prst') as ST_ShapeType;
|
||||
// 后面得改成用 SVG 实现
|
||||
geom.prst = element.getAttribute('prst') as ST_ShapeType;
|
||||
|
||||
for (const child of element.children) {
|
||||
const tagName = child.tagName;
|
||||
switch (tagName) {
|
||||
case 'a:avLst': {
|
||||
geom.avLst = parseShapeGuide(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return geom;
|
||||
}
|
||||
}
|
||||
|
52
packages/ooxml-viewer/src/openxml/word/drawing/Path.ts
Normal file
52
packages/ooxml-viewer/src/openxml/word/drawing/Path.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import {ST_PathFillMode} from '../../Types';
|
||||
|
||||
export interface IPath {
|
||||
type: 'moveTo' | 'lnTo' | 'arcTo' | 'cubicBezTo' | 'quadBezTo' | 'close';
|
||||
}
|
||||
|
||||
export interface PathPoint {
|
||||
x: string;
|
||||
y: string;
|
||||
}
|
||||
|
||||
export interface LnTo extends IPath {
|
||||
pt: PathPoint;
|
||||
}
|
||||
|
||||
export interface MoveTo extends IPath {
|
||||
pt: PathPoint;
|
||||
}
|
||||
|
||||
export interface ArcTo extends IPath {
|
||||
wR: string;
|
||||
hR: string;
|
||||
stAng: string;
|
||||
swAng: string;
|
||||
}
|
||||
|
||||
export interface QuadBezTo extends IPath {
|
||||
pts: PathPoint[];
|
||||
}
|
||||
|
||||
export interface CubicBezTo extends IPath {
|
||||
pts: PathPoint[];
|
||||
}
|
||||
|
||||
export interface Close extends IPath {}
|
||||
|
||||
export type ShapeDefine =
|
||||
| MoveTo
|
||||
| LnTo
|
||||
| ArcTo
|
||||
| QuadBezTo
|
||||
| CubicBezTo
|
||||
| Close;
|
||||
|
||||
export interface Path {
|
||||
h?: number;
|
||||
w?: number;
|
||||
fill?: ST_PathFillMode;
|
||||
extrusionOk?: boolean;
|
||||
stroke?: boolean;
|
||||
defines: ShapeDefine[];
|
||||
}
|
29
packages/ooxml-viewer/src/openxml/word/drawing/Shape.ts
Normal file
29
packages/ooxml-viewer/src/openxml/word/drawing/Shape.ts
Normal file
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Shape 相关的定义
|
||||
*/
|
||||
|
||||
import {Path} from './Path';
|
||||
|
||||
export interface ShapeGuide {
|
||||
// 名称
|
||||
n: string;
|
||||
// 公式
|
||||
f: string;
|
||||
}
|
||||
|
||||
// http://webapp.docx4java.org/OnlineDemo/ecma376/DrawingML/rect.html
|
||||
export interface Rect {
|
||||
b: string;
|
||||
l: string;
|
||||
r: string;
|
||||
t: string;
|
||||
}
|
||||
|
||||
export interface Shape {
|
||||
avLst?: ShapeGuide[];
|
||||
gdLst?: ShapeGuide[];
|
||||
|
||||
rect?: Rect;
|
||||
|
||||
pathLst?: Path[];
|
||||
}
|
@ -9,6 +9,7 @@ import {CSSStyle} from './../../Style';
|
||||
import {parseSize, LengthUsage} from '../../../parse/parseSize';
|
||||
import {Geom} from './Geom';
|
||||
import {parseChildColor} from '../../../parse/parseChildColor';
|
||||
import {CustomGeom} from './CustomGeom';
|
||||
|
||||
function prstDashToCSSBorderType(prstDash: ST_PresetLineDashVal) {
|
||||
let borderType = 'solid';
|
||||
@ -32,29 +33,39 @@ function prstDashToCSSBorderType(prstDash: ST_PresetLineDashVal) {
|
||||
return borderType;
|
||||
}
|
||||
|
||||
function parseOutline(word: Word, element: Element, style: CSSStyle) {
|
||||
export type OutLine = {
|
||||
style?: 'solid' | 'dashed' | 'dotted' | string;
|
||||
width?: string;
|
||||
color?: string;
|
||||
radius?: string;
|
||||
};
|
||||
|
||||
function parseOutline(word: Word, element: Element) {
|
||||
const borderWidth = parseSize(element, 'w', LengthUsage.Emu);
|
||||
style['border-width'] = borderWidth;
|
||||
style['border-style'] = 'solid';
|
||||
const outline: OutLine = {
|
||||
width: borderWidth
|
||||
};
|
||||
|
||||
outline.style = 'solid';
|
||||
|
||||
for (const child of element.children) {
|
||||
const tagName = child.tagName;
|
||||
switch (tagName) {
|
||||
case 'a:solidFill':
|
||||
style['border-color'] = parseChildColor(word, child);
|
||||
outline.color = parseChildColor(word, child);
|
||||
|
||||
break;
|
||||
|
||||
case 'a:noFill':
|
||||
style['border'] = 'none';
|
||||
break;
|
||||
return {};
|
||||
|
||||
case 'a:round':
|
||||
// 瞎写的,规范里也没写是多少
|
||||
style['border-radius'] = '8%';
|
||||
outline.radius = '8%';
|
||||
break;
|
||||
|
||||
case 'a:prstDash':
|
||||
style['border-style'] = prstDashToCSSBorderType(
|
||||
outline.style = prstDashToCSSBorderType(
|
||||
child.getAttribute('val') as ST_PresetLineDashVal
|
||||
);
|
||||
break;
|
||||
@ -63,21 +74,28 @@ function parseOutline(word: Word, element: Element, style: CSSStyle) {
|
||||
console.warn('parseOutline: Unknown tag ', tagName, child);
|
||||
}
|
||||
}
|
||||
|
||||
return outline;
|
||||
}
|
||||
|
||||
export class ShapePr {
|
||||
xfrm?: Transform;
|
||||
|
||||
// 内置图形
|
||||
prstGeom?: Geom;
|
||||
geom?: Geom;
|
||||
|
||||
// 主要是边框样式
|
||||
style?: CSSStyle;
|
||||
// 自定义
|
||||
custGeom?: CustomGeom;
|
||||
|
||||
// 边框样式
|
||||
outline?: OutLine;
|
||||
|
||||
// 填充颜色
|
||||
fillColor?: string;
|
||||
|
||||
static fromXML(word: Word, element?: Element | null): ShapePr {
|
||||
const shapePr = new ShapePr();
|
||||
const style: CSSStyle = {};
|
||||
shapePr.style = style;
|
||||
|
||||
if (element) {
|
||||
for (const child of element.children) {
|
||||
const tagName = child.tagName;
|
||||
@ -87,12 +105,16 @@ export class ShapePr {
|
||||
break;
|
||||
|
||||
case 'a:prstGeom':
|
||||
shapePr.prstGeom = Geom.fromXML(word, child, style);
|
||||
shapePr.geom = Geom.fromXML(word, child);
|
||||
break;
|
||||
|
||||
case 'a:custGeom':
|
||||
shapePr.custGeom = CustomGeom.fromXML(word, child);
|
||||
break;
|
||||
|
||||
case 'a:ln':
|
||||
// http://officeopenxml.com/drwSp-outline.php
|
||||
parseOutline(word, child, style);
|
||||
shapePr.outline = parseOutline(word, child);
|
||||
break;
|
||||
|
||||
case 'a:noFill':
|
||||
@ -100,7 +122,7 @@ export class ShapePr {
|
||||
break;
|
||||
|
||||
case 'a:solidFill':
|
||||
style['background-color'] = parseChildColor(word, child);
|
||||
shapePr.fillColor = parseChildColor(word, child);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
38785
packages/ooxml-viewer/src/openxml/word/drawing/presetShape.ts
Normal file
38785
packages/ooxml-viewer/src/openxml/word/drawing/presetShape.ts
Normal file
File diff suppressed because it is too large
Load Diff
122
packages/ooxml-viewer/src/openxml/word/drawing/svg/arcToA.ts
Normal file
122
packages/ooxml-viewer/src/openxml/word/drawing/svg/arcToA.ts
Normal file
@ -0,0 +1,122 @@
|
||||
/**
|
||||
* 将 arc 定义转成 SVG PATH 里的 A 命令
|
||||
*/
|
||||
|
||||
function floatEqual(a: number, b: number) {
|
||||
if (a === b) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const diff = Math.abs(a - b);
|
||||
|
||||
if (diff < Number.EPSILON) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return diff <= Number.EPSILON * Math.min(Math.abs(a), Math.abs(b));
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算角度
|
||||
*/
|
||||
const radians = (deg: number) => Math.PI * (deg / 60000 / 180);
|
||||
|
||||
/**
|
||||
* 将 arc 转成 A 指令,但目前看来不太正确,比如 curvedRightArrow 的显示就不对
|
||||
*/
|
||||
export default function arcToPathA(
|
||||
wR: number,
|
||||
hR: number,
|
||||
stAng: number,
|
||||
swAng: number,
|
||||
preX: number,
|
||||
preY: number
|
||||
) {
|
||||
let startR = radians(stAng);
|
||||
let swAngR = radians(swAng);
|
||||
let endR = radians(stAng + swAng);
|
||||
|
||||
if (floatEqual(swAng, 60000 * 360)) {
|
||||
// 如果是圆会变成点,所以减少一下避免这种情况
|
||||
endR = endR - 0.0001;
|
||||
}
|
||||
|
||||
const end = getEndPoint(wR, hR, startR, endR, 0, preX, preY);
|
||||
|
||||
// 是否是大弧
|
||||
const largeArcFlag = Math.abs(swAngR) > Math.PI ? 1 : 0;
|
||||
// 是否是顺时针
|
||||
const sweepFlag = swAng > 0 ? 1 : 0;
|
||||
|
||||
const path = `A ${wR} ${hR} 0 ${largeArcFlag} ${sweepFlag} ${end.x},${end.y}`;
|
||||
|
||||
return {
|
||||
path,
|
||||
end
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 简单实现的矩阵相乘,只支持一种输入
|
||||
*/
|
||||
function matrixMul(first: number[][], second: number[]) {
|
||||
return [
|
||||
first[0][0] * second[0] + first[0][1] * second[1],
|
||||
first[1][0] * second[0] + first[1][1] * second[1]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 算出结束点位置,公式来自
|
||||
* https://www.cnblogs.com/ryzen/p/15191386.html
|
||||
* https://wiki.documentfoundation.org/Development/Improve_handles_of_DrawingML_shapes
|
||||
*
|
||||
* @param rx 半长轴半径
|
||||
* @param ry 半短轴半径
|
||||
* @param stAng 起始角度
|
||||
* @param swAng 旋转角度
|
||||
* @param rotate 旋转
|
||||
* @param x 起始点 x 坐标
|
||||
* @param y 起始点 y 坐标
|
||||
* @returns 结束点位置
|
||||
*/
|
||||
function getEndPoint(
|
||||
rx: number,
|
||||
ry: number,
|
||||
stAng: number,
|
||||
swAng: number,
|
||||
rotate: number,
|
||||
x: number,
|
||||
y: number
|
||||
) {
|
||||
let startR = stAng;
|
||||
let endR = swAng;
|
||||
|
||||
// 起始节点坐标
|
||||
const matrixX1Y1 = [x, y];
|
||||
const matrix1 = [
|
||||
[Math.cos(rotate), -Math.sin(rotate)],
|
||||
[Math.sin(rotate), Math.cos(rotate)]
|
||||
];
|
||||
|
||||
const matrix2 = [rx * Math.cos(startR), ry * Math.sin(startR)];
|
||||
|
||||
// 公式第二部分
|
||||
const secondPart = matrixMul(matrix1, matrix2);
|
||||
|
||||
const matrixCxCy = [
|
||||
matrixX1Y1[0] - secondPart[0],
|
||||
matrixX1Y1[1] - secondPart[1]
|
||||
];
|
||||
|
||||
const matrix3 = [rx * Math.cos(endR), ry * Math.sin(endR)];
|
||||
|
||||
const firstPart = matrixMul(matrix1, matrix3);
|
||||
|
||||
const result = [matrixCxCy[0] + firstPart[0], matrixCxCy[1] + firstPart[1]];
|
||||
|
||||
return {
|
||||
x: result[0],
|
||||
y: result[1]
|
||||
};
|
||||
}
|
109
packages/ooxml-viewer/src/openxml/word/drawing/svg/formulas.ts
Normal file
109
packages/ooxml-viewer/src/openxml/word/drawing/svg/formulas.ts
Normal file
@ -0,0 +1,109 @@
|
||||
/**
|
||||
* 计算公式 20.1.9.11
|
||||
*/
|
||||
|
||||
const angleFactor = (1 / 60000 / 180) * Math.PI;
|
||||
|
||||
export const formulas = {
|
||||
'*/': function (x: number, y: number, z: number) {
|
||||
return (x * y) / z;
|
||||
},
|
||||
'+-': function (x: number, y: number, z: number) {
|
||||
return x + y - z;
|
||||
},
|
||||
'+/': function (x: number, y: number, z: number) {
|
||||
return (x + y) / z;
|
||||
},
|
||||
'?:': function (x: number, y: number, z: number) {
|
||||
return x > 0 ? y : z;
|
||||
},
|
||||
'abs': function (x: number) {
|
||||
return Math.abs(x);
|
||||
},
|
||||
'at2': function (x: number, y: number) {
|
||||
// 转回角度,因为后续的计算是基于角度的
|
||||
return (Math.atan2(y, x) * 180 * 60000) / Math.PI;
|
||||
},
|
||||
'cat2': function (x: number, y: number, z: number) {
|
||||
return x * Math.cos(Math.atan2(z, y));
|
||||
},
|
||||
'cos': function (x: number, y: number) {
|
||||
return x * Math.cos(y * angleFactor);
|
||||
},
|
||||
'max': function (x: number, y: number) {
|
||||
return Math.max(x, y);
|
||||
},
|
||||
'min': function (x: number, y: number) {
|
||||
return Math.min(x, y);
|
||||
},
|
||||
'mod': function (x: number, y: number, z: number) {
|
||||
return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2) + Math.pow(z, 2));
|
||||
},
|
||||
'pin': function (x: number, y: number, z: number) {
|
||||
return y < x ? x : y > z ? z : y;
|
||||
},
|
||||
'sat2': function (x: number, y: number, z: number) {
|
||||
return x * Math.sin(Math.atan2(z, y));
|
||||
},
|
||||
'sin': function (x: number, y: number) {
|
||||
return x * Math.sin(y * angleFactor);
|
||||
},
|
||||
'sqrt': function (x: number) {
|
||||
return Math.sqrt(x);
|
||||
},
|
||||
'tan': function (x: number, y: number) {
|
||||
return x * Math.tan(y * angleFactor);
|
||||
},
|
||||
'val': function (a: string) {
|
||||
const parse = parseInt(a, 10);
|
||||
if (isNaN(parse)) {
|
||||
return parse;
|
||||
}
|
||||
return parse;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 执行公式计算并返回结果
|
||||
* @param fmla 公式
|
||||
* @param vars 变量值
|
||||
* @returns
|
||||
*/
|
||||
export function evalFmla(
|
||||
name: string,
|
||||
fmla: string,
|
||||
vars: Record<string, number>
|
||||
): number {
|
||||
const fmlaArr = fmla.split(/[ ]+/);
|
||||
if (fmlaArr.length <= 1) {
|
||||
console.warn('fmla format error', fmla);
|
||||
}
|
||||
const fmlaName = fmlaArr[0];
|
||||
const fmlaArgs = fmlaArr.slice(1);
|
||||
// 这里要求 gd 定义必须顺序,不然就找不到之前的值了
|
||||
const fmlaArgsNum = fmlaArgs.map(arg => {
|
||||
if (arg in vars) {
|
||||
return vars[arg];
|
||||
}
|
||||
const parse = parseInt(arg, 10);
|
||||
if (isNaN(parse)) {
|
||||
console.warn('fmla arg error', arg, fmla);
|
||||
return 0;
|
||||
} else {
|
||||
return parse;
|
||||
}
|
||||
});
|
||||
if (fmlaName in formulas) {
|
||||
const val = formulas[fmlaName as keyof typeof formulas].apply(
|
||||
null,
|
||||
fmlaArgsNum
|
||||
);
|
||||
if (isNaN(val)) {
|
||||
console.warn('fmla eval error', fmla, name);
|
||||
return 0;
|
||||
} else {
|
||||
vars[name] = val;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
import {ArcTo, LnTo, MoveTo, Path, QuadBezTo, ShapeDefine} from '../Path';
|
||||
import arcToPathA from './arcToA';
|
||||
|
||||
export type Var = Record<string, number>;
|
||||
|
||||
function getVal(name: string, vars: Var, scale?: number): number {
|
||||
let result = 0;
|
||||
if (name in vars) {
|
||||
result = vars[name];
|
||||
} else {
|
||||
result = parseInt(name, 10);
|
||||
if (isNaN(result)) {
|
||||
console.warn('var not found', name);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (scale) {
|
||||
return result * scale;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export type Point = {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* 转成 svg path 里的定义
|
||||
* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
|
||||
*/
|
||||
export function generateDefines(path: Path, vars: Var, prevPoint: Point[]) {
|
||||
const defines = path.defines;
|
||||
const paths: string[] = [];
|
||||
|
||||
const w = path.w;
|
||||
const h = path.h;
|
||||
let wScale = 1;
|
||||
let hScale = 1;
|
||||
if (w) {
|
||||
wScale = vars['w'] / w;
|
||||
}
|
||||
if (h) {
|
||||
hScale = vars['h'] / h;
|
||||
}
|
||||
for (const def of defines) {
|
||||
switch (def.type) {
|
||||
case 'moveTo': {
|
||||
const pt = (def as MoveTo).pt;
|
||||
const x = getVal(pt.x, vars, wScale);
|
||||
const y = getVal(pt.y, vars, hScale);
|
||||
paths.push(`M ${x} ${y}`);
|
||||
prevPoint.push({x, y});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'lnTo': {
|
||||
const pt = (def as LnTo).pt;
|
||||
const x = getVal(pt.x, vars, wScale);
|
||||
const y = getVal(pt.y, vars, hScale);
|
||||
paths.push(`L ${x} ${y}`);
|
||||
prevPoint.push({x, y});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'arcTo': {
|
||||
const arc = def as ArcTo;
|
||||
const wR = getVal(arc.wR, vars, wScale);
|
||||
const hR = getVal(arc.hR, vars, hScale);
|
||||
const stAng = getVal(arc.stAng, vars);
|
||||
const swAng = getVal(arc.swAng, vars);
|
||||
let prev = {
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
if (prevPoint.length > 0) {
|
||||
prev = prevPoint[prevPoint.length - 1];
|
||||
}
|
||||
|
||||
const aPath = arcToPathA(wR, hR, stAng, swAng, prev.x, prev.y);
|
||||
paths.push(aPath.path);
|
||||
prevPoint.push({x: aPath.end.x, y: aPath.end.y});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'quadBezTo': {
|
||||
const quadBezTo = def as QuadBezTo;
|
||||
if (quadBezTo.pts.length >= 2) {
|
||||
const pt1 = quadBezTo.pts[0];
|
||||
const pt2 = quadBezTo.pts[1];
|
||||
const x1 = getVal(pt1.x, vars, wScale);
|
||||
const y1 = getVal(pt1.y, vars, hScale);
|
||||
const x2 = getVal(pt2.x, vars, wScale);
|
||||
const y2 = getVal(pt2.y, vars, hScale);
|
||||
paths.push(`Q ${x1},${y1} ${x2},${y2}`);
|
||||
|
||||
if (quadBezTo.pts.length > 2) {
|
||||
const pt3 = quadBezTo.pts[2];
|
||||
const x3 = getVal(pt3.x, vars, wScale);
|
||||
const y3 = getVal(pt3.y, vars, hScale);
|
||||
paths.push(`T ${x3},${y3}`);
|
||||
prevPoint.push({x: x3, y: y3});
|
||||
} else {
|
||||
prevPoint.push({x: x2, y: y2});
|
||||
}
|
||||
} else {
|
||||
console.warn('quadBezTo pts length must large than 2', def);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'cubicBezTo': {
|
||||
const cubicBezTo = def as QuadBezTo;
|
||||
if (cubicBezTo.pts.length === 3) {
|
||||
const pt1 = cubicBezTo.pts[0];
|
||||
const pt2 = cubicBezTo.pts[1];
|
||||
const pt3 = cubicBezTo.pts[2];
|
||||
const x1 = getVal(pt1.x, vars, wScale);
|
||||
const y1 = getVal(pt1.y, vars, hScale);
|
||||
const x2 = getVal(pt2.x, vars, wScale);
|
||||
const y2 = getVal(pt2.y, vars, hScale);
|
||||
const x3 = getVal(pt3.x, vars, wScale);
|
||||
const y3 = getVal(pt3.y, vars, hScale);
|
||||
paths.push(`C ${x1},${y1} ${x2},${y2} ${x3},${y3}`);
|
||||
prevPoint.push({x: x3, y: y3});
|
||||
} else {
|
||||
console.warn('cubicBezTo pts length must be 3', def);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'close':
|
||||
paths.push('Z');
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return paths.join(' ');
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 获取默认 val,20.1.10.56
|
||||
*
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
export function presetVal(w: number, h: number) {
|
||||
const ss = Math.min(w, h);
|
||||
const ssd2 = ss / 6;
|
||||
const ssd6 = ss / 6;
|
||||
const ssd8 = ss / 8;
|
||||
const ssd32 = ss / 32;
|
||||
const ssd16 = ss / 16;
|
||||
return {
|
||||
't': 0,
|
||||
|
||||
'3cd4': 16200000,
|
||||
'3cd8': 8100000,
|
||||
'5cd8': 13500000,
|
||||
'7cd8': 18900000,
|
||||
'b': h,
|
||||
'cd2': 10800000,
|
||||
'cd4': 5400000,
|
||||
'cd8': 2700000,
|
||||
h,
|
||||
'hd2': h / 2,
|
||||
'hd3': h / 3,
|
||||
'hd4': h / 4,
|
||||
'hd6': h / 6,
|
||||
'hd8': h / 8,
|
||||
'l': 0,
|
||||
'ls': Math.max(w, h),
|
||||
'r': w,
|
||||
|
||||
ss,
|
||||
ssd2,
|
||||
ssd6,
|
||||
ssd8,
|
||||
ssd16,
|
||||
ssd32,
|
||||
|
||||
'hc': w / 2,
|
||||
|
||||
'vc': h / 2,
|
||||
w,
|
||||
'wd2': w / 2,
|
||||
'wd3': w / 3,
|
||||
'wd4': w / 4,
|
||||
'wd6': w / 6,
|
||||
'wd8': w / 8,
|
||||
'wd10': w / 10,
|
||||
'wd16': w / 16,
|
||||
'wd32': w / 32
|
||||
};
|
||||
}
|
123
packages/ooxml-viewer/src/openxml/word/drawing/svg/shapeToSVG.ts
Normal file
123
packages/ooxml-viewer/src/openxml/word/drawing/svg/shapeToSVG.ts
Normal file
@ -0,0 +1,123 @@
|
||||
/**
|
||||
* 将 shape 转成 svg 格式
|
||||
*
|
||||
* https://wiki.documentfoundation.org/Development/Improve_handles_of_DrawingML_shapes
|
||||
*/
|
||||
|
||||
import {Color} from '../../../../util/color';
|
||||
import {createSVGElement} from '../../../../util/dom';
|
||||
import {WPSStyle} from '../../wps/WPSStyle';
|
||||
import {Shape, ShapeGuide} from '../Shape';
|
||||
import {ShapePr} from '../ShapeProperties';
|
||||
import {evalFmla} from './formulas';
|
||||
import {Point, Var, generateDefines} from './generateDefines';
|
||||
import {presetVal} from './presetVal';
|
||||
|
||||
export function shapeToSVG(
|
||||
shape: Shape,
|
||||
avLst: ShapeGuide[],
|
||||
shapePr: ShapePr,
|
||||
width: number,
|
||||
height: number,
|
||||
wpsStyle?: WPSStyle
|
||||
): SVGElement {
|
||||
const svg = createSVGElement('svg');
|
||||
// 边框有时候会超过
|
||||
svg.setAttribute('style', 'overflow: visible');
|
||||
svg.setAttribute('width', width.toString() + 'px');
|
||||
svg.setAttribute('height', height.toString() + 'px');
|
||||
|
||||
// 变量值
|
||||
const vars: Var = presetVal(width, height);
|
||||
|
||||
// 先执行 avLst 定义初始变量
|
||||
for (const gd of shape.avLst || []) {
|
||||
evalFmla(gd.n, gd.f, vars);
|
||||
}
|
||||
|
||||
// 自定义 avLst
|
||||
for (const gd of avLst) {
|
||||
evalFmla(gd.n, gd.f, vars);
|
||||
}
|
||||
|
||||
// 执行 gdLst
|
||||
for (const gd of shape.gdLst || []) {
|
||||
evalFmla(gd.n, gd.f, vars);
|
||||
}
|
||||
|
||||
const outline = shapePr.outline;
|
||||
const prevPoint: Point[] = [];
|
||||
for (const path of shape.pathLst || []) {
|
||||
const pathEl = createSVGElement('path');
|
||||
const d = generateDefines(path, vars, prevPoint);
|
||||
pathEl.setAttribute('d', d);
|
||||
|
||||
if (shapePr.fillColor) {
|
||||
pathEl.setAttribute('fill', shapePr.fillColor);
|
||||
} else if (wpsStyle && wpsStyle.fillColor) {
|
||||
pathEl.setAttribute('fill', wpsStyle.fillColor);
|
||||
} else {
|
||||
pathEl.setAttribute('fill', 'none');
|
||||
}
|
||||
|
||||
if (outline) {
|
||||
if (outline.color) {
|
||||
pathEl.setAttribute('stroke', outline.color);
|
||||
}
|
||||
if (outline.width) {
|
||||
pathEl.setAttribute('stroke-width', outline.width);
|
||||
}
|
||||
} else if (wpsStyle && wpsStyle.lineColor) {
|
||||
pathEl.setAttribute('stroke', wpsStyle.lineColor);
|
||||
}
|
||||
|
||||
const fillColor = pathEl.getAttribute('fill');
|
||||
if (fillColor && fillColor !== 'none') {
|
||||
const color = new Color(fillColor);
|
||||
const fillMode = path.fill;
|
||||
let changeColor = '';
|
||||
switch (fillMode) {
|
||||
// 这些值目前是瞎编的,官方规范里没说
|
||||
// http://webapp.docx4java.org/OnlineDemo/ecma376/DrawingML/ST_PathFillMode.html
|
||||
case 'darken':
|
||||
changeColor = color.lumOff(-0.5).toHex();
|
||||
break;
|
||||
|
||||
case 'darkenLess':
|
||||
changeColor = color.lumOff(-0.2).toHex();
|
||||
break;
|
||||
|
||||
case 'lighten':
|
||||
changeColor = color.lumOff(0.5).toHex();
|
||||
break;
|
||||
|
||||
case 'lightenLess':
|
||||
changeColor = color.lumOff(0.2).toHex();
|
||||
break;
|
||||
}
|
||||
if (changeColor) {
|
||||
pathEl.setAttribute('fill', changeColor);
|
||||
}
|
||||
}
|
||||
if (path.fill === 'none') {
|
||||
pathEl.setAttribute('fill', 'none');
|
||||
}
|
||||
|
||||
// 如果没有 fill 也没有 stroke 就没法看了,所以设置个默认颜色
|
||||
const strokeColor = pathEl.getAttribute('stroke');
|
||||
if (!strokeColor && pathEl.getAttribute('fill') === 'none') {
|
||||
pathEl.setAttribute('stroke', 'black');
|
||||
}
|
||||
|
||||
if (path.stroke === false) {
|
||||
pathEl.setAttribute('stroke', 'none');
|
||||
if (!path.fill) {
|
||||
pathEl.setAttribute('fill', 'none');
|
||||
}
|
||||
}
|
||||
|
||||
svg.appendChild(pathEl);
|
||||
}
|
||||
|
||||
return svg;
|
||||
}
|
@ -11,7 +11,7 @@ import {parseTable} from '../../../parse/parseTable';
|
||||
import {CSSStyle} from '../../../openxml/Style';
|
||||
import {ST_TextVerticalType} from '../../../openxml/Types';
|
||||
import {convertAngle} from '../../../parse/parseSize';
|
||||
import {parseChildColor} from '../../../parse/parseChildColor';
|
||||
import {WPSStyle} from './WPSStyle';
|
||||
|
||||
export type TxbxContentChild = Paragraph | Table;
|
||||
|
||||
@ -58,20 +58,9 @@ function parseBodyPr(element: Element, style: CSSStyle) {
|
||||
}
|
||||
}
|
||||
|
||||
function parseWpsStyle(word: Word, element: Element, style: CSSStyle) {
|
||||
for (const child of element.children) {
|
||||
const tagName = child.tagName;
|
||||
switch (tagName) {
|
||||
// 目前只支持这个
|
||||
case 'a:fillRef':
|
||||
style['background-color'] = parseChildColor(word, child);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class WPS {
|
||||
spPr?: ShapePr;
|
||||
wpsStyle?: WPSStyle;
|
||||
txbxContent: TxbxContentChild[];
|
||||
// 外层容器样式
|
||||
style: CSSStyle = {};
|
||||
@ -117,7 +106,7 @@ export class WPS {
|
||||
|
||||
case 'wps:style':
|
||||
// http://webapp.docx4java.org/OnlineDemo/ecma376/DrawingML/style_1.html
|
||||
parseWpsStyle(word, child, wps.style);
|
||||
wps.wpsStyle = WPSStyle.fromXML(word, child);
|
||||
break;
|
||||
|
||||
case 'wps:bodyPr':
|
||||
|
31
packages/ooxml-viewer/src/openxml/word/wps/WPSStyle.ts
Normal file
31
packages/ooxml-viewer/src/openxml/word/wps/WPSStyle.ts
Normal file
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 没去不太清楚这个是什么,目前主要用于形状的颜色
|
||||
*/
|
||||
|
||||
import Word from '../../../Word';
|
||||
import {parseChildColor} from '../../../parse/parseChildColor';
|
||||
|
||||
export class WPSStyle {
|
||||
lineColor?: string;
|
||||
|
||||
fillColor?: string;
|
||||
|
||||
static fromXML(word: Word, element: Element) {
|
||||
const wpsStyle = new WPSStyle();
|
||||
|
||||
for (const child of element.children) {
|
||||
const tagName = child.tagName;
|
||||
switch (tagName) {
|
||||
case 'a:fillRef':
|
||||
wpsStyle.fillColor = parseChildColor(word, child);
|
||||
break;
|
||||
|
||||
case 'a:lnRef':
|
||||
wpsStyle.lineColor = parseChildColor(word, child);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return wpsStyle;
|
||||
}
|
||||
}
|
220
packages/ooxml-viewer/src/parse/parseShape.ts
Normal file
220
packages/ooxml-viewer/src/parse/parseShape.ts
Normal file
@ -0,0 +1,220 @@
|
||||
/**
|
||||
* 解析 shape
|
||||
*/
|
||||
|
||||
import {getAttrBoolean} from '../OpenXML';
|
||||
import {ST_PathFillMode} from '../openxml/Types';
|
||||
import {
|
||||
LnTo,
|
||||
QuadBezTo,
|
||||
CubicBezTo,
|
||||
ArcTo,
|
||||
MoveTo,
|
||||
Path,
|
||||
IPath,
|
||||
PathPoint
|
||||
} from '../openxml/word/drawing/Path';
|
||||
import {Rect, Shape, ShapeGuide} from '../openxml/word/drawing/Shape';
|
||||
|
||||
export function parsePts(element: Element) {
|
||||
const pts: PathPoint[] = [];
|
||||
for (const child of element.children) {
|
||||
const tagName = child.tagName;
|
||||
if (tagName === 'a:pt' || tagName === 'pt') {
|
||||
const x = child.getAttribute('x');
|
||||
const y = child.getAttribute('y');
|
||||
if (x && y) {
|
||||
pts.push({x, y});
|
||||
}
|
||||
} else {
|
||||
console.warn('unknown pt', tagName, child);
|
||||
}
|
||||
}
|
||||
|
||||
return pts;
|
||||
}
|
||||
|
||||
// http://webapp.docx4java.org/OnlineDemo/ecma376/DrawingML/path_2.html
|
||||
export function parsePath(element: Element) {
|
||||
const pathChild: IPath[] = [];
|
||||
|
||||
for (const child of element.children) {
|
||||
const tagName = child.tagName;
|
||||
switch (tagName) {
|
||||
case 'a:moveTo':
|
||||
case 'moveTo':
|
||||
const moveToPt = parsePts(child);
|
||||
if (moveToPt.length) {
|
||||
const moveTo: MoveTo = {
|
||||
type: 'moveTo',
|
||||
pt: moveToPt[0]
|
||||
};
|
||||
pathChild.push(moveTo);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'a:lnTo':
|
||||
case 'lnTo':
|
||||
const lnToPt = parsePts(child);
|
||||
if (lnToPt.length) {
|
||||
const lnTo: LnTo = {
|
||||
type: 'lnTo',
|
||||
pt: lnToPt[0]
|
||||
};
|
||||
pathChild.push(lnTo);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'a:quadBezTo':
|
||||
case 'quadBezTo':
|
||||
const quadBezToPt = parsePts(child);
|
||||
if (quadBezToPt.length) {
|
||||
const quadBezTo: QuadBezTo = {
|
||||
type: 'quadBezTo',
|
||||
pts: quadBezToPt
|
||||
};
|
||||
pathChild.push(quadBezTo);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'a:cubicBezTo':
|
||||
case 'cubicBezTo':
|
||||
const cubicBezToPt = parsePts(child);
|
||||
if (cubicBezToPt.length) {
|
||||
const cubicBezTo: CubicBezTo = {
|
||||
type: 'cubicBezTo',
|
||||
pts: cubicBezToPt
|
||||
};
|
||||
pathChild.push(cubicBezTo);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'a:arcTo':
|
||||
case 'arcTo':
|
||||
const wR = child.getAttribute('wR');
|
||||
const hR = child.getAttribute('hR');
|
||||
const stAng = child.getAttribute('stAng');
|
||||
const swAng = child.getAttribute('swAng');
|
||||
if (wR && hR && stAng && swAng) {
|
||||
const arcTo: ArcTo = {
|
||||
type: 'arcTo',
|
||||
wR,
|
||||
hR,
|
||||
stAng,
|
||||
swAng
|
||||
};
|
||||
pathChild.push(arcTo);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'a:close':
|
||||
case 'close':
|
||||
pathChild.push({
|
||||
type: 'close'
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn('parsePath: unknown tag', tagName, child);
|
||||
}
|
||||
}
|
||||
|
||||
const path: Path = {defines: pathChild};
|
||||
|
||||
const fill = element.getAttribute('fill') as ST_PathFillMode;
|
||||
if (fill) {
|
||||
path.fill = fill;
|
||||
}
|
||||
|
||||
path.extrusionOk = getAttrBoolean(element, 'extrusionOk', false);
|
||||
path.stroke = getAttrBoolean(element, 'stroke', true);
|
||||
|
||||
const w = element.getAttribute('w');
|
||||
if (w) {
|
||||
path.w = parseInt(w, 10);
|
||||
}
|
||||
|
||||
const h = element.getAttribute('h');
|
||||
if (h) {
|
||||
path.h = parseInt(h, 10);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
export function parsePathLst(element: Element) {
|
||||
const pathLst: Path[] = [];
|
||||
for (const child of element.children) {
|
||||
const tagName = child.tagName;
|
||||
switch (tagName) {
|
||||
case 'a:path':
|
||||
case 'path':
|
||||
pathLst.push(parsePath(child));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return pathLst;
|
||||
}
|
||||
|
||||
export function parseShapeGuide(element: Element) {
|
||||
const gds: ShapeGuide[] = [];
|
||||
|
||||
for (const child of element.children) {
|
||||
const tagName = child.tagName;
|
||||
switch (tagName) {
|
||||
case 'a:gd':
|
||||
case 'gd':
|
||||
const name = child.getAttribute('name');
|
||||
const fmla = child.getAttribute('fmla');
|
||||
if (name && fmla) {
|
||||
const gd: ShapeGuide = {
|
||||
n: name,
|
||||
f: fmla
|
||||
};
|
||||
gds.push(gd);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return gds;
|
||||
}
|
||||
|
||||
export function parseShape(element: Element) {
|
||||
const shape: Shape = {};
|
||||
|
||||
for (const child of element.children) {
|
||||
const tagName = child.tagName;
|
||||
switch (tagName) {
|
||||
case 'a:avLst':
|
||||
case 'avLst':
|
||||
shape.avLst = parseShapeGuide(child);
|
||||
break;
|
||||
|
||||
case 'a:gdLst':
|
||||
case 'gdLst':
|
||||
shape.gdLst = parseShapeGuide(child);
|
||||
break;
|
||||
|
||||
case 'a:rect':
|
||||
case 'react':
|
||||
const rect: Rect = {
|
||||
b: child.getAttribute('b') || '',
|
||||
l: child.getAttribute('l') || '',
|
||||
r: child.getAttribute('r') || '',
|
||||
t: child.getAttribute('t') || ''
|
||||
};
|
||||
shape.rect = rect;
|
||||
break;
|
||||
|
||||
case 'a:pathLst':
|
||||
case 'pathLst':
|
||||
shape.pathLst = parsePathLst(child);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return shape;
|
||||
}
|
18
packages/ooxml-viewer/src/render/renderCustGeom.ts
Normal file
18
packages/ooxml-viewer/src/render/renderCustGeom.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import {ShapePr} from '../openxml/word/drawing/ShapeProperties';
|
||||
import {shapeToSVG} from '../openxml/word/drawing/svg/shapeToSVG';
|
||||
import {WPSStyle} from '../openxml/word/wps/WPSStyle';
|
||||
import {CustomGeom} from '../openxml/word/drawing/CustomGeom';
|
||||
|
||||
export function renderCustGeom(
|
||||
geom: CustomGeom,
|
||||
shapePr: ShapePr,
|
||||
width: number,
|
||||
height: number,
|
||||
wpsStyle?: WPSStyle
|
||||
) {
|
||||
if (geom.shape) {
|
||||
return shapeToSVG(geom.shape, [], shapePr, width, height, wpsStyle);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
@ -6,6 +6,8 @@ import {appendChild, applyStyle} from '../util/dom';
|
||||
import renderParagraph from './renderParagraph';
|
||||
import renderTable from './renderTable';
|
||||
import {Table} from '../openxml/word/Table';
|
||||
import {renderGeom} from './renderGeom';
|
||||
import {renderCustGeom} from './renderCustGeom';
|
||||
|
||||
/**
|
||||
* 渲染图片
|
||||
@ -65,13 +67,29 @@ export function renderDrawing(word: Word, drawing: Drawing): HTMLElement {
|
||||
const wps = drawing.wps;
|
||||
const spPr = wps.spPr;
|
||||
applyStyle(container, wps.style);
|
||||
applyStyle(container, spPr?.style);
|
||||
|
||||
if (spPr?.xfrm) {
|
||||
const ext = spPr.xfrm.ext;
|
||||
if (ext) {
|
||||
container.style.width = ext.cx;
|
||||
container.style.height = ext.cy;
|
||||
|
||||
if (spPr.geom) {
|
||||
const width = parseFloat(ext.cx.replace('px', ''));
|
||||
const height = parseFloat(ext.cy.replace('px', ''));
|
||||
appendChild(
|
||||
container,
|
||||
renderGeom(spPr.geom, spPr, width, height, wps.wpsStyle)
|
||||
);
|
||||
}
|
||||
if (spPr.custGeom) {
|
||||
const width = parseFloat(ext.cx.replace('px', ''));
|
||||
const height = parseFloat(ext.cy.replace('px', ''));
|
||||
appendChild(
|
||||
container,
|
||||
renderCustGeom(spPr.custGeom, spPr, width, height, wps.wpsStyle)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (spPr.xfrm.rot) {
|
||||
container.style.transform = `rotate(${spPr.xfrm.rot}deg)`;
|
||||
|
29
packages/ooxml-viewer/src/render/renderGeom.ts
Normal file
29
packages/ooxml-viewer/src/render/renderGeom.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import {presetShape} from '../openxml/word/drawing/presetShape';
|
||||
import {Geom} from '../openxml/word/drawing/Geom';
|
||||
import {ShapePr} from '../openxml/word/drawing/ShapeProperties';
|
||||
import {shapeToSVG} from '../openxml/word/drawing/svg/shapeToSVG';
|
||||
import {WPSStyle} from '../openxml/word/wps/WPSStyle';
|
||||
|
||||
export function renderGeom(
|
||||
geom: Geom,
|
||||
shapePr: ShapePr,
|
||||
width: number,
|
||||
height: number,
|
||||
wpsStyle?: WPSStyle
|
||||
) {
|
||||
if (geom.prst) {
|
||||
const shape = presetShape[geom.prst];
|
||||
if (shape) {
|
||||
return shapeToSVG(
|
||||
shape,
|
||||
geom.avLst || [],
|
||||
shapePr,
|
||||
width,
|
||||
height,
|
||||
wpsStyle
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
@ -120,6 +120,7 @@ export class Color {
|
||||
this.r = rgb.r;
|
||||
this.g = rgb.g;
|
||||
this.b = rgb.b;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -134,6 +135,7 @@ export class Color {
|
||||
this.r = rgb.r;
|
||||
this.g = rgb.g;
|
||||
this.b = rgb.b;
|
||||
return this;
|
||||
}
|
||||
|
||||
toHex() {
|
||||
|
@ -40,6 +40,13 @@ export function createElement(tagName: string): HTMLElement {
|
||||
return document.createElement(tagName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 SVG 元素
|
||||
*/
|
||||
export function createSVGElement(tagName: string): SVGElement {
|
||||
return document.createElementNS('http://www.w3.org/2000/svg', tagName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建片段
|
||||
*/
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1 +1,5 @@
|
||||
schema 文件来自 https://www.ecma-international.org/publications-and-standards/standards/ecma-376/
|
||||
|
||||
presetShapeDefinitions.xml 来自 https://github.com/LibreOffice/core/blob/master/oox/source/drawingml/customshapes/presetShapeDefinitions.xml
|
||||
|
||||
因为官方的有错误,漏了 upArrow
|
||||
|
51
packages/ooxml-viewer/tools/converDrawingML.ts
Normal file
51
packages/ooxml-viewer/tools/converDrawingML.ts
Normal file
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 将 drawingml 配置转成内置 json 格式,避免运行时再次解析
|
||||
*/
|
||||
|
||||
import {readFileSync, writeFileSync} from 'fs';
|
||||
import {parseXML} from '../src/util/xml';
|
||||
import {Shape} from '../src/openxml/word/drawing/Shape';
|
||||
import {parseShape} from '../src/parse/parseShape';
|
||||
|
||||
import jsdom from 'jsdom';
|
||||
import prettier from 'prettier';
|
||||
|
||||
const {JSDOM} = jsdom;
|
||||
const {DOMParser} = new JSDOM(``).window;
|
||||
|
||||
global.DOMParser = DOMParser;
|
||||
|
||||
const drawingML = parseXML(
|
||||
readFileSync(
|
||||
'./OfficeOpenXML-DrawingMLGeometries/presetShapeDefinitions.xml',
|
||||
'utf-8'
|
||||
)
|
||||
);
|
||||
|
||||
let outputFile: string[] = [
|
||||
'/** generated by tools/converDarwingML.ts, do not edit */',
|
||||
`import {Shape} from './Shape';`
|
||||
];
|
||||
|
||||
const shapeMap: {[key: string]: Shape} = {};
|
||||
|
||||
for (const shape of drawingML
|
||||
.getElementsByTagName('presetShapeDefinitons')
|
||||
.item(0)!.children) {
|
||||
const shapeName = shape.tagName;
|
||||
shapeMap[shapeName] = parseShape(shape);
|
||||
}
|
||||
|
||||
outputFile.push(
|
||||
'export const presetShape: Record<string, Shape> = ' +
|
||||
JSON.stringify(shapeMap, null, 2) +
|
||||
';'
|
||||
);
|
||||
|
||||
prettier.resolveConfig('../../../.prettierrc').then(options => {
|
||||
const formatted = prettier.format(outputFile.join('\n'), {
|
||||
...options,
|
||||
parser: 'typescript'
|
||||
});
|
||||
writeFileSync('../src/openxml/word/drawing/presetShape.ts', formatted);
|
||||
});
|
Loading…
Reference in New Issue
Block a user