@l1yp/file-viewer 0.1.4 → 0.1.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.md +57 -6
- package/dist/assets/step.worker-CfDTYOf_.js.map +1 -0
- package/dist/components/AnsiText.d.ts +47 -0
- package/dist/components/AnsiText.js +41 -0
- package/dist/components/AnsiText.js.map +1 -0
- package/dist/components/CertViewer.js +1 -1
- package/dist/components/CertViewer.js.map +1 -1
- package/dist/components/CertViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/CodeView.js +1 -1
- package/dist/components/CodeView.js.map +1 -1
- package/dist/components/CodeView.vue_vue_type_script_setup_true_lang.js +80 -71
- package/dist/components/CodeView.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/DocxEmbeddedFiles.js +9 -0
- package/dist/components/DocxEmbeddedFiles.js.map +1 -0
- package/dist/components/DocxEmbeddedFiles.vue.d.ts +15 -0
- package/dist/components/DocxEmbeddedFiles.vue_vue_type_script_setup_true_lang.js +69 -0
- package/dist/components/DocxEmbeddedFiles.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/DocxNavigationPane.js +1 -1
- package/dist/components/DocxNavigationPane.js.map +1 -1
- package/dist/components/DocxNavigationPane.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/DocxViewer.js +1 -1
- package/dist/components/DocxViewer.js.map +1 -1
- package/dist/components/DocxViewer.vue.d.ts +1 -1
- package/dist/components/DocxViewer.vue_vue_type_script_setup_true_lang.js +182 -50
- package/dist/components/DocxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/EplViewer.js +1 -1
- package/dist/components/EplViewer.js.map +1 -1
- package/dist/components/EplViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/FileViewerBody.js +1 -1
- package/dist/components/FileViewerBody.js.map +1 -1
- package/dist/components/FileViewerBody.vue.d.ts +1 -0
- package/dist/components/FileViewerBody.vue_vue_type_script_setup_true_lang.js +292 -281
- package/dist/components/FileViewerBody.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/FileViewerPanel.js +1 -1
- package/dist/components/FileViewerPanel.js.map +1 -1
- package/dist/components/FileViewerPanel.vue.d.ts +6 -1
- package/dist/components/FileViewerPanel.vue_vue_type_script_setup_true_lang.js +53 -51
- package/dist/components/FileViewerPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/HarViewer.js +1 -1
- package/dist/components/HarViewer.js.map +1 -1
- package/dist/components/HarViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/JksViewer.js +1 -1
- package/dist/components/JksViewer.js.map +1 -1
- package/dist/components/JksViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/LogViewer.js +1 -1
- package/dist/components/LogViewer.js.map +1 -1
- package/dist/components/LogViewer.vue.d.ts +3 -1
- package/dist/components/LogViewer.vue_vue_type_script_setup_true_lang.js +284 -280
- package/dist/components/LogViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/ModelViewModes.js +9 -0
- package/dist/components/ModelViewModes.js.map +1 -0
- package/dist/components/ModelViewModes.vue.d.ts +11 -0
- package/dist/components/ModelViewModes.vue_vue_type_script_setup_true_lang.js +49 -0
- package/dist/components/ModelViewModes.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/ModelViewer.js +1 -1
- package/dist/components/ModelViewer.js.map +1 -1
- package/dist/components/ModelViewer.vue.d.ts +2 -1
- package/dist/components/ModelViewer.vue_vue_type_script_setup_true_lang.js +183 -96
- package/dist/components/ModelViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/PdfNavigationPane.js +1 -1
- package/dist/components/PdfNavigationPane.js.map +1 -1
- package/dist/components/PdfNavigationPane.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/PdfViewer.js +1 -1
- package/dist/components/PdfViewer.js.map +1 -1
- package/dist/components/PdfViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/PptxNavigationPane.js +1 -1
- package/dist/components/PptxNavigationPane.js.map +1 -1
- package/dist/components/PptxNavigationPane.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/PsdViewer.js +1 -1
- package/dist/components/PsdViewer.js.map +1 -1
- package/dist/components/PsdViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/SqliteViewer.js +1 -1
- package/dist/components/SqliteViewer.js.map +1 -1
- package/dist/components/SqliteViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/ViewerLoadError.js +9 -0
- package/dist/components/ViewerLoadError.js.map +1 -0
- package/dist/components/ViewerLoadError.vue.d.ts +17 -0
- package/dist/components/ViewerLoadError.vue_vue_type_script_setup_true_lang.js +58 -0
- package/dist/components/ViewerLoadError.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/XlsxChart.js +1 -1
- package/dist/components/XlsxChart.js.map +1 -1
- package/dist/components/XlsxChart.vue_vue_type_script_setup_true_lang.js +9 -9
- package/dist/components/XlsxChart.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/XlsxImageLayer.js +9 -0
- package/dist/components/XlsxImageLayer.js.map +1 -0
- package/dist/components/XlsxImageLayer.vue.d.ts +9 -0
- package/dist/components/XlsxImageLayer.vue_vue_type_script_setup_true_lang.js +46 -0
- package/dist/components/XlsxImageLayer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/XlsxViewer.js +1 -1
- package/dist/components/XlsxViewer.js.map +1 -1
- package/dist/components/XlsxViewer.vue_vue_type_script_setup_true_lang.js +230 -216
- package/dist/components/XlsxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/epl/EplResource.js +1 -1
- package/dist/components/epl/EplResource.js.map +1 -1
- package/dist/components/epl/EplResource.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/optionalViewer.d.ts +3 -2
- package/dist/components/optionalViewer.js +6 -3
- package/dist/components/optionalViewer.js.map +1 -1
- package/dist/composables/useFileViewer.d.ts +11 -3
- package/dist/composables/useFileViewer.js.map +1 -1
- package/dist/composables/useTeleportTheme.d.ts +7 -0
- package/dist/composables/useTeleportTheme.js +42 -0
- package/dist/composables/useTeleportTheme.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +29 -28
- package/dist/lib/ansi.d.ts +44 -0
- package/dist/lib/ansi.js +224 -0
- package/dist/lib/ansi.js.map +1 -0
- package/dist/lib/cfb.d.ts +37 -0
- package/dist/lib/cfb.js +112 -0
- package/dist/lib/cfb.js.map +1 -0
- package/dist/lib/docxEmbeddedDom.d.ts +22 -0
- package/dist/lib/docxEmbeddedDom.js +128 -0
- package/dist/lib/docxEmbeddedDom.js.map +1 -0
- package/dist/lib/docxEmbeddings.d.ts +41 -0
- package/dist/lib/docxEmbeddings.js +226 -0
- package/dist/lib/docxEmbeddings.js.map +1 -0
- package/dist/lib/docxTheme.d.ts +6 -0
- package/dist/lib/docxTheme.js +64 -0
- package/dist/lib/docxTheme.js.map +1 -0
- package/dist/lib/epl/code/structures.js +82 -0
- package/dist/lib/epl/code/structures.js.map +1 -0
- package/dist/lib/epl/code/variable.js +16 -0
- package/dist/lib/epl/code/variable.js.map +1 -0
- package/dist/lib/epl/eplDocument.js +28 -22
- package/dist/lib/epl/eplDocument.js.map +1 -1
- package/dist/lib/epl/index.d.ts +2 -1
- package/dist/lib/epl/index.js.map +1 -1
- package/dist/lib/epl/sections/code.js +37 -0
- package/dist/lib/epl/sections/code.js.map +1 -0
- package/dist/lib/epl/sections/esystemInfo.js +29 -0
- package/dist/lib/epl/sections/esystemInfo.js.map +1 -0
- package/dist/lib/epl/sections/losable.js +33 -0
- package/dist/lib/epl/sections/losable.js.map +1 -0
- package/dist/lib/epl/sections/projectConfig.js +39 -0
- package/dist/lib/epl/sections/projectConfig.js.map +1 -0
- package/dist/lib/epl/sections/resource.js +84 -0
- package/dist/lib/epl/sections/resource.js.map +1 -0
- package/dist/lib/fileType.d.ts +3 -1
- package/dist/lib/fileType.js +150 -137
- package/dist/lib/fileType.js.map +1 -1
- package/dist/lib/modelAppearance.d.ts +7 -0
- package/dist/lib/modelAppearance.js +41 -0
- package/dist/lib/modelAppearance.js.map +1 -0
- package/dist/lib/msi.js +62 -154
- package/dist/lib/msi.js.map +1 -1
- package/dist/lib/olePackage.d.ts +8 -0
- package/dist/lib/olePackage.js +37 -0
- package/dist/lib/olePackage.js.map +1 -0
- package/dist/lib/pptxFirstSlide.d.ts +11 -0
- package/dist/lib/pptxFirstSlide.js +34 -0
- package/dist/lib/pptxFirstSlide.js.map +1 -0
- package/dist/lib/step.d.ts +10 -0
- package/dist/lib/step.js +35 -0
- package/dist/lib/step.js.map +1 -0
- package/dist/lib/step.worker.d.ts +1 -0
- package/dist/lib/step.worker.js +18 -0
- package/dist/lib/step.worker.js.map +1 -0
- package/dist/lib/stepObject.d.ts +4 -0
- package/dist/lib/stepObject.js +52 -0
- package/dist/lib/stepObject.js.map +1 -0
- package/dist/lib/stepTypes.d.ts +60 -0
- package/dist/lib/viewerKind.js +10 -5
- package/dist/lib/viewerKind.js.map +1 -1
- package/dist/lib/xlsx.d.ts +1 -1
- package/dist/lib/xlsx.js +106 -89
- package/dist/lib/xlsx.js.map +1 -1
- package/dist/lib/xlsxCharts.js +71 -119
- package/dist/lib/xlsxCharts.js.map +1 -1
- package/dist/lib/xlsxImages.d.ts +43 -0
- package/dist/lib/xlsxImages.js +114 -0
- package/dist/lib/xlsxImages.js.map +1 -0
- package/dist/lib/xlsxParts.d.ts +17 -0
- package/dist/lib/xlsxParts.js +86 -0
- package/dist/lib/xlsxParts.js.map +1 -0
- package/dist/style.css +1 -1
- package/package.json +8 -3
package/dist/lib/cfb.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
//#region src/lib/cfb.ts
|
|
2
|
+
var e = [
|
|
3
|
+
208,
|
|
4
|
+
207,
|
|
5
|
+
17,
|
|
6
|
+
224,
|
|
7
|
+
161,
|
|
8
|
+
177,
|
|
9
|
+
26,
|
|
10
|
+
225
|
|
11
|
+
], t = 4294967294, n = 4294967295, r = class extends Error {
|
|
12
|
+
constructor(e) {
|
|
13
|
+
super(e), this.name = "CompoundFileError";
|
|
14
|
+
}
|
|
15
|
+
}, i = class {
|
|
16
|
+
buffer;
|
|
17
|
+
sectorSize;
|
|
18
|
+
maxSectors;
|
|
19
|
+
miniSectorSize;
|
|
20
|
+
miniCutoff;
|
|
21
|
+
fat = [];
|
|
22
|
+
miniFat = [];
|
|
23
|
+
miniStream = new Uint8Array();
|
|
24
|
+
entries = [];
|
|
25
|
+
constructor(i) {
|
|
26
|
+
this.buffer = i;
|
|
27
|
+
let a = new DataView(i);
|
|
28
|
+
if (i.byteLength < 512) throw new r("文件过小,不是有效的 OLE 复合文件");
|
|
29
|
+
for (let t = 0; t < e.length; t++) if (a.getUint8(t) !== e[t]) throw new r("不是 OLE 复合文件(缺少 D0CF11E0 魔数)");
|
|
30
|
+
let o = a.getUint16(26, !0), s = a.getUint16(30, !0), c = a.getUint16(32, !0);
|
|
31
|
+
if (s !== 9 && s !== 12 || c !== 6) throw new r("OLE 复合文件的扇区尺寸无效");
|
|
32
|
+
this.sectorSize = 1 << s, this.maxSectors = Math.max(0, Math.floor(i.byteLength / this.sectorSize) - 1), this.miniSectorSize = 1 << c, this.miniCutoff = a.getUint32(56, !0);
|
|
33
|
+
let l = a.getUint32(48, !0), u = a.getUint32(60, !0), d = a.getUint32(68, !0), f = this.sectorSize / 4, p = [];
|
|
34
|
+
for (let e = 0; e < 109; e++) {
|
|
35
|
+
let r = a.getUint32(76 + e * 4, !0);
|
|
36
|
+
r !== n && r !== t && p.push(r);
|
|
37
|
+
}
|
|
38
|
+
let m = d, h = 0;
|
|
39
|
+
for (; m !== t && m !== n && h++ < this.maxSectors;) {
|
|
40
|
+
let e = this.sectorOffset(m);
|
|
41
|
+
if (e + this.sectorSize > i.byteLength) break;
|
|
42
|
+
for (let t = 0; t < f - 1; t++) {
|
|
43
|
+
let r = a.getUint32(e + t * 4, !0);
|
|
44
|
+
r !== n && p.push(r);
|
|
45
|
+
}
|
|
46
|
+
m = a.getUint32(e + (f - 1) * 4, !0);
|
|
47
|
+
}
|
|
48
|
+
for (let e of p) {
|
|
49
|
+
let t = this.sectorOffset(e);
|
|
50
|
+
if (!(t + this.sectorSize > i.byteLength)) for (let e = 0; e < f; e++) this.fat.push(a.getUint32(t + e * 4, !0));
|
|
51
|
+
}
|
|
52
|
+
let g = this.readFatStream(l, this.chain(l).length * this.sectorSize), _ = new DataView(g.buffer, g.byteOffset, g.byteLength);
|
|
53
|
+
for (let e = 0; e + 128 <= g.length; e += 128) {
|
|
54
|
+
let t = _.getUint8(e + 66);
|
|
55
|
+
if (t === 0) continue;
|
|
56
|
+
let n = Math.min(_.getUint16(e + 64, !0), 64), r = "";
|
|
57
|
+
for (let t = 0; t + 1 < n; t += 2) {
|
|
58
|
+
let n = _.getUint16(e + t, !0);
|
|
59
|
+
if (n === 0) break;
|
|
60
|
+
r += String.fromCharCode(n);
|
|
61
|
+
}
|
|
62
|
+
let i = _.getUint32(e + 116, !0), a = o <= 3 ? _.getUint32(e + 120, !0) : Number(_.getBigUint64(e + 120, !0));
|
|
63
|
+
Number.isSafeInteger(a) && a >= 0 && this.entries.push({
|
|
64
|
+
name: r,
|
|
65
|
+
type: t,
|
|
66
|
+
start: i,
|
|
67
|
+
size: a
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
let v = this.entries.find((e) => e.type === 5);
|
|
71
|
+
v && (this.miniStream = this.readFatStream(v.start, v.size));
|
|
72
|
+
for (let e of this.chain(u)) {
|
|
73
|
+
let t = this.sectorOffset(e);
|
|
74
|
+
if (!(t + this.sectorSize > i.byteLength)) for (let e = 0; e < f; e++) this.miniFat.push(a.getUint32(t + e * 4, !0));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
findStream(e) {
|
|
78
|
+
return this.entries.find((t) => t.type === 2 && t.name === e);
|
|
79
|
+
}
|
|
80
|
+
stream(e) {
|
|
81
|
+
return e.size === 0 ? new Uint8Array() : e.type === 5 || e.size >= this.miniCutoff ? this.readFatStream(e.start, e.size) : this.readMiniStream(e.start, e.size);
|
|
82
|
+
}
|
|
83
|
+
sectorOffset(e) {
|
|
84
|
+
return (e + 1) * this.sectorSize;
|
|
85
|
+
}
|
|
86
|
+
chain(e) {
|
|
87
|
+
let r = [], i = e, a = 0;
|
|
88
|
+
for (; i !== t && i !== n && i < this.fat.length && i < this.maxSectors && a++ < this.maxSectors;) r.push(i), i = this.fat[i];
|
|
89
|
+
return r;
|
|
90
|
+
}
|
|
91
|
+
sectorBytes(e) {
|
|
92
|
+
let t = this.sectorOffset(e), n = Math.min(this.buffer.byteLength, t + this.sectorSize), r = new Uint8Array(this.sectorSize);
|
|
93
|
+
return t < this.buffer.byteLength && r.set(new Uint8Array(this.buffer, t, n - t)), r;
|
|
94
|
+
}
|
|
95
|
+
readFatStream(e, t) {
|
|
96
|
+
let n = this.chain(e), r = new Uint8Array(n.length * this.sectorSize);
|
|
97
|
+
for (let e = 0; e < n.length; e++) r.set(this.sectorBytes(n[e]), e * this.sectorSize);
|
|
98
|
+
return r.subarray(0, Math.min(t, r.length));
|
|
99
|
+
}
|
|
100
|
+
readMiniStream(e, r) {
|
|
101
|
+
let i = Math.ceil(r / this.miniSectorSize), a = new Uint8Array(i * this.miniSectorSize), o = e, s = 0;
|
|
102
|
+
for (; o !== t && o !== n && o < this.miniFat.length && s < i;) {
|
|
103
|
+
let e = o * this.miniSectorSize;
|
|
104
|
+
a.set(this.miniStream.subarray(e, e + this.miniSectorSize), s * this.miniSectorSize), s++, o = this.miniFat[o];
|
|
105
|
+
}
|
|
106
|
+
return a.subarray(0, Math.min(r, a.length));
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
//#endregion
|
|
110
|
+
export { i as CompoundFile, r as CompoundFileError };
|
|
111
|
+
|
|
112
|
+
//# sourceMappingURL=cfb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cfb.js","names":[],"sources":["../../src/lib/cfb.ts"],"sourcesContent":["/** Generic, dependency-free reader for OLE2 Compound File Binary Format (MS-CFB / CFBF). */\n\nconst CFB_SIGNATURE = [0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1]\nconst ENDOFCHAIN = 0xfffffffe\nconst FREESECT = 0xffffffff\n\nexport interface CompoundFileEntry {\n /** Raw UTF-16 directory name, excluding its null terminator. */\n name: string\n /** 1 = storage, 2 = stream, 5 = root storage. */\n type: number\n start: number\n size: number\n}\n\nexport class CompoundFileError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'CompoundFileError'\n }\n}\n\n/**\n * Read-only compound-file mini-filesystem. Used by both MSI metadata parsing and DOCX OLE package extraction.\n * Sector chains are bounded and stream reads are clamped to the input so corrupt files cannot loop forever or\n * read outside their supplied ArrayBuffer.\n */\nexport class CompoundFile {\n private readonly buffer: ArrayBuffer\n private readonly sectorSize: number\n private readonly maxSectors: number\n private readonly miniSectorSize: number\n private readonly miniCutoff: number\n private readonly fat: number[] = []\n private readonly miniFat: number[] = []\n private miniStream: Uint8Array = new Uint8Array(0)\n readonly entries: CompoundFileEntry[] = []\n\n constructor(buffer: ArrayBuffer) {\n this.buffer = buffer\n const view = new DataView(buffer)\n if (buffer.byteLength < 512) throw new CompoundFileError('文件过小,不是有效的 OLE 复合文件')\n for (let i = 0; i < CFB_SIGNATURE.length; i++) {\n if (view.getUint8(i) !== CFB_SIGNATURE[i]) {\n throw new CompoundFileError('不是 OLE 复合文件(缺少 D0CF11E0 魔数)')\n }\n }\n\n const major = view.getUint16(0x1a, true)\n const sectorShift = view.getUint16(0x1e, true)\n const miniSectorShift = view.getUint16(0x20, true)\n if ((sectorShift !== 9 && sectorShift !== 12) || miniSectorShift !== 6) {\n throw new CompoundFileError('OLE 复合文件的扇区尺寸无效')\n }\n this.sectorSize = 1 << sectorShift\n this.maxSectors = Math.max(0, Math.floor(buffer.byteLength / this.sectorSize) - 1)\n this.miniSectorSize = 1 << miniSectorShift\n this.miniCutoff = view.getUint32(0x38, true)\n const firstDirectory = view.getUint32(0x30, true)\n const firstMiniFat = view.getUint32(0x3c, true)\n const firstDifat = view.getUint32(0x44, true)\n const entriesPerSector = this.sectorSize / 4\n\n // DIFAT → FAT-sector indices. The header carries the first 109; large files chain additional sectors.\n const difat: number[] = []\n for (let i = 0; i < 109; i++) {\n const value = view.getUint32(0x4c + i * 4, true)\n if (value !== FREESECT && value !== ENDOFCHAIN) difat.push(value)\n }\n let difatSector = firstDifat\n let difatGuard = 0\n while (\n difatSector !== ENDOFCHAIN &&\n difatSector !== FREESECT &&\n difatGuard++ < this.maxSectors\n ) {\n const base = this.sectorOffset(difatSector)\n if (base + this.sectorSize > buffer.byteLength) break\n for (let i = 0; i < entriesPerSector - 1; i++) {\n const value = view.getUint32(base + i * 4, true)\n if (value !== FREESECT) difat.push(value)\n }\n difatSector = view.getUint32(base + (entriesPerSector - 1) * 4, true)\n }\n for (const fatSector of difat) {\n const base = this.sectorOffset(fatSector)\n if (base + this.sectorSize > buffer.byteLength) continue\n for (let i = 0; i < entriesPerSector; i++) {\n this.fat.push(view.getUint32(base + i * 4, true))\n }\n }\n\n // Directory entries are 128-byte records in a regular FAT stream.\n const directoryBytes = this.readFatStream(\n firstDirectory,\n this.chain(firstDirectory).length * this.sectorSize,\n )\n const directoryView = new DataView(\n directoryBytes.buffer,\n directoryBytes.byteOffset,\n directoryBytes.byteLength,\n )\n for (let offset = 0; offset + 128 <= directoryBytes.length; offset += 128) {\n const type = directoryView.getUint8(offset + 66)\n if (type === 0) continue\n const nameLength = Math.min(directoryView.getUint16(offset + 64, true), 64)\n let name = ''\n for (let cursor = 0; cursor + 1 < nameLength; cursor += 2) {\n const code = directoryView.getUint16(offset + cursor, true)\n if (code === 0) break\n name += String.fromCharCode(code)\n }\n const start = directoryView.getUint32(offset + 116, true)\n const size =\n major <= 3\n ? directoryView.getUint32(offset + 120, true)\n : Number(directoryView.getBigUint64(offset + 120, true))\n if (Number.isSafeInteger(size) && size >= 0) this.entries.push({ name, type, start, size })\n }\n\n // Small streams are stored inside the root's regular-FAT mini stream and indexed by the MiniFAT.\n const root = this.entries.find((entry) => entry.type === 5)\n if (root) this.miniStream = this.readFatStream(root.start, root.size)\n for (const sector of this.chain(firstMiniFat)) {\n const base = this.sectorOffset(sector)\n if (base + this.sectorSize > buffer.byteLength) continue\n for (let i = 0; i < entriesPerSector; i++) {\n this.miniFat.push(view.getUint32(base + i * 4, true))\n }\n }\n }\n\n findStream(name: string): CompoundFileEntry | undefined {\n return this.entries.find((entry) => entry.type === 2 && entry.name === name)\n }\n\n /** Return one stream's bytes, bounded to its declared size and the input's available sectors. */\n stream(entry: CompoundFileEntry): Uint8Array {\n if (entry.size === 0) return new Uint8Array(0)\n if (entry.type === 5 || entry.size >= this.miniCutoff) {\n return this.readFatStream(entry.start, entry.size)\n }\n return this.readMiniStream(entry.start, entry.size)\n }\n\n private sectorOffset(sector: number): number {\n return (sector + 1) * this.sectorSize\n }\n\n private chain(start: number): number[] {\n const result: number[] = []\n let sector = start\n let guard = 0\n while (\n sector !== ENDOFCHAIN &&\n sector !== FREESECT &&\n sector < this.fat.length &&\n sector < this.maxSectors &&\n guard++ < this.maxSectors\n ) {\n result.push(sector)\n sector = this.fat[sector]\n }\n return result\n }\n\n private sectorBytes(sector: number): Uint8Array {\n const offset = this.sectorOffset(sector)\n const end = Math.min(this.buffer.byteLength, offset + this.sectorSize)\n const result = new Uint8Array(this.sectorSize)\n if (offset < this.buffer.byteLength) {\n result.set(new Uint8Array(this.buffer, offset, end - offset))\n }\n return result\n }\n\n private readFatStream(start: number, size: number): Uint8Array {\n const sectors = this.chain(start)\n const result = new Uint8Array(sectors.length * this.sectorSize)\n for (let i = 0; i < sectors.length; i++) {\n result.set(this.sectorBytes(sectors[i]), i * this.sectorSize)\n }\n return result.subarray(0, Math.min(size, result.length))\n }\n\n private readMiniStream(start: number, size: number): Uint8Array {\n const needed = Math.ceil(size / this.miniSectorSize)\n const result = new Uint8Array(needed * this.miniSectorSize)\n let sector = start\n let index = 0\n while (\n sector !== ENDOFCHAIN &&\n sector !== FREESECT &&\n sector < this.miniFat.length &&\n index < needed\n ) {\n const offset = sector * this.miniSectorSize\n result.set(\n this.miniStream.subarray(offset, offset + this.miniSectorSize),\n index * this.miniSectorSize,\n )\n index++\n sector = this.miniFat[sector]\n }\n return result.subarray(0, Math.min(size, result.length))\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type DocxEmbeddedFile } from '@/lib/docxEmbeddings';
|
|
2
|
+
export interface DocxEmbeddedDomController {
|
|
3
|
+
/** Number of embedded objects mounted into their original document position. */
|
|
4
|
+
mounted: number;
|
|
5
|
+
/** Resolves after every inline snapshot/slide has either rendered or fallen back to a file card. */
|
|
6
|
+
ready: Promise<void>;
|
|
7
|
+
dispose: () => void;
|
|
8
|
+
}
|
|
9
|
+
interface MountDocxEmbeddedPreviewsOptions {
|
|
10
|
+
root: HTMLElement;
|
|
11
|
+
buffer: ArrayBuffer;
|
|
12
|
+
files: readonly DocxEmbeddedFile[];
|
|
13
|
+
onOpen: (file: DocxEmbeddedFile) => void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Put Word/WPS's own OLE snapshots, or a PPTX first-slide fallback, into the empty paragraphs left by
|
|
17
|
+
* docx-preview.
|
|
18
|
+
* The third-party renderer owns this subtree imperatively, so this adapter also uses explicit DOM nodes and
|
|
19
|
+
* listeners; dispose removes every listener and Blob URL before the renderer replaces the document.
|
|
20
|
+
*/
|
|
21
|
+
export declare function mountDocxEmbeddedPreviews(options: MountDocxEmbeddedPreviewsOptions): DocxEmbeddedDomController;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { extractDocxEmbeddedFile as e, extractDocxEmbeddedPreview as t } from "./docxEmbeddings.js";
|
|
2
|
+
//#region src/lib/docxEmbeddedDom.ts
|
|
3
|
+
function n(e) {
|
|
4
|
+
return (e.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
5
|
+
}
|
|
6
|
+
function r(e) {
|
|
7
|
+
return !n(e) && !e.querySelector("img, table, svg, button, object");
|
|
8
|
+
}
|
|
9
|
+
function i(e, t) {
|
|
10
|
+
let i = Array.from(e.querySelectorAll("section.docx p")), a = [], o = /* @__PURE__ */ new Set(), s = 0;
|
|
11
|
+
for (let e of t) {
|
|
12
|
+
let t = -1;
|
|
13
|
+
if (e.label) {
|
|
14
|
+
for (let r = s; r < i.length; r++) if (n(i[r]) === e.label) {
|
|
15
|
+
t = r;
|
|
16
|
+
break;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
let c;
|
|
20
|
+
if (t >= 0) for (let e = t + 1; e < i.length; e++) {
|
|
21
|
+
let t = i[e];
|
|
22
|
+
if (r(t) && !o.has(t)) {
|
|
23
|
+
c = t, s = e + 1;
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
if (n(t)) break;
|
|
27
|
+
}
|
|
28
|
+
if (!c) for (let e = s; e < i.length - 1; e++) {
|
|
29
|
+
let t = i[e], a = n(i[e + 1]);
|
|
30
|
+
if (r(t) && !o.has(t) && /^(?:点击|双击).*(?:查看|打开).*(?:文件|表格)/.test(a)) {
|
|
31
|
+
c = t, s = e + 1;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (!c && e.storage === "ole-package") for (let e = s; e < i.length; e++) {
|
|
36
|
+
let t = i[e];
|
|
37
|
+
if (r(t) && t.childElementCount > 0 && !t.querySelector("[id^=\"heading_\"]") && !o.has(t)) {
|
|
38
|
+
c = t, s = e + 1;
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
c && (o.add(c), a.push({
|
|
43
|
+
file: e,
|
|
44
|
+
host: c
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
return a;
|
|
48
|
+
}
|
|
49
|
+
function a(e) {
|
|
50
|
+
let t = e.lastIndexOf(".");
|
|
51
|
+
return t > 0 ? e.slice(t + 1).toUpperCase() : "FILE";
|
|
52
|
+
}
|
|
53
|
+
function o(e) {
|
|
54
|
+
return e.contentType === "application/vnd.openxmlformats-officedocument.presentationml.presentation" || /\.pptx$/i.test(e.name);
|
|
55
|
+
}
|
|
56
|
+
function s(e, t) {
|
|
57
|
+
e.classList.remove("docx-embedded-preview--image", "docx-embedded-preview--pptx", "docx-embedded-preview--loading"), e.removeAttribute("aria-busy"), e.style.aspectRatio = "";
|
|
58
|
+
let n = document.createElement("span");
|
|
59
|
+
n.className = "docx-embedded-preview__badge", n.textContent = a(t.name);
|
|
60
|
+
let r = document.createElement("span");
|
|
61
|
+
r.className = "docx-embedded-preview__fallback-label", r.textContent = `${t.label || t.name} · 点击预览完整文件`, e.replaceChildren(n, r);
|
|
62
|
+
}
|
|
63
|
+
function c(n) {
|
|
64
|
+
let r = i(n.root, n.files), c = [], l = [], u = !1, d = r.map(async ({ file: r, host: i }) => {
|
|
65
|
+
i.classList.add("docx-embedded-host");
|
|
66
|
+
let d = document.createElement("div");
|
|
67
|
+
d.className = "docx-embedded-preview", d.dataset.docxEmbeddedId = r.id, d.title = `预览 ${r.name}`, d.setAttribute("role", "button"), d.setAttribute("aria-label", `预览内嵌文件 ${r.name}`), d.tabIndex = 0, r.preview?.widthPt && (d.style.maxWidth = `${r.preview.widthPt}pt`);
|
|
68
|
+
let f = () => n.onOpen(r), p = (e) => {
|
|
69
|
+
e.key !== "Enter" && e.key !== " " || (e.preventDefault(), f());
|
|
70
|
+
}, m = null, h = null;
|
|
71
|
+
d.addEventListener("click", f), d.addEventListener("keydown", p), s(d, r), i.replaceChildren(d), l.push(() => {
|
|
72
|
+
m?.abort(), h?.dispose(), d.removeEventListener("click", f), d.removeEventListener("keydown", p), i.contains(d) && i.replaceChildren(), i.classList.remove("docx-embedded-host");
|
|
73
|
+
});
|
|
74
|
+
try {
|
|
75
|
+
let i = await t(n.buffer, r);
|
|
76
|
+
if (u) return;
|
|
77
|
+
if (i) {
|
|
78
|
+
r.preview?.widthPt && r.preview.heightPt && (d.style.aspectRatio = `${r.preview.widthPt} / ${r.preview.heightPt}`);
|
|
79
|
+
let e = URL.createObjectURL(new Blob([i.bytes], { type: i.contentType }));
|
|
80
|
+
if (u) {
|
|
81
|
+
URL.revokeObjectURL(e);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
c.push(e);
|
|
85
|
+
let t = document.createElement("img");
|
|
86
|
+
t.className = "docx-embedded-preview__image", t.src = e, t.alt = `${r.label || r.name} 内容预览`, t.draggable = !1;
|
|
87
|
+
let n = document.createElement("span");
|
|
88
|
+
n.className = "docx-embedded-preview__hint", n.textContent = `${a(r.name)} · 点击预览完整文件`, d.classList.add("docx-embedded-preview--image"), d.replaceChildren(t, n);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (!o(r)) return;
|
|
92
|
+
d.classList.add("docx-embedded-preview--pptx", "docx-embedded-preview--loading"), d.setAttribute("aria-busy", "true"), d.style.aspectRatio = r.preview?.widthPt && r.preview.heightPt ? `${r.preview.widthPt} / ${r.preview.heightPt}` : "16 / 9";
|
|
93
|
+
let s = document.createElement("div");
|
|
94
|
+
s.className = "docx-embedded-preview__pptx-slide";
|
|
95
|
+
let l = document.createElement("span");
|
|
96
|
+
l.className = "docx-embedded-preview__pptx-status", l.textContent = "正在渲染 PPTX 第一页…", d.replaceChildren(s, l), m = new AbortController();
|
|
97
|
+
let [f, p] = await Promise.all([e(n.buffer, r), import("./pptxFirstSlide.js")]);
|
|
98
|
+
if (u) return;
|
|
99
|
+
if (h = await p.mountPptxFirstSlide({
|
|
100
|
+
container: s,
|
|
101
|
+
bytes: f,
|
|
102
|
+
signal: m.signal
|
|
103
|
+
}), u) {
|
|
104
|
+
h.dispose(), h = null;
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
d.classList.remove("docx-embedded-preview--loading"), d.removeAttribute("aria-busy"), l.className = "docx-embedded-preview__hint", l.textContent = "PPTX · 点击预览完整文件";
|
|
108
|
+
} catch {
|
|
109
|
+
u || s(d, r);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
return {
|
|
113
|
+
mounted: r.length,
|
|
114
|
+
ready: Promise.allSettled(d).then(() => void 0),
|
|
115
|
+
dispose: () => {
|
|
116
|
+
if (!u) {
|
|
117
|
+
u = !0;
|
|
118
|
+
for (let e of l) e();
|
|
119
|
+
for (let e of c) URL.revokeObjectURL(e);
|
|
120
|
+
c.length = 0, l.length = 0;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|
|
126
|
+
export { c as mountDocxEmbeddedPreviews };
|
|
127
|
+
|
|
128
|
+
//# sourceMappingURL=docxEmbeddedDom.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docxEmbeddedDom.js","names":[],"sources":["../../src/lib/docxEmbeddedDom.ts"],"sourcesContent":["import {\n extractDocxEmbeddedFile,\n extractDocxEmbeddedPreview,\n type DocxEmbeddedFile,\n} from '@/lib/docxEmbeddings'\nimport type { PptxFirstSlideController } from '@/lib/pptxFirstSlide'\n\nexport interface DocxEmbeddedDomController {\n /** Number of embedded objects mounted into their original document position. */\n mounted: number\n /** Resolves after every inline snapshot/slide has either rendered or fallen back to a file card. */\n ready: Promise<void>\n dispose: () => void\n}\n\ninterface MountDocxEmbeddedPreviewsOptions {\n root: HTMLElement\n buffer: ArrayBuffer\n files: readonly DocxEmbeddedFile[]\n onOpen: (file: DocxEmbeddedFile) => void\n}\n\nfunction normalizedText(element: Element): string {\n return (element.textContent ?? '').replace(/\\s+/g, ' ').trim()\n}\n\nfunction isEmptyParagraph(paragraph: HTMLParagraphElement): boolean {\n return !normalizedText(paragraph) && !paragraph.querySelector('img, table, svg, button, object')\n}\n\nfunction findHostParagraphs(\n root: HTMLElement,\n files: readonly DocxEmbeddedFile[],\n): Array<{ file: DocxEmbeddedFile; host: HTMLParagraphElement }> {\n const paragraphs = Array.from(root.querySelectorAll<HTMLParagraphElement>('section.docx p'))\n const result: Array<{ file: DocxEmbeddedFile; host: HTMLParagraphElement }> = []\n const used = new Set<HTMLParagraphElement>()\n let cursor = 0\n\n for (const file of files) {\n let headingIndex = -1\n if (file.label) {\n for (let i = cursor; i < paragraphs.length; i++) {\n if (normalizedText(paragraphs[i]) === file.label) {\n headingIndex = i\n break\n }\n }\n }\n\n let host: HTMLParagraphElement | undefined\n if (headingIndex >= 0) {\n // docx-preview drops `w:object` itself but preserves its containing paragraph as the first empty <p>\n // after the caption. Stop at the next non-empty paragraph so a distant layout spacer is never hijacked.\n for (let i = headingIndex + 1; i < paragraphs.length; i++) {\n const paragraph = paragraphs[i]\n if (isEmptyParagraph(paragraph) && !used.has(paragraph)) {\n host = paragraph\n cursor = i + 1\n break\n }\n if (normalizedText(paragraph)) break\n }\n }\n\n if (!host) {\n // Caption-less fallback: accept only an empty paragraph immediately followed by Word's usual OLE hint.\n for (let i = cursor; i < paragraphs.length - 1; i++) {\n const paragraph = paragraphs[i]\n const nextText = normalizedText(paragraphs[i + 1])\n if (\n isEmptyParagraph(paragraph) &&\n !used.has(paragraph) &&\n /^(?:点击|双击).*(?:查看|打开).*(?:文件|表格)/.test(nextText)\n ) {\n host = paragraph\n cursor = i + 1\n break\n }\n }\n }\n\n if (!host && file.storage === 'ole-package') {\n // A caption-less OLE object at the document tail still leaves a non-empty DOM shell (typically its\n // bookmark spans). Prefer that over truly empty spacer paragraphs and generated heading anchors.\n for (let i = cursor; i < paragraphs.length; i++) {\n const paragraph = paragraphs[i]\n if (\n isEmptyParagraph(paragraph) &&\n paragraph.childElementCount > 0 &&\n !paragraph.querySelector('[id^=\"heading_\"]') &&\n !used.has(paragraph)\n ) {\n host = paragraph\n cursor = i + 1\n break\n }\n }\n }\n\n if (host) {\n used.add(host)\n result.push({ file, host })\n }\n }\n return result\n}\n\nfunction extension(name: string): string {\n const i = name.lastIndexOf('.')\n return i > 0 ? name.slice(i + 1).toUpperCase() : 'FILE'\n}\n\nfunction isPptx(file: DocxEmbeddedFile): boolean {\n return (\n file.contentType ===\n 'application/vnd.openxmlformats-officedocument.presentationml.presentation' ||\n /\\.pptx$/i.test(file.name)\n )\n}\n\nfunction mountFallback(previewElement: HTMLElement, file: DocxEmbeddedFile): void {\n previewElement.classList.remove(\n 'docx-embedded-preview--image',\n 'docx-embedded-preview--pptx',\n 'docx-embedded-preview--loading',\n )\n previewElement.removeAttribute('aria-busy')\n previewElement.style.aspectRatio = ''\n const badge = document.createElement('span')\n badge.className = 'docx-embedded-preview__badge'\n badge.textContent = extension(file.name)\n const label = document.createElement('span')\n label.className = 'docx-embedded-preview__fallback-label'\n label.textContent = `${file.label || file.name} · 点击预览完整文件`\n previewElement.replaceChildren(badge, label)\n}\n\n/**\n * Put Word/WPS's own OLE snapshots, or a PPTX first-slide fallback, into the empty paragraphs left by\n * docx-preview.\n * The third-party renderer owns this subtree imperatively, so this adapter also uses explicit DOM nodes and\n * listeners; dispose removes every listener and Blob URL before the renderer replaces the document.\n */\nexport function mountDocxEmbeddedPreviews(\n options: MountDocxEmbeddedPreviewsOptions,\n): DocxEmbeddedDomController {\n const mounts = findHostParagraphs(options.root, options.files)\n const urls: string[] = []\n const cleanups: Array<() => void> = []\n let disposed = false\n\n const loads = mounts.map(async ({ file, host }) => {\n host.classList.add('docx-embedded-host')\n const previewElement = document.createElement('div')\n previewElement.className = 'docx-embedded-preview'\n previewElement.dataset.docxEmbeddedId = file.id\n previewElement.title = `预览 ${file.name}`\n previewElement.setAttribute('role', 'button')\n previewElement.setAttribute('aria-label', `预览内嵌文件 ${file.name}`)\n previewElement.tabIndex = 0\n if (file.preview?.widthPt) previewElement.style.maxWidth = `${file.preview.widthPt}pt`\n const open = (): void => options.onOpen(file)\n const openFromKeyboard = (event: KeyboardEvent): void => {\n if (event.key !== 'Enter' && event.key !== ' ') return\n event.preventDefault()\n open()\n }\n let pptxAbort: AbortController | null = null\n let pptxController: PptxFirstSlideController | null = null\n previewElement.addEventListener('click', open)\n previewElement.addEventListener('keydown', openFromKeyboard)\n mountFallback(previewElement, file)\n host.replaceChildren(previewElement)\n\n cleanups.push(() => {\n pptxAbort?.abort()\n pptxController?.dispose()\n previewElement.removeEventListener('click', open)\n previewElement.removeEventListener('keydown', openFromKeyboard)\n if (host.contains(previewElement)) host.replaceChildren()\n host.classList.remove('docx-embedded-host')\n })\n\n try {\n const preview = await extractDocxEmbeddedPreview(options.buffer, file)\n if (disposed) return\n if (preview) {\n if (file.preview?.widthPt && file.preview.heightPt) {\n previewElement.style.aspectRatio = `${file.preview.widthPt} / ${file.preview.heightPt}`\n }\n const url = URL.createObjectURL(new Blob([preview.bytes], { type: preview.contentType }))\n if (disposed) {\n URL.revokeObjectURL(url)\n return\n }\n urls.push(url)\n const image = document.createElement('img')\n image.className = 'docx-embedded-preview__image'\n image.src = url\n image.alt = `${file.label || file.name} 内容预览`\n image.draggable = false\n const hint = document.createElement('span')\n hint.className = 'docx-embedded-preview__hint'\n hint.textContent = `${extension(file.name)} · 点击预览完整文件`\n previewElement.classList.add('docx-embedded-preview--image')\n previewElement.replaceChildren(image, hint)\n return\n }\n\n if (!isPptx(file)) return\n previewElement.classList.add('docx-embedded-preview--pptx', 'docx-embedded-preview--loading')\n previewElement.setAttribute('aria-busy', 'true')\n previewElement.style.aspectRatio =\n file.preview?.widthPt && file.preview.heightPt\n ? `${file.preview.widthPt} / ${file.preview.heightPt}`\n : '16 / 9'\n const slide = document.createElement('div')\n slide.className = 'docx-embedded-preview__pptx-slide'\n const hint = document.createElement('span')\n hint.className = 'docx-embedded-preview__pptx-status'\n hint.textContent = '正在渲染 PPTX 第一页…'\n previewElement.replaceChildren(slide, hint)\n\n pptxAbort = new AbortController()\n const [bytes, renderer] = await Promise.all([\n extractDocxEmbeddedFile(options.buffer, file),\n import('@/lib/pptxFirstSlide'),\n ])\n if (disposed) return\n pptxController = await renderer.mountPptxFirstSlide({\n container: slide,\n bytes,\n signal: pptxAbort.signal,\n })\n if (disposed) {\n pptxController.dispose()\n pptxController = null\n return\n }\n previewElement.classList.remove('docx-embedded-preview--loading')\n previewElement.removeAttribute('aria-busy')\n hint.className = 'docx-embedded-preview__hint'\n hint.textContent = 'PPTX · 点击预览完整文件'\n } catch {\n // The actual embedded file remains previewable from this fallback card and from the top toolbar.\n if (!disposed) mountFallback(previewElement, file)\n }\n })\n\n return {\n mounted: mounts.length,\n ready: Promise.allSettled(loads).then(() => undefined),\n dispose: () => {\n if (disposed) return\n disposed = true\n for (const cleanup of cleanups) cleanup()\n for (const url of urls) URL.revokeObjectURL(url)\n urls.length = 0\n cleanups.length = 0\n },\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type ZipEntry } from '@/lib/zip';
|
|
2
|
+
export interface DocxEmbeddedFile {
|
|
3
|
+
/** Stable relationship id inside word/document.xml. */
|
|
4
|
+
id: string;
|
|
5
|
+
/** Human-facing caption inferred from the nearest preceding Word paragraph. */
|
|
6
|
+
label: string;
|
|
7
|
+
/** Friendly download name (caption + original extension when a caption exists). */
|
|
8
|
+
name: string;
|
|
9
|
+
/** Original package basename, e.g. Microsoft_Excel_Worksheet1.xlsx. */
|
|
10
|
+
originalName: string;
|
|
11
|
+
/** Resolved package path inside the DOCX ZIP. */
|
|
12
|
+
path: string;
|
|
13
|
+
size: number;
|
|
14
|
+
contentType: string;
|
|
15
|
+
storage: 'package' | 'ole-package';
|
|
16
|
+
/** Word/WPS-generated visual snapshot for the embedded object, when present. */
|
|
17
|
+
preview?: DocxEmbeddedPreview;
|
|
18
|
+
/** Internal ZIP descriptor retained so extraction stays on-demand. */
|
|
19
|
+
entry: ZipEntry;
|
|
20
|
+
}
|
|
21
|
+
export interface DocxEmbeddedPreview {
|
|
22
|
+
path: string;
|
|
23
|
+
size: number;
|
|
24
|
+
contentType: string;
|
|
25
|
+
widthPt?: number;
|
|
26
|
+
heightPt?: number;
|
|
27
|
+
entry: ZipEntry;
|
|
28
|
+
}
|
|
29
|
+
export interface ExtractedDocxPreview {
|
|
30
|
+
bytes: Uint8Array;
|
|
31
|
+
contentType: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Discover embedded package relationships (`w:object` / `o:OLEObject`) in document order.
|
|
35
|
+
* `docx-preview` currently drops these nodes, so the caller can expose the original bytes separately.
|
|
36
|
+
*/
|
|
37
|
+
export declare function readDocxEmbeddedFiles(buffer: ArrayBuffer): Promise<DocxEmbeddedFile[]>;
|
|
38
|
+
/** Extract one discovered embedded package on demand, with the same per-file ceiling as server exports. */
|
|
39
|
+
export declare function extractDocxEmbeddedFile(buffer: ArrayBuffer, file: DocxEmbeddedFile): Promise<Uint8Array>;
|
|
40
|
+
/** Extract the inert Word/WPS snapshot paired with an embedded object. */
|
|
41
|
+
export declare function extractDocxEmbeddedPreview(buffer: ArrayBuffer, file: DocxEmbeddedFile): Promise<ExtractedDocxPreview | null>;
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { extractZipEntry as e, readZip as t } from "./zip.js";
|
|
2
|
+
import { extractOlePackage as n } from "./olePackage.js";
|
|
3
|
+
//#region src/lib/docxEmbeddings.ts
|
|
4
|
+
var r = "word/document.xml", i = "word/_rels/document.xml.rels", a = "[Content_Types].xml", o = 100 * 1024 * 1024, s = 20 * 1024 * 1024, c = {
|
|
5
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
6
|
+
pdf: "application/pdf",
|
|
7
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
8
|
+
xls: "application/vnd.ms-excel",
|
|
9
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
10
|
+
};
|
|
11
|
+
function l(e, t) {
|
|
12
|
+
let n = new DOMParser().parseFromString(e, "application/xml"), r = Array.from(n.getElementsByTagNameNS("*", "parsererror"))[0];
|
|
13
|
+
if (r) throw Error(`${t} XML 无法解析:${r.textContent?.trim() || "格式错误"}`);
|
|
14
|
+
return n;
|
|
15
|
+
}
|
|
16
|
+
function u(e, t) {
|
|
17
|
+
return Array.from(e.getElementsByTagNameNS("*", t));
|
|
18
|
+
}
|
|
19
|
+
function d(e, t) {
|
|
20
|
+
return Array.from(e.attributes).find((e) => e.localName === t)?.value ?? "";
|
|
21
|
+
}
|
|
22
|
+
function f(e) {
|
|
23
|
+
return new TextDecoder("utf-8").decode(e);
|
|
24
|
+
}
|
|
25
|
+
function p(e) {
|
|
26
|
+
let t = e.lastIndexOf(".");
|
|
27
|
+
return t > 0 && t < e.length - 1 ? e.slice(t + 1).toLowerCase() : "";
|
|
28
|
+
}
|
|
29
|
+
function m(e) {
|
|
30
|
+
return e.slice(e.lastIndexOf("/") + 1);
|
|
31
|
+
}
|
|
32
|
+
function h(e) {
|
|
33
|
+
for (let t of e) if (t.charCodeAt(0) < 32) return !0;
|
|
34
|
+
return !1;
|
|
35
|
+
}
|
|
36
|
+
function g(e) {
|
|
37
|
+
let t = "";
|
|
38
|
+
for (let n of e) t += n.charCodeAt(0) < 32 || "<>:\"/\\|?*".includes(n) ? "_" : n;
|
|
39
|
+
return t;
|
|
40
|
+
}
|
|
41
|
+
function _(e, t) {
|
|
42
|
+
let n = t.split(/[?#]/, 1)[0]?.replace(/\\/g, "/") ?? "";
|
|
43
|
+
if (!n || h(n)) return "";
|
|
44
|
+
let r = n.startsWith("/") ? [] : e.split("/").slice(0, -1);
|
|
45
|
+
for (let e of n.split("/")) {
|
|
46
|
+
let t;
|
|
47
|
+
try {
|
|
48
|
+
t = decodeURIComponent(e);
|
|
49
|
+
} catch {
|
|
50
|
+
return "";
|
|
51
|
+
}
|
|
52
|
+
if (!(!t || t === ".")) {
|
|
53
|
+
if (t === "..") {
|
|
54
|
+
if (r.length === 0) return "";
|
|
55
|
+
r.pop();
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (t.includes("/") || t.includes("\\")) return "";
|
|
59
|
+
r.push(t);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return r.join("/");
|
|
63
|
+
}
|
|
64
|
+
function v(e) {
|
|
65
|
+
return u(e, "t").map((e) => e.textContent ?? "").join("").replace(/\s+/g, " ").trim();
|
|
66
|
+
}
|
|
67
|
+
function y(e) {
|
|
68
|
+
let t = e.replace(/\s+/g, " ").trim();
|
|
69
|
+
return /^(?:点击|双击).*(?:查看|打开).*(?:文件|表格)/.test(t) ? "" : t.slice(0, 100);
|
|
70
|
+
}
|
|
71
|
+
function b(e, t) {
|
|
72
|
+
if (!e) return t;
|
|
73
|
+
let n = g(e).replace(/[. ]+$/g, "").trim().slice(0, 120);
|
|
74
|
+
if (!n) return t;
|
|
75
|
+
let r = p(t);
|
|
76
|
+
return r && !n.toLowerCase().endsWith(`.${r}`) ? `${n}.${r}` : n;
|
|
77
|
+
}
|
|
78
|
+
function x(e) {
|
|
79
|
+
let t = l(e, "DOCX relationship"), n = /* @__PURE__ */ new Map();
|
|
80
|
+
for (let e of u(t, "Relationship")) {
|
|
81
|
+
let t = e.getAttribute("Id") ?? "", r = e.getAttribute("Target") ?? "";
|
|
82
|
+
!t || !r || n.set(t, {
|
|
83
|
+
target: r,
|
|
84
|
+
type: e.getAttribute("Type") ?? "",
|
|
85
|
+
external: (e.getAttribute("TargetMode") ?? "").toLowerCase() === "external"
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return n;
|
|
89
|
+
}
|
|
90
|
+
function S(e) {
|
|
91
|
+
let t = /* @__PURE__ */ new Map(), n = /* @__PURE__ */ new Map();
|
|
92
|
+
if (!e) return {
|
|
93
|
+
defaults: t,
|
|
94
|
+
overrides: n
|
|
95
|
+
};
|
|
96
|
+
let r = l(e, "DOCX content types");
|
|
97
|
+
for (let e of u(r, "Default")) {
|
|
98
|
+
let n = (e.getAttribute("Extension") ?? "").toLowerCase(), r = e.getAttribute("ContentType") ?? "";
|
|
99
|
+
n && r && t.set(n, r);
|
|
100
|
+
}
|
|
101
|
+
for (let e of u(r, "Override")) {
|
|
102
|
+
let t = (e.getAttribute("PartName") ?? "").replace(/^\/+/, ""), r = e.getAttribute("ContentType") ?? "";
|
|
103
|
+
t && r && n.set(t, r);
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
defaults: t,
|
|
107
|
+
overrides: n
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function C(e, t) {
|
|
111
|
+
let n = p(e);
|
|
112
|
+
return t.overrides.get(e) ?? t.defaults.get(n) ?? c[n] ?? "application/octet-stream";
|
|
113
|
+
}
|
|
114
|
+
function w(e) {
|
|
115
|
+
let t = e.toLowerCase();
|
|
116
|
+
return t === "powerpoint.show.12" || t === "powerpoint.slide.12" ? {
|
|
117
|
+
extension: "pptx",
|
|
118
|
+
contentType: c.pptx,
|
|
119
|
+
label: "PowerPoint 演示文稿"
|
|
120
|
+
} : t === "excel.sheet.12" ? {
|
|
121
|
+
extension: "xlsx",
|
|
122
|
+
contentType: c.xlsx,
|
|
123
|
+
label: "Excel 工作簿"
|
|
124
|
+
} : t === "word.document.12" ? {
|
|
125
|
+
extension: "docx",
|
|
126
|
+
contentType: c.docx,
|
|
127
|
+
label: "Word 文档"
|
|
128
|
+
} : null;
|
|
129
|
+
}
|
|
130
|
+
function T(e, t) {
|
|
131
|
+
let n = e;
|
|
132
|
+
for (; n;) {
|
|
133
|
+
if (n.localName === t) return n;
|
|
134
|
+
n = n.parentElement;
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
function E(e, t) {
|
|
139
|
+
let n = e.match(RegExp(`(?:^|;)\\s*${t}\\s*:\\s*([0-9]+(?:\\.[0-9]+)?)(pt|px|in|cm|mm)`, "i"));
|
|
140
|
+
if (!n) return;
|
|
141
|
+
let r = Number(n[1]), i = n[2]?.toLowerCase(), a = i === "pt" ? r : i === "px" ? r * .75 : i === "in" ? r * 72 : i === "cm" ? r * 72 / 2.54 : r * 72 / 25.4;
|
|
142
|
+
return Number.isFinite(a) && a > 0 && a <= 2e3 ? a : void 0;
|
|
143
|
+
}
|
|
144
|
+
function D(e, t, n, i) {
|
|
145
|
+
let a = T(e, "object");
|
|
146
|
+
if (!a) return;
|
|
147
|
+
let o = u(a, "imagedata")[0], s = o ? d(o, "id") : "", c = t.get(s);
|
|
148
|
+
if (!s || !c || c.external || !c.type.toLowerCase().endsWith("/image")) return;
|
|
149
|
+
let l = _(r, c.target), f = n.get(l);
|
|
150
|
+
if (!l || !f) return;
|
|
151
|
+
let p = u(a, "shape")[0]?.getAttribute("style") ?? "";
|
|
152
|
+
return {
|
|
153
|
+
path: l,
|
|
154
|
+
size: f.size,
|
|
155
|
+
contentType: C(l, i),
|
|
156
|
+
widthPt: E(p, "width"),
|
|
157
|
+
heightPt: E(p, "height"),
|
|
158
|
+
entry: f
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function O(e, t) {
|
|
162
|
+
return e.length >= 8 && e[0] === 137 && e[1] === 80 && e[2] === 78 && e[3] === 71 ? "image/png" : e.length >= 3 && e[0] === 255 && e[1] === 216 && e[2] === 255 ? "image/jpeg" : e.length >= 6 && e[0] === 71 && e[1] === 73 && e[2] === 70 && e[3] === 56 ? "image/gif" : e.length >= 2 && e[0] === 66 && e[1] === 77 ? "image/bmp" : e.length >= 12 && e[0] === 82 && e[1] === 73 && e[2] === 70 && e[3] === 70 && e[8] === 87 && e[9] === 69 && e[10] === 66 && e[11] === 80 ? "image/webp" : t.startsWith("image/") ? t : "application/octet-stream";
|
|
163
|
+
}
|
|
164
|
+
async function k(n) {
|
|
165
|
+
let o = t(n), s = new Map(o.filter((e) => !e.isDir).map((e) => [e.path, e])), c = s.get(r), p = s.get(i);
|
|
166
|
+
if (!c || !p) return [];
|
|
167
|
+
let [h, g, T] = await Promise.all([
|
|
168
|
+
e(n, c).then(f),
|
|
169
|
+
e(n, p).then(f),
|
|
170
|
+
s.has(a) ? e(n, s.get(a)).then(f) : Promise.resolve(null)
|
|
171
|
+
]), E = l(h, "DOCX document"), O = x(g), k = S(T), A = [], j = /* @__PURE__ */ new Set(), M = "";
|
|
172
|
+
for (let e of u(E, "p")) {
|
|
173
|
+
let t = v(e);
|
|
174
|
+
for (let n of u(e, "OLEObject")) {
|
|
175
|
+
let e = d(n, "id"), i = O.get(e);
|
|
176
|
+
if (!e || !i || i.external) continue;
|
|
177
|
+
let a = i.type.toLowerCase(), o = a.endsWith("/package"), c = a.endsWith("/oleobject");
|
|
178
|
+
if (!o && !c) continue;
|
|
179
|
+
let l = n.getAttribute("ProgID") ?? d(n, "ProgID"), u = c ? w(l) : null;
|
|
180
|
+
if (c && !u) continue;
|
|
181
|
+
let f = _(r, i.target), p = s.get(f);
|
|
182
|
+
if (!f || !p || j.has(f)) continue;
|
|
183
|
+
j.add(f);
|
|
184
|
+
let h = m(f), g = u ? `${u.label}.${u.extension}` : h, v = y(t || (u ? u.label : M));
|
|
185
|
+
A.push({
|
|
186
|
+
id: e,
|
|
187
|
+
label: v,
|
|
188
|
+
name: b(v, g),
|
|
189
|
+
originalName: g,
|
|
190
|
+
path: f,
|
|
191
|
+
size: p.size,
|
|
192
|
+
contentType: u?.contentType ?? C(f, k),
|
|
193
|
+
storage: u ? "ole-package" : "package",
|
|
194
|
+
preview: D(n, O, s, k),
|
|
195
|
+
entry: p
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
t && (M = t);
|
|
199
|
+
}
|
|
200
|
+
return A;
|
|
201
|
+
}
|
|
202
|
+
async function A(t, r) {
|
|
203
|
+
if (r.size > o) throw Error(`内嵌文件超过 ${o / 1024 / 1024} MiB,已拒绝在浏览器中展开`);
|
|
204
|
+
let i = await e(t, r.entry);
|
|
205
|
+
return r.storage === "ole-package" ? n(i, o) : i;
|
|
206
|
+
}
|
|
207
|
+
async function j(t, n) {
|
|
208
|
+
let r = n.preview;
|
|
209
|
+
if (!r) return null;
|
|
210
|
+
if (r.size > s) throw Error(`内嵌预览图超过 ${s / 1024 / 1024} MiB,已跳过正文预览`);
|
|
211
|
+
let i = await e(t, r.entry), a = O(i, r.contentType);
|
|
212
|
+
return [
|
|
213
|
+
"image/png",
|
|
214
|
+
"image/jpeg",
|
|
215
|
+
"image/gif",
|
|
216
|
+
"image/bmp",
|
|
217
|
+
"image/webp"
|
|
218
|
+
].includes(a) ? {
|
|
219
|
+
bytes: i,
|
|
220
|
+
contentType: a
|
|
221
|
+
} : null;
|
|
222
|
+
}
|
|
223
|
+
//#endregion
|
|
224
|
+
export { A as extractDocxEmbeddedFile, j as extractDocxEmbeddedPreview, k as readDocxEmbeddedFiles };
|
|
225
|
+
|
|
226
|
+
//# sourceMappingURL=docxEmbeddings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docxEmbeddings.js","names":[],"sources":["../../src/lib/docxEmbeddings.ts"],"sourcesContent":["import { extractZipEntry, readZip, type ZipEntry } from '@/lib/zip'\nimport { extractOlePackage } from '@/lib/olePackage'\n\nconst DOCUMENT_PART = 'word/document.xml'\nconst DOCUMENT_RELS_PART = 'word/_rels/document.xml.rels'\nconst CONTENT_TYPES_PART = '[Content_Types].xml'\nconst MAX_EMBEDDED_FILE_BYTES = 100 * 1024 * 1024\nconst MAX_PREVIEW_IMAGE_BYTES = 20 * 1024 * 1024\n\nconst MIME_BY_EXTENSION: Record<string, string> = {\n docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n pdf: 'application/pdf',\n pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n xls: 'application/vnd.ms-excel',\n xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n}\n\nexport interface DocxEmbeddedFile {\n /** Stable relationship id inside word/document.xml. */\n id: string\n /** Human-facing caption inferred from the nearest preceding Word paragraph. */\n label: string\n /** Friendly download name (caption + original extension when a caption exists). */\n name: string\n /** Original package basename, e.g. Microsoft_Excel_Worksheet1.xlsx. */\n originalName: string\n /** Resolved package path inside the DOCX ZIP. */\n path: string\n size: number\n contentType: string\n storage: 'package' | 'ole-package'\n /** Word/WPS-generated visual snapshot for the embedded object, when present. */\n preview?: DocxEmbeddedPreview\n /** Internal ZIP descriptor retained so extraction stays on-demand. */\n entry: ZipEntry\n}\n\nexport interface DocxEmbeddedPreview {\n path: string\n size: number\n contentType: string\n widthPt?: number\n heightPt?: number\n entry: ZipEntry\n}\n\nexport interface ExtractedDocxPreview {\n bytes: Uint8Array\n contentType: string\n}\n\ninterface Relationship {\n target: string\n type: string\n external: boolean\n}\n\ninterface OlePackageKind {\n extension: string\n contentType: string\n label: string\n}\n\nfunction parseXml(xml: string, label: string): XMLDocument {\n const document = new DOMParser().parseFromString(xml, 'application/xml')\n const error = Array.from(document.getElementsByTagNameNS('*', 'parsererror'))[0]\n if (error) throw new Error(`${label} XML 无法解析:${error.textContent?.trim() || '格式错误'}`)\n return document\n}\n\nfunction elements(root: Document | Element, localName: string): Element[] {\n return Array.from(root.getElementsByTagNameNS('*', localName))\n}\n\nfunction attrByLocalName(element: Element, localName: string): string {\n return (\n Array.from(element.attributes).find((attribute) => attribute.localName === localName)?.value ??\n ''\n )\n}\n\nfunction decodeEntry(bytes: Uint8Array): string {\n return new TextDecoder('utf-8').decode(bytes)\n}\n\nfunction extension(name: string): string {\n const i = name.lastIndexOf('.')\n return i > 0 && i < name.length - 1 ? name.slice(i + 1).toLowerCase() : ''\n}\n\nfunction basename(path: string): string {\n return path.slice(path.lastIndexOf('/') + 1)\n}\n\nfunction hasControlCharacters(value: string): boolean {\n for (const character of value) if (character.charCodeAt(0) < 0x20) return true\n return false\n}\n\nfunction sanitizeFileName(value: string): string {\n const invalid = '<>:\"/\\\\|?*'\n let result = ''\n for (const character of value) {\n result += character.charCodeAt(0) < 0x20 || invalid.includes(character) ? '_' : character\n }\n return result\n}\n\n/** Resolve an OPC relationship target relative to its source part without allowing it above the package root. */\nfunction resolvePartTarget(sourcePart: string, target: string): string {\n const raw = target.split(/[?#]/, 1)[0]?.replace(/\\\\/g, '/') ?? ''\n if (!raw || hasControlCharacters(raw)) return ''\n const parts = raw.startsWith('/') ? [] : sourcePart.split('/').slice(0, -1)\n for (const encoded of raw.split('/')) {\n let segment: string\n try {\n segment = decodeURIComponent(encoded)\n } catch {\n return ''\n }\n if (!segment || segment === '.') continue\n if (segment === '..') {\n if (parts.length === 0) return ''\n parts.pop()\n continue\n }\n if (segment.includes('/') || segment.includes('\\\\')) return ''\n parts.push(segment)\n }\n return parts.join('/')\n}\n\nfunction paragraphText(paragraph: Element): string {\n return elements(paragraph, 't')\n .map((node) => node.textContent ?? '')\n .join('')\n .replace(/\\s+/g, ' ')\n .trim()\n}\n\nfunction cleanLabel(value: string): string {\n const label = value.replace(/\\s+/g, ' ').trim()\n if (/^(?:点击|双击).*(?:查看|打开).*(?:文件|表格)/.test(label)) return ''\n return label.slice(0, 100)\n}\n\nfunction friendlyName(label: string, originalName: string): string {\n if (!label) return originalName\n const safe = sanitizeFileName(label)\n .replace(/[. ]+$/g, '')\n .trim()\n .slice(0, 120)\n if (!safe) return originalName\n const ext = extension(originalName)\n return ext && !safe.toLowerCase().endsWith(`.${ext}`) ? `${safe}.${ext}` : safe\n}\n\nfunction readRelationships(xml: string): Map<string, Relationship> {\n const document = parseXml(xml, 'DOCX relationship')\n const out = new Map<string, Relationship>()\n for (const node of elements(document, 'Relationship')) {\n const id = node.getAttribute('Id') ?? ''\n const target = node.getAttribute('Target') ?? ''\n if (!id || !target) continue\n out.set(id, {\n target,\n type: node.getAttribute('Type') ?? '',\n external: (node.getAttribute('TargetMode') ?? '').toLowerCase() === 'external',\n })\n }\n return out\n}\n\nfunction readContentTypes(xml: string | null): {\n defaults: Map<string, string>\n overrides: Map<string, string>\n} {\n const defaults = new Map<string, string>()\n const overrides = new Map<string, string>()\n if (!xml) return { defaults, overrides }\n const document = parseXml(xml, 'DOCX content types')\n for (const node of elements(document, 'Default')) {\n const ext = (node.getAttribute('Extension') ?? '').toLowerCase()\n const type = node.getAttribute('ContentType') ?? ''\n if (ext && type) defaults.set(ext, type)\n }\n for (const node of elements(document, 'Override')) {\n const path = (node.getAttribute('PartName') ?? '').replace(/^\\/+/, '')\n const type = node.getAttribute('ContentType') ?? ''\n if (path && type) overrides.set(path, type)\n }\n return { defaults, overrides }\n}\n\nfunction contentTypeFor(path: string, contentTypes: ReturnType<typeof readContentTypes>): string {\n const ext = extension(path)\n return (\n contentTypes.overrides.get(path) ??\n contentTypes.defaults.get(ext) ??\n MIME_BY_EXTENSION[ext] ??\n 'application/octet-stream'\n )\n}\n\nfunction olePackageKind(programId: string): OlePackageKind | null {\n const normalized = programId.toLowerCase()\n if (normalized === 'powerpoint.show.12' || normalized === 'powerpoint.slide.12') {\n return {\n extension: 'pptx',\n contentType: MIME_BY_EXTENSION.pptx,\n label: 'PowerPoint 演示文稿',\n }\n }\n if (normalized === 'excel.sheet.12') {\n return { extension: 'xlsx', contentType: MIME_BY_EXTENSION.xlsx, label: 'Excel 工作簿' }\n }\n if (normalized === 'word.document.12') {\n return { extension: 'docx', contentType: MIME_BY_EXTENSION.docx, label: 'Word 文档' }\n }\n return null\n}\n\nfunction ancestorByLocalName(element: Element, localName: string): Element | null {\n let current: Element | null = element\n while (current) {\n if (current.localName === localName) return current\n current = current.parentElement\n }\n return null\n}\n\nfunction vmlLengthInPoints(style: string, property: 'width' | 'height'): number | undefined {\n const match = style.match(\n new RegExp(`(?:^|;)\\\\s*${property}\\\\s*:\\\\s*([0-9]+(?:\\\\.[0-9]+)?)(pt|px|in|cm|mm)`, 'i'),\n )\n if (!match) return undefined\n const value = Number(match[1])\n const unit = match[2]?.toLowerCase()\n const points =\n unit === 'pt'\n ? value\n : unit === 'px'\n ? value * 0.75\n : unit === 'in'\n ? value * 72\n : unit === 'cm'\n ? (value * 72) / 2.54\n : (value * 72) / 25.4\n return Number.isFinite(points) && points > 0 && points <= 2000 ? points : undefined\n}\n\nfunction embeddedPreview(\n object: Element,\n relationships: ReadonlyMap<string, Relationship>,\n byPath: ReadonlyMap<string, ZipEntry>,\n contentTypes: ReturnType<typeof readContentTypes>,\n): DocxEmbeddedPreview | undefined {\n const wrapper = ancestorByLocalName(object, 'object')\n if (!wrapper) return undefined\n const imageData = elements(wrapper, 'imagedata')[0]\n const relationshipId = imageData ? attrByLocalName(imageData, 'id') : ''\n const relationship = relationships.get(relationshipId)\n if (\n !relationshipId ||\n !relationship ||\n relationship.external ||\n !relationship.type.toLowerCase().endsWith('/image')\n )\n return undefined\n const path = resolvePartTarget(DOCUMENT_PART, relationship.target)\n const entry = byPath.get(path)\n if (!path || !entry) return undefined\n const shape = elements(wrapper, 'shape')[0]\n const style = shape?.getAttribute('style') ?? ''\n return {\n path,\n size: entry.size,\n contentType: contentTypeFor(path, contentTypes),\n widthPt: vmlLengthInPoints(style, 'width'),\n heightPt: vmlLengthInPoints(style, 'height'),\n entry,\n }\n}\n\nfunction sniffImageContentType(bytes: Uint8Array, declared: string): string {\n if (\n bytes.length >= 8 &&\n bytes[0] === 0x89 &&\n bytes[1] === 0x50 &&\n bytes[2] === 0x4e &&\n bytes[3] === 0x47\n )\n return 'image/png'\n if (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff)\n return 'image/jpeg'\n if (\n bytes.length >= 6 &&\n bytes[0] === 0x47 &&\n bytes[1] === 0x49 &&\n bytes[2] === 0x46 &&\n bytes[3] === 0x38\n )\n return 'image/gif'\n if (bytes.length >= 2 && bytes[0] === 0x42 && bytes[1] === 0x4d) return 'image/bmp'\n if (\n bytes.length >= 12 &&\n bytes[0] === 0x52 &&\n bytes[1] === 0x49 &&\n bytes[2] === 0x46 &&\n bytes[3] === 0x46 &&\n bytes[8] === 0x57 &&\n bytes[9] === 0x45 &&\n bytes[10] === 0x42 &&\n bytes[11] === 0x50\n )\n return 'image/webp'\n return declared.startsWith('image/') ? declared : 'application/octet-stream'\n}\n\n/**\n * Discover embedded package relationships (`w:object` / `o:OLEObject`) in document order.\n * `docx-preview` currently drops these nodes, so the caller can expose the original bytes separately.\n */\nexport async function readDocxEmbeddedFiles(buffer: ArrayBuffer): Promise<DocxEmbeddedFile[]> {\n const entries = readZip(buffer)\n const byPath = new Map(\n entries.filter((entry) => !entry.isDir).map((entry) => [entry.path, entry]),\n )\n const documentEntry = byPath.get(DOCUMENT_PART)\n const relationshipsEntry = byPath.get(DOCUMENT_RELS_PART)\n if (!documentEntry || !relationshipsEntry) return []\n\n const [documentXml, relationshipsXml, contentTypesXml] = await Promise.all([\n extractZipEntry(buffer, documentEntry).then(decodeEntry),\n extractZipEntry(buffer, relationshipsEntry).then(decodeEntry),\n byPath.has(CONTENT_TYPES_PART)\n ? extractZipEntry(buffer, byPath.get(CONTENT_TYPES_PART) as ZipEntry).then(decodeEntry)\n : Promise.resolve(null),\n ])\n const document = parseXml(documentXml, 'DOCX document')\n const relationships = readRelationships(relationshipsXml)\n const contentTypes = readContentTypes(contentTypesXml)\n const result: DocxEmbeddedFile[] = []\n const seenPaths = new Set<string>()\n let previousText = ''\n\n for (const paragraph of elements(document, 'p')) {\n const currentText = paragraphText(paragraph)\n for (const object of elements(paragraph, 'OLEObject')) {\n const relationshipId = attrByLocalName(object, 'id')\n const relationship = relationships.get(relationshipId)\n if (!relationshipId || !relationship || relationship.external) continue\n const relationshipType = relationship.type.toLowerCase()\n const directPackage = relationshipType.endsWith('/package')\n const oleObject = relationshipType.endsWith('/oleobject')\n if (!directPackage && !oleObject) continue\n const programId = object.getAttribute('ProgID') ?? attrByLocalName(object, 'ProgID')\n const oleKind = oleObject ? olePackageKind(programId) : null\n if (oleObject && !oleKind) continue\n\n const path = resolvePartTarget(DOCUMENT_PART, relationship.target)\n const entry = byPath.get(path)\n if (!path || !entry || seenPaths.has(path)) continue\n seenPaths.add(path)\n\n const sourceName = basename(path)\n const originalName = oleKind ? `${oleKind.label}.${oleKind.extension}` : sourceName\n const label = cleanLabel(currentText || (oleKind ? oleKind.label : previousText))\n result.push({\n id: relationshipId,\n label,\n name: friendlyName(label, originalName),\n originalName,\n path,\n size: entry.size,\n contentType: oleKind?.contentType ?? contentTypeFor(path, contentTypes),\n storage: oleKind ? 'ole-package' : 'package',\n preview: embeddedPreview(object, relationships, byPath, contentTypes),\n entry,\n })\n }\n if (currentText) previousText = currentText\n }\n return result\n}\n\n/** Extract one discovered embedded package on demand, with the same per-file ceiling as server exports. */\nexport async function extractDocxEmbeddedFile(\n buffer: ArrayBuffer,\n file: DocxEmbeddedFile,\n): Promise<Uint8Array> {\n if (file.size > MAX_EMBEDDED_FILE_BYTES) {\n throw new Error(\n `内嵌文件超过 ${MAX_EMBEDDED_FILE_BYTES / 1024 / 1024} MiB,已拒绝在浏览器中展开`,\n )\n }\n const bytes = await extractZipEntry(buffer, file.entry)\n return file.storage === 'ole-package' ? extractOlePackage(bytes, MAX_EMBEDDED_FILE_BYTES) : bytes\n}\n\n/** Extract the inert Word/WPS snapshot paired with an embedded object. */\nexport async function extractDocxEmbeddedPreview(\n buffer: ArrayBuffer,\n file: DocxEmbeddedFile,\n): Promise<ExtractedDocxPreview | null> {\n const preview = file.preview\n if (!preview) return null\n if (preview.size > MAX_PREVIEW_IMAGE_BYTES) {\n throw new Error(`内嵌预览图超过 ${MAX_PREVIEW_IMAGE_BYTES / 1024 / 1024} MiB,已跳过正文预览`)\n }\n const bytes = await extractZipEntry(buffer, preview.entry)\n const contentType = sniffImageContentType(bytes, preview.contentType)\n if (!['image/png', 'image/jpeg', 'image/gif', 'image/bmp', 'image/webp'].includes(contentType)) {\n return null\n }\n return { bytes, contentType }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapt simple black-on-white Word pages once after rendering. CSS variables handle subsequent theme
|
|
3
|
+
* changes without re-parsing or replacing the document. Pages with authored colors, shading or complex
|
|
4
|
+
* drawings stay intact: a partial override could make their foreground/background combinations unreadable.
|
|
5
|
+
*/
|
|
6
|
+
export declare function applyDocxReadingTheme(root: HTMLElement): void;
|