@office-open/core 0.10.11 → 0.10.13
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 +10 -9
- package/dist/chart/index.d.mts +1 -1
- package/dist/chart/index.mjs +1 -1
- package/dist/{chart-DwE8FCFk.mjs → chart-DOO1st0K.mjs} +3 -1
- package/dist/chart-DOO1st0K.mjs.map +1 -0
- package/dist/converters-C_Y2QX1w.mjs +1789 -0
- package/dist/converters-C_Y2QX1w.mjs.map +1 -0
- package/dist/data-type-CZMKVaaT.d.mts +10 -0
- package/dist/data-type-CZMKVaaT.d.mts.map +1 -0
- package/dist/descriptor/index.d.mts +2 -2
- package/dist/descriptor/index.mjs +2 -2
- package/dist/{descriptor-DAER86Rt.mjs → descriptor-BjNkn5jo.mjs} +3 -33
- package/dist/descriptor-BjNkn5jo.mjs.map +1 -0
- package/dist/drawingml/index.d.mts +2 -2
- package/dist/drawingml/index.mjs +2 -2
- package/dist/{src-zBJYrHrv.mjs → drawingml-sj1J3lb0.mjs} +22 -3728
- package/dist/drawingml-sj1J3lb0.mjs.map +1 -0
- package/dist/index-BVK2dv6C.d.mts +63 -0
- package/dist/index-BVK2dv6C.d.mts.map +1 -0
- package/dist/index-BfHfKuKy.d.mts +275 -0
- package/dist/index-BfHfKuKy.d.mts.map +1 -0
- package/dist/index-CE53SwpZ.d.mts +1463 -0
- package/dist/index-CE53SwpZ.d.mts.map +1 -0
- package/dist/{index-3STznXzZ.d.mts → index-D6VE-MnJ.d.mts} +3 -15
- package/dist/index-D6VE-MnJ.d.mts.map +1 -0
- package/dist/{index-CN0YjNSx.d.mts → index-DDyd2ErZ.d.mts} +3 -15
- package/dist/index-DDyd2ErZ.d.mts.map +1 -0
- package/dist/{index-CsQP7Cl4.d.mts → index-DlPA26cw.d.mts} +2 -47
- package/dist/index-DlPA26cw.d.mts.map +1 -0
- package/dist/index-k8WKLXnA.d.mts +150 -0
- package/dist/index-k8WKLXnA.d.mts.map +1 -0
- package/dist/index.d.mts +650 -8
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1271 -7
- package/dist/index.mjs.map +1 -0
- package/dist/output-D_G_JR_8.d.mts +24 -0
- package/dist/output-D_G_JR_8.d.mts.map +1 -0
- package/dist/patch/index.d.mts +2 -2
- package/dist/patch/index.mjs +1 -1
- package/dist/{patch-DEPPafeB.mjs → patch-BmRfghOo.mjs} +67 -86
- package/dist/patch-BmRfghOo.mjs.map +1 -0
- package/dist/smartart/index.d.mts +1 -1
- package/dist/smartart/index.mjs +1 -1
- package/dist/{smartart-DCY-Vdv7.mjs → smartart-B7ukZ-Tw.mjs} +5 -3
- package/dist/smartart-B7ukZ-Tw.mjs.map +1 -0
- package/dist/theme/index.d.mts +1 -1
- package/dist/theme-CiNzdl-9.mjs +2 -0
- package/dist/theme-CiNzdl-9.mjs.map +1 -0
- package/dist/util/index.d.mts +4 -0
- package/dist/util/index.mjs +3 -0
- package/dist/util-Tq9PSjK0.mjs +1113 -0
- package/dist/util-Tq9PSjK0.mjs.map +1 -0
- package/dist/values-8di32lIe.d.mts +56 -0
- package/dist/values-8di32lIe.d.mts.map +1 -0
- package/package.json +7 -6
- package/dist/index-CHGFwCCQ.d.mts +0 -266
- package/dist/index-CpNwAcem.d.mts +0 -169
- package/dist/index-DdXU1_sq.d.mts +0 -5029
- package/dist/util/values.d.mts +0 -2
- package/dist/util/values.mjs +0 -2
- package/dist/values-CVIZcTRw.mjs +0 -371
- package/dist/values-DQfI1FSg.d.mts +0 -426
|
@@ -0,0 +1,1113 @@
|
|
|
1
|
+
import { U as encodeBase64, W as Buffer } from "./converters-C_Y2QX1w.mjs";
|
|
2
|
+
import { AsyncZipDeflate, Zip, ZipPassThrough, strFromU8, strFromU8 as strFromU8$1, unzipSync as unzipSync$1, zip, zipSync } from "fflate";
|
|
3
|
+
import { sha512 } from "@noble/hashes/sha2.js";
|
|
4
|
+
//#region src/opc/output.ts
|
|
5
|
+
/**
|
|
6
|
+
* Output type definitions for OOXML document export.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
const OoxmlMimeType = {
|
|
11
|
+
DOCX: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
12
|
+
PPTX: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
13
|
+
XLSX: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
14
|
+
};
|
|
15
|
+
const convertOutput = (data, type, mimeType = OoxmlMimeType.DOCX) => {
|
|
16
|
+
switch (type) {
|
|
17
|
+
case "nodebuffer": return Buffer.from(data.buffer, data.byteOffset, data.byteLength);
|
|
18
|
+
case "blob": return new Blob([data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength)], { type: mimeType });
|
|
19
|
+
case "arraybuffer": return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
20
|
+
case "uint8array": return data;
|
|
21
|
+
case "base64": return encodeBase64(data);
|
|
22
|
+
case "string":
|
|
23
|
+
case "text":
|
|
24
|
+
case "binarystring": return strFromU8(data, true);
|
|
25
|
+
case "array": return [...data];
|
|
26
|
+
default: return data;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/opc/zip-native.ts
|
|
31
|
+
const CRC_TABLE = new Int32Array(256);
|
|
32
|
+
for (let i = 0; i < 256; i++) {
|
|
33
|
+
let c = i;
|
|
34
|
+
for (let k = 0; k < 8; k++) c = c & 1 ? c >>> 1 ^ -306674912 : c >>> 1;
|
|
35
|
+
CRC_TABLE[i] = c;
|
|
36
|
+
}
|
|
37
|
+
function computeCrc32(data) {
|
|
38
|
+
let crc = -1;
|
|
39
|
+
for (const byte of data) crc = CRC_TABLE[(crc ^ byte) & 255] ^ crc >>> 8;
|
|
40
|
+
return ~crc;
|
|
41
|
+
}
|
|
42
|
+
let _nativeDeflate;
|
|
43
|
+
let _nativeDeflateAsync;
|
|
44
|
+
let _nativeInflate;
|
|
45
|
+
let _nativeCrc32;
|
|
46
|
+
try {
|
|
47
|
+
const zlib = await import("node:zlib");
|
|
48
|
+
if (typeof zlib.deflateRawSync !== "function") throw new Error("no native deflate");
|
|
49
|
+
_nativeDeflate = (data, level) => zlib.deflateRawSync(data, { level });
|
|
50
|
+
_nativeDeflateAsync = (data, level) => new Promise((resolve, reject) => zlib.deflateRaw(data, { level }, (err, result) => {
|
|
51
|
+
if (err) reject(err);
|
|
52
|
+
else resolve(result);
|
|
53
|
+
}));
|
|
54
|
+
_nativeCrc32 = typeof zlib.crc32 === "function" ? (data) => zlib.crc32(data) : computeCrc32;
|
|
55
|
+
if (typeof zlib.inflateRawSync === "function") _nativeInflate = (data) => zlib.inflateRawSync(data);
|
|
56
|
+
} catch {}
|
|
57
|
+
const hasNativeDeflate = () => _nativeDeflate !== void 0;
|
|
58
|
+
const hasNativeInflate = () => _nativeInflate !== void 0;
|
|
59
|
+
function wU16(b, o, v) {
|
|
60
|
+
b[o] = v & 255;
|
|
61
|
+
b[o + 1] = v >> 8 & 255;
|
|
62
|
+
}
|
|
63
|
+
function wU32(b, o, v) {
|
|
64
|
+
b[o] = v & 255;
|
|
65
|
+
b[o + 1] = v >> 8 & 255;
|
|
66
|
+
b[o + 2] = v >> 16 & 255;
|
|
67
|
+
b[o + 3] = v >> 24 & 255;
|
|
68
|
+
}
|
|
69
|
+
function resolveEntryData(raw, defaultLevel) {
|
|
70
|
+
if (Array.isArray(raw)) return {
|
|
71
|
+
data: raw[0],
|
|
72
|
+
level: raw[1].level ?? defaultLevel
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
data: raw,
|
|
76
|
+
level: defaultLevel
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function compressOne(data, level, deflate) {
|
|
80
|
+
if (level === 0) return {
|
|
81
|
+
compressed: data,
|
|
82
|
+
method: 0
|
|
83
|
+
};
|
|
84
|
+
const compressed = deflate(data, level);
|
|
85
|
+
if (compressed.length >= data.length) return {
|
|
86
|
+
compressed: data,
|
|
87
|
+
method: 0
|
|
88
|
+
};
|
|
89
|
+
return {
|
|
90
|
+
compressed,
|
|
91
|
+
method: 8
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
async function compressOneAsync(data, level, deflate) {
|
|
95
|
+
if (level === 0) return {
|
|
96
|
+
compressed: data,
|
|
97
|
+
method: 0
|
|
98
|
+
};
|
|
99
|
+
const compressed = await deflate(data, level);
|
|
100
|
+
if (compressed.length >= data.length) return {
|
|
101
|
+
compressed: data,
|
|
102
|
+
method: 0
|
|
103
|
+
};
|
|
104
|
+
return {
|
|
105
|
+
compressed,
|
|
106
|
+
method: 8
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function writeZipBuffer(entries) {
|
|
110
|
+
let totalSize = 0;
|
|
111
|
+
for (const e of entries) totalSize += 30 + e.filename.length + e.data.length;
|
|
112
|
+
const cdOffset = totalSize;
|
|
113
|
+
let cdSize = 0;
|
|
114
|
+
for (const e of entries) cdSize += 46 + e.filename.length;
|
|
115
|
+
totalSize += cdSize + 22;
|
|
116
|
+
const buf = new Uint8Array(totalSize);
|
|
117
|
+
let offset = 0;
|
|
118
|
+
for (const e of entries) {
|
|
119
|
+
e.localOffset = offset;
|
|
120
|
+
wU32(buf, offset, 67324752);
|
|
121
|
+
wU16(buf, offset + 4, 20);
|
|
122
|
+
wU16(buf, offset + 6, 0);
|
|
123
|
+
wU16(buf, offset + 8, e.method);
|
|
124
|
+
wU16(buf, offset + 10, 0);
|
|
125
|
+
wU16(buf, offset + 12, 0);
|
|
126
|
+
wU32(buf, offset + 14, e.crc);
|
|
127
|
+
wU32(buf, offset + 18, e.data.length);
|
|
128
|
+
wU32(buf, offset + 22, e.uncompressedSize);
|
|
129
|
+
wU16(buf, offset + 26, e.filename.length);
|
|
130
|
+
wU16(buf, offset + 28, 0);
|
|
131
|
+
offset += 30;
|
|
132
|
+
buf.set(e.filename, offset);
|
|
133
|
+
offset += e.filename.length;
|
|
134
|
+
buf.set(e.data, offset);
|
|
135
|
+
offset += e.data.length;
|
|
136
|
+
}
|
|
137
|
+
for (const e of entries) {
|
|
138
|
+
wU32(buf, offset, 33639248);
|
|
139
|
+
wU16(buf, offset + 4, 20);
|
|
140
|
+
wU16(buf, offset + 6, 20);
|
|
141
|
+
wU16(buf, offset + 8, 0);
|
|
142
|
+
wU16(buf, offset + 10, e.method);
|
|
143
|
+
wU16(buf, offset + 12, 0);
|
|
144
|
+
wU16(buf, offset + 14, 0);
|
|
145
|
+
wU32(buf, offset + 16, e.crc);
|
|
146
|
+
wU32(buf, offset + 20, e.data.length);
|
|
147
|
+
wU32(buf, offset + 24, e.uncompressedSize);
|
|
148
|
+
wU16(buf, offset + 28, e.filename.length);
|
|
149
|
+
wU16(buf, offset + 30, 0);
|
|
150
|
+
wU16(buf, offset + 32, 0);
|
|
151
|
+
wU16(buf, offset + 34, 0);
|
|
152
|
+
wU16(buf, offset + 36, 0);
|
|
153
|
+
wU32(buf, offset + 38, 0);
|
|
154
|
+
wU32(buf, offset + 42, e.localOffset);
|
|
155
|
+
offset += 46;
|
|
156
|
+
buf.set(e.filename, offset);
|
|
157
|
+
offset += e.filename.length;
|
|
158
|
+
}
|
|
159
|
+
wU32(buf, offset, 101010256);
|
|
160
|
+
wU16(buf, offset + 4, 0);
|
|
161
|
+
wU16(buf, offset + 6, 0);
|
|
162
|
+
wU16(buf, offset + 8, entries.length);
|
|
163
|
+
wU16(buf, offset + 10, entries.length);
|
|
164
|
+
wU32(buf, offset + 12, cdSize);
|
|
165
|
+
wU32(buf, offset + 16, cdOffset);
|
|
166
|
+
wU16(buf, offset + 20, 0);
|
|
167
|
+
return buf;
|
|
168
|
+
}
|
|
169
|
+
const textEncoder = new TextEncoder();
|
|
170
|
+
const textDecoder = new TextDecoder();
|
|
171
|
+
function nativeZip(files, level = 6) {
|
|
172
|
+
if (!_nativeDeflate) throw new Error("Native deflate not available");
|
|
173
|
+
const crc = _nativeCrc32 ?? computeCrc32;
|
|
174
|
+
const entries = [];
|
|
175
|
+
for (const key of Object.keys(files)) {
|
|
176
|
+
const raw = files[key];
|
|
177
|
+
if (raw === void 0) continue;
|
|
178
|
+
const { data, level: entryLevel } = resolveEntryData(raw, level);
|
|
179
|
+
const c = crc(data);
|
|
180
|
+
const { compressed, method } = compressOne(data, entryLevel, _nativeDeflate);
|
|
181
|
+
entries.push({
|
|
182
|
+
filename: textEncoder.encode(key),
|
|
183
|
+
data: compressed,
|
|
184
|
+
uncompressedSize: data.length,
|
|
185
|
+
crc: c,
|
|
186
|
+
method,
|
|
187
|
+
localOffset: 0
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return writeZipBuffer(entries);
|
|
191
|
+
}
|
|
192
|
+
async function nativeZipAsync(files, level = 6) {
|
|
193
|
+
if (!_nativeDeflateAsync) throw new Error("Native async deflate not available");
|
|
194
|
+
const crc = _nativeCrc32 ?? computeCrc32;
|
|
195
|
+
const entries = [];
|
|
196
|
+
for (const key of Object.keys(files)) {
|
|
197
|
+
const raw = files[key];
|
|
198
|
+
if (raw === void 0) continue;
|
|
199
|
+
const { data, level: entryLevel } = resolveEntryData(raw, level);
|
|
200
|
+
const c = crc(data);
|
|
201
|
+
const { compressed, method } = await compressOneAsync(data, entryLevel, _nativeDeflateAsync);
|
|
202
|
+
entries.push({
|
|
203
|
+
filename: textEncoder.encode(key),
|
|
204
|
+
data: compressed,
|
|
205
|
+
uncompressedSize: data.length,
|
|
206
|
+
crc: c,
|
|
207
|
+
method,
|
|
208
|
+
localOffset: 0
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
return writeZipBuffer(entries);
|
|
212
|
+
}
|
|
213
|
+
function rU16(b, o) {
|
|
214
|
+
return b[o] | b[o + 1] << 8;
|
|
215
|
+
}
|
|
216
|
+
function rU32(b, o) {
|
|
217
|
+
return (b[o] | b[o + 1] << 8 | b[o + 2] << 16 | b[o + 3] << 24) >>> 0;
|
|
218
|
+
}
|
|
219
|
+
const EOCD_MAGIC = 101010256;
|
|
220
|
+
const CD_MAGIC = 33639248;
|
|
221
|
+
/** Locate the End-of-Central-Directory record (within a 64 KB comment window). */
|
|
222
|
+
function findEocd(b) {
|
|
223
|
+
const min = Math.max(0, b.length - 65557);
|
|
224
|
+
for (let i = b.length - 22; i >= min; i--) if (rU32(b, i) === EOCD_MAGIC) return i;
|
|
225
|
+
throw new Error("ZIP EOCD record not found");
|
|
226
|
+
}
|
|
227
|
+
/** Walk the central directory and resolve each entry's payload offset. */
|
|
228
|
+
function readCentralDirectory(buf) {
|
|
229
|
+
const eocd = findEocd(buf);
|
|
230
|
+
const count = rU16(buf, eocd + 10);
|
|
231
|
+
const cdOffset = rU32(buf, eocd + 16);
|
|
232
|
+
const entries = [];
|
|
233
|
+
let p = cdOffset;
|
|
234
|
+
for (let i = 0; i < count; i++) {
|
|
235
|
+
if (rU32(buf, p) !== CD_MAGIC) throw new Error(`ZIP central directory corrupt at offset ${p}`);
|
|
236
|
+
const method = rU16(buf, p + 10);
|
|
237
|
+
const crc = rU32(buf, p + 16);
|
|
238
|
+
const compSize = rU32(buf, p + 20);
|
|
239
|
+
const nameLen = rU16(buf, p + 28);
|
|
240
|
+
const extraLen = rU16(buf, p + 30);
|
|
241
|
+
const cmtLen = rU16(buf, p + 32);
|
|
242
|
+
const localOff = rU32(buf, p + 42);
|
|
243
|
+
const name = textDecoder.decode(buf.subarray(p + 46, p + 46 + nameLen));
|
|
244
|
+
const lNameLen = rU16(buf, localOff + 26);
|
|
245
|
+
const lExtraLen = rU16(buf, localOff + 28);
|
|
246
|
+
entries.push({
|
|
247
|
+
name,
|
|
248
|
+
method,
|
|
249
|
+
compSize,
|
|
250
|
+
crc,
|
|
251
|
+
dataStart: localOff + 30 + lNameLen + lExtraLen
|
|
252
|
+
});
|
|
253
|
+
p += 46 + nameLen + extraLen + cmtLen;
|
|
254
|
+
}
|
|
255
|
+
return entries;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Decompress a ZIP archive via `node:zlib` `inflateRawSync`, mirroring fflate's
|
|
259
|
+
* `unzipSync` signature and CRC-32 integrity check. Node/Bun only — callers
|
|
260
|
+
* gate on {@link hasNativeInflate} and fall back to fflate `unzipSync` elsewhere.
|
|
261
|
+
* Measured ~2x faster than fflate on large OOXML packages.
|
|
262
|
+
*/
|
|
263
|
+
function nativeUnzip(buf) {
|
|
264
|
+
if (!_nativeInflate) throw new Error("Native inflate not available");
|
|
265
|
+
const inflate = _nativeInflate;
|
|
266
|
+
const crc = _nativeCrc32 ?? computeCrc32;
|
|
267
|
+
const out = {};
|
|
268
|
+
for (const e of readCentralDirectory(buf)) {
|
|
269
|
+
const raw = buf.subarray(e.dataStart, e.dataStart + e.compSize);
|
|
270
|
+
const dec = e.method === 0 ? raw.slice() : inflate(raw);
|
|
271
|
+
if (crc(dec) >>> 0 !== e.crc) throw new Error(`ZIP CRC-32 mismatch: ${e.name}`);
|
|
272
|
+
out[e.name] = dec;
|
|
273
|
+
}
|
|
274
|
+
return out;
|
|
275
|
+
}
|
|
276
|
+
//#endregion
|
|
277
|
+
//#region src/opc/packer.ts
|
|
278
|
+
/**
|
|
279
|
+
* Shared packer utilities for OOXML document generation.
|
|
280
|
+
*
|
|
281
|
+
* @module
|
|
282
|
+
*/
|
|
283
|
+
/** Default DEFLATE level for XML entries (SuperFast, matching MS Office). */
|
|
284
|
+
const ZIP_DEFLATE_LEVEL = 1;
|
|
285
|
+
/** Level for already-compressed media — STORE (no compression). */
|
|
286
|
+
const ZIP_STORED_LEVEL = 0;
|
|
287
|
+
/**
|
|
288
|
+
* Media formats already compressed internally (DEFLATE for PNG, DCT for JPEG,
|
|
289
|
+
* LZW for GIF). Re-compressing via zip DEFLATE wastes CPU and often inflates
|
|
290
|
+
* the data, so MS Office STORE-s these (CompressionOption.NotCompressed → zip
|
|
291
|
+
* method 0). Everything else (EMF/WMF/BMP/TIFF/SVG/…) is compressible → DEFLATE.
|
|
292
|
+
*/
|
|
293
|
+
const PRECOMPRESSED_MEDIA_EXT = new Set([
|
|
294
|
+
"jpg",
|
|
295
|
+
"jpeg",
|
|
296
|
+
"png",
|
|
297
|
+
"gif"
|
|
298
|
+
]);
|
|
299
|
+
/**
|
|
300
|
+
* Resolve the ZIP level for a media entry by file-name extension, matching MS
|
|
301
|
+
* Office: already-compressed raster formats → STORE (0), everything else →
|
|
302
|
+
* DEFLATE (`mediaLevel`, default Normal). A `compression.media` override
|
|
303
|
+
* therefore applies only to compressible formats, never forcing DEFLATE onto
|
|
304
|
+
* pre-compressed assets.
|
|
305
|
+
*/
|
|
306
|
+
const levelForMediaName = (fileName, mediaLevel) => {
|
|
307
|
+
const dot = fileName.lastIndexOf(".");
|
|
308
|
+
const ext = dot < 0 ? "" : fileName.slice(dot + 1).toLowerCase();
|
|
309
|
+
return PRECOMPRESSED_MEDIA_EXT.has(ext) ? 0 : mediaLevel;
|
|
310
|
+
};
|
|
311
|
+
/**
|
|
312
|
+
* Asynchronously compress files and convert to the requested output format.
|
|
313
|
+
*
|
|
314
|
+
* Uses fflate Web Workers for non-blocking DEFLATE compression.
|
|
315
|
+
* XML entries use DEFLATE level 1 (SuperFast) by default. Media entries are
|
|
316
|
+
* split by type, matching MS Office: already-compressed formats (PNG/JPEG/GIF)
|
|
317
|
+
* are STOREd, everything else uses the `media` level (default Normal).
|
|
318
|
+
* Set `{ media: ZIP_STORED_LEVEL }` to STORE all compressible media too.
|
|
319
|
+
*/
|
|
320
|
+
const zipAndConvert = async (files, type, mimeType, level = 1) => {
|
|
321
|
+
return convertOutput(hasNativeDeflate() ? await nativeZipAsync(files, level) : await new Promise((resolve, reject) => {
|
|
322
|
+
zip(files, {
|
|
323
|
+
level,
|
|
324
|
+
consume: true
|
|
325
|
+
}, (err, data) => {
|
|
326
|
+
if (err) reject(err);
|
|
327
|
+
else resolve(data);
|
|
328
|
+
});
|
|
329
|
+
}), type, mimeType);
|
|
330
|
+
};
|
|
331
|
+
/**
|
|
332
|
+
* Synchronously compress files and convert to the requested output format.
|
|
333
|
+
*
|
|
334
|
+
* Uses synchronous DEFLATE compression for maximum throughput.
|
|
335
|
+
* Blocks the event loop — prefer {@link zipAndConvert} in server contexts.
|
|
336
|
+
*/
|
|
337
|
+
const zipSyncAndConvert = (files, type, mimeType, level = 1) => {
|
|
338
|
+
return convertOutput(hasNativeDeflate() ? nativeZip(files, level) : zipSync(files, { level }), type, mimeType);
|
|
339
|
+
};
|
|
340
|
+
/**
|
|
341
|
+
* Create a `ReadableStream<Uint8Array>` from compressed file entries.
|
|
342
|
+
*
|
|
343
|
+
* Uses fflate's `AsyncZipDeflate` for non-blocking DEFLATE compression.
|
|
344
|
+
* `STORED` entries (media) pass through synchronously.
|
|
345
|
+
* Works in both Node.js and browsers (Web Streams API).
|
|
346
|
+
*/
|
|
347
|
+
const createZipStream = (files, defaultLevel = 1) => {
|
|
348
|
+
return new ReadableStream({ start(controller) {
|
|
349
|
+
try {
|
|
350
|
+
const zip = new Zip((err, chunk, _final) => {
|
|
351
|
+
if (err) {
|
|
352
|
+
controller.error(err);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
controller.enqueue(chunk);
|
|
356
|
+
if (_final) controller.close();
|
|
357
|
+
});
|
|
358
|
+
for (const [name, data] of Object.entries(files)) {
|
|
359
|
+
const raw = Array.isArray(data) ? data[0] : data;
|
|
360
|
+
const level = Array.isArray(data) ? data[1].level ?? defaultLevel : defaultLevel;
|
|
361
|
+
const entry = level === 0 ? new ZipPassThrough(name) : new AsyncZipDeflate(name, { level });
|
|
362
|
+
zip.add(entry);
|
|
363
|
+
entry.push(raw, true);
|
|
364
|
+
}
|
|
365
|
+
zip.end();
|
|
366
|
+
} catch (err) {
|
|
367
|
+
controller.error(err instanceof Error ? err : new Error(String(err)));
|
|
368
|
+
}
|
|
369
|
+
} });
|
|
370
|
+
};
|
|
371
|
+
/**
|
|
372
|
+
* Create a Packer object with all output format methods.
|
|
373
|
+
*
|
|
374
|
+
* Centralises the ZIP → convert pipeline and the streaming implementation
|
|
375
|
+
* so that each OOXML package only needs to provide a `compile` function and
|
|
376
|
+
* a MIME type.
|
|
377
|
+
*/
|
|
378
|
+
const createPacker = (options) => {
|
|
379
|
+
const { compile, mimeType } = options;
|
|
380
|
+
const pack = async (file, opts) => {
|
|
381
|
+
const type = opts?.type ?? "nodebuffer";
|
|
382
|
+
return zipAndConvert(compile(file, opts?.overrides ?? [], opts?.compression?.media ?? 6), type, mimeType, opts?.compression?.xml ?? 1);
|
|
383
|
+
};
|
|
384
|
+
const toBytes = (file, opts) => pack(file, {
|
|
385
|
+
...opts,
|
|
386
|
+
type: "uint8array"
|
|
387
|
+
});
|
|
388
|
+
const toString = (file, opts) => pack(file, {
|
|
389
|
+
...opts,
|
|
390
|
+
type: "string"
|
|
391
|
+
});
|
|
392
|
+
const toBuffer = (file, opts) => pack(file, {
|
|
393
|
+
...opts,
|
|
394
|
+
type: "nodebuffer"
|
|
395
|
+
});
|
|
396
|
+
const toBase64 = (file, opts) => pack(file, {
|
|
397
|
+
...opts,
|
|
398
|
+
type: "base64"
|
|
399
|
+
});
|
|
400
|
+
const toBlob = (file, opts) => pack(file, {
|
|
401
|
+
...opts,
|
|
402
|
+
type: "blob"
|
|
403
|
+
});
|
|
404
|
+
const toArrayBuffer = (file, opts) => pack(file, {
|
|
405
|
+
...opts,
|
|
406
|
+
type: "arraybuffer"
|
|
407
|
+
});
|
|
408
|
+
const packSync = (file, opts) => {
|
|
409
|
+
const type = opts?.type ?? "nodebuffer";
|
|
410
|
+
return zipSyncAndConvert(compile(file, opts?.overrides ?? [], opts?.compression?.media ?? 6), type, mimeType, opts?.compression?.xml ?? 1);
|
|
411
|
+
};
|
|
412
|
+
const toBytesSync = (file, opts) => packSync(file, {
|
|
413
|
+
...opts,
|
|
414
|
+
type: "uint8array"
|
|
415
|
+
});
|
|
416
|
+
const toStringSync = (file, opts) => packSync(file, {
|
|
417
|
+
...opts,
|
|
418
|
+
type: "string"
|
|
419
|
+
});
|
|
420
|
+
const toBufferSync = (file, opts) => packSync(file, {
|
|
421
|
+
...opts,
|
|
422
|
+
type: "nodebuffer"
|
|
423
|
+
});
|
|
424
|
+
const toBase64Sync = (file, opts) => packSync(file, {
|
|
425
|
+
...opts,
|
|
426
|
+
type: "base64"
|
|
427
|
+
});
|
|
428
|
+
const toBlobSync = (file, opts) => packSync(file, {
|
|
429
|
+
...opts,
|
|
430
|
+
type: "blob"
|
|
431
|
+
});
|
|
432
|
+
const toArrayBufferSync = (file, opts) => packSync(file, {
|
|
433
|
+
...opts,
|
|
434
|
+
type: "arraybuffer"
|
|
435
|
+
});
|
|
436
|
+
const toStream = (file, opts) => {
|
|
437
|
+
const mediaLevel = opts?.compression?.media ?? 6;
|
|
438
|
+
let files;
|
|
439
|
+
try {
|
|
440
|
+
files = compile(file, opts?.overrides ?? [], mediaLevel);
|
|
441
|
+
} catch (err) {
|
|
442
|
+
return new ReadableStream({ start(controller) {
|
|
443
|
+
controller.error(err instanceof Error ? err : new Error(String(err)));
|
|
444
|
+
} });
|
|
445
|
+
}
|
|
446
|
+
return createZipStream(files, opts?.compression?.xml ?? 1);
|
|
447
|
+
};
|
|
448
|
+
return {
|
|
449
|
+
compile,
|
|
450
|
+
pack,
|
|
451
|
+
packSync,
|
|
452
|
+
toBytes,
|
|
453
|
+
toBytesSync,
|
|
454
|
+
toString,
|
|
455
|
+
toStringSync,
|
|
456
|
+
toBuffer,
|
|
457
|
+
toBufferSync,
|
|
458
|
+
toBase64,
|
|
459
|
+
toBase64Sync,
|
|
460
|
+
toBlob,
|
|
461
|
+
toBlobSync,
|
|
462
|
+
toArrayBuffer,
|
|
463
|
+
toArrayBufferSync,
|
|
464
|
+
toStream
|
|
465
|
+
};
|
|
466
|
+
};
|
|
467
|
+
//#endregion
|
|
468
|
+
//#region src/util/compile.ts
|
|
469
|
+
/**
|
|
470
|
+
* Shared compiler utilities for OOXML document generation.
|
|
471
|
+
*
|
|
472
|
+
* @module
|
|
473
|
+
*/
|
|
474
|
+
/** Reusable TextEncoder instance (stateless, safe to share). */
|
|
475
|
+
const encoder = new TextEncoder();
|
|
476
|
+
/**
|
|
477
|
+
* Convert a mapping of XML files + overrides + media into a Zippable structure.
|
|
478
|
+
*
|
|
479
|
+
* Centralises the mapping→Zippable conversion shared by all three compilers.
|
|
480
|
+
*/
|
|
481
|
+
function compileMapping(mapping, overrides, media, mediaLevel = 0) {
|
|
482
|
+
const files = {};
|
|
483
|
+
for (const entry of Object.values(mapping)) files[entry.path] = encoder.encode(entry.data);
|
|
484
|
+
if (overrides) for (const o of overrides) files[o.path] = typeof o.data === "string" ? encoder.encode(o.data) : o.data;
|
|
485
|
+
if (media) for (const m of media) files[m.path] = [m.data, { level: levelForMediaName(m.path, mediaLevel) }];
|
|
486
|
+
return files;
|
|
487
|
+
}
|
|
488
|
+
//#endregion
|
|
489
|
+
//#region src/util/crypto.ts
|
|
490
|
+
/**
|
|
491
|
+
* Password hashing utilities for OOXML document protection.
|
|
492
|
+
*
|
|
493
|
+
* Implements ECMA-376 Agile Encryption password derivation.
|
|
494
|
+
* Passwords are encoded as UTF-16LE per the OOXML specification.
|
|
495
|
+
*
|
|
496
|
+
* Uses @noble/hashes for SHA-512 — audited, cross-platform,
|
|
497
|
+
* and comparable in speed to node:crypto for this workload.
|
|
498
|
+
*
|
|
499
|
+
* @module
|
|
500
|
+
*/
|
|
501
|
+
function utf16leEncode(str) {
|
|
502
|
+
const bytes = new Uint8Array(str.length * 2);
|
|
503
|
+
for (let i = 0; i < str.length; i++) {
|
|
504
|
+
const code = str.charCodeAt(i);
|
|
505
|
+
bytes[i * 2] = code & 255;
|
|
506
|
+
bytes[i * 2 + 1] = code >> 8 & 255;
|
|
507
|
+
}
|
|
508
|
+
return bytes;
|
|
509
|
+
}
|
|
510
|
+
function randomBytes(length) {
|
|
511
|
+
const bytes = new Uint8Array(length);
|
|
512
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) crypto.getRandomValues(bytes);
|
|
513
|
+
else for (let i = 0; i < length; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
514
|
+
return bytes;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Computes the ECMA-376 Agile Encryption hash for a password.
|
|
518
|
+
*
|
|
519
|
+
* Algorithm: SHA-512(salt + UTF-16LE(password)), then iterate
|
|
520
|
+
* SHA-512(hash + i.toUint32LE()) spinCount times.
|
|
521
|
+
*/
|
|
522
|
+
function hashPasswordAgile(password, salt, spinCount) {
|
|
523
|
+
const pwBytes = utf16leEncode(password);
|
|
524
|
+
const initial = new Uint8Array(salt.length + pwBytes.length);
|
|
525
|
+
initial.set(salt, 0);
|
|
526
|
+
initial.set(pwBytes, salt.length);
|
|
527
|
+
let h = sha512(initial);
|
|
528
|
+
for (let i = 0; i < spinCount; i++) {
|
|
529
|
+
const buf = new Uint8Array(h.length + 4);
|
|
530
|
+
buf.set(h, 0);
|
|
531
|
+
buf[h.length] = i & 255;
|
|
532
|
+
buf[h.length + 1] = i >> 8 & 255;
|
|
533
|
+
buf[h.length + 2] = i >> 16 & 255;
|
|
534
|
+
buf[h.length + 3] = i >> 24 & 255;
|
|
535
|
+
h = sha512(buf);
|
|
536
|
+
}
|
|
537
|
+
return encodeBase64(new Uint8Array(h));
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Derives a password hash for OOXML document protection.
|
|
541
|
+
*
|
|
542
|
+
* Returns base64-encoded hashValue, saltValue, spinCount, and algorithmName
|
|
543
|
+
* suitable for direct use in document protection options.
|
|
544
|
+
*/
|
|
545
|
+
function derivePasswordHash(password, spinCount = 1e5) {
|
|
546
|
+
const salt = randomBytes(16);
|
|
547
|
+
return {
|
|
548
|
+
hashValue: hashPasswordAgile(password, salt, spinCount),
|
|
549
|
+
saltValue: encodeBase64(salt),
|
|
550
|
+
spinCount,
|
|
551
|
+
algorithmName: "SHA-512"
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
//#endregion
|
|
555
|
+
//#region src/util/placeholder.ts
|
|
556
|
+
const formatId = (offset, index, style) => style === "rId" ? `rId${offset + index}` : `${offset + index}`;
|
|
557
|
+
/** Single-pass regex replacement for `{key}` placeholders using a Map lookup. */
|
|
558
|
+
const PLACEHOLDER_RE = /\{([^{}]+)\}/g;
|
|
559
|
+
function replacePlaceholders(xml, map) {
|
|
560
|
+
const parts = [];
|
|
561
|
+
let last = 0;
|
|
562
|
+
for (const m of xml.matchAll(PLACEHOLDER_RE)) {
|
|
563
|
+
const key = m[1];
|
|
564
|
+
if (key === void 0) continue;
|
|
565
|
+
const replacement = map.get(key);
|
|
566
|
+
if (replacement !== void 0) {
|
|
567
|
+
parts.push(xml.substring(last, m.index), replacement);
|
|
568
|
+
last = m.index + m[0].length;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
if (last === 0) return xml;
|
|
572
|
+
parts.push(xml.substring(last));
|
|
573
|
+
return parts.join("");
|
|
574
|
+
}
|
|
575
|
+
function hasPlaceholders(xml) {
|
|
576
|
+
return xml.includes("{");
|
|
577
|
+
}
|
|
578
|
+
function collectPlaceholderKeys(xml, prefix) {
|
|
579
|
+
const keys = /* @__PURE__ */ new Set();
|
|
580
|
+
const search = `{${prefix}`;
|
|
581
|
+
let pos = 0;
|
|
582
|
+
while ((pos = xml.indexOf(search, pos)) !== -1) {
|
|
583
|
+
const keyStart = pos + search.length;
|
|
584
|
+
const keyEnd = xml.indexOf("}", keyStart);
|
|
585
|
+
if (keyEnd === -1) break;
|
|
586
|
+
const key = xml.substring(keyStart, keyEnd);
|
|
587
|
+
if (key.length > 0) keys.add(key);
|
|
588
|
+
pos = keyEnd + 1;
|
|
589
|
+
}
|
|
590
|
+
return [...keys];
|
|
591
|
+
}
|
|
592
|
+
function replaceImagePlaceholders(xml, mediaData, offset, idFormat = "rId") {
|
|
593
|
+
if (mediaData.length === 0) return xml;
|
|
594
|
+
const map = /* @__PURE__ */ new Map();
|
|
595
|
+
for (const [i, item] of mediaData.entries()) map.set(item.fileName, formatId(offset, i, idFormat));
|
|
596
|
+
return replacePlaceholders(xml, map);
|
|
597
|
+
}
|
|
598
|
+
function getReferencedMedia(xml, mediaArray) {
|
|
599
|
+
return mediaArray.filter((image) => xml.includes(`{${image.fileName}}`));
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Combined find + replace for image placeholders in a single pass.
|
|
603
|
+
*
|
|
604
|
+
* Scans XML for `{fileName}` placeholders, replaces them with formatted IDs,
|
|
605
|
+
* and returns the list of referenced media items for relationship registration.
|
|
606
|
+
* More efficient than calling getReferencedMedia() + replaceImagePlaceholders() separately.
|
|
607
|
+
*/
|
|
608
|
+
function findAndReplaceImagePlaceholders(xml, mediaArray, offset, idFormat = "rId") {
|
|
609
|
+
if (mediaArray.length === 0 || !hasPlaceholders(xml)) return {
|
|
610
|
+
xml,
|
|
611
|
+
referenced: []
|
|
612
|
+
};
|
|
613
|
+
const itemMap = /* @__PURE__ */ new Map();
|
|
614
|
+
for (const item of mediaArray) itemMap.set(item.fileName, item);
|
|
615
|
+
const referenced = [];
|
|
616
|
+
const replaceMap = /* @__PURE__ */ new Map();
|
|
617
|
+
const parts = [];
|
|
618
|
+
let last = 0;
|
|
619
|
+
for (const m of xml.matchAll(PLACEHOLDER_RE)) {
|
|
620
|
+
const key = m[1];
|
|
621
|
+
if (key === void 0) continue;
|
|
622
|
+
const item = itemMap.get(key);
|
|
623
|
+
if (item !== void 0) {
|
|
624
|
+
if (!replaceMap.has(key)) {
|
|
625
|
+
replaceMap.set(key, formatId(offset, referenced.length, idFormat));
|
|
626
|
+
referenced.push(item);
|
|
627
|
+
}
|
|
628
|
+
parts.push(xml.substring(last, m.index), replaceMap.get(key));
|
|
629
|
+
last = m.index + m[0].length;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
if (last === 0) return {
|
|
633
|
+
xml,
|
|
634
|
+
referenced: []
|
|
635
|
+
};
|
|
636
|
+
parts.push(xml.substring(last));
|
|
637
|
+
return {
|
|
638
|
+
xml: parts.join(""),
|
|
639
|
+
referenced
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Replace all placeholder types in a single pass.
|
|
644
|
+
*
|
|
645
|
+
* Combines image, chart, smartart, and arbitrary key-value replacements
|
|
646
|
+
* into one regex scan of the XML string. Used by compilers to avoid
|
|
647
|
+
* multiple full-string passes on large documents.
|
|
648
|
+
*/
|
|
649
|
+
function replaceAllPlaceholders(xml, entries) {
|
|
650
|
+
if (entries.length === 0 || !hasPlaceholders(xml)) return xml;
|
|
651
|
+
const map = /* @__PURE__ */ new Map();
|
|
652
|
+
for (const { prefix, key, value } of entries) map.set(prefix ? `${prefix}${key}` : key, value);
|
|
653
|
+
return replacePlaceholders(xml, map);
|
|
654
|
+
}
|
|
655
|
+
function replaceChartPlaceholders(xml, chartKeys, offset, idFormat = "rId") {
|
|
656
|
+
if (chartKeys.length === 0) return xml;
|
|
657
|
+
const map = /* @__PURE__ */ new Map();
|
|
658
|
+
for (let i = 0; i < chartKeys.length; i++) map.set(`chart:${chartKeys[i]}`, formatId(offset, i, idFormat));
|
|
659
|
+
return replacePlaceholders(xml, map);
|
|
660
|
+
}
|
|
661
|
+
function replaceSmartArtPlaceholders(xml, keys, dataOffset, idFormat = "rId") {
|
|
662
|
+
if (keys.length === 0) return xml;
|
|
663
|
+
const count = keys.length;
|
|
664
|
+
const loOffset = dataOffset + count;
|
|
665
|
+
const qsOffset = loOffset + count;
|
|
666
|
+
const csOffset = qsOffset + count;
|
|
667
|
+
const map = /* @__PURE__ */ new Map();
|
|
668
|
+
for (let i = 0; i < count; i++) {
|
|
669
|
+
map.set(`smartart:${keys[i]}`, formatId(dataOffset, i, idFormat));
|
|
670
|
+
map.set(`smartart-lo:${keys[i]}`, formatId(loOffset, i, idFormat));
|
|
671
|
+
map.set(`smartart-qs:${keys[i]}`, formatId(qsOffset, i, idFormat));
|
|
672
|
+
map.set(`smartart-cs:${keys[i]}`, formatId(csOffset, i, idFormat));
|
|
673
|
+
}
|
|
674
|
+
return replacePlaceholders(xml, map);
|
|
675
|
+
}
|
|
676
|
+
function addSmartArtRelationships(keys, addRel, baseOffset, globalStartIndex, options) {
|
|
677
|
+
const { pathPrefix, styleRelType } = options;
|
|
678
|
+
const count = keys.length;
|
|
679
|
+
const loOffset = baseOffset + count;
|
|
680
|
+
const qsOffset = loOffset + count;
|
|
681
|
+
const csOffset = qsOffset + count;
|
|
682
|
+
for (let i = 0; i < keys.length; i++) {
|
|
683
|
+
const gi = globalStartIndex + i;
|
|
684
|
+
addRel(baseOffset + i, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramData", `${pathPrefix}diagrams/data${gi + 1}.xml`);
|
|
685
|
+
addRel(loOffset + i, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramLayout", `${pathPrefix}diagrams/layout${gi + 1}.xml`);
|
|
686
|
+
addRel(qsOffset + i, styleRelType, `${pathPrefix}diagrams/quickStyle${gi + 1}.xml`);
|
|
687
|
+
addRel(csOffset + i, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramColors", `${pathPrefix}diagrams/colors${gi + 1}.xml`);
|
|
688
|
+
addRel(csOffset + count + i, "http://schemas.microsoft.com/office/2007/relationships/diagramDrawing", `${pathPrefix}diagrams/drawing${gi + 1}.xml`);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
/** Replace `{reference-instance}` placeholders with numId values. */
|
|
692
|
+
function replaceNumberingPlaceholders(xml, concreteNumberings) {
|
|
693
|
+
if (concreteNumberings.length === 0) return xml;
|
|
694
|
+
const map = /* @__PURE__ */ new Map();
|
|
695
|
+
for (const { reference, instance, numId } of concreteNumberings) map.set(`${reference}-${instance}`, numId.toString());
|
|
696
|
+
return replacePlaceholders(xml, map);
|
|
697
|
+
}
|
|
698
|
+
/** Replace `{media:fileName}` placeholders with relationship IDs. */
|
|
699
|
+
function replaceMediaPlaceholders(xml, mediaData, offset) {
|
|
700
|
+
if (mediaData.length === 0) return xml;
|
|
701
|
+
const map = /* @__PURE__ */ new Map();
|
|
702
|
+
for (const [i, item] of mediaData.entries()) map.set(`media:${item.fileName}`, formatId(offset, i, "rId"));
|
|
703
|
+
return replacePlaceholders(xml, map);
|
|
704
|
+
}
|
|
705
|
+
/** Replace `{video:fileName}` placeholders with relationship IDs. */
|
|
706
|
+
function replaceVideoPlaceholders(xml, mediaData, offset) {
|
|
707
|
+
if (mediaData.length === 0) return xml;
|
|
708
|
+
const map = /* @__PURE__ */ new Map();
|
|
709
|
+
for (const [i, item] of mediaData.entries()) map.set(`video:${item.fileName}`, formatId(offset, i, "rId"));
|
|
710
|
+
return replacePlaceholders(xml, map);
|
|
711
|
+
}
|
|
712
|
+
/** Collect media items referenced by `{media:...}` placeholders in XML. */
|
|
713
|
+
function getMediaRefs(xml, mediaArray) {
|
|
714
|
+
return collectPrefixedRefs(xml, "media:", mediaArray);
|
|
715
|
+
}
|
|
716
|
+
/** Collect media items referenced by `{video:...}` placeholders in XML. */
|
|
717
|
+
function getVideoRefs(xml, mediaArray) {
|
|
718
|
+
return collectPrefixedRefs(xml, "video:", mediaArray);
|
|
719
|
+
}
|
|
720
|
+
function collectPrefixedRefs(xml, prefix, mediaArray) {
|
|
721
|
+
const keys = /* @__PURE__ */ new Set();
|
|
722
|
+
const search = `{${prefix}`;
|
|
723
|
+
let pos = 0;
|
|
724
|
+
while ((pos = xml.indexOf(search, pos)) !== -1) {
|
|
725
|
+
const keyStart = pos + search.length;
|
|
726
|
+
const keyEnd = xml.indexOf("}", keyStart);
|
|
727
|
+
if (keyEnd === -1) break;
|
|
728
|
+
keys.add(xml.substring(keyStart, keyEnd));
|
|
729
|
+
pos = keyEnd + 1;
|
|
730
|
+
}
|
|
731
|
+
return mediaArray.filter((m) => keys.has(m.fileName));
|
|
732
|
+
}
|
|
733
|
+
/** Replace `{hlink:key}` placeholders with relationship IDs. */
|
|
734
|
+
function replaceHyperlinkPlaceholders(xml, hyperlinks, offset) {
|
|
735
|
+
if (hyperlinks.length === 0) return xml;
|
|
736
|
+
const map = /* @__PURE__ */ new Map();
|
|
737
|
+
for (const [i, h] of hyperlinks.entries()) map.set(`hlink:${h.key}`, formatId(offset, i, "rId"));
|
|
738
|
+
return replacePlaceholders(xml, map);
|
|
739
|
+
}
|
|
740
|
+
//#endregion
|
|
741
|
+
//#region src/util/values.ts
|
|
742
|
+
/**
|
|
743
|
+
* Validates and converts a number to an integer (decimal number).
|
|
744
|
+
*
|
|
745
|
+
* Reference: ST_DecimalNumber in OOXML specification
|
|
746
|
+
*
|
|
747
|
+
* @param val - The number to validate and convert
|
|
748
|
+
* @returns The floored integer value
|
|
749
|
+
* @throws Error if the value is NaN
|
|
750
|
+
*
|
|
751
|
+
* @example
|
|
752
|
+
* ```typescript
|
|
753
|
+
* const num = decimalNumber(10.7); // Returns 10
|
|
754
|
+
* const negative = decimalNumber(-5.3); // Returns -5
|
|
755
|
+
* ```
|
|
756
|
+
*/
|
|
757
|
+
const decimalNumber = (val) => {
|
|
758
|
+
if (!Number.isFinite(val)) throw new Error(`Invalid value '${val}' specified. Must be a finite number.`);
|
|
759
|
+
return Math.floor(val);
|
|
760
|
+
};
|
|
761
|
+
/**
|
|
762
|
+
* Validates and converts a number to a positive integer (unsigned decimal number).
|
|
763
|
+
*
|
|
764
|
+
* Reference: ST_UnsignedDecimalNumber in OOXML specification
|
|
765
|
+
*
|
|
766
|
+
* @param val - The number to validate and convert
|
|
767
|
+
* @returns The floored positive integer value
|
|
768
|
+
* @throws Error if the value is NaN or negative
|
|
769
|
+
*
|
|
770
|
+
* @example
|
|
771
|
+
* ```typescript
|
|
772
|
+
* const num = unsignedDecimalNumber(10.7); // Returns 10
|
|
773
|
+
* const invalid = unsignedDecimalNumber(-5); // Throws Error
|
|
774
|
+
* ```
|
|
775
|
+
*/
|
|
776
|
+
const unsignedDecimalNumber = (val) => {
|
|
777
|
+
const value = decimalNumber(val);
|
|
778
|
+
if (value < 0) throw new Error(`Invalid value '${val}' specified. Must be a positive integer.`);
|
|
779
|
+
return value;
|
|
780
|
+
};
|
|
781
|
+
/**
|
|
782
|
+
* Validates and normalizes a hexadecimal binary value.
|
|
783
|
+
*
|
|
784
|
+
* The xsd:hexBinary type represents binary data as a sequence of binary octets
|
|
785
|
+
* using hexadecimal encoding, where each binary octet is a two-character
|
|
786
|
+
* hexadecimal number. Both lowercase and uppercase letters A-F are permitted.
|
|
787
|
+
*
|
|
788
|
+
* @param val - The hexadecimal string to validate
|
|
789
|
+
* @param length - The expected length in bytes (not characters)
|
|
790
|
+
* @returns The validated hexadecimal string
|
|
791
|
+
* @throws Error if the value is not a valid hex string of the expected length
|
|
792
|
+
*
|
|
793
|
+
* @example
|
|
794
|
+
* ```typescript
|
|
795
|
+
* hexBinary("0FB8", 2); // Valid: 2 bytes = 4 characters
|
|
796
|
+
* hexBinary("ABC", 2); // Invalid: wrong length
|
|
797
|
+
* ```
|
|
798
|
+
*/
|
|
799
|
+
const hexBinary = (val, length) => {
|
|
800
|
+
const expectedLength = length * 2;
|
|
801
|
+
if (val.length !== expectedLength || isNaN(Number(`0x${val}`))) throw new Error(`Invalid hex value '${val}'. Expected ${expectedLength} digit hex value`);
|
|
802
|
+
return val;
|
|
803
|
+
};
|
|
804
|
+
/**
|
|
805
|
+
* Validates a long hexadecimal number (4 bytes / 8 characters).
|
|
806
|
+
*
|
|
807
|
+
* Reference: ST_LongHexNumber in OOXML specification
|
|
808
|
+
*
|
|
809
|
+
* @param val - The hexadecimal string to validate
|
|
810
|
+
* @returns The validated hexadecimal string
|
|
811
|
+
* @throws Error if the value is not a valid 8-character hex string
|
|
812
|
+
*
|
|
813
|
+
* @example
|
|
814
|
+
* ```typescript
|
|
815
|
+
* const hex = longHexNumber("ABCD1234"); // Valid
|
|
816
|
+
* ```
|
|
817
|
+
*/
|
|
818
|
+
const longHexNumber = (val) => hexBinary(val, 4);
|
|
819
|
+
/**
|
|
820
|
+
* Validates a short hexadecimal number (2 bytes / 4 characters).
|
|
821
|
+
*
|
|
822
|
+
* Reference: ST_ShortHexNumber in OOXML specification
|
|
823
|
+
*
|
|
824
|
+
* @param val - The hexadecimal string to validate
|
|
825
|
+
* @returns The validated hexadecimal string
|
|
826
|
+
* @throws Error if the value is not a valid 4-character hex string
|
|
827
|
+
*
|
|
828
|
+
* @example
|
|
829
|
+
* ```typescript
|
|
830
|
+
* const hex = shortHexNumber("AB12"); // Valid
|
|
831
|
+
* ```
|
|
832
|
+
*/
|
|
833
|
+
const shortHexNumber = (val) => hexBinary(val, 2);
|
|
834
|
+
/**
|
|
835
|
+
* Validates a single-byte hexadecimal number (1 byte / 2 characters).
|
|
836
|
+
*
|
|
837
|
+
* Reference: ST_UcharHexNumber in OOXML specification
|
|
838
|
+
*
|
|
839
|
+
* @param val - The hexadecimal string to validate
|
|
840
|
+
* @returns The validated hexadecimal string
|
|
841
|
+
* @throws Error if the value is not a valid 2-character hex string
|
|
842
|
+
*
|
|
843
|
+
* @example
|
|
844
|
+
* ```typescript
|
|
845
|
+
* const hex = uCharHexNumber("FF"); // Valid
|
|
846
|
+
* ```
|
|
847
|
+
*/
|
|
848
|
+
const uCharHexNumber = (val) => hexBinary(val, 1);
|
|
849
|
+
/**
|
|
850
|
+
* Normalizes a universal measure value by parsing and reformatting.
|
|
851
|
+
*
|
|
852
|
+
* Ensures the numeric portion is properly formatted while preserving the unit.
|
|
853
|
+
*
|
|
854
|
+
* Reference: ST_UniversalMeasure in OOXML specification
|
|
855
|
+
*
|
|
856
|
+
* @param val - The universal measure string to normalize
|
|
857
|
+
* @returns The normalized universal measure
|
|
858
|
+
*
|
|
859
|
+
* @example
|
|
860
|
+
* ```typescript
|
|
861
|
+
* const measure = universalMeasureValue("10.500mm"); // Returns "10.5mm"
|
|
862
|
+
* ```
|
|
863
|
+
*/
|
|
864
|
+
const universalMeasureValue = (val) => {
|
|
865
|
+
const unit = val.slice(-2);
|
|
866
|
+
const amount = val.substring(0, val.length - 2);
|
|
867
|
+
return `${Number(amount)}${unit}`;
|
|
868
|
+
};
|
|
869
|
+
/**
|
|
870
|
+
* Validates and normalizes a positive universal measure value.
|
|
871
|
+
*
|
|
872
|
+
* Reference: ST_PositiveUniversalMeasure in OOXML specification
|
|
873
|
+
*
|
|
874
|
+
* @param val - The positive universal measure string to validate
|
|
875
|
+
* @returns The normalized positive universal measure
|
|
876
|
+
* @throws Error if the value is negative
|
|
877
|
+
*
|
|
878
|
+
* @example
|
|
879
|
+
* ```typescript
|
|
880
|
+
* const measure = positiveUniversalMeasureValue("10.5mm"); // Valid
|
|
881
|
+
* const invalid = positiveUniversalMeasureValue("-5mm"); // Throws Error
|
|
882
|
+
* ```
|
|
883
|
+
*/
|
|
884
|
+
const positiveUniversalMeasureValue = (val) => {
|
|
885
|
+
const value = universalMeasureValue(val);
|
|
886
|
+
if (parseFloat(value) < 0) throw new Error(`Invalid value '${value}' specified. Expected a positive number.`);
|
|
887
|
+
return value;
|
|
888
|
+
};
|
|
889
|
+
/**
|
|
890
|
+
* Validates and normalizes a hexadecimal color value.
|
|
891
|
+
*
|
|
892
|
+
* Accepts either "auto" or a 6-character RGB hex value (with or without # prefix).
|
|
893
|
+
* The # prefix is commonly used but technically invalid in OOXML, so it is stripped
|
|
894
|
+
* for strict compliance.
|
|
895
|
+
*
|
|
896
|
+
* Reference: ST_HexColor in OOXML specification
|
|
897
|
+
*
|
|
898
|
+
* @param val - The color value to validate ("auto" or hex color)
|
|
899
|
+
* @returns The normalized color value
|
|
900
|
+
* @throws Error if the hex color is invalid
|
|
901
|
+
*
|
|
902
|
+
* @example
|
|
903
|
+
* ```typescript
|
|
904
|
+
* const color1 = hexColorValue("auto"); // Returns "auto"
|
|
905
|
+
* const color2 = hexColorValue("FF0000"); // Returns "FF0000"
|
|
906
|
+
* const color3 = hexColorValue("#00FF00"); // Returns "00FF00" (# stripped)
|
|
907
|
+
* ```
|
|
908
|
+
*/
|
|
909
|
+
const hexColorValue = (val) => {
|
|
910
|
+
if (val === "auto") return val;
|
|
911
|
+
return hexBinary(val.charAt(0) === "#" ? val.substring(1) : val, 3);
|
|
912
|
+
};
|
|
913
|
+
/**
|
|
914
|
+
* Validates a signed TWIP measurement value.
|
|
915
|
+
*
|
|
916
|
+
* Accepts either a universal measure string or a numeric TWIP value.
|
|
917
|
+
*
|
|
918
|
+
* Reference: ST_SignedTwipsMeasure in OOXML specification
|
|
919
|
+
*
|
|
920
|
+
* @param val - The measurement value (universal measure or number)
|
|
921
|
+
* @returns The normalized measurement value
|
|
922
|
+
*
|
|
923
|
+
* @example
|
|
924
|
+
* ```typescript
|
|
925
|
+
* const measure1 = signedTwipsMeasureValue("10mm");
|
|
926
|
+
* const measure2 = signedTwipsMeasureValue(1440); // 1 inch in TWIP
|
|
927
|
+
* ```
|
|
928
|
+
*/
|
|
929
|
+
const signedTwipsMeasureValue = (val) => typeof val === "string" ? universalMeasureValue(val) : decimalNumber(val);
|
|
930
|
+
/**
|
|
931
|
+
* Validates a half-point (HPS) measurement value.
|
|
932
|
+
*
|
|
933
|
+
* Accepts either a positive universal measure string or a positive number.
|
|
934
|
+
* HPS (half-points) are commonly used for font sizes.
|
|
935
|
+
*
|
|
936
|
+
* Reference: ST_HpsMeasure in OOXML specification
|
|
937
|
+
*
|
|
938
|
+
* @param val - The measurement value (positive universal measure or number)
|
|
939
|
+
* @returns The normalized measurement value
|
|
940
|
+
*
|
|
941
|
+
* @example
|
|
942
|
+
* ```typescript
|
|
943
|
+
* const fontSize1 = hpsMeasureValue("12pt");
|
|
944
|
+
* const fontSize2 = hpsMeasureValue(24); // 12pt in half-points
|
|
945
|
+
* ```
|
|
946
|
+
*/
|
|
947
|
+
const hpsMeasureValue = (val) => typeof val === "string" ? positiveUniversalMeasureValue(val) : unsignedDecimalNumber(val);
|
|
948
|
+
/**
|
|
949
|
+
* Validates a signed half-point (HPS) measurement value.
|
|
950
|
+
*
|
|
951
|
+
* Accepts either a universal measure string or a numeric value.
|
|
952
|
+
*
|
|
953
|
+
* Reference: ST_SignedHpsMeasure in OOXML specification
|
|
954
|
+
*
|
|
955
|
+
* @param val - The measurement value (universal measure or number)
|
|
956
|
+
* @returns The normalized measurement value
|
|
957
|
+
*
|
|
958
|
+
* @example
|
|
959
|
+
* ```typescript
|
|
960
|
+
* const spacing1 = signedHpsMeasureValue("6pt");
|
|
961
|
+
* const spacing2 = signedHpsMeasureValue(-12); // Negative spacing
|
|
962
|
+
* ```
|
|
963
|
+
*/
|
|
964
|
+
const signedHpsMeasureValue = (val) => typeof val === "string" ? universalMeasureValue(val) : decimalNumber(val);
|
|
965
|
+
/**
|
|
966
|
+
* Validates a positive TWIP measurement value.
|
|
967
|
+
*
|
|
968
|
+
* Accepts either a positive universal measure string or a positive number.
|
|
969
|
+
*
|
|
970
|
+
* Reference: ST_TwipsMeasure in OOXML specification
|
|
971
|
+
*
|
|
972
|
+
* @param val - The measurement value (positive universal measure or number)
|
|
973
|
+
* @returns The normalized measurement value
|
|
974
|
+
*
|
|
975
|
+
* @example
|
|
976
|
+
* ```typescript
|
|
977
|
+
* const width1 = twipsMeasureValue("25.4mm");
|
|
978
|
+
* const width2 = twipsMeasureValue(1440); // 1 inch in TWIP
|
|
979
|
+
* ```
|
|
980
|
+
*/
|
|
981
|
+
const twipsMeasureValue = (val) => typeof val === "string" ? positiveUniversalMeasureValue(val) : unsignedDecimalNumber(val);
|
|
982
|
+
/**
|
|
983
|
+
* Normalizes a percentage value by parsing and reformatting.
|
|
984
|
+
*
|
|
985
|
+
* Reference: ST_Percentage in OOXML specification
|
|
986
|
+
*
|
|
987
|
+
* @param val - The percentage string to normalize
|
|
988
|
+
* @returns The normalized percentage
|
|
989
|
+
*
|
|
990
|
+
* @example
|
|
991
|
+
* ```typescript
|
|
992
|
+
* const percent = percentageValue("50.000%"); // Returns "50%"
|
|
993
|
+
* ```
|
|
994
|
+
*/
|
|
995
|
+
const percentageValue = (val) => {
|
|
996
|
+
const percent = val.substring(0, val.length - 1);
|
|
997
|
+
return `${Number(percent)}%`;
|
|
998
|
+
};
|
|
999
|
+
/**
|
|
1000
|
+
* Validates a measurement value that can be expressed as a number, percentage, or universal measure.
|
|
1001
|
+
*
|
|
1002
|
+
* Reference: ST_MeasurementOrPercent in OOXML specification
|
|
1003
|
+
*
|
|
1004
|
+
* @param val - The measurement value (number, percentage, or universal measure)
|
|
1005
|
+
* @returns The normalized measurement value
|
|
1006
|
+
*
|
|
1007
|
+
* @example
|
|
1008
|
+
* ```typescript
|
|
1009
|
+
* const measure1 = measurementOrPercentValue(100); // Unqualified number
|
|
1010
|
+
* const measure2 = measurementOrPercentValue("50%"); // Percentage
|
|
1011
|
+
* const measure3 = measurementOrPercentValue("10mm"); // Universal measure
|
|
1012
|
+
* ```
|
|
1013
|
+
*/
|
|
1014
|
+
const measurementOrPercentValue = (val) => {
|
|
1015
|
+
if (typeof val === "number") return decimalNumber(val);
|
|
1016
|
+
if (val.slice(-1) === "%") return percentageValue(val);
|
|
1017
|
+
return universalMeasureValue(val);
|
|
1018
|
+
};
|
|
1019
|
+
/**
|
|
1020
|
+
* Validates an eighth-point measurement value.
|
|
1021
|
+
*
|
|
1022
|
+
* Eighth-points are used for fine-grained measurements in text formatting.
|
|
1023
|
+
*
|
|
1024
|
+
* Reference: ST_EighthPointMeasure in OOXML specification
|
|
1025
|
+
*
|
|
1026
|
+
* @param val - The measurement value in eighth-points
|
|
1027
|
+
* @returns The validated positive integer value
|
|
1028
|
+
*
|
|
1029
|
+
* @example
|
|
1030
|
+
* ```typescript
|
|
1031
|
+
* const measure = eighthPointMeasureValue(16); // 2 points
|
|
1032
|
+
* ```
|
|
1033
|
+
*/
|
|
1034
|
+
const eighthPointMeasureValue = unsignedDecimalNumber;
|
|
1035
|
+
/**
|
|
1036
|
+
* Validates a point measurement value.
|
|
1037
|
+
*
|
|
1038
|
+
* Reference: ST_PointMeasure in OOXML specification
|
|
1039
|
+
*
|
|
1040
|
+
* @param val - The measurement value in points
|
|
1041
|
+
* @returns The validated positive integer value
|
|
1042
|
+
*
|
|
1043
|
+
* @example
|
|
1044
|
+
* ```typescript
|
|
1045
|
+
* const fontSize = pointMeasureValue(12); // 12pt
|
|
1046
|
+
* ```
|
|
1047
|
+
*/
|
|
1048
|
+
const pointMeasureValue = unsignedDecimalNumber;
|
|
1049
|
+
/**
|
|
1050
|
+
* Converts a JavaScript Date object to an ISO 8601 date-time string.
|
|
1051
|
+
*
|
|
1052
|
+
* The format is CCYY-MM-DDThh:mm:ss.sssZ where T is a literal and Z indicates UTC.
|
|
1053
|
+
* This matches the xsd:dateTime format required by OOXML.
|
|
1054
|
+
*
|
|
1055
|
+
* Reference: ST_DateTime in OOXML specification
|
|
1056
|
+
*
|
|
1057
|
+
* @param val - The Date object to convert
|
|
1058
|
+
* @returns An ISO 8601 formatted date-time string
|
|
1059
|
+
*
|
|
1060
|
+
* @example
|
|
1061
|
+
* ```typescript
|
|
1062
|
+
* const now = new Date();
|
|
1063
|
+
* const timestamp = dateTimeValue(now); // Returns "2024-01-15T10:30:00.000Z"
|
|
1064
|
+
* ```
|
|
1065
|
+
*/
|
|
1066
|
+
const dateTimeValue = (val) => val.toISOString();
|
|
1067
|
+
/**
|
|
1068
|
+
* Theme color values used throughout OOXML for referencing document theme colors.
|
|
1069
|
+
*
|
|
1070
|
+
* Reference: ST_ThemeColor in OOXML specification
|
|
1071
|
+
*
|
|
1072
|
+
* @publicApi
|
|
1073
|
+
*/
|
|
1074
|
+
const ThemeColor = {
|
|
1075
|
+
DARK1: "dark1",
|
|
1076
|
+
LIGHT1: "light1",
|
|
1077
|
+
DARK2: "dark2",
|
|
1078
|
+
LIGHT2: "light2",
|
|
1079
|
+
ACCENT1: "accent1",
|
|
1080
|
+
ACCENT2: "accent2",
|
|
1081
|
+
ACCENT3: "accent3",
|
|
1082
|
+
ACCENT4: "accent4",
|
|
1083
|
+
ACCENT5: "accent5",
|
|
1084
|
+
ACCENT6: "accent6",
|
|
1085
|
+
HYPERLINK: "hyperlink",
|
|
1086
|
+
FOLLOWED_HYPERLINK: "followedHyperlink",
|
|
1087
|
+
NONE: "none",
|
|
1088
|
+
BACKGROUND1: "background1",
|
|
1089
|
+
TEXT1: "text1",
|
|
1090
|
+
BACKGROUND2: "background2",
|
|
1091
|
+
TEXT2: "text2"
|
|
1092
|
+
};
|
|
1093
|
+
/**
|
|
1094
|
+
* Theme font values used for referencing document theme fonts.
|
|
1095
|
+
*
|
|
1096
|
+
* Reference: ST_Theme in OOXML specification
|
|
1097
|
+
*
|
|
1098
|
+
* @publicApi
|
|
1099
|
+
*/
|
|
1100
|
+
const ThemeFont = {
|
|
1101
|
+
MAJOR_EAST_ASIA: "majorEastAsia",
|
|
1102
|
+
MAJOR_BIDI: "majorBidi",
|
|
1103
|
+
MAJOR_ASCII: "majorAscii",
|
|
1104
|
+
MAJOR_H_ANSI: "majorHAnsi",
|
|
1105
|
+
MINOR_EAST_ASIA: "minorEastAsia",
|
|
1106
|
+
MINOR_BIDI: "minorBidi",
|
|
1107
|
+
MINOR_ASCII: "minorAscii",
|
|
1108
|
+
MINOR_H_ANSI: "minorHAnsi"
|
|
1109
|
+
};
|
|
1110
|
+
//#endregion
|
|
1111
|
+
export { convertOutput as $, replaceChartPlaceholders as A, compileMapping as B, findAndReplaceImagePlaceholders as C, getVideoRefs as D, getReferencedMedia as E, replaceSmartArtPlaceholders as F, levelForMediaName as G, ZIP_STORED_LEVEL as H, replaceVideoPlaceholders as I, zipAndConvert as J, strFromU8$1 as K, derivePasswordHash as L, replaceImagePlaceholders as M, replaceMediaPlaceholders as N, hasPlaceholders as O, replaceNumberingPlaceholders as P, OoxmlMimeType as Q, hashPasswordAgile as R, collectPlaceholderKeys as S, getMediaRefs as T, createPacker as U, ZIP_DEFLATE_LEVEL as V, createZipStream as W, hasNativeInflate as X, zipSyncAndConvert as Y, nativeUnzip as Z, twipsMeasureValue as _, eighthPointMeasureValue as a, unsignedDecimalNumber as b, hpsMeasureValue as c, percentageValue as d, pointMeasureValue as f, signedTwipsMeasureValue as g, signedHpsMeasureValue as h, decimalNumber as i, replaceHyperlinkPlaceholders as j, replaceAllPlaceholders as k, longHexNumber as l, shortHexNumber as m, ThemeFont as n, hexBinary as o, positiveUniversalMeasureValue as p, unzipSync$1 as q, dateTimeValue as r, hexColorValue as s, ThemeColor as t, measurementOrPercentValue as u, uCharHexNumber as v, formatId as w, addSmartArtRelationships as x, universalMeasureValue as y, randomBytes as z };
|
|
1112
|
+
|
|
1113
|
+
//# sourceMappingURL=util-Tq9PSjK0.mjs.map
|