@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
@@ -11,6 +11,7 @@
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.exportPdf = exportPdf;
13
13
  const pdf_writer_1 = require("../core/pdf-writer");
14
+ const pdf_stream_1 = require("../core/pdf-stream");
14
15
  const pdf_object_1 = require("../core/pdf-object");
15
16
  const font_manager_1 = require("../font/font-manager");
16
17
  const ttf_parser_1 = require("../font/ttf-parser");
@@ -86,8 +87,21 @@ async function finishExport(ctx, workbook, options) {
86
87
  ensureAtLeastOnePage(allPages, documentOptions, sheets);
87
88
  fixPageNumbers(allPages);
88
89
  trackFontsForHeaders(allPages, fontManager);
90
+ // Track watermark fonts
91
+ const watermark = documentOptions.watermark;
92
+ if (watermark && watermark.type === "text") {
93
+ const wmFontFamily = watermark.fontFamily ?? "Helvetica";
94
+ const wmBold = watermark.bold ?? false;
95
+ const wmItalic = watermark.italic ?? false;
96
+ if (fontManager.hasEmbeddedFont()) {
97
+ fontManager.trackText(watermark.text);
98
+ }
99
+ else {
100
+ fontManager.ensureFont((0, font_manager_1.resolvePdfFontName)(wmFontFamily, wmBold, wmItalic));
101
+ }
102
+ }
89
103
  const fontObjectMap = fontManager.writeFontResources(writer);
90
- const { pageObjNums, sheetFirstPage, pagesTreeObjNum } = await renderAllPages(allPages, fontManager, writer, fontObjectMap);
104
+ const { pageObjNums, sheetFirstPage, pagesTreeObjNum } = await renderAllPages(allPages, fontManager, writer, fontObjectMap, watermark);
91
105
  return buildFinalPdf(writer, pageObjNums, pagesTreeObjNum, sheetFirstPage, documentOptions, workbook, options);
92
106
  }
93
107
  function ensureAtLeastOnePage(allPages, documentOptions, sheets) {
@@ -136,20 +150,20 @@ function trackFontsForHeaders(allPages, fontManager) {
136
150
  }
137
151
  }
138
152
  }
139
- async function renderAllPages(allPages, fontManager, writer, fontObjectMap) {
153
+ async function renderAllPages(allPages, fontManager, writer, fontObjectMap, watermark) {
140
154
  const pageObjNums = [];
141
155
  const pagesTreeObjNum = writer.allocObject();
142
156
  const sheetFirstPage = new Map();
143
157
  const totalPages = allPages.length;
144
158
  for (let i = 0; i < allPages.length; i++) {
145
- renderSinglePage(allPages[i], fontManager, writer, fontObjectMap, totalPages, pageObjNums, pagesTreeObjNum, sheetFirstPage);
159
+ renderSinglePage(allPages[i], fontManager, writer, fontObjectMap, totalPages, pageObjNums, pagesTreeObjNum, sheetFirstPage, watermark);
146
160
  if (i < allPages.length - 1) {
147
161
  await (0, utils_base_1.yieldToEventLoop)();
148
162
  }
149
163
  }
150
164
  return { pageObjNums, sheetFirstPage, pagesTreeObjNum };
151
165
  }
152
- function renderSinglePage(page, fontManager, writer, fontObjectMap, totalPages, pageObjNums, pagesTreeObjNum, sheetFirstPage) {
166
+ function renderSinglePage(page, fontManager, writer, fontObjectMap, totalPages, pageObjNums, pagesTreeObjNum, sheetFirstPage, watermark) {
153
167
  try {
154
168
  const { stream: contentStream, alphaValues } = (0, page_renderer_1.renderPage)(page, page.options, fontManager, totalPages);
155
169
  // Handle images: create XObject Image entries and draw them
@@ -163,10 +177,46 @@ function renderSinglePage(page, fontManager, writer, fontObjectMap, totalPages,
163
177
  contentStream.drawImage(imgName, img.rect.x, img.rect.y, img.rect.width, img.rect.height);
164
178
  }
165
179
  }
166
- // Add content stream object
180
+ // --- Render watermark into a separate content stream ---
181
+ // PDF supports Contents as an array of stream references. The watermark stream
182
+ // is placed BEFORE the main content stream so it renders behind everything.
183
+ let watermarkContentObjNum;
184
+ const shouldApplyWatermark = watermark && isWatermarkApplicable(watermark, page);
185
+ if (shouldApplyWatermark) {
186
+ const wmContentStream = new pdf_stream_1.PdfContentStream();
187
+ const wmResult = (0, page_renderer_1.renderWatermark)(wmContentStream, page, watermark, fontManager);
188
+ // Register watermark alpha values in the shared set
189
+ for (const alpha of wmResult.alphaValues) {
190
+ alphaValues.add(alpha);
191
+ }
192
+ // Register watermark image XObjects
193
+ for (const wmImg of wmResult.imageXObjects) {
194
+ const imgObjNum = writeImageXObject(writer, wmImg.data, wmImg.format);
195
+ imageXObjects.set(wmImg.name, imgObjNum);
196
+ }
197
+ // Write watermark content stream object
198
+ watermarkContentObjNum = writer.allocObject();
199
+ writer.addStreamObject(watermarkContentObjNum, new pdf_object_1.PdfDict(), wmContentStream);
200
+ }
201
+ // Add main content stream object
167
202
  const contentObjNum = writer.allocObject();
168
- const contentDict = new pdf_object_1.PdfDict();
169
- writer.addStreamObject(contentObjNum, contentDict, contentStream);
203
+ writer.addStreamObject(contentObjNum, new pdf_object_1.PdfDict(), contentStream);
204
+ // Build Contents reference — array if watermark exists, single ref otherwise.
205
+ // placement "under" (default): watermark stream first, then content
206
+ // placement "over": content first, then watermark stream on top
207
+ let contentsRef;
208
+ if (watermarkContentObjNum) {
209
+ const placement = watermark?.placement ?? "under";
210
+ if (placement === "over") {
211
+ contentsRef = `[${(0, pdf_object_1.pdfRef)(contentObjNum)} ${(0, pdf_object_1.pdfRef)(watermarkContentObjNum)}]`;
212
+ }
213
+ else {
214
+ contentsRef = `[${(0, pdf_object_1.pdfRef)(watermarkContentObjNum)} ${(0, pdf_object_1.pdfRef)(contentObjNum)}]`;
215
+ }
216
+ }
217
+ else {
218
+ contentsRef = (0, pdf_object_1.pdfRef)(contentObjNum);
219
+ }
170
220
  // Add resources dictionary object
171
221
  const resourcesObjNum = writer.allocObject();
172
222
  const fontDictStr = fontManager.buildFontDictString(fontObjectMap);
@@ -215,7 +265,7 @@ function renderSinglePage(page, fontManager, writer, fontObjectMap, totalPages,
215
265
  parentRef: pagesTreeObjNum,
216
266
  width: page.width,
217
267
  height: page.height,
218
- contentsRef: contentObjNum,
268
+ contentsRef: contentsRef,
219
269
  resourcesRef: resourcesObjNum,
220
270
  annotRefs: annotRefs.length > 0 ? annotRefs : undefined
221
271
  });
@@ -340,7 +390,8 @@ function resolveOptions(options, sheet) {
340
390
  title: options?.title ?? "",
341
391
  author: options?.author ?? "",
342
392
  subject: options?.subject ?? "",
343
- creator: options?.creator ?? "excelts"
393
+ creator: options?.creator ?? "excelts",
394
+ watermark: options?.watermark
344
395
  };
345
396
  }
346
397
  /** Map PaperSize enum values to PDF page sizes. */
@@ -431,6 +482,28 @@ function buildOutlines(writer, sheetFirstPage, pageObjNums) {
431
482
  return outlinesObjNum;
432
483
  }
433
484
  // =============================================================================
485
+ // Watermark Filtering
486
+ // =============================================================================
487
+ /**
488
+ * Check if a watermark should be applied to a specific page based on
489
+ * optional page number and sheet name filters.
490
+ */
491
+ function isWatermarkApplicable(watermark, page) {
492
+ if (watermark.pages && watermark.pages.length > 0) {
493
+ if (!watermark.pages.includes(page.pageNumber)) {
494
+ return false;
495
+ }
496
+ }
497
+ if (watermark.sheets && watermark.sheets.length > 0) {
498
+ // Case-insensitive sheet name matching, consistent with the rest of the API
499
+ const sheetLower = page.sheetName.toLowerCase();
500
+ if (!watermark.sheets.some(s => s.toLowerCase() === sheetLower)) {
501
+ return false;
502
+ }
503
+ }
504
+ return true;
505
+ }
506
+ // =============================================================================
434
507
  // Image XObject
435
508
  // =============================================================================
436
509
  /**
@@ -447,7 +520,7 @@ function writeImageXObject(writer, data, format) {
447
520
  */
448
521
  function writeJpegImageXObject(writer, data) {
449
522
  const objNum = writer.allocObject();
450
- const dims = getJpegDimensions(data);
523
+ const dims = (0, page_renderer_1.parseImageDimensions)(data, "jpeg");
451
524
  const dict = new pdf_object_1.PdfDict()
452
525
  .set("Type", "/XObject")
453
526
  .set("Subtype", "/Image")
@@ -489,39 +562,3 @@ function writePngImageXObject(writer, data) {
489
562
  writer.addStreamObject(objNum, dict, png.pixels);
490
563
  return objNum;
491
564
  }
492
- /**
493
- * Extract width and height from a JPEG file header.
494
- * Scans for SOF markers (SOF0-SOF3, SOF5-SOF7, SOF9-SOF11, SOF13-SOF15)
495
- * which all share the same frame header layout.
496
- * Handles 0xFF padding bytes per JPEG spec.
497
- */
498
- function getJpegDimensions(data) {
499
- let offset = 2; // skip SOI marker (0xFFD8)
500
- while (offset < data.length - 1) {
501
- // Skip any 0xFF fill bytes
502
- while (offset < data.length && data[offset] === 0xff && data[offset + 1] === 0xff) {
503
- offset++;
504
- }
505
- if (offset >= data.length - 1 || data[offset] !== 0xff) {
506
- break;
507
- }
508
- const marker = data[offset + 1];
509
- // SOF markers: C0-C3, C5-C7, C9-CB, CD-CF (excluding C4=DHT, C8=JPG, CC=DAC)
510
- const isSof = marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc;
511
- if (isSof) {
512
- if (offset + 8 < data.length) {
513
- const height = (data[offset + 5] << 8) | data[offset + 6];
514
- const width = (data[offset + 7] << 8) | data[offset + 8];
515
- return { width, height };
516
- }
517
- break;
518
- }
519
- // Skip segment: 2 byte marker + segment length (includes the 2 length bytes)
520
- if (offset + 3 >= data.length) {
521
- break;
522
- }
523
- const segLen = (data[offset + 2] << 8) | data[offset + 3];
524
- offset += 2 + segLen;
525
- }
526
- return { width: 1, height: 1 };
527
- }
@@ -25,6 +25,8 @@ export * from "./modules/excel/enums.js";
25
25
  // =============================================================================
26
26
  // Export all type definitions from types.ts
27
27
  export * from "./modules/excel/types.js";
28
+ // Watermark image generator utility
29
+ export { createTextWatermarkImage } from "./modules/excel/utils/watermark-image.js";
28
30
  // =============================================================================
29
31
  // Streaming Writer (Browser-compatible)
30
32
  // Uses cross-platform base implementation without Node.js fs
package/dist/esm/index.js CHANGED
@@ -29,6 +29,8 @@ export * from "./modules/excel/enums.js";
29
29
  // =============================================================================
30
30
  // Export all type definitions from types.ts
31
31
  export * from "./modules/excel/types.js";
32
+ // Watermark image generator utility
33
+ export { createTextWatermarkImage } from "./modules/excel/utils/watermark-image.js";
32
34
  export { CsvParserStream, CsvFormatterStream, createCsvParserStream, createCsvFormatterStream } from "./modules/csv/stream/index.js";
33
35
  // =============================================================================
34
36
  // Additional Classes & Types
@@ -15,6 +15,20 @@ class Image {
15
15
  type: this.type,
16
16
  imageId: this.imageId ?? ""
17
17
  };
18
+ case "watermark":
19
+ return {
20
+ type: this.type,
21
+ imageId: this.imageId ?? "",
22
+ opacity: this.opacity
23
+ };
24
+ case "headerImage":
25
+ return {
26
+ type: this.type,
27
+ imageId: this.imageId ?? "",
28
+ headerWidth: this.headerWidth,
29
+ headerHeight: this.headerHeight,
30
+ applyTo: this.applyTo
31
+ };
18
32
  case "image": {
19
33
  const range = this.range;
20
34
  if (!range) {
@@ -36,9 +50,13 @@ class Image {
36
50
  throw new ImageError("Invalid Image Type");
37
51
  }
38
52
  }
39
- set model({ type, imageId, range, hyperlinks }) {
53
+ set model({ type, imageId, range, hyperlinks, opacity, headerWidth, headerHeight, applyTo }) {
40
54
  this.type = type;
41
55
  this.imageId = imageId;
56
+ this.opacity = opacity;
57
+ this.headerWidth = headerWidth;
58
+ this.headerHeight = headerHeight;
59
+ this.applyTo = applyTo;
42
60
  if (type === "image") {
43
61
  if (typeof range === "string") {
44
62
  const decoded = colCache.decode(range);
@@ -67,6 +85,10 @@ class Image {
67
85
  const cloned = new Image(target);
68
86
  cloned.type = this.type;
69
87
  cloned.imageId = this.imageId;
88
+ cloned.opacity = this.opacity;
89
+ cloned.headerWidth = this.headerWidth;
90
+ cloned.headerHeight = this.headerHeight;
91
+ cloned.applyTo = this.applyTo;
70
92
  if (this.range) {
71
93
  cloned.range = {
72
94
  tl: this.range.tl.clone(target),
@@ -151,6 +151,8 @@ class WorksheetWriter {
151
151
  // auto filter
152
152
  this.autoFilter = options.autoFilter ?? null;
153
153
  this._media = [];
154
+ // watermark
155
+ this._watermark = null;
154
156
  // worksheet protection
155
157
  this.sheetProtection = null;
156
158
  // start writing to stream now
@@ -448,6 +450,57 @@ class WorksheetWriter {
448
450
  getImages() {
449
451
  return this._media;
450
452
  }
453
+ // =========================================================================
454
+ // Watermark
455
+ /**
456
+ * Add a watermark to the worksheet using an image from `WorkbookWriter.addImage()`.
457
+ * Supports overlay mode (DrawingML with transparency) and header mode (VML behind content).
458
+ */
459
+ addWatermark(options) {
460
+ // Remove existing watermark entries (both stored type tags)
461
+ this._media = this._media.filter(m => m._watermarkTag !== true);
462
+ const opacity = options.opacity !== undefined ? Math.max(0, Math.min(1, options.opacity)) : 0.15;
463
+ this._watermark = {
464
+ imageId: String(options.imageId),
465
+ mode: options.mode ?? "overlay",
466
+ opacity,
467
+ headerWidth: options.headerWidth,
468
+ headerHeight: options.headerHeight,
469
+ applyTo: options.applyTo
470
+ };
471
+ if (this._watermark.mode === "overlay") {
472
+ // Coverage range is computed lazily during commit() via _resolveWatermarkRange()
473
+ const entry = {
474
+ type: "image",
475
+ imageId: String(options.imageId),
476
+ range: {
477
+ tl: { nativeCol: 0, nativeColOff: 0, nativeRow: 0, nativeRowOff: 0 },
478
+ br: { nativeCol: 100, nativeColOff: 0, nativeRow: 200, nativeRowOff: 0 },
479
+ editAs: "absolute"
480
+ },
481
+ // Internal tag for dedup — not part of the WriterImageModel type
482
+ _watermarkTag: true,
483
+ opacity
484
+ };
485
+ this._media.push(entry);
486
+ }
487
+ // Note: header mode for streaming writer is limited — the VML file generation
488
+ // happens in WorkbookWriter.addWorksheets(), which handles worksheet.headerImage.
489
+ // We store the config in _watermark and it's picked up by the commit path.
490
+ }
491
+ /**
492
+ * Get the current watermark configuration.
493
+ */
494
+ getWatermark() {
495
+ return this._watermark;
496
+ }
497
+ /**
498
+ * Remove the watermark from the worksheet.
499
+ */
500
+ removeWatermark() {
501
+ this._watermark = null;
502
+ this._media = this._media.filter(m => m._watermarkTag !== true);
503
+ }
451
504
  /**
452
505
  * Parse the user-supplied range into a normalised internal model
453
506
  * mirroring what the regular Worksheet / Image class does.
@@ -626,6 +679,21 @@ class WorksheetWriter {
626
679
  if (this._media.length === 0) {
627
680
  return;
628
681
  }
682
+ // Resolve watermark coverage range from actual worksheet dimensions
683
+ // (at commit time, all rows have been flushed so _dimensions is accurate)
684
+ for (const entry of this._media) {
685
+ if (entry._watermarkTag) {
686
+ const dims = this._dimensions.model;
687
+ const maxCol = dims ? Math.max(dims.right ?? 100, 100) : 100;
688
+ const maxRow = dims ? Math.max(dims.bottom ?? 200, 200) : 200;
689
+ entry.range.br = {
690
+ nativeCol: maxCol,
691
+ nativeColOff: 0,
692
+ nativeRow: maxRow,
693
+ nativeRowOff: 0
694
+ };
695
+ }
696
+ }
629
697
  // Build the drawing model from the stored images.
630
698
  // The drawing XML will be generated later by WorkbookWriterBase.addDrawings().
631
699
  const drawingName = `drawing${this.id}`;
@@ -64,6 +64,11 @@ export function buildDrawingAnchorsAndRels(media, existingRels, options) {
64
64
  },
65
65
  range: medium.range
66
66
  };
67
+ // Pass through watermark opacity as alphaModFix
68
+ if (medium.opacity !== undefined) {
69
+ const clamped = Math.max(0, Math.min(1, medium.opacity));
70
+ anchor.picture.alphaModFix = Math.round(clamped * 100000);
71
+ }
67
72
  // Handle image hyperlinks
68
73
  if (medium.hyperlinks && medium.hyperlinks.hyperlink) {
69
74
  const rIdHyperlink = options.nextRId(rels);
@@ -17,6 +17,7 @@ const mediaFilenameRegex = /^xl\/media\/([a-zA-Z0-9]+[.][a-zA-Z0-9]{3,4})$/;
17
17
  const drawingXmlRegex = /^xl\/drawings\/(drawing\d+)[.]xml$/;
18
18
  const drawingRelsXmlRegex = /^xl\/drawings\/_rels\/(drawing\d+)[.]xml[.]rels$/;
19
19
  const vmlDrawingRegex = /^xl\/drawings\/(vmlDrawing\d+)[.]vml$/;
20
+ const vmlDrawingHFRegex = /^xl\/drawings\/(vmlDrawingHF\d+)[.]vml$/;
20
21
  const commentsXmlRegex = /^xl\/comments(\d+)[.]xml$/;
21
22
  const tableXmlRegex = /^xl\/tables\/(table\d+)[.]xml$/;
22
23
  const pivotTableXmlRegex = /^xl\/pivotTables\/(pivotTable\d+)[.]xml$/;
@@ -72,6 +73,10 @@ export function getVmlDrawingNameFromPath(path) {
72
73
  const match = vmlDrawingRegex.exec(path);
73
74
  return match ? match[1] : undefined;
74
75
  }
76
+ export function getVmlDrawingHFNameFromPath(path) {
77
+ const match = vmlDrawingHFRegex.exec(path);
78
+ return match ? match[1] : undefined;
79
+ }
75
80
  export function getCommentsIndexFromPath(path) {
76
81
  const match = commentsXmlRegex.exec(path);
77
82
  return match ? match[1] : undefined;
@@ -129,6 +134,12 @@ export function commentsPathFromName(commentName) {
129
134
  export function vmlDrawingPath(sheetId) {
130
135
  return `xl/drawings/vmlDrawing${sheetId}.vml`;
131
136
  }
137
+ export function vmlDrawingHFPath(sheetId) {
138
+ return `xl/drawings/vmlDrawingHF${sheetId}.vml`;
139
+ }
140
+ export function vmlDrawingHFRelsPath(sheetId) {
141
+ return `xl/drawings/_rels/vmlDrawingHF${sheetId}.vml.rels`;
142
+ }
132
143
  export function tablePath(target) {
133
144
  return `xl/tables/${target}`;
134
145
  }
@@ -178,6 +189,10 @@ export function vmlDrawingRelTargetFromWorksheet(sheetId) {
178
189
  // Target inside xl/worksheets/_rels/sheetN.xml.rels (base: xl/worksheets/)
179
190
  return `../drawings/vmlDrawing${sheetId}.vml`;
180
191
  }
192
+ export function vmlDrawingHFRelTargetFromWorksheet(sheetId) {
193
+ // Target inside xl/worksheets/_rels/sheetN.xml.rels (base: xl/worksheets/)
194
+ return `../drawings/vmlDrawingHF${sheetId}.vml`;
195
+ }
181
196
  export function drawingRelTargetFromWorksheet(drawingName) {
182
197
  // Target inside xl/worksheets/_rels/sheetN.xml.rels (base: xl/worksheets/)
183
198
  return `../drawings/${drawingName}.xml`;