@file-viewer/core 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.
- package/README.en.md +17 -0
- package/README.md +17 -0
- package/dist/contracts/types.d.ts +60 -3
- package/dist/features/printMask.d.ts +3 -2
- package/dist/features/printMask.js +74 -12
- package/dist/features/printMaskDesigner.d.ts +2 -1
- package/dist/features/printMaskDesigner.js +186 -13
- package/dist/headless.d.ts +3 -1
- package/dist/headless.js +1 -0
- package/dist/i18n/messages.ja.js +15 -3
- package/dist/i18n/messages.js +30 -6
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -1
- package/dist/output/export.js +2 -1
- package/dist/renderers/image.js +48 -12
- package/dist/source/precheck.d.ts +36 -0
- package/dist/source/precheck.js +250 -0
- package/dist/viewer/createViewer.js +2 -1
- package/dist/viewer/operations.js +4 -3
- package/package.json +1 -1
package/dist/renderers/image.js
CHANGED
|
@@ -22,13 +22,18 @@ const imageStyle = `
|
|
|
22
22
|
.image-viewer{position:relative;width:100%;height:100%;overflow:auto;background:var(--file-viewer-render-surface-background,#eef1f4);box-sizing:border-box}
|
|
23
23
|
.image-stage{min-width:100%;min-height:100%;display:flex;align-items:center;justify-content:center;padding:24px;box-sizing:border-box}
|
|
24
24
|
.image-stage img{display:block;width:auto;max-width:none;margin:0 auto;border:0;box-shadow:0 18px 48px rgba(15,23,42,.16);background:#fff;cursor:zoom-in}
|
|
25
|
-
.image-
|
|
26
|
-
.image-lightbox
|
|
27
|
-
.image-lightbox
|
|
28
|
-
.image-lightbox
|
|
25
|
+
.image-stage img:focus-visible{outline:3px solid #2563eb;outline-offset:4px}
|
|
26
|
+
.image-lightbox{position:absolute;inset:0;z-index:40;display:flex;align-items:center;justify-content:center;padding:40px;background:rgba(15,23,42,.9);box-sizing:border-box;opacity:0;visibility:hidden;pointer-events:none;transition:opacity .18s ease,visibility 0s linear .18s}
|
|
27
|
+
.image-lightbox[data-open='true']{opacity:1;visibility:visible;pointer-events:auto;transition-delay:0s}
|
|
28
|
+
.image-lightbox img{display:block;max-width:100%;max-height:100%;object-fit:contain;background:#fff;box-shadow:0 30px 80px rgba(0,0,0,.4);cursor:default;transform:scale(.985);transition:transform .18s ease}
|
|
29
|
+
.image-lightbox[data-open='true'] img{transform:scale(1)}
|
|
30
|
+
.image-lightbox button{position:absolute;top:16px;right:16px;display:grid;width:40px;height:40px;place-items:center;padding:0;border:1px solid rgba(255,255,255,.7);border-radius:999px;background:rgba(255,255,255,.96);color:#172033;font:400 27px/1 Arial,sans-serif;cursor:pointer;box-shadow:0 12px 28px rgba(0,0,0,.24);transition:background-color .14s ease,transform .14s ease}
|
|
31
|
+
.image-lightbox button:hover{background:#fff;transform:scale(1.04)}
|
|
32
|
+
.image-lightbox button:focus-visible{outline:3px solid #60a5fa;outline-offset:2px}
|
|
29
33
|
[data-viewer-theme='dark'] .image-viewer{background:var(--file-viewer-render-surface-background,#101820)}
|
|
30
34
|
@media (prefers-color-scheme:dark){[data-viewer-theme='system'] .image-viewer{background:var(--file-viewer-render-surface-background,#101820)}}
|
|
31
35
|
@media (max-width:767px){.image-stage{padding:12px}.image-lightbox{padding:16px}.image-lightbox button{top:12px;right:12px}}
|
|
36
|
+
@media (prefers-reduced-motion:reduce){.image-lightbox,.image-lightbox img,.image-lightbox button{transition:none}}
|
|
32
37
|
`;
|
|
33
38
|
const createStyle = (documentRef) => {
|
|
34
39
|
const style = documentRef.createElement('style');
|
|
@@ -68,35 +73,55 @@ const roundImageScale = (value) => {
|
|
|
68
73
|
const createLightbox = (documentRef, src, t) => {
|
|
69
74
|
const lightbox = documentRef.createElement('div');
|
|
70
75
|
lightbox.className = 'image-lightbox';
|
|
71
|
-
lightbox.
|
|
76
|
+
lightbox.dataset.open = 'false';
|
|
72
77
|
lightbox.setAttribute('role', 'dialog');
|
|
73
78
|
lightbox.setAttribute('aria-modal', 'true');
|
|
79
|
+
lightbox.setAttribute('aria-hidden', 'true');
|
|
74
80
|
const image = documentRef.createElement('img');
|
|
75
81
|
image.alt = t('image.lightbox.alt');
|
|
76
82
|
image.src = src;
|
|
77
83
|
const closeButton = documentRef.createElement('button');
|
|
78
84
|
closeButton.type = 'button';
|
|
79
85
|
closeButton.setAttribute('aria-label', t('image.lightbox.close'));
|
|
80
|
-
closeButton.textContent = '
|
|
86
|
+
closeButton.textContent = '×';
|
|
87
|
+
let previousFocus = null;
|
|
81
88
|
const close = () => {
|
|
82
|
-
lightbox.
|
|
89
|
+
if (lightbox.dataset.open !== 'true')
|
|
90
|
+
return;
|
|
91
|
+
lightbox.dataset.open = 'false';
|
|
92
|
+
lightbox.setAttribute('aria-hidden', 'true');
|
|
93
|
+
if (previousFocus === null || previousFocus === void 0 ? void 0 : previousFocus.isConnected) {
|
|
94
|
+
previousFocus.focus({ preventScroll: true });
|
|
95
|
+
}
|
|
96
|
+
previousFocus = null;
|
|
97
|
+
};
|
|
98
|
+
const onKeyDown = (event) => {
|
|
99
|
+
if (event.key === 'Escape' && lightbox.dataset.open === 'true') {
|
|
100
|
+
event.preventDefault();
|
|
101
|
+
close();
|
|
102
|
+
}
|
|
83
103
|
};
|
|
84
104
|
closeButton.addEventListener('click', close);
|
|
85
|
-
image.addEventListener('click', close);
|
|
86
105
|
lightbox.addEventListener('click', event => {
|
|
87
106
|
if (event.target === lightbox) {
|
|
88
107
|
close();
|
|
89
108
|
}
|
|
90
109
|
});
|
|
110
|
+
documentRef.addEventListener('keydown', onKeyDown);
|
|
91
111
|
lightbox.append(image, closeButton);
|
|
92
112
|
return {
|
|
93
113
|
element: lightbox,
|
|
94
|
-
open() {
|
|
95
|
-
|
|
114
|
+
open(invoker) {
|
|
115
|
+
previousFocus =
|
|
116
|
+
invoker ||
|
|
117
|
+
(documentRef.activeElement instanceof HTMLElement ? documentRef.activeElement : null);
|
|
118
|
+
lightbox.dataset.open = 'true';
|
|
119
|
+
lightbox.setAttribute('aria-hidden', 'false');
|
|
120
|
+
closeButton.focus({ preventScroll: true });
|
|
96
121
|
},
|
|
97
122
|
destroy() {
|
|
98
123
|
closeButton.removeEventListener('click', close);
|
|
99
|
-
|
|
124
|
+
documentRef.removeEventListener('keydown', onKeyDown);
|
|
100
125
|
lightbox.remove();
|
|
101
126
|
},
|
|
102
127
|
};
|
|
@@ -122,11 +147,21 @@ export default async function renderImage(buffer, target, type, context) {
|
|
|
122
147
|
const image = documentRef.createElement('img');
|
|
123
148
|
image.alt = t('image.alt');
|
|
124
149
|
image.src = src;
|
|
150
|
+
image.tabIndex = 0;
|
|
151
|
+
image.setAttribute('role', 'button');
|
|
152
|
+
image.setAttribute('aria-haspopup', 'dialog');
|
|
125
153
|
stage.append(image);
|
|
126
154
|
root.append(stage);
|
|
127
155
|
const lightbox = createLightbox(documentRef, src, t);
|
|
128
|
-
const openLightbox = () => lightbox.open();
|
|
156
|
+
const openLightbox = () => lightbox.open(image);
|
|
157
|
+
const openLightboxFromKeyboard = (event) => {
|
|
158
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
159
|
+
event.preventDefault();
|
|
160
|
+
lightbox.open(image);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
129
163
|
image.addEventListener('click', openLightbox);
|
|
164
|
+
image.addEventListener('keydown', openLightboxFromKeyboard);
|
|
130
165
|
const getMinScale = () => Math.min(0.1, fitScale || 0.1);
|
|
131
166
|
const clampScale = (value) => {
|
|
132
167
|
const minScale = getMinScale();
|
|
@@ -250,6 +285,7 @@ export default async function renderImage(buffer, target, type, context) {
|
|
|
250
285
|
resizeObserver.disconnect();
|
|
251
286
|
image.removeEventListener('load', updateViewportSize);
|
|
252
287
|
image.removeEventListener('click', openLightbox);
|
|
288
|
+
image.removeEventListener('keydown', openLightboxFromKeyboard);
|
|
253
289
|
lightbox.destroy();
|
|
254
290
|
target.replaceChildren();
|
|
255
291
|
},
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { FileViewerFileRef, FileViewerSource } from '../contracts/types';
|
|
2
|
+
export type FileViewerPrecheckStatus = 'ready' | 'invalid' | 'unsupported';
|
|
3
|
+
export type FileViewerPrecheckReason = 'content-valid' | 'content-not-inspected' | 'content-inconclusive' | 'empty-structured-file' | 'signature-mismatch' | 'invalid-package' | 'missing-package-part' | 'unsupported-extension';
|
|
4
|
+
export interface FileViewerPrecheckOptions {
|
|
5
|
+
/** Filename used when a Blob or ArrayBuffer has no name. */
|
|
6
|
+
filename?: string;
|
|
7
|
+
/** Explicit extension, with or without a leading dot. */
|
|
8
|
+
type?: string;
|
|
9
|
+
/** Restricts the result to the renderers installed by the host application. */
|
|
10
|
+
supportedExtensions?: Iterable<string>;
|
|
11
|
+
/** Maximum ZIP central-directory bytes read from a Blob. Defaults to 8 MiB. */
|
|
12
|
+
maxPackageMetadataBytes?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface FileViewerPrecheckResult {
|
|
15
|
+
filename: string;
|
|
16
|
+
extension: string;
|
|
17
|
+
rendererId: string | null;
|
|
18
|
+
supported: boolean;
|
|
19
|
+
inspected: boolean;
|
|
20
|
+
/** `null` means the extension is supported but no structural validator exists. */
|
|
21
|
+
valid: boolean | null;
|
|
22
|
+
previewable: boolean;
|
|
23
|
+
status: FileViewerPrecheckStatus;
|
|
24
|
+
reason: FileViewerPrecheckReason;
|
|
25
|
+
missingParts: string[];
|
|
26
|
+
}
|
|
27
|
+
type FileViewerPrecheckInput = FileViewerSource | FileViewerFileRef;
|
|
28
|
+
/**
|
|
29
|
+
* Performs a cheap, renderer-free capability and structure check.
|
|
30
|
+
*
|
|
31
|
+
* This intentionally does not promise rendering fidelity. A `ready` result
|
|
32
|
+
* means the extension is available and any known container/signature checks
|
|
33
|
+
* passed; the real renderer remains the final authority for malformed content.
|
|
34
|
+
*/
|
|
35
|
+
export declare const precheckFileViewerSource: (input: FileViewerPrecheckInput, options?: FileViewerPrecheckOptions) => Promise<FileViewerPrecheckResult>;
|
|
36
|
+
export {};
|
|
@@ -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: (
|
|
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: (
|
|
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) {
|
package/package.json
CHANGED