@cj-tech-master/excelts 9.1.0 → 9.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/README.md +16 -1
  2. package/dist/browser/modules/archive/compression/crc32.js +1 -1
  3. package/dist/browser/modules/archive/crypto/aes.d.ts +0 -8
  4. package/dist/browser/modules/archive/crypto/aes.js +1 -20
  5. package/dist/browser/modules/archive/crypto/index.d.ts +2 -1
  6. package/dist/browser/modules/archive/crypto/index.js +3 -1
  7. package/dist/browser/modules/csv/parse/row-processor.d.ts +1 -1
  8. package/dist/browser/modules/csv/worker/worker-script.generated.js +1 -1
  9. package/dist/browser/modules/excel/utils/cell-matrix.js +1 -0
  10. package/dist/browser/modules/excel/utils/encryptor.browser.d.ts +4 -5
  11. package/dist/browser/modules/excel/utils/encryptor.browser.js +7 -12
  12. package/dist/browser/modules/excel/utils/encryptor.d.ts +1 -1
  13. package/dist/browser/modules/excel/utils/encryptor.js +4 -7
  14. package/dist/browser/modules/pdf/builder/document-builder.d.ts +517 -0
  15. package/dist/browser/modules/pdf/builder/document-builder.js +1493 -0
  16. package/dist/browser/modules/pdf/builder/form-appearance.d.ts +56 -0
  17. package/dist/browser/modules/pdf/builder/form-appearance.js +140 -0
  18. package/dist/browser/modules/pdf/builder/image-utils.d.ts +39 -0
  19. package/dist/browser/modules/pdf/builder/image-utils.js +129 -0
  20. package/dist/browser/modules/pdf/builder/pdf-editor.d.ts +230 -0
  21. package/dist/browser/modules/pdf/builder/pdf-editor.js +1574 -0
  22. package/dist/browser/modules/pdf/builder/resource-merger.d.ts +41 -0
  23. package/dist/browser/modules/pdf/builder/resource-merger.js +258 -0
  24. package/dist/browser/modules/pdf/core/digital-signature.d.ts +109 -0
  25. package/dist/browser/modules/pdf/core/digital-signature.js +659 -0
  26. package/dist/browser/modules/pdf/core/encryption.js +8 -7
  27. package/dist/browser/modules/pdf/core/pdf-object.d.ts +11 -0
  28. package/dist/browser/modules/pdf/core/pdf-object.js +38 -0
  29. package/dist/browser/modules/pdf/core/pdf-stream.d.ts +32 -0
  30. package/dist/browser/modules/pdf/core/pdf-stream.js +66 -0
  31. package/dist/browser/modules/pdf/core/pdf-writer.d.ts +55 -1
  32. package/dist/browser/modules/pdf/core/pdf-writer.js +271 -6
  33. package/dist/browser/modules/pdf/core/pdfa.d.ts +62 -0
  34. package/dist/browser/modules/pdf/core/pdfa.js +261 -0
  35. package/dist/browser/modules/pdf/index.d.ts +11 -0
  36. package/dist/browser/modules/pdf/index.js +9 -0
  37. package/dist/browser/modules/pdf/reader/bookmark-extractor.d.ts +35 -0
  38. package/dist/browser/modules/pdf/reader/bookmark-extractor.js +324 -0
  39. package/dist/browser/modules/pdf/reader/pdf-decrypt.js +6 -5
  40. package/dist/browser/modules/pdf/reader/pdf-reader.d.ts +17 -0
  41. package/dist/browser/modules/pdf/reader/pdf-reader.js +26 -2
  42. package/dist/browser/modules/pdf/reader/table-extractor.d.ts +69 -0
  43. package/dist/browser/modules/pdf/reader/table-extractor.js +365 -0
  44. package/dist/browser/modules/pdf/render/layout-engine.d.ts +21 -1
  45. package/dist/browser/modules/pdf/render/layout-engine.js +112 -5
  46. package/dist/browser/modules/pdf/render/page-renderer.d.ts +2 -9
  47. package/dist/browser/modules/pdf/render/page-renderer.js +62 -103
  48. package/dist/browser/modules/pdf/render/pdf-exporter.js +2 -61
  49. package/dist/browser/modules/pdf/render/style-converter.d.ts +4 -0
  50. package/dist/browser/modules/pdf/render/style-converter.js +1 -1
  51. package/dist/browser/modules/pdf/types.d.ts +14 -1
  52. package/dist/browser/modules/stream/browser/readable.js +8 -2
  53. package/dist/browser/utils/crypto.browser.d.ts +64 -0
  54. package/dist/browser/{modules/pdf/core/crypto.js → utils/crypto.browser.js} +91 -101
  55. package/dist/browser/utils/crypto.d.ts +97 -0
  56. package/dist/browser/utils/crypto.js +209 -0
  57. package/dist/cjs/modules/archive/compression/crc32.js +1 -1
  58. package/dist/cjs/modules/archive/crypto/aes.js +2 -23
  59. package/dist/cjs/modules/archive/crypto/index.js +3 -1
  60. package/dist/cjs/modules/csv/worker/worker-script.generated.js +1 -1
  61. package/dist/cjs/modules/excel/utils/cell-matrix.js +1 -0
  62. package/dist/cjs/modules/excel/utils/encryptor.browser.js +7 -12
  63. package/dist/cjs/modules/excel/utils/encryptor.js +4 -10
  64. package/dist/cjs/modules/pdf/builder/document-builder.js +1532 -0
  65. package/dist/cjs/modules/pdf/builder/form-appearance.js +145 -0
  66. package/dist/cjs/modules/pdf/builder/image-utils.js +135 -0
  67. package/dist/cjs/modules/pdf/builder/pdf-editor.js +1612 -0
  68. package/dist/cjs/modules/pdf/builder/resource-merger.js +263 -0
  69. package/dist/cjs/modules/pdf/core/digital-signature.js +667 -0
  70. package/dist/cjs/modules/pdf/core/encryption.js +8 -7
  71. package/dist/cjs/modules/pdf/core/pdf-object.js +38 -0
  72. package/dist/cjs/modules/pdf/core/pdf-stream.js +66 -0
  73. package/dist/cjs/modules/pdf/core/pdf-writer.js +272 -6
  74. package/dist/cjs/modules/pdf/core/pdfa.js +266 -0
  75. package/dist/cjs/modules/pdf/index.js +19 -1
  76. package/dist/cjs/modules/pdf/reader/bookmark-extractor.js +327 -0
  77. package/dist/cjs/modules/pdf/reader/pdf-decrypt.js +6 -5
  78. package/dist/cjs/modules/pdf/reader/pdf-reader.js +26 -2
  79. package/dist/cjs/modules/pdf/reader/table-extractor.js +368 -0
  80. package/dist/cjs/modules/pdf/render/layout-engine.js +113 -4
  81. package/dist/cjs/modules/pdf/render/page-renderer.js +63 -105
  82. package/dist/cjs/modules/pdf/render/pdf-exporter.js +3 -62
  83. package/dist/cjs/modules/pdf/render/style-converter.js +1 -0
  84. package/dist/cjs/modules/stream/browser/readable.js +8 -2
  85. package/dist/cjs/{modules/pdf/core/crypto.js → utils/crypto.browser.js} +95 -102
  86. package/dist/cjs/utils/crypto.js +228 -0
  87. package/dist/esm/modules/archive/compression/crc32.js +1 -1
  88. package/dist/esm/modules/archive/crypto/aes.js +1 -20
  89. package/dist/esm/modules/archive/crypto/index.js +3 -1
  90. package/dist/esm/modules/csv/worker/worker-script.generated.js +1 -1
  91. package/dist/esm/modules/excel/utils/cell-matrix.js +1 -0
  92. package/dist/esm/modules/excel/utils/encryptor.browser.js +7 -12
  93. package/dist/esm/modules/excel/utils/encryptor.js +4 -7
  94. package/dist/esm/modules/pdf/builder/document-builder.js +1493 -0
  95. package/dist/esm/modules/pdf/builder/form-appearance.js +140 -0
  96. package/dist/esm/modules/pdf/builder/image-utils.js +129 -0
  97. package/dist/esm/modules/pdf/builder/pdf-editor.js +1574 -0
  98. package/dist/esm/modules/pdf/builder/resource-merger.js +258 -0
  99. package/dist/esm/modules/pdf/core/digital-signature.js +659 -0
  100. package/dist/esm/modules/pdf/core/encryption.js +8 -7
  101. package/dist/esm/modules/pdf/core/pdf-object.js +38 -0
  102. package/dist/esm/modules/pdf/core/pdf-stream.js +66 -0
  103. package/dist/esm/modules/pdf/core/pdf-writer.js +271 -6
  104. package/dist/esm/modules/pdf/core/pdfa.js +261 -0
  105. package/dist/esm/modules/pdf/index.js +9 -0
  106. package/dist/esm/modules/pdf/reader/bookmark-extractor.js +324 -0
  107. package/dist/esm/modules/pdf/reader/pdf-decrypt.js +6 -5
  108. package/dist/esm/modules/pdf/reader/pdf-reader.js +26 -2
  109. package/dist/esm/modules/pdf/reader/table-extractor.js +365 -0
  110. package/dist/esm/modules/pdf/render/layout-engine.js +112 -5
  111. package/dist/esm/modules/pdf/render/page-renderer.js +62 -103
  112. package/dist/esm/modules/pdf/render/pdf-exporter.js +2 -61
  113. package/dist/esm/modules/pdf/render/style-converter.js +1 -1
  114. package/dist/esm/modules/stream/browser/readable.js +8 -2
  115. package/dist/esm/{modules/pdf/core/crypto.js → utils/crypto.browser.js} +91 -101
  116. package/dist/esm/utils/crypto.js +209 -0
  117. package/dist/iife/excelts.iife.js +1248 -1074
  118. package/dist/iife/excelts.iife.js.map +1 -1
  119. package/dist/iife/excelts.iife.min.js +53 -54
  120. package/dist/types/modules/archive/crypto/aes.d.ts +0 -8
  121. package/dist/types/modules/archive/crypto/index.d.ts +2 -1
  122. package/dist/types/modules/csv/parse/row-processor.d.ts +1 -1
  123. package/dist/types/modules/excel/utils/encryptor.browser.d.ts +4 -5
  124. package/dist/types/modules/excel/utils/encryptor.d.ts +1 -1
  125. package/dist/types/modules/pdf/builder/document-builder.d.ts +517 -0
  126. package/dist/types/modules/pdf/builder/form-appearance.d.ts +56 -0
  127. package/dist/types/modules/pdf/builder/image-utils.d.ts +39 -0
  128. package/dist/types/modules/pdf/builder/pdf-editor.d.ts +230 -0
  129. package/dist/types/modules/pdf/builder/resource-merger.d.ts +41 -0
  130. package/dist/types/modules/pdf/core/digital-signature.d.ts +109 -0
  131. package/dist/types/modules/pdf/core/pdf-object.d.ts +11 -0
  132. package/dist/types/modules/pdf/core/pdf-stream.d.ts +32 -0
  133. package/dist/types/modules/pdf/core/pdf-writer.d.ts +55 -1
  134. package/dist/types/modules/pdf/core/pdfa.d.ts +62 -0
  135. package/dist/types/modules/pdf/index.d.ts +11 -0
  136. package/dist/types/modules/pdf/reader/bookmark-extractor.d.ts +35 -0
  137. package/dist/types/modules/pdf/reader/pdf-reader.d.ts +17 -0
  138. package/dist/types/modules/pdf/reader/table-extractor.d.ts +69 -0
  139. package/dist/types/modules/pdf/render/layout-engine.d.ts +21 -1
  140. package/dist/types/modules/pdf/render/page-renderer.d.ts +2 -9
  141. package/dist/types/modules/pdf/render/style-converter.d.ts +4 -0
  142. package/dist/types/modules/pdf/types.d.ts +14 -1
  143. package/dist/types/utils/crypto.browser.d.ts +64 -0
  144. package/dist/types/utils/crypto.d.ts +97 -0
  145. package/package.json +110 -111
  146. package/dist/browser/modules/pdf/core/crypto.d.ts +0 -65
  147. package/dist/types/modules/pdf/core/crypto.d.ts +0 -65
@@ -1,11 +1,14 @@
1
1
  /*!
2
- * @cj-tech-master/excelts v9.1.0
2
+ * @cj-tech-master/excelts v9.2.0
3
3
  * Zero-dependency TypeScript toolkit — Excel (XLSX), PDF, CSV, Markdown, XML, ZIP/TAR, and streaming.
4
4
  * (c) 2026 cjnoname
5
5
  * Released under the MIT License
6
6
  */
7
7
  var ExcelTS = (function(exports) {
8
8
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
9
+ //#region \0rolldown/runtime.js
10
+ var __esmMin = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
11
+ //#endregion
9
12
  //#region src/utils/errors.ts
10
13
  /**
11
14
  * Base class for all library errors.
@@ -4805,7 +4808,660 @@ var ExcelTS = (function(exports) {
4805
4808
  return cell.text;
4806
4809
  }
4807
4810
  //#endregion
4811
+ //#region src/utils/crypto.browser.ts
4812
+ function gf2(a) {
4813
+ return a < 128 ? a << 1 : a << 1 ^ 283;
4814
+ }
4815
+ function aesKeyExpansion(key) {
4816
+ const nk = key.length / 4;
4817
+ const nr = nk + 6;
4818
+ const w = [];
4819
+ for (let i = 0; i < nk; i++) w.push(new Uint8Array([
4820
+ key[4 * i],
4821
+ key[4 * i + 1],
4822
+ key[4 * i + 2],
4823
+ key[4 * i + 3]
4824
+ ]));
4825
+ for (let i = nk; i < 4 * (nr + 1); i++) {
4826
+ const temp = new Uint8Array(w[i - 1]);
4827
+ if (i % nk === 0) {
4828
+ const t0 = temp[0];
4829
+ temp[0] = SBOX[temp[1]] ^ RCON[i / nk - 1];
4830
+ temp[1] = SBOX[temp[2]];
4831
+ temp[2] = SBOX[temp[3]];
4832
+ temp[3] = SBOX[t0];
4833
+ } else if (nk > 6 && i % nk === 4) {
4834
+ temp[0] = SBOX[temp[0]];
4835
+ temp[1] = SBOX[temp[1]];
4836
+ temp[2] = SBOX[temp[2]];
4837
+ temp[3] = SBOX[temp[3]];
4838
+ }
4839
+ const word = new Uint8Array(4);
4840
+ for (let j = 0; j < 4; j++) word[j] = w[i - nk][j] ^ temp[j];
4841
+ w.push(word);
4842
+ }
4843
+ return w;
4844
+ }
4845
+ function aesEncryptBlock(block, roundKeys) {
4846
+ const nr = roundKeys.length / 4 - 1;
4847
+ const state = new Uint8Array(16);
4848
+ state.set(block);
4849
+ for (let c = 0; c < 4; c++) for (let r = 0; r < 4; r++) state[4 * c + r] ^= roundKeys[c][r];
4850
+ for (let round = 1; round < nr; round++) {
4851
+ for (let i = 0; i < 16; i++) state[i] = SBOX[state[i]];
4852
+ let tmp;
4853
+ tmp = state[1];
4854
+ state[1] = state[5];
4855
+ state[5] = state[9];
4856
+ state[9] = state[13];
4857
+ state[13] = tmp;
4858
+ tmp = state[2];
4859
+ state[2] = state[10];
4860
+ state[10] = tmp;
4861
+ tmp = state[6];
4862
+ state[6] = state[14];
4863
+ state[14] = tmp;
4864
+ tmp = state[15];
4865
+ state[15] = state[11];
4866
+ state[11] = state[7];
4867
+ state[7] = state[3];
4868
+ state[3] = tmp;
4869
+ for (let c = 0; c < 4; c++) {
4870
+ const s0 = state[4 * c];
4871
+ const s1 = state[4 * c + 1];
4872
+ const s2 = state[4 * c + 2];
4873
+ const s3 = state[4 * c + 3];
4874
+ state[4 * c] = gf2(s0) ^ gf2(s1) ^ s1 ^ s2 ^ s3;
4875
+ state[4 * c + 1] = s0 ^ gf2(s1) ^ gf2(s2) ^ s2 ^ s3;
4876
+ state[4 * c + 2] = s0 ^ s1 ^ gf2(s2) ^ gf2(s3) ^ s3;
4877
+ state[4 * c + 3] = gf2(s0) ^ s0 ^ s1 ^ s2 ^ gf2(s3);
4878
+ }
4879
+ const keyOffset = round * 4;
4880
+ for (let c = 0; c < 4; c++) for (let r = 0; r < 4; r++) state[4 * c + r] ^= roundKeys[keyOffset + c][r];
4881
+ }
4882
+ for (let i = 0; i < 16; i++) state[i] = SBOX[state[i]];
4883
+ let tmp;
4884
+ tmp = state[1];
4885
+ state[1] = state[5];
4886
+ state[5] = state[9];
4887
+ state[9] = state[13];
4888
+ state[13] = tmp;
4889
+ tmp = state[2];
4890
+ state[2] = state[10];
4891
+ state[10] = tmp;
4892
+ tmp = state[6];
4893
+ state[6] = state[14];
4894
+ state[14] = tmp;
4895
+ tmp = state[15];
4896
+ state[15] = state[11];
4897
+ state[11] = state[7];
4898
+ state[7] = state[3];
4899
+ state[3] = tmp;
4900
+ for (let c = 0; c < 4; c++) for (let r = 0; r < 4; r++) state[4 * c + r] ^= roundKeys[nr * 4 + c][r];
4901
+ return state;
4902
+ }
4903
+ function aesCbcEncrypt(plaintext, key, iv) {
4904
+ const padLen = 16 - plaintext.length % 16;
4905
+ const padded = new Uint8Array(plaintext.length + padLen);
4906
+ padded.set(plaintext);
4907
+ for (let i = plaintext.length; i < padded.length; i++) padded[i] = padLen;
4908
+ const roundKeys = aesKeyExpansion(key);
4909
+ const numBlocks = padded.length / 16;
4910
+ const output = new Uint8Array(padded.length);
4911
+ let prevBlock = iv;
4912
+ for (let b = 0; b < numBlocks; b++) {
4913
+ const block = new Uint8Array(16);
4914
+ for (let i = 0; i < 16; i++) block[i] = padded[b * 16 + i] ^ prevBlock[i];
4915
+ const encrypted = aesEncryptBlock(block, roundKeys);
4916
+ output.set(encrypted, b * 16);
4917
+ prevBlock = encrypted;
4918
+ }
4919
+ return output;
4920
+ }
4921
+ function aesCbcEncryptRaw(plaintext, key, iv) {
4922
+ if (plaintext.length % 16 !== 0) throw new Error("aesCbcEncryptRaw: plaintext length must be a multiple of 16");
4923
+ const roundKeys = aesKeyExpansion(key);
4924
+ const numBlocks = plaintext.length / 16;
4925
+ const output = new Uint8Array(plaintext.length);
4926
+ let prevBlock = iv;
4927
+ for (let b = 0; b < numBlocks; b++) {
4928
+ const block = new Uint8Array(16);
4929
+ for (let i = 0; i < 16; i++) block[i] = plaintext[b * 16 + i] ^ prevBlock[i];
4930
+ const encrypted = aesEncryptBlock(block, roundKeys);
4931
+ output.set(encrypted, b * 16);
4932
+ prevBlock = encrypted;
4933
+ }
4934
+ return output;
4935
+ }
4936
+ function aesEcbEncrypt(block, key) {
4937
+ return aesEncryptBlock(block, aesKeyExpansion(key));
4938
+ }
4939
+ function rotr32(x, n) {
4940
+ return (x >>> n | x << 32 - n) >>> 0;
4941
+ }
4942
+ function sha256(input) {
4943
+ const msgLen = input.length;
4944
+ const paddedLen = Math.ceil((msgLen + 9) / 64) * 64;
4945
+ const padded = new Uint8Array(paddedLen);
4946
+ padded.set(input);
4947
+ padded[msgLen] = 128;
4948
+ const bitLen = msgLen * 8;
4949
+ const view = new DataView(padded.buffer, padded.byteOffset, padded.byteLength);
4950
+ view.setUint32(paddedLen - 8, 0, false);
4951
+ view.setUint32(paddedLen - 4, bitLen, false);
4952
+ let h0 = SHA256_H[0];
4953
+ let h1 = SHA256_H[1];
4954
+ let h2 = SHA256_H[2];
4955
+ let h3 = SHA256_H[3];
4956
+ let h4 = SHA256_H[4];
4957
+ let h5 = SHA256_H[5];
4958
+ let h6 = SHA256_H[6];
4959
+ let h7 = SHA256_H[7];
4960
+ const w = new Uint32Array(64);
4961
+ for (let offset = 0; offset < paddedLen; offset += 64) {
4962
+ for (let i = 0; i < 16; i++) w[i] = view.getUint32(offset + i * 4, false);
4963
+ for (let i = 16; i < 64; i++) {
4964
+ const s0 = rotr32(w[i - 15], 7) ^ rotr32(w[i - 15], 18) ^ w[i - 15] >>> 3;
4965
+ const s1 = rotr32(w[i - 2], 17) ^ rotr32(w[i - 2], 19) ^ w[i - 2] >>> 10;
4966
+ w[i] = w[i - 16] + s0 + w[i - 7] + s1 >>> 0;
4967
+ }
4968
+ let a = h0;
4969
+ let b = h1;
4970
+ let c = h2;
4971
+ let d = h3;
4972
+ let e = h4;
4973
+ let f = h5;
4974
+ let g = h6;
4975
+ let h = h7;
4976
+ for (let i = 0; i < 64; i++) {
4977
+ const S1 = rotr32(e, 6) ^ rotr32(e, 11) ^ rotr32(e, 25);
4978
+ const ch = e & f ^ ~e & g;
4979
+ const temp1 = h + S1 + ch + SHA256_K[i] + w[i] >>> 0;
4980
+ const temp2 = (rotr32(a, 2) ^ rotr32(a, 13) ^ rotr32(a, 22)) + (a & b ^ a & c ^ b & c) >>> 0;
4981
+ h = g;
4982
+ g = f;
4983
+ f = e;
4984
+ e = d + temp1 >>> 0;
4985
+ d = c;
4986
+ c = b;
4987
+ b = a;
4988
+ a = temp1 + temp2 >>> 0;
4989
+ }
4990
+ h0 = h0 + a >>> 0;
4991
+ h1 = h1 + b >>> 0;
4992
+ h2 = h2 + c >>> 0;
4993
+ h3 = h3 + d >>> 0;
4994
+ h4 = h4 + e >>> 0;
4995
+ h5 = h5 + f >>> 0;
4996
+ h6 = h6 + g >>> 0;
4997
+ h7 = h7 + h >>> 0;
4998
+ }
4999
+ const result = new Uint8Array(32);
5000
+ const resultView = new DataView(result.buffer);
5001
+ resultView.setUint32(0, h0, false);
5002
+ resultView.setUint32(4, h1, false);
5003
+ resultView.setUint32(8, h2, false);
5004
+ resultView.setUint32(12, h3, false);
5005
+ resultView.setUint32(16, h4, false);
5006
+ resultView.setUint32(20, h5, false);
5007
+ resultView.setUint32(24, h6, false);
5008
+ resultView.setUint32(28, h7, false);
5009
+ return result;
5010
+ }
5011
+ /**
5012
+ * Generate cryptographically secure random bytes.
5013
+ * Uses crypto.getRandomValues (available in all modern browsers).
5014
+ */
5015
+ function randomBytes(length) {
5016
+ const bytes = new Uint8Array(length);
5017
+ globalThis.crypto.getRandomValues(bytes);
5018
+ return bytes;
5019
+ }
5020
+ /**
5021
+ * Compute a hash digest using Web Crypto API.
5022
+ *
5023
+ * NOTE: In the browser, this is async. The Node.js version is sync.
5024
+ * For callers that need sync hashing, use `sha256()` or `md5()` directly.
5025
+ *
5026
+ * @param algorithm - Hash algorithm name (e.g., "SHA-256", "SHA-512", "SHA-1").
5027
+ * @param data - Data to hash
5028
+ * @returns The digest bytes
5029
+ */
5030
+ async function hashAsync(algorithm, data) {
5031
+ const buf = await globalThis.crypto.subtle.digest(normalizeAlgorithmForWebCrypto(algorithm), data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength));
5032
+ return new Uint8Array(buf);
5033
+ }
5034
+ /**
5035
+ * Normalize a hash algorithm name to the format Web Crypto API expects.
5036
+ * Accepts: "sha256", "SHA-256", "sha-256", "SHA256" → "SHA-256"
5037
+ */
5038
+ function normalizeAlgorithmForWebCrypto(algorithm) {
5039
+ switch (algorithm.toLowerCase().replace(/-/g, "")) {
5040
+ case "sha1": return "SHA-1";
5041
+ case "sha256": return "SHA-256";
5042
+ case "sha384": return "SHA-384";
5043
+ case "sha512": return "SHA-512";
5044
+ default: return algorithm;
5045
+ }
5046
+ }
5047
+ var SBOX, RCON, SHA256_H, SHA256_K;
5048
+ var init_crypto_browser = __esmMin((() => {
5049
+ SBOX = new Uint8Array([
5050
+ 99,
5051
+ 124,
5052
+ 119,
5053
+ 123,
5054
+ 242,
5055
+ 107,
5056
+ 111,
5057
+ 197,
5058
+ 48,
5059
+ 1,
5060
+ 103,
5061
+ 43,
5062
+ 254,
5063
+ 215,
5064
+ 171,
5065
+ 118,
5066
+ 202,
5067
+ 130,
5068
+ 201,
5069
+ 125,
5070
+ 250,
5071
+ 89,
5072
+ 71,
5073
+ 240,
5074
+ 173,
5075
+ 212,
5076
+ 162,
5077
+ 175,
5078
+ 156,
5079
+ 164,
5080
+ 114,
5081
+ 192,
5082
+ 183,
5083
+ 253,
5084
+ 147,
5085
+ 38,
5086
+ 54,
5087
+ 63,
5088
+ 247,
5089
+ 204,
5090
+ 52,
5091
+ 165,
5092
+ 229,
5093
+ 241,
5094
+ 113,
5095
+ 216,
5096
+ 49,
5097
+ 21,
5098
+ 4,
5099
+ 199,
5100
+ 35,
5101
+ 195,
5102
+ 24,
5103
+ 150,
5104
+ 5,
5105
+ 154,
5106
+ 7,
5107
+ 18,
5108
+ 128,
5109
+ 226,
5110
+ 235,
5111
+ 39,
5112
+ 178,
5113
+ 117,
5114
+ 9,
5115
+ 131,
5116
+ 44,
5117
+ 26,
5118
+ 27,
5119
+ 110,
5120
+ 90,
5121
+ 160,
5122
+ 82,
5123
+ 59,
5124
+ 214,
5125
+ 179,
5126
+ 41,
5127
+ 227,
5128
+ 47,
5129
+ 132,
5130
+ 83,
5131
+ 209,
5132
+ 0,
5133
+ 237,
5134
+ 32,
5135
+ 252,
5136
+ 177,
5137
+ 91,
5138
+ 106,
5139
+ 203,
5140
+ 190,
5141
+ 57,
5142
+ 74,
5143
+ 76,
5144
+ 88,
5145
+ 207,
5146
+ 208,
5147
+ 239,
5148
+ 170,
5149
+ 251,
5150
+ 67,
5151
+ 77,
5152
+ 51,
5153
+ 133,
5154
+ 69,
5155
+ 249,
5156
+ 2,
5157
+ 127,
5158
+ 80,
5159
+ 60,
5160
+ 159,
5161
+ 168,
5162
+ 81,
5163
+ 163,
5164
+ 64,
5165
+ 143,
5166
+ 146,
5167
+ 157,
5168
+ 56,
5169
+ 245,
5170
+ 188,
5171
+ 182,
5172
+ 218,
5173
+ 33,
5174
+ 16,
5175
+ 255,
5176
+ 243,
5177
+ 210,
5178
+ 205,
5179
+ 12,
5180
+ 19,
5181
+ 236,
5182
+ 95,
5183
+ 151,
5184
+ 68,
5185
+ 23,
5186
+ 196,
5187
+ 167,
5188
+ 126,
5189
+ 61,
5190
+ 100,
5191
+ 93,
5192
+ 25,
5193
+ 115,
5194
+ 96,
5195
+ 129,
5196
+ 79,
5197
+ 220,
5198
+ 34,
5199
+ 42,
5200
+ 144,
5201
+ 136,
5202
+ 70,
5203
+ 238,
5204
+ 184,
5205
+ 20,
5206
+ 222,
5207
+ 94,
5208
+ 11,
5209
+ 219,
5210
+ 224,
5211
+ 50,
5212
+ 58,
5213
+ 10,
5214
+ 73,
5215
+ 6,
5216
+ 36,
5217
+ 92,
5218
+ 194,
5219
+ 211,
5220
+ 172,
5221
+ 98,
5222
+ 145,
5223
+ 149,
5224
+ 228,
5225
+ 121,
5226
+ 231,
5227
+ 200,
5228
+ 55,
5229
+ 109,
5230
+ 141,
5231
+ 213,
5232
+ 78,
5233
+ 169,
5234
+ 108,
5235
+ 86,
5236
+ 244,
5237
+ 234,
5238
+ 101,
5239
+ 122,
5240
+ 174,
5241
+ 8,
5242
+ 186,
5243
+ 120,
5244
+ 37,
5245
+ 46,
5246
+ 28,
5247
+ 166,
5248
+ 180,
5249
+ 198,
5250
+ 232,
5251
+ 221,
5252
+ 116,
5253
+ 31,
5254
+ 75,
5255
+ 189,
5256
+ 139,
5257
+ 138,
5258
+ 112,
5259
+ 62,
5260
+ 181,
5261
+ 102,
5262
+ 72,
5263
+ 3,
5264
+ 246,
5265
+ 14,
5266
+ 97,
5267
+ 53,
5268
+ 87,
5269
+ 185,
5270
+ 134,
5271
+ 193,
5272
+ 29,
5273
+ 158,
5274
+ 225,
5275
+ 248,
5276
+ 152,
5277
+ 17,
5278
+ 105,
5279
+ 217,
5280
+ 142,
5281
+ 148,
5282
+ 155,
5283
+ 30,
5284
+ 135,
5285
+ 233,
5286
+ 206,
5287
+ 85,
5288
+ 40,
5289
+ 223,
5290
+ 140,
5291
+ 161,
5292
+ 137,
5293
+ 13,
5294
+ 191,
5295
+ 230,
5296
+ 66,
5297
+ 104,
5298
+ 65,
5299
+ 153,
5300
+ 45,
5301
+ 15,
5302
+ 176,
5303
+ 84,
5304
+ 187,
5305
+ 22
5306
+ ]);
5307
+ RCON = [
5308
+ 1,
5309
+ 2,
5310
+ 4,
5311
+ 8,
5312
+ 16,
5313
+ 32,
5314
+ 64,
5315
+ 128,
5316
+ 27,
5317
+ 54
5318
+ ];
5319
+ SHA256_H = new Uint32Array([
5320
+ 1779033703,
5321
+ 3144134277,
5322
+ 1013904242,
5323
+ 2773480762,
5324
+ 1359893119,
5325
+ 2600822924,
5326
+ 528734635,
5327
+ 1541459225
5328
+ ]);
5329
+ SHA256_K = new Uint32Array([
5330
+ 1116352408,
5331
+ 1899447441,
5332
+ 3049323471,
5333
+ 3921009573,
5334
+ 961987163,
5335
+ 1508970993,
5336
+ 2453635748,
5337
+ 2870763221,
5338
+ 3624381080,
5339
+ 310598401,
5340
+ 607225278,
5341
+ 1426881987,
5342
+ 1925078388,
5343
+ 2162078206,
5344
+ 2614888103,
5345
+ 3248222580,
5346
+ 3835390401,
5347
+ 4022224774,
5348
+ 264347078,
5349
+ 604807628,
5350
+ 770255983,
5351
+ 1249150122,
5352
+ 1555081692,
5353
+ 1996064986,
5354
+ 2554220882,
5355
+ 2821834349,
5356
+ 2952996808,
5357
+ 3210313671,
5358
+ 3336571891,
5359
+ 3584528711,
5360
+ 113926993,
5361
+ 338241895,
5362
+ 666307205,
5363
+ 773529912,
5364
+ 1294757372,
5365
+ 1396182291,
5366
+ 1695183700,
5367
+ 1986661051,
5368
+ 2177026350,
5369
+ 2456956037,
5370
+ 2730485921,
5371
+ 2820302411,
5372
+ 3259730800,
5373
+ 3345764771,
5374
+ 3516065817,
5375
+ 3600352804,
5376
+ 4094571909,
5377
+ 275423344,
5378
+ 430227734,
5379
+ 506948616,
5380
+ 659060556,
5381
+ 883997877,
5382
+ 958139571,
5383
+ 1322822218,
5384
+ 1537002063,
5385
+ 1747873779,
5386
+ 1955562222,
5387
+ 2024104815,
5388
+ 2227730452,
5389
+ 2361852424,
5390
+ 2428436474,
5391
+ 2756734187,
5392
+ 3204031479,
5393
+ 3329325298
5394
+ ]);
5395
+ new Uint32Array([
5396
+ 3614090360,
5397
+ 3905402710,
5398
+ 606105819,
5399
+ 3250441966,
5400
+ 4118548399,
5401
+ 1200080426,
5402
+ 2821735955,
5403
+ 4249261313,
5404
+ 1770035416,
5405
+ 2336552879,
5406
+ 4294925233,
5407
+ 2304563134,
5408
+ 1804603682,
5409
+ 4254626195,
5410
+ 2792965006,
5411
+ 1236535329,
5412
+ 4129170786,
5413
+ 3225465664,
5414
+ 643717713,
5415
+ 3921069994,
5416
+ 3593408605,
5417
+ 38016083,
5418
+ 3634488961,
5419
+ 3889429448,
5420
+ 568446438,
5421
+ 3275163606,
5422
+ 4107603335,
5423
+ 1163531501,
5424
+ 2850285829,
5425
+ 4243563512,
5426
+ 1735328473,
5427
+ 2368359562,
5428
+ 4294588738,
5429
+ 2272392833,
5430
+ 1839030562,
5431
+ 4259657740,
5432
+ 2763975236,
5433
+ 1272893353,
5434
+ 4139469664,
5435
+ 3200236656,
5436
+ 681279174,
5437
+ 3936430074,
5438
+ 3572445317,
5439
+ 76029189,
5440
+ 3654602809,
5441
+ 3873151461,
5442
+ 530742520,
5443
+ 3299628645,
5444
+ 4096336452,
5445
+ 1126891415,
5446
+ 2878612391,
5447
+ 4237533241,
5448
+ 1700485571,
5449
+ 2399980690,
5450
+ 4293915773,
5451
+ 2240044497,
5452
+ 1873313359,
5453
+ 4264355552,
5454
+ 2734768916,
5455
+ 1309151649,
5456
+ 4149444226,
5457
+ 3174756917,
5458
+ 718787259,
5459
+ 3951481745
5460
+ ]);
5461
+ }));
5462
+ //#endregion
4808
5463
  //#region src/utils/binary.ts
5464
+ init_crypto_browser();
4809
5465
  /**
4810
5466
  * Binary Utilities
4811
5467
  *
@@ -5056,8 +5712,7 @@ var ExcelTS = (function(exports) {
5056
5712
  //#endregion
5057
5713
  //#region src/modules/excel/utils/encryptor.browser.ts
5058
5714
  /**
5059
- * Browser-only Encryptor
5060
- * Uses Web Crypto API (hardware accelerated)
5715
+ * Browser-only Encryptor — uses shared crypto primitives from `@utils/crypto`.
5061
5716
  */
5062
5717
  function uint32ToLe(num) {
5063
5718
  const arr = new Uint8Array(4);
@@ -5069,9 +5724,7 @@ var ExcelTS = (function(exports) {
5069
5724
  }
5070
5725
  const Encryptor = {
5071
5726
  async hash(algorithm, ...buffers) {
5072
- const data = concatUint8Arrays(buffers);
5073
- const hashBuffer = await crypto.subtle.digest(algorithm, new Uint8Array(data));
5074
- return new Uint8Array(hashBuffer);
5727
+ return hashAsync(algorithm, concatUint8Arrays(buffers));
5075
5728
  },
5076
5729
  async convertPasswordToHash(password, hashAlgorithm, saltValue, spinCount) {
5077
5730
  const passwordBuffer = stringToUtf16Le(password);
@@ -5081,9 +5734,7 @@ var ExcelTS = (function(exports) {
5081
5734
  return uint8ArrayToBase64(key);
5082
5735
  },
5083
5736
  randomBytes(size) {
5084
- const bytes = new Uint8Array(size);
5085
- crypto.getRandomValues(bytes);
5086
- return bytes;
5737
+ return randomBytes(size);
5087
5738
  }
5088
5739
  };
5089
5740
  //#endregion
@@ -27863,6 +28514,7 @@ self.onmessage = async function(event) {
27863
28514
  *
27864
28515
  * Works in both Node.js and browsers using the Web Crypto API.
27865
28516
  */
28517
+ init_crypto_browser();
27866
28518
  /**
27867
28519
  * AES vendor ID for WinZip format.
27868
28520
  */
@@ -27911,22 +28563,6 @@ self.onmessage = async function(event) {
27911
28563
  throw new Error("Web Crypto API not available");
27912
28564
  }
27913
28565
  /**
27914
- * Get crypto.getRandomValues (works in both Node.js and browsers).
27915
- */
27916
- function getRandomValues(array) {
27917
- if (typeof globalThis.crypto?.getRandomValues !== "undefined") {
27918
- globalThis.crypto.getRandomValues(array);
27919
- return array;
27920
- }
27921
- throw new Error("crypto.getRandomValues not available");
27922
- }
27923
- /**
27924
- * Generate random bytes.
27925
- */
27926
- function randomBytes$1(length) {
27927
- return getRandomValues(new Uint8Array(length));
27928
- }
27929
- /**
27930
28566
  * Derive AES keys from password using PBKDF2.
27931
28567
  *
27932
28568
  * The derived key material is split into:
@@ -28077,7 +28713,7 @@ self.onmessage = async function(event) {
28077
28713
  */
28078
28714
  async function aesEncrypt(data, password, keyStrength) {
28079
28715
  const saltLen = AES_SALT_LENGTH[keyStrength];
28080
- const salt = randomBytes$1(saltLen);
28716
+ const salt = randomBytes(saltLen);
28081
28717
  const keys = await aesDerive(password, salt, keyStrength);
28082
28718
  const ciphertext = await aesCtr(keys.encryptionKey, data, true);
28083
28719
  const hmac = await aesComputeHmac(keys.hmacKey, ciphertext);
@@ -29017,7 +29653,7 @@ self.onmessage = async function(event) {
29017
29653
  if (this._zipCryptoState || this._encryptionMethod !== "zipcrypto") return;
29018
29654
  this._zipCryptoState = zipCryptoInitKeys(this._password);
29019
29655
  const dosTimeForCheck = this.dosTime << 16 | this.dosDate;
29020
- const header = zipCryptoCreateHeader(this._zipCryptoState, dosTimeForCheck, randomBytes$1);
29656
+ const header = zipCryptoCreateHeader(this._zipCryptoState, dosTimeForCheck, randomBytes);
29021
29657
  this._compressedSize += header.length;
29022
29658
  this._enqueueData(header, false);
29023
29659
  }
@@ -46284,15 +46920,31 @@ onmessage = async (ev) => {
46284
46920
  * Builds a PDF dictionary object from key-value pairs.
46285
46921
  * Values are already-serialized PDF strings.
46286
46922
  */
46287
- var PdfDict = class {
46923
+ var PdfDict = class PdfDict {
46288
46924
  constructor() {
46289
46925
  this.entries = [];
46926
+ this._raw = null;
46927
+ }
46928
+ /**
46929
+ * Create a PdfDict that wraps a pre-serialized dictionary string.
46930
+ * toString() returns the raw string (with any set() overrides applied).
46931
+ */
46932
+ static fromRawString(raw) {
46933
+ const d = new PdfDict();
46934
+ d._raw = raw;
46935
+ return d;
46290
46936
  }
46291
46937
  /**
46292
46938
  * Set a dictionary entry. The key should NOT include the leading /.
46293
46939
  * The value should be a pre-serialized PDF value string.
46294
46940
  */
46295
46941
  set(key, value) {
46942
+ if (this._raw !== null) {
46943
+ const keyPattern = new RegExp(`(/${key})\\s+\\S+(?:\\s+\\d+\\s+R)?`);
46944
+ if (keyPattern.test(this._raw)) this._raw = this._raw.replace(keyPattern, `/${key} ${value}`);
46945
+ else this._raw = this._raw.replace(/>>$/, `\n${pdfName(key)} ${value}\n>>`);
46946
+ return this;
46947
+ }
46296
46948
  const idx = this.entries.findIndex(([k]) => k === key);
46297
46949
  if (idx >= 0) this.entries[idx] = [key, value];
46298
46950
  else this.entries.push([key, value]);
@@ -46306,9 +46958,18 @@ onmessage = async (ev) => {
46306
46958
  return this;
46307
46959
  }
46308
46960
  /**
46961
+ * Remove a dictionary entry by key.
46962
+ */
46963
+ delete(key) {
46964
+ const idx = this.entries.findIndex(([k]) => k === key);
46965
+ if (idx >= 0) this.entries.splice(idx, 1);
46966
+ return this;
46967
+ }
46968
+ /**
46309
46969
  * Serialize to a PDF dictionary string.
46310
46970
  */
46311
46971
  toString() {
46972
+ if (this._raw !== null) return this._raw;
46312
46973
  const parts = ["<<"];
46313
46974
  for (const [key, value] of this.entries) parts.push(`${pdfName(key)} ${value}`);
46314
46975
  parts.push(">>");
@@ -46363,702 +47024,8 @@ onmessage = async (ev) => {
46363
47024
  return err instanceof PdfError;
46364
47025
  }
46365
47026
  //#endregion
46366
- //#region src/modules/pdf/core/crypto.ts
46367
- /**
46368
- * Shared cryptographic primitives for PDF encryption/decryption.
46369
- *
46370
- * Zero-dependency, pure JavaScript implementations of:
46371
- * - AES (128/256-bit) CBC encrypt and decrypt
46372
- * - SHA-256
46373
- * - MD5
46374
- * - RC4 (for reading legacy PDFs only)
46375
- *
46376
- * @see FIPS 197 — AES
46377
- * @see FIPS 180-4 — SHA-256
46378
- * @see RFC 1321 — MD5
46379
- */
46380
- /** AES S-Box */
46381
- const SBOX = new Uint8Array([
46382
- 99,
46383
- 124,
46384
- 119,
46385
- 123,
46386
- 242,
46387
- 107,
46388
- 111,
46389
- 197,
46390
- 48,
46391
- 1,
46392
- 103,
46393
- 43,
46394
- 254,
46395
- 215,
46396
- 171,
46397
- 118,
46398
- 202,
46399
- 130,
46400
- 201,
46401
- 125,
46402
- 250,
46403
- 89,
46404
- 71,
46405
- 240,
46406
- 173,
46407
- 212,
46408
- 162,
46409
- 175,
46410
- 156,
46411
- 164,
46412
- 114,
46413
- 192,
46414
- 183,
46415
- 253,
46416
- 147,
46417
- 38,
46418
- 54,
46419
- 63,
46420
- 247,
46421
- 204,
46422
- 52,
46423
- 165,
46424
- 229,
46425
- 241,
46426
- 113,
46427
- 216,
46428
- 49,
46429
- 21,
46430
- 4,
46431
- 199,
46432
- 35,
46433
- 195,
46434
- 24,
46435
- 150,
46436
- 5,
46437
- 154,
46438
- 7,
46439
- 18,
46440
- 128,
46441
- 226,
46442
- 235,
46443
- 39,
46444
- 178,
46445
- 117,
46446
- 9,
46447
- 131,
46448
- 44,
46449
- 26,
46450
- 27,
46451
- 110,
46452
- 90,
46453
- 160,
46454
- 82,
46455
- 59,
46456
- 214,
46457
- 179,
46458
- 41,
46459
- 227,
46460
- 47,
46461
- 132,
46462
- 83,
46463
- 209,
46464
- 0,
46465
- 237,
46466
- 32,
46467
- 252,
46468
- 177,
46469
- 91,
46470
- 106,
46471
- 203,
46472
- 190,
46473
- 57,
46474
- 74,
46475
- 76,
46476
- 88,
46477
- 207,
46478
- 208,
46479
- 239,
46480
- 170,
46481
- 251,
46482
- 67,
46483
- 77,
46484
- 51,
46485
- 133,
46486
- 69,
46487
- 249,
46488
- 2,
46489
- 127,
46490
- 80,
46491
- 60,
46492
- 159,
46493
- 168,
46494
- 81,
46495
- 163,
46496
- 64,
46497
- 143,
46498
- 146,
46499
- 157,
46500
- 56,
46501
- 245,
46502
- 188,
46503
- 182,
46504
- 218,
46505
- 33,
46506
- 16,
46507
- 255,
46508
- 243,
46509
- 210,
46510
- 205,
46511
- 12,
46512
- 19,
46513
- 236,
46514
- 95,
46515
- 151,
46516
- 68,
46517
- 23,
46518
- 196,
46519
- 167,
46520
- 126,
46521
- 61,
46522
- 100,
46523
- 93,
46524
- 25,
46525
- 115,
46526
- 96,
46527
- 129,
46528
- 79,
46529
- 220,
46530
- 34,
46531
- 42,
46532
- 144,
46533
- 136,
46534
- 70,
46535
- 238,
46536
- 184,
46537
- 20,
46538
- 222,
46539
- 94,
46540
- 11,
46541
- 219,
46542
- 224,
46543
- 50,
46544
- 58,
46545
- 10,
46546
- 73,
46547
- 6,
46548
- 36,
46549
- 92,
46550
- 194,
46551
- 211,
46552
- 172,
46553
- 98,
46554
- 145,
46555
- 149,
46556
- 228,
46557
- 121,
46558
- 231,
46559
- 200,
46560
- 55,
46561
- 109,
46562
- 141,
46563
- 213,
46564
- 78,
46565
- 169,
46566
- 108,
46567
- 86,
46568
- 244,
46569
- 234,
46570
- 101,
46571
- 122,
46572
- 174,
46573
- 8,
46574
- 186,
46575
- 120,
46576
- 37,
46577
- 46,
46578
- 28,
46579
- 166,
46580
- 180,
46581
- 198,
46582
- 232,
46583
- 221,
46584
- 116,
46585
- 31,
46586
- 75,
46587
- 189,
46588
- 139,
46589
- 138,
46590
- 112,
46591
- 62,
46592
- 181,
46593
- 102,
46594
- 72,
46595
- 3,
46596
- 246,
46597
- 14,
46598
- 97,
46599
- 53,
46600
- 87,
46601
- 185,
46602
- 134,
46603
- 193,
46604
- 29,
46605
- 158,
46606
- 225,
46607
- 248,
46608
- 152,
46609
- 17,
46610
- 105,
46611
- 217,
46612
- 142,
46613
- 148,
46614
- 155,
46615
- 30,
46616
- 135,
46617
- 233,
46618
- 206,
46619
- 85,
46620
- 40,
46621
- 223,
46622
- 140,
46623
- 161,
46624
- 137,
46625
- 13,
46626
- 191,
46627
- 230,
46628
- 66,
46629
- 104,
46630
- 65,
46631
- 153,
46632
- 45,
46633
- 15,
46634
- 176,
46635
- 84,
46636
- 187,
46637
- 22
46638
- ]);
46639
- /** AES round constants */
46640
- const RCON = [
46641
- 1,
46642
- 2,
46643
- 4,
46644
- 8,
46645
- 16,
46646
- 32,
46647
- 64,
46648
- 128,
46649
- 27,
46650
- 54
46651
- ];
46652
- /** GF(2^8) multiplication by 2 */
46653
- function gf2(a) {
46654
- return a < 128 ? a << 1 : a << 1 ^ 283;
46655
- }
46656
- /**
46657
- * AES key expansion. Supports AES-128 (16-byte key) and AES-256 (32-byte key).
46658
- */
46659
- function aesKeyExpansion(key) {
46660
- const nk = key.length / 4;
46661
- const nr = nk + 6;
46662
- const w = [];
46663
- for (let i = 0; i < nk; i++) w.push(new Uint8Array([
46664
- key[4 * i],
46665
- key[4 * i + 1],
46666
- key[4 * i + 2],
46667
- key[4 * i + 3]
46668
- ]));
46669
- for (let i = nk; i < 4 * (nr + 1); i++) {
46670
- const temp = new Uint8Array(w[i - 1]);
46671
- if (i % nk === 0) {
46672
- const t0 = temp[0];
46673
- temp[0] = SBOX[temp[1]] ^ RCON[i / nk - 1];
46674
- temp[1] = SBOX[temp[2]];
46675
- temp[2] = SBOX[temp[3]];
46676
- temp[3] = SBOX[t0];
46677
- } else if (nk > 6 && i % nk === 4) {
46678
- temp[0] = SBOX[temp[0]];
46679
- temp[1] = SBOX[temp[1]];
46680
- temp[2] = SBOX[temp[2]];
46681
- temp[3] = SBOX[temp[3]];
46682
- }
46683
- const word = new Uint8Array(4);
46684
- for (let j = 0; j < 4; j++) word[j] = w[i - nk][j] ^ temp[j];
46685
- w.push(word);
46686
- }
46687
- return w;
46688
- }
46689
- /**
46690
- * Encrypt a single AES block (16 bytes).
46691
- * State layout: column-major per FIPS 197 §3.4.
46692
- */
46693
- function aesEncryptBlock(block, roundKeys) {
46694
- const nr = roundKeys.length / 4 - 1;
46695
- const state = new Uint8Array(16);
46696
- state.set(block);
46697
- for (let c = 0; c < 4; c++) for (let r = 0; r < 4; r++) state[4 * c + r] ^= roundKeys[c][r];
46698
- for (let round = 1; round < nr; round++) {
46699
- for (let i = 0; i < 16; i++) state[i] = SBOX[state[i]];
46700
- let tmp;
46701
- tmp = state[1];
46702
- state[1] = state[5];
46703
- state[5] = state[9];
46704
- state[9] = state[13];
46705
- state[13] = tmp;
46706
- tmp = state[2];
46707
- state[2] = state[10];
46708
- state[10] = tmp;
46709
- tmp = state[6];
46710
- state[6] = state[14];
46711
- state[14] = tmp;
46712
- tmp = state[15];
46713
- state[15] = state[11];
46714
- state[11] = state[7];
46715
- state[7] = state[3];
46716
- state[3] = tmp;
46717
- for (let c = 0; c < 4; c++) {
46718
- const s0 = state[4 * c];
46719
- const s1 = state[4 * c + 1];
46720
- const s2 = state[4 * c + 2];
46721
- const s3 = state[4 * c + 3];
46722
- state[4 * c] = gf2(s0) ^ gf2(s1) ^ s1 ^ s2 ^ s3;
46723
- state[4 * c + 1] = s0 ^ gf2(s1) ^ gf2(s2) ^ s2 ^ s3;
46724
- state[4 * c + 2] = s0 ^ s1 ^ gf2(s2) ^ gf2(s3) ^ s3;
46725
- state[4 * c + 3] = gf2(s0) ^ s0 ^ s1 ^ s2 ^ gf2(s3);
46726
- }
46727
- const keyOffset = round * 4;
46728
- for (let c = 0; c < 4; c++) for (let r = 0; r < 4; r++) state[4 * c + r] ^= roundKeys[keyOffset + c][r];
46729
- }
46730
- for (let i = 0; i < 16; i++) state[i] = SBOX[state[i]];
46731
- let tmp;
46732
- tmp = state[1];
46733
- state[1] = state[5];
46734
- state[5] = state[9];
46735
- state[9] = state[13];
46736
- state[13] = tmp;
46737
- tmp = state[2];
46738
- state[2] = state[10];
46739
- state[10] = tmp;
46740
- tmp = state[6];
46741
- state[6] = state[14];
46742
- state[14] = tmp;
46743
- tmp = state[15];
46744
- state[15] = state[11];
46745
- state[11] = state[7];
46746
- state[7] = state[3];
46747
- state[3] = tmp;
46748
- for (let c = 0; c < 4; c++) for (let r = 0; r < 4; r++) state[4 * c + r] ^= roundKeys[nr * 4 + c][r];
46749
- return state;
46750
- }
46751
- /**
46752
- * AES-CBC encryption with PKCS#7 padding.
46753
- * Supports AES-128 (16-byte key) and AES-256 (32-byte key).
46754
- */
46755
- function aesCbcEncrypt(plaintext, key, iv) {
46756
- const padLen = 16 - plaintext.length % 16;
46757
- const padded = new Uint8Array(plaintext.length + padLen);
46758
- padded.set(plaintext);
46759
- for (let i = plaintext.length; i < padded.length; i++) padded[i] = padLen;
46760
- const roundKeys = aesKeyExpansion(key);
46761
- const numBlocks = padded.length / 16;
46762
- const output = new Uint8Array(padded.length);
46763
- let prevBlock = iv;
46764
- for (let b = 0; b < numBlocks; b++) {
46765
- const block = new Uint8Array(16);
46766
- for (let i = 0; i < 16; i++) block[i] = padded[b * 16 + i] ^ prevBlock[i];
46767
- const encrypted = aesEncryptBlock(block, roundKeys);
46768
- output.set(encrypted, b * 16);
46769
- prevBlock = encrypted;
46770
- }
46771
- return output;
46772
- }
46773
- /**
46774
- * AES-CBC encryption WITHOUT PKCS#7 padding.
46775
- * Used when the plaintext is already block-aligned (e.g., encrypting
46776
- * the 32-byte file encryption key in V=5).
46777
- *
46778
- * @throws if plaintext length is not a multiple of 16.
46779
- */
46780
- function aesCbcEncryptRaw(plaintext, key, iv) {
46781
- if (plaintext.length % 16 !== 0) throw new Error("aesCbcEncryptRaw: plaintext length must be a multiple of 16");
46782
- const roundKeys = aesKeyExpansion(key);
46783
- const numBlocks = plaintext.length / 16;
46784
- const output = new Uint8Array(plaintext.length);
46785
- let prevBlock = iv;
46786
- for (let b = 0; b < numBlocks; b++) {
46787
- const block = new Uint8Array(16);
46788
- for (let i = 0; i < 16; i++) block[i] = plaintext[b * 16 + i] ^ prevBlock[i];
46789
- const encrypted = aesEncryptBlock(block, roundKeys);
46790
- output.set(encrypted, b * 16);
46791
- prevBlock = encrypted;
46792
- }
46793
- return output;
46794
- }
46795
- /**
46796
- * AES-ECB encryption of a single 16-byte block (no padding, no IV).
46797
- * Used for the /Perms value in V=5 encryption.
46798
- */
46799
- function aesEcbEncrypt(block, key) {
46800
- return aesEncryptBlock(block, aesKeyExpansion(key));
46801
- }
46802
- /** SHA-256 initial hash values */
46803
- const SHA256_H = new Uint32Array([
46804
- 1779033703,
46805
- 3144134277,
46806
- 1013904242,
46807
- 2773480762,
46808
- 1359893119,
46809
- 2600822924,
46810
- 528734635,
46811
- 1541459225
46812
- ]);
46813
- /** SHA-256 round constants */
46814
- const SHA256_K = new Uint32Array([
46815
- 1116352408,
46816
- 1899447441,
46817
- 3049323471,
46818
- 3921009573,
46819
- 961987163,
46820
- 1508970993,
46821
- 2453635748,
46822
- 2870763221,
46823
- 3624381080,
46824
- 310598401,
46825
- 607225278,
46826
- 1426881987,
46827
- 1925078388,
46828
- 2162078206,
46829
- 2614888103,
46830
- 3248222580,
46831
- 3835390401,
46832
- 4022224774,
46833
- 264347078,
46834
- 604807628,
46835
- 770255983,
46836
- 1249150122,
46837
- 1555081692,
46838
- 1996064986,
46839
- 2554220882,
46840
- 2821834349,
46841
- 2952996808,
46842
- 3210313671,
46843
- 3336571891,
46844
- 3584528711,
46845
- 113926993,
46846
- 338241895,
46847
- 666307205,
46848
- 773529912,
46849
- 1294757372,
46850
- 1396182291,
46851
- 1695183700,
46852
- 1986661051,
46853
- 2177026350,
46854
- 2456956037,
46855
- 2730485921,
46856
- 2820302411,
46857
- 3259730800,
46858
- 3345764771,
46859
- 3516065817,
46860
- 3600352804,
46861
- 4094571909,
46862
- 275423344,
46863
- 430227734,
46864
- 506948616,
46865
- 659060556,
46866
- 883997877,
46867
- 958139571,
46868
- 1322822218,
46869
- 1537002063,
46870
- 1747873779,
46871
- 1955562222,
46872
- 2024104815,
46873
- 2227730452,
46874
- 2361852424,
46875
- 2428436474,
46876
- 2756734187,
46877
- 3204031479,
46878
- 3329325298
46879
- ]);
46880
- function rotr32(x, n) {
46881
- return (x >>> n | x << 32 - n) >>> 0;
46882
- }
46883
- /**
46884
- * SHA-256 hash function.
46885
- * @returns 32-byte digest
46886
- */
46887
- function sha256(input) {
46888
- const msgLen = input.length;
46889
- const paddedLen = Math.ceil((msgLen + 9) / 64) * 64;
46890
- const padded = new Uint8Array(paddedLen);
46891
- padded.set(input);
46892
- padded[msgLen] = 128;
46893
- const bitLen = msgLen * 8;
46894
- const view = new DataView(padded.buffer, padded.byteOffset, padded.byteLength);
46895
- view.setUint32(paddedLen - 8, 0, false);
46896
- view.setUint32(paddedLen - 4, bitLen, false);
46897
- let h0 = SHA256_H[0];
46898
- let h1 = SHA256_H[1];
46899
- let h2 = SHA256_H[2];
46900
- let h3 = SHA256_H[3];
46901
- let h4 = SHA256_H[4];
46902
- let h5 = SHA256_H[5];
46903
- let h6 = SHA256_H[6];
46904
- let h7 = SHA256_H[7];
46905
- const w = new Uint32Array(64);
46906
- for (let offset = 0; offset < paddedLen; offset += 64) {
46907
- for (let i = 0; i < 16; i++) w[i] = view.getUint32(offset + i * 4, false);
46908
- for (let i = 16; i < 64; i++) {
46909
- const s0 = rotr32(w[i - 15], 7) ^ rotr32(w[i - 15], 18) ^ w[i - 15] >>> 3;
46910
- const s1 = rotr32(w[i - 2], 17) ^ rotr32(w[i - 2], 19) ^ w[i - 2] >>> 10;
46911
- w[i] = w[i - 16] + s0 + w[i - 7] + s1 >>> 0;
46912
- }
46913
- let a = h0;
46914
- let b = h1;
46915
- let c = h2;
46916
- let d = h3;
46917
- let e = h4;
46918
- let f = h5;
46919
- let g = h6;
46920
- let h = h7;
46921
- for (let i = 0; i < 64; i++) {
46922
- const S1 = rotr32(e, 6) ^ rotr32(e, 11) ^ rotr32(e, 25);
46923
- const ch = e & f ^ ~e & g;
46924
- const temp1 = h + S1 + ch + SHA256_K[i] + w[i] >>> 0;
46925
- const temp2 = (rotr32(a, 2) ^ rotr32(a, 13) ^ rotr32(a, 22)) + (a & b ^ a & c ^ b & c) >>> 0;
46926
- h = g;
46927
- g = f;
46928
- f = e;
46929
- e = d + temp1 >>> 0;
46930
- d = c;
46931
- c = b;
46932
- b = a;
46933
- a = temp1 + temp2 >>> 0;
46934
- }
46935
- h0 = h0 + a >>> 0;
46936
- h1 = h1 + b >>> 0;
46937
- h2 = h2 + c >>> 0;
46938
- h3 = h3 + d >>> 0;
46939
- h4 = h4 + e >>> 0;
46940
- h5 = h5 + f >>> 0;
46941
- h6 = h6 + g >>> 0;
46942
- h7 = h7 + h >>> 0;
46943
- }
46944
- const result = new Uint8Array(32);
46945
- const resultView = new DataView(result.buffer);
46946
- resultView.setUint32(0, h0, false);
46947
- resultView.setUint32(4, h1, false);
46948
- resultView.setUint32(8, h2, false);
46949
- resultView.setUint32(12, h3, false);
46950
- resultView.setUint32(16, h4, false);
46951
- resultView.setUint32(20, h5, false);
46952
- resultView.setUint32(24, h6, false);
46953
- resultView.setUint32(28, h7, false);
46954
- return result;
46955
- }
46956
- new Uint32Array([
46957
- 3614090360,
46958
- 3905402710,
46959
- 606105819,
46960
- 3250441966,
46961
- 4118548399,
46962
- 1200080426,
46963
- 2821735955,
46964
- 4249261313,
46965
- 1770035416,
46966
- 2336552879,
46967
- 4294925233,
46968
- 2304563134,
46969
- 1804603682,
46970
- 4254626195,
46971
- 2792965006,
46972
- 1236535329,
46973
- 4129170786,
46974
- 3225465664,
46975
- 643717713,
46976
- 3921069994,
46977
- 3593408605,
46978
- 38016083,
46979
- 3634488961,
46980
- 3889429448,
46981
- 568446438,
46982
- 3275163606,
46983
- 4107603335,
46984
- 1163531501,
46985
- 2850285829,
46986
- 4243563512,
46987
- 1735328473,
46988
- 2368359562,
46989
- 4294588738,
46990
- 2272392833,
46991
- 1839030562,
46992
- 4259657740,
46993
- 2763975236,
46994
- 1272893353,
46995
- 4139469664,
46996
- 3200236656,
46997
- 681279174,
46998
- 3936430074,
46999
- 3572445317,
47000
- 76029189,
47001
- 3654602809,
47002
- 3873151461,
47003
- 530742520,
47004
- 3299628645,
47005
- 4096336452,
47006
- 1126891415,
47007
- 2878612391,
47008
- 4237533241,
47009
- 1700485571,
47010
- 2399980690,
47011
- 4293915773,
47012
- 2240044497,
47013
- 1873313359,
47014
- 4264355552,
47015
- 2734768916,
47016
- 1309151649,
47017
- 4149444226,
47018
- 3174756917,
47019
- 718787259,
47020
- 3951481745
47021
- ]);
47022
- /**
47023
- * Generate pseudo-random bytes.
47024
- * Uses Math.random — adequate for PDF IVs but not cryptographically secure.
47025
- */
47026
- function randomBytes(length) {
47027
- const bytes = new Uint8Array(length);
47028
- for (let i = 0; i < length; i++) bytes[i] = Math.random() * 256 | 0;
47029
- return bytes;
47030
- }
47031
- /**
47032
- * Concatenate multiple Uint8Arrays.
47033
- */
47034
- function concatArrays(...arrays) {
47035
- let totalLen = 0;
47036
- for (const arr of arrays) totalLen += arr.length;
47037
- const result = new Uint8Array(totalLen);
47038
- let offset = 0;
47039
- for (const arr of arrays) {
47040
- result.set(arr, offset);
47041
- offset += arr.length;
47042
- }
47043
- return result;
47044
- }
47045
- //#endregion
47046
47027
  //#region src/modules/pdf/core/encryption.ts
47047
- /**
47048
- * PDF encryption support (Standard Security Handler, V=5, R=5).
47049
- *
47050
- * Implements AES-256 encryption compatible with PDF 2.0 (ISO 32000-2:2020).
47051
- * Supports:
47052
- * - User password (required to open the document)
47053
- * - Owner password (grants full access)
47054
- * - Permission flags (print, copy, modify, etc.)
47055
- *
47056
- * The file encryption key (FEK) is a random 256-bit key.
47057
- * All streams and strings are encrypted using AES-256-CBC with a random
47058
- * 16-byte IV prepended to each encrypted value.
47059
- *
47060
- * @see ISO 32000-2:2020, §7.6 — Encryption
47061
- */
47028
+ init_crypto_browser();
47062
47029
  /**
47063
47030
  * Initialize encryption state for AES-256 (V=5, R=5).
47064
47031
  */
@@ -47071,12 +47038,28 @@ onmessage = async (ev) => {
47071
47038
  const uKeySalt = randomBytes(8);
47072
47039
  const oValidationSalt = randomBytes(8);
47073
47040
  const oKeySalt = randomBytes(8);
47074
- const uValue = concatArrays(sha256(concatArrays(userPwd, uValidationSalt)), uValidationSalt, uKeySalt);
47075
- const ueKey = sha256(concatArrays(userPwd, uKeySalt));
47041
+ const uValue = concatUint8Arrays([
47042
+ sha256(concatUint8Arrays([userPwd, uValidationSalt])),
47043
+ uValidationSalt,
47044
+ uKeySalt
47045
+ ]);
47046
+ const ueKey = sha256(concatUint8Arrays([userPwd, uKeySalt]));
47076
47047
  const zeroIv = new Uint8Array(16);
47077
47048
  const ueValue = aesCbcEncryptRaw(encryptionKey, ueKey, zeroIv);
47078
- const oValue = concatArrays(sha256(concatArrays(ownerPwd, oValidationSalt, uValue)), oValidationSalt, oKeySalt);
47079
- const oeValue = aesCbcEncryptRaw(encryptionKey, sha256(concatArrays(ownerPwd, oKeySalt, uValue)), zeroIv);
47049
+ const oValue = concatUint8Arrays([
47050
+ sha256(concatUint8Arrays([
47051
+ ownerPwd,
47052
+ oValidationSalt,
47053
+ uValue
47054
+ ])),
47055
+ oValidationSalt,
47056
+ oKeySalt
47057
+ ]);
47058
+ const oeValue = aesCbcEncryptRaw(encryptionKey, sha256(concatUint8Arrays([
47059
+ ownerPwd,
47060
+ oKeySalt,
47061
+ uValue
47062
+ ])), zeroIv);
47080
47063
  const permsBlock = new Uint8Array(16);
47081
47064
  new DataView(permsBlock.buffer).setInt32(0, perms, true);
47082
47065
  permsBlock[4] = 255;
@@ -47146,6 +47129,9 @@ onmessage = async (ev) => {
47146
47129
  * 3. Cross-reference table
47147
47130
  * 4. Trailer (with document catalog reference)
47148
47131
  *
47132
+ * Also provides {@link buildIncremental} for appending incremental updates
47133
+ * to an existing PDF without rewriting the original bytes.
47134
+ *
47149
47135
  * Encryption uses AES-256 (V=5, R=5) per ISO 32000-2:2020.
47150
47136
  *
47151
47137
  * @see ISO 32000-2:2020, Chapter 7.5 — File Structure
@@ -47165,6 +47151,14 @@ onmessage = async (ev) => {
47165
47151
  this.catalogRef = 0;
47166
47152
  this.infoRef = 0;
47167
47153
  this.encryption = null;
47154
+ this.pdfVersion = "2.0";
47155
+ }
47156
+ /**
47157
+ * Set the PDF version string (e.g. "1.4", "1.7", "2.0").
47158
+ * Default is "2.0".
47159
+ */
47160
+ setVersion(version) {
47161
+ this.pdfVersion = version;
47168
47162
  }
47169
47163
  /**
47170
47164
  * Enable encryption for this document.
@@ -47191,9 +47185,9 @@ onmessage = async (ev) => {
47191
47185
  content: typeof dict === "string" ? dict : dict.toString()
47192
47186
  });
47193
47187
  }
47194
- addStreamObject(objectNumber, dict, data) {
47188
+ addStreamObject(objectNumber, dict, data, options) {
47195
47189
  let streamData = data instanceof Uint8Array ? data : data.toUint8Array();
47196
- if (streamData.length > 256 && !dict.toString().includes("/Filter")) {
47190
+ if ((options?.compress ?? true) && streamData.length > 256 && !dict.toString().includes("/Filter")) {
47197
47191
  const compressed = zlibSync(streamData, { level: 6 });
47198
47192
  if (compressed.length < streamData.length) {
47199
47193
  dict.set("Filter", "/FlateDecode");
@@ -47209,6 +47203,20 @@ onmessage = async (ev) => {
47209
47203
  });
47210
47204
  }
47211
47205
  /**
47206
+ * Return all stored objects for inspection (e.g., incremental update remapping).
47207
+ * Stream objects include their binary data.
47208
+ */
47209
+ getObjects() {
47210
+ return this.objects.map((o) => {
47211
+ const result = {
47212
+ objectNumber: o.objectNumber,
47213
+ content: o.content
47214
+ };
47215
+ if ("streamData" in o) result.streamData = o.streamData;
47216
+ return result;
47217
+ });
47218
+ }
47219
+ /**
47212
47220
  * Set the document catalog object number.
47213
47221
  * This is required and references the root of the document structure.
47214
47222
  */
@@ -47245,14 +47253,19 @@ onmessage = async (ev) => {
47245
47253
  }
47246
47254
  /**
47247
47255
  * Create and add the Catalog dictionary.
47256
+ *
47257
+ * @param pagesRef - Object number of the Pages tree root
47258
+ * @param optionsOrOutlinesRef - Either an outlinesRef number (legacy) or an options object
47248
47259
  */
47249
- addCatalog(pagesRef, outlinesRef) {
47260
+ addCatalog(pagesRef, optionsOrOutlinesRef) {
47261
+ const resolvedOptions = typeof optionsOrOutlinesRef === "number" ? { outlinesRef: optionsOrOutlinesRef } : optionsOrOutlinesRef ?? {};
47250
47262
  const objNum = this.allocObject();
47251
47263
  const dict = new PdfDict().set("Type", "/Catalog").set("Pages", pdfRef(pagesRef));
47252
- if (outlinesRef) {
47253
- dict.set("Outlines", pdfRef(outlinesRef));
47264
+ if (resolvedOptions.outlinesRef) {
47265
+ dict.set("Outlines", pdfRef(resolvedOptions.outlinesRef));
47254
47266
  dict.set("PageMode", "/UseOutlines");
47255
47267
  }
47268
+ if (resolvedOptions.extraEntries) for (const [key, value] of resolvedOptions.extraEntries) dict.set(key, value);
47256
47269
  this.addObject(objNum, dict);
47257
47270
  this.setCatalog(objNum);
47258
47271
  return objNum;
@@ -47265,7 +47278,8 @@ onmessage = async (ev) => {
47265
47278
  const encoder = new TextEncoder();
47266
47279
  const chunks = [];
47267
47280
  let byteOffset = 0;
47268
- const headerStrBytes = encoder.encode("%PDF-2.0\n");
47281
+ const headerStr = `%PDF-${this.pdfVersion}\n`;
47282
+ const headerStrBytes = encoder.encode(headerStr);
47269
47283
  chunks.push(headerStrBytes);
47270
47284
  byteOffset += headerStrBytes.length;
47271
47285
  const binaryComment = new Uint8Array([
@@ -47604,6 +47618,30 @@ onmessage = async (ev) => {
47604
47618
  return this;
47605
47619
  }
47606
47620
  /**
47621
+ * Append a cubic Bezier curve to the current path.
47622
+ * From current point to (x3, y3), with control points (x1, y1) and (x2, y2).
47623
+ */
47624
+ curveTo(x1, y1, x2, y2, x3, y3) {
47625
+ this.parts.push(`${pdfNumber(x1)} ${pdfNumber(y1)} ${pdfNumber(x2)} ${pdfNumber(y2)} ${pdfNumber(x3)} ${pdfNumber(y3)} c`);
47626
+ return this;
47627
+ }
47628
+ /**
47629
+ * Append a cubic Bezier curve where the first control point is the current point.
47630
+ * From current point to (x3, y3), with control points (current, y1) and (x2, y2).
47631
+ */
47632
+ curveToV(x2, y2, x3, y3) {
47633
+ this.parts.push(`${pdfNumber(x2)} ${pdfNumber(y2)} ${pdfNumber(x3)} ${pdfNumber(y3)} v`);
47634
+ return this;
47635
+ }
47636
+ /**
47637
+ * Append a cubic Bezier curve where the second control point equals (x3, y3).
47638
+ * From current point to (x3, y3), with control point (x1, y1).
47639
+ */
47640
+ curveToY(x1, y1, x3, y3) {
47641
+ this.parts.push(`${pdfNumber(x1)} ${pdfNumber(y1)} ${pdfNumber(x3)} ${pdfNumber(y3)} y`);
47642
+ return this;
47643
+ }
47644
+ /**
47607
47645
  * Stroke the current path.
47608
47646
  */
47609
47647
  stroke() {
@@ -47805,6 +47843,47 @@ onmessage = async (ev) => {
47805
47843
  return this.moveTo(x1, y1).lineTo(x2, y2).stroke().restore();
47806
47844
  }
47807
47845
  /**
47846
+ * Append an ellipse to the current path using 4 cubic Bezier curves.
47847
+ * (cx, cy) is the center; rx, ry are the radii.
47848
+ *
47849
+ * Uses the standard kappa = 4 * (sqrt(2) - 1) / 3 ≈ 0.5522847 approximation.
47850
+ */
47851
+ ellipse(cx, cy, rx, ry) {
47852
+ const k = .5522847;
47853
+ const kx = k * rx;
47854
+ const ky = k * ry;
47855
+ this.moveTo(cx + rx, cy);
47856
+ this.curveTo(cx + rx, cy + ky, cx + kx, cy + ry, cx, cy + ry);
47857
+ this.curveTo(cx - kx, cy + ry, cx - rx, cy + ky, cx - rx, cy);
47858
+ this.curveTo(cx - rx, cy - ky, cx - kx, cy - ry, cx, cy - ry);
47859
+ this.curveTo(cx + kx, cy - ry, cx + rx, cy - ky, cx + rx, cy);
47860
+ return this;
47861
+ }
47862
+ /**
47863
+ * Append a circle to the current path.
47864
+ * (cx, cy) is the center; r is the radius.
47865
+ */
47866
+ circle(cx, cy, r) {
47867
+ return this.ellipse(cx, cy, r, r);
47868
+ }
47869
+ /**
47870
+ * Append a rounded rectangle to the current path.
47871
+ * (x, y) is the lower-left corner; r is the corner radius.
47872
+ */
47873
+ roundedRect(x, y, width, height, r) {
47874
+ const kr = .5522847 * r;
47875
+ this.moveTo(x + r, y);
47876
+ this.lineTo(x + width - r, y);
47877
+ this.curveTo(x + width - r + kr, y, x + width, y + r - kr, x + width, y + r);
47878
+ this.lineTo(x + width, y + height - r);
47879
+ this.curveTo(x + width, y + height - r + kr, x + width - r + kr, y + height, x + width - r, y + height);
47880
+ this.lineTo(x + r, y + height);
47881
+ this.curveTo(x + r - kr, y + height, x, y + height - r + kr, x, y + height - r);
47882
+ this.lineTo(x, y + r);
47883
+ this.curveTo(x, y + r - kr, x + r - kr, y, x + r, y);
47884
+ return this;
47885
+ }
47886
+ /**
47808
47887
  * Get the content stream as a string.
47809
47888
  */
47810
47889
  toString() {
@@ -49806,6 +49885,299 @@ onmessage = async (ev) => {
49806
49885
  const LINE_HEIGHT_FACTOR = 1.2;
49807
49886
  const PX_TO_PT = 72 / 96;
49808
49887
  //#endregion
49888
+ //#region src/modules/pdf/render/png-decoder.ts
49889
+ /**
49890
+ * Minimal PNG decoder for PDF image embedding.
49891
+ *
49892
+ * Extracts raw RGB pixel data from a PNG file. Handles:
49893
+ * - Color types: RGB (2), RGBA (6), Grayscale (0), Grayscale+Alpha (4), Palette (3)
49894
+ * - Bit depth: 8 (most common)
49895
+ * - Interlacing: non-interlaced only (Adam7 interlacing is not supported)
49896
+ * - All 5 PNG filter types (None, Sub, Up, Average, Paeth)
49897
+ *
49898
+ * For RGBA images, produces separate RGB pixels and an alpha mask (for PDF SMask).
49899
+ */
49900
+ /**
49901
+ * Maximum allowed pixel count for PNG decoding (default: 100 million pixels).
49902
+ * A 10000x10000 RGBA image at 100M pixels would need ~400MB for raw data alone.
49903
+ * This limit prevents memory exhaustion from malicious PNG files with
49904
+ * excessively large declared dimensions.
49905
+ */
49906
+ const MAX_PNG_PIXELS = 1e8;
49907
+ /**
49908
+ * Decode a PNG file to raw RGB pixels for PDF embedding.
49909
+ * @throws on invalid or unsupported PNG data
49910
+ */
49911
+ function decodePng(data) {
49912
+ if (data.length < 8 || data[0] !== 137 || data[1] !== 80 || data[2] !== 78 || data[3] !== 71) throw new Error("Invalid PNG signature");
49913
+ let offset = 8;
49914
+ let width = 0;
49915
+ let height = 0;
49916
+ let bitDepth = 0;
49917
+ let colorType = 0;
49918
+ const idatChunks = [];
49919
+ let palette = null;
49920
+ let trns = null;
49921
+ while (offset + 8 <= data.length) {
49922
+ const chunkLen = new DataView(data.buffer, data.byteOffset, data.byteLength).getUint32(offset, false);
49923
+ const chunkType = String.fromCharCode(data[offset + 4], data[offset + 5], data[offset + 6], data[offset + 7]);
49924
+ const chunkData = data.subarray(offset + 8, offset + 8 + chunkLen);
49925
+ offset += 8 + chunkLen + 4;
49926
+ switch (chunkType) {
49927
+ case "IHDR": {
49928
+ const hdr = new DataView(chunkData.buffer, chunkData.byteOffset, chunkData.byteLength);
49929
+ width = hdr.getUint32(0, false);
49930
+ height = hdr.getUint32(4, false);
49931
+ bitDepth = chunkData[8];
49932
+ colorType = chunkData[9];
49933
+ if (chunkData[12] !== 0) throw new Error("Interlaced PNG is not supported");
49934
+ if (bitDepth !== 8) throw new Error(`Unsupported PNG bit depth: ${bitDepth}. Only 8-bit PNGs are supported.`);
49935
+ if (width === 0 || height === 0) throw new Error(`Invalid PNG dimensions: ${width}x${height}`);
49936
+ const totalPixels = width * height;
49937
+ if (totalPixels > MAX_PNG_PIXELS) throw new Error(`PNG dimensions too large: ${width}x${height} (${totalPixels} pixels). Maximum allowed: ${MAX_PNG_PIXELS} pixels.`);
49938
+ break;
49939
+ }
49940
+ case "PLTE":
49941
+ palette = new Uint8Array(chunkData);
49942
+ break;
49943
+ case "tRNS":
49944
+ trns = new Uint8Array(chunkData);
49945
+ break;
49946
+ case "IDAT":
49947
+ idatChunks.push(chunkData);
49948
+ break;
49949
+ case "IEND": break;
49950
+ }
49951
+ }
49952
+ if (width === 0 || height === 0) throw new Error("PNG missing IHDR chunk");
49953
+ const compressedData = concatUint8Arrays(idatChunks);
49954
+ let rawCompressed;
49955
+ if (compressedData.length > 6 && (compressedData[0] & 15) === 8) rawCompressed = compressedData.subarray(2, compressedData.length - 4);
49956
+ else rawCompressed = compressedData;
49957
+ const rawData = decompressSync(rawCompressed);
49958
+ const channels = getChannelCount(colorType);
49959
+ const bytesPerPixel = Math.max(1, channels * bitDepth / 8);
49960
+ const scanlineLen = Math.ceil(width * channels * bitDepth / 8);
49961
+ return toRgb(applyFilters(rawData, width, height, scanlineLen, bytesPerPixel), width, height, colorType, bitDepth, palette, trns);
49962
+ }
49963
+ function applyFilters(data, _width, height, scanlineLen, bytesPerPixel) {
49964
+ const result = new Uint8Array(height * scanlineLen);
49965
+ const bpp = Math.max(1, Math.floor(bytesPerPixel));
49966
+ let srcOffset = 0;
49967
+ for (let y = 0; y < height; y++) {
49968
+ const filterType = data[srcOffset++];
49969
+ const dstOffset = y * scanlineLen;
49970
+ const prevRow = y > 0 ? (y - 1) * scanlineLen : -1;
49971
+ for (let x = 0; x < scanlineLen; x++) {
49972
+ const raw = data[srcOffset++] ?? 0;
49973
+ const a = x >= bpp ? result[dstOffset + x - bpp] : 0;
49974
+ const b = prevRow >= 0 ? result[prevRow + x] : 0;
49975
+ const c = prevRow >= 0 && x >= bpp ? result[prevRow + x - bpp] : 0;
49976
+ switch (filterType) {
49977
+ case 0:
49978
+ result[dstOffset + x] = raw;
49979
+ break;
49980
+ case 1:
49981
+ result[dstOffset + x] = raw + a & 255;
49982
+ break;
49983
+ case 2:
49984
+ result[dstOffset + x] = raw + b & 255;
49985
+ break;
49986
+ case 3:
49987
+ result[dstOffset + x] = raw + Math.floor((a + b) / 2) & 255;
49988
+ break;
49989
+ case 4:
49990
+ result[dstOffset + x] = raw + paethPredictor(a, b, c) & 255;
49991
+ break;
49992
+ default: result[dstOffset + x] = raw;
49993
+ }
49994
+ }
49995
+ }
49996
+ return result;
49997
+ }
49998
+ function paethPredictor(a, b, c) {
49999
+ const p = a + b - c;
50000
+ const pa = Math.abs(p - a);
50001
+ const pb = Math.abs(p - b);
50002
+ const pc = Math.abs(p - c);
50003
+ if (pa <= pb && pa <= pc) return a;
50004
+ if (pb <= pc) return b;
50005
+ return c;
50006
+ }
50007
+ function getChannelCount(colorType) {
50008
+ switch (colorType) {
50009
+ case 0: return 1;
50010
+ case 2: return 3;
50011
+ case 3: return 1;
50012
+ case 4: return 2;
50013
+ case 6: return 4;
50014
+ default: return 3;
50015
+ }
50016
+ }
50017
+ function toRgb(data, width, height, colorType, _bitDepth, palette, trns) {
50018
+ const totalPixels = width * height;
50019
+ const pixels = new Uint8Array(totalPixels * 3);
50020
+ let alpha = null;
50021
+ switch (colorType) {
50022
+ case 2:
50023
+ pixels.set(data.subarray(0, totalPixels * 3));
50024
+ if (trns && trns.length >= 6) {
50025
+ const trR = trns[1];
50026
+ const trG = trns[3];
50027
+ const trB = trns[5];
50028
+ alpha = new Uint8Array(totalPixels);
50029
+ alpha.fill(255);
50030
+ for (let i = 0; i < totalPixels; i++) if (data[i * 3] === trR && data[i * 3 + 1] === trG && data[i * 3 + 2] === trB) alpha[i] = 0;
50031
+ }
50032
+ break;
50033
+ case 6:
50034
+ alpha = new Uint8Array(totalPixels);
50035
+ for (let i = 0; i < totalPixels; i++) {
50036
+ pixels[i * 3] = data[i * 4];
50037
+ pixels[i * 3 + 1] = data[i * 4 + 1];
50038
+ pixels[i * 3 + 2] = data[i * 4 + 2];
50039
+ alpha[i] = data[i * 4 + 3];
50040
+ }
50041
+ break;
50042
+ case 0: {
50043
+ let trGray = -1;
50044
+ if (trns && trns.length >= 2) trGray = trns[1];
50045
+ if (trGray >= 0) {
50046
+ alpha = new Uint8Array(totalPixels);
50047
+ alpha.fill(255);
50048
+ }
50049
+ for (let i = 0; i < totalPixels; i++) {
50050
+ const g = data[i];
50051
+ pixels[i * 3] = g;
50052
+ pixels[i * 3 + 1] = g;
50053
+ pixels[i * 3 + 2] = g;
50054
+ if (alpha && g === trGray) alpha[i] = 0;
50055
+ }
50056
+ break;
50057
+ }
50058
+ case 4:
50059
+ alpha = new Uint8Array(totalPixels);
50060
+ for (let i = 0; i < totalPixels; i++) {
50061
+ const g = data[i * 2];
50062
+ pixels[i * 3] = g;
50063
+ pixels[i * 3 + 1] = g;
50064
+ pixels[i * 3 + 2] = g;
50065
+ alpha[i] = data[i * 2 + 1];
50066
+ }
50067
+ break;
50068
+ case 3:
50069
+ if (!palette) throw new Error("PNG palette color type (3) but missing PLTE chunk");
50070
+ if (trns && trns.length > 0) alpha = new Uint8Array(totalPixels);
50071
+ for (let i = 0; i < totalPixels; i++) {
50072
+ const idx = data[i];
50073
+ pixels[i * 3] = palette[idx * 3] ?? 0;
50074
+ pixels[i * 3 + 1] = palette[idx * 3 + 1] ?? 0;
50075
+ pixels[i * 3 + 2] = palette[idx * 3 + 2] ?? 0;
50076
+ if (alpha) alpha[i] = idx < trns.length ? trns[idx] : 255;
50077
+ }
50078
+ break;
50079
+ default: throw new Error(`Unsupported PNG color type: ${colorType}`);
50080
+ }
50081
+ if (alpha) {
50082
+ let fullyOpaque = true;
50083
+ for (let i = 0; i < alpha.length; i++) if (alpha[i] !== 255) {
50084
+ fullyOpaque = false;
50085
+ break;
50086
+ }
50087
+ if (fullyOpaque) alpha = null;
50088
+ }
50089
+ return {
50090
+ width,
50091
+ height,
50092
+ pixels,
50093
+ alpha,
50094
+ bitsPerComponent: 8
50095
+ };
50096
+ }
50097
+ //#endregion
50098
+ //#region src/modules/pdf/builder/image-utils.ts
50099
+ /**
50100
+ * Parse image dimensions from raw bytes.
50101
+ */
50102
+ function parseImageDimensions(data, format) {
50103
+ if (format === "png") return parsePngDimensions(data);
50104
+ return parseJpegDimensions(data);
50105
+ }
50106
+ /**
50107
+ * Read width/height from a PNG IHDR chunk (bytes 16-23).
50108
+ */
50109
+ function parsePngDimensions(data) {
50110
+ if (data.length >= 24 && data[12] === 73 && data[13] === 72 && data[14] === 68 && data[15] === 82) return {
50111
+ width: data[16] << 24 | data[17] << 16 | data[18] << 8 | data[19],
50112
+ height: data[20] << 24 | data[21] << 16 | data[22] << 8 | data[23]
50113
+ };
50114
+ return {
50115
+ width: 1,
50116
+ height: 1
50117
+ };
50118
+ }
50119
+ /**
50120
+ * Read width/height from JPEG SOF marker.
50121
+ *
50122
+ * Correctly excludes non-SOF markers in the 0xC0-0xCF range:
50123
+ * - 0xC4 = DHT (Define Huffman Table)
50124
+ * - 0xC8 = JPG (reserved)
50125
+ * - 0xCC = DAC (Define Arithmetic Coding)
50126
+ */
50127
+ function parseJpegDimensions(data) {
50128
+ let offset = 2;
50129
+ while (offset < data.length - 1) {
50130
+ while (offset < data.length && data[offset] === 255 && data[offset + 1] === 255) offset++;
50131
+ if (offset >= data.length - 1 || data[offset] !== 255) break;
50132
+ const marker = data[offset + 1];
50133
+ if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204 && offset + 8 < data.length) return {
50134
+ width: data[offset + 7] << 8 | data[offset + 8],
50135
+ height: data[offset + 5] << 8 | data[offset + 6]
50136
+ };
50137
+ if (offset + 3 >= data.length) break;
50138
+ const segLen = data[offset + 2] << 8 | data[offset + 3];
50139
+ offset += 2 + segLen;
50140
+ }
50141
+ return {
50142
+ width: 1,
50143
+ height: 1
50144
+ };
50145
+ }
50146
+ /**
50147
+ * Write an image XObject (JPEG or PNG) to the writer.
50148
+ * Returns the allocated object number.
50149
+ */
50150
+ function writeImageXObject(writer, data, format) {
50151
+ if (format === "png") return writePngImageXObject(writer, data);
50152
+ return writeJpegImageXObject(writer, data);
50153
+ }
50154
+ /**
50155
+ * Write a JPEG image using DCTDecode (raw JPEG data embedded directly).
50156
+ */
50157
+ function writeJpegImageXObject(writer, data) {
50158
+ const objNum = writer.allocObject();
50159
+ const dims = parseJpegDimensions(data);
50160
+ const dict = new PdfDict().set("Type", "/XObject").set("Subtype", "/Image").set("Width", pdfNumber(dims.width)).set("Height", pdfNumber(dims.height)).set("ColorSpace", "/DeviceRGB").set("BitsPerComponent", "8").set("Filter", "/DCTDecode");
50161
+ writer.addStreamObject(objNum, dict, data);
50162
+ return objNum;
50163
+ }
50164
+ /**
50165
+ * Write a PNG image: decode to raw RGB, create SMask for alpha if needed.
50166
+ */
50167
+ function writePngImageXObject(writer, data) {
50168
+ const png = decodePng(data);
50169
+ const objNum = writer.allocObject();
50170
+ const dict = new PdfDict().set("Type", "/XObject").set("Subtype", "/Image").set("Width", pdfNumber(png.width)).set("Height", pdfNumber(png.height)).set("ColorSpace", "/DeviceRGB").set("BitsPerComponent", pdfNumber(png.bitsPerComponent));
50171
+ if (png.alpha) {
50172
+ const smaskObjNum = writer.allocObject();
50173
+ const smaskDict = new PdfDict().set("Type", "/XObject").set("Subtype", "/Image").set("Width", pdfNumber(png.width)).set("Height", pdfNumber(png.height)).set("ColorSpace", "/DeviceGray").set("BitsPerComponent", "8");
50174
+ writer.addStreamObject(smaskObjNum, smaskDict, png.alpha);
50175
+ dict.set("SMask", pdfRef(smaskObjNum));
50176
+ }
50177
+ writer.addStreamObject(objNum, dict, png.pixels);
50178
+ return objNum;
50179
+ }
50180
+ //#endregion
49809
50181
  //#region src/modules/pdf/render/page-renderer.ts
49810
50182
  /**
49811
50183
  * Page renderer for PDF generation.
@@ -49818,6 +50190,14 @@ onmessage = async (ev) => {
49818
50190
  * - Grid lines
49819
50191
  * - Page headers (sheet names) and footers (page numbers)
49820
50192
  */
50193
+ function computeCellPadding(cell, scaleFactor = 1) {
50194
+ return {
50195
+ left: (3 + cell.borderInsets.left) * scaleFactor,
50196
+ right: (3 + cell.borderInsets.right) * scaleFactor,
50197
+ top: (2 + cell.borderInsets.top) * scaleFactor,
50198
+ bottom: (2 + cell.borderInsets.bottom) * scaleFactor
50199
+ };
50200
+ }
49821
50201
  /**
49822
50202
  * Render a single page to a PDF content stream.
49823
50203
  */
@@ -49923,10 +50303,9 @@ onmessage = async (ev) => {
49923
50303
  function drawCellText(stream, cell, fontManager, alphaValues, scaleFactor = 1) {
49924
50304
  const { rect, text, fontSize, horizontalAlign, verticalAlign, wrapText } = cell;
49925
50305
  if (!text && !cell.richText) return;
49926
- const padH = 3 * scaleFactor;
49927
- const padV = 2 * scaleFactor;
49928
- const availWidth = rect.width - padH * 2;
49929
- const availHeight = rect.height - padV * 2;
50306
+ const pad = computeCellPadding(cell, scaleFactor);
50307
+ const availWidth = rect.width - pad.left - pad.right;
50308
+ const availHeight = rect.height - pad.top - pad.bottom;
49930
50309
  if (availWidth <= 0 || availHeight <= 0) return;
49931
50310
  const indentPts = cell.indent * 10 * scaleFactor;
49932
50311
  const clipWidth = rect.width + (cell.textOverflowWidth || 0);
@@ -49967,28 +50346,27 @@ onmessage = async (ev) => {
49967
50346
  const lines = wrapText ? wrapTextLines(text, measure, effectiveWidth) : text.split(/\r?\n/);
49968
50347
  const lineHeight = fontSize * LINE_HEIGHT_FACTOR;
49969
50348
  const ascent = fontManager.getFontAscent(resourceName, fontSize);
49970
- const textStartY = computeTextStartY(verticalAlign, rect, lines.length * lineHeight, ascent, padV);
50349
+ const textStartY = computeTextStartY(verticalAlign, rect, lines.length * lineHeight, ascent, pad.top, pad.bottom);
49971
50350
  stream.setFillColor(cell.textColor);
49972
50351
  stream.beginText();
49973
50352
  stream.setFont(resourceName, fontSize);
49974
50353
  for (let i = 0; i < lines.length; i++) {
49975
50354
  const line = lines[i];
49976
50355
  const lineY = textStartY - i * lineHeight;
49977
- const textX = computeTextX(horizontalAlign, rect, measure(line), indentPts, padH);
50356
+ const textX = computeTextX(horizontalAlign, rect, measure(line), indentPts, pad.left, pad.right);
49978
50357
  stream.setTextMatrix(1, 0, 0, 1, textX, lineY);
49979
50358
  const hexEncoded = fontManager.encodeText(line, resourceName);
49980
50359
  if (hexEncoded) stream.showTextHex(hexEncoded);
49981
50360
  else stream.showText(line);
49982
50361
  }
49983
50362
  stream.endText();
49984
- drawTextDecorations(stream, cell, lines, lineHeight, textStartY, measure, resourceName, fontManager, indentPts);
50363
+ drawTextDecorations(stream, cell, lines, lineHeight, textStartY, measure, resourceName, fontManager, indentPts, pad);
49985
50364
  stream.restore();
49986
50365
  }
49987
50366
  function drawRichText(stream, cell, fontManager, indentPts, scaleFactor = 1) {
49988
50367
  const { rect, horizontalAlign, verticalAlign, wrapText } = cell;
49989
50368
  const runs = cell.richText;
49990
- const padH = 3 * scaleFactor;
49991
- const padV = 2 * scaleFactor;
50369
+ const pad = computeCellPadding(cell, scaleFactor);
49992
50370
  let maxFontSize = cell.fontSize;
49993
50371
  for (const run of runs) if (run.fontSize > maxFontSize) maxFontSize = run.fontSize;
49994
50372
  const primaryFontSize = maxFontSize;
@@ -49996,7 +50374,7 @@ onmessage = async (ev) => {
49996
50374
  const isEmbedded = fontManager.hasEmbeddedFont();
49997
50375
  const runResource = (run) => isEmbedded ? fontManager.getEmbeddedResourceName() : fontManager.ensureFont(resolvePdfFontName(run.fontFamily, run.bold, run.italic));
49998
50376
  if (wrapText) {
49999
- const availWidth = rect.width - padH * 2 - indentPts;
50377
+ const availWidth = rect.width - pad.left - pad.right - indentPts;
50000
50378
  if (availWidth <= 0) return;
50001
50379
  const fullText = runs.map((r) => r.text).join("");
50002
50380
  const primaryResource = runResource(runs[0]);
@@ -50006,7 +50384,7 @@ onmessage = async (ev) => {
50006
50384
  for (let ri = 0; ri < runs.length; ri++) for (let ci = 0; ci < runs[ri].text.length; ci++) runForChar.push(ri);
50007
50385
  const primaryResourceName = runResource(runs[0]);
50008
50386
  const ascent = fontManager.getFontAscent(primaryResourceName, primaryFontSize);
50009
- const textStartY = computeTextStartY(verticalAlign, rect, lines.length * lineHeight, ascent, padV);
50387
+ const textStartY = computeTextStartY(verticalAlign, rect, lines.length * lineHeight, ascent, pad.top, pad.bottom);
50010
50388
  let charPos = 0;
50011
50389
  for (let li = 0; li < lines.length; li++) {
50012
50390
  const lineY = textStartY - li * lineHeight;
@@ -50034,7 +50412,7 @@ onmessage = async (ev) => {
50034
50412
  }
50035
50413
  let lineWidth = 0;
50036
50414
  for (const seg of segments) lineWidth += fontManager.measureText(seg.text, seg.resourceName, seg.run.fontSize);
50037
- let textX = computeTextX(horizontalAlign, rect, lineWidth, indentPts, padH);
50415
+ let textX = computeTextX(horizontalAlign, rect, lineWidth, indentPts, pad.left, pad.right);
50038
50416
  for (const seg of segments) {
50039
50417
  const { run, text, resourceName } = seg;
50040
50418
  const segWidth = fontManager.measureText(text, resourceName, run.fontSize);
@@ -50071,8 +50449,8 @@ onmessage = async (ev) => {
50071
50449
  totalWidth += w;
50072
50450
  }
50073
50451
  const primaryResourceName = runMetrics[0]?.resourceName ?? "F1";
50074
- const textStartY = computeTextStartY(verticalAlign, rect, lineHeight, fontManager.getFontAscent(primaryResourceName, primaryFontSize), padV);
50075
- let textX = computeTextX(horizontalAlign, rect, totalWidth, indentPts, padH);
50452
+ const textStartY = computeTextStartY(verticalAlign, rect, lineHeight, fontManager.getFontAscent(primaryResourceName, primaryFontSize), pad.top, pad.bottom);
50453
+ let textX = computeTextX(horizontalAlign, rect, totalWidth, indentPts, pad.left, pad.right);
50076
50454
  for (let i = 0; i < runs.length; i++) {
50077
50455
  const run = runs[i];
50078
50456
  const { resourceName } = runMetrics[i];
@@ -50099,8 +50477,7 @@ onmessage = async (ev) => {
50099
50477
  function drawRotatedText(stream, cell, fontManager, indentPts, scaleFactor = 1) {
50100
50478
  const { rect, wrapText } = cell;
50101
50479
  let { fontSize } = cell;
50102
- const padH = 3 * scaleFactor;
50103
- const padV = 2 * scaleFactor;
50480
+ const pad = computeCellPadding(cell, scaleFactor);
50104
50481
  const resourceName = fontManager.hasEmbeddedFont() ? fontManager.getEmbeddedResourceName() : fontManager.ensureFont(resolvePdfFontName(cell.fontFamily, cell.bold, cell.italic));
50105
50482
  const degrees = excelRotationToDegrees(cell.textRotation);
50106
50483
  const radians = degrees * Math.PI / 180;
@@ -50108,8 +50485,8 @@ onmessage = async (ev) => {
50108
50485
  const sin = Math.sin(radians);
50109
50486
  const absSin = Math.abs(sin);
50110
50487
  const absCos = Math.abs(cos);
50111
- const maxWidth = rect.width - padH * 2;
50112
- const maxHeight = rect.height - padV * 2;
50488
+ const maxWidth = rect.width - pad.left - pad.right;
50489
+ const maxHeight = rect.height - pad.top - pad.bottom;
50113
50490
  let availTextLength;
50114
50491
  if (absSin > .01 && absCos > .01) availTextLength = Math.min(maxHeight / absSin, maxWidth / absCos);
50115
50492
  else if (absSin > .01) availTextLength = maxHeight / absSin;
@@ -50138,27 +50515,27 @@ onmessage = async (ev) => {
50138
50515
  const is90 = Math.abs(degrees - 90) < .01;
50139
50516
  const isMinus90 = Math.abs(degrees + 90) < .01;
50140
50517
  stream.setFillColor(cell.textColor);
50141
- if (is90) drawRotated90(stream, cell, lines, fontManager, resourceName, fontSize, scaledLineHeight, ascent, padH, padV);
50142
- else if (isMinus90) drawRotatedMinus90(stream, cell, lines, fontManager, resourceName, fontSize, scaledLineHeight, ascent, padH, padV);
50518
+ if (is90) drawRotated90(stream, cell, lines, fontManager, resourceName, fontSize, scaledLineHeight, ascent, pad);
50519
+ else if (isMinus90) drawRotatedMinus90(stream, cell, lines, fontManager, resourceName, fontSize, scaledLineHeight, ascent, pad);
50143
50520
  else drawRotatedGeneral(stream, cell, lines, fontManager, resourceName, fontSize, scaledLineHeight, ascent, cos, sin, indentPts);
50144
50521
  }
50145
50522
  /** 90° CCW: text reads bottom-to-top, lines stack left-to-right. */
50146
- function drawRotated90(stream, cell, lines, fontManager, resourceName, fontSize, lineHeight, ascent, padH, padV) {
50523
+ function drawRotated90(stream, cell, lines, fontManager, resourceName, fontSize, lineHeight, ascent, pad) {
50147
50524
  const { rect, horizontalAlign, verticalAlign } = cell;
50148
50525
  const totalColumnsWidth = lines.length * lineHeight;
50149
50526
  let startX;
50150
50527
  if (horizontalAlign === "center") startX = rect.x + rect.width / 2 - totalColumnsWidth / 2 + ascent;
50151
- else if (horizontalAlign === "right") startX = rect.x + rect.width - padH - totalColumnsWidth + ascent;
50152
- else startX = rect.x + padH + ascent;
50528
+ else if (horizontalAlign === "right") startX = rect.x + rect.width - pad.right - totalColumnsWidth + ascent;
50529
+ else startX = rect.x + pad.left + ascent;
50153
50530
  for (let i = 0; i < lines.length; i++) {
50154
50531
  const line = lines[i];
50155
50532
  const lineWidth = fontManager.measureText(line, resourceName, fontSize);
50156
50533
  const colX = startX + i * lineHeight;
50157
50534
  let ty;
50158
- if (verticalAlign === "top") ty = rect.y + rect.height - padV - lineWidth;
50535
+ if (verticalAlign === "top") ty = rect.y + rect.height - pad.top - lineWidth;
50159
50536
  else if (verticalAlign === "middle") ty = rect.y + (rect.height - lineWidth) / 2;
50160
- else ty = rect.y + padV;
50161
- ty = Math.max(ty, rect.y + padV);
50537
+ else ty = rect.y + pad.bottom;
50538
+ ty = Math.max(ty, rect.y + pad.bottom);
50162
50539
  stream.beginText();
50163
50540
  stream.setFont(resourceName, fontSize);
50164
50541
  stream.setTextMatrix(0, 1, -1, 0, colX, ty);
@@ -50167,22 +50544,22 @@ onmessage = async (ev) => {
50167
50544
  }
50168
50545
  }
50169
50546
  /** -90° (270° CW): text reads top-to-bottom, lines stack right-to-left. */
50170
- function drawRotatedMinus90(stream, cell, lines, fontManager, resourceName, fontSize, lineHeight, ascent, padH, padV) {
50547
+ function drawRotatedMinus90(stream, cell, lines, fontManager, resourceName, fontSize, lineHeight, ascent, pad) {
50171
50548
  const { rect, horizontalAlign, verticalAlign } = cell;
50172
50549
  const totalColumnsWidth = lines.length * lineHeight;
50173
50550
  let startX;
50174
50551
  if (horizontalAlign === "center") startX = rect.x + rect.width / 2 + totalColumnsWidth / 2 - lineHeight + ascent;
50175
- else if (horizontalAlign === "right") startX = rect.x + rect.width - padH - lineHeight + ascent;
50176
- else startX = rect.x + padH + totalColumnsWidth - lineHeight + ascent;
50552
+ else if (horizontalAlign === "right") startX = rect.x + rect.width - pad.right - lineHeight + ascent;
50553
+ else startX = rect.x + pad.left + totalColumnsWidth - lineHeight + ascent;
50177
50554
  for (let i = 0; i < lines.length; i++) {
50178
50555
  const line = lines[i];
50179
50556
  const lineWidth = fontManager.measureText(line, resourceName, fontSize);
50180
50557
  const colX = startX - i * lineHeight;
50181
50558
  let ty;
50182
- if (verticalAlign === "top") ty = rect.y + rect.height - padV;
50559
+ if (verticalAlign === "top") ty = rect.y + rect.height - pad.top;
50183
50560
  else if (verticalAlign === "middle") ty = rect.y + (rect.height + lineWidth) / 2;
50184
- else ty = rect.y + padV + lineWidth;
50185
- ty = Math.min(ty, rect.y + rect.height - padV);
50561
+ else ty = rect.y + pad.bottom + lineWidth;
50562
+ ty = Math.min(ty, rect.y + rect.height - pad.top);
50186
50563
  stream.beginText();
50187
50564
  stream.setFont(resourceName, fontSize);
50188
50565
  stream.setTextMatrix(0, -1, 1, 0, colX, ty);
@@ -50193,8 +50570,7 @@ onmessage = async (ev) => {
50193
50570
  /** General rotation — center a multi-line text block in the cell. */
50194
50571
  function drawRotatedGeneral(stream, cell, lines, fontManager, resourceName, fontSize, lineHeight, ascent, cos, sin, indentPts) {
50195
50572
  const { rect, horizontalAlign, verticalAlign } = cell;
50196
- const padH = 3;
50197
- const padV = 2;
50573
+ const pad = computeCellPadding(cell);
50198
50574
  let maxLineWidth = 0;
50199
50575
  for (const line of lines) {
50200
50576
  const w = fontManager.measureText(line, resourceName, fontSize);
@@ -50208,14 +50584,14 @@ onmessage = async (ev) => {
50208
50584
  const slantShift = computeSlantOffset(cell.textRotation, rect.height) / 2;
50209
50585
  const indentOffset = horizontalAlign === "left" ? indentPts / 2 : horizontalAlign === "right" ? -indentPts / 2 : 0;
50210
50586
  let cy;
50211
- if (verticalAlign === "top") cy = rect.y + rect.height - padV - rotatedHeight / 2;
50212
- else if (verticalAlign === "bottom") cy = rect.y + padV + rotatedHeight / 2;
50587
+ if (verticalAlign === "top") cy = rect.y + rect.height - pad.top - rotatedHeight / 2;
50588
+ else if (verticalAlign === "bottom") cy = rect.y + pad.bottom + rotatedHeight / 2;
50213
50589
  else cy = rect.y + rect.height / 2;
50214
50590
  const verticalRatio = rect.height > 0 ? (cy - rect.y) / rect.height : .5;
50215
50591
  const slantAtCy = slantShift * 2 * verticalRatio;
50216
50592
  let cx;
50217
- if (horizontalAlign === "right") cx = rect.x + rect.width - padH - rotatedWidth / 2 + indentOffset + slantAtCy;
50218
- else if (horizontalAlign === "left") cx = rect.x + padH + rotatedWidth / 2 + indentOffset + slantAtCy;
50593
+ if (horizontalAlign === "right") cx = rect.x + rect.width - pad.right - rotatedWidth / 2 + indentOffset + slantAtCy;
50594
+ else if (horizontalAlign === "left") cx = rect.x + pad.left + rotatedWidth / 2 + indentOffset + slantAtCy;
50219
50595
  else cx = rect.x + rect.width / 2 + indentOffset + slantAtCy;
50220
50596
  for (let i = 0; i < lines.length; i++) {
50221
50597
  const line = lines[i];
@@ -50244,8 +50620,7 @@ onmessage = async (ev) => {
50244
50620
  */
50245
50621
  function drawVerticalStackedText(stream, cell, fontManager, _indentPts, scaleFactor = 1) {
50246
50622
  const { rect, text, fontSize, horizontalAlign, verticalAlign } = cell;
50247
- const padH = 3 * scaleFactor;
50248
- const padV = 2 * scaleFactor;
50623
+ const pad = computeCellPadding(cell, scaleFactor);
50249
50624
  const resourceName = fontManager.hasEmbeddedFont() ? fontManager.getEmbeddedResourceName() : fontManager.ensureFont(resolvePdfFontName(cell.fontFamily, cell.bold, cell.italic));
50250
50625
  const charHeight = fontSize * 1.3;
50251
50626
  const ascent = fontManager.getFontAscent(resourceName, fontSize);
@@ -50254,8 +50629,8 @@ onmessage = async (ev) => {
50254
50629
  const totalColumnsWidth = columns.length * columnWidth;
50255
50630
  let startX;
50256
50631
  if (horizontalAlign === "center") startX = rect.x + rect.width / 2 - totalColumnsWidth / 2 + columnWidth / 2;
50257
- else if (horizontalAlign === "right") startX = rect.x + rect.width - padH - totalColumnsWidth + columnWidth / 2;
50258
- else startX = rect.x + padH + columnWidth / 2;
50632
+ else if (horizontalAlign === "right") startX = rect.x + rect.width - pad.right - totalColumnsWidth + columnWidth / 2;
50633
+ else startX = rect.x + pad.left + columnWidth / 2;
50259
50634
  stream.setFillColor(cell.textColor);
50260
50635
  for (let colIdx = 0; colIdx < columns.length; colIdx++) {
50261
50636
  const colText = columns[colIdx];
@@ -50263,10 +50638,10 @@ onmessage = async (ev) => {
50263
50638
  const totalTextHeight = colText.length * charHeight;
50264
50639
  let currentY;
50265
50640
  if (verticalAlign === "middle") currentY = rect.y + rect.height / 2 + totalTextHeight / 2 - ascent;
50266
- else if (verticalAlign === "bottom") currentY = rect.y + padV + totalTextHeight - ascent;
50267
- else currentY = rect.y + rect.height - padV - ascent;
50641
+ else if (verticalAlign === "bottom") currentY = rect.y + pad.bottom + totalTextHeight - ascent;
50642
+ else currentY = rect.y + rect.height - pad.top - ascent;
50268
50643
  for (const ch of colText) {
50269
- if (currentY < rect.y + padV) break;
50644
+ if (currentY < rect.y + pad.bottom) break;
50270
50645
  const charWidth = fontManager.measureText(ch, resourceName, fontSize);
50271
50646
  stream.beginText();
50272
50647
  stream.setFont(resourceName, fontSize);
@@ -50285,49 +50660,49 @@ onmessage = async (ev) => {
50285
50660
  function alphaGsName(alpha) {
50286
50661
  return `GS${Math.round(alpha * 1e4)}`;
50287
50662
  }
50288
- function computeTextStartY(verticalAlign, rect, totalTextHeight, ascent, padV = 2) {
50663
+ function computeTextStartY(verticalAlign, rect, totalTextHeight, ascent, padVTop = 2, padVBottom = padVTop) {
50289
50664
  let y;
50290
50665
  switch (verticalAlign) {
50291
50666
  case "top":
50292
- y = rect.y + rect.height - padV - ascent;
50667
+ y = rect.y + rect.height - padVTop - ascent;
50293
50668
  break;
50294
50669
  case "middle":
50295
50670
  y = rect.y + rect.height / 2 + totalTextHeight / 2 - ascent;
50296
50671
  break;
50297
50672
  default:
50298
- y = rect.y + padV + (totalTextHeight - ascent);
50673
+ y = rect.y + padVBottom + (totalTextHeight - ascent);
50299
50674
  break;
50300
50675
  }
50301
- const maxY = rect.y + rect.height - padV - ascent;
50676
+ const maxY = rect.y + rect.height - padVTop - ascent;
50302
50677
  if (y > maxY) y = maxY;
50303
- const minY = rect.y + padV;
50678
+ const minY = rect.y + padVBottom;
50304
50679
  if (y < minY) y = minY;
50305
50680
  return y;
50306
50681
  }
50307
- function computeTextX(align, rect, textWidth, indentPts = 0, padH = 3) {
50682
+ function computeTextX(align, rect, textWidth, indentPts = 0, padHLeft = 3, padHRight = padHLeft) {
50308
50683
  let x;
50309
50684
  switch (align) {
50310
50685
  case "center":
50311
50686
  x = rect.x + (rect.width - textWidth) / 2;
50312
50687
  break;
50313
50688
  case "right":
50314
- x = rect.x + rect.width - padH - textWidth;
50689
+ x = rect.x + rect.width - padHRight - textWidth;
50315
50690
  break;
50316
50691
  default:
50317
- x = rect.x + padH + indentPts;
50692
+ x = rect.x + padHLeft + indentPts;
50318
50693
  break;
50319
50694
  }
50320
- const minX = rect.x + padH;
50695
+ const minX = rect.x + padHLeft;
50321
50696
  if (x < minX) x = minX;
50322
50697
  return x;
50323
50698
  }
50324
- function drawTextDecorations(stream, cell, lines, lineHeight, textStartY, measure, resourceName, fontManager, indentPts) {
50699
+ function drawTextDecorations(stream, cell, lines, lineHeight, textStartY, measure, resourceName, fontManager, indentPts, pad) {
50325
50700
  if (cell.strike) {
50326
50701
  const strikeY = textStartY + fontManager.getFontDescent(resourceName, cell.fontSize) + cell.fontSize * .3;
50327
50702
  for (let i = 0; i < lines.length; i++) {
50328
50703
  const lineY = strikeY - i * lineHeight;
50329
50704
  const lw = measure(lines[i]);
50330
- const startX = computeTextX(cell.horizontalAlign, cell.rect, lw, indentPts);
50705
+ const startX = computeTextX(cell.horizontalAlign, cell.rect, lw, indentPts, pad?.left, pad?.right);
50331
50706
  stream.drawLine(startX, lineY, startX + lw, lineY, cell.textColor, .5);
50332
50707
  }
50333
50708
  }
@@ -50336,7 +50711,7 @@ onmessage = async (ev) => {
50336
50711
  for (let i = 0; i < lines.length; i++) {
50337
50712
  const lineY = textStartY - i * lineHeight + underlineOffset;
50338
50713
  const lw = measure(lines[i]);
50339
- const startX = computeTextX(cell.horizontalAlign, cell.rect, lw, indentPts);
50714
+ const startX = computeTextX(cell.horizontalAlign, cell.rect, lw, indentPts, pad?.left, pad?.right);
50340
50715
  stream.drawLine(startX, lineY, startX + lw, lineY, cell.textColor, .5);
50341
50716
  }
50342
50717
  }
@@ -50581,41 +50956,6 @@ onmessage = async (ev) => {
50581
50956
  /**
50582
50957
  * Parse image dimensions from raw JPEG or PNG data without a full decode.
50583
50958
  */
50584
- function parseImageDimensions(data, format) {
50585
- if (format === "png") return parsePngDimensions(data);
50586
- return parseJpegDimensions(data);
50587
- }
50588
- /** Read width/height from a PNG IHDR chunk (bytes 16-23). */
50589
- function parsePngDimensions(data) {
50590
- if (data.length >= 24 && data[12] === 73 && data[13] === 72 && data[14] === 68 && data[15] === 82) return {
50591
- width: data[16] << 24 | data[17] << 16 | data[18] << 8 | data[19],
50592
- height: data[20] << 24 | data[21] << 16 | data[22] << 8 | data[23]
50593
- };
50594
- return {
50595
- width: 1,
50596
- height: 1
50597
- };
50598
- }
50599
- /** Read width/height from JPEG SOF marker. */
50600
- function parseJpegDimensions(data) {
50601
- let offset = 2;
50602
- while (offset < data.length - 1) {
50603
- while (offset < data.length && data[offset] === 255 && data[offset + 1] === 255) offset++;
50604
- if (offset >= data.length - 1 || data[offset] !== 255) break;
50605
- const marker = data[offset + 1];
50606
- if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204 && offset + 8 < data.length) return {
50607
- width: data[offset + 7] << 8 | data[offset + 8],
50608
- height: data[offset + 5] << 8 | data[offset + 6]
50609
- };
50610
- if (offset + 3 >= data.length) break;
50611
- const segLen = data[offset + 2] << 8 | data[offset + 3];
50612
- offset += 2 + segLen;
50613
- }
50614
- return {
50615
- width: 1,
50616
- height: 1
50617
- };
50618
- }
50619
50959
  /**
50620
50960
  * Resolve the center position for a watermark on a given page.
50621
50961
  */
@@ -50791,6 +51131,7 @@ onmessage = async (ev) => {
50791
51131
  cellGrid.set(`${ri}:${gci}`, layoutCell);
50792
51132
  }
50793
51133
  }
51134
+ resolveSharedBorders(cellGrid, rowPage.length, colGroup.length);
50794
51135
  computeTextOverflows(cellGrid, rowPage, colGroup, visibleRows, visibleCols, groupColWidths, mergeMap, fontManager);
50795
51136
  return {
50796
51137
  pageNumber: currentPageCount + 1,
@@ -50930,7 +51271,9 @@ onmessage = async (ev) => {
50930
51271
  const fontSize = getCellFontSize(cell);
50931
51272
  const wrapLineCount = countWrapLines(cell, fontSize, scaleFactor, sheet, fontManager, options);
50932
51273
  const lineHeight = fontSize * LINE_HEIGHT_FACTOR;
50933
- const neededHeight = fontSize + (wrapLineCount - 1) * lineHeight + 4;
51274
+ const borderTop = cell.style?.border?.top?.style ? borderStyleToLineWidth(cell.style.border.top.style) / 2 : 0;
51275
+ const borderBottom = cell.style?.border?.bottom?.style ? borderStyleToLineWidth(cell.style.border.bottom.style) / 2 : 0;
51276
+ const neededHeight = fontSize + (wrapLineCount - 1) * lineHeight + (2 + borderTop + borderBottom) * 2;
50934
51277
  if (neededHeight > height) height = neededHeight;
50935
51278
  }
50936
51279
  }
@@ -50968,7 +51311,10 @@ onmessage = async (ev) => {
50968
51311
  const lineCount = Math.max(1, (text.match(/\n/g) ?? []).length + 1);
50969
51312
  if (!cell.style?.alignment?.wrapText || text.length === 0) return lineCount;
50970
51313
  const scaledColPts = ((sheet.columns.get(cell.col)?.width ?? DEFAULT_COLUMN_WIDTH) * 7 + 5) * PX_TO_PT * scaleFactor;
50971
- const padding = 6 + (cell.style.alignment.indent ?? 0) * 10;
51314
+ const indent = cell.style.alignment.indent ?? 0;
51315
+ const borderLeft = cell.style?.border?.left?.style ? borderStyleToLineWidth(cell.style.border.left.style) / 2 : 0;
51316
+ const borderRight = cell.style?.border?.right?.style ? borderStyleToLineWidth(cell.style.border.right.style) / 2 : 0;
51317
+ const padding = 3 + borderLeft + (3 + borderRight) + indent * 10;
50972
51318
  const effectiveWidth = Math.max(scaledColPts - padding, 1);
50973
51319
  const scaledFontSize = fontSize * scaleFactor;
50974
51320
  const fontProps = extractFontProperties(cell.style.font, options.defaultFontFamily, options.defaultFontSize);
@@ -51109,6 +51455,7 @@ onmessage = async (ev) => {
51109
51455
  fontManager.ensureFont(pdfFontName);
51110
51456
  }
51111
51457
  const richText = buildRichTextRuns(cell, options, fontManager, scaleFactor);
51458
+ const borders = excelBordersToPdf(style.border);
51112
51459
  return {
51113
51460
  text,
51114
51461
  rect: {
@@ -51128,7 +51475,13 @@ onmessage = async (ev) => {
51128
51475
  horizontalAlign: resolveHorizontalAlign(style.alignment, cell?.type, cell?.result),
51129
51476
  verticalAlign: excelVAlignToPdf(style.alignment),
51130
51477
  wrapText: style.alignment?.wrapText ?? false,
51131
- borders: excelBordersToPdf(style.border),
51478
+ borders,
51479
+ borderInsets: {
51480
+ top: (borders.top?.width ?? 0) / 2,
51481
+ right: (borders.right?.width ?? 0) / 2,
51482
+ bottom: (borders.bottom?.width ?? 0) / 2,
51483
+ left: (borders.left?.width ?? 0) / 2
51484
+ },
51132
51485
  colSpan,
51133
51486
  rowSpan,
51134
51487
  hyperlink: cell?.hyperlink ?? null,
@@ -51139,6 +51492,64 @@ onmessage = async (ev) => {
51139
51492
  };
51140
51493
  }
51141
51494
  /**
51495
+ * Border precedence weight.
51496
+ *
51497
+ * When two adjacent cells both declare a border on a shared edge the winning
51498
+ * border is chosen by: 1. thicker wins, 2. solid beats dashed,
51499
+ * 3. double beats single, 4. darker colour wins (tie-break).
51500
+ *
51501
+ * Returns a numeric score – higher score wins.
51502
+ */
51503
+ function borderPrecedence(b) {
51504
+ let score = b.width * 1e3;
51505
+ if (b.dashPattern.length === 0) score += 100;
51506
+ if (b.isDouble) score += 50;
51507
+ const brightness = b.color.r + b.color.g + b.color.b;
51508
+ score += (3 - brightness) * 10;
51509
+ return score;
51510
+ }
51511
+ /**
51512
+ * Resolve shared borders between adjacent cells.
51513
+ *
51514
+ * For each shared edge, determine the winning border (by precedence), then:
51515
+ * - The cell that "owns" the winning border keeps it in `borders` for drawing.
51516
+ * - The losing cell has that border side set to `null` (it won't draw).
51517
+ * - Both cells' `borderInsets` are updated to reflect the winning border's
51518
+ * half-width, so text padding accounts for the line that is actually there.
51519
+ */
51520
+ function resolveSharedBorders(cellGrid, rowCount, colCount) {
51521
+ for (let ri = 0; ri < rowCount; ri++) for (let gci = 0; gci < colCount; gci++) {
51522
+ const cell = cellGrid.get(`${ri}:${gci}`);
51523
+ if (!cell) continue;
51524
+ if (cell.borders.right) {
51525
+ const rightNeighbor = cellGrid.get(`${ri}:${gci + 1}`);
51526
+ if (rightNeighbor?.borders.left) {
51527
+ const myScore = borderPrecedence(cell.borders.right);
51528
+ if (borderPrecedence(rightNeighbor.borders.left) > myScore) {
51529
+ cell.borderInsets.right = rightNeighbor.borders.left.width / 2;
51530
+ cell.borders.right = null;
51531
+ } else {
51532
+ rightNeighbor.borderInsets.left = cell.borders.right.width / 2;
51533
+ rightNeighbor.borders.left = null;
51534
+ }
51535
+ }
51536
+ }
51537
+ if (cell.borders.bottom) {
51538
+ const belowNeighbor = cellGrid.get(`${ri + 1}:${gci}`);
51539
+ if (belowNeighbor?.borders.top) {
51540
+ const myScore = borderPrecedence(cell.borders.bottom);
51541
+ if (borderPrecedence(belowNeighbor.borders.top) > myScore) {
51542
+ cell.borderInsets.bottom = belowNeighbor.borders.top.width / 2;
51543
+ cell.borders.bottom = null;
51544
+ } else {
51545
+ belowNeighbor.borderInsets.top = cell.borders.bottom.width / 2;
51546
+ belowNeighbor.borders.top = null;
51547
+ }
51548
+ }
51549
+ }
51550
+ }
51551
+ }
51552
+ /**
51142
51553
  * Assign pre-collected images to the pages that contain their top-left anchor.
51143
51554
  */
51144
51555
  function assignImagesToPages(images, layoutPages, scaleFactor) {
@@ -51199,7 +51610,10 @@ onmessage = async (ev) => {
51199
51610
  const rightCellData = sheet.rows.get(wsRowNumber)?.cells.get(rightCol);
51200
51611
  if (rightCellData?.style?.border?.right) {
51201
51612
  const converted = excelBordersToPdf({ right: rightCellData.style.border.right });
51202
- if (converted.right) layoutCell.borders.right = converted.right;
51613
+ if (converted.right) {
51614
+ layoutCell.borders.right = converted.right;
51615
+ layoutCell.borderInsets.right = converted.right.width / 2;
51616
+ }
51203
51617
  }
51204
51618
  }
51205
51619
  if (mergeInfo.rowSpan > 1) {
@@ -51207,7 +51621,10 @@ onmessage = async (ev) => {
51207
51621
  const bottomCellData = sheet.rows.get(bottomRowNum)?.cells.get(wsColNumber);
51208
51622
  if (bottomCellData?.style?.border?.bottom) {
51209
51623
  const converted = excelBordersToPdf({ bottom: bottomCellData.style.border.bottom });
51210
- if (converted.bottom) layoutCell.borders.bottom = converted.bottom;
51624
+ if (converted.bottom) {
51625
+ layoutCell.borders.bottom = converted.bottom;
51626
+ layoutCell.borderInsets.bottom = converted.bottom.width / 2;
51627
+ }
51211
51628
  }
51212
51629
  }
51213
51630
  }
@@ -51222,7 +51639,7 @@ onmessage = async (ev) => {
51222
51639
  if (!cell || cell.wrapText || cell.colSpan > 1 || !cell.text || cell.richText || typeof cell.textRotation === "number" && cell.textRotation !== 0 || cell.textRotation === "vertical") continue;
51223
51640
  const resourceName = fontManager.hasEmbeddedFont() ? fontManager.getEmbeddedResourceName() : fontManager.ensureFont(resolvePdfFontName(cell.fontFamily, cell.bold, cell.italic));
51224
51641
  const textWidth = fontManager.measureText(cell.text, resourceName, cell.fontSize);
51225
- const cellContentWidth = cell.rect.width - 6;
51642
+ const cellContentWidth = cell.rect.width - (3 + cell.borderInsets.left) - (3 + cell.borderInsets.right);
51226
51643
  if (textWidth <= cellContentWidth) continue;
51227
51644
  const overflowNeeded = textWidth - cellContentWidth;
51228
51645
  let overflowAvailable = 0;
@@ -51267,216 +51684,6 @@ onmessage = async (ev) => {
51267
51684
  });
51268
51685
  }
51269
51686
  //#endregion
51270
- //#region src/modules/pdf/render/png-decoder.ts
51271
- /**
51272
- * Minimal PNG decoder for PDF image embedding.
51273
- *
51274
- * Extracts raw RGB pixel data from a PNG file. Handles:
51275
- * - Color types: RGB (2), RGBA (6), Grayscale (0), Grayscale+Alpha (4), Palette (3)
51276
- * - Bit depth: 8 (most common)
51277
- * - Interlacing: non-interlaced only (Adam7 interlacing is not supported)
51278
- * - All 5 PNG filter types (None, Sub, Up, Average, Paeth)
51279
- *
51280
- * For RGBA images, produces separate RGB pixels and an alpha mask (for PDF SMask).
51281
- */
51282
- /**
51283
- * Maximum allowed pixel count for PNG decoding (default: 100 million pixels).
51284
- * A 10000x10000 RGBA image at 100M pixels would need ~400MB for raw data alone.
51285
- * This limit prevents memory exhaustion from malicious PNG files with
51286
- * excessively large declared dimensions.
51287
- */
51288
- const MAX_PNG_PIXELS = 1e8;
51289
- /**
51290
- * Decode a PNG file to raw RGB pixels for PDF embedding.
51291
- * @throws on invalid or unsupported PNG data
51292
- */
51293
- function decodePng(data) {
51294
- if (data.length < 8 || data[0] !== 137 || data[1] !== 80 || data[2] !== 78 || data[3] !== 71) throw new Error("Invalid PNG signature");
51295
- let offset = 8;
51296
- let width = 0;
51297
- let height = 0;
51298
- let bitDepth = 0;
51299
- let colorType = 0;
51300
- const idatChunks = [];
51301
- let palette = null;
51302
- let trns = null;
51303
- while (offset + 8 <= data.length) {
51304
- const chunkLen = new DataView(data.buffer, data.byteOffset, data.byteLength).getUint32(offset, false);
51305
- const chunkType = String.fromCharCode(data[offset + 4], data[offset + 5], data[offset + 6], data[offset + 7]);
51306
- const chunkData = data.subarray(offset + 8, offset + 8 + chunkLen);
51307
- offset += 8 + chunkLen + 4;
51308
- switch (chunkType) {
51309
- case "IHDR": {
51310
- const hdr = new DataView(chunkData.buffer, chunkData.byteOffset, chunkData.byteLength);
51311
- width = hdr.getUint32(0, false);
51312
- height = hdr.getUint32(4, false);
51313
- bitDepth = chunkData[8];
51314
- colorType = chunkData[9];
51315
- if (chunkData[12] !== 0) throw new Error("Interlaced PNG is not supported");
51316
- if (bitDepth !== 8) throw new Error(`Unsupported PNG bit depth: ${bitDepth}. Only 8-bit PNGs are supported.`);
51317
- if (width === 0 || height === 0) throw new Error(`Invalid PNG dimensions: ${width}x${height}`);
51318
- const totalPixels = width * height;
51319
- if (totalPixels > MAX_PNG_PIXELS) throw new Error(`PNG dimensions too large: ${width}x${height} (${totalPixels} pixels). Maximum allowed: ${MAX_PNG_PIXELS} pixels.`);
51320
- break;
51321
- }
51322
- case "PLTE":
51323
- palette = new Uint8Array(chunkData);
51324
- break;
51325
- case "tRNS":
51326
- trns = new Uint8Array(chunkData);
51327
- break;
51328
- case "IDAT":
51329
- idatChunks.push(chunkData);
51330
- break;
51331
- case "IEND": break;
51332
- }
51333
- }
51334
- if (width === 0 || height === 0) throw new Error("PNG missing IHDR chunk");
51335
- const compressedData = concatUint8Arrays(idatChunks);
51336
- let rawCompressed;
51337
- if (compressedData.length > 6 && (compressedData[0] & 15) === 8) rawCompressed = compressedData.subarray(2, compressedData.length - 4);
51338
- else rawCompressed = compressedData;
51339
- const rawData = decompressSync(rawCompressed);
51340
- const channels = getChannelCount(colorType);
51341
- const bytesPerPixel = Math.max(1, channels * bitDepth / 8);
51342
- const scanlineLen = Math.ceil(width * channels * bitDepth / 8);
51343
- return toRgb(applyFilters(rawData, width, height, scanlineLen, bytesPerPixel), width, height, colorType, bitDepth, palette, trns);
51344
- }
51345
- function applyFilters(data, _width, height, scanlineLen, bytesPerPixel) {
51346
- const result = new Uint8Array(height * scanlineLen);
51347
- const bpp = Math.max(1, Math.floor(bytesPerPixel));
51348
- let srcOffset = 0;
51349
- for (let y = 0; y < height; y++) {
51350
- const filterType = data[srcOffset++];
51351
- const dstOffset = y * scanlineLen;
51352
- const prevRow = y > 0 ? (y - 1) * scanlineLen : -1;
51353
- for (let x = 0; x < scanlineLen; x++) {
51354
- const raw = data[srcOffset++] ?? 0;
51355
- const a = x >= bpp ? result[dstOffset + x - bpp] : 0;
51356
- const b = prevRow >= 0 ? result[prevRow + x] : 0;
51357
- const c = prevRow >= 0 && x >= bpp ? result[prevRow + x - bpp] : 0;
51358
- switch (filterType) {
51359
- case 0:
51360
- result[dstOffset + x] = raw;
51361
- break;
51362
- case 1:
51363
- result[dstOffset + x] = raw + a & 255;
51364
- break;
51365
- case 2:
51366
- result[dstOffset + x] = raw + b & 255;
51367
- break;
51368
- case 3:
51369
- result[dstOffset + x] = raw + Math.floor((a + b) / 2) & 255;
51370
- break;
51371
- case 4:
51372
- result[dstOffset + x] = raw + paethPredictor(a, b, c) & 255;
51373
- break;
51374
- default: result[dstOffset + x] = raw;
51375
- }
51376
- }
51377
- }
51378
- return result;
51379
- }
51380
- function paethPredictor(a, b, c) {
51381
- const p = a + b - c;
51382
- const pa = Math.abs(p - a);
51383
- const pb = Math.abs(p - b);
51384
- const pc = Math.abs(p - c);
51385
- if (pa <= pb && pa <= pc) return a;
51386
- if (pb <= pc) return b;
51387
- return c;
51388
- }
51389
- function getChannelCount(colorType) {
51390
- switch (colorType) {
51391
- case 0: return 1;
51392
- case 2: return 3;
51393
- case 3: return 1;
51394
- case 4: return 2;
51395
- case 6: return 4;
51396
- default: return 3;
51397
- }
51398
- }
51399
- function toRgb(data, width, height, colorType, _bitDepth, palette, trns) {
51400
- const totalPixels = width * height;
51401
- const pixels = new Uint8Array(totalPixels * 3);
51402
- let alpha = null;
51403
- switch (colorType) {
51404
- case 2:
51405
- pixels.set(data.subarray(0, totalPixels * 3));
51406
- if (trns && trns.length >= 6) {
51407
- const trR = trns[1];
51408
- const trG = trns[3];
51409
- const trB = trns[5];
51410
- alpha = new Uint8Array(totalPixels);
51411
- alpha.fill(255);
51412
- for (let i = 0; i < totalPixels; i++) if (data[i * 3] === trR && data[i * 3 + 1] === trG && data[i * 3 + 2] === trB) alpha[i] = 0;
51413
- }
51414
- break;
51415
- case 6:
51416
- alpha = new Uint8Array(totalPixels);
51417
- for (let i = 0; i < totalPixels; i++) {
51418
- pixels[i * 3] = data[i * 4];
51419
- pixels[i * 3 + 1] = data[i * 4 + 1];
51420
- pixels[i * 3 + 2] = data[i * 4 + 2];
51421
- alpha[i] = data[i * 4 + 3];
51422
- }
51423
- break;
51424
- case 0: {
51425
- let trGray = -1;
51426
- if (trns && trns.length >= 2) trGray = trns[1];
51427
- if (trGray >= 0) {
51428
- alpha = new Uint8Array(totalPixels);
51429
- alpha.fill(255);
51430
- }
51431
- for (let i = 0; i < totalPixels; i++) {
51432
- const g = data[i];
51433
- pixels[i * 3] = g;
51434
- pixels[i * 3 + 1] = g;
51435
- pixels[i * 3 + 2] = g;
51436
- if (alpha && g === trGray) alpha[i] = 0;
51437
- }
51438
- break;
51439
- }
51440
- case 4:
51441
- alpha = new Uint8Array(totalPixels);
51442
- for (let i = 0; i < totalPixels; i++) {
51443
- const g = data[i * 2];
51444
- pixels[i * 3] = g;
51445
- pixels[i * 3 + 1] = g;
51446
- pixels[i * 3 + 2] = g;
51447
- alpha[i] = data[i * 2 + 1];
51448
- }
51449
- break;
51450
- case 3:
51451
- if (!palette) throw new Error("PNG palette color type (3) but missing PLTE chunk");
51452
- if (trns && trns.length > 0) alpha = new Uint8Array(totalPixels);
51453
- for (let i = 0; i < totalPixels; i++) {
51454
- const idx = data[i];
51455
- pixels[i * 3] = palette[idx * 3] ?? 0;
51456
- pixels[i * 3 + 1] = palette[idx * 3 + 1] ?? 0;
51457
- pixels[i * 3 + 2] = palette[idx * 3 + 2] ?? 0;
51458
- if (alpha) alpha[i] = idx < trns.length ? trns[idx] : 255;
51459
- }
51460
- break;
51461
- default: throw new Error(`Unsupported PNG color type: ${colorType}`);
51462
- }
51463
- if (alpha) {
51464
- let fullyOpaque = true;
51465
- for (let i = 0; i < alpha.length; i++) if (alpha[i] !== 255) {
51466
- fullyOpaque = false;
51467
- break;
51468
- }
51469
- if (fullyOpaque) alpha = null;
51470
- }
51471
- return {
51472
- width,
51473
- height,
51474
- pixels,
51475
- alpha,
51476
- bitsPerComponent: 8
51477
- };
51478
- }
51479
- //#endregion
51480
51687
  //#region src/modules/pdf/render/pdf-exporter.ts
51481
51688
  /**
51482
51689
  * PDF Exporter - Main orchestrator for PDF document generation.
@@ -51822,39 +52029,6 @@ onmessage = async (ev) => {
51822
52029
  }
51823
52030
  return true;
51824
52031
  }
51825
- /**
51826
- * Write a JPEG or PNG image as a PDF XObject Image.
51827
- */
51828
- function writeImageXObject(writer, data, format) {
51829
- if (format === "png") return writePngImageXObject(writer, data);
51830
- return writeJpegImageXObject(writer, data);
51831
- }
51832
- /**
51833
- * Write a JPEG image using DCTDecode (raw JPEG data embedded directly).
51834
- */
51835
- function writeJpegImageXObject(writer, data) {
51836
- const objNum = writer.allocObject();
51837
- const dims = parseImageDimensions(data, "jpeg");
51838
- const dict = new PdfDict().set("Type", "/XObject").set("Subtype", "/Image").set("Width", pdfNumber(dims.width)).set("Height", pdfNumber(dims.height)).set("ColorSpace", "/DeviceRGB").set("BitsPerComponent", "8").set("Filter", "/DCTDecode");
51839
- writer.addStreamObject(objNum, dict, data);
51840
- return objNum;
51841
- }
51842
- /**
51843
- * Write a PNG image: decode to raw RGB, create SMask for alpha if needed.
51844
- */
51845
- function writePngImageXObject(writer, data) {
51846
- const png = decodePng(data);
51847
- const objNum = writer.allocObject();
51848
- const dict = new PdfDict().set("Type", "/XObject").set("Subtype", "/Image").set("Width", pdfNumber(png.width)).set("Height", pdfNumber(png.height)).set("ColorSpace", "/DeviceRGB").set("BitsPerComponent", pdfNumber(png.bitsPerComponent));
51849
- if (png.alpha) {
51850
- const smaskObjNum = writer.allocObject();
51851
- const smaskDict = new PdfDict().set("Type", "/XObject").set("Subtype", "/Image").set("Width", pdfNumber(png.width)).set("Height", pdfNumber(png.height)).set("ColorSpace", "/DeviceGray").set("BitsPerComponent", "8");
51852
- writer.addStreamObject(smaskObjNum, smaskDict, png.alpha);
51853
- dict.set("SMask", pdfRef(smaskObjNum));
51854
- }
51855
- writer.addStreamObject(objNum, dict, png.pixels);
51856
- return objNum;
51857
- }
51858
52032
  //#endregion
51859
52033
  //#region src/modules/pdf/pdf.ts
51860
52034
  /**