@file-viewer/renderer-iwork 0.0.2 → 2.3.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/LICENSE +5 -158
- package/README.en.md +2 -15
- package/README.md +2 -15
- package/THIRD_PARTY_NOTICES.md +6 -0
- package/dist/errors.d.ts +4 -0
- package/dist/errors.js +7 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +17 -72
- package/dist/iwork.d.ts +2 -0
- package/dist/iwork.js +368 -0
- package/dist/iwork.parser.d.ts +1 -0
- package/dist/iwork.parser.js +100 -0
- package/dist/iwork.worker.d.ts +1 -0
- package/dist/iwork.worker.js +100 -0
- package/dist/limits.d.ts +2 -0
- package/dist/limits.js +7 -0
- package/dist/model.d.ts +122 -0
- package/dist/model.js +1 -0
- package/dist/parser.d.ts +17 -2
- package/dist/parser.js +1387 -1
- package/dist/snappy.d.ts +2 -0
- package/dist/snappy.js +109 -0
- package/dist/workerClient.d.ts +3 -0
- package/dist/workerClient.js +60 -0
- package/package.json +37 -22
package/dist/snappy.d.ts
ADDED
package/dist/snappy.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const concat = (chunks) => {
|
|
2
|
+
const length = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
3
|
+
const output = new Uint8Array(length);
|
|
4
|
+
let offset = 0;
|
|
5
|
+
chunks.forEach(chunk => { output.set(chunk, offset); offset += chunk.length; });
|
|
6
|
+
return output;
|
|
7
|
+
};
|
|
8
|
+
const readVarint = (bytes, pointer) => {
|
|
9
|
+
let value = 0;
|
|
10
|
+
let shift = 0;
|
|
11
|
+
for (let count = 0; count < 10; count += 1) {
|
|
12
|
+
if (pointer.offset >= bytes.length)
|
|
13
|
+
throw new Error('Malformed Snappy varint.');
|
|
14
|
+
const byte = bytes[pointer.offset++];
|
|
15
|
+
value += (byte & 0x7f) * 2 ** shift;
|
|
16
|
+
if (!(byte & 0x80))
|
|
17
|
+
return value;
|
|
18
|
+
shift += 7;
|
|
19
|
+
}
|
|
20
|
+
throw new Error('Snappy varint exceeds 10 bytes.');
|
|
21
|
+
};
|
|
22
|
+
const copyFromHistory = (output, offset, length, maximum) => {
|
|
23
|
+
if (!offset || offset > output.length)
|
|
24
|
+
throw new Error('Invalid Snappy copy offset.');
|
|
25
|
+
if (output.length + length > maximum)
|
|
26
|
+
throw new Error('Snappy output exceeds the configured safety limit.');
|
|
27
|
+
for (let index = 0; index < length; index += 1)
|
|
28
|
+
output.push(output[output.length - offset]);
|
|
29
|
+
};
|
|
30
|
+
const decompressChunk = (type, bytes, maximum) => {
|
|
31
|
+
if (type !== 0)
|
|
32
|
+
throw new Error(`Unexpected iWork Snappy chunk type ${type}.`);
|
|
33
|
+
const pointer = { offset: 0 };
|
|
34
|
+
const expected = readVarint(bytes, pointer);
|
|
35
|
+
if (expected > maximum)
|
|
36
|
+
throw new Error('Snappy frame declares an unsafe output length.');
|
|
37
|
+
const output = [];
|
|
38
|
+
while (pointer.offset < bytes.length) {
|
|
39
|
+
const tagByte = bytes[pointer.offset++];
|
|
40
|
+
const tag = tagByte & 3;
|
|
41
|
+
if (tag === 0) {
|
|
42
|
+
let length = tagByte >>> 2;
|
|
43
|
+
if (length < 60)
|
|
44
|
+
length += 1;
|
|
45
|
+
else {
|
|
46
|
+
const byteCount = length - 59;
|
|
47
|
+
if (byteCount > 4 || pointer.offset + byteCount > bytes.length)
|
|
48
|
+
throw new Error('Malformed Snappy literal length.');
|
|
49
|
+
length = 0;
|
|
50
|
+
for (let index = 0; index < byteCount; index += 1)
|
|
51
|
+
length |= bytes[pointer.offset++] << (8 * index);
|
|
52
|
+
length = (length >>> 0) + 1;
|
|
53
|
+
}
|
|
54
|
+
if (pointer.offset + length > bytes.length || output.length + length > maximum)
|
|
55
|
+
throw new Error('Snappy literal exceeds its frame or safety limit.');
|
|
56
|
+
for (let index = 0; index < length; index += 1)
|
|
57
|
+
output.push(bytes[pointer.offset++]);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
let length;
|
|
61
|
+
let offset;
|
|
62
|
+
if (tag === 1) {
|
|
63
|
+
length = ((tagByte >>> 2) & 7) + 4;
|
|
64
|
+
if (pointer.offset >= bytes.length)
|
|
65
|
+
throw new Error('Malformed Snappy copy-1.');
|
|
66
|
+
offset = ((tagByte & 0xe0) << 3) | bytes[pointer.offset++];
|
|
67
|
+
}
|
|
68
|
+
else if (tag === 2) {
|
|
69
|
+
length = (tagByte >>> 2) + 1;
|
|
70
|
+
if (pointer.offset + 2 > bytes.length)
|
|
71
|
+
throw new Error('Malformed Snappy copy-2.');
|
|
72
|
+
offset = bytes[pointer.offset] | (bytes[pointer.offset + 1] << 8);
|
|
73
|
+
pointer.offset += 2;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
length = (tagByte >>> 2) + 1;
|
|
77
|
+
if (pointer.offset + 4 > bytes.length)
|
|
78
|
+
throw new Error('Malformed Snappy copy-4.');
|
|
79
|
+
offset = (bytes[pointer.offset] | (bytes[pointer.offset + 1] << 8) | (bytes[pointer.offset + 2] << 16) | (bytes[pointer.offset + 3] << 24)) >>> 0;
|
|
80
|
+
pointer.offset += 4;
|
|
81
|
+
}
|
|
82
|
+
copyFromHistory(output, offset, length, maximum);
|
|
83
|
+
}
|
|
84
|
+
if (output.length !== expected)
|
|
85
|
+
throw new Error(`Unexpected Snappy output length ${output.length}; expected ${expected}.`);
|
|
86
|
+
return Uint8Array.from(output);
|
|
87
|
+
};
|
|
88
|
+
export const decompressIwaFrames = (bytes, maximum) => {
|
|
89
|
+
const output = [];
|
|
90
|
+
let offset = 0;
|
|
91
|
+
let total = 0;
|
|
92
|
+
while (offset < bytes.length) {
|
|
93
|
+
if (offset + 4 > bytes.length)
|
|
94
|
+
throw new Error('Malformed iWork Snappy frame header.');
|
|
95
|
+
const type = bytes[offset++];
|
|
96
|
+
const length = bytes[offset] | (bytes[offset + 1] << 8) | (bytes[offset + 2] << 16);
|
|
97
|
+
offset += 3;
|
|
98
|
+
if (offset + length > bytes.length)
|
|
99
|
+
throw new Error('Malformed iWork Snappy frame length.');
|
|
100
|
+
const chunk = decompressChunk(type, bytes.slice(offset, offset + length), maximum - total);
|
|
101
|
+
output.push(chunk);
|
|
102
|
+
total += chunk.length;
|
|
103
|
+
if (total > maximum)
|
|
104
|
+
throw new Error('IWA decompression exceeds the configured safety limit.');
|
|
105
|
+
offset += length;
|
|
106
|
+
}
|
|
107
|
+
return output;
|
|
108
|
+
};
|
|
109
|
+
export const decompressIwaFile = (bytes, maximum) => concat(decompressIwaFrames(bytes, maximum));
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type FileViewerIworkOptions } from '@file-viewer/core';
|
|
2
|
+
import type { IworkDocument } from './model.js';
|
|
3
|
+
export declare const parseIworkWithWorker: (buffer: ArrayBuffer, type: string | undefined, options?: FileViewerIworkOptions, signal?: AbortSignal) => Promise<IworkDocument>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { resolveFileViewerIworkWorkerUrl } from '@file-viewer/core';
|
|
2
|
+
import { IworkContainerMismatchError } from './errors.js';
|
|
3
|
+
let requestId = 0;
|
|
4
|
+
const parseLimits = (options) => ({
|
|
5
|
+
maxUncompressedBytes: options === null || options === void 0 ? void 0 : options.maxUncompressedBytes,
|
|
6
|
+
maxCompressionRatio: options === null || options === void 0 ? void 0 : options.maxCompressionRatio,
|
|
7
|
+
maxObjects: options === null || options === void 0 ? void 0 : options.maxObjects,
|
|
8
|
+
maxImagePixels: options === null || options === void 0 ? void 0 : options.maxImagePixels,
|
|
9
|
+
maxNestingDepth: options === null || options === void 0 ? void 0 : options.maxNestingDepth,
|
|
10
|
+
});
|
|
11
|
+
export const parseIworkWithWorker = async (buffer, type, options, signal) => {
|
|
12
|
+
var _a;
|
|
13
|
+
const limits = Object.fromEntries(Object.entries(parseLimits(options)).filter(([, value]) => value != null));
|
|
14
|
+
if ((options === null || options === void 0 ? void 0 : options.useWorker) === false || typeof Worker === 'undefined') {
|
|
15
|
+
const { parseIworkDocument } = await import('./iwork.parser.js');
|
|
16
|
+
return parseIworkDocument(buffer, type, limits);
|
|
17
|
+
}
|
|
18
|
+
const workerUrl = resolveFileViewerIworkWorkerUrl(options, typeof document === 'undefined' ? undefined : document.baseURI);
|
|
19
|
+
const worker = new Worker(workerUrl, { type: 'module', name: 'file-viewer-iwork' });
|
|
20
|
+
const id = ++requestId;
|
|
21
|
+
const timeoutMs = Math.max(1000, (_a = options === null || options === void 0 ? void 0 : options.workerTimeoutMs) !== null && _a !== void 0 ? _a : 60000);
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
let settled = false;
|
|
24
|
+
const finish = (callback) => {
|
|
25
|
+
if (settled)
|
|
26
|
+
return;
|
|
27
|
+
settled = true;
|
|
28
|
+
clearTimeout(timer);
|
|
29
|
+
signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', onAbort);
|
|
30
|
+
worker.terminate();
|
|
31
|
+
callback();
|
|
32
|
+
};
|
|
33
|
+
const onAbort = () => finish(() => reject(new DOMException('iWork parsing was aborted.', 'AbortError')));
|
|
34
|
+
const timer = setTimeout(() => finish(() => reject(new Error(`iWork parsing exceeded ${timeoutMs}ms.`))), timeoutMs);
|
|
35
|
+
worker.addEventListener('message', (event) => {
|
|
36
|
+
if (event.data.id !== id)
|
|
37
|
+
return;
|
|
38
|
+
if (event.data.ok && event.data.document) {
|
|
39
|
+
finish(() => resolve(event.data.document));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const details = event.data.error;
|
|
43
|
+
finish(() => {
|
|
44
|
+
if ((details === null || details === void 0 ? void 0 : details.name) === 'IworkContainerMismatchError' && details.actualRendererId) {
|
|
45
|
+
reject(new IworkContainerMismatchError(details.actualRendererId, details.message || 'iWork container mismatch.'));
|
|
46
|
+
}
|
|
47
|
+
else
|
|
48
|
+
reject(new Error((details === null || details === void 0 ? void 0 : details.message) || 'iWork Worker failed.'));
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
worker.addEventListener('error', event => finish(() => reject(new Error(event.message || 'iWork Worker failed to load.'))));
|
|
52
|
+
signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', onAbort, { once: true });
|
|
53
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
54
|
+
onAbort();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const transferable = buffer.slice(0);
|
|
58
|
+
worker.postMessage({ id, buffer: transferable, type, limits }, [transferable]);
|
|
59
|
+
});
|
|
60
|
+
};
|
package/package.json
CHANGED
|
@@ -1,34 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@file-viewer/renderer-iwork",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
|
-
"description": "
|
|
6
|
+
"description": "Offline Apple Pages, Numbers and Keynote renderer for modern IWA and iWork '09 containers.",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"file-viewer",
|
|
9
9
|
"renderer",
|
|
10
|
-
"iwork",
|
|
11
10
|
"pages",
|
|
12
11
|
"numbers",
|
|
13
|
-
"keynote"
|
|
12
|
+
"keynote",
|
|
13
|
+
"iwork",
|
|
14
|
+
"iwa",
|
|
15
|
+
"snappy",
|
|
16
|
+
"offline",
|
|
17
|
+
"self-hosted"
|
|
14
18
|
],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public",
|
|
21
|
+
"registry": "https://registry.npmjs.org/"
|
|
22
|
+
},
|
|
15
23
|
"author": {
|
|
16
24
|
"name": "Yu Wang",
|
|
17
25
|
"email": "admin@flyfish.dev"
|
|
18
26
|
},
|
|
19
|
-
"license": "Apache-2.0",
|
|
20
27
|
"repository": {
|
|
21
28
|
"type": "git",
|
|
22
|
-
"url": "git+https://github.com/flyfish-dev/
|
|
23
|
-
"directory": "packages/
|
|
29
|
+
"url": "git+https://github.com/flyfish-dev/file-viewer.git",
|
|
30
|
+
"directory": "packages/renderers/iwork"
|
|
24
31
|
},
|
|
25
|
-
"homepage": "https://
|
|
32
|
+
"homepage": "https://doc.file-viewer.app/guide/on-demand-renderers",
|
|
26
33
|
"bugs": {
|
|
27
|
-
"url": "https://github.com/flyfish-dev/
|
|
34
|
+
"url": "https://github.com/flyfish-dev/file-viewer/issues"
|
|
28
35
|
},
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
36
|
+
"funding": {
|
|
37
|
+
"type": "individual",
|
|
38
|
+
"url": "https://dev.flyfish.group/donate?source=npm"
|
|
32
39
|
},
|
|
33
40
|
"main": "./dist/index.js",
|
|
34
41
|
"module": "./dist/index.js",
|
|
@@ -40,29 +47,37 @@
|
|
|
40
47
|
"default": "./dist/index.js"
|
|
41
48
|
},
|
|
42
49
|
"./parser": {
|
|
43
|
-
"types": "./dist/parser.d.ts",
|
|
44
|
-
"import": "./dist/parser.js",
|
|
45
|
-
"default": "./dist/parser.js"
|
|
50
|
+
"types": "./dist/iwork.parser.d.ts",
|
|
51
|
+
"import": "./dist/iwork.parser.js",
|
|
52
|
+
"default": "./dist/iwork.parser.js"
|
|
46
53
|
},
|
|
54
|
+
"./worker/iwork.worker.js": "./dist/iwork.worker.js",
|
|
47
55
|
"./package.json": "./package.json"
|
|
48
56
|
},
|
|
49
57
|
"files": [
|
|
50
58
|
"dist",
|
|
51
59
|
"README.md",
|
|
52
60
|
"README.en.md",
|
|
53
|
-
"LICENSE"
|
|
61
|
+
"LICENSE",
|
|
62
|
+
"THIRD_PARTY_NOTICES.md"
|
|
54
63
|
],
|
|
55
64
|
"dependencies": {
|
|
56
|
-
"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
"
|
|
65
|
+
"@file-viewer/core": "2.3.0",
|
|
66
|
+
"@xmldom/xmldom": "^0.9.10",
|
|
67
|
+
"jszip": "^3.10.1",
|
|
68
|
+
"keynote-archives": "2.0.1",
|
|
69
|
+
"pako": "^2.1.0",
|
|
70
|
+
"styled-exceljs": "0.21.1",
|
|
71
|
+
"tslib": "^2.8.1"
|
|
60
72
|
},
|
|
61
73
|
"devDependencies": {
|
|
74
|
+
"@types/pako": "^2.0.4",
|
|
75
|
+
"esbuild": "^0.28.1",
|
|
62
76
|
"typescript": "^6.0.3"
|
|
63
77
|
},
|
|
78
|
+
"license": "Apache-2.0",
|
|
64
79
|
"scripts": {
|
|
65
|
-
"build": "
|
|
66
|
-
"type-check": "tsc -b tsconfig.json
|
|
80
|
+
"build": "tsc -b tsconfig.json --force && node scripts/build-worker.mjs",
|
|
81
|
+
"type-check": "tsc -b tsconfig.json"
|
|
67
82
|
}
|
|
68
83
|
}
|