@file-viewer/renderer-word 3.0.3 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docxAnchors.d.ts +8 -0
- package/dist/docxAnchors.js +60 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +20 -5
- package/dist/wordDocx.js +2 -0
- package/dist/wordMl.d.ts +8 -0
- package/dist/wordMl.js +211 -0
- package/package.json +4 -4
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A paragraph-relative vertical anchor makes that paragraph an absolute-position
|
|
3
|
+
* containing block. Its horizontal axis can still be relative to the column or
|
|
4
|
+
* page margins: paragraph indentation must not be added to that authored offset.
|
|
5
|
+
* Keep the engine's original CSS length and correct only the containing-block
|
|
6
|
+
* origin. Recompute after fitting/resizing; never accumulate pixel corrections.
|
|
7
|
+
*/
|
|
8
|
+
export declare function correctDocxMixedAnchorOrigins(section: HTMLElement): void;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const authoredLeft = new WeakMap();
|
|
2
|
+
const number = (value) => Number.parseFloat(value) || 0;
|
|
3
|
+
/**
|
|
4
|
+
* A paragraph-relative vertical anchor makes that paragraph an absolute-position
|
|
5
|
+
* containing block. Its horizontal axis can still be relative to the column or
|
|
6
|
+
* page margins: paragraph indentation must not be added to that authored offset.
|
|
7
|
+
* Keep the engine's original CSS length and correct only the containing-block
|
|
8
|
+
* origin. Recompute after fitting/resizing; never accumulate pixel corrections.
|
|
9
|
+
*/
|
|
10
|
+
export function correctDocxMixedAnchorOrigins(section) {
|
|
11
|
+
var _a;
|
|
12
|
+
const view = section.ownerDocument.defaultView;
|
|
13
|
+
if (!view)
|
|
14
|
+
return;
|
|
15
|
+
const pageRect = section.getBoundingClientRect();
|
|
16
|
+
const pageStyle = view.getComputedStyle(section);
|
|
17
|
+
const pageWidth = number(pageStyle.width) + (pageStyle.boxSizing === 'border-box' ? 0 :
|
|
18
|
+
number(pageStyle.paddingLeft) + number(pageStyle.paddingRight) + number(pageStyle.borderLeftWidth) + number(pageStyle.borderRightWidth));
|
|
19
|
+
const scale = pageWidth ? pageRect.width / pageWidth : 0;
|
|
20
|
+
if (!Number.isFinite(scale) || scale <= 0)
|
|
21
|
+
return;
|
|
22
|
+
for (const anchor of section.querySelectorAll('[data-docx-anchor-horizontal="column"], [data-docx-anchor-horizontal="margin"]')) {
|
|
23
|
+
const paragraph = anchor.closest('[data-docx-anchor-context="paragraph"]');
|
|
24
|
+
if (!paragraph || anchor.offsetParent !== paragraph || view.getComputedStyle(anchor).position !== 'absolute' || !anchor.style.left)
|
|
25
|
+
continue;
|
|
26
|
+
// Cell-relative drawings are left to the engine's layoutInCell policy.
|
|
27
|
+
if (paragraph.closest('td, th'))
|
|
28
|
+
continue;
|
|
29
|
+
const root = anchor.dataset.docxAnchorHorizontal === 'margin'
|
|
30
|
+
? section : paragraph.closest('article, header, footer');
|
|
31
|
+
if (!root || !section.contains(root) && root !== section)
|
|
32
|
+
continue;
|
|
33
|
+
const rootStyle = view.getComputedStyle(root);
|
|
34
|
+
const rootRect = root.getBoundingClientRect();
|
|
35
|
+
let origin = rootRect.left + (number(rootStyle.borderLeftWidth) + number(rootStyle.paddingLeft)) * scale;
|
|
36
|
+
const paragraphStyle = view.getComputedStyle(paragraph);
|
|
37
|
+
const paragraphRect = paragraph.getBoundingClientRect();
|
|
38
|
+
if (root !== section) {
|
|
39
|
+
const count = number(rootStyle.columnCount);
|
|
40
|
+
const width = number(rootStyle.columnWidth);
|
|
41
|
+
if (count > 1 || width > 0) {
|
|
42
|
+
const gap = rootStyle.columnGap === 'normal' ? number(rootStyle.fontSize) : number(rootStyle.columnGap);
|
|
43
|
+
const contentWidth = root.clientWidth - number(rootStyle.paddingLeft) - number(rootStyle.paddingRight);
|
|
44
|
+
const columns = count > 0 ? count : Math.max(1, Math.floor((contentWidth + gap) / (width + gap)));
|
|
45
|
+
const stride = (contentWidth + gap) / columns;
|
|
46
|
+
if (stride > 0) {
|
|
47
|
+
const unindentedLeft = (paragraphRect.left - origin) / scale - number(paragraphStyle.marginLeft);
|
|
48
|
+
origin += Math.round(unindentedLeft / stride) * stride * scale;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const containingOrigin = paragraphRect.left + number(paragraphStyle.borderLeftWidth) * scale;
|
|
53
|
+
const correction = (origin - containingOrigin) / scale;
|
|
54
|
+
if (!Number.isFinite(correction))
|
|
55
|
+
continue;
|
|
56
|
+
const left = (_a = authoredLeft.get(anchor)) !== null && _a !== void 0 ? _a : anchor.style.left;
|
|
57
|
+
authoredLeft.set(anchor, left);
|
|
58
|
+
anchor.style.left = Math.abs(correction) < 0.01 ? left : `calc(${left} + ${correction.toFixed(4)}px)`;
|
|
59
|
+
}
|
|
60
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,9 +8,10 @@ export declare const renderFileViewerWordDocx: FileRenderHandler<FileViewerRende
|
|
|
8
8
|
/**
|
|
9
9
|
* A surprising number of legacy systems keep the `.doc` suffix after saving
|
|
10
10
|
* an OOXML document. Route those ZIP-based files through the DOCX engine while
|
|
11
|
-
* leaving genuine OLE/CFB `.doc` files on the binary parser.
|
|
11
|
+
* leaving genuine OLE/CFB `.doc` files on the binary parser. Word 2003 XML
|
|
12
|
+
* has its own single-file container and is adapted lazily to the OOXML renderer.
|
|
12
13
|
*/
|
|
13
|
-
export declare const resolveFileViewerWordContainer: (buffer: ArrayBuffer) => "openxml" | "binary";
|
|
14
|
+
export declare const resolveFileViewerWordContainer: (buffer: ArrayBuffer) => "openxml" | "wordml" | "binary";
|
|
14
15
|
export declare const renderFileViewerWordDoc: FileRenderHandler<FileViewerRenderedInstance, HTMLDivElement>;
|
|
15
16
|
export declare const renderFileViewerOpenDocument: FileRenderHandler<FileViewerRenderedInstance, HTMLDivElement>;
|
|
16
17
|
export declare const wordRenderer: FileViewerRendererPlugin<FileRenderHandler<FileViewerRenderedInstance, HTMLDivElement>>;
|
package/dist/index.js
CHANGED
|
@@ -15,18 +15,33 @@ export const renderFileViewerWordDocx = (buffer, target, _type, context) => impo
|
|
|
15
15
|
/**
|
|
16
16
|
* A surprising number of legacy systems keep the `.doc` suffix after saving
|
|
17
17
|
* an OOXML document. Route those ZIP-based files through the DOCX engine while
|
|
18
|
-
* leaving genuine OLE/CFB `.doc` files on the binary parser.
|
|
18
|
+
* leaving genuine OLE/CFB `.doc` files on the binary parser. Word 2003 XML
|
|
19
|
+
* has its own single-file container and is adapted lazily to the OOXML renderer.
|
|
19
20
|
*/
|
|
20
21
|
export const resolveFileViewerWordContainer = (buffer) => {
|
|
21
22
|
if (buffer.byteLength < 2) {
|
|
22
23
|
return 'binary';
|
|
23
24
|
}
|
|
24
25
|
const bytes = new Uint8Array(buffer, 0, 2);
|
|
25
|
-
|
|
26
|
+
if (bytes[0] === 0x50 && bytes[1] === 0x4b)
|
|
27
|
+
return 'openxml';
|
|
28
|
+
const prefix = new Uint8Array(buffer, 0, Math.min(buffer.byteLength, 8192));
|
|
29
|
+
const utf16le = (bytes[0] === 0xff && bytes[1] === 0xfe) || (bytes[0] === 0x3c && bytes[1] === 0);
|
|
30
|
+
const utf16be = (bytes[0] === 0xfe && bytes[1] === 0xff) || (bytes[0] === 0 && bytes[1] === 0x3c);
|
|
31
|
+
const text = new TextDecoder(utf16le ? 'utf-16le' : utf16be ? 'utf-16be' : 'utf-8').decode(prefix);
|
|
32
|
+
return /<(?:[\w.-]+:)?wordDocument(?:\s|>)/.test(text) &&
|
|
33
|
+
text.includes('http://schemas.microsoft.com/office/word/2003/wordml') ? 'wordml' : 'binary';
|
|
34
|
+
};
|
|
35
|
+
export const renderFileViewerWordDoc = (buffer, target, _type, context) => {
|
|
36
|
+
const container = resolveFileViewerWordContainer(buffer);
|
|
37
|
+
if (container === 'wordml') {
|
|
38
|
+
return Promise.all([import('./wordMl.js'), import('./wordDocx.js')])
|
|
39
|
+
.then(async ([{ convertWordMlToDocx }, { default: renderWordDocx }]) => renderWordDocx(await convertWordMlToDocx(buffer, target), target, context));
|
|
40
|
+
}
|
|
41
|
+
return container === 'openxml'
|
|
42
|
+
? import('./wordDocx.js').then(({ default: renderWordDocx }) => renderWordDocx(buffer, target, context))
|
|
43
|
+
: import('./wordDoc.js').then(({ default: renderWordDoc }) => renderWordDoc(buffer, target, context));
|
|
26
44
|
};
|
|
27
|
-
export const renderFileViewerWordDoc = (buffer, target, _type, context) => resolveFileViewerWordContainer(buffer) === 'openxml'
|
|
28
|
-
? import('./wordDocx.js').then(({ default: renderWordDocx }) => renderWordDocx(buffer, target, context))
|
|
29
|
-
: import('./wordDoc.js').then(({ default: renderWordDoc }) => renderWordDoc(buffer, target, context));
|
|
30
45
|
export const renderFileViewerOpenDocument = (buffer, target, type, context) => import('./openDocument.js').then(({ default: renderOpenDocument }) => renderOpenDocument(buffer, target, type, context));
|
|
31
46
|
export const wordRenderer = {
|
|
32
47
|
id: 'file-viewer-renderer-word',
|
package/dist/wordDocx.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import JSZip from 'jszip';
|
|
2
|
+
import { correctDocxMixedAnchorOrigins } from './docxAnchors.js';
|
|
2
3
|
import { DEFAULT_FILE_VIEWER_DOCX_RUNTIME_VERSION, resolveFileViewerDocxWorkerJsZipUrl, resolveFileViewerDocxWorkerUrl, resolveFileViewerRuntimeAssetBaseUrl, } from '@file-viewer/core/assets';
|
|
3
4
|
import { applyPrintPageSize, buildPrintPageStyle, createFileViewerTranslator, createFileViewerZoomChangeEmitter as createZoomChangeEmitter, formatCssPixels, getElementPrintPageSize, normalizeFileViewerTheme, replaceFileViewerCanvasWithImages, registerFileViewerZoomProvider, resolveFileViewerFitScale, unregisterFileViewerZoomProvider, waitForFileViewerNextPaint, } from '@file-viewer/core';
|
|
4
5
|
const DOCX_DEFAULT_PAGE_SIZE = {
|
|
@@ -482,6 +483,7 @@ function makeDocxResponsive(target, context) {
|
|
|
482
483
|
return;
|
|
483
484
|
}
|
|
484
485
|
page.style.transform = 'translateX(-50%)';
|
|
486
|
+
correctDocxMixedAnchorOrigins(page);
|
|
485
487
|
const pageWidth = page.offsetWidth;
|
|
486
488
|
const contentHeight = pagedLayout
|
|
487
489
|
? page.offsetHeight
|
package/dist/wordMl.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function decodeWordMlBytes(buffer: ArrayBuffer): string;
|
|
2
|
+
/**
|
|
3
|
+
* Adapt the single-file Word 2003 XML container to the existing OOXML renderer.
|
|
4
|
+
* This is structural conversion, not HTML insertion or remote conversion. Body,
|
|
5
|
+
* styles, table properties, section geometry and VML remain renderer-owned.
|
|
6
|
+
* Inline headers/footers become package parts; only embedded image data is read.
|
|
7
|
+
*/
|
|
8
|
+
export declare function convertWordMlToDocx(buffer: ArrayBuffer, target: HTMLElement): Promise<ArrayBuffer>;
|
package/dist/wordMl.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import JSZip from 'jszip';
|
|
2
|
+
const WORDML = 'http://schemas.microsoft.com/office/word/2003/wordml';
|
|
3
|
+
const WORD = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main';
|
|
4
|
+
const REL = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships';
|
|
5
|
+
const PACKAGE_REL = 'http://schemas.openxmlformats.org/package/2006/relationships';
|
|
6
|
+
const CONTENT_TYPES = 'http://schemas.openxmlformats.org/package/2006/content-types';
|
|
7
|
+
const AUX = 'http://schemas.microsoft.com/office/word/2003/auxHint';
|
|
8
|
+
const VML = 'urn:schemas-microsoft-com:vml';
|
|
9
|
+
const XMLNS = 'http://www.w3.org/2000/xmlns/';
|
|
10
|
+
const aliases = {
|
|
11
|
+
'h-ansi': 'hAnsi', fareast: 'eastAsia', 'b-cs': 'bCs', 'i-cs': 'iCs',
|
|
12
|
+
'sz-cs': 'szCs', 'lang-cs': 'lang', 'panose-1': 'panose1',
|
|
13
|
+
'line-rule': 'lineRule', 'line-pitch': 'linePitch', 'char-space': 'charSpace',
|
|
14
|
+
'hanging-chars': 'hangingChars', 'first-line': 'firstLine',
|
|
15
|
+
'first-line-chars': 'firstLineChars', 'left-chars': 'leftChars', 'right-chars': 'rightChars',
|
|
16
|
+
};
|
|
17
|
+
export function decodeWordMlBytes(buffer) {
|
|
18
|
+
const bytes = new Uint8Array(buffer);
|
|
19
|
+
const utf16le = (bytes[0] === 0xff && bytes[1] === 0xfe) || (bytes[0] === 0x3c && bytes[1] === 0);
|
|
20
|
+
const utf16be = (bytes[0] === 0xfe && bytes[1] === 0xff) || (bytes[0] === 0 && bytes[1] === 0x3c);
|
|
21
|
+
return new TextDecoder(utf16le ? 'utf-16le' : utf16be ? 'utf-16be' : 'utf-8').decode(bytes);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Adapt the single-file Word 2003 XML container to the existing OOXML renderer.
|
|
25
|
+
* This is structural conversion, not HTML insertion or remote conversion. Body,
|
|
26
|
+
* styles, table properties, section geometry and VML remain renderer-owned.
|
|
27
|
+
* Inline headers/footers become package parts; only embedded image data is read.
|
|
28
|
+
*/
|
|
29
|
+
export async function convertWordMlToDocx(buffer, target) {
|
|
30
|
+
var _a, _b, _c, _d;
|
|
31
|
+
const source = decodeWordMlBytes(buffer);
|
|
32
|
+
if (/<!DOCTYPE\b|<!ENTITY\b/i.test(source)) {
|
|
33
|
+
throw new Error('Word 2003 XML must not contain a DTD or entity declarations.');
|
|
34
|
+
}
|
|
35
|
+
const view = target.ownerDocument.defaultView;
|
|
36
|
+
const Parser = (_a = view === null || view === void 0 ? void 0 : view.DOMParser) !== null && _a !== void 0 ? _a : globalThis.DOMParser;
|
|
37
|
+
const Serializer = (_b = view === null || view === void 0 ? void 0 : view.XMLSerializer) !== null && _b !== void 0 ? _b : globalThis.XMLSerializer;
|
|
38
|
+
const input = new Parser().parseFromString(source, 'application/xml');
|
|
39
|
+
if (input.getElementsByTagName('parsererror').length || input.documentElement.localName !== 'wordDocument' || input.documentElement.namespaceURI !== WORDML) {
|
|
40
|
+
throw new Error('Invalid Word 2003 XML document.');
|
|
41
|
+
}
|
|
42
|
+
const body = Array.from(input.documentElement.children).find(element => element.namespaceURI === WORDML && element.localName === 'body');
|
|
43
|
+
if (!body)
|
|
44
|
+
throw new Error('Word 2003 XML document has no body.');
|
|
45
|
+
const zip = new JSZip();
|
|
46
|
+
const serializer = new Serializer();
|
|
47
|
+
const overrides = [];
|
|
48
|
+
const images = new Map();
|
|
49
|
+
// Never resolve a WordML image name as a file/HTTP URL. It must have binData.
|
|
50
|
+
for (const binary of Array.from(input.getElementsByTagNameNS(WORDML, 'binData'))) {
|
|
51
|
+
const name = binary.getAttributeNS(WORDML, 'name') || '';
|
|
52
|
+
if (images.has(name))
|
|
53
|
+
continue;
|
|
54
|
+
const extension = (_c = /\.(png|jpe?g|gif|bmp|tiff?|emf|wmf)$/i.exec(name)) === null || _c === void 0 ? void 0 : _c[1].toLowerCase();
|
|
55
|
+
const bytes = ((_d = binary.textContent) === null || _d === void 0 ? void 0 : _d.replace(/\s/g, '')) || '';
|
|
56
|
+
if (name && extension && /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(bytes) && bytes) {
|
|
57
|
+
const path = `media/image${images.size + 1}.${extension}`;
|
|
58
|
+
images.set(name, { path, bytes });
|
|
59
|
+
zip.file(`word/${path}`, bytes, { base64: true });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const createDocument = (name) => {
|
|
63
|
+
const doc = input.implementation.createDocument(WORD, `w:${name}`, null);
|
|
64
|
+
doc.documentElement.setAttributeNS(XMLNS, 'xmlns:r', REL);
|
|
65
|
+
return doc;
|
|
66
|
+
};
|
|
67
|
+
const serialize = (document) => `<?xml version="1.0" encoding="UTF-8"?>${serializer.serializeToString(document)}`;
|
|
68
|
+
const relationships = new Map();
|
|
69
|
+
function relate(part, type, destination) {
|
|
70
|
+
let doc = relationships.get(part);
|
|
71
|
+
if (!doc) {
|
|
72
|
+
doc = input.implementation.createDocument(PACKAGE_REL, 'Relationships', null);
|
|
73
|
+
relationships.set(part, doc);
|
|
74
|
+
}
|
|
75
|
+
const id = `rId${doc.documentElement.children.length + 1}`;
|
|
76
|
+
const item = doc.createElementNS(PACKAGE_REL, 'Relationship');
|
|
77
|
+
item.setAttribute('Id', id);
|
|
78
|
+
item.setAttribute('Type', `${REL}/${type}`);
|
|
79
|
+
item.setAttribute('Target', destination);
|
|
80
|
+
doc.documentElement.appendChild(item);
|
|
81
|
+
return id;
|
|
82
|
+
}
|
|
83
|
+
let headerNumber = 0;
|
|
84
|
+
let footerNumber = 0;
|
|
85
|
+
function appendConverted(node, parent, part, depth = 0) {
|
|
86
|
+
if (depth > 256)
|
|
87
|
+
throw new Error('Word 2003 XML nesting is too deep.');
|
|
88
|
+
const output = parent.ownerDocument;
|
|
89
|
+
if (node.nodeType === 3 || node.nodeType === 4) {
|
|
90
|
+
parent.appendChild(output.createTextNode(node.textContent || ''));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (node.nodeType !== 1)
|
|
94
|
+
return;
|
|
95
|
+
const element = node;
|
|
96
|
+
const isWord = element.namespaceURI === WORDML;
|
|
97
|
+
if (isWord && element.localName === 'binData')
|
|
98
|
+
return;
|
|
99
|
+
if (element.namespaceURI === AUX) {
|
|
100
|
+
// wx:sect and wx:sub-section are grouping hints, not content containers.
|
|
101
|
+
for (const child of Array.from(element.childNodes))
|
|
102
|
+
appendConverted(child, parent, part, depth + 1);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (isWord && (element.localName === 'hdr' || element.localName === 'ftr') && parent.localName === 'sectPr') {
|
|
106
|
+
const header = element.localName === 'hdr';
|
|
107
|
+
const partName = `${header ? 'header' : 'footer'}${header ? ++headerNumber : ++footerNumber}.xml`;
|
|
108
|
+
const document = createDocument(header ? 'hdr' : 'ftr');
|
|
109
|
+
for (const child of Array.from(element.childNodes))
|
|
110
|
+
appendConverted(child, document.documentElement, partName, depth + 1);
|
|
111
|
+
zip.file(`word/${partName}`, serialize(document));
|
|
112
|
+
overrides.push([`/word/${partName}`, `application/vnd.openxmlformats-officedocument.wordprocessingml.${header ? 'header' : 'footer'}+xml`]);
|
|
113
|
+
const reference = output.createElementNS(WORD, `w:${header ? 'header' : 'footer'}Reference`);
|
|
114
|
+
const type = element.getAttributeNS(WORDML, 'type');
|
|
115
|
+
reference.setAttributeNS(WORD, 'w:type', type === 'first' || type === 'even' ? type : 'default');
|
|
116
|
+
reference.setAttributeNS(REL, 'r:id', relate(part, header ? 'header' : 'footer', partName));
|
|
117
|
+
parent.appendChild(reference);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const namespace = isWord ? WORD : element.namespaceURI;
|
|
121
|
+
const localName = isWord ? aliases[element.localName] || element.localName : element.localName;
|
|
122
|
+
const name = isWord ? `w:${localName}` : element.nodeName;
|
|
123
|
+
const converted = output.createElementNS(namespace, name);
|
|
124
|
+
for (const attribute of Array.from(element.attributes)) {
|
|
125
|
+
if (attribute.namespaceURI === XMLNS)
|
|
126
|
+
continue;
|
|
127
|
+
// VML image references are replaced with package-local relationships below.
|
|
128
|
+
if (namespace === VML && (localName === 'imagedata' || localName === 'fill') && (attribute.localName === 'src' || attribute.localName === 'href'))
|
|
129
|
+
continue;
|
|
130
|
+
const wordAttribute = attribute.namespaceURI === WORDML;
|
|
131
|
+
const key = wordAttribute ? aliases[attribute.localName] || attribute.localName : attribute.localName;
|
|
132
|
+
let value = attribute.value;
|
|
133
|
+
if (wordAttribute && key === 'hint' && value === 'fareast')
|
|
134
|
+
value = 'eastAsia';
|
|
135
|
+
if (isWord && localName === 'fldChar' && wordAttribute && key === 'fldCharType' && value === 'start')
|
|
136
|
+
value = 'begin';
|
|
137
|
+
converted.setAttributeNS(wordAttribute ? WORD : attribute.namespaceURI, wordAttribute ? `w:${key}` : attribute.name, value);
|
|
138
|
+
}
|
|
139
|
+
if (namespace === VML && (localName === 'imagedata' || localName === 'fill')) {
|
|
140
|
+
const image = images.get(element.getAttribute('src') || '');
|
|
141
|
+
if (image)
|
|
142
|
+
converted.setAttributeNS(REL, 'r:id', relate(part, 'image', image.path));
|
|
143
|
+
}
|
|
144
|
+
parent.appendChild(converted);
|
|
145
|
+
for (const child of Array.from(element.childNodes))
|
|
146
|
+
appendConverted(child, converted, part, depth + 1);
|
|
147
|
+
}
|
|
148
|
+
const document = createDocument('document');
|
|
149
|
+
const outputBody = document.createElementNS(WORD, 'w:body');
|
|
150
|
+
document.documentElement.appendChild(outputBody);
|
|
151
|
+
for (const child of Array.from(body.childNodes))
|
|
152
|
+
appendConverted(child, outputBody, 'document.xml');
|
|
153
|
+
// In OOXML only the final section properties may be a direct body child.
|
|
154
|
+
// Earlier WordML wx:sect properties belong on the preceding paragraph.
|
|
155
|
+
for (const section of Array.from(outputBody.children)) {
|
|
156
|
+
if (section.localName !== 'sectPr' || section === outputBody.lastElementChild)
|
|
157
|
+
continue;
|
|
158
|
+
let paragraph = section.previousElementSibling;
|
|
159
|
+
if (!paragraph || paragraph.localName !== 'p') {
|
|
160
|
+
paragraph = document.createElementNS(WORD, 'w:p');
|
|
161
|
+
outputBody.insertBefore(paragraph, section);
|
|
162
|
+
}
|
|
163
|
+
let properties = Array.from(paragraph.children).find(child => child.localName === 'pPr');
|
|
164
|
+
if (!properties) {
|
|
165
|
+
properties = document.createElementNS(WORD, 'w:pPr');
|
|
166
|
+
paragraph.insertBefore(properties, paragraph.firstChild);
|
|
167
|
+
}
|
|
168
|
+
properties.appendChild(section);
|
|
169
|
+
}
|
|
170
|
+
zip.file('word/document.xml', serialize(document));
|
|
171
|
+
overrides.push(['/word/document.xml', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml']);
|
|
172
|
+
for (const [sourceName, rootName, path, type] of [
|
|
173
|
+
['styles', 'styles', 'styles.xml', 'styles'],
|
|
174
|
+
['fonts', 'fonts', 'fontTable.xml', 'fontTable'],
|
|
175
|
+
['docPr', 'settings', 'settings.xml', 'settings'],
|
|
176
|
+
]) {
|
|
177
|
+
const element = Array.from(input.documentElement.children).find(child => child.namespaceURI === WORDML && child.localName === sourceName);
|
|
178
|
+
if (!element)
|
|
179
|
+
continue;
|
|
180
|
+
const part = createDocument(rootName);
|
|
181
|
+
for (const child of Array.from(element.childNodes))
|
|
182
|
+
appendConverted(child, part.documentElement, path);
|
|
183
|
+
zip.file(`word/${path}`, serialize(part));
|
|
184
|
+
relate('document.xml', type, path);
|
|
185
|
+
overrides.push([`/word/${path}`, `application/vnd.openxmlformats-officedocument.wordprocessingml.${type}+xml`]);
|
|
186
|
+
}
|
|
187
|
+
for (const [part, relations] of relationships)
|
|
188
|
+
zip.file(`word/_rels/${part}.rels`, serialize(relations));
|
|
189
|
+
const rootRelations = input.implementation.createDocument(PACKAGE_REL, 'Relationships', null);
|
|
190
|
+
const office = rootRelations.createElementNS(PACKAGE_REL, 'Relationship');
|
|
191
|
+
for (const [name, value] of [['Id', 'rId1'], ['Type', `${REL}/officeDocument`], ['Target', 'word/document.xml']])
|
|
192
|
+
office.setAttribute(name, value);
|
|
193
|
+
rootRelations.documentElement.appendChild(office);
|
|
194
|
+
zip.file('_rels/.rels', serialize(rootRelations));
|
|
195
|
+
const types = input.implementation.createDocument(CONTENT_TYPES, 'Types', null);
|
|
196
|
+
const imageTypes = { png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', bmp: 'image/bmp', tif: 'image/tiff', tiff: 'image/tiff', emf: 'image/x-emf', wmf: 'image/x-wmf' };
|
|
197
|
+
for (const [extension, mime] of [['rels', 'application/vnd.openxmlformats-package.relationships+xml'], ['xml', 'application/xml'], ...Object.entries(imageTypes)]) {
|
|
198
|
+
const entry = types.createElementNS(CONTENT_TYPES, 'Default');
|
|
199
|
+
entry.setAttribute('Extension', extension);
|
|
200
|
+
entry.setAttribute('ContentType', mime);
|
|
201
|
+
types.documentElement.appendChild(entry);
|
|
202
|
+
}
|
|
203
|
+
for (const [path, mime] of overrides) {
|
|
204
|
+
const entry = types.createElementNS(CONTENT_TYPES, 'Override');
|
|
205
|
+
entry.setAttribute('PartName', path);
|
|
206
|
+
entry.setAttribute('ContentType', mime);
|
|
207
|
+
types.documentElement.appendChild(entry);
|
|
208
|
+
}
|
|
209
|
+
zip.file('[Content_Types].xml', serialize(types));
|
|
210
|
+
return zip.generateAsync({ type: 'arraybuffer' });
|
|
211
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@file-viewer/renderer-word",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Standalone DOCX, DOC, and OpenDocument renderer for File Viewer; RTF parsing is an explicit opt-in capability.",
|
|
@@ -58,9 +58,9 @@
|
|
|
58
58
|
"LICENSE"
|
|
59
59
|
],
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"@file-viewer/core": "3.0
|
|
62
|
-
"@file-viewer/doc": "3.0
|
|
63
|
-
"@file-viewer/docx": "0.3.
|
|
61
|
+
"@file-viewer/core": "3.1.0",
|
|
62
|
+
"@file-viewer/doc": "3.1.0",
|
|
63
|
+
"@file-viewer/docx": "0.3.32",
|
|
64
64
|
"jszip": "3.10.2"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|