@cj-tech-master/excelts 9.6.0 → 9.6.1
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/dist/browser/modules/archive/io/random-access.d.ts +1 -1
- package/dist/browser/modules/excel/workbook.browser.d.ts +1 -1
- package/dist/browser/modules/excel/xlsx/xform/comment/comment-xform.d.ts +3 -0
- package/dist/browser/modules/excel/xlsx/xform/comment/comment-xform.js +30 -7
- package/dist/browser/modules/pdf/excel-bridge.d.ts +32 -0
- package/dist/browser/modules/pdf/excel-bridge.js +67 -1
- package/dist/browser/modules/pdf/word-bridge.d.ts +20 -15
- package/dist/browser/modules/pdf/word-bridge.js +49 -34
- package/dist/browser/modules/stream/common/consumers.d.ts +2 -1
- package/dist/browser/modules/word/advanced/diff.js +125 -13
- package/dist/browser/modules/word/advanced/drawing-shapes.js +3 -0
- package/dist/browser/modules/word/bridge/excel-bridge.js +21 -1
- package/dist/browser/modules/word/builder/document-handle.d.ts +2 -0
- package/dist/browser/modules/word/builder/document-handle.js +14 -2
- package/dist/browser/modules/word/builder/paragraph-builders.js +10 -1
- package/dist/browser/modules/word/builder/run-builders.d.ts +19 -2
- package/dist/browser/modules/word/builder/run-builders.js +2 -6
- package/dist/browser/modules/word/convert/odt/odt.js +6 -1
- package/dist/browser/modules/word/layout/layout-full.d.ts +12 -0
- package/dist/browser/modules/word/layout/layout-full.js +74 -9
- package/dist/browser/modules/word/layout/layout-model.d.ts +12 -0
- package/dist/browser/modules/word/query/merge.js +26 -10
- package/dist/browser/modules/word/query/split.js +68 -2
- package/dist/browser/modules/word/reader/docx-reader.js +23 -0
- package/dist/browser/modules/word/security/cfb-reader.d.ts +14 -3
- package/dist/browser/modules/word/security/cfb-reader.js +271 -153
- package/dist/browser/modules/word/security/document-protection.js +10 -4
- package/dist/browser/modules/word/security/encryption.js +194 -32
- package/dist/browser/modules/word/types.d.ts +17 -0
- package/dist/browser/modules/word/units.d.ts +10 -4
- package/dist/browser/modules/word/units.js +10 -4
- package/dist/browser/modules/word/writer/document-writer.js +28 -4
- package/dist/browser/modules/word/writer/docx-packager.js +45 -5
- package/dist/browser/modules/word/writer/image-writer.d.ts +1 -1
- package/dist/browser/modules/word/writer/image-writer.js +2 -2
- package/dist/browser/modules/word/writer/render-context.d.ts +15 -0
- package/dist/browser/modules/word/writer/run-writer.js +8 -4
- package/dist/browser/modules/word/writer/section-writer.js +46 -35
- package/dist/browser/modules/word/writer/streaming-writer.js +4 -0
- package/dist/browser/modules/word/writer/styles-writer.js +11 -0
- package/dist/browser/modules/word/writer/table-writer.js +6 -0
- package/dist/cjs/modules/excel/xlsx/xform/comment/comment-xform.js +30 -7
- package/dist/cjs/modules/pdf/excel-bridge.js +67 -0
- package/dist/cjs/modules/pdf/word-bridge.js +49 -34
- package/dist/cjs/modules/word/advanced/diff.js +125 -13
- package/dist/cjs/modules/word/advanced/drawing-shapes.js +3 -0
- package/dist/cjs/modules/word/bridge/excel-bridge.js +21 -1
- package/dist/cjs/modules/word/builder/document-handle.js +14 -2
- package/dist/cjs/modules/word/builder/paragraph-builders.js +10 -1
- package/dist/cjs/modules/word/builder/run-builders.js +2 -6
- package/dist/cjs/modules/word/convert/odt/odt.js +6 -1
- package/dist/cjs/modules/word/layout/layout-full.js +74 -9
- package/dist/cjs/modules/word/query/merge.js +26 -10
- package/dist/cjs/modules/word/query/split.js +68 -2
- package/dist/cjs/modules/word/reader/docx-reader.js +23 -0
- package/dist/cjs/modules/word/security/cfb-reader.js +271 -153
- package/dist/cjs/modules/word/security/document-protection.js +10 -4
- package/dist/cjs/modules/word/security/encryption.js +193 -31
- package/dist/cjs/modules/word/units.js +10 -4
- package/dist/cjs/modules/word/writer/document-writer.js +28 -4
- package/dist/cjs/modules/word/writer/docx-packager.js +45 -5
- package/dist/cjs/modules/word/writer/image-writer.js +2 -2
- package/dist/cjs/modules/word/writer/run-writer.js +8 -4
- package/dist/cjs/modules/word/writer/section-writer.js +46 -35
- package/dist/cjs/modules/word/writer/streaming-writer.js +4 -0
- package/dist/cjs/modules/word/writer/styles-writer.js +11 -0
- package/dist/cjs/modules/word/writer/table-writer.js +6 -0
- package/dist/esm/modules/excel/xlsx/xform/comment/comment-xform.js +30 -7
- package/dist/esm/modules/pdf/excel-bridge.js +67 -1
- package/dist/esm/modules/pdf/word-bridge.js +49 -34
- package/dist/esm/modules/word/advanced/diff.js +125 -13
- package/dist/esm/modules/word/advanced/drawing-shapes.js +3 -0
- package/dist/esm/modules/word/bridge/excel-bridge.js +21 -1
- package/dist/esm/modules/word/builder/document-handle.js +14 -2
- package/dist/esm/modules/word/builder/paragraph-builders.js +10 -1
- package/dist/esm/modules/word/builder/run-builders.js +2 -6
- package/dist/esm/modules/word/convert/odt/odt.js +6 -1
- package/dist/esm/modules/word/layout/layout-full.js +74 -9
- package/dist/esm/modules/word/query/merge.js +26 -10
- package/dist/esm/modules/word/query/split.js +68 -2
- package/dist/esm/modules/word/reader/docx-reader.js +23 -0
- package/dist/esm/modules/word/security/cfb-reader.js +271 -153
- package/dist/esm/modules/word/security/document-protection.js +10 -4
- package/dist/esm/modules/word/security/encryption.js +194 -32
- package/dist/esm/modules/word/units.js +10 -4
- package/dist/esm/modules/word/writer/document-writer.js +28 -4
- package/dist/esm/modules/word/writer/docx-packager.js +45 -5
- package/dist/esm/modules/word/writer/image-writer.js +2 -2
- package/dist/esm/modules/word/writer/run-writer.js +8 -4
- package/dist/esm/modules/word/writer/section-writer.js +46 -35
- package/dist/esm/modules/word/writer/streaming-writer.js +4 -0
- package/dist/esm/modules/word/writer/styles-writer.js +11 -0
- package/dist/esm/modules/word/writer/table-writer.js +6 -0
- package/dist/iife/excelts.iife.js +20 -8
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +2 -2
- package/dist/types/modules/archive/io/random-access.d.ts +1 -1
- package/dist/types/modules/excel/workbook.browser.d.ts +1 -1
- package/dist/types/modules/excel/xlsx/xform/comment/comment-xform.d.ts +3 -0
- package/dist/types/modules/pdf/excel-bridge.d.ts +32 -0
- package/dist/types/modules/pdf/word-bridge.d.ts +20 -15
- package/dist/types/modules/stream/common/consumers.d.ts +2 -1
- package/dist/types/modules/word/builder/document-handle.d.ts +2 -0
- package/dist/types/modules/word/builder/run-builders.d.ts +19 -2
- package/dist/types/modules/word/layout/layout-full.d.ts +12 -0
- package/dist/types/modules/word/layout/layout-model.d.ts +12 -0
- package/dist/types/modules/word/security/cfb-reader.d.ts +14 -3
- package/dist/types/modules/word/types.d.ts +17 -0
- package/dist/types/modules/word/units.d.ts +10 -4
- package/dist/types/modules/word/writer/image-writer.d.ts +1 -1
- package/dist/types/modules/word/writer/render-context.d.ts +15 -0
- package/package.json +2 -2
|
@@ -149,15 +149,19 @@ async function verifyPassword(password, info) {
|
|
|
149
149
|
try {
|
|
150
150
|
// Derive verifier hash input key
|
|
151
151
|
const verifierInputKey = await deriveEncryptionKey(password, info, exports.AGILE_BLOCK_KEYS.verifierHashInput);
|
|
152
|
-
// Decrypt the verifier hash input
|
|
153
|
-
|
|
154
|
-
//
|
|
152
|
+
// Decrypt the verifier hash input. MS-OFFCRYPTO §2.3.4.13 specifies
|
|
153
|
+
// these blobs are AES-CBC with the plaintext zero-padded to the block
|
|
154
|
+
// boundary — NOT PKCS#7. Using a PKCS#7 decrypt would mis-strip bytes
|
|
155
|
+
// and break interop with Word / msoffcrypto.
|
|
156
|
+
const verifierInput = aesCbcRawDecrypt(info.encryptedVerifierHashInput, verifierInputKey, info.keySalt);
|
|
157
|
+
// Hash the verifier input. The plaintext is exactly 16 bytes (saltSize),
|
|
158
|
+
// so trim any zero padding before hashing.
|
|
155
159
|
const hashAlg = mapHashName(info.hashAlgorithm);
|
|
156
|
-
const computedHash = await (0, crypto_1.hashAsync)(hashAlg, verifierInput);
|
|
160
|
+
const computedHash = await (0, crypto_1.hashAsync)(hashAlg, verifierInput.slice(0, info.blockSize));
|
|
157
161
|
// Derive verifier hash value key
|
|
158
162
|
const verifierValueKey = await deriveEncryptionKey(password, info, exports.AGILE_BLOCK_KEYS.verifierHashValue);
|
|
159
|
-
// Decrypt the verifier hash value (
|
|
160
|
-
const expectedHash =
|
|
163
|
+
// Decrypt the verifier hash value (zero-padded AES-CBC, see above).
|
|
164
|
+
const expectedHash = aesCbcRawDecrypt(info.encryptedVerifierHashValue, verifierValueKey, info.keySalt);
|
|
161
165
|
// Compare (truncate to hashSize in case of padding)
|
|
162
166
|
return bytesEqual(computedHash.slice(0, info.hashSize), expectedHash.slice(0, info.hashSize));
|
|
163
167
|
}
|
|
@@ -180,8 +184,9 @@ async function verifyPassword(password, info) {
|
|
|
180
184
|
async function decryptPackage(encryptedPackage, info, password, maxDecryptedSize = 512 * 1024 * 1024) {
|
|
181
185
|
// Derive key encryption key
|
|
182
186
|
const keyEncryptionKey = await deriveEncryptionKey(password, info, exports.AGILE_BLOCK_KEYS.encryptedKey);
|
|
183
|
-
// Decrypt the actual package key
|
|
184
|
-
|
|
187
|
+
// Decrypt the actual package key. Zero-padded AES-CBC per MS-OFFCRYPTO
|
|
188
|
+
// §2.3.4.13 (NOT PKCS#7); the key is exactly keyBits/8 bytes.
|
|
189
|
+
const packageKey = aesCbcRawDecrypt(info.encryptedKeyValue, keyEncryptionKey, info.keySalt).slice(0, info.keyBits / 8);
|
|
185
190
|
if (encryptedPackage.length < 8) {
|
|
186
191
|
throw new errors_1.DocxDecryptionError("EncryptedPackage too small (missing 8-byte size header)");
|
|
187
192
|
}
|
|
@@ -265,25 +270,18 @@ function getHashSizeFor(hashName) {
|
|
|
265
270
|
}
|
|
266
271
|
}
|
|
267
272
|
/**
|
|
268
|
-
* Decrypt an AES-CBC blob
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
* bytes per the OOXML spec.
|
|
273
|
+
* Decrypt an AES-CBC blob written with zero padding (no PKCS#7). Used for
|
|
274
|
+
* the encryptedKeyValue / encryptedVerifierHashInput / encryptedVerifierHashValue
|
|
275
|
+
* blobs, per MS-OFFCRYPTO §2.3.4.13. The IV is truncated/extended to 16 bytes.
|
|
272
276
|
*/
|
|
273
|
-
function
|
|
274
|
-
return (0, crypto_1.
|
|
275
|
-
}
|
|
276
|
-
/**
|
|
277
|
-
* Encrypt with AES-CBC + PKCS#7 padding — the Agile spec's standard
|
|
278
|
-
* choice for verifier blobs and the encrypted package key.
|
|
279
|
-
*/
|
|
280
|
-
function aesCbcPkcs7Encrypt(data, key, iv) {
|
|
281
|
-
return (0, crypto_1.aesCbcEncrypt)(data, key, ivToBlockSize(iv));
|
|
277
|
+
function aesCbcRawDecrypt(data, key, iv) {
|
|
278
|
+
return (0, crypto_1.aesCbcDecryptRaw)(data, key, ivToBlockSize(iv));
|
|
282
279
|
}
|
|
283
280
|
/**
|
|
284
281
|
* Encrypt with AES-CBC and zero-padding (no PKCS#7). Used by package
|
|
285
|
-
* segment encryption: data is already padded
|
|
286
|
-
* the caller, and the on-disk format does not
|
|
282
|
+
* segment encryption and the verifier / key blobs: data is already padded
|
|
283
|
+
* to a 16-byte boundary by the caller, and the on-disk format does not
|
|
284
|
+
* include a PKCS#7 trailer.
|
|
287
285
|
*/
|
|
288
286
|
function aesCbcZeroPadEncrypt(data, key, iv) {
|
|
289
287
|
return (0, crypto_1.aesCbcEncryptRaw)(data, key, ivToBlockSize(iv));
|
|
@@ -297,12 +295,51 @@ function ivToBlockSize(iv) {
|
|
|
297
295
|
out.set(iv.slice(0, 16));
|
|
298
296
|
return out;
|
|
299
297
|
}
|
|
298
|
+
/** Right-pad data with zeros so its length is a multiple of `blockSize`. */
|
|
299
|
+
function padToBlock(data, blockSize) {
|
|
300
|
+
if (data.length % blockSize === 0) {
|
|
301
|
+
return data;
|
|
302
|
+
}
|
|
303
|
+
const out = new Uint8Array(Math.ceil(data.length / blockSize) * blockSize);
|
|
304
|
+
out.set(data);
|
|
305
|
+
return out;
|
|
306
|
+
}
|
|
300
307
|
function concat(a, b) {
|
|
301
308
|
const result = new Uint8Array(a.length + b.length);
|
|
302
309
|
result.set(a, 0);
|
|
303
310
|
result.set(b, a.length);
|
|
304
311
|
return result;
|
|
305
312
|
}
|
|
313
|
+
/** HMAC block size (in bytes) for the supported hash algorithms. */
|
|
314
|
+
function hmacBlockSize(hashName) {
|
|
315
|
+
// SHA-1 / SHA-256 use a 512-bit (64-byte) block; SHA-384 / SHA-512 use a
|
|
316
|
+
// 1024-bit (128-byte) block.
|
|
317
|
+
return hashName === "SHA-384" || hashName === "SHA-512" ? 128 : 64;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Compute HMAC(hashName, key, message) using the generic hash primitive.
|
|
321
|
+
* Implemented here (rather than in @utils/crypto, which only ships
|
|
322
|
+
* hmacSha256) so agile encryption can use SHA-512 etc. for the data
|
|
323
|
+
* integrity HMAC that Word verifies on open.
|
|
324
|
+
*/
|
|
325
|
+
async function hmac(hashName, key, message) {
|
|
326
|
+
const blockSize = hmacBlockSize(hashName);
|
|
327
|
+
// Keys longer than the block size are hashed down first.
|
|
328
|
+
let k = key.length > blockSize ? await (0, crypto_1.hashAsync)(hashName, key) : key;
|
|
329
|
+
if (k.length < blockSize) {
|
|
330
|
+
const padded = new Uint8Array(blockSize);
|
|
331
|
+
padded.set(k);
|
|
332
|
+
k = padded;
|
|
333
|
+
}
|
|
334
|
+
const ipad = new Uint8Array(blockSize);
|
|
335
|
+
const opad = new Uint8Array(blockSize);
|
|
336
|
+
for (let i = 0; i < blockSize; i++) {
|
|
337
|
+
ipad[i] = k[i] ^ 0x36;
|
|
338
|
+
opad[i] = k[i] ^ 0x5c;
|
|
339
|
+
}
|
|
340
|
+
const inner = await (0, crypto_1.hashAsync)(hashName, concat(ipad, message));
|
|
341
|
+
return (0, crypto_1.hashAsync)(hashName, concat(opad, inner));
|
|
342
|
+
}
|
|
306
343
|
function stringToUtf16LE(s) {
|
|
307
344
|
const buf = new Uint8Array(s.length * 2);
|
|
308
345
|
for (let i = 0; i < s.length; i++) {
|
|
@@ -602,17 +639,33 @@ async function encryptDocx(zipBytes, password, options) {
|
|
|
602
639
|
const packageKey = (0, internal_utils_1.randomBytes)(keyBytes);
|
|
603
640
|
// 2. Generate key encryption key (for encrypting the package key)
|
|
604
641
|
const keyEncryptionKey = await deriveEncryptionKey(password, { keySalt, spinCount, hashAlgorithm, keyBits }, exports.AGILE_BLOCK_KEYS.encryptedKey);
|
|
605
|
-
// 3. Encrypt the package key
|
|
606
|
-
|
|
642
|
+
// 3. Encrypt the package key. MS-OFFCRYPTO §2.3.4.13: these blobs use
|
|
643
|
+
// zero-padded AES-CBC (no PKCS#7). packageKey is already block-aligned.
|
|
644
|
+
const encryptedKeyValue = aesCbcZeroPadEncrypt(padToBlock(packageKey, blockSize), keyEncryptionKey, keySalt);
|
|
607
645
|
// 4. Generate verifier: random 16 bytes, hash them, encrypt both
|
|
608
646
|
const verifierInput = (0, internal_utils_1.randomBytes)(16);
|
|
609
647
|
const verifierHash = await (0, crypto_1.hashAsync)(hashName, verifierInput);
|
|
610
648
|
const verifierInputKey = await deriveEncryptionKey(password, { keySalt, spinCount, hashAlgorithm, keyBits }, exports.AGILE_BLOCK_KEYS.verifierHashInput);
|
|
611
|
-
const encryptedVerifierHashInput =
|
|
649
|
+
const encryptedVerifierHashInput = aesCbcZeroPadEncrypt(padToBlock(verifierInput, blockSize), verifierInputKey, keySalt);
|
|
612
650
|
const verifierValueKey = await deriveEncryptionKey(password, { keySalt, spinCount, hashAlgorithm, keyBits }, exports.AGILE_BLOCK_KEYS.verifierHashValue);
|
|
613
|
-
const encryptedVerifierHashValue =
|
|
651
|
+
const encryptedVerifierHashValue = aesCbcZeroPadEncrypt(padToBlock(verifierHash, blockSize), verifierValueKey, keySalt);
|
|
614
652
|
// 5. Encrypt the ZIP data in 4096-byte segments
|
|
615
653
|
const encryptedPackage = await encryptPackageData(zipBytes, packageKey, keySalt, hashName, blockSize);
|
|
654
|
+
// 5b. Data integrity (MS-OFFCRYPTO §2.3.4.14). Word verifies this HMAC on
|
|
655
|
+
// open; an empty or missing value makes it report the file as corrupt.
|
|
656
|
+
// - hmacKey: random, hashSize bytes (padded to block size).
|
|
657
|
+
// - encryptedHmacKey = AES-CBC(packageKey, IV0, hmacKey)
|
|
658
|
+
// - hmacValue = HMAC(hashAlg, hmacKey, encryptedPackage) (entire
|
|
659
|
+
// stream including the 8-byte size prefix)
|
|
660
|
+
// - encryptedHmacValue = AES-CBC(packageKey, IV1, hmacValue)
|
|
661
|
+
// IV0 = H(keySalt + blockKeyDataIntegrityKey)[:blockSize]
|
|
662
|
+
// IV1 = H(keySalt + blockKeyDataIntegrityValue)[:blockSize]
|
|
663
|
+
const hmacKey = (0, internal_utils_1.randomBytes)(hashSize);
|
|
664
|
+
const ivHmacKey = (await (0, crypto_1.hashAsync)(hashName, concat(keySalt, exports.AGILE_BLOCK_KEYS.dataIntegrityKey))).slice(0, blockSize);
|
|
665
|
+
const ivHmacValue = (await (0, crypto_1.hashAsync)(hashName, concat(keySalt, exports.AGILE_BLOCK_KEYS.dataIntegrityValue))).slice(0, blockSize);
|
|
666
|
+
const encryptedHmacKey = aesCbcZeroPadEncrypt(padToBlock(hmacKey, blockSize), packageKey, ivHmacKey);
|
|
667
|
+
const hmacValue = await hmac(hashName, hmacKey, encryptedPackage);
|
|
668
|
+
const encryptedHmacValue = aesCbcZeroPadEncrypt(padToBlock(hmacValue, blockSize), packageKey, ivHmacValue);
|
|
616
669
|
// 6. Generate EncryptionInfo XML
|
|
617
670
|
const encInfoXml = buildEncryptionInfoXml({
|
|
618
671
|
keyBits,
|
|
@@ -623,7 +676,9 @@ async function encryptDocx(zipBytes, password, options) {
|
|
|
623
676
|
keySalt,
|
|
624
677
|
encryptedVerifierHashInput,
|
|
625
678
|
encryptedVerifierHashValue,
|
|
626
|
-
encryptedKeyValue
|
|
679
|
+
encryptedKeyValue,
|
|
680
|
+
encryptedHmacKey,
|
|
681
|
+
encryptedHmacValue
|
|
627
682
|
});
|
|
628
683
|
// Prepend 8-byte version header: version 4.4 + flags 0x40
|
|
629
684
|
const xmlBytes = internal_utils_1.utf8Encoder.encode(encInfoXml);
|
|
@@ -633,8 +688,12 @@ async function encryptDocx(zipBytes, password, options) {
|
|
|
633
688
|
encInfoView.setUint16(2, 4, true); // version minor
|
|
634
689
|
encInfoView.setUint32(4, 0x40, true); // flags (agile)
|
|
635
690
|
encInfoStream.set(xmlBytes, 8);
|
|
636
|
-
// 7. Package into CFB
|
|
691
|
+
// 7. Package into CFB.
|
|
692
|
+
// Office requires the \x06DataSpaces structure (MS-OFFCRYPTO §2.3.2)
|
|
693
|
+
// in addition to EncryptionInfo + EncryptedPackage; without it Word
|
|
694
|
+
// rejects the file as corrupt even when the password is correct.
|
|
637
695
|
const cfbBytes = (0, cfb_reader_1.writeCfb)([
|
|
696
|
+
...buildDataSpacesStreams(),
|
|
638
697
|
{ name: "EncryptionInfo", data: encInfoStream },
|
|
639
698
|
{ name: "EncryptedPackage", data: encryptedPackage }
|
|
640
699
|
]);
|
|
@@ -643,6 +702,107 @@ async function encryptDocx(zipBytes, password, options) {
|
|
|
643
702
|
// =============================================================================
|
|
644
703
|
// Encrypt Helpers
|
|
645
704
|
// =============================================================================
|
|
705
|
+
// -----------------------------------------------------------------------------
|
|
706
|
+
// \x06DataSpaces structure (MS-OFFCRYPTO §2.3.2)
|
|
707
|
+
//
|
|
708
|
+
// Office encrypted documents wrap the EncryptedPackage in a DataSpaces map so
|
|
709
|
+
// the consumer knows which transform (StrongEncryption) was applied. The four
|
|
710
|
+
// streams below are byte-for-byte what Office writes for password-based agile
|
|
711
|
+
// encryption. Word validates this structure on open; omitting it makes the
|
|
712
|
+
// file "corrupt" even with the correct password.
|
|
713
|
+
// -----------------------------------------------------------------------------
|
|
714
|
+
/** Encode a UTF-8 length-prefixed unicode string (UNICODE-LP-P4):
|
|
715
|
+
* [4-byte LE byte length of UTF-16LE payload][UTF-16LE chars][pad to 4-byte boundary]. */
|
|
716
|
+
function lengthPrefixedUtf16(str) {
|
|
717
|
+
const chars = stringToUtf16LE(str);
|
|
718
|
+
const padded = Math.ceil(chars.length / 4) * 4;
|
|
719
|
+
const out = new Uint8Array(4 + padded);
|
|
720
|
+
new DataView(out.buffer).setUint32(0, chars.length, true);
|
|
721
|
+
out.set(chars, 4);
|
|
722
|
+
return out;
|
|
723
|
+
}
|
|
724
|
+
/** Concatenate several byte arrays. */
|
|
725
|
+
function concatAll(...parts) {
|
|
726
|
+
const total = parts.reduce((s, p) => s + p.length, 0);
|
|
727
|
+
const out = new Uint8Array(total);
|
|
728
|
+
let off = 0;
|
|
729
|
+
for (const p of parts) {
|
|
730
|
+
out.set(p, off);
|
|
731
|
+
off += p.length;
|
|
732
|
+
}
|
|
733
|
+
return out;
|
|
734
|
+
}
|
|
735
|
+
/** Build the four \x06DataSpaces streams Office requires for agile encryption. */
|
|
736
|
+
function buildDataSpacesStreams() {
|
|
737
|
+
const DATASPACES = "\u0006DataSpaces";
|
|
738
|
+
// --- Version stream (DataSpaceVersionInfo) ---
|
|
739
|
+
// FeatureIdentifier "Microsoft.Container.DataSpaces" + reader/updater/writer
|
|
740
|
+
// version (each major=1, minor=0).
|
|
741
|
+
const versionStream = concatAll(lengthPrefixedUtf16("Microsoft.Container.DataSpaces"), u16le(1), u16le(0), // reader version
|
|
742
|
+
u16le(1), u16le(0), // updater version
|
|
743
|
+
u16le(1), u16le(0) // writer version
|
|
744
|
+
);
|
|
745
|
+
// --- DataSpaceMap stream ---
|
|
746
|
+
// Header: HeaderLength(8) + EntryCount(1) followed by one MapEntry.
|
|
747
|
+
// MapEntry: EntryLength + ReferenceComponentCount(1) +
|
|
748
|
+
// [ReferenceComponent: type(0=stream) + LP name "EncryptedPackage"] +
|
|
749
|
+
// LP DataSpaceName "StrongEncryptionDataSpace".
|
|
750
|
+
const refName = lengthPrefixedUtf16("EncryptedPackage");
|
|
751
|
+
const dsName = lengthPrefixedUtf16("StrongEncryptionDataSpace");
|
|
752
|
+
const refComponent = concatAll(u32le(0), refName); // 0 = stream component
|
|
753
|
+
const mapEntryBody = concatAll(u32le(1), refComponent, dsName); // 1 reference component
|
|
754
|
+
const entryLength = 4 + mapEntryBody.length; // include the EntryLength field itself
|
|
755
|
+
const mapEntry = concatAll(u32le(entryLength), mapEntryBody);
|
|
756
|
+
const dataSpaceMap = concatAll(u32le(8), u32le(1), mapEntry); // headerLen=8, entryCount=1
|
|
757
|
+
// --- DataSpaceInfo/StrongEncryptionDataSpace stream (DataSpaceDefinition) ---
|
|
758
|
+
// HeaderLength(8) + TransformReferenceCount(1) + LP transform name.
|
|
759
|
+
const transformName = lengthPrefixedUtf16("StrongEncryptionTransform");
|
|
760
|
+
const dataSpaceDefinition = concatAll(u32le(8), u32le(1), transformName);
|
|
761
|
+
// --- TransformInfo/StrongEncryptionTransform/\x06Primary stream ---
|
|
762
|
+
// TransformInfoHeader:
|
|
763
|
+
// TransformLength + TransformType(1) + LP TransformId +
|
|
764
|
+
// LP TransformName + reader/updater/writer versions.
|
|
765
|
+
// Followed by EncryptionTransformInfo:
|
|
766
|
+
// LP EncryptionName + EncryptionBlockSize(4) + CipherMode(4).
|
|
767
|
+
const transformId = lengthPrefixedUtf16("{FF9A3F03-56EF-4613-BDD5-5A41C1D07246}");
|
|
768
|
+
const transformNamePrimary = lengthPrefixedUtf16("Microsoft.Container.EncryptionTransform");
|
|
769
|
+
const headerBody = concatAll(u32le(1), // TransformType = 1
|
|
770
|
+
transformId, transformNamePrimary, u16le(1), u16le(0), // reader version
|
|
771
|
+
u16le(1), u16le(0), // updater version
|
|
772
|
+
u16le(1), u16le(0) // writer version
|
|
773
|
+
);
|
|
774
|
+
const transformLength = 4 + headerBody.length; // include the length field itself
|
|
775
|
+
const transformHeader = concatAll(u32le(transformLength), headerBody);
|
|
776
|
+
const encryptionTransformInfo = concatAll(lengthPrefixedUtf16(""), // EncryptionName (empty for agile)
|
|
777
|
+
u32le(0), // EncryptionBlockSize
|
|
778
|
+
u32le(0) // CipherMode
|
|
779
|
+
);
|
|
780
|
+
const primary = concatAll(transformHeader, encryptionTransformInfo);
|
|
781
|
+
return [
|
|
782
|
+
{ name: "Version", path: [DATASPACES], data: versionStream },
|
|
783
|
+
{ name: "DataSpaceMap", path: [DATASPACES], data: dataSpaceMap },
|
|
784
|
+
{
|
|
785
|
+
name: "StrongEncryptionDataSpace",
|
|
786
|
+
path: [DATASPACES, "DataSpaceInfo"],
|
|
787
|
+
data: dataSpaceDefinition
|
|
788
|
+
},
|
|
789
|
+
{
|
|
790
|
+
name: "\u0006Primary",
|
|
791
|
+
path: [DATASPACES, "TransformInfo", "StrongEncryptionTransform"],
|
|
792
|
+
data: primary
|
|
793
|
+
}
|
|
794
|
+
];
|
|
795
|
+
}
|
|
796
|
+
function u16le(n) {
|
|
797
|
+
const b = new Uint8Array(2);
|
|
798
|
+
new DataView(b.buffer).setUint16(0, n, true);
|
|
799
|
+
return b;
|
|
800
|
+
}
|
|
801
|
+
function u32le(n) {
|
|
802
|
+
const b = new Uint8Array(4);
|
|
803
|
+
new DataView(b.buffer).setUint32(0, n, true);
|
|
804
|
+
return b;
|
|
805
|
+
}
|
|
646
806
|
/**
|
|
647
807
|
* Encrypt the package data in 4096-byte segments.
|
|
648
808
|
*
|
|
@@ -684,11 +844,13 @@ async function encryptPackageData(data, packageKey, keySalt, hashName, blockSize
|
|
|
684
844
|
}
|
|
685
845
|
/** Build the Agile EncryptionInfo XML document. */
|
|
686
846
|
function buildEncryptionInfoXml(params) {
|
|
687
|
-
const { keyBits, hashAlgorithm, hashSize, spinCount, blockSize, keySalt, encryptedVerifierHashInput, encryptedVerifierHashValue, encryptedKeyValue } = params;
|
|
847
|
+
const { keyBits, hashAlgorithm, hashSize, spinCount, blockSize, keySalt, encryptedVerifierHashInput, encryptedVerifierHashValue, encryptedKeyValue, encryptedHmacKey, encryptedHmacValue } = params;
|
|
688
848
|
const saltB64 = (0, internal_utils_1.bytesToBase64)(keySalt);
|
|
689
849
|
const vhiB64 = (0, internal_utils_1.bytesToBase64)(encryptedVerifierHashInput);
|
|
690
850
|
const vhvB64 = (0, internal_utils_1.bytesToBase64)(encryptedVerifierHashValue);
|
|
691
851
|
const ekvB64 = (0, internal_utils_1.bytesToBase64)(encryptedKeyValue);
|
|
852
|
+
const hmacKeyB64 = (0, internal_utils_1.bytesToBase64)(encryptedHmacKey);
|
|
853
|
+
const hmacValB64 = (0, internal_utils_1.bytesToBase64)(encryptedHmacValue);
|
|
692
854
|
return ('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r\n' +
|
|
693
855
|
'<encryption xmlns="http://schemas.microsoft.com/office/2006/encryption" ' +
|
|
694
856
|
'xmlns:p="http://schemas.microsoft.com/office/2006/keyEncryptor/password" ' +
|
|
@@ -696,7 +858,7 @@ function buildEncryptionInfoXml(params) {
|
|
|
696
858
|
`<keyData saltSize="16" blockSize="${blockSize}" keyBits="${keyBits}" ` +
|
|
697
859
|
`hashSize="${hashSize}" cipherAlgorithm="AES" cipherChaining="ChainingModeCBC" ` +
|
|
698
860
|
`hashAlgorithm="${hashAlgorithm}" saltValue="${saltB64}"/>\r\n` +
|
|
699
|
-
|
|
861
|
+
`<dataIntegrity encryptedHmacKey="${hmacKeyB64}" encryptedHmacValue="${hmacValB64}"/>\r\n` +
|
|
700
862
|
"<keyEncryptors>\r\n" +
|
|
701
863
|
'<keyEncryptor uri="http://schemas.microsoft.com/office/2006/keyEncryptor/password">\r\n' +
|
|
702
864
|
`<p:encryptedKey spinCount="${spinCount}" saltSize="16" blockSize="${blockSize}" ` +
|
|
@@ -35,10 +35,16 @@ exports.tablePctToPercent = tablePctToPercent;
|
|
|
35
35
|
exports.TWIPS_PER_INCH = 1440;
|
|
36
36
|
/** Twips per point. */
|
|
37
37
|
exports.TWIPS_PER_POINT = 20;
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Twips per centimeter, derived exactly from 1 inch = 2.54 cm = 1440 twips
|
|
40
|
+
* (= 566.9291…). Using the exact factor — rather than the rounded 567 — keeps
|
|
41
|
+
* metric page sizes aligned with their canonical twip values, e.g.
|
|
42
|
+
* `cmToTwips(21)` → 11906 and `cmToTwips(29.7)` → 16838 (A4), matching
|
|
43
|
+
* `A4_PAGE_WIDTH` / `A4_PAGE_HEIGHT` in constants.ts.
|
|
44
|
+
*/
|
|
45
|
+
exports.TWIPS_PER_CM = exports.TWIPS_PER_INCH / 2.54;
|
|
46
|
+
/** Twips per millimeter, derived exactly from {@link TWIPS_PER_CM}. */
|
|
47
|
+
exports.TWIPS_PER_MM = exports.TWIPS_PER_CM / 10;
|
|
42
48
|
/** EMU (English Metric Units) per inch — DrawingML coordinate space. */
|
|
43
49
|
exports.EMU_PER_INCH = 914400;
|
|
44
50
|
/** EMU per centimeter. */
|
|
@@ -47,6 +47,7 @@ function renderSdt(xml, sdt, ctx, helpers) {
|
|
|
47
47
|
? {
|
|
48
48
|
imageRemap: ctx.imageRIdRemap,
|
|
49
49
|
hyperlinkRIds: ctx.hyperlinkRIds,
|
|
50
|
+
nextDocPrId: ctx.ids.nextDocPrId,
|
|
50
51
|
rawXmlPolicy: ctx.rawXmlPolicy
|
|
51
52
|
}
|
|
52
53
|
: undefined;
|
|
@@ -83,6 +84,7 @@ function renderBodyContent(xml, content, ctx) {
|
|
|
83
84
|
const helpers = {
|
|
84
85
|
imageRemap: renderCtx.imageRIdRemap,
|
|
85
86
|
hyperlinkRIds: renderCtx.hyperlinkRIds,
|
|
87
|
+
nextDocPrId: renderCtx.ids.nextDocPrId,
|
|
86
88
|
rawXmlPolicy: renderCtx.rawXmlPolicy
|
|
87
89
|
};
|
|
88
90
|
switch (content.type) {
|
|
@@ -93,7 +95,7 @@ function renderBodyContent(xml, content, ctx) {
|
|
|
93
95
|
(0, table_writer_1.renderTable)(xml, content, helpers);
|
|
94
96
|
break;
|
|
95
97
|
case "floatingImage":
|
|
96
|
-
(0, image_writer_1.renderFloatingImage)(xml, content, renderCtx.imageRIdRemap);
|
|
98
|
+
(0, image_writer_1.renderFloatingImage)(xml, content, renderCtx.imageRIdRemap, renderCtx.ids.nextDocPrId);
|
|
97
99
|
break;
|
|
98
100
|
case "tableOfContents":
|
|
99
101
|
(0, toc_writer_1.renderTableOfContents)(xml, content);
|
|
@@ -108,7 +110,13 @@ function renderBodyContent(xml, content, ctx) {
|
|
|
108
110
|
renderSdt(xml, content, renderCtx);
|
|
109
111
|
break;
|
|
110
112
|
case "checkBox":
|
|
113
|
+
// A checkbox renders as an inline (run-level) SDT whose sdtContent holds
|
|
114
|
+
// a run. At block level that run would be an illegal child of
|
|
115
|
+
// CT_SdtContentBlock, so wrap it in a paragraph — making it a valid
|
|
116
|
+
// run-level SDT inside a block-level paragraph.
|
|
117
|
+
xml.openNode("w:p");
|
|
111
118
|
(0, checkbox_writer_1.renderCheckBox)(xml, content);
|
|
119
|
+
xml.closeNode();
|
|
112
120
|
break;
|
|
113
121
|
case "drawingShape":
|
|
114
122
|
renderDrawingShape(xml, content, renderCtx);
|
|
@@ -295,7 +303,17 @@ function renderDrawingShape(xml, shape, ctx) {
|
|
|
295
303
|
// Shape properties
|
|
296
304
|
xml.openNode("wps:spPr");
|
|
297
305
|
// Transform
|
|
298
|
-
|
|
306
|
+
const xfrmAttrs = {};
|
|
307
|
+
if (shape.rotation) {
|
|
308
|
+
xfrmAttrs["rot"] = String(shape.rotation);
|
|
309
|
+
}
|
|
310
|
+
if (shape.flipHorizontal) {
|
|
311
|
+
xfrmAttrs["flipH"] = "1";
|
|
312
|
+
}
|
|
313
|
+
if (shape.flipVertical) {
|
|
314
|
+
xfrmAttrs["flipV"] = "1";
|
|
315
|
+
}
|
|
316
|
+
xml.openNode("a:xfrm", Object.keys(xfrmAttrs).length > 0 ? xfrmAttrs : {});
|
|
299
317
|
xml.leafNode("a:off", { x: "0", y: "0" });
|
|
300
318
|
xml.leafNode("a:ext", { cx: String(shape.width), cy: String(shape.height) });
|
|
301
319
|
xml.closeNode(); // a:xfrm
|
|
@@ -371,6 +389,7 @@ function renderDrawingShape(xml, shape, ctx) {
|
|
|
371
389
|
? {
|
|
372
390
|
imageRemap: ctx.imageRIdRemap,
|
|
373
391
|
hyperlinkRIds: ctx.hyperlinkRIds,
|
|
392
|
+
nextDocPrId: ctx.ids.nextDocPrId,
|
|
374
393
|
rawXmlPolicy: ctx.rawXmlPolicy
|
|
375
394
|
}
|
|
376
395
|
: undefined;
|
|
@@ -380,8 +399,13 @@ function renderDrawingShape(xml, shape, ctx) {
|
|
|
380
399
|
xml.closeNode(); // w:txbxContent
|
|
381
400
|
xml.closeNode(); // wps:txbx
|
|
382
401
|
}
|
|
383
|
-
// Body properties (required)
|
|
384
|
-
|
|
402
|
+
// Body properties (required). The vertical text anchor lives on a:bodyPr/@anchor.
|
|
403
|
+
if (shape.textBodyAnchor) {
|
|
404
|
+
xml.leafNode("wps:bodyPr", { anchor: shape.textBodyAnchor });
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
xml.leafNode("wps:bodyPr");
|
|
408
|
+
}
|
|
385
409
|
xml.closeNode(); // wps:wsp
|
|
386
410
|
xml.closeNode(); // a:graphicData
|
|
387
411
|
xml.closeNode(); // a:graphic
|
|
@@ -127,6 +127,24 @@ function collectHyperlinks(body) {
|
|
|
127
127
|
});
|
|
128
128
|
return links;
|
|
129
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* A minimal valid 1×1 transparent PNG.
|
|
132
|
+
*
|
|
133
|
+
* Used as an automatic raster fallback for SVG images that ship without an
|
|
134
|
+
* explicit `fallbackData`. In OOXML, `a:blip/@r:embed` must reference a raster
|
|
135
|
+
* image — Microsoft Word does not rasterize an SVG referenced directly by
|
|
136
|
+
* `a:blip`, so an SVG without a raster fallback renders as a broken/empty
|
|
137
|
+
* picture. By always pairing the SVG (`asvg:svgBlip`) with a raster blip we
|
|
138
|
+
* guarantee the drawing is renderable everywhere: SVG-aware Word shows the
|
|
139
|
+
* vector, older readers show the raster placeholder.
|
|
140
|
+
*/
|
|
141
|
+
const SVG_RASTER_FALLBACK_PNG = Uint8Array.from([
|
|
142
|
+
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
|
|
143
|
+
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1f, 0x15, 0xc4,
|
|
144
|
+
0x89, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x63, 0x60, 0x00, 0x02, 0x00,
|
|
145
|
+
0x00, 0x05, 0x00, 0x01, 0xe2, 0x26, 0x05, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44,
|
|
146
|
+
0xae, 0x42, 0x60, 0x82
|
|
147
|
+
]);
|
|
130
148
|
/** Infer a content type for an opaque part based on its file extension. */
|
|
131
149
|
function inferContentType(ext) {
|
|
132
150
|
const map = {
|
|
@@ -379,7 +397,12 @@ async function _packageDocxInner(doc, options) {
|
|
|
379
397
|
};
|
|
380
398
|
for (const img of doc.images) {
|
|
381
399
|
const oldRid = img.rId;
|
|
382
|
-
if (img.mediaType === "svg"
|
|
400
|
+
if (img.mediaType === "svg") {
|
|
401
|
+
// SVG must always be paired with a raster fallback that `a:blip`
|
|
402
|
+
// references; Word cannot rasterize an SVG referenced directly by
|
|
403
|
+
// a:blip. If the caller did not provide one, synthesize a minimal
|
|
404
|
+
// transparent PNG so the drawing is still valid and renderable.
|
|
405
|
+
const fallbackData = img.fallbackData ?? SVG_RASTER_FALLBACK_PNG;
|
|
383
406
|
// Main rId points at the PNG fallback (the raster image consumed by
|
|
384
407
|
// a:blip).
|
|
385
408
|
const baseName = img.fileName.replace(/\.[^.]+$/, "");
|
|
@@ -395,7 +418,7 @@ async function _packageDocxInner(doc, options) {
|
|
|
395
418
|
if (ext) {
|
|
396
419
|
imageExtensions.add(ext);
|
|
397
420
|
}
|
|
398
|
-
svgFallbacks.push({ fallbackFileName, data:
|
|
421
|
+
svgFallbacks.push({ fallbackFileName, data: fallbackData });
|
|
399
422
|
}
|
|
400
423
|
else {
|
|
401
424
|
registerImageRel(oldRid, `media/${img.fileName}`);
|
|
@@ -1200,14 +1223,24 @@ async function _packageDocxInner(doc, options) {
|
|
|
1200
1223
|
archive.add(constants_1.PartPath.Footnotes,
|
|
1201
1224
|
// Footnotes are an independent OPC part — their r:id values must
|
|
1202
1225
|
// resolve against word/_rels/footnotes.xml.rels, not document.xml.rels.
|
|
1203
|
-
renderXml(xml => (0, footnote_writer_1.renderFootnotes)(xml, doc.footnotes, {
|
|
1226
|
+
renderXml(xml => (0, footnote_writer_1.renderFootnotes)(xml, doc.footnotes, {
|
|
1227
|
+
imageRemap: new Map(),
|
|
1228
|
+
hyperlinkRIds,
|
|
1229
|
+
nextDocPrId: renderCtx.ids.nextDocPrId,
|
|
1230
|
+
rawXmlPolicy
|
|
1231
|
+
})));
|
|
1204
1232
|
if ((0, relationships_1.getRelationshipCount)(footnoteRels) > 0) {
|
|
1205
1233
|
archive.add("word/_rels/footnotes.xml.rels", renderXml(xml => (0, relationships_1.renderRelationships)(footnoteRels, xml)));
|
|
1206
1234
|
}
|
|
1207
1235
|
}
|
|
1208
1236
|
// word/endnotes.xml + endnotes.xml.rels
|
|
1209
1237
|
if (hasEndnotes) {
|
|
1210
|
-
archive.add(constants_1.PartPath.Endnotes, renderXml(xml => (0, footnote_writer_1.renderEndnotes)(xml, doc.endnotes, {
|
|
1238
|
+
archive.add(constants_1.PartPath.Endnotes, renderXml(xml => (0, footnote_writer_1.renderEndnotes)(xml, doc.endnotes, {
|
|
1239
|
+
imageRemap: new Map(),
|
|
1240
|
+
hyperlinkRIds,
|
|
1241
|
+
nextDocPrId: renderCtx.ids.nextDocPrId,
|
|
1242
|
+
rawXmlPolicy
|
|
1243
|
+
})));
|
|
1211
1244
|
if ((0, relationships_1.getRelationshipCount)(endnoteRels) > 0) {
|
|
1212
1245
|
archive.add("word/_rels/endnotes.xml.rels", renderXml(xml => (0, relationships_1.renderRelationships)(endnoteRels, xml)));
|
|
1213
1246
|
}
|
|
@@ -1218,7 +1251,12 @@ async function _packageDocxInner(doc, options) {
|
|
|
1218
1251
|
// Comments live in their own OPC part; pass helpers so embedded
|
|
1219
1252
|
// hyperlinks/images render with the right r:id (the rels manager
|
|
1220
1253
|
// below registered them under their model-original id).
|
|
1221
|
-
renderXml(xml => (0, comment_writer_1.renderComments)(xml, doc.comments, {
|
|
1254
|
+
renderXml(xml => (0, comment_writer_1.renderComments)(xml, doc.comments, {
|
|
1255
|
+
imageRemap: new Map(),
|
|
1256
|
+
hyperlinkRIds,
|
|
1257
|
+
nextDocPrId: renderCtx.ids.nextDocPrId,
|
|
1258
|
+
rawXmlPolicy
|
|
1259
|
+
})));
|
|
1222
1260
|
if ((0, relationships_1.getRelationshipCount)(commentRels) > 0) {
|
|
1223
1261
|
archive.add("word/_rels/comments.xml.rels", renderXml(xml => (0, relationships_1.renderRelationships)(commentRels, xml)));
|
|
1224
1262
|
}
|
|
@@ -1243,6 +1281,7 @@ async function _packageDocxInner(doc, options) {
|
|
|
1243
1281
|
renderXml(xml => (0, header_footer_writer_1.renderHeader)(xml, headerDef.content, {
|
|
1244
1282
|
imageRemap: new Map(),
|
|
1245
1283
|
hyperlinkRIds,
|
|
1284
|
+
nextDocPrId: renderCtx.ids.nextDocPrId,
|
|
1246
1285
|
rawXmlPolicy
|
|
1247
1286
|
})));
|
|
1248
1287
|
// Header .rels file
|
|
@@ -1266,6 +1305,7 @@ async function _packageDocxInner(doc, options) {
|
|
|
1266
1305
|
renderXml(xml => (0, header_footer_writer_1.renderFooter)(xml, footerDef.content, {
|
|
1267
1306
|
imageRemap: new Map(),
|
|
1268
1307
|
hyperlinkRIds,
|
|
1308
|
+
nextDocPrId: renderCtx.ids.nextDocPrId,
|
|
1269
1309
|
rawXmlPolicy
|
|
1270
1310
|
})));
|
|
1271
1311
|
// Footer .rels file
|
|
@@ -10,8 +10,8 @@ exports.renderFloatingImage = renderFloatingImage;
|
|
|
10
10
|
const constants_1 = require("../constants");
|
|
11
11
|
const units_1 = require("../units");
|
|
12
12
|
/** Render a floating image as a standalone paragraph with wp:anchor. */
|
|
13
|
-
function renderFloatingImage(xml, img, imageRemap) {
|
|
14
|
-
const drawingId = img.drawingId ?? 1;
|
|
13
|
+
function renderFloatingImage(xml, img, imageRemap, nextDocPrId) {
|
|
14
|
+
const drawingId = nextDocPrId?.() ?? img.drawingId ?? 1;
|
|
15
15
|
const name = img.name ?? "Picture";
|
|
16
16
|
// Resolve relationship id used in r:embed via packager-provided remap.
|
|
17
17
|
const embedRId = imageRemap?.get(img.rId) ?? img.rId;
|
|
@@ -323,8 +323,8 @@ function renderShading(xml, shd) {
|
|
|
323
323
|
});
|
|
324
324
|
}
|
|
325
325
|
/** Render an inline image (w:drawing > wp:inline). */
|
|
326
|
-
function renderInlineImage(xml, img, imageRemap) {
|
|
327
|
-
const drawingId = img.drawingId ?? 1;
|
|
326
|
+
function renderInlineImage(xml, img, imageRemap, nextDocPrId) {
|
|
327
|
+
const drawingId = nextDocPrId?.() ?? img.drawingId ?? 1;
|
|
328
328
|
const name = img.name ?? "Picture";
|
|
329
329
|
// Resolve the relationship id used in r:embed: prefer a packager-provided
|
|
330
330
|
// remap (used when the model rId clashed with an existing relationship in
|
|
@@ -488,7 +488,11 @@ function renderFfData(xml, ff) {
|
|
|
488
488
|
}
|
|
489
489
|
if (ff.entries) {
|
|
490
490
|
for (const entry of ff.entries) {
|
|
491
|
-
|
|
491
|
+
// Word rejects FORMDROPDOWN list entries with an empty value
|
|
492
|
+
// ("Word experienced an error trying to open the file"). Substitute a
|
|
493
|
+
// single space so an intended blank/placeholder item still renders and
|
|
494
|
+
// the entry indices (and `w:default`) stay aligned.
|
|
495
|
+
xml.leafNode("w:listEntry", { "w:val": entry === "" ? " " : entry });
|
|
492
496
|
}
|
|
493
497
|
}
|
|
494
498
|
xml.closeNode();
|
|
@@ -675,7 +679,7 @@ function renderRunContent(xml, content, helpers) {
|
|
|
675
679
|
}
|
|
676
680
|
return true;
|
|
677
681
|
}
|
|
678
|
-
renderInlineImage(xml, content, helpers?.imageRemap);
|
|
682
|
+
renderInlineImage(xml, content, helpers?.imageRemap, helpers?.nextDocPrId);
|
|
679
683
|
return true;
|
|
680
684
|
case "field":
|
|
681
685
|
// Fields create their own runs — must be rendered outside the current run
|