@file-viewer/renderer-word 2.2.4 → 2.2.6

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.
@@ -1,6 +1,10 @@
1
1
  import type { Options, renderAsync } from '@file-viewer/docx';
2
- import { type FileRenderContext, type FileViewerRenderedInstance as AppWrapper } from '@file-viewer/core';
2
+ import { type FileRenderContext, type FileViewerDocxOptions, type FileViewerRenderedInstance as AppWrapper } from '@file-viewer/core';
3
3
  type DocxRenderAsync = typeof renderAsync;
4
+ type DocxExternalLinkPolicy = NonNullable<FileViewerDocxOptions['externalLinkPolicy']>;
5
+ type DocxRenderOptions = Partial<Options> & {
6
+ externalLinkPolicy: DocxExternalLinkPolicy;
7
+ };
4
8
  export declare const isMissingDocxHeaderFooterRootError: (error: unknown) => boolean;
5
9
  /**
6
10
  * Some malformed or partially generated DOCX files reference a header/footer
@@ -9,6 +13,15 @@ export declare const isMissingDocxHeaderFooterRootError: (error: unknown) => boo
9
13
  * rolls through lockfiles and private registries.
10
14
  */
11
15
  export declare const renderDocxWithHeaderFooterFallback: (render: DocxRenderAsync, buffer: ArrayBuffer, target: HTMLDivElement, options: Options) => Promise<boolean>;
16
+ /**
17
+ * WPS and Word can store a page background as a document-level VML fill. The
18
+ * DOCX engine intentionally ignores that legacy drawing node, so resolve only
19
+ * its package-local image relationship here and leave all body layout to it.
20
+ */
21
+ export declare const resolveDocxPageBackgroundImage: (buffer: ArrayBuffer, createXmlParser?: () => Pick<DOMParser, "parseFromString">) => Promise<string | undefined>;
22
+ export declare const applyDocxPageBackgroundImage: (target: HTMLDivElement, imageUrl: string | undefined) => number;
23
+ export declare const applyDocxExternalLinkPolicy: (target: Pick<ParentNode, "querySelectorAll">, policy: DocxExternalLinkPolicy) => number;
24
+ export declare const createDocxOptions: (target: HTMLDivElement, context: FileRenderContext | undefined, notifyProgressiveRender: () => void) => DocxRenderOptions;
12
25
  /**
13
26
  * 渲染docx文件
14
27
  */
package/dist/wordDocx.js CHANGED
@@ -1,3 +1,4 @@
1
+ import JSZip from 'jszip';
1
2
  import { resolveFileViewerDocxWorkerJsZipUrl, resolveFileViewerDocxWorkerUrl, resolveFileViewerRuntimeAssetBaseUrl, } from '@file-viewer/core/assets';
2
3
  import { applyPrintPageSize, buildPrintPageStyle, createFileViewerTranslator, createFileViewerZoomChangeEmitter as createZoomChangeEmitter, formatCssPixels, getElementPrintPageSize, normalizeFileViewerTheme, registerFileViewerZoomProvider, resolveFileViewerFitScale, unregisterFileViewerZoomProvider, } from '@file-viewer/core';
3
4
  const DOCX_DEFAULT_PAGE_SIZE = {
@@ -10,6 +11,24 @@ const DOCX_MAX_SCALE = 3;
10
11
  const DOCX_ZOOM_STEP = 0.15;
11
12
  const DOCX_VENDOR_ASSET_VERSION = '0.3.26';
12
13
  const ZIP_SIGNATURE_PK = 0x504b;
14
+ const WORDPROCESSINGML_NAMESPACE = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main';
15
+ const OFFICE_RELATIONSHIP_NAMESPACE = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships';
16
+ const PACKAGE_RELATIONSHIP_NAMESPACE = 'http://schemas.openxmlformats.org/package/2006/relationships';
17
+ const VML_NAMESPACE = 'urn:schemas-microsoft-com:vml';
18
+ const DOCX_DOCUMENT_PART = 'word/document.xml';
19
+ const DOCX_DOCUMENT_RELATIONSHIPS_PART = 'word/_rels/document.xml.rels';
20
+ const DOCX_PAGE_BACKGROUND_CLASS = 'docx-page-background';
21
+ const DOCX_BACKGROUND_MIME_TYPES = {
22
+ bmp: 'image/bmp',
23
+ gif: 'image/gif',
24
+ jpeg: 'image/jpeg',
25
+ jpg: 'image/jpeg',
26
+ png: 'image/png',
27
+ svg: 'image/svg+xml',
28
+ tif: 'image/tiff',
29
+ tiff: 'image/tiff',
30
+ webp: 'image/webp'
31
+ };
13
32
  // Modern bundlers expose the ESM named exports, while some legacy webpack
14
33
  // configurations wrap the CommonJS browser API in `default`.
15
34
  const resolveDocxLibrary = (module) => {
@@ -79,6 +98,11 @@ const assertValidDocxPackage = (buffer, context) => {
79
98
  const getTargetWindow = (target) => {
80
99
  return target.ownerDocument.defaultView;
81
100
  };
101
+ const createTargetXmlParser = (target) => {
102
+ var _a, _b;
103
+ const DOMParserCtor = (_b = (_a = getTargetWindow(target)) === null || _a === void 0 ? void 0 : _a.DOMParser) !== null && _b !== void 0 ? _b : globalThis.DOMParser;
104
+ return new DOMParserCtor();
105
+ };
82
106
  const getTargetProtocol = (target) => {
83
107
  var _a, _b, _c;
84
108
  const candidates = [
@@ -96,6 +120,103 @@ const getTargetProtocol = (target) => {
96
120
  }
97
121
  return '';
98
122
  };
123
+ const getElementsByLocalName = (root, namespace, localName) => {
124
+ const namespaced = Array.from(root.getElementsByTagNameNS(namespace, localName));
125
+ if (namespaced.length) {
126
+ return namespaced;
127
+ }
128
+ return Array.from(root.getElementsByTagName('*')).filter(element => element.localName === localName);
129
+ };
130
+ const parseDocxXml = (source, parser) => {
131
+ const xml = parser.parseFromString(source, 'application/xml');
132
+ return getElementsByLocalName(xml, 'http://www.mozilla.org/newlayout/xml/parsererror.xml', 'parsererror').length
133
+ ? null
134
+ : xml;
135
+ };
136
+ const resolvePackagePartPath = (basePart, relationshipTarget) => {
137
+ const segments = relationshipTarget.startsWith('/') ? [] : basePart.split('/').slice(0, -1);
138
+ relationshipTarget
139
+ .replace(/^\/+/, '')
140
+ .split('/')
141
+ .forEach(segment => {
142
+ if (!segment || segment === '.') {
143
+ return;
144
+ }
145
+ if (segment === '..') {
146
+ segments.pop();
147
+ return;
148
+ }
149
+ segments.push(segment);
150
+ });
151
+ return segments.join('/');
152
+ };
153
+ const resolveDocxImageMimeType = (partName) => {
154
+ var _a;
155
+ const extension = ((_a = partName.split('.').pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase()) || '';
156
+ return DOCX_BACKGROUND_MIME_TYPES[extension];
157
+ };
158
+ /**
159
+ * WPS and Word can store a page background as a document-level VML fill. The
160
+ * DOCX engine intentionally ignores that legacy drawing node, so resolve only
161
+ * its package-local image relationship here and leave all body layout to it.
162
+ */
163
+ export const resolveDocxPageBackgroundImage = async (buffer, createXmlParser = () => new DOMParser()) => {
164
+ try {
165
+ const archive = await JSZip.loadAsync(buffer);
166
+ const documentEntry = archive.file(DOCX_DOCUMENT_PART);
167
+ const relationshipsEntry = archive.file(DOCX_DOCUMENT_RELATIONSHIPS_PART);
168
+ if (!documentEntry || !relationshipsEntry) {
169
+ return undefined;
170
+ }
171
+ const parser = createXmlParser();
172
+ const documentXml = parseDocxXml(await documentEntry.async('string'), parser);
173
+ const relationshipsXml = parseDocxXml(await relationshipsEntry.async('string'), parser);
174
+ if (!documentXml || !relationshipsXml) {
175
+ return undefined;
176
+ }
177
+ const background = getElementsByLocalName(documentXml, WORDPROCESSINGML_NAMESPACE, 'background')[0];
178
+ const fill = background && getElementsByLocalName(background, VML_NAMESPACE, 'fill')[0];
179
+ const relationshipId = (fill === null || fill === void 0 ? void 0 : fill.getAttributeNS(OFFICE_RELATIONSHIP_NAMESPACE, 'id')) || (fill === null || fill === void 0 ? void 0 : fill.getAttribute('r:id'));
180
+ if (!relationshipId) {
181
+ return undefined;
182
+ }
183
+ const relationship = getElementsByLocalName(relationshipsXml, PACKAGE_RELATIONSHIP_NAMESPACE, 'Relationship').find(candidate => candidate.getAttribute('Id') === relationshipId);
184
+ const target = relationship === null || relationship === void 0 ? void 0 : relationship.getAttribute('Target');
185
+ if (!target || (relationship === null || relationship === void 0 ? void 0 : relationship.getAttribute('TargetMode')) === 'External') {
186
+ return undefined;
187
+ }
188
+ const partName = resolvePackagePartPath(DOCX_DOCUMENT_PART, target);
189
+ const mimeType = resolveDocxImageMimeType(partName);
190
+ const imageEntry = archive.file(partName) || archive.file(decodeURIComponent(partName));
191
+ if (!mimeType || !imageEntry) {
192
+ return undefined;
193
+ }
194
+ return `data:${mimeType};base64,${await imageEntry.async('base64')}`;
195
+ }
196
+ catch {
197
+ // A page background is optional and must never make an otherwise readable
198
+ // document fail. The DOCX engine remains responsible for package errors.
199
+ return undefined;
200
+ }
201
+ };
202
+ export const applyDocxPageBackgroundImage = (target, imageUrl) => {
203
+ if (!imageUrl) {
204
+ return 0;
205
+ }
206
+ let applied = 0;
207
+ target.querySelectorAll('section.docx').forEach(page => {
208
+ const existing = Array.from(page.children).find(child => child.classList.contains(DOCX_PAGE_BACKGROUND_CLASS));
209
+ const background = existing || target.ownerDocument.createElement('div');
210
+ background.className = DOCX_PAGE_BACKGROUND_CLASS;
211
+ background.setAttribute('aria-hidden', 'true');
212
+ background.style.backgroundImage = `url("${imageUrl}")`;
213
+ if (!existing) {
214
+ page.prepend(background);
215
+ }
216
+ applied += 1;
217
+ });
218
+ return applied;
219
+ };
99
220
  const shouldUseDocxWorker = (target, docxOptions) => {
100
221
  var _a, _b;
101
222
  if ((docxOptions === null || docxOptions === void 0 ? void 0 : docxOptions.worker) === false) {
@@ -139,8 +260,27 @@ const appendDocxVendorAssetVersion = (url, explicitUrl) => {
139
260
  }
140
261
  return `${url}${url.includes('?') ? '&' : '?'}file-viewer-docx=${DOCX_VENDOR_ASSET_VERSION}`;
141
262
  };
142
- const createDocxOptions = (target, context, notifyProgressiveRender) => {
143
- var _a, _b;
263
+ export const applyDocxExternalLinkPolicy = (target, policy) => {
264
+ if (policy === 'allow') {
265
+ return 0;
266
+ }
267
+ let blocked = 0;
268
+ target.querySelectorAll('a[href]').forEach(anchor => {
269
+ const href = anchor.getAttribute('href');
270
+ if (!href || href.startsWith('#')) {
271
+ return;
272
+ }
273
+ if (!anchor.hasAttribute('data-docx-external-href')) {
274
+ anchor.setAttribute('data-docx-external-href', href);
275
+ }
276
+ anchor.removeAttribute('href');
277
+ anchor.setAttribute('aria-disabled', 'true');
278
+ blocked += 1;
279
+ });
280
+ return blocked;
281
+ };
282
+ export const createDocxOptions = (target, context, notifyProgressiveRender) => {
283
+ var _a, _b, _c;
144
284
  const docxOptions = (_a = context === null || context === void 0 ? void 0 : context.options) === null || _a === void 0 ? void 0 : _a.docx;
145
285
  const documentBaseUrl = resolveFileViewerRuntimeAssetBaseUrl(target.ownerDocument);
146
286
  const useWorker = shouldUseDocxWorker(target, docxOptions);
@@ -151,12 +291,19 @@ const createDocxOptions = (target, context, notifyProgressiveRender) => {
151
291
  notifyProgressiveRender();
152
292
  }
153
293
  };
294
+ const externalLinkPolicy = (_b = docxOptions === null || docxOptions === void 0 ? void 0 : docxOptions.externalLinkPolicy) !== null && _b !== void 0 ? _b : 'block';
154
295
  const options = {
155
296
  useWorker,
156
297
  breakPages: usePagedLayout,
157
- ignoreLastRenderedPageBreak: (_b = docxOptions === null || docxOptions === void 0 ? void 0 : docxOptions.ignoreLastRenderedPageBreak) !== null && _b !== void 0 ? _b : !usePagedLayout,
298
+ ignoreLastRenderedPageBreak: (_c = docxOptions === null || docxOptions === void 0 ? void 0 : docxOptions.ignoreLastRenderedPageBreak) !== null && _c !== void 0 ? _c : !usePagedLayout,
299
+ externalLinkPolicy,
158
300
  darkMode,
159
- progress
301
+ progress: event => {
302
+ if (event.phase === 'render' || event.phase === 'layout' || event.phase === 'done') {
303
+ applyDocxExternalLinkPolicy(target, externalLinkPolicy);
304
+ }
305
+ progress(event);
306
+ }
160
307
  };
161
308
  if (useWorker) {
162
309
  options.workerUrl = appendDocxVendorAssetVersion(resolveFileViewerDocxWorkerUrl(docxOptions, documentBaseUrl), !!(docxOptions === null || docxOptions === void 0 ? void 0 : docxOptions.workerUrl));
@@ -250,6 +397,15 @@ const DOCX_RESPONSIVE_CSS = `
250
397
  overflow: hidden;
251
398
  transform-origin: top center;
252
399
  }
400
+ .docx-fit-viewer .docx-page-background {
401
+ position: absolute;
402
+ inset: 0;
403
+ z-index: 0;
404
+ pointer-events: none;
405
+ background-position: center;
406
+ background-repeat: no-repeat;
407
+ background-size: 100% 100%;
408
+ }
253
409
  .docx-fit-viewer[data-docx-dark-mode='true'] .docx-page-frame > section.docx,
254
410
  .docx-fit-viewer[data-docx-dark-mode='true'] .docx-flow-frame > section.docx {
255
411
  background: rgb(51, 51, 51) !important;
@@ -552,14 +708,20 @@ export default async function (buffer, target, context) {
552
708
  (_a = context === null || context === void 0 ? void 0 : context.onProgressiveRender) === null || _a === void 0 ? void 0 : _a.call(context);
553
709
  };
554
710
  const docxOptions = createDocxOptions(target, context, notifyProgressiveRender);
555
- const { defaultOptions, renderAsync } = await loadLibrary();
711
+ const [{ defaultOptions, renderAsync }, pageBackgroundImage] = await Promise.all([
712
+ loadLibrary(),
713
+ resolveDocxPageBackgroundImage(buffer, () => createTargetXmlParser(target))
714
+ ]);
556
715
  target.dataset.docxWorker = docxOptions.useWorker ? 'self' : 'false';
557
716
  target.dataset.docxDarkMode = docxOptions.darkMode ? 'true' : 'false';
558
717
  const usedHeaderFooterFallback = await renderDocxWithHeaderFooterFallback(renderAsync, buffer, target, {
559
718
  ...defaultOptions,
560
719
  ...docxOptions
561
720
  });
721
+ applyDocxExternalLinkPolicy(target, docxOptions.externalLinkPolicy);
562
722
  target.dataset.docxHeaderFooterFallback = usedHeaderFooterFallback ? 'true' : 'false';
723
+ target.dataset.docxPageBackground =
724
+ applyDocxPageBackgroundImage(target, pageBackgroundImage) > 0 ? 'true' : 'false';
563
725
  notifyProgressiveRender();
564
726
  const disposeResponsive = makeDocxResponsive(target, context);
565
727
  (_a = context === null || context === void 0 ? void 0 : context.registerExportAdapter) === null || _a === void 0 ? void 0 : _a.call(context, {
@@ -587,6 +749,7 @@ export default async function (buffer, target, context) {
587
749
  delete target.dataset.docxWorker;
588
750
  delete target.dataset.docxDarkMode;
589
751
  delete target.dataset.docxHeaderFooterFallback;
752
+ delete target.dataset.docxPageBackground;
590
753
  target.innerHTML = '';
591
754
  }
592
755
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@file-viewer/renderer-word",
3
- "version": "2.2.4",
3
+ "version": "2.2.6",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Standalone Word and compatible document renderer plugin for File Viewer powered by @file-viewer/docx, @file-viewer/doc, and RTF/ODF parsing.",
@@ -57,8 +57,8 @@
57
57
  "LICENSE"
58
58
  ],
59
59
  "dependencies": {
60
- "@file-viewer/core": "2.2.4",
61
- "@file-viewer/doc": "2.2.4",
60
+ "@file-viewer/core": "2.2.6",
61
+ "@file-viewer/doc": "2.2.6",
62
62
  "@file-viewer/docx": "^0.3.26",
63
63
  "jszip": "^3.10.1",
64
64
  "rtf.js": "^3.0.9"