@cj-tech-master/excelts 8.1.2 → 9.0.0-canary.20260409073829.a0d72ec
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/README_zh.md +2 -2
- package/dist/browser/index.browser.d.ts +2 -0
- package/dist/browser/index.browser.js +2 -0
- package/dist/browser/index.d.ts +2 -0
- package/dist/browser/index.js +2 -0
- package/dist/browser/modules/excel/cell.js +11 -7
- package/dist/browser/modules/excel/column.js +7 -6
- package/dist/browser/modules/excel/image.d.ts +27 -2
- package/dist/browser/modules/excel/image.js +23 -1
- package/dist/browser/modules/excel/row.js +5 -1
- package/dist/browser/modules/excel/stream/worksheet-reader.js +3 -2
- package/dist/browser/modules/excel/stream/worksheet-writer.d.ts +16 -1
- package/dist/browser/modules/excel/stream/worksheet-writer.js +68 -0
- package/dist/browser/modules/excel/types.d.ts +72 -0
- package/dist/browser/modules/excel/utils/cell-format.js +64 -2
- package/dist/browser/modules/excel/utils/drawing-utils.d.ts +4 -0
- package/dist/browser/modules/excel/utils/drawing-utils.js +5 -0
- package/dist/browser/modules/excel/utils/ooxml-paths.d.ts +4 -0
- package/dist/browser/modules/excel/utils/ooxml-paths.js +15 -0
- package/dist/browser/modules/excel/utils/watermark-image.d.ts +67 -0
- package/dist/browser/modules/excel/utils/watermark-image.js +383 -0
- package/dist/browser/modules/excel/worksheet.d.ts +39 -1
- package/dist/browser/modules/excel/worksheet.js +99 -0
- package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +3 -2
- package/dist/browser/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +6 -1
- package/dist/browser/modules/excel/xlsx/xform/drawing/blip-fill-xform.d.ts +2 -1
- package/dist/browser/modules/excel/xlsx/xform/drawing/blip-fill-xform.js +0 -1
- package/dist/browser/modules/excel/xlsx/xform/drawing/blip-xform.d.ts +3 -1
- package/dist/browser/modules/excel/xlsx/xform/drawing/blip-xform.js +22 -6
- package/dist/browser/modules/excel/xlsx/xform/drawing/pic-xform.d.ts +3 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/pic-xform.js +5 -1
- package/dist/browser/modules/excel/xlsx/xform/drawing/vml-drawing-xform.d.ts +19 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/vml-drawing-xform.js +103 -4
- package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +135 -8
- package/dist/browser/modules/excel/xlsx/xlsx.browser.d.ts +1 -0
- package/dist/browser/modules/excel/xlsx/xlsx.browser.js +53 -1
- package/dist/browser/modules/pdf/core/pdf-writer.d.ts +1 -1
- package/dist/browser/modules/pdf/core/pdf-writer.js +2 -1
- package/dist/browser/modules/pdf/excel-bridge.d.ts +4 -3
- package/dist/browser/modules/pdf/excel-bridge.js +18 -5
- package/dist/browser/modules/pdf/index.d.ts +4 -4
- package/dist/browser/modules/pdf/index.js +3 -3
- package/dist/browser/modules/pdf/pdf.d.ts +7 -6
- package/dist/browser/modules/pdf/pdf.js +7 -6
- package/dist/browser/modules/pdf/reader/pdf-reader.d.ts +8 -7
- package/dist/browser/modules/pdf/reader/pdf-reader.js +81 -74
- package/dist/browser/modules/pdf/render/constants.d.ts +30 -0
- package/dist/browser/modules/pdf/render/constants.js +30 -0
- package/dist/browser/modules/pdf/render/layout-engine.d.ts +2 -1
- package/dist/browser/modules/pdf/render/layout-engine.js +359 -156
- package/dist/browser/modules/pdf/render/page-renderer.d.ts +31 -3
- package/dist/browser/modules/pdf/render/page-renderer.js +621 -114
- package/dist/browser/modules/pdf/render/pdf-exporter.d.ts +3 -2
- package/dist/browser/modules/pdf/render/pdf-exporter.js +219 -142
- package/dist/browser/modules/pdf/render/style-converter.js +27 -26
- package/dist/browser/modules/pdf/types.d.ts +243 -0
- package/dist/browser/utils/utils.base.d.ts +5 -0
- package/dist/browser/utils/utils.base.js +10 -0
- package/dist/cjs/index.js +5 -2
- package/dist/cjs/modules/excel/cell.js +11 -7
- package/dist/cjs/modules/excel/column.js +7 -6
- package/dist/cjs/modules/excel/image.js +23 -1
- package/dist/cjs/modules/excel/row.js +5 -1
- package/dist/cjs/modules/excel/stream/worksheet-reader.js +3 -2
- package/dist/cjs/modules/excel/stream/worksheet-writer.js +68 -0
- package/dist/cjs/modules/excel/utils/cell-format.js +64 -2
- package/dist/cjs/modules/excel/utils/drawing-utils.js +5 -0
- package/dist/cjs/modules/excel/utils/ooxml-paths.js +19 -0
- package/dist/cjs/modules/excel/utils/watermark-image.js +386 -0
- package/dist/cjs/modules/excel/worksheet.js +99 -0
- package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +3 -2
- package/dist/cjs/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +6 -1
- package/dist/cjs/modules/excel/xlsx/xform/drawing/blip-fill-xform.js +0 -1
- package/dist/cjs/modules/excel/xlsx/xform/drawing/blip-xform.js +22 -6
- package/dist/cjs/modules/excel/xlsx/xform/drawing/pic-xform.js +5 -1
- package/dist/cjs/modules/excel/xlsx/xform/drawing/vml-drawing-xform.js +103 -4
- package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +134 -7
- package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +52 -0
- package/dist/cjs/modules/pdf/core/pdf-writer.js +2 -1
- package/dist/cjs/modules/pdf/excel-bridge.js +18 -5
- package/dist/cjs/modules/pdf/index.js +3 -3
- package/dist/cjs/modules/pdf/pdf.js +7 -6
- package/dist/cjs/modules/pdf/reader/pdf-reader.js +81 -74
- package/dist/cjs/modules/pdf/render/constants.js +33 -0
- package/dist/cjs/modules/pdf/render/layout-engine.js +359 -156
- package/dist/cjs/modules/pdf/render/page-renderer.js +623 -114
- package/dist/cjs/modules/pdf/render/pdf-exporter.js +218 -141
- package/dist/cjs/modules/pdf/render/style-converter.js +27 -26
- package/dist/cjs/utils/utils.base.js +11 -0
- package/dist/esm/index.browser.js +2 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/modules/excel/cell.js +11 -7
- package/dist/esm/modules/excel/column.js +7 -6
- package/dist/esm/modules/excel/image.js +23 -1
- package/dist/esm/modules/excel/row.js +5 -1
- package/dist/esm/modules/excel/stream/worksheet-reader.js +3 -2
- package/dist/esm/modules/excel/stream/worksheet-writer.js +68 -0
- package/dist/esm/modules/excel/utils/cell-format.js +64 -2
- package/dist/esm/modules/excel/utils/drawing-utils.js +5 -0
- package/dist/esm/modules/excel/utils/ooxml-paths.js +15 -0
- package/dist/esm/modules/excel/utils/watermark-image.js +383 -0
- package/dist/esm/modules/excel/worksheet.js +99 -0
- package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +3 -2
- package/dist/esm/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +6 -1
- package/dist/esm/modules/excel/xlsx/xform/drawing/blip-fill-xform.js +0 -1
- package/dist/esm/modules/excel/xlsx/xform/drawing/blip-xform.js +22 -6
- package/dist/esm/modules/excel/xlsx/xform/drawing/pic-xform.js +5 -1
- package/dist/esm/modules/excel/xlsx/xform/drawing/vml-drawing-xform.js +103 -4
- package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +135 -8
- package/dist/esm/modules/excel/xlsx/xlsx.browser.js +53 -1
- package/dist/esm/modules/pdf/core/pdf-writer.js +2 -1
- package/dist/esm/modules/pdf/excel-bridge.js +18 -5
- package/dist/esm/modules/pdf/index.js +3 -3
- package/dist/esm/modules/pdf/pdf.js +7 -6
- package/dist/esm/modules/pdf/reader/pdf-reader.js +81 -74
- package/dist/esm/modules/pdf/render/constants.js +30 -0
- package/dist/esm/modules/pdf/render/layout-engine.js +359 -156
- package/dist/esm/modules/pdf/render/page-renderer.js +621 -114
- package/dist/esm/modules/pdf/render/pdf-exporter.js +219 -142
- package/dist/esm/modules/pdf/render/style-converter.js +27 -26
- package/dist/esm/utils/utils.base.js +10 -0
- package/dist/iife/excelts.iife.js +4243 -1977
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +49 -49
- package/dist/types/index.browser.d.ts +2 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/modules/excel/image.d.ts +27 -2
- package/dist/types/modules/excel/stream/worksheet-writer.d.ts +16 -1
- package/dist/types/modules/excel/types.d.ts +72 -0
- package/dist/types/modules/excel/utils/drawing-utils.d.ts +4 -0
- package/dist/types/modules/excel/utils/ooxml-paths.d.ts +4 -0
- package/dist/types/modules/excel/utils/watermark-image.d.ts +67 -0
- package/dist/types/modules/excel/worksheet.d.ts +39 -1
- package/dist/types/modules/excel/xlsx/xform/drawing/blip-fill-xform.d.ts +2 -1
- package/dist/types/modules/excel/xlsx/xform/drawing/blip-xform.d.ts +3 -1
- package/dist/types/modules/excel/xlsx/xform/drawing/pic-xform.d.ts +3 -0
- package/dist/types/modules/excel/xlsx/xform/drawing/vml-drawing-xform.d.ts +19 -0
- package/dist/types/modules/excel/xlsx/xlsx.browser.d.ts +1 -0
- package/dist/types/modules/pdf/core/pdf-writer.d.ts +1 -1
- package/dist/types/modules/pdf/excel-bridge.d.ts +4 -3
- package/dist/types/modules/pdf/index.d.ts +4 -4
- package/dist/types/modules/pdf/pdf.d.ts +7 -6
- package/dist/types/modules/pdf/reader/pdf-reader.d.ts +8 -7
- package/dist/types/modules/pdf/render/constants.d.ts +30 -0
- package/dist/types/modules/pdf/render/layout-engine.d.ts +2 -1
- package/dist/types/modules/pdf/render/page-renderer.d.ts +31 -3
- package/dist/types/modules/pdf/render/pdf-exporter.d.ts +3 -2
- package/dist/types/modules/pdf/types.d.ts +243 -0
- package/dist/types/utils/utils.base.d.ts +5 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -100,14 +100,14 @@ wb.getWorksheet(1).eachRow((row, n) => console.log(n, row.values));
|
|
|
100
100
|
|
|
101
101
|
// PDF — generate from data, no Workbook needed
|
|
102
102
|
import { pdf } from "@cj-tech-master/excelts/pdf";
|
|
103
|
-
const pdfBytes = pdf([
|
|
103
|
+
const pdfBytes = await pdf([
|
|
104
104
|
["Product", "Revenue"],
|
|
105
105
|
["Widget", 1000]
|
|
106
106
|
]);
|
|
107
107
|
|
|
108
108
|
// PDF — read text, images, and metadata from any PDF
|
|
109
109
|
import { readPdf } from "@cj-tech-master/excelts/pdf";
|
|
110
|
-
const result = readPdf(pdfBytes);
|
|
110
|
+
const result = await readPdf(pdfBytes);
|
|
111
111
|
console.log(result.text); // extracted text
|
|
112
112
|
console.log(result.metadata); // title, author, etc.
|
|
113
113
|
|
package/README_zh.md
CHANGED
|
@@ -100,14 +100,14 @@ wb.getWorksheet(1).eachRow((row, n) => console.log(n, row.values));
|
|
|
100
100
|
|
|
101
101
|
// PDF — 直接从数据生成,无需 Workbook
|
|
102
102
|
import { pdf } from "@cj-tech-master/excelts/pdf";
|
|
103
|
-
const pdfBytes = pdf([
|
|
103
|
+
const pdfBytes = await pdf([
|
|
104
104
|
["产品", "收入"],
|
|
105
105
|
["小工具", 1000]
|
|
106
106
|
]);
|
|
107
107
|
|
|
108
108
|
// PDF — 读取任意 PDF 的文本、图片和元数据
|
|
109
109
|
import { readPdf } from "@cj-tech-master/excelts/pdf";
|
|
110
|
-
const result = readPdf(pdfBytes);
|
|
110
|
+
const result = await readPdf(pdfBytes);
|
|
111
111
|
console.log(result.text); // 提取的文本
|
|
112
112
|
console.log(result.metadata); // 标题、作者等
|
|
113
113
|
|
|
@@ -15,6 +15,8 @@ export { DataValidations } from "./modules/excel/data-validations.js";
|
|
|
15
15
|
export { FormCheckbox } from "./modules/excel/form-control.js";
|
|
16
16
|
export * from "./modules/excel/enums.js";
|
|
17
17
|
export * from "./modules/excel/types.js";
|
|
18
|
+
export { createTextWatermarkImage } from "./modules/excel/utils/watermark-image.js";
|
|
19
|
+
export type { TextWatermarkImageOptions } from "./modules/excel/utils/watermark-image.js";
|
|
18
20
|
export type { PivotTable, PivotTableModel, PivotTableValue, PivotTableSource, CacheField, SharedItemValue, DataField, PivotTableSubtotal, RecordValue, ParsedCacheDefinition, ParsedCacheRecords } from "./modules/excel/pivot-table.js";
|
|
19
21
|
export type { FormCheckboxModel, FormCheckboxOptions, FormControlRange, FormControlAnchor } from "./modules/excel/form-control.js";
|
|
20
22
|
export { WorkbookWriter } from "./modules/excel/stream/workbook-writer.browser.js";
|
|
@@ -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/browser/index.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export { WorksheetWriter } from "./modules/excel/stream/worksheet-writer.js";
|
|
|
15
15
|
export { WorksheetReader } from "./modules/excel/stream/worksheet-reader.js";
|
|
16
16
|
export * from "./modules/excel/enums.js";
|
|
17
17
|
export * from "./modules/excel/types.js";
|
|
18
|
+
export { createTextWatermarkImage } from "./modules/excel/utils/watermark-image.js";
|
|
19
|
+
export type { TextWatermarkImageOptions } from "./modules/excel/utils/watermark-image.js";
|
|
18
20
|
export type { PivotTable, PivotTableModel, PivotTableValue, PivotTableSource, CacheField, SharedItemValue, DataField, PivotTableSubtotal, RecordValue, ParsedCacheDefinition, ParsedCacheRecords } from "./modules/excel/pivot-table.js";
|
|
19
21
|
export type { FormCheckboxModel, FormCheckboxOptions, FormControlRange, FormControlAnchor } from "./modules/excel/form-control.js";
|
|
20
22
|
export type { WorkbookReaderOptions, ParseEvent, SharedStringEvent, WorksheetReadyEvent, HyperlinksEvent } from "./modules/excel/stream/workbook-reader.js";
|
package/dist/browser/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
|
|
@@ -3,6 +3,7 @@ import { Enums } from "./enums.js";
|
|
|
3
3
|
import { Note } from "./note.js";
|
|
4
4
|
import { escapeHtml } from "./utils/under-dash.js";
|
|
5
5
|
import { slideFormula } from "./utils/shared-formula.js";
|
|
6
|
+
import { copyStyle } from "./utils/copy-style.js";
|
|
6
7
|
import { ExcelError, InvalidValueTypeError } from "./errors.js";
|
|
7
8
|
// Returns true if the value is a non-empty object (has at least one own key),
|
|
8
9
|
// or any truthy non-object value. Returns false for undefined, null, false, 0,
|
|
@@ -87,27 +88,27 @@ class Cell {
|
|
|
87
88
|
const font = (rowStyle && hasOwnKeys(rowStyle.font) && rowStyle.font) ||
|
|
88
89
|
(colStyle && hasOwnKeys(colStyle.font) && colStyle.font);
|
|
89
90
|
if (font) {
|
|
90
|
-
style.font = font;
|
|
91
|
+
style.font = structuredClone(font);
|
|
91
92
|
}
|
|
92
93
|
const alignment = (rowStyle && hasOwnKeys(rowStyle.alignment) && rowStyle.alignment) ||
|
|
93
94
|
(colStyle && hasOwnKeys(colStyle.alignment) && colStyle.alignment);
|
|
94
95
|
if (alignment) {
|
|
95
|
-
style.alignment = alignment;
|
|
96
|
+
style.alignment = structuredClone(alignment);
|
|
96
97
|
}
|
|
97
98
|
const border = (rowStyle && hasOwnKeys(rowStyle.border) && rowStyle.border) ||
|
|
98
99
|
(colStyle && hasOwnKeys(colStyle.border) && colStyle.border);
|
|
99
100
|
if (border) {
|
|
100
|
-
style.border = border;
|
|
101
|
+
style.border = structuredClone(border);
|
|
101
102
|
}
|
|
102
103
|
const fill = (rowStyle && hasOwnKeys(rowStyle.fill) && rowStyle.fill) ||
|
|
103
104
|
(colStyle && hasOwnKeys(colStyle.fill) && colStyle.fill);
|
|
104
105
|
if (fill) {
|
|
105
|
-
style.fill = fill;
|
|
106
|
+
style.fill = structuredClone(fill);
|
|
106
107
|
}
|
|
107
108
|
const protection = (rowStyle && hasOwnKeys(rowStyle.protection) && rowStyle.protection) ||
|
|
108
109
|
(colStyle && hasOwnKeys(colStyle.protection) && colStyle.protection);
|
|
109
110
|
if (protection) {
|
|
110
|
-
style.protection = protection;
|
|
111
|
+
style.protection = structuredClone(protection);
|
|
111
112
|
}
|
|
112
113
|
return style;
|
|
113
114
|
}
|
|
@@ -151,7 +152,10 @@ class Cell {
|
|
|
151
152
|
this._value.release();
|
|
152
153
|
this._value = Value.create(Cell.Types.Merge, this, master);
|
|
153
154
|
if (!ignoreStyle) {
|
|
154
|
-
|
|
155
|
+
// Deep-copy so each cell has an independent style object.
|
|
156
|
+
// Without this, all cells in the merge share the same reference,
|
|
157
|
+
// and setting a property (e.g. border) on any cell mutates all of them.
|
|
158
|
+
this.style = copyStyle(master.style) ?? {};
|
|
155
159
|
}
|
|
156
160
|
}
|
|
157
161
|
unmerge() {
|
|
@@ -318,7 +322,7 @@ class Cell {
|
|
|
318
322
|
}
|
|
319
323
|
}
|
|
320
324
|
if (value.style) {
|
|
321
|
-
this.style = value.style;
|
|
325
|
+
this.style = copyStyle(value.style) ?? {};
|
|
322
326
|
}
|
|
323
327
|
else {
|
|
324
328
|
this.style = {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { colCache } from "./utils/col-cache.js";
|
|
2
2
|
import { isEqual } from "./utils/under-dash.js";
|
|
3
|
+
import { copyStyle } from "./utils/copy-style.js";
|
|
3
4
|
import { Enums } from "./enums.js";
|
|
4
5
|
const DEFAULT_COLUMN_WIDTH = 9;
|
|
5
6
|
/**
|
|
@@ -48,7 +49,7 @@ class Column {
|
|
|
48
49
|
this.width = value.width !== undefined ? value.width : DEFAULT_COLUMN_WIDTH;
|
|
49
50
|
this.outlineLevel = value.outlineLevel;
|
|
50
51
|
if (value.style) {
|
|
51
|
-
this.style = value.style;
|
|
52
|
+
this.style = copyStyle(value.style) ?? {};
|
|
52
53
|
}
|
|
53
54
|
else {
|
|
54
55
|
this.style = {};
|
|
@@ -259,7 +260,7 @@ class Column {
|
|
|
259
260
|
set font(value) {
|
|
260
261
|
this.style.font = value;
|
|
261
262
|
this.eachCell(cell => {
|
|
262
|
-
cell.font = value;
|
|
263
|
+
cell.font = value ? structuredClone(value) : value;
|
|
263
264
|
});
|
|
264
265
|
}
|
|
265
266
|
get alignment() {
|
|
@@ -268,7 +269,7 @@ class Column {
|
|
|
268
269
|
set alignment(value) {
|
|
269
270
|
this.style.alignment = value;
|
|
270
271
|
this.eachCell(cell => {
|
|
271
|
-
cell.alignment = value;
|
|
272
|
+
cell.alignment = value ? structuredClone(value) : value;
|
|
272
273
|
});
|
|
273
274
|
}
|
|
274
275
|
get protection() {
|
|
@@ -277,7 +278,7 @@ class Column {
|
|
|
277
278
|
set protection(value) {
|
|
278
279
|
this.style.protection = value;
|
|
279
280
|
this.eachCell(cell => {
|
|
280
|
-
cell.protection = value;
|
|
281
|
+
cell.protection = value ? structuredClone(value) : value;
|
|
281
282
|
});
|
|
282
283
|
}
|
|
283
284
|
get border() {
|
|
@@ -286,7 +287,7 @@ class Column {
|
|
|
286
287
|
set border(value) {
|
|
287
288
|
this.style.border = value;
|
|
288
289
|
this.eachCell(cell => {
|
|
289
|
-
cell.border = value;
|
|
290
|
+
cell.border = value ? structuredClone(value) : value;
|
|
290
291
|
});
|
|
291
292
|
}
|
|
292
293
|
get fill() {
|
|
@@ -295,7 +296,7 @@ class Column {
|
|
|
295
296
|
set fill(value) {
|
|
296
297
|
this.style.fill = value;
|
|
297
298
|
this.eachCell(cell => {
|
|
298
|
-
cell.fill = value;
|
|
299
|
+
cell.fill = value ? structuredClone(value) : value;
|
|
299
300
|
});
|
|
300
301
|
}
|
|
301
302
|
// =============================================================================
|
|
@@ -19,6 +19,19 @@ interface BackgroundModel {
|
|
|
19
19
|
type: "background";
|
|
20
20
|
imageId: string;
|
|
21
21
|
}
|
|
22
|
+
interface WatermarkModel {
|
|
23
|
+
type: "watermark";
|
|
24
|
+
imageId: string;
|
|
25
|
+
/** Opacity 0-1 for overlay mode */
|
|
26
|
+
opacity?: number;
|
|
27
|
+
}
|
|
28
|
+
interface HeaderImageModel {
|
|
29
|
+
type: "headerImage";
|
|
30
|
+
imageId: string;
|
|
31
|
+
headerWidth?: number;
|
|
32
|
+
headerHeight?: number;
|
|
33
|
+
applyTo?: "all" | "odd" | "even" | "first";
|
|
34
|
+
}
|
|
22
35
|
interface ImageRangeModel {
|
|
23
36
|
tl: AnchorModel;
|
|
24
37
|
br?: AnchorModel;
|
|
@@ -31,7 +44,7 @@ interface ImageModel {
|
|
|
31
44
|
hyperlinks?: ImageHyperlinks;
|
|
32
45
|
range: ImageRangeModel;
|
|
33
46
|
}
|
|
34
|
-
type Model = BackgroundModel | ImageModel;
|
|
47
|
+
type Model = BackgroundModel | ImageModel | WatermarkModel | HeaderImageModel;
|
|
35
48
|
type ImageModelInput = ModelInput;
|
|
36
49
|
interface RangeInput {
|
|
37
50
|
tl?: AnchorModel | {
|
|
@@ -51,15 +64,27 @@ interface ModelInput {
|
|
|
51
64
|
imageId: string;
|
|
52
65
|
range?: string | RangeInput | ImageRangeModel;
|
|
53
66
|
hyperlinks?: ImageHyperlinks;
|
|
67
|
+
opacity?: number;
|
|
68
|
+
headerWidth?: number;
|
|
69
|
+
headerHeight?: number;
|
|
70
|
+
applyTo?: "all" | "odd" | "even" | "first";
|
|
54
71
|
}
|
|
55
72
|
declare class Image {
|
|
56
73
|
readonly worksheet: Worksheet;
|
|
57
74
|
type?: string;
|
|
58
75
|
imageId?: string;
|
|
59
76
|
range?: ImageRange;
|
|
77
|
+
/** Opacity for watermark overlay mode (0-1). */
|
|
78
|
+
opacity?: number;
|
|
79
|
+
/** Header image width in points. */
|
|
80
|
+
headerWidth?: number;
|
|
81
|
+
/** Header image height in points. */
|
|
82
|
+
headerHeight?: number;
|
|
83
|
+
/** Header watermark applyTo setting. */
|
|
84
|
+
applyTo?: "all" | "odd" | "even" | "first";
|
|
60
85
|
constructor(worksheet: Worksheet, model?: ModelInput);
|
|
61
86
|
get model(): Model;
|
|
62
|
-
set model({ type, imageId, range, hyperlinks }: ModelInput);
|
|
87
|
+
set model({ type, imageId, range, hyperlinks, opacity, headerWidth, headerHeight, applyTo }: ModelInput);
|
|
63
88
|
clone(worksheet?: Worksheet): Image;
|
|
64
89
|
}
|
|
65
90
|
export { Image, type Model as ImageModel, type ImageModelInput };
|
|
@@ -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),
|
|
@@ -297,7 +297,11 @@ class Row {
|
|
|
297
297
|
this.style[name] = value;
|
|
298
298
|
this._cells.forEach(cell => {
|
|
299
299
|
if (cell) {
|
|
300
|
-
cell
|
|
300
|
+
// Clone object values so each cell gets an independent copy.
|
|
301
|
+
// Without this, mutating a sub-property (e.g. cell.border.top = ...)
|
|
302
|
+
// would leak to every other cell that received the same reference.
|
|
303
|
+
cell.style[name] =
|
|
304
|
+
typeof value === "object" && value !== null ? structuredClone(value) : value;
|
|
301
305
|
}
|
|
302
306
|
});
|
|
303
307
|
}
|
|
@@ -7,6 +7,7 @@ import { EventEmitter } from "../../../utils/event-emitter.js";
|
|
|
7
7
|
import { SaxParser } from "../../xml/sax.js";
|
|
8
8
|
import { ExcelStreamStateError } from "../errors.js";
|
|
9
9
|
import { isDateFmt, excelToDate, decodeOoxmlEscape } from "../../../utils/utils.browser.js";
|
|
10
|
+
import { copyStyle } from "../utils/copy-style.js";
|
|
10
11
|
import { colCache } from "../utils/col-cache.js";
|
|
11
12
|
import { Dimensions } from "../range.js";
|
|
12
13
|
import { Row } from "../row.js";
|
|
@@ -178,7 +179,7 @@ class WorksheetReader extends EventEmitter {
|
|
|
178
179
|
const styleId = parseInt(node.attributes.s, 10);
|
|
179
180
|
const style = styles.getStyleModel(styleId);
|
|
180
181
|
if (style) {
|
|
181
|
-
row.style = style;
|
|
182
|
+
row.style = copyStyle(style) ?? {};
|
|
182
183
|
}
|
|
183
184
|
}
|
|
184
185
|
}
|
|
@@ -279,7 +280,7 @@ class WorksheetReader extends EventEmitter {
|
|
|
279
280
|
if (c.s !== undefined) {
|
|
280
281
|
const style = styles.getStyleModel(c.s);
|
|
281
282
|
if (style) {
|
|
282
|
-
cell.style = style;
|
|
283
|
+
cell.style = copyStyle(style) ?? {};
|
|
283
284
|
}
|
|
284
285
|
}
|
|
285
286
|
if (c.f) {
|
|
@@ -3,7 +3,7 @@ import { Row } from "../row.js";
|
|
|
3
3
|
import type { Cell } from "../cell.js";
|
|
4
4
|
import { Column } from "../column.js";
|
|
5
5
|
import { DataValidations } from "../data-validations.js";
|
|
6
|
-
import type { RowBreak, ColBreak, PageSetup, HeaderFooter, WorksheetProperties, WorksheetView, WorksheetState, AutoFilter, WorksheetProtection, ConditionalFormattingOptions, AddImageRange } from "../types.js";
|
|
6
|
+
import type { RowBreak, ColBreak, PageSetup, HeaderFooter, WorksheetProperties, WorksheetView, WorksheetState, AutoFilter, WorksheetProtection, ConditionalFormattingOptions, AddImageRange, WatermarkOptions } from "../types.js";
|
|
7
7
|
interface WorksheetWriterOptions {
|
|
8
8
|
id: number;
|
|
9
9
|
name?: string;
|
|
@@ -97,6 +97,8 @@ declare class WorksheetWriter {
|
|
|
97
97
|
startedData: boolean;
|
|
98
98
|
private _background?;
|
|
99
99
|
private _headerRowCount?;
|
|
100
|
+
/** Watermark configuration */
|
|
101
|
+
private _watermark;
|
|
100
102
|
/** Drawing model — populated during commit if images were added */
|
|
101
103
|
private _drawing?;
|
|
102
104
|
/** Relationship Id - assigned by WorkbookWriter */
|
|
@@ -141,6 +143,19 @@ declare class WorksheetWriter {
|
|
|
141
143
|
* Each entry contains imageId and the normalised range (with native anchors).
|
|
142
144
|
*/
|
|
143
145
|
getImages(): ReadonlyArray<WriterImageModel>;
|
|
146
|
+
/**
|
|
147
|
+
* Add a watermark to the worksheet using an image from `WorkbookWriter.addImage()`.
|
|
148
|
+
* Supports overlay mode (DrawingML with transparency) and header mode (VML behind content).
|
|
149
|
+
*/
|
|
150
|
+
addWatermark(options: WatermarkOptions): void;
|
|
151
|
+
/**
|
|
152
|
+
* Get the current watermark configuration.
|
|
153
|
+
*/
|
|
154
|
+
getWatermark(): WatermarkOptions | null;
|
|
155
|
+
/**
|
|
156
|
+
* Remove the watermark from the worksheet.
|
|
157
|
+
*/
|
|
158
|
+
removeWatermark(): void;
|
|
144
159
|
/**
|
|
145
160
|
* Parse the user-supplied range into a normalised internal model
|
|
146
161
|
* mirroring what the regular Worksheet / Image class does.
|
|
@@ -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}`;
|
|
@@ -368,6 +368,78 @@ export interface ImageHyperlinkValue {
|
|
|
368
368
|
hyperlink: string;
|
|
369
369
|
tooltip?: string;
|
|
370
370
|
}
|
|
371
|
+
/**
|
|
372
|
+
* Watermark placement mode in the Excel worksheet.
|
|
373
|
+
*
|
|
374
|
+
* - `"overlay"` — Places the watermark image as a DrawingML picture on top of cells.
|
|
375
|
+
* Visible on screen AND when printed. Supports transparency via `<a:alphaModFix>`.
|
|
376
|
+
* Users can move/delete the watermark unless the sheet is protected.
|
|
377
|
+
*
|
|
378
|
+
* - `"header"` — Places the watermark image in the page header using VML.
|
|
379
|
+
* Renders behind cell content. Visible in Page Layout view and Print Preview.
|
|
380
|
+
* Cannot be accidentally moved/deleted. The standard "true watermark" approach.
|
|
381
|
+
*/
|
|
382
|
+
export type WatermarkMode = "overlay" | "header";
|
|
383
|
+
/**
|
|
384
|
+
* Options for adding a watermark to a worksheet.
|
|
385
|
+
*
|
|
386
|
+
* @example Overlay watermark (visible on screen + prints):
|
|
387
|
+
* ```typescript
|
|
388
|
+
* const imgId = workbook.addImage({ buffer: pngData, extension: "png" });
|
|
389
|
+
* worksheet.addWatermark({
|
|
390
|
+
* imageId: imgId,
|
|
391
|
+
* mode: "overlay",
|
|
392
|
+
* opacity: 0.15
|
|
393
|
+
* });
|
|
394
|
+
* ```
|
|
395
|
+
*
|
|
396
|
+
* @example Header watermark (behind content, prints correctly):
|
|
397
|
+
* ```typescript
|
|
398
|
+
* const imgId = workbook.addImage({ buffer: pngData, extension: "png" });
|
|
399
|
+
* worksheet.addWatermark({
|
|
400
|
+
* imageId: imgId,
|
|
401
|
+
* mode: "header"
|
|
402
|
+
* });
|
|
403
|
+
* ```
|
|
404
|
+
*/
|
|
405
|
+
export interface WatermarkOptions {
|
|
406
|
+
/** Image ID obtained from `workbook.addImage()`. */
|
|
407
|
+
imageId: string | number;
|
|
408
|
+
/**
|
|
409
|
+
* Watermark placement mode.
|
|
410
|
+
* @default "overlay"
|
|
411
|
+
*/
|
|
412
|
+
mode?: WatermarkMode;
|
|
413
|
+
/**
|
|
414
|
+
* Opacity for overlay mode (0 = fully transparent, 1 = fully opaque).
|
|
415
|
+
* Expressed as a percentage in OOXML (e.g. 0.15 = 15000 out of 100000).
|
|
416
|
+
* Only applies to `"overlay"` mode. In `"header"` mode, transparency
|
|
417
|
+
* must be baked into the image itself (use a PNG with alpha channel).
|
|
418
|
+
* @default 0.15
|
|
419
|
+
*/
|
|
420
|
+
opacity?: number;
|
|
421
|
+
/**
|
|
422
|
+
* Image width in points (for "header" mode VML rendering).
|
|
423
|
+
* @default 467.25
|
|
424
|
+
*/
|
|
425
|
+
headerWidth?: number;
|
|
426
|
+
/**
|
|
427
|
+
* Image height in points (for "header" mode VML rendering).
|
|
428
|
+
* @default 311.25
|
|
429
|
+
*/
|
|
430
|
+
headerHeight?: number;
|
|
431
|
+
/**
|
|
432
|
+
* Which header sections to apply the watermark to (only for "header" mode).
|
|
433
|
+
*
|
|
434
|
+
* - `"all"` — applies to oddHeader, evenHeader, and firstHeader
|
|
435
|
+
* - `"odd"` — applies only to oddHeader (standard pages)
|
|
436
|
+
* - `"even"` — applies only to evenHeader
|
|
437
|
+
* - `"first"` — applies only to firstHeader
|
|
438
|
+
*
|
|
439
|
+
* @default "all"
|
|
440
|
+
*/
|
|
441
|
+
applyTo?: "all" | "odd" | "even" | "first";
|
|
442
|
+
}
|
|
371
443
|
export type Location = {
|
|
372
444
|
top: number;
|
|
373
445
|
left: number;
|
|
@@ -513,6 +513,35 @@ function formatNumberPattern(val, fmt) {
|
|
|
513
513
|
const decimalPlaces = decFmt.replace(/[^0#?]/g, "").length;
|
|
514
514
|
// Round the value
|
|
515
515
|
const roundedVal = roundTo(scaledVal, decimalPlaces);
|
|
516
|
+
// When value is zero and the format has no required '0' digit placeholders,
|
|
517
|
+
// '?' placeholders become spaces and '#' placeholders produce nothing.
|
|
518
|
+
// This handles accounting format zero sections like "-"?? → "- " (dash + spaces).
|
|
519
|
+
if (roundedVal === 0 && !intFmt.includes("0") && !decFmt.includes("0")) {
|
|
520
|
+
let result = "";
|
|
521
|
+
for (const ch of intFmt) {
|
|
522
|
+
if (ch === "?") {
|
|
523
|
+
result += " ";
|
|
524
|
+
}
|
|
525
|
+
else if (ch !== "#" && ch !== ",") {
|
|
526
|
+
// Preserve literal characters (already unquoted at this point)
|
|
527
|
+
result += ch;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
if (decimalPlaces > 0) {
|
|
531
|
+
// Only emit the decimal point if the decimal format has '?' or '0' placeholders.
|
|
532
|
+
// Pure '#' decimal digits produce nothing for zero values.
|
|
533
|
+
const hasDecContent = /[0?]/.test(decFmt);
|
|
534
|
+
if (hasDecContent) {
|
|
535
|
+
result += ".";
|
|
536
|
+
for (const ch of decFmt) {
|
|
537
|
+
if (ch === "?") {
|
|
538
|
+
result += " ";
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return sign + result;
|
|
544
|
+
}
|
|
516
545
|
// Split into integer and decimal parts
|
|
517
546
|
const [intPart, decPart = ""] = roundedVal.toString().split(".");
|
|
518
547
|
// Check if format has literal characters mixed with digit placeholders (like "0-0", "000-0000")
|
|
@@ -552,16 +581,49 @@ function formatNumberPattern(val, fmt) {
|
|
|
552
581
|
if (intFmt.includes(",")) {
|
|
553
582
|
formattedInt = commaify(intPart);
|
|
554
583
|
}
|
|
555
|
-
// Pad integer with leading zeros if needed
|
|
584
|
+
// Pad integer with leading zeros/spaces if needed
|
|
585
|
+
// '0' placeholder → pad with "0", '?' placeholder → pad with " "
|
|
556
586
|
const minIntDigits = (intFmt.match(/0/g) ?? []).length;
|
|
587
|
+
const totalIntSlots = (intFmt.match(/[0?]/g) ?? []).length;
|
|
557
588
|
if (formattedInt.length < minIntDigits) {
|
|
558
589
|
formattedInt = "0".repeat(minIntDigits - formattedInt.length) + formattedInt;
|
|
559
590
|
}
|
|
591
|
+
if (formattedInt.length < totalIntSlots) {
|
|
592
|
+
formattedInt = " ".repeat(totalIntSlots - formattedInt.length) + formattedInt;
|
|
593
|
+
}
|
|
594
|
+
// '#' integer placeholder: suppress "0" when there are no required '0' or '?' digits
|
|
595
|
+
// and the integer value is zero (e.g. "#" format with value 0 → empty)
|
|
596
|
+
if (formattedInt === "0" && minIntDigits === 0 && totalIntSlots === 0) {
|
|
597
|
+
formattedInt = "";
|
|
598
|
+
}
|
|
560
599
|
}
|
|
561
600
|
// Format decimal part
|
|
562
601
|
let formattedDec = "";
|
|
563
602
|
if (decimalPlaces > 0) {
|
|
564
|
-
|
|
603
|
+
const rawDec = (decPart + "0".repeat(decimalPlaces)).substring(0, decimalPlaces);
|
|
604
|
+
// Process each decimal digit position according to its placeholder:
|
|
605
|
+
// '0' → always show digit, '?' → show digit or space, '#' → show digit or nothing (trim trailing)
|
|
606
|
+
const decChars = rawDec.split("");
|
|
607
|
+
// Walk from the end: '#' trailing zeros are removed, '?' trailing zeros become spaces
|
|
608
|
+
for (let i = decFmt.length - 1; i >= 0; i--) {
|
|
609
|
+
if (i >= decChars.length) {
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
if (decFmt[i] === "#" && decChars[i] === "0") {
|
|
613
|
+
decChars[i] = "";
|
|
614
|
+
}
|
|
615
|
+
else if (decFmt[i] === "?" && decChars[i] === "0") {
|
|
616
|
+
decChars[i] = " ";
|
|
617
|
+
}
|
|
618
|
+
else {
|
|
619
|
+
break; // stop at first non-zero or '0' placeholder
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
const decStr = decChars.join("");
|
|
623
|
+
// Only emit decimal point if there is content after it
|
|
624
|
+
if (decStr.length > 0) {
|
|
625
|
+
formattedDec = "." + decStr;
|
|
626
|
+
}
|
|
565
627
|
}
|
|
566
628
|
return sign + formattedInt + formattedDec;
|
|
567
629
|
}
|
|
@@ -12,6 +12,8 @@ interface DrawingAnchor {
|
|
|
12
12
|
tooltip?: string;
|
|
13
13
|
rId: string;
|
|
14
14
|
};
|
|
15
|
+
/** Alpha modulation for transparency (OOXML percentage, e.g. 15000 = 15%). */
|
|
16
|
+
alphaModFix?: number;
|
|
15
17
|
};
|
|
16
18
|
range: any;
|
|
17
19
|
}
|
|
@@ -32,6 +34,8 @@ interface ImageMedium {
|
|
|
32
34
|
hyperlink?: string;
|
|
33
35
|
tooltip?: string;
|
|
34
36
|
};
|
|
37
|
+
/** Opacity 0-1 for watermark overlay mode. */
|
|
38
|
+
opacity?: number;
|
|
35
39
|
}
|
|
36
40
|
/**
|
|
37
41
|
* Resolves a media filename into the drawing-level relative target path.
|