@file-viewer/core 2.2.3 → 2.2.5

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.
@@ -0,0 +1,250 @@
1
+ import { DEFAULT_RENDERER_DEFINITIONS, DEFAULT_SUPPORTED_EXTENSIONS } from '../registry/formats.js';
2
+ import { getExtension, normalizeFileExtension, normalizeFilename, normalizeSource, } from './index.js';
3
+ const DEFAULT_MAX_PACKAGE_METADATA_BYTES = 8 * 1024 * 1024;
4
+ const ZIP_LOCAL_SIGNATURE = 0x04034b50;
5
+ const ZIP_CENTRAL_SIGNATURE = 0x02014b50;
6
+ const ZIP_END_SIGNATURE = 0x06054b50;
7
+ const ZIP64_SENTINEL = 0xffffffff;
8
+ const OLE_SIGNATURE = [0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1];
9
+ const OPEN_XML_REQUIRED_PARTS = {
10
+ docx: ['[content_types].xml', 'word/document.xml'],
11
+ docm: ['[content_types].xml', 'word/document.xml'],
12
+ dotx: ['[content_types].xml', 'word/document.xml'],
13
+ dotm: ['[content_types].xml', 'word/document.xml'],
14
+ xlsx: ['[content_types].xml', 'xl/workbook.xml'],
15
+ xltx: ['[content_types].xml', 'xl/workbook.xml'],
16
+ xlsm: ['[content_types].xml', 'xl/workbook.xml'],
17
+ xltm: ['[content_types].xml', 'xl/workbook.xml'],
18
+ xlsb: ['[content_types].xml', 'xl/workbook.bin'],
19
+ pptx: ['[content_types].xml', 'ppt/presentation.xml'],
20
+ pptm: ['[content_types].xml', 'ppt/presentation.xml'],
21
+ potx: ['[content_types].xml', 'ppt/presentation.xml'],
22
+ potm: ['[content_types].xml', 'ppt/presentation.xml'],
23
+ ppsx: ['[content_types].xml', 'ppt/presentation.xml'],
24
+ ppsm: ['[content_types].xml', 'ppt/presentation.xml'],
25
+ odt: ['content.xml'],
26
+ ods: ['content.xml'],
27
+ odp: ['content.xml'],
28
+ ofd: ['ofd.xml'],
29
+ numbers: ['index/document.iwa'],
30
+ epub: ['meta-inf/container.xml'],
31
+ xps: ['[content_types].xml'],
32
+ dwfx: ['[content_types].xml'],
33
+ };
34
+ const ZIP_EXTENSIONS = new Set([
35
+ ...Object.keys(OPEN_XML_REQUIRED_PARTS),
36
+ 'zip', 'zipx', 'jar', 'war', 'ear', 'apk', 'cbz',
37
+ ]);
38
+ const OLE_EXTENSIONS = new Set(['doc', 'dot', 'xls', 'xlt', 'ppt', 'msg']);
39
+ const TEXT_DECODER = new TextDecoder('utf-8');
40
+ const isArrayBuffer = (value) => value instanceof ArrayBuffer;
41
+ const isBlob = (value) => (typeof Blob !== 'undefined' && value instanceof Blob);
42
+ const toSource = (input, options) => {
43
+ if (isArrayBuffer(input)) {
44
+ return { buffer: input, filename: options.filename, type: options.type };
45
+ }
46
+ if (isBlob(input)) {
47
+ return { file: input, filename: options.filename, type: options.type };
48
+ }
49
+ return {
50
+ ...input,
51
+ filename: options.filename || input.filename,
52
+ type: options.type || input.type,
53
+ };
54
+ };
55
+ const startsWithBytes = (bytes, expected) => (expected.every((value, index) => bytes[index] === value));
56
+ const findEndOfCentralDirectory = (bytes) => {
57
+ for (let offset = bytes.byteLength - 22; offset >= 0; offset -= 1) {
58
+ if (bytes[offset] === 0x50 &&
59
+ bytes[offset + 1] === 0x4b &&
60
+ bytes[offset + 2] === 0x05 &&
61
+ bytes[offset + 3] === 0x06) {
62
+ return offset;
63
+ }
64
+ }
65
+ return -1;
66
+ };
67
+ const collectCentralDirectoryEntries = (bytes) => {
68
+ const entries = new Set();
69
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
70
+ let offset = 0;
71
+ while (offset + 46 <= bytes.byteLength) {
72
+ if (view.getUint32(offset, true) !== ZIP_CENTRAL_SIGNATURE) {
73
+ break;
74
+ }
75
+ const filenameLength = view.getUint16(offset + 28, true);
76
+ const extraLength = view.getUint16(offset + 30, true);
77
+ const commentLength = view.getUint16(offset + 32, true);
78
+ const end = offset + 46 + filenameLength;
79
+ if (end > bytes.byteLength) {
80
+ return { entries, complete: false };
81
+ }
82
+ entries.add(TEXT_DECODER.decode(bytes.subarray(offset + 46, end)).replace(/^\/+/, '').toLowerCase());
83
+ offset = end + extraLength + commentLength;
84
+ }
85
+ return { entries, complete: offset === bytes.byteLength };
86
+ };
87
+ const inspectZipBuffer = (buffer) => {
88
+ const bytes = new Uint8Array(buffer);
89
+ if (bytes.byteLength < 22) {
90
+ return { signatureValid: false, complete: true, entries: new Set() };
91
+ }
92
+ const view = new DataView(buffer);
93
+ const firstSignature = view.getUint32(0, true);
94
+ if (firstSignature !== ZIP_LOCAL_SIGNATURE && firstSignature !== ZIP_END_SIGNATURE) {
95
+ return { signatureValid: false, complete: true, entries: new Set() };
96
+ }
97
+ const endOffset = findEndOfCentralDirectory(bytes);
98
+ if (endOffset < 0) {
99
+ return { signatureValid: true, complete: false, entries: new Set() };
100
+ }
101
+ const centralSize = view.getUint32(endOffset + 12, true);
102
+ const centralOffset = view.getUint32(endOffset + 16, true);
103
+ if (centralSize === ZIP64_SENTINEL || centralOffset === ZIP64_SENTINEL) {
104
+ return { signatureValid: true, complete: false, entries: new Set() };
105
+ }
106
+ if (centralOffset + centralSize > bytes.byteLength) {
107
+ return { signatureValid: true, complete: false, entries: new Set() };
108
+ }
109
+ const result = collectCentralDirectoryEntries(bytes.subarray(centralOffset, centralOffset + centralSize));
110
+ return { signatureValid: true, complete: result.complete, entries: result.entries };
111
+ };
112
+ const inspectZipBlob = async (blob, maxPackageMetadataBytes) => {
113
+ if (blob.size < 22) {
114
+ return { signatureValid: false, complete: true, entries: new Set() };
115
+ }
116
+ const prefix = new Uint8Array(await blob.slice(0, 4).arrayBuffer());
117
+ const prefixView = new DataView(prefix.buffer, prefix.byteOffset, prefix.byteLength);
118
+ const firstSignature = prefixView.getUint32(0, true);
119
+ if (firstSignature !== ZIP_LOCAL_SIGNATURE && firstSignature !== ZIP_END_SIGNATURE) {
120
+ return { signatureValid: false, complete: true, entries: new Set() };
121
+ }
122
+ const tailStart = Math.max(0, blob.size - 65557);
123
+ const tail = new Uint8Array(await blob.slice(tailStart).arrayBuffer());
124
+ const endOffset = findEndOfCentralDirectory(tail);
125
+ if (endOffset < 0) {
126
+ return { signatureValid: true, complete: false, entries: new Set() };
127
+ }
128
+ const tailView = new DataView(tail.buffer, tail.byteOffset, tail.byteLength);
129
+ const centralSize = tailView.getUint32(endOffset + 12, true);
130
+ const centralOffset = tailView.getUint32(endOffset + 16, true);
131
+ if (centralSize === ZIP64_SENTINEL ||
132
+ centralOffset === ZIP64_SENTINEL ||
133
+ centralSize > maxPackageMetadataBytes ||
134
+ centralOffset + centralSize > blob.size) {
135
+ return { signatureValid: true, complete: false, entries: new Set() };
136
+ }
137
+ const central = new Uint8Array(await blob.slice(centralOffset, centralOffset + centralSize).arrayBuffer());
138
+ const result = collectCentralDirectoryEntries(central);
139
+ return { signatureValid: true, complete: result.complete, entries: result.entries };
140
+ };
141
+ const inspectZip = async (source, maxPackageMetadataBytes) => {
142
+ if (source.buffer) {
143
+ return inspectZipBuffer(source.buffer);
144
+ }
145
+ if (source.file) {
146
+ return inspectZipBlob(source.file, maxPackageMetadataBytes);
147
+ }
148
+ return null;
149
+ };
150
+ const readPrefix = async (source, byteLength = 1024) => {
151
+ if (source.buffer) {
152
+ return new Uint8Array(source.buffer, 0, Math.min(source.buffer.byteLength, byteLength));
153
+ }
154
+ if (source.file) {
155
+ return new Uint8Array(await source.file.slice(0, byteLength).arrayBuffer());
156
+ }
157
+ return null;
158
+ };
159
+ const inspectStructuredContent = async (source, extension, maxPackageMetadataBytes) => {
160
+ var _a, _b, _c;
161
+ const size = (_b = (_a = source.buffer) === null || _a === void 0 ? void 0 : _a.byteLength) !== null && _b !== void 0 ? _b : (_c = source.file) === null || _c === void 0 ? void 0 : _c.size;
162
+ if (size === 0 && (ZIP_EXTENSIONS.has(extension) || OLE_EXTENSIONS.has(extension) || extension === 'pdf')) {
163
+ return { inspected: true, valid: false, reason: 'empty-structured-file' };
164
+ }
165
+ if (ZIP_EXTENSIONS.has(extension)) {
166
+ const zip = await inspectZip(source, maxPackageMetadataBytes);
167
+ if (!zip) {
168
+ return { inspected: false, valid: null, reason: 'content-not-inspected' };
169
+ }
170
+ if (!zip.signatureValid) {
171
+ return { inspected: true, valid: false, reason: 'signature-mismatch' };
172
+ }
173
+ const required = OPEN_XML_REQUIRED_PARTS[extension] || [];
174
+ if (!zip.complete) {
175
+ return { inspected: true, valid: null, reason: 'content-inconclusive' };
176
+ }
177
+ const missingParts = required.filter(part => !zip.entries.has(part));
178
+ if (missingParts.length) {
179
+ return { inspected: true, valid: false, reason: 'missing-package-part', missingParts };
180
+ }
181
+ return { inspected: true, valid: true, reason: 'content-valid' };
182
+ }
183
+ const prefix = await readPrefix(source);
184
+ if (!prefix) {
185
+ return { inspected: false, valid: null, reason: 'content-not-inspected' };
186
+ }
187
+ if (OLE_EXTENSIONS.has(extension)) {
188
+ return startsWithBytes(prefix, OLE_SIGNATURE)
189
+ ? { inspected: true, valid: true, reason: 'content-valid' }
190
+ : { inspected: true, valid: false, reason: 'signature-mismatch' };
191
+ }
192
+ if (extension === 'pdf') {
193
+ const header = TEXT_DECODER.decode(prefix);
194
+ return /%PDF-\d\.\d/.test(header)
195
+ ? { inspected: true, valid: true, reason: 'content-valid' }
196
+ : { inspected: true, valid: false, reason: 'signature-mismatch' };
197
+ }
198
+ if (extension === 'rtf') {
199
+ const header = TEXT_DECODER.decode(prefix);
200
+ return /^\s*\{\\rtf\d/.test(header)
201
+ ? { inspected: true, valid: true, reason: 'content-valid' }
202
+ : { inspected: true, valid: false, reason: 'signature-mismatch' };
203
+ }
204
+ return { inspected: false, valid: null, reason: 'content-not-inspected' };
205
+ };
206
+ /**
207
+ * Performs a cheap, renderer-free capability and structure check.
208
+ *
209
+ * This intentionally does not promise rendering fidelity. A `ready` result
210
+ * means the extension is available and any known container/signature checks
211
+ * passed; the real renderer remains the final authority for malformed content.
212
+ */
213
+ export const precheckFileViewerSource = async (input, options = {}) => {
214
+ var _a;
215
+ const source = toSource(input, options);
216
+ const normalized = normalizeSource(source);
217
+ const extension = normalizeFileExtension(options.type || normalized.extension || getExtension(options.filename || normalized.filename));
218
+ const filename = normalizeFilename(options.filename || normalized.filename);
219
+ const supportedExtensions = new Set(Array.from(options.supportedExtensions || DEFAULT_SUPPORTED_EXTENSIONS, normalizeFileExtension));
220
+ const supported = supportedExtensions.has(extension);
221
+ const rendererId = ((_a = DEFAULT_RENDERER_DEFINITIONS.find(definition => definition.extensions.some(candidate => normalizeFileExtension(candidate) === extension))) === null || _a === void 0 ? void 0 : _a.id) || null;
222
+ if (!supported) {
223
+ return {
224
+ filename,
225
+ extension,
226
+ rendererId,
227
+ supported: false,
228
+ inspected: false,
229
+ valid: null,
230
+ previewable: false,
231
+ status: 'unsupported',
232
+ reason: 'unsupported-extension',
233
+ missingParts: [],
234
+ };
235
+ }
236
+ const inspection = await inspectStructuredContent(source, extension, Math.max(64 * 1024, options.maxPackageMetadataBytes || DEFAULT_MAX_PACKAGE_METADATA_BYTES));
237
+ const valid = inspection.valid;
238
+ return {
239
+ filename,
240
+ extension,
241
+ rendererId,
242
+ supported: true,
243
+ inspected: inspection.inspected,
244
+ valid,
245
+ previewable: valid !== false,
246
+ status: valid === false ? 'invalid' : 'ready',
247
+ reason: inspection.reason,
248
+ missingParts: inspection.missingParts || [],
249
+ };
250
+ };
@@ -636,13 +636,14 @@ export const createViewer = (container, createOptions = {}) => {
636
636
  });
637
637
  },
638
638
  async printWithMask(printOptions = {}) {
639
- var _a, _b, _c, _d;
639
+ var _a, _b, _c, _d, _e;
640
640
  const result = await openFileViewerPrintMaskDesignerAsync({
641
641
  root: container,
642
642
  pages: (_b = (_a = renderSurfaceState.exportAdapter) === null || _a === void 0 ? void 0 : _a.getPrintMaskPages) === null || _b === void 0 ? void 0 : _b.call(_a),
643
643
  i18n: options,
644
644
  color: (_c = printOptions.mask) === null || _c === void 0 ? void 0 : _c.color,
645
645
  initialRegions: (_d = printOptions.mask) === null || _d === void 0 ? void 0 : _d.regions,
646
+ initialStamps: (_e = printOptions.mask) === null || _e === void 0 ? void 0 : _e.stamps,
646
647
  });
647
648
  if (!(result === null || result === void 0 ? void 0 : result.mask)) {
648
649
  return;
@@ -262,7 +262,7 @@ export const createFileViewerOperationActionHandlers = ({ getBuffer, getFile, ge
262
262
  }
263
263
  },
264
264
  async printWithMask(options = {}) {
265
- var _a, _b, _c, _d, _e, _f, _g;
265
+ var _a, _b, _c, _d, _e, _f, _g, _h;
266
266
  try {
267
267
  const source = getRenderedSource();
268
268
  if (!source) {
@@ -276,6 +276,7 @@ export const createFileViewerOperationActionHandlers = ({ getBuffer, getFile, ge
276
276
  i18n: resolveI18n(),
277
277
  color: (_c = options.mask) === null || _c === void 0 ? void 0 : _c.color,
278
278
  initialRegions: (_d = options.mask) === null || _d === void 0 ? void 0 : _d.regions,
279
+ initialStamps: (_e = options.mask) === null || _e === void 0 ? void 0 : _e.stamps,
279
280
  });
280
281
  if (!(result === null || result === void 0 ? void 0 : result.mask)) {
281
282
  return undefined;
@@ -283,9 +284,9 @@ export const createFileViewerOperationActionHandlers = ({ getBuffer, getFile, ge
283
284
  return await executeFileViewerPrintOperation({
284
285
  ...getRenderedOperationInput(),
285
286
  ...options,
286
- watermarkInlineStyle: (_f = (_e = options.watermarkInlineStyle) !== null && _e !== void 0 ? _e : getWatermarkInlineStyle === null || getWatermarkInlineStyle === void 0 ? void 0 : getWatermarkInlineStyle()) !== null && _f !== void 0 ? _f : undefined,
287
+ watermarkInlineStyle: (_g = (_f = options.watermarkInlineStyle) !== null && _f !== void 0 ? _f : getWatermarkInlineStyle === null || getWatermarkInlineStyle === void 0 ? void 0 : getWatermarkInlineStyle()) !== null && _g !== void 0 ? _g : undefined,
287
288
  mask: result.mask,
288
- printAvailable: (_g = getPrintAvailable === null || getPrintAvailable === void 0 ? void 0 : getPrintAvailable()) !== null && _g !== void 0 ? _g : true,
289
+ printAvailable: (_h = getPrintAvailable === null || getPrintAvailable === void 0 ? void 0 : getPrintAvailable()) !== null && _h !== void 0 ? _h : true,
289
290
  });
290
291
  }
291
292
  catch (error) {
@@ -19,9 +19,19 @@ export const DEFAULT_FILE_VIEWER_STATE_THEME = Object.freeze({
19
19
  hint: '正在整理内容结构并生成预览。',
20
20
  });
21
21
  export const DEFAULT_FILE_VIEWER_UNSUPPORTED_DESCRIPTION = '支持 Office、PDF、OFD、Typst、压缩包、邮件、OLB/DRA、CAD、地理数据、3D 模型、Excalidraw、draw.io、EPUB、UMD、Markdown、代码/文本、图片、音视频、字体和数据资产的在线预览';
22
- const extensionLabel = (extension) => {
22
+ const extensionLabel = (extension, i18n) => {
23
23
  const normalized = normalizeFileExtension(extension);
24
- return normalized ? `.${normalized}` : '当前';
24
+ if (normalized) {
25
+ return `.${normalized}`;
26
+ }
27
+ const locale = resolveFileViewerLocale(i18n);
28
+ if (locale === 'zh-CN') {
29
+ return '当前文件';
30
+ }
31
+ if (locale === 'ja-JP') {
32
+ return '現在のファイル';
33
+ }
34
+ return 'current file';
25
35
  };
26
36
  const rendererPackageById = {
27
37
  'office-word-openxml': '@file-viewer/renderer-word',
@@ -159,14 +169,16 @@ export const createFileViewerEmptyState = (extension = '', theme = DEFAULT_FILE_
159
169
  recoverable: true,
160
170
  });
161
171
  export const createFileViewerUnsupportedState = (extension = '', theme = DEFAULT_FILE_VIEWER_STATE_THEME, i18n) => {
162
- const label = extensionLabel(extension);
172
+ const label = extensionLabel(extension, i18n);
163
173
  const installHint = resolveFileViewerRendererInstallHint(extension);
164
174
  if (installHint) {
165
175
  const locale = resolveFileViewerLocale(i18n);
166
176
  const localeRendererTip = installHint.rendererPackage
167
177
  ? locale === 'zh-CN'
168
178
  ? `;如果需要极致裁剪,也可以只安装 ${installHint.rendererPackage}`
169
- : `; for a strict custom cut, install only ${installHint.rendererPackage}`
179
+ : locale === 'ja-JP'
180
+ ? `;厳密に最小構成へ絞る場合は ${installHint.rendererPackage} のみをインストールすることもできます`
181
+ : `; for a strict custom cut, install only ${installHint.rendererPackage}`
170
182
  : '';
171
183
  return createFileViewerStateDescriptor({
172
184
  state: 'unsupported',
@@ -258,9 +270,9 @@ export const formatFileViewerErrorMessage = (prefix, error, i18n) => {
258
270
  if (!detail) {
259
271
  return prefix;
260
272
  }
261
- return resolveFileViewerLocale(i18n) === 'zh-CN'
262
- ? `${prefix}:${detail}`
263
- : `${prefix}: ${detail}`;
273
+ return resolveFileViewerLocale(i18n) === 'en-US'
274
+ ? `${prefix}: ${detail}`
275
+ : `${prefix}:${detail}`;
264
276
  };
265
277
  export const createFileViewerErrorState = (extension = '', error = undefined, theme = DEFAULT_FILE_VIEWER_STATE_THEME, i18n) => createFileViewerStateDescriptor({
266
278
  state: 'error',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@file-viewer/core",
3
- "version": "2.2.3",
3
+ "version": "2.2.5",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Framework-neutral modular TypeScript core for File Viewer renderer orchestration, lifecycle APIs, search, zoom, print, and export",