From 3f1e39c5609a93b34305c26d2b7222b4ce14c21b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=A4=9A=E7=9B=8A?= Date: Tue, 21 Mar 2023 18:46:12 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20office=20viewer=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E4=B8=AD=E8=8B=B1=E6=96=87=E7=A9=BA=E8=A1=8C=EF=BC=9B=E4=BC=98?= =?UTF-8?q?=E5=8C=96=20tblpPr=20=E7=9A=84=E6=94=AF=E6=8C=81=20(#6433)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/amis-core/src/utils/debug.tsx | 1 + packages/office-viewer/README.md | 4 ++ .../__tests__/docx/simple/text.docx | Bin 0 -> 15473 bytes .../__tests__/util/autoSpace.test.ts | 9 ++++ .../src/openxml/word/AlternateContent.ts | 0 .../office-viewer/src/openxml/word/Body.ts | 3 ++ .../src/openxml/word/Paragraph.ts | 15 ++++++- .../office-viewer/src/openxml/word/Table.ts | 35 ++++++++++----- packages/office-viewer/src/parse/parsePr.ts | 23 ++++++++++ .../src/render/renderHyperLink.ts | 9 +++- .../src/render/renderNumbering.ts | 5 +++ .../src/render/renderParagraph.ts | 4 +- .../office-viewer/src/render/renderRun.ts | 21 ++++++--- .../office-viewer/src/render/renderSection.ts | 2 + packages/office-viewer/src/util/autoSpace.ts | 41 ++++++++++++++++++ 15 files changed, 150 insertions(+), 22 deletions(-) create mode 100644 packages/office-viewer/__tests__/docx/simple/text.docx create mode 100644 packages/office-viewer/__tests__/util/autoSpace.test.ts create mode 100644 packages/office-viewer/src/openxml/word/AlternateContent.ts create mode 100644 packages/office-viewer/src/util/autoSpace.ts diff --git a/packages/amis-core/src/utils/debug.tsx b/packages/amis-core/src/utils/debug.tsx index 97f3f0a82..5364e060e 100644 --- a/packages/amis-core/src/utils/debug.tsx +++ b/packages/amis-core/src/utils/debug.tsx @@ -385,6 +385,7 @@ export function enableDebug() { interface DebugWrapperProps { renderer: any; + children?: React.ReactNode; } export class DebugWrapper extends Component { diff --git a/packages/office-viewer/README.md b/packages/office-viewer/README.md index 08d544a03..b08af991c 100644 --- a/packages/office-viewer/README.md +++ b/packages/office-viewer/README.md @@ -6,6 +6,10 @@ docx 渲染器,原理是将 docx 里的 xml 格式转成 html 相对于 Canvas 渲染,这个实现方案比较简单,最终页面也可以很方便复制,但无法保证和原始 docx 文件展现一致,因为有部分功能难以在 HTML 中实现,比如图文环绕效果。 +## 还不支持的功能 + +- wmf,需要使用 https://github.com/SheetJS/js-wmf + ## 参考资料 - [官方规范](https://www.ecma-international.org/publications-and-standards/standards/ecma-376/) diff --git a/packages/office-viewer/__tests__/docx/simple/text.docx b/packages/office-viewer/__tests__/docx/simple/text.docx new file mode 100644 index 0000000000000000000000000000000000000000..ed371f9e81f77d0dff213e2d18567fa22a7f2d7d GIT binary patch literal 15473 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 literal 0 HcmV?d00001 diff --git a/packages/office-viewer/__tests__/util/autoSpace.test.ts b/packages/office-viewer/__tests__/util/autoSpace.test.ts new file mode 100644 index 000000000..1b9918afa --- /dev/null +++ b/packages/office-viewer/__tests__/util/autoSpace.test.ts @@ -0,0 +1,9 @@ +import {cjkspace} from '../../src/util/autoSpace'; + +test('autoSpace', async () => { + expect(cjkspace('a中'.split(''))).toBe('a 中'); +}); + +test('autoSpace 2', async () => { + expect(cjkspace('abc中def,测试'.split(''))).toBe('abc 中 def,测试'); +}); diff --git a/packages/office-viewer/src/openxml/word/AlternateContent.ts b/packages/office-viewer/src/openxml/word/AlternateContent.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/office-viewer/src/openxml/word/Body.ts b/packages/office-viewer/src/openxml/word/Body.ts index 37b8b1a41..df9265400 100644 --- a/packages/office-viewer/src/openxml/word/Body.ts +++ b/packages/office-viewer/src/openxml/word/Body.ts @@ -52,6 +52,9 @@ export class Body { body.addChild(table); break; + case 'w:bookmarkEnd': + break; + default: console.warn('Body.fromXML Unknown key', tagName, child); } diff --git a/packages/office-viewer/src/openxml/word/Paragraph.ts b/packages/office-viewer/src/openxml/word/Paragraph.ts index 6e605b157..a9074c5a8 100644 --- a/packages/office-viewer/src/openxml/word/Paragraph.ts +++ b/packages/office-viewer/src/openxml/word/Paragraph.ts @@ -21,6 +21,11 @@ export interface ParagraphPr extends Properties { numPr?: NumberPr; runPr?: RunPr; tabs?: Tab[]; + + /** + * 其实是区分 autoSpaceDN 和 autoSpaceDE 的,但这里简化了 + */ + autoSpace?: boolean; } export type ParagraphChild = @@ -45,6 +50,12 @@ export type ParagraphChild = // | CommentRangeEnd // | CommentReference; +function parseAutoSpace(element: Element): boolean { + const autoSpaceDE = element.getElementsByTagName('w:autoSpaceDE').item(0); + const autoSpaceDN = element.getElementsByTagName('w:autoSpaceDN').item(0); + return !!autoSpaceDE || !!autoSpaceDN; +} + export class Paragraph { // 主要是为了方便调试用的 paraId?: string; @@ -78,7 +89,9 @@ export class Paragraph { tabs.push(Tab.fromXML(word, tabElement)); } - return {cssStyle, pStyle, numPr, tabs}; + const autoSpace = parseAutoSpace(element); + + return {cssStyle, pStyle, numPr, tabs, autoSpace}; } static fromXML(word: Word, element: Element): Paragraph { diff --git a/packages/office-viewer/src/openxml/word/Table.ts b/packages/office-viewer/src/openxml/word/Table.ts index 1f1d19bd6..1d160cf1f 100644 --- a/packages/office-viewer/src/openxml/word/Table.ts +++ b/packages/office-viewer/src/openxml/word/Table.ts @@ -76,7 +76,8 @@ function parseTblJc(element: Element, cssStyle: CSSStyle) { switch (val) { case 'left': case 'start': - cssStyle['float'] = 'left'; + // TODO: 会导致前面的文字掉下去,感觉还是不能支持这个功能 + // cssStyle['float'] = 'left'; break; case 'right': case 'end': @@ -165,16 +166,26 @@ function parseTblLook(child: Element) { * http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/tblpPr.html * 只支持部分 */ -function parsetTlpPr(child: Element, style: CSSStyle) { - 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); +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 { @@ -257,7 +268,7 @@ export class Table { break; case 'w:tblpPr': - parsetTlpPr(child, tableStyle); + parsetTlpPr(word, child, tableStyle); break; default: diff --git a/packages/office-viewer/src/parse/parsePr.ts b/packages/office-viewer/src/parse/parsePr.ts index 209cebed9..86d44e9d4 100644 --- a/packages/office-viewer/src/parse/parsePr.ts +++ b/packages/office-viewer/src/parse/parsePr.ts @@ -343,6 +343,29 @@ export function parsePr(word: Word, element: Element, type: 'r' | 'p' = 'p') { // 目前是自动计算的,所以不需要这个了 break; + case 'w:bidi': + // http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/bidi_1.html + // TODO: 还不清楚和 w:textDirection 是什么关系 + if (getValBoolean(child, true)) { + console.warn('w:bidi is not supported.'); + } + break; + + case 'w:autoSpaceDE': + case 'w:autoSpaceDN': + // 这个在其它地方实现了 + break; + + case 'w:kinsoku': + // http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/kinsoku.html + // 控制不了所以忽略了 + break; + + case 'w:overflowPunct': + // http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/overflowPunct.html + // 支持不了 + break; + default: console.warn('parsePr Unknown tagName', tagName, child); } diff --git a/packages/office-viewer/src/render/renderHyperLink.ts b/packages/office-viewer/src/render/renderHyperLink.ts index f3457d247..c386bdc24 100644 --- a/packages/office-viewer/src/render/renderHyperLink.ts +++ b/packages/office-viewer/src/render/renderHyperLink.ts @@ -3,11 +3,16 @@ import {appendChild, createElement} from '../util/dom'; import Word from '../Word'; import {Run} from '../openxml/word/Run'; import renderRun from './renderRun'; +import type {Paragraph} from '../openxml/word/Paragraph'; /** * 渲染链接 */ -export function renderHyperLink(word: Word, hyperlink: Hyperlink): HTMLElement { +export function renderHyperLink( + word: Word, + hyperlink: Hyperlink, + paragraph?: Paragraph +): HTMLElement { const a = createElement('a') as HTMLAnchorElement; if (hyperlink.relation) { @@ -24,7 +29,7 @@ export function renderHyperLink(word: Word, hyperlink: Hyperlink): HTMLElement { for (const child of hyperlink.children) { if (child instanceof Run) { - const span = renderRun(word, child); + const span = renderRun(word, child, paragraph); appendChild(a, span); } } diff --git a/packages/office-viewer/src/render/renderNumbering.ts b/packages/office-viewer/src/render/renderNumbering.ts index 6e817e2c5..56db7c936 100644 --- a/packages/office-viewer/src/render/renderNumbering.ts +++ b/packages/office-viewer/src/render/renderNumbering.ts @@ -78,6 +78,11 @@ export function renderNumbering( return null; } + if (!numbering) { + console.warn('renderNumbering: numbering is empty'); + return null; + } + const num = numbering.nums[numId]; if (!num) { diff --git a/packages/office-viewer/src/render/renderParagraph.ts b/packages/office-viewer/src/render/renderParagraph.ts index 280ecf9f0..237963a11 100644 --- a/packages/office-viewer/src/render/renderParagraph.ts +++ b/packages/office-viewer/src/render/renderParagraph.ts @@ -45,11 +45,11 @@ export default function renderParagraph( for (const child of paragraph.children) { if (child instanceof Run) { - appendChild(p, renderRun(word, child)); + appendChild(p, renderRun(word, child, paragraph)); } else if (child instanceof BookmarkStart) { appendChild(p, renderBookmarkStart(word, child)); } else if (child instanceof Hyperlink) { - const hyperlink = renderHyperLink(word, child); + const hyperlink = renderHyperLink(word, child, paragraph); appendChild(p, hyperlink); } else if (child instanceof SmartTag) { renderInlineText(word, child, p); diff --git a/packages/office-viewer/src/render/renderRun.ts b/packages/office-viewer/src/render/renderRun.ts index 4fd091ebe..3503c7c4e 100644 --- a/packages/office-viewer/src/render/renderRun.ts +++ b/packages/office-viewer/src/render/renderRun.ts @@ -20,16 +20,27 @@ import {InstrText} from '../openxml/word/InstrText'; import {renderInstrText} from './renderInstrText'; import {Sym} from '../openxml/word/Sym'; import {renderSym} from './renderSym'; +import {cjkspace} from '../util/autoSpace'; +import type {Paragraph} from './../openxml/word/Paragraph'; const VARIABLE_CLASS_NAME = 'variable'; /** * 对文本进行替换 */ -function renderText(span: HTMLElement, word: Word, text: string) { +function renderText( + span: HTMLElement, + word: Word, + text: string, + paragraph?: Paragraph +) { // 简单过滤一下提升性能 if (text.indexOf('{{') === -1) { - span.textContent = text; + if (paragraph?.properties?.autoSpace) { + span.textContent = cjkspace(text.split('')); + } else { + span.textContent = text; + } } else { span.dataset.originText = text; // 加个标识,后续可以通过它来查找哪些变量需要替换,这样就不用重新渲染整个文档了 @@ -53,7 +64,7 @@ export function updateVariableText(word: Word) { /** * 渲染 run 节点 */ -export default function renderRun(word: Word, run: Run) { +export default function renderRun(word: Word, run: Run, paragraph?: Paragraph) { const span = createElement('span'); word.addClass(span, 'r'); @@ -62,12 +73,12 @@ export default function renderRun(word: Word, run: Run) { if (run.children.length === 1 && run.children[0] instanceof Text) { const text = run.children[0] as Text; - renderText(span, word, text.text); + renderText(span, word, text.text, paragraph); } else { for (const child of run.children) { if (child instanceof Text) { let newSpan = createElement('span'); - renderText(span, word, child.text); + renderText(span, word, child.text, paragraph); appendChild(span, newSpan); } else if (child instanceof Break) { const br = renderBr(child); diff --git a/packages/office-viewer/src/render/renderSection.ts b/packages/office-viewer/src/render/renderSection.ts index dec83efd9..1e9ad7054 100644 --- a/packages/office-viewer/src/render/renderSection.ts +++ b/packages/office-viewer/src/render/renderSection.ts @@ -8,6 +8,8 @@ import Word from '../Word'; export function renderSection(word: Word, section: Section) { const sectionEl = createElement('section') as HTMLElement; + // 用于后续绝对定位 + sectionEl.style.position = 'relative'; const props = section.properties; const pageSize = props.pageSize; if (pageSize) { diff --git a/packages/office-viewer/src/util/autoSpace.ts b/packages/office-viewer/src/util/autoSpace.ts new file mode 100644 index 000000000..f43ca662c --- /dev/null +++ b/packages/office-viewer/src/util/autoSpace.ts @@ -0,0 +1,41 @@ +/** + * 中英文间自动加空格,基于下面代码改的,去掉了 lodash 依赖 + * https://gist.github.com/wyl8899/e0f31068681023480e20c34f6b19a275 + */ + +/* Partial implementation from https://zhuanlan.zhihu.com/p/33612593 */ + +/* 标点 */ +const punctuationRegex = /\p{Punctuation}/u; +/* 空格 */ +const spaceRegex = /\p{Separator}/u; +/* CJK 字符,中日韩 */ +const cjkRegex = + /\p{Script=Han}|\p{Script=Katakana}|\p{Script=Hiragana}|\p{Script=Hangul}/u; + +const shouldSpace = (a: string, b: string): boolean => { + if (cjkRegex.test(a)) { + return !( + punctuationRegex.test(b) || + spaceRegex.test(b) || + cjkRegex.test(b) + ); + } else { + return cjkRegex.test(b) && !punctuationRegex.test(a) && !spaceRegex.test(a); + } +}; + +const join = ( + parts: string[], + sepFunc: (a: string, b: string) => string +): string => { + return parts.reduce((r, p, i) => { + const sep = i !== 0 ? sepFunc(p, parts[i - 1]) : ''; + return r + sep + p; + }, ''); +}; + +export const cjkspace = (strings: string[]): string => { + const filtered = strings.filter(c => c !== undefined && c !== '') as string[]; + return join(filtered, (a, b) => (shouldSpace(a, b) ? ' ' : '')); +};