mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-30 02:48:55 +08:00
修复背景色不正确问题;优化位置信息; 大小改回 pt 提升打印准确度
This commit is contained in:
parent
6b749b8b53
commit
7409ad6df1
@ -10,20 +10,7 @@ order: 23
|
||||
|
||||
> 2.9.0 及以上版本
|
||||
|
||||
用于渲染 office 文档,目前只支持 docx 格式,通过前端转成 HTML 的方式进行渲染,支持以下功能:
|
||||
|
||||
- 基础文本样式
|
||||
- 表格及表格样式
|
||||
- 内嵌图片
|
||||
- 列表
|
||||
- 注音
|
||||
- 链接
|
||||
- 文本框
|
||||
- 形状
|
||||
- 数学公式(依赖 MathML,需要比较新的浏览器,或者试试 [polyfill](https://github.com/w3c/mathml-polyfills))
|
||||
- 分页渲染
|
||||
|
||||
不支持的功能:艺术字、域、对象、目录
|
||||
用于渲染 office 文档,目前只支持 docx 格式
|
||||
|
||||
## 基本用法
|
||||
|
||||
@ -42,6 +29,21 @@ order: 23
|
||||
|
||||
目前只支持 Word 文档,所以只有 word 的配置项,放在 `wordOptions` 下
|
||||
|
||||
Word 渲染支持以下功能:
|
||||
|
||||
- 基础文本样式
|
||||
- 表格及表格样式
|
||||
- 内嵌图片
|
||||
- 列表
|
||||
- 注音
|
||||
- 链接
|
||||
- 文本框
|
||||
- 形状
|
||||
- 数学公式(依赖 MathML,需要比较新的浏览器,或者试试 [polyfill](https://github.com/w3c/mathml-polyfills))
|
||||
- 分页渲染
|
||||
|
||||
不支持的功能:艺术字、域、对象、目录
|
||||
|
||||
### word 渲染配置属性表
|
||||
|
||||
```json
|
||||
@ -63,6 +65,7 @@ order: 23
|
||||
| fontMapping | `object` | | 字体映射,是个键值对,用于替换文档中的字体 |
|
||||
| forceLineHeight | `string` | | 设置段落行高,忽略文档中的设置 |
|
||||
| enableVar | `boolean` | true | 是否开启变量替换功能 |
|
||||
| printOptions | `object` | | 针对打印的特殊设置,可以覆盖其它所有设置项 |
|
||||
|
||||
#### 分页渲染
|
||||
|
||||
@ -83,17 +86,16 @@ order: 23
|
||||
|
||||
分页渲染的其它设置项
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ------------------ | --------- | --------- | ------------------------------------------------ |
|
||||
| page | `boolean` | false | 是否开启分页渲染 |
|
||||
| pageMarginBottom | `number` | 20 | 页面上下间距 |
|
||||
| pageBackground | `string` | '#FFF' | 页面内背景色 |
|
||||
| pageShadow | `boolean` | true | 是否显示阴影 |
|
||||
| pageWrap | `boolean` | true | 是否显示页面包裹,开启这个后才能设置包裹的背景色 |
|
||||
| pageWrap | `boolean` | true | 是否显示页面包裹 |
|
||||
| pageWrapBackground | `string` | '#ECECEC' | 是否显示页面包裹 |
|
||||
| zoom | `number` | | 缩放比例,取值 0-1 之间 |
|
||||
| zoomFitWidth | `boolean` | false | 自适应宽度缩放,如果设置了 zoom 将不会生效 |
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ------------------ | --------- | --------- | ------------------------------------------ | --- |
|
||||
| page | `boolean` | false | 是否开启分页渲染 |
|
||||
| pageMarginBottom | `number` | 20 | 页面上下间距 |
|
||||
| pageBackground | `string` | '#FFF' | 页面内背景色 |
|
||||
| pageShadow | `boolean` | true | 是否显示阴影 |
|
||||
| pageWrap | `boolean` | true | 是否显示页面包裹 | |
|
||||
| pageWrapBackground | `string` | '#ECECEC' | 页面包裹的背景色 |
|
||||
| zoom | `number` | | 缩放比例,取值 0-1 之间 |
|
||||
| zoomFitWidth | `boolean` | false | 自适应宽度缩放,如果设置了 zoom 将不会生效 |
|
||||
|
||||
### 关于渲染效果差异
|
||||
|
||||
@ -303,6 +305,18 @@ order: 23
|
||||
]
|
||||
```
|
||||
|
||||
有个 `printOptions` 配置项可以用来自定义在打印时的配置,默认设置是:
|
||||
|
||||
```json
|
||||
{
|
||||
"page": true,
|
||||
"pageWrap": false,
|
||||
"pageShadow": false,
|
||||
"pageMarginBottom": 0,
|
||||
"pageWrapPadding": undefined
|
||||
}
|
||||
```
|
||||
|
||||
## 配合文件上传实现预览功能
|
||||
|
||||
配置和 `input-file` 相同的 `name` 即可
|
||||
@ -331,7 +345,7 @@ order: 23
|
||||
|
||||
## 是否显示 loading
|
||||
|
||||
通过 `"loading": true` 配置显示 loading
|
||||
通过 `"loading": true` 配置显示 loading,主要用于网络较慢的场景。
|
||||
|
||||
## 属性表
|
||||
|
||||
|
@ -78,6 +78,14 @@ export default class OfficeViewer extends React.Component<
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
JSON.stringify(prevProps.wordOptions) !==
|
||||
JSON.stringify(props.wordOptions) ||
|
||||
prevProps.display !== props.display
|
||||
) {
|
||||
this.renderWord();
|
||||
}
|
||||
|
||||
// 这个变量替换只会更新变化的部分,所以性能还能接受
|
||||
this.word?.updateVariable();
|
||||
}
|
||||
@ -147,6 +155,9 @@ export default class OfficeViewer extends React.Component<
|
||||
|
||||
if (display !== false) {
|
||||
word.render(this.rootElement?.current!);
|
||||
} else if (display === false && this.rootElement?.current) {
|
||||
// 设置为 false 后清空
|
||||
this.rootElement.current.innerHTML = '';
|
||||
}
|
||||
|
||||
this.word = word;
|
||||
@ -172,6 +183,9 @@ export default class OfficeViewer extends React.Component<
|
||||
});
|
||||
if (display !== false) {
|
||||
word.render(this.rootElement?.current!);
|
||||
} else if (display === false && this.rootElement?.current) {
|
||||
// 设置为 false 后清空
|
||||
this.rootElement.current.innerHTML = '';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
Binary file not shown.
BIN
packages/ooxml-viewer/__tests__/docx/simple/padding.docx
Normal file
BIN
packages/ooxml-viewer/__tests__/docx/simple/padding.docx
Normal file
Binary file not shown.
2805
packages/ooxml-viewer/__tests__/docx/simple/pict-position.xml
Normal file
2805
packages/ooxml-viewer/__tests__/docx/simple/pict-position.xml
Normal file
File diff suppressed because it is too large
Load Diff
1548
packages/ooxml-viewer/__tests__/docx/simple/tab.xml
Normal file
1548
packages/ooxml-viewer/__tests__/docx/simple/tab.xml
Normal file
File diff suppressed because it is too large
Load Diff
2430
packages/ooxml-viewer/__tests__/docx/simple/table-background.xml
Normal file
2430
packages/ooxml-viewer/__tests__/docx/simple/table-background.xml
Normal file
File diff suppressed because it is too large
Load Diff
67097
packages/ooxml-viewer/__tests__/docx/simple/table-height.xml
Normal file
67097
packages/ooxml-viewer/__tests__/docx/simple/table-height.xml
Normal file
File diff suppressed because it is too large
Load Diff
1221
packages/ooxml-viewer/__tests__/docx/simple/test.xml
Normal file
1221
packages/ooxml-viewer/__tests__/docx/simple/test.xml
Normal file
File diff suppressed because it is too large
Load Diff
BIN
packages/ooxml-viewer/__tests__/docx/unsupport/table-height.docx
Normal file
BIN
packages/ooxml-viewer/__tests__/docx/unsupport/table-height.docx
Normal file
Binary file not shown.
@ -45,7 +45,6 @@ const fileLists = {
|
||||
],
|
||||
docx4j: [
|
||||
'ArialUnicodeMS.docx',
|
||||
'DOCPROP_builtin.docx',
|
||||
'Symbols.docx',
|
||||
'Word2007-fonts.docx',
|
||||
'chart.docx',
|
||||
@ -115,8 +114,7 @@ const data = {
|
||||
};
|
||||
const renderOptions = {
|
||||
debug: true,
|
||||
page,
|
||||
zoomFitWidth: true
|
||||
page
|
||||
};
|
||||
|
||||
async function renderDocx(fileName: string) {
|
||||
|
@ -27,6 +27,7 @@ import {Note} from './openxml/word/Note';
|
||||
import {parseFootnotes} from './parse/Footnotes';
|
||||
import {parseEndnotes} from './parse/parseEndnotes';
|
||||
import {renderNotes} from './render/renderNotes';
|
||||
import {Section} from './openxml/word/Section';
|
||||
|
||||
/**
|
||||
* 渲染配置
|
||||
@ -145,6 +146,11 @@ export interface WordRenderOptions {
|
||||
* 自适应宽度,如果设置了 zoom,那么 zoom 优先级更高,这个设置只在 ignoreWidth 为 false 的时候生效
|
||||
*/
|
||||
zoomFitWidth?: boolean;
|
||||
|
||||
/**
|
||||
* 打印可以覆盖其它配置
|
||||
*/
|
||||
printOptions?: WordRenderOptions;
|
||||
}
|
||||
|
||||
const defaultRenderOptions: WordRenderOptions = {
|
||||
@ -288,6 +294,11 @@ export default class Word {
|
||||
*/
|
||||
breakPage = false;
|
||||
|
||||
/**
|
||||
* 当前渲染的段,因为很多渲染需要,所以为了避免大量传参,这里直接挂在这里
|
||||
*/
|
||||
currentSection: Section;
|
||||
|
||||
/**
|
||||
* 初始化一些公共资源,比如
|
||||
*/
|
||||
@ -634,10 +645,21 @@ export default class Word {
|
||||
iframe.style.position = 'absolute';
|
||||
iframe.style.top = '-10000px';
|
||||
document.body.appendChild(iframe);
|
||||
iframe.contentDocument?.write('<div id="print"></div>');
|
||||
iframe.contentDocument?.write(
|
||||
'<style>html, body {margin:0; padding:0}</style><div id="print"></div>'
|
||||
);
|
||||
await this.render(
|
||||
iframe.contentDocument?.getElementById('print') as HTMLElement,
|
||||
{page: false, pageWrap: false}
|
||||
// 这些配置可以让打印还原度更高
|
||||
{
|
||||
page: true,
|
||||
pageWrap: false,
|
||||
pageShadow: false,
|
||||
pageMarginBottom: 0,
|
||||
pageWrapPadding: undefined,
|
||||
zoom: 1,
|
||||
...this.renderOptions.printOptions
|
||||
}
|
||||
);
|
||||
setTimeout(function () {
|
||||
iframe.focus();
|
||||
@ -682,7 +704,7 @@ export default class Word {
|
||||
root.classList.add(this.getClassPrefix());
|
||||
if (renderOptions.page && renderOptions.pageWrap) {
|
||||
root.classList.add(this.wrapClassName);
|
||||
root.style.padding = `${renderOptions.pageWrapPadding || 0}px`;
|
||||
root.style.padding = `${renderOptions.pageWrapPadding || 0}pt`;
|
||||
root.style.background = renderOptions.pageWrapBackground || '#ECECEC';
|
||||
}
|
||||
|
||||
|
@ -61,9 +61,25 @@ export class WDocument {
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log('unknown background', background);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (const child of background.children) {
|
||||
const name = child.tagName;
|
||||
switch (name) {
|
||||
case 'v:background':
|
||||
// vml 的背景色,不支持
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log('unknown background', background);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
doc.documentBackground = documentBackground;
|
||||
}
|
||||
|
||||
return doc;
|
||||
|
@ -23,9 +23,13 @@ export function shapeToSVG(
|
||||
): SVGElement {
|
||||
const svg = createSVGElement('svg');
|
||||
// 边框有时候会超过
|
||||
svg.setAttribute('style', 'overflow: visible');
|
||||
svg.setAttribute('width', width.toString() + 'px');
|
||||
svg.setAttribute('height', height.toString() + 'px');
|
||||
// z-index 是因为后面可能会有文字,避免遮挡
|
||||
svg.setAttribute(
|
||||
'style',
|
||||
'overflow: visible; position: absolute; z-index: -1'
|
||||
);
|
||||
svg.setAttribute('width', width.toString() + 'pt');
|
||||
svg.setAttribute('height', height.toString() + 'pt');
|
||||
|
||||
// 变量值
|
||||
const vars: Var = presetVal(width, height);
|
||||
|
@ -24,7 +24,9 @@ function parseBodyPr(element: Element, style: CSSStyle) {
|
||||
const value = attribute.value;
|
||||
switch (name) {
|
||||
case 'numCol':
|
||||
style['column-count'] = value;
|
||||
if (value !== '1') {
|
||||
style['column-count'] = value;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'vert':
|
||||
|
@ -74,7 +74,7 @@ export function parseShdColor(word: Word, element: Element) {
|
||||
const val = getVal(element) as ST_Shd;
|
||||
|
||||
if (color === 'auto') {
|
||||
color = '000000';
|
||||
color = 'FFFFFF';
|
||||
}
|
||||
|
||||
if (color.length === 6) {
|
||||
@ -139,6 +139,10 @@ export function parseShdColor(word: Word, element: Element) {
|
||||
* 用 alpha 来模拟 ptc 功能
|
||||
*/
|
||||
function colorPercent(color: string, percent: number): string {
|
||||
// 白色取 alpha 没什么意义,转成黑色
|
||||
if (color === 'FFFFFF') {
|
||||
color = '000000';
|
||||
}
|
||||
const r = parseInt(color.substring(0, 2), 16);
|
||||
const g = parseInt(color.substring(2, 4), 16);
|
||||
const b = parseInt(color.substring(4, 6), 16);
|
||||
|
@ -363,6 +363,10 @@ export function parsePr(word: Word, element: Element, type: 'r' | 'p' = 'p') {
|
||||
// 目前还不支持 grid
|
||||
break;
|
||||
|
||||
case 'w:topLinePunct':
|
||||
// 没法支持
|
||||
break;
|
||||
|
||||
case 'w:wordWrap':
|
||||
// 不太确定这里是用 word-break 还是 overflow-wrap
|
||||
if (getValBoolean(child)) {
|
||||
@ -425,26 +429,26 @@ export function parsePr(word: Word, element: Element, type: 'r' | 'p' = 'p') {
|
||||
|
||||
case 'w:outline':
|
||||
style['text-shadow'] =
|
||||
'-1px -1px 0 #AAA, 1px -1px 0 #AAA, -1px 1px 0 #AAA, 1px 1px 0 #AAA';
|
||||
'-1pt -1pt 0 #AAA, 1pt -1pt 0 #AAA, -1pt 1pt 0 #AAA, 1pt 1pt 0 #AAA';
|
||||
break;
|
||||
|
||||
case 'w:shadown':
|
||||
case 'w:imprint':
|
||||
if (getValBoolean(child, true)) {
|
||||
style['text-shadow'] = '1px 1px 2px rgba(0, 0, 0, 0.6)';
|
||||
style['text-shadow'] = '1pt 1pt 2pt rgba(0, 0, 0, 0.6)';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'w14:shadow':
|
||||
const blurRad =
|
||||
parseSize(child, 'w14:blurRad', LengthUsage.Emu) || '2px';
|
||||
parseSize(child, 'w14:blurRad', LengthUsage.Emu) || '2pt';
|
||||
// 其它结果算出来不像就先忽略了
|
||||
let color = 'rgba(0, 0, 0, 0.6)';
|
||||
const childColor = parseChildColor(word, child);
|
||||
if (childColor) {
|
||||
color = childColor;
|
||||
}
|
||||
style['text-shadow'] = `1px 1px ${blurRad} ${color}`;
|
||||
style['text-shadow'] = `1pt 1pt ${blurRad} ${color}`;
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -2,18 +2,16 @@
|
||||
* 单位相关的解析,参考了 docxjs 里的实现
|
||||
*/
|
||||
|
||||
export type LengthType = 'px' | 'pt' | '%' | '';
|
||||
export type LengthType = 'pt' | '%' | '';
|
||||
|
||||
export type LengthUsageType = {mul: number; unit: LengthType};
|
||||
|
||||
const ptToPx = 1.3333;
|
||||
|
||||
export const LengthUsage: Record<string, LengthUsageType> = {
|
||||
Dxa: {mul: ptToPx * 0.05, unit: 'px'}, //twips
|
||||
Emu: {mul: (ptToPx * 1) / 12700, unit: 'px'},
|
||||
FontSize: {mul: ptToPx * 0.5, unit: 'px'},
|
||||
Border: {mul: ptToPx * 0.125, unit: 'px'},
|
||||
Point: {mul: ptToPx * 1, unit: 'px'},
|
||||
Dxa: {mul: 0.05, unit: 'pt'}, //twips
|
||||
Emu: {mul: 1 / 12700, unit: 'pt'},
|
||||
FontSize: {mul: 0.5, unit: 'pt'},
|
||||
Border: {mul: 0.125, unit: 'pt'},
|
||||
Point: {mul: 1, unit: 'pt'},
|
||||
Percent: {mul: 0.02, unit: '%'},
|
||||
LineHeight: {mul: 1 / 240, unit: ''},
|
||||
VmlEmu: {mul: 1 / 12700, unit: ''}
|
||||
|
@ -45,11 +45,13 @@ function appendToSection(
|
||||
section: Section,
|
||||
child: HTMLElement
|
||||
) {
|
||||
// 如果是第一个节点,即便超长也得写入,不然就会出现一个空 section
|
||||
const isFirst = sectionEl.children.length === 0;
|
||||
// 首先尝试写入
|
||||
appendChild(sectionEl, child);
|
||||
|
||||
// 如果超出了就新建一个 section
|
||||
if (createNewSection(word, sectionEnd, child)) {
|
||||
if (!isFirst && createNewSection(word, sectionEnd, child)) {
|
||||
const newChild = child.cloneNode(true) as HTMLElement;
|
||||
removeChild(sectionEl, child);
|
||||
let newSectionEl = renderSection(word, section, renderOptions);
|
||||
@ -75,11 +77,11 @@ function getSectionEnd(section: Section, sectionEl: HTMLElement): SectionEnd {
|
||||
const pageMargin = section.properties.pageMargin;
|
||||
let bottom = sectionBound.top + sectionBound.height;
|
||||
if (pageMargin?.bottom) {
|
||||
bottom = bottom - parseInt(pageMargin.bottom.replace('px', ''), 10);
|
||||
bottom = bottom - parseInt(pageMargin.bottom.replace('pt', ''), 10);
|
||||
}
|
||||
let right = sectionBound.left + sectionBound.width;
|
||||
if (pageMargin?.right) {
|
||||
right = right - parseInt(pageMargin.right.replace('px', ''), 10);
|
||||
right = right - parseInt(pageMargin.right.replace('pt', ''), 10);
|
||||
}
|
||||
return {bottom, right};
|
||||
}
|
||||
@ -97,15 +99,15 @@ function getTransform(
|
||||
if (renderOptions.zoomFitWidth && !renderOptions.ignoreWidth) {
|
||||
const pageWidth = pageSize?.width;
|
||||
if (rootWidth && pageWidth) {
|
||||
let pageWidthNum = parseInt(pageWidth.replace('px', ''), 10);
|
||||
let pageWidthNum = parseInt(pageWidth.replace('pt', ''), 10);
|
||||
|
||||
if (props.pageMargin) {
|
||||
const pageMargin = props.pageMargin;
|
||||
pageWidthNum += pageMargin.left
|
||||
? parseInt(pageMargin.left.replace('px', ''), 10)
|
||||
? parseInt(pageMargin.left.replace('pt', ''), 10)
|
||||
: 0;
|
||||
pageWidthNum += pageMargin.right
|
||||
? parseInt(pageMargin.right.replace('px', ''), 10)
|
||||
? parseInt(pageMargin.right.replace('pt', ''), 10)
|
||||
: 0;
|
||||
}
|
||||
const zoomWidth = rootWidth / pageWidthNum;
|
||||
@ -196,6 +198,7 @@ export default function renderBody(
|
||||
let isLastSection = false;
|
||||
for (const section of sections) {
|
||||
zooms.push(getTransform(rootWidth, section, renderOptions));
|
||||
word.currentSection = section;
|
||||
let sectionEl = renderSection(word, section, renderOptions);
|
||||
appendChild(bodyEl, sectionEl);
|
||||
|
||||
@ -207,7 +210,6 @@ export default function renderBody(
|
||||
renderSectionInPage(
|
||||
word,
|
||||
bodyEl,
|
||||
|
||||
renderOptions,
|
||||
sectionEl,
|
||||
section,
|
||||
|
@ -8,6 +8,8 @@ import renderTable from './renderTable';
|
||||
import {Table} from '../openxml/word/Table';
|
||||
import {renderGeom} from './renderGeom';
|
||||
import {renderCustGeom} from './renderCustGeom';
|
||||
import {CSSStyle} from '../openxml/Style';
|
||||
import parseSides from '../util/parseSides';
|
||||
|
||||
/**
|
||||
* 渲染图片
|
||||
@ -44,12 +46,52 @@ function renderPic(pic: Pic, word: Word, drawing: Drawing) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修复绝对定位的位置,主要是加上 padding,因为 html 中的定位是不考虑 padding 的
|
||||
* @param word
|
||||
* @param style
|
||||
*/
|
||||
function fixAbsolutePosition(word: Word, style: CSSStyle) {
|
||||
let paddingLeft = 0;
|
||||
let paddingTop = 0;
|
||||
const customPadding = word.renderOptions.padding;
|
||||
if (customPadding) {
|
||||
const {left, top} = parseSides(customPadding);
|
||||
paddingLeft = parseInt(left || '0');
|
||||
paddingTop = parseInt(top || '0');
|
||||
} else {
|
||||
const currentSection = word.currentSection;
|
||||
if (currentSection) {
|
||||
const pageMargin = currentSection.properties.pageMargin;
|
||||
if (pageMargin) {
|
||||
paddingLeft = parseInt(pageMargin.left || '0');
|
||||
paddingTop = parseInt(pageMargin.top || '0');
|
||||
}
|
||||
}
|
||||
}
|
||||
const leftStyle = style.left;
|
||||
if (leftStyle) {
|
||||
style.left = `${
|
||||
parseInt(String(leftStyle).replace('pt', ''), 10) + paddingLeft
|
||||
}pt`;
|
||||
}
|
||||
const topStyle = style.top;
|
||||
if (topStyle) {
|
||||
style.top = `${
|
||||
parseInt(String(topStyle).replace('pt', ''), 10) + paddingTop
|
||||
}pt`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染图片,目前只支持 picture
|
||||
* http://officeopenxml.com/drwOverview.php
|
||||
*
|
||||
*/
|
||||
export function renderDrawing(word: Word, drawing: Drawing): HTMLElement {
|
||||
export function renderDrawing(
|
||||
word: Word,
|
||||
drawing: Drawing
|
||||
): HTMLElement | null {
|
||||
const container = document.createElement('div');
|
||||
|
||||
if (drawing.position === 'inline') {
|
||||
@ -61,11 +103,16 @@ export function renderDrawing(word: Word, drawing: Drawing): HTMLElement {
|
||||
appendChild(container, renderPic(drawing.pic, word, drawing));
|
||||
}
|
||||
|
||||
if (!drawing.relativeFromParagraph) {
|
||||
fixAbsolutePosition(word, drawing.containerStyle || {});
|
||||
}
|
||||
|
||||
applyStyle(container, drawing.containerStyle);
|
||||
|
||||
if (drawing.wps) {
|
||||
const wps = drawing.wps;
|
||||
const spPr = wps.spPr;
|
||||
|
||||
applyStyle(container, wps.style);
|
||||
|
||||
if (spPr?.xfrm) {
|
||||
@ -75,16 +122,16 @@ export function renderDrawing(word: Word, drawing: Drawing): HTMLElement {
|
||||
container.style.height = ext.cy;
|
||||
|
||||
if (spPr.geom) {
|
||||
const width = parseFloat(ext.cx.replace('px', ''));
|
||||
const height = parseFloat(ext.cy.replace('px', ''));
|
||||
const width = parseFloat(ext.cx.replace('pt', ''));
|
||||
const height = parseFloat(ext.cy.replace('pt', ''));
|
||||
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', ''));
|
||||
const width = parseFloat(ext.cx.replace('pt', ''));
|
||||
const height = parseFloat(ext.cy.replace('pt', ''));
|
||||
appendChild(
|
||||
container,
|
||||
renderCustGeom(spPr.custGeom, spPr, width, height, wps.wpsStyle)
|
||||
@ -106,5 +153,10 @@ export function renderDrawing(word: Word, drawing: Drawing): HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没内容就不渲染了,避免高度导致撑开父节点
|
||||
if (container.children.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
@ -37,15 +37,34 @@ function renderNote(
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 过滤掉 0 和 -1 后是否还有其他的值,没有的话就不需要渲染
|
||||
*/
|
||||
function hasNote(notes: Record<string, Note>) {
|
||||
if (!notes) {
|
||||
return false;
|
||||
}
|
||||
for (const id in notes) {
|
||||
if (id !== '0' && id !== '-1') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function renderNotes(word: Word) {
|
||||
const noteRoot = createElement('div');
|
||||
|
||||
for (const fId in word.footNotes || {}) {
|
||||
renderNote(word, noteRoot, 'footnote', fId, word.footNotes[fId]);
|
||||
if (hasNote(word.footNotes)) {
|
||||
for (const fId in word.footNotes) {
|
||||
renderNote(word, noteRoot, 'footnote', fId, word.footNotes[fId]);
|
||||
}
|
||||
}
|
||||
|
||||
for (const fId in word.endNotes || {}) {
|
||||
renderNote(word, noteRoot, 'endnote', fId, word.endNotes[fId]);
|
||||
if (hasNote(word.endNotes)) {
|
||||
for (const fId in word.endNotes || {}) {
|
||||
renderNote(word, noteRoot, 'endnote', fId, word.endNotes[fId]);
|
||||
}
|
||||
}
|
||||
|
||||
if (noteRoot.children.length) {
|
||||
|
@ -37,12 +37,6 @@ export default function renderParagraph(
|
||||
appendChild(p, renderNumbering(p, word, properties.numPr));
|
||||
}
|
||||
|
||||
if (properties.tabs) {
|
||||
for (const tab of properties.tabs) {
|
||||
appendChild(p, renderTab(word, tab));
|
||||
}
|
||||
}
|
||||
|
||||
let inFldChar = false;
|
||||
|
||||
for (const child of paragraph.children) {
|
||||
|
@ -16,13 +16,12 @@ export function renderSection(
|
||||
sectionEl.style.position = 'relative';
|
||||
|
||||
if (renderOptions.page) {
|
||||
sectionEl.style.overflow = 'hidden';
|
||||
if (renderOptions.pageMarginBottom) {
|
||||
sectionEl.style.marginBottom = renderOptions.pageMarginBottom + 'px';
|
||||
sectionEl.style.marginBottom = renderOptions.pageMarginBottom + 'pt';
|
||||
}
|
||||
|
||||
if (renderOptions.pageShadow) {
|
||||
sectionEl.style.boxShadow = '0 0 8px rgba(0, 0, 0, 0.5)';
|
||||
sectionEl.style.boxShadow = '0 0 8pt rgba(0, 0, 0, 0.5)';
|
||||
}
|
||||
|
||||
if (renderOptions.pageBackground) {
|
||||
|
@ -2,6 +2,6 @@ import {createElement} from '../util/dom';
|
||||
|
||||
export function renderSeparator() {
|
||||
const sep = createElement('hr');
|
||||
sep.style.borderTop = '1px solid #bbb';
|
||||
sep.style.borderTop = '1pt solid #bbb';
|
||||
return sep;
|
||||
}
|
||||
|
@ -5,15 +5,14 @@ import Word from '../Word';
|
||||
|
||||
/**
|
||||
* 渲染 tab
|
||||
* 不支持 tabs 里的自定义宽度,因为要算渲染后的宽度,比较麻烦
|
||||
* http://officeopenxml.com/WPtab.php
|
||||
*/
|
||||
export function renderTab(word: Word, tab: Tab) {
|
||||
const tabElement = createElement('span');
|
||||
tabElement.style.display = 'inline-block';
|
||||
tabElement.style.width = tab.pos;
|
||||
tabElement.innerHTML = ' ';
|
||||
tabElement.innerHTML = ' ';
|
||||
if (tab.leader === 'dot') {
|
||||
tabElement.style.borderBottom = '1px dotted';
|
||||
tabElement.style.borderBottom = '1pt dotted';
|
||||
}
|
||||
return tabElement;
|
||||
}
|
||||
|
43
packages/ooxml-viewer/src/util/parseSides.ts
Normal file
43
packages/ooxml-viewer/src/util/parseSides.ts
Normal file
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 来自 https://github.com/jednano/parse-css-sides/blob/master/src/index.ts
|
||||
*/
|
||||
|
||||
export interface ISides {
|
||||
top: string;
|
||||
right: string;
|
||||
bottom: string;
|
||||
left: string;
|
||||
important?: boolean;
|
||||
}
|
||||
|
||||
export default function parseSides(value: string): ISides {
|
||||
const sides = value.split(/\s+/);
|
||||
const pos = sides.lastIndexOf('!important');
|
||||
const important = pos !== -1;
|
||||
if (important) {
|
||||
sides.splice(pos, 1);
|
||||
}
|
||||
|
||||
const numberOfSides = sides.length;
|
||||
if (numberOfSides < 1 || numberOfSides > 4) {
|
||||
throw new Error(`Cannot parse ${numberOfSides} sides`);
|
||||
}
|
||||
|
||||
const [first, ...rest] = sides;
|
||||
return createSides(first, ...rest);
|
||||
|
||||
function createSides(
|
||||
top: string,
|
||||
right: string = top,
|
||||
bottom: string = top,
|
||||
left: string = right
|
||||
) {
|
||||
return {
|
||||
bottom,
|
||||
left,
|
||||
right,
|
||||
top,
|
||||
...(important ? {important} : {})
|
||||
};
|
||||
}
|
||||
}
|
@ -31,6 +31,13 @@
|
||||
"amis-editor": ["./packages/amis-editor/src/index.tsx"]
|
||||
}
|
||||
},
|
||||
"watchOptions": {
|
||||
"watchFile": "useFsEvents",
|
||||
"watchDirectory": "useFsEvents",
|
||||
"fallbackPolling": "dynamicPriority",
|
||||
"synchronousWatchDirectory": true,
|
||||
"excludeDirectories": ["**/node_modules"]
|
||||
},
|
||||
"types": ["typePatches"],
|
||||
"references": [],
|
||||
"include": [
|
||||
|
Loading…
Reference in New Issue
Block a user