@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
@@ -12,6 +12,15 @@
12
12
  import { PdfContentStream } from "../core/pdf-stream.js";
13
13
  import { resolvePdfFontName } from "../font/font-manager.js";
14
14
  import { CELL_PADDING_H, CELL_PADDING_V, LINE_HEIGHT_FACTOR, INDENT_WIDTH } from "./constants.js";
15
+ import { parseImageDimensions } from "../builder/image-utils.js";
16
+ function computeCellPadding(cell, scaleFactor = 1) {
17
+ return {
18
+ left: (CELL_PADDING_H + cell.borderInsets.left) * scaleFactor,
19
+ right: (CELL_PADDING_H + cell.borderInsets.right) * scaleFactor,
20
+ top: (CELL_PADDING_V + cell.borderInsets.top) * scaleFactor,
21
+ bottom: (CELL_PADDING_V + cell.borderInsets.bottom) * scaleFactor
22
+ };
23
+ }
15
24
  /**
16
25
  * Render a single page to a PDF content stream.
17
26
  */
@@ -187,10 +196,9 @@ function drawCellText(stream, cell, fontManager, alphaValues, scaleFactor = 1) {
187
196
  if (!text && !cell.richText) {
188
197
  return;
189
198
  }
190
- const padH = CELL_PADDING_H * scaleFactor;
191
- const padV = CELL_PADDING_V * scaleFactor;
192
- const availWidth = rect.width - padH * 2;
193
- const availHeight = rect.height - padV * 2;
199
+ const pad = computeCellPadding(cell, scaleFactor);
200
+ const availWidth = rect.width - pad.left - pad.right;
201
+ const availHeight = rect.height - pad.top - pad.bottom;
194
202
  if (availWidth <= 0 || availHeight <= 0) {
195
203
  return;
196
204
  }
@@ -248,7 +256,7 @@ function drawCellText(stream, cell, fontManager, alphaValues, scaleFactor = 1) {
248
256
  const lineHeight = fontSize * LINE_HEIGHT_FACTOR;
249
257
  const ascent = fontManager.getFontAscent(resourceName, fontSize);
250
258
  const totalTextHeight = lines.length * lineHeight;
251
- const textStartY = computeTextStartY(verticalAlign, rect, totalTextHeight, ascent, padV);
259
+ const textStartY = computeTextStartY(verticalAlign, rect, totalTextHeight, ascent, pad.top, pad.bottom);
252
260
  stream.setFillColor(cell.textColor);
253
261
  stream.beginText();
254
262
  stream.setFont(resourceName, fontSize);
@@ -256,7 +264,7 @@ function drawCellText(stream, cell, fontManager, alphaValues, scaleFactor = 1) {
256
264
  const line = lines[i];
257
265
  const lineY = textStartY - i * lineHeight;
258
266
  const textWidth = measure(line);
259
- const textX = computeTextX(horizontalAlign, rect, textWidth, indentPts, padH);
267
+ const textX = computeTextX(horizontalAlign, rect, textWidth, indentPts, pad.left, pad.right);
260
268
  stream.setTextMatrix(1, 0, 0, 1, textX, lineY);
261
269
  const hexEncoded = fontManager.encodeText(line, resourceName);
262
270
  if (hexEncoded) {
@@ -267,7 +275,7 @@ function drawCellText(stream, cell, fontManager, alphaValues, scaleFactor = 1) {
267
275
  }
268
276
  }
269
277
  stream.endText();
270
- drawTextDecorations(stream, cell, lines, lineHeight, textStartY, measure, resourceName, fontManager, indentPts);
278
+ drawTextDecorations(stream, cell, lines, lineHeight, textStartY, measure, resourceName, fontManager, indentPts, pad);
271
279
  stream.restore();
272
280
  }
273
281
  // =============================================================================
@@ -276,8 +284,7 @@ function drawCellText(stream, cell, fontManager, alphaValues, scaleFactor = 1) {
276
284
  function drawRichText(stream, cell, fontManager, indentPts, scaleFactor = 1) {
277
285
  const { rect, horizontalAlign, verticalAlign, wrapText } = cell;
278
286
  const runs = cell.richText;
279
- const padH = CELL_PADDING_H * scaleFactor;
280
- const padV = CELL_PADDING_V * scaleFactor;
287
+ const pad = computeCellPadding(cell, scaleFactor);
281
288
  // Use the largest font size across all runs for line height calculation
282
289
  let maxFontSize = cell.fontSize;
283
290
  for (const run of runs) {
@@ -294,7 +301,7 @@ function drawRichText(stream, cell, fontManager, indentPts, scaleFactor = 1) {
294
301
  : fontManager.ensureFont(resolvePdfFontName(run.fontFamily, run.bold, run.italic));
295
302
  // --- Wrapping path ---
296
303
  if (wrapText) {
297
- const availWidth = rect.width - padH * 2 - indentPts;
304
+ const availWidth = rect.width - pad.left - pad.right - indentPts;
298
305
  if (availWidth <= 0) {
299
306
  return;
300
307
  }
@@ -313,7 +320,7 @@ function drawRichText(stream, cell, fontManager, indentPts, scaleFactor = 1) {
313
320
  const primaryResourceName = runResource(runs[0]);
314
321
  const ascent = fontManager.getFontAscent(primaryResourceName, primaryFontSize);
315
322
  const totalTextHeight = lines.length * lineHeight;
316
- const textStartY = computeTextStartY(verticalAlign, rect, totalTextHeight, ascent, padV);
323
+ const textStartY = computeTextStartY(verticalAlign, rect, totalTextHeight, ascent, pad.top, pad.bottom);
317
324
  let charPos = 0;
318
325
  for (let li = 0; li < lines.length; li++) {
319
326
  const lineY = textStartY - li * lineHeight;
@@ -351,7 +358,7 @@ function drawRichText(stream, cell, fontManager, indentPts, scaleFactor = 1) {
351
358
  for (const seg of segments) {
352
359
  lineWidth += fontManager.measureText(seg.text, seg.resourceName, seg.run.fontSize);
353
360
  }
354
- let textX = computeTextX(horizontalAlign, rect, lineWidth, indentPts, padH);
361
+ let textX = computeTextX(horizontalAlign, rect, lineWidth, indentPts, pad.left, pad.right);
355
362
  for (const seg of segments) {
356
363
  const { run, text, resourceName } = seg;
357
364
  const segWidth = fontManager.measureText(text, resourceName, run.fontSize);
@@ -394,8 +401,8 @@ function drawRichText(stream, cell, fontManager, indentPts, scaleFactor = 1) {
394
401
  }
395
402
  const primaryResourceName = runMetrics[0]?.resourceName ?? "F1";
396
403
  const ascent = fontManager.getFontAscent(primaryResourceName, primaryFontSize);
397
- const textStartY = computeTextStartY(verticalAlign, rect, lineHeight, ascent, padV);
398
- let textX = computeTextX(horizontalAlign, rect, totalWidth, indentPts, padH);
404
+ const textStartY = computeTextStartY(verticalAlign, rect, lineHeight, ascent, pad.top, pad.bottom);
405
+ let textX = computeTextX(horizontalAlign, rect, totalWidth, indentPts, pad.left, pad.right);
399
406
  for (let i = 0; i < runs.length; i++) {
400
407
  const run = runs[i];
401
408
  const { resourceName } = runMetrics[i];
@@ -432,8 +439,7 @@ function drawRichText(stream, cell, fontManager, indentPts, scaleFactor = 1) {
432
439
  function drawRotatedText(stream, cell, fontManager, indentPts, scaleFactor = 1) {
433
440
  const { rect, wrapText } = cell;
434
441
  let { fontSize } = cell;
435
- const padH = CELL_PADDING_H * scaleFactor;
436
- const padV = CELL_PADDING_V * scaleFactor;
442
+ const pad = computeCellPadding(cell, scaleFactor);
437
443
  const isEmbedded = fontManager.hasEmbeddedFont();
438
444
  const resourceName = isEmbedded
439
445
  ? fontManager.getEmbeddedResourceName()
@@ -445,8 +451,8 @@ function drawRotatedText(stream, cell, fontManager, indentPts, scaleFactor = 1)
445
451
  const sin = Math.sin(radians);
446
452
  const absSin = Math.abs(sin);
447
453
  const absCos = Math.abs(cos);
448
- const maxWidth = rect.width - padH * 2;
449
- const maxHeight = rect.height - padV * 2;
454
+ const maxWidth = rect.width - pad.left - pad.right;
455
+ const maxHeight = rect.height - pad.top - pad.bottom;
450
456
  // Available length along the text flow direction for wrapping
451
457
  let availTextLength;
452
458
  if (absSin > 0.01 && absCos > 0.01) {
@@ -494,11 +500,11 @@ function drawRotatedText(stream, cell, fontManager, indentPts, scaleFactor = 1)
494
500
  stream.setFillColor(cell.textColor);
495
501
  if (is90) {
496
502
  // Text reads bottom-to-top. Each line becomes a column drawn left-to-right.
497
- drawRotated90(stream, cell, lines, fontManager, resourceName, fontSize, scaledLineHeight, ascent, padH, padV);
503
+ drawRotated90(stream, cell, lines, fontManager, resourceName, fontSize, scaledLineHeight, ascent, pad);
498
504
  }
499
505
  else if (isMinus90) {
500
506
  // Text reads top-to-bottom. Each line becomes a column drawn right-to-left.
501
- drawRotatedMinus90(stream, cell, lines, fontManager, resourceName, fontSize, scaledLineHeight, ascent, padH, padV);
507
+ drawRotatedMinus90(stream, cell, lines, fontManager, resourceName, fontSize, scaledLineHeight, ascent, pad);
502
508
  }
503
509
  else {
504
510
  // General rotation — center multi-line text block in cell
@@ -506,7 +512,7 @@ function drawRotatedText(stream, cell, fontManager, indentPts, scaleFactor = 1)
506
512
  }
507
513
  }
508
514
  /** 90° CCW: text reads bottom-to-top, lines stack left-to-right. */
509
- function drawRotated90(stream, cell, lines, fontManager, resourceName, fontSize, lineHeight, ascent, padH, padV) {
515
+ function drawRotated90(stream, cell, lines, fontManager, resourceName, fontSize, lineHeight, ascent, pad) {
510
516
  const { rect, horizontalAlign, verticalAlign } = cell;
511
517
  const totalColumnsWidth = lines.length * lineHeight;
512
518
  // horizontalAlign controls X placement of line columns (same visual axis)
@@ -515,11 +521,11 @@ function drawRotated90(stream, cell, lines, fontManager, resourceName, fontSize,
515
521
  startX = rect.x + rect.width / 2 - totalColumnsWidth / 2 + ascent;
516
522
  }
517
523
  else if (horizontalAlign === "right") {
518
- startX = rect.x + rect.width - padH - totalColumnsWidth + ascent;
524
+ startX = rect.x + rect.width - pad.right - totalColumnsWidth + ascent;
519
525
  }
520
526
  else {
521
527
  // left (default)
522
- startX = rect.x + padH + ascent;
528
+ startX = rect.x + pad.left + ascent;
523
529
  }
524
530
  for (let i = 0; i < lines.length; i++) {
525
531
  const line = lines[i];
@@ -530,16 +536,16 @@ function drawRotated90(stream, cell, lines, fontManager, resourceName, fontSize,
530
536
  let ty;
531
537
  if (verticalAlign === "top") {
532
538
  // text at top → text end near top → ty starts at bottom so text reaches top
533
- ty = rect.y + rect.height - padV - lineWidth;
539
+ ty = rect.y + rect.height - pad.top - lineWidth;
534
540
  }
535
541
  else if (verticalAlign === "middle") {
536
542
  ty = rect.y + (rect.height - lineWidth) / 2;
537
543
  }
538
544
  else {
539
545
  // bottom (default) → text at bottom → ty near bottom
540
- ty = rect.y + padV;
546
+ ty = rect.y + pad.bottom;
541
547
  }
542
- ty = Math.max(ty, rect.y + padV);
548
+ ty = Math.max(ty, rect.y + pad.bottom);
543
549
  stream.beginText();
544
550
  stream.setFont(resourceName, fontSize);
545
551
  stream.setTextMatrix(0, 1, -1, 0, colX, ty);
@@ -548,7 +554,7 @@ function drawRotated90(stream, cell, lines, fontManager, resourceName, fontSize,
548
554
  }
549
555
  }
550
556
  /** -90° (270° CW): text reads top-to-bottom, lines stack right-to-left. */
551
- function drawRotatedMinus90(stream, cell, lines, fontManager, resourceName, fontSize, lineHeight, ascent, padH, padV) {
557
+ function drawRotatedMinus90(stream, cell, lines, fontManager, resourceName, fontSize, lineHeight, ascent, pad) {
552
558
  const { rect, horizontalAlign, verticalAlign } = cell;
553
559
  const totalColumnsWidth = lines.length * lineHeight;
554
560
  // horizontalAlign controls X placement: lines stack right-to-left
@@ -557,11 +563,11 @@ function drawRotatedMinus90(stream, cell, lines, fontManager, resourceName, font
557
563
  startX = rect.x + rect.width / 2 + totalColumnsWidth / 2 - lineHeight + ascent;
558
564
  }
559
565
  else if (horizontalAlign === "right") {
560
- startX = rect.x + rect.width - padH - lineHeight + ascent;
566
+ startX = rect.x + rect.width - pad.right - lineHeight + ascent;
561
567
  }
562
568
  else {
563
569
  // left (default)
564
- startX = rect.x + padH + totalColumnsWidth - lineHeight + ascent;
570
+ startX = rect.x + pad.left + totalColumnsWidth - lineHeight + ascent;
565
571
  }
566
572
  for (let i = 0; i < lines.length; i++) {
567
573
  const line = lines[i];
@@ -572,16 +578,16 @@ function drawRotatedMinus90(stream, cell, lines, fontManager, resourceName, font
572
578
  let ty;
573
579
  if (verticalAlign === "top") {
574
580
  // text at top → ty near top (high PDF y)
575
- ty = rect.y + rect.height - padV;
581
+ ty = rect.y + rect.height - pad.top;
576
582
  }
577
583
  else if (verticalAlign === "middle") {
578
584
  ty = rect.y + (rect.height + lineWidth) / 2;
579
585
  }
580
586
  else {
581
587
  // bottom (default) → text at bottom → ty so text ends at bottom
582
- ty = rect.y + padV + lineWidth;
588
+ ty = rect.y + pad.bottom + lineWidth;
583
589
  }
584
- ty = Math.min(ty, rect.y + rect.height - padV);
590
+ ty = Math.min(ty, rect.y + rect.height - pad.top);
585
591
  stream.beginText();
586
592
  stream.setFont(resourceName, fontSize);
587
593
  stream.setTextMatrix(0, -1, 1, 0, colX, ty);
@@ -592,8 +598,8 @@ function drawRotatedMinus90(stream, cell, lines, fontManager, resourceName, font
592
598
  /** General rotation — center a multi-line text block in the cell. */
593
599
  function drawRotatedGeneral(stream, cell, lines, fontManager, resourceName, fontSize, lineHeight, ascent, cos, sin, indentPts) {
594
600
  const { rect, horizontalAlign, verticalAlign } = cell;
595
- const padH = CELL_PADDING_H;
596
- const padV = CELL_PADDING_V;
601
+ // Use border-aware padding (no scaleFactor — font size is already scaled by caller)
602
+ const pad = computeCellPadding(cell);
597
603
  // Compute the rotated bounding box of the text block
598
604
  let maxLineWidth = 0;
599
605
  for (const line of lines) {
@@ -613,10 +619,10 @@ function drawRotatedGeneral(stream, cell, lines, fontManager, resourceName, font
613
619
  const indentOffset = horizontalAlign === "left" ? indentPts / 2 : horizontalAlign === "right" ? -indentPts / 2 : 0;
614
620
  let cy;
615
621
  if (verticalAlign === "top") {
616
- cy = rect.y + rect.height - padV - rotatedHeight / 2;
622
+ cy = rect.y + rect.height - pad.top - rotatedHeight / 2;
617
623
  }
618
624
  else if (verticalAlign === "bottom") {
619
- cy = rect.y + padV + rotatedHeight / 2;
625
+ cy = rect.y + pad.bottom + rotatedHeight / 2;
620
626
  }
621
627
  else {
622
628
  // middle (default)
@@ -629,10 +635,10 @@ function drawRotatedGeneral(stream, cell, lines, fontManager, resourceName, font
629
635
  const slantAtCy = slantShift * 2 * verticalRatio; // slantShift*2 = full slantOffset
630
636
  let cx;
631
637
  if (horizontalAlign === "right") {
632
- cx = rect.x + rect.width - padH - rotatedWidth / 2 + indentOffset + slantAtCy;
638
+ cx = rect.x + rect.width - pad.right - rotatedWidth / 2 + indentOffset + slantAtCy;
633
639
  }
634
640
  else if (horizontalAlign === "left") {
635
- cx = rect.x + padH + rotatedWidth / 2 + indentOffset + slantAtCy;
641
+ cx = rect.x + pad.left + rotatedWidth / 2 + indentOffset + slantAtCy;
636
642
  }
637
643
  else {
638
644
  // center (default for rotated)
@@ -669,8 +675,7 @@ function emitText(stream, fontManager, text, resourceName) {
669
675
  */
670
676
  function drawVerticalStackedText(stream, cell, fontManager, _indentPts, scaleFactor = 1) {
671
677
  const { rect, text, fontSize, horizontalAlign, verticalAlign } = cell;
672
- const padH = CELL_PADDING_H * scaleFactor;
673
- const padV = CELL_PADDING_V * scaleFactor;
678
+ const pad = computeCellPadding(cell, scaleFactor);
674
679
  const isEmbedded = fontManager.hasEmbeddedFont();
675
680
  const resourceName = isEmbedded
676
681
  ? fontManager.getEmbeddedResourceName()
@@ -687,11 +692,11 @@ function drawVerticalStackedText(stream, cell, fontManager, _indentPts, scaleFac
687
692
  startX = rect.x + rect.width / 2 - totalColumnsWidth / 2 + columnWidth / 2;
688
693
  }
689
694
  else if (horizontalAlign === "right") {
690
- startX = rect.x + rect.width - padH - totalColumnsWidth + columnWidth / 2;
695
+ startX = rect.x + rect.width - pad.right - totalColumnsWidth + columnWidth / 2;
691
696
  }
692
697
  else {
693
698
  // left (default)
694
- startX = rect.x + padH + columnWidth / 2;
699
+ startX = rect.x + pad.left + columnWidth / 2;
695
700
  }
696
701
  stream.setFillColor(cell.textColor);
697
702
  for (let colIdx = 0; colIdx < columns.length; colIdx++) {
@@ -704,14 +709,14 @@ function drawVerticalStackedText(stream, cell, fontManager, _indentPts, scaleFac
704
709
  currentY = rect.y + rect.height / 2 + totalTextHeight / 2 - ascent;
705
710
  }
706
711
  else if (verticalAlign === "bottom") {
707
- currentY = rect.y + padV + totalTextHeight - ascent;
712
+ currentY = rect.y + pad.bottom + totalTextHeight - ascent;
708
713
  }
709
714
  else {
710
715
  // top (default)
711
- currentY = rect.y + rect.height - padV - ascent;
716
+ currentY = rect.y + rect.height - pad.top - ascent;
712
717
  }
713
718
  for (const ch of colText) {
714
- if (currentY < rect.y + padV) {
719
+ if (currentY < rect.y + pad.bottom) {
715
720
  break;
716
721
  }
717
722
  const charWidth = fontManager.measureText(ch, resourceName, fontSize);
@@ -738,60 +743,60 @@ export function alphaGsName(alpha) {
738
743
  // =============================================================================
739
744
  // Text Layout Helpers
740
745
  // =============================================================================
741
- export function computeTextStartY(verticalAlign, rect, totalTextHeight, ascent, padV = CELL_PADDING_V) {
746
+ export function computeTextStartY(verticalAlign, rect, totalTextHeight, ascent, padVTop = CELL_PADDING_V, padVBottom = padVTop) {
742
747
  let y;
743
748
  switch (verticalAlign) {
744
749
  case "top":
745
- y = rect.y + rect.height - padV - ascent;
750
+ y = rect.y + rect.height - padVTop - ascent;
746
751
  break;
747
752
  case "middle":
748
753
  y = rect.y + rect.height / 2 + totalTextHeight / 2 - ascent;
749
754
  break;
750
755
  case "bottom":
751
756
  default:
752
- y = rect.y + padV + (totalTextHeight - ascent);
757
+ y = rect.y + padVBottom + (totalTextHeight - ascent);
753
758
  break;
754
759
  }
755
760
  // Clamp: ensure text ascent doesn't exceed the cell top
756
- const maxY = rect.y + rect.height - padV - ascent;
761
+ const maxY = rect.y + rect.height - padVTop - ascent;
757
762
  if (y > maxY) {
758
763
  y = maxY;
759
764
  }
760
765
  // Clamp: ensure text descent doesn't go below cell bottom
761
- const minY = rect.y + padV;
766
+ const minY = rect.y + padVBottom;
762
767
  if (y < minY) {
763
768
  y = minY;
764
769
  }
765
770
  return y;
766
771
  }
767
- export function computeTextX(align, rect, textWidth, indentPts = 0, padH = CELL_PADDING_H) {
772
+ export function computeTextX(align, rect, textWidth, indentPts = 0, padHLeft = CELL_PADDING_H, padHRight = padHLeft) {
768
773
  let x;
769
774
  switch (align) {
770
775
  case "center":
771
776
  x = rect.x + (rect.width - textWidth) / 2;
772
777
  break;
773
778
  case "right":
774
- x = rect.x + rect.width - padH - textWidth;
779
+ x = rect.x + rect.width - padHRight - textWidth;
775
780
  break;
776
781
  default:
777
- x = rect.x + padH + indentPts;
782
+ x = rect.x + padHLeft + indentPts;
778
783
  break;
779
784
  }
780
785
  // Clamp: don't start before cell left edge
781
- const minX = rect.x + padH;
786
+ const minX = rect.x + padHLeft;
782
787
  if (x < minX) {
783
788
  x = minX;
784
789
  }
785
790
  return x;
786
791
  }
787
- function drawTextDecorations(stream, cell, lines, lineHeight, textStartY, measure, resourceName, fontManager, indentPts) {
792
+ function drawTextDecorations(stream, cell, lines, lineHeight, textStartY, measure, resourceName, fontManager, indentPts, pad) {
788
793
  if (cell.strike) {
789
794
  const descent = fontManager.getFontDescent(resourceName, cell.fontSize);
790
795
  const strikeY = textStartY + descent + cell.fontSize * 0.3;
791
796
  for (let i = 0; i < lines.length; i++) {
792
797
  const lineY = strikeY - i * lineHeight;
793
798
  const lw = measure(lines[i]);
794
- const startX = computeTextX(cell.horizontalAlign, cell.rect, lw, indentPts);
799
+ const startX = computeTextX(cell.horizontalAlign, cell.rect, lw, indentPts, pad?.left, pad?.right);
795
800
  stream.drawLine(startX, lineY, startX + lw, lineY, cell.textColor, 0.5);
796
801
  }
797
802
  }
@@ -801,7 +806,7 @@ function drawTextDecorations(stream, cell, lines, lineHeight, textStartY, measur
801
806
  for (let i = 0; i < lines.length; i++) {
802
807
  const lineY = textStartY - i * lineHeight + underlineOffset;
803
808
  const lw = measure(lines[i]);
804
- const startX = computeTextX(cell.horizontalAlign, cell.rect, lw, indentPts);
809
+ const startX = computeTextX(cell.horizontalAlign, cell.rect, lw, indentPts, pad?.left, pad?.right);
805
810
  stream.drawLine(startX, lineY, startX + lw, lineY, cell.textColor, 0.5);
806
811
  }
807
812
  }
@@ -1070,52 +1075,6 @@ function renderImageWatermark(stream, page, watermark) {
1070
1075
  /**
1071
1076
  * Parse image dimensions from raw JPEG or PNG data without a full decode.
1072
1077
  */
1073
- export function parseImageDimensions(data, format) {
1074
- if (format === "png") {
1075
- return parsePngDimensions(data);
1076
- }
1077
- return parseJpegDimensions(data);
1078
- }
1079
- /** Read width/height from a PNG IHDR chunk (bytes 16-23). */
1080
- function parsePngDimensions(data) {
1081
- // PNG header: 8 byte signature, then IHDR chunk: 4 byte length, 4 byte type, 4 byte width, 4 byte height
1082
- if (data.length >= 24 &&
1083
- data[12] === 0x49 &&
1084
- data[13] === 0x48 &&
1085
- data[14] === 0x44 &&
1086
- data[15] === 0x52) {
1087
- const width = (data[16] << 24) | (data[17] << 16) | (data[18] << 8) | data[19];
1088
- const height = (data[20] << 24) | (data[21] << 16) | (data[22] << 8) | data[23];
1089
- return { width, height };
1090
- }
1091
- return { width: 1, height: 1 };
1092
- }
1093
- /** Read width/height from JPEG SOF marker. */
1094
- function parseJpegDimensions(data) {
1095
- let offset = 2; // skip SOI marker
1096
- while (offset < data.length - 1) {
1097
- while (offset < data.length && data[offset] === 0xff && data[offset + 1] === 0xff) {
1098
- offset++;
1099
- }
1100
- if (offset >= data.length - 1 || data[offset] !== 0xff) {
1101
- break;
1102
- }
1103
- const marker = data[offset + 1];
1104
- const isSof = marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc;
1105
- if (isSof && offset + 8 < data.length) {
1106
- return {
1107
- width: (data[offset + 7] << 8) | data[offset + 8],
1108
- height: (data[offset + 5] << 8) | data[offset + 6]
1109
- };
1110
- }
1111
- if (offset + 3 >= data.length) {
1112
- break;
1113
- }
1114
- const segLen = (data[offset + 2] << 8) | data[offset + 3];
1115
- offset += 2 + segLen;
1116
- }
1117
- return { width: 1, height: 1 };
1118
- }
1119
1078
  /**
1120
1079
  * Resolve the center position for a watermark on a given page.
1121
1080
  */
@@ -14,8 +14,8 @@ import { FontManager, resolvePdfFontName } from "../font/font-manager.js";
14
14
  import { parseTtf } from "../font/ttf-parser.js";
15
15
  import { initEncryption } from "../core/encryption.js";
16
16
  import { layoutSheet } from "./layout-engine.js";
17
- import { renderPage, alphaGsName, renderWatermark, parseImageDimensions } from "./page-renderer.js";
18
- import { decodePng } from "./png-decoder.js";
17
+ import { renderPage, alphaGsName, renderWatermark } from "./page-renderer.js";
18
+ import { writeImageXObject } from "../builder/image-utils.js";
19
19
  import { PdfError, PdfRenderError } from "../errors.js";
20
20
  import { PageSizes } from "../types.js";
21
21
  import { argbToPdfColor } from "./style-converter.js";
@@ -500,62 +500,3 @@ function isWatermarkApplicable(watermark, page) {
500
500
  }
501
501
  return true;
502
502
  }
503
- // =============================================================================
504
- // Image XObject
505
- // =============================================================================
506
- /**
507
- * Write a JPEG or PNG image as a PDF XObject Image.
508
- */
509
- function writeImageXObject(writer, data, format) {
510
- if (format === "png") {
511
- return writePngImageXObject(writer, data);
512
- }
513
- return writeJpegImageXObject(writer, data);
514
- }
515
- /**
516
- * Write a JPEG image using DCTDecode (raw JPEG data embedded directly).
517
- */
518
- function writeJpegImageXObject(writer, data) {
519
- const objNum = writer.allocObject();
520
- const dims = parseImageDimensions(data, "jpeg");
521
- const dict = new PdfDict()
522
- .set("Type", "/XObject")
523
- .set("Subtype", "/Image")
524
- .set("Width", pdfNumber(dims.width))
525
- .set("Height", pdfNumber(dims.height))
526
- .set("ColorSpace", "/DeviceRGB")
527
- .set("BitsPerComponent", "8")
528
- .set("Filter", "/DCTDecode");
529
- writer.addStreamObject(objNum, dict, data);
530
- return objNum;
531
- }
532
- /**
533
- * Write a PNG image: decode to raw RGB, create SMask for alpha if needed.
534
- */
535
- function writePngImageXObject(writer, data) {
536
- const png = decodePng(data);
537
- const objNum = writer.allocObject();
538
- const dict = new PdfDict()
539
- .set("Type", "/XObject")
540
- .set("Subtype", "/Image")
541
- .set("Width", pdfNumber(png.width))
542
- .set("Height", pdfNumber(png.height))
543
- .set("ColorSpace", "/DeviceRGB")
544
- .set("BitsPerComponent", pdfNumber(png.bitsPerComponent));
545
- // If the image has an alpha channel, create a soft mask XObject
546
- if (png.alpha) {
547
- const smaskObjNum = writer.allocObject();
548
- const smaskDict = new PdfDict()
549
- .set("Type", "/XObject")
550
- .set("Subtype", "/Image")
551
- .set("Width", pdfNumber(png.width))
552
- .set("Height", pdfNumber(png.height))
553
- .set("ColorSpace", "/DeviceGray")
554
- .set("BitsPerComponent", "8");
555
- writer.addStreamObject(smaskObjNum, smaskDict, png.alpha);
556
- dict.set("SMask", pdfRef(smaskObjNum));
557
- }
558
- // RGB pixel data — compression handled by addStreamObject
559
- writer.addStreamObject(objNum, dict, png.pixels);
560
- return objNum;
561
- }
@@ -53,6 +53,10 @@ export declare function extractFontProperties(font: Partial<PdfFontStyle> | unde
53
53
  * Other patterns are approximated or ignored.
54
54
  */
55
55
  export declare function excelFillToPdfColor(fill: PdfFillData | undefined): PdfColor | null;
56
+ /**
57
+ * Map border styles to PDF line widths.
58
+ */
59
+ export declare function borderStyleToLineWidth(style: string): number;
56
60
  /**
57
61
  * Convert border data to PDF LayoutBorders.
58
62
  */
@@ -179,7 +179,7 @@ export function excelFillToPdfColor(fill) {
179
179
  /**
180
180
  * Map border styles to PDF line widths.
181
181
  */
182
- function borderStyleToLineWidth(style) {
182
+ export function borderStyleToLineWidth(style) {
183
183
  switch (style) {
184
184
  case "thin":
185
185
  return 0.25;
@@ -655,8 +655,21 @@ export interface LayoutCell {
655
655
  verticalAlign: "top" | "middle" | "bottom";
656
656
  /** Whether text wrapping is enabled */
657
657
  wrapText: boolean;
658
- /** Border definitions for this cell */
658
+ /** Border definitions for this cell (after shared-edge resolution: only edges this cell draws) */
659
659
  borders: LayoutBorders;
660
+ /**
661
+ * Effective border insets in points for text padding.
662
+ *
663
+ * On a shared edge the border line is drawn by only one of the two cells,
664
+ * but it still visually intrudes into both. These values record the actual
665
+ * half-width intrusion on each side regardless of which cell draws the line.
666
+ */
667
+ borderInsets: {
668
+ top: number;
669
+ right: number;
670
+ bottom: number;
671
+ left: number;
672
+ };
660
673
  /** Number of columns this cell spans (for merged cells) */
661
674
  colSpan: number;
662
675
  /** Number of rows this cell spans (for merged cells) */
@@ -232,7 +232,10 @@ export class Readable extends EventEmitter {
232
232
  err.code = "ERR_INVALID_ARG_TYPE";
233
233
  throw err;
234
234
  }
235
- const readable = new Readable({ ...options, objectMode: options?.objectMode ?? true });
235
+ const readable = new Readable({
236
+ ...options,
237
+ objectMode: options?.objectMode ?? true
238
+ });
236
239
  const iter = iterable;
237
240
  // Node.js treats strings as a single chunk, not as Iterable<char>.
238
241
  // Match that behavior by wrapping strings in an array.
@@ -1738,7 +1741,10 @@ export class Readable extends EventEmitter {
1738
1741
  }
1739
1742
  }
1740
1743
  else {
1741
- for await (const item of _mapWithConcurrency(this, async (chunk) => ({ chunk, match: await fn(chunk, { signal: innerSignal }) }), concurrency, signal)) {
1744
+ for await (const item of _mapWithConcurrency(this, async (chunk) => ({
1745
+ chunk,
1746
+ match: await fn(chunk, { signal: innerSignal })
1747
+ }), concurrency, signal)) {
1742
1748
  if (item.match) {
1743
1749
  this.destroy();
1744
1750
  return item.chunk;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Cryptographic primitives — Browser version.
3
+ *
4
+ * Uses pure JavaScript for synchronous operations (SHA-256, MD5, AES-CBC, RC4)
5
+ * since Web Crypto API is async-only and cannot replace synchronous call sites.
6
+ *
7
+ * Uses Web Crypto API for:
8
+ * - `randomBytes` (crypto.getRandomValues — truly random)
9
+ * - `rsaVerify` / `rsaSign` (SubtleCrypto — hardware-accelerated)
10
+ *
11
+ * Exports the same API as `crypto.ts` (Node.js version).
12
+ */
13
+ export declare function aesCbcEncrypt(plaintext: Uint8Array, key: Uint8Array, iv: Uint8Array): Uint8Array;
14
+ export declare function aesCbcDecrypt(ciphertext: Uint8Array, key: Uint8Array, iv: Uint8Array): Uint8Array;
15
+ export declare function aesCbcDecryptRaw(ciphertext: Uint8Array, key: Uint8Array, iv: Uint8Array): Uint8Array;
16
+ export declare function aesCbcEncryptRaw(plaintext: Uint8Array, key: Uint8Array, iv: Uint8Array): Uint8Array;
17
+ export declare function aesEcbEncrypt(block: Uint8Array, key: Uint8Array): Uint8Array;
18
+ export declare function sha256(input: Uint8Array): Uint8Array;
19
+ export declare function md5(input: Uint8Array): Uint8Array;
20
+ /**
21
+ * RC4 stream cipher.
22
+ * @deprecated Only used for reading legacy encrypted PDFs.
23
+ */
24
+ export declare function rc4(key: Uint8Array, data: Uint8Array): Uint8Array;
25
+ /**
26
+ * Generate cryptographically secure random bytes.
27
+ * Uses crypto.getRandomValues (available in all modern browsers).
28
+ */
29
+ export declare function randomBytes(length: number): Uint8Array;
30
+ /**
31
+ * Compute a hash digest using Web Crypto API.
32
+ *
33
+ * NOTE: In the browser, this is async. The Node.js version is sync.
34
+ * For callers that need sync hashing, use `sha256()` or `md5()` directly.
35
+ *
36
+ * @param algorithm - Hash algorithm name (e.g., "SHA-256", "SHA-512", "SHA-1").
37
+ * @param data - Data to hash
38
+ * @returns The digest bytes
39
+ */
40
+ export declare function hashAsync(algorithm: string, data: Uint8Array): Promise<Uint8Array>;
41
+ /**
42
+ * Compute a hash digest synchronously (pure JS — SHA-256 and MD5 only).
43
+ *
44
+ * @param algorithm - "SHA-256" or "MD5" (case-insensitive, hyphens optional)
45
+ * @param data - Data to hash
46
+ * @returns The digest bytes
47
+ * @throws If algorithm is not SHA-256 or MD5
48
+ */
49
+ export declare function hash(algorithm: string, data: Uint8Array): Uint8Array;
50
+ /**
51
+ * Verify an RSA PKCS#1 v1.5 signature.
52
+ *
53
+ * @param publicKeyDer - DER-encoded SubjectPublicKeyInfo
54
+ * @param signature - The signature bytes
55
+ * @param data - The signed data (will be hashed with SHA-256)
56
+ */
57
+ export declare function rsaVerify(publicKeyDer: Uint8Array, signature: Uint8Array, data: Uint8Array): Promise<boolean>;
58
+ /**
59
+ * Create an RSA PKCS#1 v1.5 signature.
60
+ *
61
+ * @param privateKeyDer - DER-encoded PKCS#8 private key
62
+ * @param data - The data to sign (will be hashed with SHA-256)
63
+ */
64
+ export declare function rsaSign(privateKeyDer: Uint8Array, data: Uint8Array): Promise<Uint8Array>;