@cj-tech-master/excelts 9.0.0 → 9.1.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 (96) hide show
  1. package/dist/browser/index.browser.d.ts +2 -0
  2. package/dist/browser/index.browser.js +2 -0
  3. package/dist/browser/index.d.ts +2 -0
  4. package/dist/browser/index.js +2 -0
  5. package/dist/browser/modules/excel/image.d.ts +27 -2
  6. package/dist/browser/modules/excel/image.js +23 -1
  7. package/dist/browser/modules/excel/stream/worksheet-writer.d.ts +16 -1
  8. package/dist/browser/modules/excel/stream/worksheet-writer.js +68 -0
  9. package/dist/browser/modules/excel/types.d.ts +72 -0
  10. package/dist/browser/modules/excel/utils/drawing-utils.d.ts +4 -0
  11. package/dist/browser/modules/excel/utils/drawing-utils.js +5 -0
  12. package/dist/browser/modules/excel/utils/ooxml-paths.d.ts +4 -0
  13. package/dist/browser/modules/excel/utils/ooxml-paths.js +15 -0
  14. package/dist/browser/modules/excel/utils/watermark-image.d.ts +67 -0
  15. package/dist/browser/modules/excel/utils/watermark-image.js +383 -0
  16. package/dist/browser/modules/excel/worksheet.d.ts +39 -1
  17. package/dist/browser/modules/excel/worksheet.js +99 -0
  18. package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +3 -2
  19. package/dist/browser/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +6 -1
  20. package/dist/browser/modules/excel/xlsx/xform/drawing/blip-fill-xform.d.ts +2 -1
  21. package/dist/browser/modules/excel/xlsx/xform/drawing/blip-fill-xform.js +0 -1
  22. package/dist/browser/modules/excel/xlsx/xform/drawing/blip-xform.d.ts +3 -1
  23. package/dist/browser/modules/excel/xlsx/xform/drawing/blip-xform.js +22 -6
  24. package/dist/browser/modules/excel/xlsx/xform/drawing/pic-xform.d.ts +3 -0
  25. package/dist/browser/modules/excel/xlsx/xform/drawing/pic-xform.js +5 -1
  26. package/dist/browser/modules/excel/xlsx/xform/drawing/vml-drawing-xform.d.ts +19 -0
  27. package/dist/browser/modules/excel/xlsx/xform/drawing/vml-drawing-xform.js +103 -4
  28. package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +135 -8
  29. package/dist/browser/modules/excel/xlsx/xlsx.browser.d.ts +1 -0
  30. package/dist/browser/modules/excel/xlsx/xlsx.browser.js +53 -1
  31. package/dist/browser/modules/pdf/core/pdf-writer.d.ts +1 -1
  32. package/dist/browser/modules/pdf/core/pdf-writer.js +2 -1
  33. package/dist/browser/modules/pdf/index.d.ts +1 -1
  34. package/dist/browser/modules/pdf/render/page-renderer.d.ts +29 -1
  35. package/dist/browser/modules/pdf/render/page-renderer.js +394 -25
  36. package/dist/browser/modules/pdf/render/pdf-exporter.js +84 -47
  37. package/dist/browser/modules/pdf/types.d.ts +235 -0
  38. package/dist/cjs/index.js +5 -2
  39. package/dist/cjs/modules/excel/image.js +23 -1
  40. package/dist/cjs/modules/excel/stream/worksheet-writer.js +68 -0
  41. package/dist/cjs/modules/excel/utils/drawing-utils.js +5 -0
  42. package/dist/cjs/modules/excel/utils/ooxml-paths.js +19 -0
  43. package/dist/cjs/modules/excel/utils/watermark-image.js +386 -0
  44. package/dist/cjs/modules/excel/worksheet.js +99 -0
  45. package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +3 -2
  46. package/dist/cjs/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +6 -1
  47. package/dist/cjs/modules/excel/xlsx/xform/drawing/blip-fill-xform.js +0 -1
  48. package/dist/cjs/modules/excel/xlsx/xform/drawing/blip-xform.js +22 -6
  49. package/dist/cjs/modules/excel/xlsx/xform/drawing/pic-xform.js +5 -1
  50. package/dist/cjs/modules/excel/xlsx/xform/drawing/vml-drawing-xform.js +103 -4
  51. package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +134 -7
  52. package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +52 -0
  53. package/dist/cjs/modules/pdf/core/pdf-writer.js +2 -1
  54. package/dist/cjs/modules/pdf/render/page-renderer.js +396 -25
  55. package/dist/cjs/modules/pdf/render/pdf-exporter.js +83 -46
  56. package/dist/esm/index.browser.js +2 -0
  57. package/dist/esm/index.js +2 -0
  58. package/dist/esm/modules/excel/image.js +23 -1
  59. package/dist/esm/modules/excel/stream/worksheet-writer.js +68 -0
  60. package/dist/esm/modules/excel/utils/drawing-utils.js +5 -0
  61. package/dist/esm/modules/excel/utils/ooxml-paths.js +15 -0
  62. package/dist/esm/modules/excel/utils/watermark-image.js +383 -0
  63. package/dist/esm/modules/excel/worksheet.js +99 -0
  64. package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +3 -2
  65. package/dist/esm/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +6 -1
  66. package/dist/esm/modules/excel/xlsx/xform/drawing/blip-fill-xform.js +0 -1
  67. package/dist/esm/modules/excel/xlsx/xform/drawing/blip-xform.js +22 -6
  68. package/dist/esm/modules/excel/xlsx/xform/drawing/pic-xform.js +5 -1
  69. package/dist/esm/modules/excel/xlsx/xform/drawing/vml-drawing-xform.js +103 -4
  70. package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +135 -8
  71. package/dist/esm/modules/excel/xlsx/xlsx.browser.js +53 -1
  72. package/dist/esm/modules/pdf/core/pdf-writer.js +2 -1
  73. package/dist/esm/modules/pdf/render/page-renderer.js +394 -25
  74. package/dist/esm/modules/pdf/render/pdf-exporter.js +84 -47
  75. package/dist/iife/excelts.iife.js +2390 -469
  76. package/dist/iife/excelts.iife.js.map +1 -1
  77. package/dist/iife/excelts.iife.min.js +47 -47
  78. package/dist/types/index.browser.d.ts +2 -0
  79. package/dist/types/index.d.ts +2 -0
  80. package/dist/types/modules/excel/image.d.ts +27 -2
  81. package/dist/types/modules/excel/stream/worksheet-writer.d.ts +16 -1
  82. package/dist/types/modules/excel/types.d.ts +72 -0
  83. package/dist/types/modules/excel/utils/drawing-utils.d.ts +4 -0
  84. package/dist/types/modules/excel/utils/ooxml-paths.d.ts +4 -0
  85. package/dist/types/modules/excel/utils/watermark-image.d.ts +67 -0
  86. package/dist/types/modules/excel/worksheet.d.ts +39 -1
  87. package/dist/types/modules/excel/xlsx/xform/drawing/blip-fill-xform.d.ts +2 -1
  88. package/dist/types/modules/excel/xlsx/xform/drawing/blip-xform.d.ts +3 -1
  89. package/dist/types/modules/excel/xlsx/xform/drawing/pic-xform.d.ts +3 -0
  90. package/dist/types/modules/excel/xlsx/xform/drawing/vml-drawing-xform.d.ts +19 -0
  91. package/dist/types/modules/excel/xlsx/xlsx.browser.d.ts +1 -0
  92. package/dist/types/modules/pdf/core/pdf-writer.d.ts +1 -1
  93. package/dist/types/modules/pdf/index.d.ts +1 -1
  94. package/dist/types/modules/pdf/render/page-renderer.d.ts +29 -1
  95. package/dist/types/modules/pdf/types.d.ts +235 -0
  96. package/package.json +1 -1
@@ -12,6 +12,7 @@ exports.isBinaryEntryPath = isBinaryEntryPath;
12
12
  exports.getDrawingNameFromPath = getDrawingNameFromPath;
13
13
  exports.getDrawingNameFromRelsPath = getDrawingNameFromRelsPath;
14
14
  exports.getVmlDrawingNameFromPath = getVmlDrawingNameFromPath;
15
+ exports.getVmlDrawingHFNameFromPath = getVmlDrawingHFNameFromPath;
15
16
  exports.getCommentsIndexFromPath = getCommentsIndexFromPath;
16
17
  exports.getTableNameFromPath = getTableNameFromPath;
17
18
  exports.getPivotTableNameFromPath = getPivotTableNameFromPath;
@@ -28,6 +29,8 @@ exports.worksheetRelTarget = worksheetRelTarget;
28
29
  exports.commentsPath = commentsPath;
29
30
  exports.commentsPathFromName = commentsPathFromName;
30
31
  exports.vmlDrawingPath = vmlDrawingPath;
32
+ exports.vmlDrawingHFPath = vmlDrawingHFPath;
33
+ exports.vmlDrawingHFRelsPath = vmlDrawingHFRelsPath;
31
34
  exports.tablePath = tablePath;
32
35
  exports.drawingPath = drawingPath;
33
36
  exports.drawingRelsPath = drawingRelsPath;
@@ -41,6 +44,7 @@ exports.pivotCacheDefinitionRelTargetFromPivotTable = pivotCacheDefinitionRelTar
41
44
  exports.pivotCacheDefinitionRelTargetFromWorkbook = pivotCacheDefinitionRelTargetFromWorkbook;
42
45
  exports.commentsRelTargetFromWorksheet = commentsRelTargetFromWorksheet;
43
46
  exports.vmlDrawingRelTargetFromWorksheet = vmlDrawingRelTargetFromWorksheet;
47
+ exports.vmlDrawingHFRelTargetFromWorksheet = vmlDrawingHFRelTargetFromWorksheet;
44
48
  exports.drawingRelTargetFromWorksheet = drawingRelTargetFromWorksheet;
45
49
  exports.vmlDrawingRelTargetFromWorksheetName = vmlDrawingRelTargetFromWorksheetName;
46
50
  exports.commentsRelTargetFromWorksheetName = commentsRelTargetFromWorksheetName;
@@ -70,6 +74,7 @@ const mediaFilenameRegex = /^xl\/media\/([a-zA-Z0-9]+[.][a-zA-Z0-9]{3,4})$/;
70
74
  const drawingXmlRegex = /^xl\/drawings\/(drawing\d+)[.]xml$/;
71
75
  const drawingRelsXmlRegex = /^xl\/drawings\/_rels\/(drawing\d+)[.]xml[.]rels$/;
72
76
  const vmlDrawingRegex = /^xl\/drawings\/(vmlDrawing\d+)[.]vml$/;
77
+ const vmlDrawingHFRegex = /^xl\/drawings\/(vmlDrawingHF\d+)[.]vml$/;
73
78
  const commentsXmlRegex = /^xl\/comments(\d+)[.]xml$/;
74
79
  const tableXmlRegex = /^xl\/tables\/(table\d+)[.]xml$/;
75
80
  const pivotTableXmlRegex = /^xl\/pivotTables\/(pivotTable\d+)[.]xml$/;
@@ -125,6 +130,10 @@ function getVmlDrawingNameFromPath(path) {
125
130
  const match = vmlDrawingRegex.exec(path);
126
131
  return match ? match[1] : undefined;
127
132
  }
133
+ function getVmlDrawingHFNameFromPath(path) {
134
+ const match = vmlDrawingHFRegex.exec(path);
135
+ return match ? match[1] : undefined;
136
+ }
128
137
  function getCommentsIndexFromPath(path) {
129
138
  const match = commentsXmlRegex.exec(path);
130
139
  return match ? match[1] : undefined;
@@ -182,6 +191,12 @@ function commentsPathFromName(commentName) {
182
191
  function vmlDrawingPath(sheetId) {
183
192
  return `xl/drawings/vmlDrawing${sheetId}.vml`;
184
193
  }
194
+ function vmlDrawingHFPath(sheetId) {
195
+ return `xl/drawings/vmlDrawingHF${sheetId}.vml`;
196
+ }
197
+ function vmlDrawingHFRelsPath(sheetId) {
198
+ return `xl/drawings/_rels/vmlDrawingHF${sheetId}.vml.rels`;
199
+ }
185
200
  function tablePath(target) {
186
201
  return `xl/tables/${target}`;
187
202
  }
@@ -231,6 +246,10 @@ function vmlDrawingRelTargetFromWorksheet(sheetId) {
231
246
  // Target inside xl/worksheets/_rels/sheetN.xml.rels (base: xl/worksheets/)
232
247
  return `../drawings/vmlDrawing${sheetId}.vml`;
233
248
  }
249
+ function vmlDrawingHFRelTargetFromWorksheet(sheetId) {
250
+ // Target inside xl/worksheets/_rels/sheetN.xml.rels (base: xl/worksheets/)
251
+ return `../drawings/vmlDrawingHF${sheetId}.vml`;
252
+ }
234
253
  function drawingRelTargetFromWorksheet(drawingName) {
235
254
  // Target inside xl/worksheets/_rels/sheetN.xml.rels (base: xl/worksheets/)
236
255
  return `../drawings/${drawingName}.xml`;
@@ -0,0 +1,386 @@
1
+ "use strict";
2
+ /**
3
+ * Zero-dependency text-to-PNG watermark image generator.
4
+ *
5
+ * Renders text into a semi-transparent PNG suitable for use as an Excel watermark.
6
+ * Uses a built-in bitmap font for ASCII characters — no Canvas or external fonts required.
7
+ * PNG data is deflate-compressed using the archive module's built-in compressor.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const png = createTextWatermarkImage("CONFIDENTIAL", {
12
+ * fontSize: 48,
13
+ * color: { r: 128, g: 128, b: 128 },
14
+ * opacity: 40,
15
+ * rotation: -45
16
+ * });
17
+ * const imgId = workbook.addImage({ buffer: png, extension: "png" });
18
+ * worksheet.addWatermark({ imageId: imgId });
19
+ * ```
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.createTextWatermarkImage = createTextWatermarkImage;
23
+ // =============================================================================
24
+ // Public API
25
+ // =============================================================================
26
+ const deflate_fallback_1 = require("../../archive/compression/deflate-fallback.js");
27
+ /**
28
+ * Generate a PNG image containing watermark text.
29
+ *
30
+ * The image has an alpha channel so the watermark is semi-transparent.
31
+ * Works in both Node.js and browsers with zero dependencies.
32
+ */
33
+ function createTextWatermarkImage(text, options) {
34
+ const fontSize = options?.fontSize ?? 48;
35
+ const color = options?.color ?? { r: 128, g: 128, b: 128 };
36
+ const opacity = Math.max(0, Math.min(100, options?.opacity ?? 40));
37
+ const rotation = options?.rotation ?? -45;
38
+ const padding = options?.padding ?? 20;
39
+ // Scale factor: built-in font is 8px tall
40
+ const scale = Math.max(1, Math.round(fontSize / GLYPH_HEIGHT));
41
+ // Render text to unrotated bitmap
42
+ const { width: textW, height: textH, pixels: textPixels } = renderTextBitmap(text, scale);
43
+ // Add padding
44
+ const paddedW = textW + padding * 2;
45
+ const paddedH = textH + padding * 2;
46
+ const paddedPixels = new Uint8Array(paddedW * paddedH);
47
+ for (let y = 0; y < textH; y++) {
48
+ for (let x = 0; x < textW; x++) {
49
+ paddedPixels[(y + padding) * paddedW + (x + padding)] = textPixels[y * textW + x];
50
+ }
51
+ }
52
+ // Rotate
53
+ const { width: rotW, height: rotH, pixels: rotPixels } = rotateBitmap(paddedPixels, paddedW, paddedH, rotation);
54
+ // Convert to RGBA PNG
55
+ const alpha = Math.round((opacity / 100) * 255);
56
+ const rgba = new Uint8Array(rotW * rotH * 4);
57
+ for (let i = 0; i < rotW * rotH; i++) {
58
+ const a = rotPixels[i];
59
+ if (a > 0) {
60
+ rgba[i * 4] = color.r;
61
+ rgba[i * 4 + 1] = color.g;
62
+ rgba[i * 4 + 2] = color.b;
63
+ rgba[i * 4 + 3] = Math.round((a / 255) * alpha);
64
+ }
65
+ // else fully transparent (already 0)
66
+ }
67
+ return encodePng(rgba, rotW, rotH);
68
+ }
69
+ // =============================================================================
70
+ // Bitmap Font — 8px tall monospace ASCII (CP437-style, printable range 32-126)
71
+ // =============================================================================
72
+ const GLYPH_WIDTH = 6;
73
+ const GLYPH_HEIGHT = 8;
74
+ /**
75
+ * Compact glyph data: each character is 8 bytes (one byte per row, 6 bits used).
76
+ * Bit 5 = leftmost pixel, bit 0 = rightmost pixel.
77
+ */
78
+ const FONT_DATA = {
79
+ // space
80
+ 32: [0, 0, 0, 0, 0, 0, 0, 0],
81
+ // !
82
+ 33: [0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x04, 0x00],
83
+ // "
84
+ 34: [0x0a, 0x0a, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00],
85
+ // #
86
+ 35: [0x0a, 0x0a, 0x1f, 0x0a, 0x1f, 0x0a, 0x0a, 0x00],
87
+ // $
88
+ 36: [0x04, 0x0f, 0x14, 0x0e, 0x05, 0x1e, 0x04, 0x00],
89
+ // %
90
+ 37: [0x18, 0x19, 0x02, 0x04, 0x08, 0x13, 0x03, 0x00],
91
+ // &
92
+ 38: [0x0c, 0x12, 0x14, 0x08, 0x15, 0x12, 0x0d, 0x00],
93
+ // '
94
+ 39: [0x04, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00],
95
+ // (
96
+ 40: [0x02, 0x04, 0x08, 0x08, 0x08, 0x04, 0x02, 0x00],
97
+ // )
98
+ 41: [0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08, 0x00],
99
+ // *
100
+ 42: [0x00, 0x04, 0x15, 0x0e, 0x15, 0x04, 0x00, 0x00],
101
+ // +
102
+ 43: [0x00, 0x04, 0x04, 0x1f, 0x04, 0x04, 0x00, 0x00],
103
+ // ,
104
+ 44: [0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x08],
105
+ // -
106
+ 45: [0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00],
107
+ // .
108
+ 46: [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00],
109
+ // /
110
+ 47: [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x00, 0x00],
111
+ // 0-9
112
+ 48: [0x0e, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0e, 0x00],
113
+ 49: [0x04, 0x0c, 0x04, 0x04, 0x04, 0x04, 0x0e, 0x00],
114
+ 50: [0x0e, 0x11, 0x01, 0x02, 0x04, 0x08, 0x1f, 0x00],
115
+ 51: [0x1f, 0x02, 0x04, 0x02, 0x01, 0x11, 0x0e, 0x00],
116
+ 52: [0x02, 0x06, 0x0a, 0x12, 0x1f, 0x02, 0x02, 0x00],
117
+ 53: [0x1f, 0x10, 0x1e, 0x01, 0x01, 0x11, 0x0e, 0x00],
118
+ 54: [0x06, 0x08, 0x10, 0x1e, 0x11, 0x11, 0x0e, 0x00],
119
+ 55: [0x1f, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08, 0x00],
120
+ 56: [0x0e, 0x11, 0x11, 0x0e, 0x11, 0x11, 0x0e, 0x00],
121
+ 57: [0x0e, 0x11, 0x11, 0x0f, 0x01, 0x02, 0x0c, 0x00],
122
+ // :
123
+ 58: [0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00],
124
+ // ;
125
+ 59: [0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x04, 0x08],
126
+ // <
127
+ 60: [0x02, 0x04, 0x08, 0x10, 0x08, 0x04, 0x02, 0x00],
128
+ // =
129
+ 61: [0x00, 0x00, 0x1f, 0x00, 0x1f, 0x00, 0x00, 0x00],
130
+ // >
131
+ 62: [0x08, 0x04, 0x02, 0x01, 0x02, 0x04, 0x08, 0x00],
132
+ // ?
133
+ 63: [0x0e, 0x11, 0x01, 0x02, 0x04, 0x00, 0x04, 0x00],
134
+ // @
135
+ 64: [0x0e, 0x11, 0x17, 0x15, 0x17, 0x10, 0x0e, 0x00],
136
+ // A-Z
137
+ 65: [0x0e, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00],
138
+ 66: [0x1e, 0x11, 0x11, 0x1e, 0x11, 0x11, 0x1e, 0x00],
139
+ 67: [0x0e, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0e, 0x00],
140
+ 68: [0x1c, 0x12, 0x11, 0x11, 0x11, 0x12, 0x1c, 0x00],
141
+ 69: [0x1f, 0x10, 0x10, 0x1e, 0x10, 0x10, 0x1f, 0x00],
142
+ 70: [0x1f, 0x10, 0x10, 0x1e, 0x10, 0x10, 0x10, 0x00],
143
+ 71: [0x0e, 0x11, 0x10, 0x17, 0x11, 0x11, 0x0f, 0x00],
144
+ 72: [0x11, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00],
145
+ 73: [0x0e, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0e, 0x00],
146
+ 74: [0x07, 0x02, 0x02, 0x02, 0x02, 0x12, 0x0c, 0x00],
147
+ 75: [0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11, 0x00],
148
+ 76: [0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1f, 0x00],
149
+ 77: [0x11, 0x1b, 0x15, 0x15, 0x11, 0x11, 0x11, 0x00],
150
+ 78: [0x11, 0x19, 0x15, 0x13, 0x11, 0x11, 0x11, 0x00],
151
+ 79: [0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00],
152
+ 80: [0x1e, 0x11, 0x11, 0x1e, 0x10, 0x10, 0x10, 0x00],
153
+ 81: [0x0e, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0d, 0x00],
154
+ 82: [0x1e, 0x11, 0x11, 0x1e, 0x14, 0x12, 0x11, 0x00],
155
+ 83: [0x0f, 0x10, 0x10, 0x0e, 0x01, 0x01, 0x1e, 0x00],
156
+ 84: [0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00],
157
+ 85: [0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00],
158
+ 86: [0x11, 0x11, 0x11, 0x11, 0x11, 0x0a, 0x04, 0x00],
159
+ 87: [0x11, 0x11, 0x11, 0x15, 0x15, 0x1b, 0x11, 0x00],
160
+ 88: [0x11, 0x11, 0x0a, 0x04, 0x0a, 0x11, 0x11, 0x00],
161
+ 89: [0x11, 0x11, 0x0a, 0x04, 0x04, 0x04, 0x04, 0x00],
162
+ 90: [0x1f, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1f, 0x00],
163
+ // [ \ ]
164
+ 91: [0x0e, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0e, 0x00],
165
+ 92: [0x00, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x00],
166
+ 93: [0x0e, 0x02, 0x02, 0x02, 0x02, 0x02, 0x0e, 0x00],
167
+ // ^ _ `
168
+ 94: [0x04, 0x0a, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00],
169
+ 95: [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00],
170
+ 96: [0x08, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00],
171
+ // a-z
172
+ 97: [0x00, 0x00, 0x0e, 0x01, 0x0f, 0x11, 0x0f, 0x00],
173
+ 98: [0x10, 0x10, 0x16, 0x19, 0x11, 0x11, 0x1e, 0x00],
174
+ 99: [0x00, 0x00, 0x0e, 0x10, 0x10, 0x11, 0x0e, 0x00],
175
+ 100: [0x01, 0x01, 0x0d, 0x13, 0x11, 0x11, 0x0f, 0x00],
176
+ 101: [0x00, 0x00, 0x0e, 0x11, 0x1f, 0x10, 0x0e, 0x00],
177
+ 102: [0x06, 0x09, 0x08, 0x1c, 0x08, 0x08, 0x08, 0x00],
178
+ 103: [0x00, 0x00, 0x0f, 0x11, 0x0f, 0x01, 0x0e, 0x00],
179
+ 104: [0x10, 0x10, 0x16, 0x19, 0x11, 0x11, 0x11, 0x00],
180
+ 105: [0x04, 0x00, 0x0c, 0x04, 0x04, 0x04, 0x0e, 0x00],
181
+ 106: [0x02, 0x00, 0x06, 0x02, 0x02, 0x12, 0x0c, 0x00],
182
+ 107: [0x10, 0x10, 0x12, 0x14, 0x18, 0x14, 0x12, 0x00],
183
+ 108: [0x0c, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0e, 0x00],
184
+ 109: [0x00, 0x00, 0x1a, 0x15, 0x15, 0x11, 0x11, 0x00],
185
+ 110: [0x00, 0x00, 0x16, 0x19, 0x11, 0x11, 0x11, 0x00],
186
+ 111: [0x00, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x0e, 0x00],
187
+ 112: [0x00, 0x00, 0x1e, 0x11, 0x1e, 0x10, 0x10, 0x00],
188
+ 113: [0x00, 0x00, 0x0d, 0x13, 0x0f, 0x01, 0x01, 0x00],
189
+ 114: [0x00, 0x00, 0x16, 0x19, 0x10, 0x10, 0x10, 0x00],
190
+ 115: [0x00, 0x00, 0x0e, 0x10, 0x0e, 0x01, 0x1e, 0x00],
191
+ 116: [0x08, 0x08, 0x1c, 0x08, 0x08, 0x09, 0x06, 0x00],
192
+ 117: [0x00, 0x00, 0x11, 0x11, 0x11, 0x13, 0x0d, 0x00],
193
+ 118: [0x00, 0x00, 0x11, 0x11, 0x11, 0x0a, 0x04, 0x00],
194
+ 119: [0x00, 0x00, 0x11, 0x11, 0x15, 0x15, 0x0a, 0x00],
195
+ 120: [0x00, 0x00, 0x11, 0x0a, 0x04, 0x0a, 0x11, 0x00],
196
+ 121: [0x00, 0x00, 0x11, 0x11, 0x0f, 0x01, 0x0e, 0x00],
197
+ 122: [0x00, 0x00, 0x1f, 0x02, 0x04, 0x08, 0x1f, 0x00],
198
+ // { | } ~
199
+ 123: [0x02, 0x04, 0x04, 0x08, 0x04, 0x04, 0x02, 0x00],
200
+ 124: [0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00],
201
+ 125: [0x08, 0x04, 0x04, 0x02, 0x04, 0x04, 0x08, 0x00],
202
+ 126: [0x00, 0x00, 0x08, 0x15, 0x02, 0x00, 0x00, 0x00]
203
+ };
204
+ // =============================================================================
205
+ // Bitmap Rendering
206
+ // =============================================================================
207
+ /** Render text string to a grayscale bitmap (0 = transparent, 255 = opaque). */
208
+ function renderTextBitmap(text, scale) {
209
+ const charW = GLYPH_WIDTH * scale;
210
+ const charH = GLYPH_HEIGHT * scale;
211
+ const width = text.length * charW;
212
+ const height = charH;
213
+ const pixels = new Uint8Array(width * height);
214
+ for (let ci = 0; ci < text.length; ci++) {
215
+ const code = text.charCodeAt(ci);
216
+ const glyph = FONT_DATA[code] ?? FONT_DATA[63]; // fallback to '?'
217
+ const xOff = ci * charW;
218
+ for (let row = 0; row < GLYPH_HEIGHT; row++) {
219
+ const bits = glyph[row];
220
+ for (let col = 0; col < GLYPH_WIDTH; col++) {
221
+ if (bits & (1 << (GLYPH_WIDTH - 1 - col))) {
222
+ // Fill scaled pixel block
223
+ for (let sy = 0; sy < scale; sy++) {
224
+ for (let sx = 0; sx < scale; sx++) {
225
+ const px = xOff + col * scale + sx;
226
+ const py = row * scale + sy;
227
+ if (px < width && py < height) {
228
+ pixels[py * width + px] = 255;
229
+ }
230
+ }
231
+ }
232
+ }
233
+ }
234
+ }
235
+ }
236
+ return { width, height, pixels };
237
+ }
238
+ /** Rotate a grayscale bitmap by the given angle in degrees. */
239
+ function rotateBitmap(pixels, srcW, srcH, angleDeg) {
240
+ if (angleDeg === 0) {
241
+ return { width: srcW, height: srcH, pixels };
242
+ }
243
+ const rad = (angleDeg * Math.PI) / 180;
244
+ const cos = Math.cos(rad);
245
+ const sin = Math.sin(rad);
246
+ // Compute bounding box of rotated rectangle
247
+ const corners = [
248
+ { x: 0, y: 0 },
249
+ { x: srcW, y: 0 },
250
+ { x: srcW, y: srcH },
251
+ { x: 0, y: srcH }
252
+ ];
253
+ let minX = Infinity;
254
+ let minY = Infinity;
255
+ let maxX = -Infinity;
256
+ let maxY = -Infinity;
257
+ for (const c of corners) {
258
+ const rx = c.x * cos - c.y * sin;
259
+ const ry = c.x * sin + c.y * cos;
260
+ minX = Math.min(minX, rx);
261
+ minY = Math.min(minY, ry);
262
+ maxX = Math.max(maxX, rx);
263
+ maxY = Math.max(maxY, ry);
264
+ }
265
+ const dstW = Math.ceil(maxX - minX);
266
+ const dstH = Math.ceil(maxY - minY);
267
+ const dst = new Uint8Array(dstW * dstH);
268
+ // Inverse rotation: for each dst pixel, find the source pixel
269
+ const invCos = cos; // cos(-θ) = cos(θ)
270
+ const invSin = -sin; // sin(-θ) = -sin(θ)
271
+ for (let dy = 0; dy < dstH; dy++) {
272
+ for (let dx = 0; dx < dstW; dx++) {
273
+ // Map dst to world, then inverse-rotate to source
274
+ const wx = dx + minX;
275
+ const wy = dy + minY;
276
+ const sx = Math.round(wx * invCos - wy * invSin);
277
+ const sy = Math.round(wx * invSin + wy * invCos);
278
+ if (sx >= 0 && sx < srcW && sy >= 0 && sy < srcH) {
279
+ dst[dy * dstW + dx] = pixels[sy * srcW + sx];
280
+ }
281
+ }
282
+ }
283
+ return { width: dstW, height: dstH, pixels: dst };
284
+ }
285
+ // =============================================================================
286
+ // PNG Encoder (RGBA, deflate-compressed, with alpha)
287
+ // =============================================================================
288
+ /** Encode RGBA pixel data to a PNG file. */
289
+ function encodePng(rgba, width, height) {
290
+ // Build IDAT data: filter byte (0 = None) + raw RGBA for each row
291
+ const rawRowSize = 1 + width * 4; // filter byte + pixels
292
+ const rawData = new Uint8Array(rawRowSize * height);
293
+ for (let y = 0; y < height; y++) {
294
+ rawData[y * rawRowSize] = 0; // filter: None
295
+ rawData.set(rgba.subarray(y * width * 4, (y + 1) * width * 4), y * rawRowSize + 1);
296
+ }
297
+ // Wrap in zlib stream with deflate compression
298
+ const deflated = zlibCompress(rawData);
299
+ // PNG signature
300
+ const sig = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);
301
+ // IHDR chunk
302
+ const ihdr = new Uint8Array(13);
303
+ writeU32BE(ihdr, 0, width);
304
+ writeU32BE(ihdr, 4, height);
305
+ ihdr[8] = 8; // bit depth
306
+ ihdr[9] = 6; // color type: RGBA
307
+ ihdr[10] = 0; // compression
308
+ ihdr[11] = 0; // filter
309
+ ihdr[12] = 0; // interlace
310
+ const ihdrChunk = pngChunk(0x49484452, ihdr);
311
+ // IDAT chunk
312
+ const idatChunk = pngChunk(0x49444154, deflated);
313
+ // IEND chunk
314
+ const iendChunk = pngChunk(0x49454e44, new Uint8Array(0));
315
+ // Concatenate
316
+ const result = new Uint8Array(sig.length + ihdrChunk.length + idatChunk.length + iendChunk.length);
317
+ let offset = 0;
318
+ result.set(sig, offset);
319
+ offset += sig.length;
320
+ result.set(ihdrChunk, offset);
321
+ offset += ihdrChunk.length;
322
+ result.set(idatChunk, offset);
323
+ offset += idatChunk.length;
324
+ result.set(iendChunk, offset);
325
+ return result;
326
+ }
327
+ /** Build a PNG chunk: length(4) + type(4) + data + crc32(4). */
328
+ function pngChunk(type, data) {
329
+ const chunk = new Uint8Array(12 + data.length);
330
+ writeU32BE(chunk, 0, data.length);
331
+ writeU32BE(chunk, 4, type);
332
+ chunk.set(data, 8);
333
+ // CRC32 over type + data
334
+ const crc = crc32(chunk.subarray(4, 8 + data.length));
335
+ writeU32BE(chunk, 8 + data.length, crc);
336
+ return chunk;
337
+ }
338
+ /** Write a 32-bit big-endian unsigned int. */
339
+ function writeU32BE(buf, offset, value) {
340
+ buf[offset] = (value >>> 24) & 0xff;
341
+ buf[offset + 1] = (value >>> 16) & 0xff;
342
+ buf[offset + 2] = (value >>> 8) & 0xff;
343
+ buf[offset + 3] = value & 0xff;
344
+ }
345
+ /** Wrap raw data in a zlib stream with deflate compression. */
346
+ function zlibCompress(data) {
347
+ // Zlib header: CMF=0x78, FLG=0x01 (deflate, no dict, check bits)
348
+ const deflated = (0, deflate_fallback_1.deflateRawCompressed)(data, 6);
349
+ const adler = adler32(data);
350
+ const result = new Uint8Array(2 + deflated.length + 4);
351
+ result[0] = 0x78;
352
+ result[1] = 0x01;
353
+ result.set(deflated, 2);
354
+ writeU32BE(result, 2 + deflated.length, adler);
355
+ return result;
356
+ }
357
+ /** Compute Adler-32 checksum. */
358
+ function adler32(data) {
359
+ let a = 1;
360
+ let b = 0;
361
+ for (let i = 0; i < data.length; i++) {
362
+ a = (a + data[i]) % 65521;
363
+ b = (b + a) % 65521;
364
+ }
365
+ return (b << 16) | a;
366
+ }
367
+ /** CRC32 lookup table. */
368
+ const CRC_TABLE = /* @__PURE__ */ (() => {
369
+ const table = new Uint32Array(256);
370
+ for (let n = 0; n < 256; n++) {
371
+ let c = n;
372
+ for (let k = 0; k < 8; k++) {
373
+ c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
374
+ }
375
+ table[n] = c;
376
+ }
377
+ return table;
378
+ })();
379
+ /** Compute CRC32 checksum. */
380
+ function crc32(data) {
381
+ let crc = 0xffffffff;
382
+ for (let i = 0; i < data.length; i++) {
383
+ crc = CRC_TABLE[(crc ^ data[i]) & 0xff] ^ (crc >>> 8);
384
+ }
385
+ return (crc ^ 0xffffffff) >>> 0;
386
+ }
@@ -107,6 +107,8 @@ class Worksheet {
107
107
  this.conditionalFormattings = [];
108
108
  // for form controls (legacy checkboxes, etc.)
109
109
  this.formControls = [];
110
+ // watermark configuration
111
+ this._watermark = null;
110
112
  }
111
113
  get name() {
112
114
  return this._name;
@@ -981,6 +983,79 @@ class Worksheet {
981
983
  return image && image.imageId;
982
984
  }
983
985
  // =========================================================================
986
+ // Watermark
987
+ /**
988
+ * Add a watermark to the worksheet using an image from `workbook.addImage()`.
989
+ *
990
+ * The watermark can be placed in one of two modes:
991
+ *
992
+ * - **overlay** (default): Places the watermark image as a drawing on top of cells.
993
+ * Visible on screen AND when printed. Supports transparency via DrawingML `alphaModFix`.
994
+ *
995
+ * - **header**: Places the watermark image in the page header using VML.
996
+ * Visible in Page Layout view and when printed. Renders behind cell content.
997
+ * Transparency must be baked into the image (PNG with alpha channel).
998
+ *
999
+ * @param options - Watermark configuration
1000
+ *
1001
+ * @example Overlay watermark with transparency:
1002
+ * ```typescript
1003
+ * const imgId = workbook.addImage({ buffer: pngData, extension: "png" });
1004
+ * worksheet.addWatermark({ imageId: imgId, opacity: 0.15 });
1005
+ * ```
1006
+ *
1007
+ * @example Header watermark (behind content):
1008
+ * ```typescript
1009
+ * const imgId = workbook.addImage({ buffer: pngData, extension: "png" });
1010
+ * worksheet.addWatermark({ imageId: imgId, mode: "header" });
1011
+ * ```
1012
+ */
1013
+ addWatermark(options) {
1014
+ // Remove any existing watermark media entries first
1015
+ this._media = this._media.filter(m => m.type !== "watermark" && m.type !== "headerImage");
1016
+ this._watermark = {
1017
+ imageId: String(options.imageId),
1018
+ mode: options.mode ?? "overlay",
1019
+ opacity: options.opacity,
1020
+ headerWidth: options.headerWidth,
1021
+ headerHeight: options.headerHeight,
1022
+ applyTo: options.applyTo
1023
+ };
1024
+ if (this._watermark.mode === "overlay") {
1025
+ // Add as a special "watermark" media entry for the drawing pipeline
1026
+ const model = {
1027
+ type: "watermark",
1028
+ imageId: String(options.imageId),
1029
+ opacity: options.opacity
1030
+ };
1031
+ this._media.push(new image_1.Image(this, model));
1032
+ }
1033
+ else {
1034
+ // Header mode: add as a "headerImage" media entry for the VML pipeline
1035
+ const model = {
1036
+ type: "headerImage",
1037
+ imageId: String(options.imageId),
1038
+ headerWidth: options.headerWidth,
1039
+ headerHeight: options.headerHeight,
1040
+ applyTo: options.applyTo
1041
+ };
1042
+ this._media.push(new image_1.Image(this, model));
1043
+ }
1044
+ }
1045
+ /**
1046
+ * Get the current watermark configuration, or null if none is set.
1047
+ */
1048
+ getWatermark() {
1049
+ return this._watermark;
1050
+ }
1051
+ /**
1052
+ * Remove the watermark from the worksheet.
1053
+ */
1054
+ removeWatermark() {
1055
+ this._watermark = null;
1056
+ this._media = this._media.filter(m => m.type !== "watermark" && m.type !== "headerImage");
1057
+ }
1058
+ // =========================================================================
984
1059
  // Form Controls (Legacy Checkboxes)
985
1060
  /**
986
1061
  * Add a form control checkbox to the worksheet.
@@ -1296,6 +1371,7 @@ class Worksheet {
1296
1371
  pivotTables: this.pivotTables,
1297
1372
  conditionalFormattings: this.conditionalFormattings,
1298
1373
  formControls: this.formControls.map(fc => fc.model),
1374
+ watermark: this._watermark,
1299
1375
  drawing: this._drawing
1300
1376
  };
1301
1377
  // =================================================
@@ -1351,6 +1427,29 @@ class Worksheet {
1351
1427
  this.views = value.views;
1352
1428
  this.autoFilter = value.autoFilter;
1353
1429
  this._media = value.media.map(medium => new image_1.Image(this, medium));
1430
+ // Restore watermark state from media entries
1431
+ this._watermark = value.watermark ?? null;
1432
+ if (!this._watermark) {
1433
+ for (const medium of this._media) {
1434
+ if (medium.type === "watermark") {
1435
+ this._watermark = {
1436
+ imageId: medium.imageId ?? "",
1437
+ mode: "overlay",
1438
+ opacity: medium.opacity
1439
+ };
1440
+ break;
1441
+ }
1442
+ else if (medium.type === "headerImage") {
1443
+ this._watermark = {
1444
+ imageId: medium.imageId ?? "",
1445
+ mode: "header",
1446
+ headerWidth: medium.headerWidth,
1447
+ headerHeight: medium.headerHeight
1448
+ };
1449
+ break;
1450
+ }
1451
+ }
1452
+ }
1354
1453
  this.sheetProtection = value.sheetProtection;
1355
1454
  this.tables = value.tables.reduce((tables, table) => {
1356
1455
  const t = new table_1.Table(this, table);
@@ -107,10 +107,11 @@ class ContentTypesXform extends base_xform_1.BaseXform {
107
107
  });
108
108
  });
109
109
  }
110
- // VML extension is needed for comments or form controls
110
+ // VML extension is needed for comments, form controls, or header watermarks
111
111
  const hasComments = model.commentRefs && model.commentRefs.length > 0;
112
112
  const hasFormControls = model.formControlRefs && model.formControlRefs.length > 0;
113
- if (hasComments || hasFormControls) {
113
+ const hasHeaderWatermark = model.hasHeaderWatermark === true;
114
+ if (hasComments || hasFormControls || hasHeaderWatermark) {
114
115
  xmlStream.leafNode("Default", {
115
116
  Extension: "vml",
116
117
  ContentType: "application/vnd.openxmlformats-officedocument.vmlDrawing"
@@ -38,7 +38,12 @@ class BaseCellAnchorXform extends base_xform_1.BaseXform {
38
38
  if (match) {
39
39
  const name = match[1];
40
40
  const mediaId = options.mediaIndex[name];
41
- return options.media[mediaId];
41
+ const medium = options.media[mediaId];
42
+ // Preserve alphaModFix (transparency) from the picture model if present
43
+ if (medium && model.alphaModFix !== undefined) {
44
+ return { ...medium, alphaModFix: model.alphaModFix };
45
+ }
46
+ return medium;
42
47
  }
43
48
  }
44
49
  return undefined;
@@ -16,7 +16,6 @@ class BlipFillXform extends base_xform_1.BaseXform {
16
16
  render(xmlStream, model) {
17
17
  xmlStream.openNode(this.tag);
18
18
  this.map["a:blip"].render(xmlStream, model);
19
- // TODO: options for this + parsing
20
19
  xmlStream.openNode("a:stretch");
21
20
  xmlStream.leafNode("a:fillRect");
22
21
  xmlStream.closeNode();
@@ -11,12 +11,23 @@ class BlipXform extends base_xform_1.BaseXform {
11
11
  return "a:blip";
12
12
  }
13
13
  render(xmlStream, model) {
14
- xmlStream.leafNode(this.tag, {
15
- "xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
16
- "r:embed": model.rId,
17
- cstate: "print"
18
- });
19
- // TODO: handle children (e.g. a:extLst=>a:ext=>a14:useLocalDpi
14
+ if (model.alphaModFix !== undefined && model.alphaModFix < 100000) {
15
+ // Render as open/close node with a:alphaModFix child
16
+ xmlStream.openNode(this.tag, {
17
+ "xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
18
+ "r:embed": model.rId,
19
+ cstate: "print"
20
+ });
21
+ xmlStream.leafNode("a:alphaModFix", { amt: String(model.alphaModFix) });
22
+ xmlStream.closeNode();
23
+ }
24
+ else {
25
+ xmlStream.leafNode(this.tag, {
26
+ "xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
27
+ "r:embed": model.rId,
28
+ cstate: "print"
29
+ });
30
+ }
20
31
  }
21
32
  parseOpen(node) {
22
33
  switch (node.name) {
@@ -25,6 +36,11 @@ class BlipXform extends base_xform_1.BaseXform {
25
36
  rId: node.attributes["r:embed"]
26
37
  };
27
38
  return true;
39
+ case "a:alphaModFix":
40
+ if (node.attributes.amt) {
41
+ this.model.alphaModFix = parseInt(node.attributes.amt, 10);
42
+ }
43
+ return true;
28
44
  default:
29
45
  return true;
30
46
  }
@@ -24,7 +24,11 @@ class PicXform extends base_xform_1.BaseXform {
24
24
  render(xmlStream, model) {
25
25
  xmlStream.openNode(this.tag);
26
26
  this.map["xdr:nvPicPr"].render(xmlStream, model);
27
- this.map["xdr:blipFill"].render(xmlStream, model);
27
+ // Pass alphaModFix through to blipFill → blip
28
+ this.map["xdr:blipFill"].render(xmlStream, {
29
+ rId: model.rId,
30
+ alphaModFix: model.alphaModFix
31
+ });
28
32
  this.map["xdr:spPr"].render(xmlStream, model);
29
33
  xmlStream.closeNode();
30
34
  }