@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.
- 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/image.d.ts +27 -2
- package/dist/browser/modules/excel/image.js +23 -1
- 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/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/index.d.ts +1 -1
- package/dist/browser/modules/pdf/render/page-renderer.d.ts +29 -1
- package/dist/browser/modules/pdf/render/page-renderer.js +394 -25
- package/dist/browser/modules/pdf/render/pdf-exporter.js +84 -47
- package/dist/browser/modules/pdf/types.d.ts +235 -0
- package/dist/cjs/index.js +5 -2
- package/dist/cjs/modules/excel/image.js +23 -1
- package/dist/cjs/modules/excel/stream/worksheet-writer.js +68 -0
- 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/render/page-renderer.js +396 -25
- package/dist/cjs/modules/pdf/render/pdf-exporter.js +83 -46
- package/dist/esm/index.browser.js +2 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/modules/excel/image.js +23 -1
- package/dist/esm/modules/excel/stream/worksheet-writer.js +68 -0
- 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/render/page-renderer.js +394 -25
- package/dist/esm/modules/pdf/render/pdf-exporter.js +84 -47
- package/dist/iife/excelts.iife.js +2390 -469
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +47 -47
- 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/index.d.ts +1 -1
- package/dist/types/modules/pdf/render/page-renderer.d.ts +29 -1
- package/dist/types/modules/pdf/types.d.ts +235 -0
- package/package.json +1 -1
|
@@ -5,6 +5,8 @@ import { FormCheckbox } from "../../../form-control.js";
|
|
|
5
5
|
class VmlDrawingXform extends BaseXform {
|
|
6
6
|
constructor() {
|
|
7
7
|
super();
|
|
8
|
+
// Internal state for parsing header image shapes
|
|
9
|
+
this._parsingHeaderImage = false;
|
|
8
10
|
this.map = {
|
|
9
11
|
"v:shape": new VmlShapeXform()
|
|
10
12
|
};
|
|
@@ -20,8 +22,10 @@ class VmlDrawingXform extends BaseXform {
|
|
|
20
22
|
const renderModel = (model || this.model);
|
|
21
23
|
const comments = renderModel.comments;
|
|
22
24
|
const formControls = renderModel.formControls;
|
|
25
|
+
const headerImage = renderModel.headerImage;
|
|
23
26
|
const hasComments = comments && comments.length > 0;
|
|
24
27
|
const hasFormControls = formControls && formControls.length > 0;
|
|
28
|
+
const hasHeaderImage = !!headerImage;
|
|
25
29
|
xmlStream.openXml(StdDocAttributes);
|
|
26
30
|
xmlStream.openNode(this.tag, VmlDrawingXform.DRAWING_ATTRIBUTES);
|
|
27
31
|
// Shape layout - shared by both notes and form controls
|
|
@@ -59,6 +63,40 @@ class VmlDrawingXform extends BaseXform {
|
|
|
59
63
|
xmlStream.leafNode("o:lock", { "v:ext": "edit", shapetype: "t" });
|
|
60
64
|
xmlStream.closeNode();
|
|
61
65
|
}
|
|
66
|
+
// Shapetype 75 for header/footer image (watermark)
|
|
67
|
+
if (hasHeaderImage) {
|
|
68
|
+
xmlStream.openNode("v:shapetype", {
|
|
69
|
+
id: "_x0000_t75",
|
|
70
|
+
coordsize: "21600,21600",
|
|
71
|
+
"o:spt": "75",
|
|
72
|
+
"o:preferrelative": "t",
|
|
73
|
+
path: "m@4@5l@4@11@9@11@9@5xe",
|
|
74
|
+
filled: "f",
|
|
75
|
+
stroked: "f"
|
|
76
|
+
});
|
|
77
|
+
xmlStream.leafNode("v:stroke", { joinstyle: "miter" });
|
|
78
|
+
xmlStream.openNode("v:formulas");
|
|
79
|
+
xmlStream.leafNode("v:f", { eqn: "if lineDrawn pixelLineWidth 0" });
|
|
80
|
+
xmlStream.leafNode("v:f", { eqn: "sum @0 1 0" });
|
|
81
|
+
xmlStream.leafNode("v:f", { eqn: "sum 0 0 @1" });
|
|
82
|
+
xmlStream.leafNode("v:f", { eqn: "prod @2 1 2" });
|
|
83
|
+
xmlStream.leafNode("v:f", { eqn: "prod @3 21600 pixelWidth" });
|
|
84
|
+
xmlStream.leafNode("v:f", { eqn: "prod @3 21600 pixelHeight" });
|
|
85
|
+
xmlStream.leafNode("v:f", { eqn: "sum @0 0 1" });
|
|
86
|
+
xmlStream.leafNode("v:f", { eqn: "prod @6 1 2" });
|
|
87
|
+
xmlStream.leafNode("v:f", { eqn: "prod @7 21600 pixelWidth" });
|
|
88
|
+
xmlStream.leafNode("v:f", { eqn: "sum @8 21600 0" });
|
|
89
|
+
xmlStream.leafNode("v:f", { eqn: "prod @7 21600 pixelHeight" });
|
|
90
|
+
xmlStream.leafNode("v:f", { eqn: "sum @10 21600 0" });
|
|
91
|
+
xmlStream.closeNode(); // v:formulas
|
|
92
|
+
xmlStream.leafNode("v:path", {
|
|
93
|
+
"o:extrusionok": "f",
|
|
94
|
+
gradientshapeok: "t",
|
|
95
|
+
"o:connecttype": "rect"
|
|
96
|
+
});
|
|
97
|
+
xmlStream.leafNode("o:lock", { "v:ext": "edit", aspectratio: "t" });
|
|
98
|
+
xmlStream.closeNode(); // v:shapetype
|
|
99
|
+
}
|
|
62
100
|
// Render comment shapes
|
|
63
101
|
if (hasComments) {
|
|
64
102
|
for (let i = 0; i < comments.length; i++) {
|
|
@@ -71,8 +109,32 @@ class VmlDrawingXform extends BaseXform {
|
|
|
71
109
|
this._renderCheckboxShape(xmlStream, control);
|
|
72
110
|
}
|
|
73
111
|
}
|
|
112
|
+
// Render header/footer image shape
|
|
113
|
+
if (hasHeaderImage) {
|
|
114
|
+
this._renderHeaderImageShape(xmlStream, headerImage);
|
|
115
|
+
}
|
|
74
116
|
xmlStream.closeNode();
|
|
75
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Render a header/footer image shape for watermark
|
|
120
|
+
*/
|
|
121
|
+
_renderHeaderImageShape(xmlStream, headerImage) {
|
|
122
|
+
const width = headerImage.width ?? 467.25;
|
|
123
|
+
const height = headerImage.height ?? 311.25;
|
|
124
|
+
// CH = Center Header, used by Excel for center-positioned header images
|
|
125
|
+
xmlStream.openNode("v:shape", {
|
|
126
|
+
id: "CH",
|
|
127
|
+
"o:spid": "_x0000_s2049",
|
|
128
|
+
type: "#_x0000_t75",
|
|
129
|
+
style: `position:absolute;margin-left:0;margin-top:0;width:${width}pt;height:${height}pt;z-index:1`
|
|
130
|
+
});
|
|
131
|
+
xmlStream.leafNode("v:imagedata", {
|
|
132
|
+
"o:relid": headerImage.imageRelId,
|
|
133
|
+
"o:title": "watermark"
|
|
134
|
+
});
|
|
135
|
+
xmlStream.leafNode("o:lock", { "v:ext": "edit", rotation: "t" });
|
|
136
|
+
xmlStream.closeNode(); // v:shape
|
|
137
|
+
}
|
|
76
138
|
/**
|
|
77
139
|
* Render a checkbox form control shape
|
|
78
140
|
*/
|
|
@@ -141,7 +203,7 @@ class VmlDrawingXform extends BaseXform {
|
|
|
141
203
|
xmlStream.closeNode(); // x:ClientData
|
|
142
204
|
xmlStream.closeNode(); // v:shape
|
|
143
205
|
}
|
|
144
|
-
// Parsing - delegate to VmlShapeXform for notes
|
|
206
|
+
// Parsing - delegate to VmlShapeXform for notes, handle header images directly
|
|
145
207
|
parseOpen(node) {
|
|
146
208
|
if (this.parser) {
|
|
147
209
|
this.parser.parseOpen(node);
|
|
@@ -155,10 +217,34 @@ class VmlDrawingXform extends BaseXform {
|
|
|
155
217
|
formControls: []
|
|
156
218
|
};
|
|
157
219
|
break;
|
|
220
|
+
case "v:shape":
|
|
221
|
+
// Check if this is a header image shape (type="#_x0000_t75")
|
|
222
|
+
if (node.attributes.type === "#_x0000_t75") {
|
|
223
|
+
this._parsingHeaderImage = true;
|
|
224
|
+
// Extract width/height from style
|
|
225
|
+
const style = node.attributes.style || "";
|
|
226
|
+
const widthMatch = /width:([0-9.]+)pt/.exec(style);
|
|
227
|
+
const heightMatch = /height:([0-9.]+)pt/.exec(style);
|
|
228
|
+
this._headerImageWidth = widthMatch ? parseFloat(widthMatch[1]) : undefined;
|
|
229
|
+
this._headerImageHeight = heightMatch ? parseFloat(heightMatch[1]) : undefined;
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
// Regular shape — delegate to VmlShapeXform (comments)
|
|
233
|
+
this.parser = this.map[node.name];
|
|
234
|
+
if (this.parser) {
|
|
235
|
+
this.parser.parseOpen(node);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
break;
|
|
158
239
|
default:
|
|
159
|
-
this.
|
|
160
|
-
|
|
161
|
-
|
|
240
|
+
if (this._parsingHeaderImage && node.name === "v:imagedata") {
|
|
241
|
+
this._headerImageRelId = node.attributes["o:relid"];
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
this.parser = this.map[node.name];
|
|
245
|
+
if (this.parser) {
|
|
246
|
+
this.parser.parseOpen(node);
|
|
247
|
+
}
|
|
162
248
|
}
|
|
163
249
|
break;
|
|
164
250
|
}
|
|
@@ -178,6 +264,19 @@ class VmlDrawingXform extends BaseXform {
|
|
|
178
264
|
return true;
|
|
179
265
|
}
|
|
180
266
|
switch (name) {
|
|
267
|
+
case "v:shape":
|
|
268
|
+
if (this._parsingHeaderImage && this._headerImageRelId) {
|
|
269
|
+
this.model.headerImage = {
|
|
270
|
+
imageRelId: this._headerImageRelId,
|
|
271
|
+
width: this._headerImageWidth,
|
|
272
|
+
height: this._headerImageHeight
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
this._parsingHeaderImage = false;
|
|
276
|
+
this._headerImageRelId = undefined;
|
|
277
|
+
this._headerImageWidth = undefined;
|
|
278
|
+
this._headerImageHeight = undefined;
|
|
279
|
+
return true;
|
|
181
280
|
case this.tag:
|
|
182
281
|
return false;
|
|
183
282
|
default:
|
|
@@ -26,7 +26,7 @@ import { ColBreaksXform } from "./col-breaks-xform.js";
|
|
|
26
26
|
import { HeaderFooterXform } from "./header-footer-xform.js";
|
|
27
27
|
import { ConditionalFormattingsXform } from "./cf/conditional-formattings-xform.js";
|
|
28
28
|
import { ExtLstXform } from "./ext-lst-xform.js";
|
|
29
|
-
import { commentsRelTargetFromWorksheet, ctrlPropRelTargetFromWorksheet, drawingRelTargetFromWorksheet, pivotTableRelTargetFromWorksheet, tableRelTargetFromWorksheet, vmlDrawingRelTargetFromWorksheet } from "../../../utils/ooxml-paths.js";
|
|
29
|
+
import { commentsRelTargetFromWorksheet, ctrlPropRelTargetFromWorksheet, drawingRelTargetFromWorksheet, pivotTableRelTargetFromWorksheet, tableRelTargetFromWorksheet, vmlDrawingRelTargetFromWorksheet, vmlDrawingHFRelTargetFromWorksheet } from "../../../utils/ooxml-paths.js";
|
|
30
30
|
import { buildDrawingAnchorsAndRels, resolveMediaTarget } from "../../../utils/drawing-utils.js";
|
|
31
31
|
const mergeRule = (rule, extRule) => {
|
|
32
32
|
Object.keys(extRule).forEach(key => {
|
|
@@ -232,6 +232,8 @@ class WorkSheetXform extends BaseXform {
|
|
|
232
232
|
// Process background and image media entries
|
|
233
233
|
const backgroundMedia = [];
|
|
234
234
|
const imageMedia = [];
|
|
235
|
+
const watermarkMedia = [];
|
|
236
|
+
const headerImageMedia = [];
|
|
235
237
|
model.media.forEach(medium => {
|
|
236
238
|
if (medium.type === "background") {
|
|
237
239
|
backgroundMedia.push(medium);
|
|
@@ -239,6 +241,12 @@ class WorkSheetXform extends BaseXform {
|
|
|
239
241
|
else if (medium.type === "image") {
|
|
240
242
|
imageMedia.push(medium);
|
|
241
243
|
}
|
|
244
|
+
else if (medium.type === "watermark") {
|
|
245
|
+
watermarkMedia.push(medium);
|
|
246
|
+
}
|
|
247
|
+
else if (medium.type === "headerImage") {
|
|
248
|
+
headerImageMedia.push(medium);
|
|
249
|
+
}
|
|
242
250
|
});
|
|
243
251
|
// Handle background images
|
|
244
252
|
backgroundMedia.forEach(medium => {
|
|
@@ -276,6 +284,107 @@ class WorkSheetXform extends BaseXform {
|
|
|
276
284
|
drawing.anchors.push(...result.anchors);
|
|
277
285
|
drawing.rels = result.rels;
|
|
278
286
|
}
|
|
287
|
+
// Handle watermark overlay images — placed as a full-sheet drawing with transparency
|
|
288
|
+
if (watermarkMedia.length > 0) {
|
|
289
|
+
let { drawing } = model;
|
|
290
|
+
if (!drawing) {
|
|
291
|
+
drawing = model.drawing = {
|
|
292
|
+
rId: nextRid(rels),
|
|
293
|
+
name: `drawing${++options.drawingsCount}`,
|
|
294
|
+
anchors: [],
|
|
295
|
+
rels: []
|
|
296
|
+
};
|
|
297
|
+
options.drawings.push(drawing);
|
|
298
|
+
rels.push({
|
|
299
|
+
Id: drawing.rId,
|
|
300
|
+
Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
|
|
301
|
+
Target: drawingRelTargetFromWorksheet(drawing.name)
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
for (const medium of watermarkMedia) {
|
|
305
|
+
const bookImage = options.media[medium.imageId];
|
|
306
|
+
if (!bookImage) {
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
const rIdImage = nextRid(drawing.rels);
|
|
310
|
+
drawing.rels.push({
|
|
311
|
+
Id: rIdImage,
|
|
312
|
+
Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
|
|
313
|
+
Target: resolveMediaTarget(bookImage)
|
|
314
|
+
});
|
|
315
|
+
// Convert opacity (0-1) to OOXML percentage (0-100000), clamped
|
|
316
|
+
const rawOpacity = medium.opacity !== undefined ? medium.opacity : 0.15;
|
|
317
|
+
const clampedOpacity = Math.max(0, Math.min(1, rawOpacity));
|
|
318
|
+
const alphaModFix = Math.round(clampedOpacity * 100000);
|
|
319
|
+
// Compute coverage based on actual worksheet dimensions.
|
|
320
|
+
// Use the model's dimensions if available, otherwise use generous defaults.
|
|
321
|
+
const dims = model.dimensions;
|
|
322
|
+
const maxCol = dims ? Math.max(dims.model?.right ?? 100, 100) : 100;
|
|
323
|
+
const maxRow = dims ? Math.max(dims.model?.bottom ?? 200, 200) : 200;
|
|
324
|
+
drawing.anchors.push({
|
|
325
|
+
picture: {
|
|
326
|
+
rId: rIdImage,
|
|
327
|
+
alphaModFix
|
|
328
|
+
},
|
|
329
|
+
// Cover the full data area with extra margin
|
|
330
|
+
range: {
|
|
331
|
+
editAs: "absolute",
|
|
332
|
+
tl: { nativeCol: 0, nativeColOff: 0, nativeRow: 0, nativeRowOff: 0 },
|
|
333
|
+
br: { nativeCol: maxCol, nativeColOff: 0, nativeRow: maxRow, nativeRowOff: 0 }
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
// Handle header watermark images — VML header/footer image
|
|
339
|
+
if (headerImageMedia.length > 0) {
|
|
340
|
+
const medium = headerImageMedia[0]; // Only one header image per sheet
|
|
341
|
+
const bookImage = options.media[medium.imageId];
|
|
342
|
+
if (bookImage) {
|
|
343
|
+
const rIdVml = nextRid(rels);
|
|
344
|
+
rels.push({
|
|
345
|
+
Id: rIdVml,
|
|
346
|
+
Type: RelType.VmlDrawing,
|
|
347
|
+
Target: vmlDrawingHFRelTargetFromWorksheet(fileIndex)
|
|
348
|
+
});
|
|
349
|
+
// Store header image info on the model for the VML writer and worksheet render
|
|
350
|
+
model.headerImage = {
|
|
351
|
+
vmlRelId: rIdVml,
|
|
352
|
+
imageId: medium.imageId,
|
|
353
|
+
bookImage,
|
|
354
|
+
headerWidth: medium.headerWidth,
|
|
355
|
+
headerHeight: medium.headerHeight
|
|
356
|
+
};
|
|
357
|
+
// Flag for content-types registration
|
|
358
|
+
options.hasHeaderWatermark = true;
|
|
359
|
+
// Update headerFooter to include &G placeholder.
|
|
360
|
+
// Respects the applyTo option: "all" (default), "odd", "even", "first".
|
|
361
|
+
if (!model.headerFooter) {
|
|
362
|
+
model.headerFooter = {};
|
|
363
|
+
}
|
|
364
|
+
const applyTo = medium.applyTo || "all";
|
|
365
|
+
const insertG = (field) => {
|
|
366
|
+
const existing = model.headerFooter[field] || "";
|
|
367
|
+
if (existing.includes("&G")) {
|
|
368
|
+
return existing;
|
|
369
|
+
}
|
|
370
|
+
if (existing.includes("&C")) {
|
|
371
|
+
return existing.replace("&C", "&C&G");
|
|
372
|
+
}
|
|
373
|
+
return existing + "&C&G";
|
|
374
|
+
};
|
|
375
|
+
if (applyTo === "all" || applyTo === "odd") {
|
|
376
|
+
model.headerFooter.oddHeader = insertG("oddHeader");
|
|
377
|
+
}
|
|
378
|
+
if (applyTo === "all" || applyTo === "even") {
|
|
379
|
+
model.headerFooter.evenHeader = insertG("evenHeader");
|
|
380
|
+
model.headerFooter.differentOddEven = true;
|
|
381
|
+
}
|
|
382
|
+
if (applyTo === "all" || applyTo === "first") {
|
|
383
|
+
model.headerFooter.firstHeader = insertG("firstHeader");
|
|
384
|
+
model.headerFooter.differentFirst = true;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
279
388
|
// prepare tables
|
|
280
389
|
model.tables.forEach(table => {
|
|
281
390
|
// relationships
|
|
@@ -436,10 +545,18 @@ class WorkSheetXform extends BaseXform {
|
|
|
436
545
|
// NOTE: Excel is picky about worksheet child element order; legacyDrawing must come before controls.
|
|
437
546
|
model.rels.forEach(rel => {
|
|
438
547
|
if (rel.Type === RelType.VmlDrawing) {
|
|
548
|
+
// Skip VML rels that are for header images (they use legacyDrawingHF instead)
|
|
549
|
+
if (model.headerImage && rel.Id === model.headerImage.vmlRelId) {
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
439
552
|
xmlStream.leafNode("legacyDrawing", { "r:id": rel.Id });
|
|
440
553
|
}
|
|
441
554
|
});
|
|
442
555
|
}
|
|
556
|
+
// legacyDrawingHF — VML drawing for header/footer images (watermark in header mode)
|
|
557
|
+
if (model.headerImage) {
|
|
558
|
+
xmlStream.leafNode("legacyDrawingHF", { "r:id": model.headerImage.vmlRelId });
|
|
559
|
+
}
|
|
443
560
|
// Controls section for legacy form controls (checkboxes, etc.)
|
|
444
561
|
// Excel expects <controls> entries that reference ctrlProp relationships.
|
|
445
562
|
if (model.formControls && model.formControls.length > 0) {
|
|
@@ -655,13 +772,23 @@ class WorkSheetXform extends BaseXform {
|
|
|
655
772
|
// Also extract images to model.media for backward compatibility
|
|
656
773
|
drawing.anchors.forEach(anchor => {
|
|
657
774
|
if (anchor.medium) {
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
775
|
+
// Detect overlay watermarks: drawings that carry alphaModFix
|
|
776
|
+
const hasAlpha = anchor.medium.alphaModFix !== undefined && anchor.medium.alphaModFix < 100000;
|
|
777
|
+
if (hasAlpha) {
|
|
778
|
+
model.media.push({
|
|
779
|
+
type: "watermark",
|
|
780
|
+
imageId: anchor.medium.index,
|
|
781
|
+
opacity: anchor.medium.alphaModFix / 100000
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
else {
|
|
785
|
+
model.media.push({
|
|
786
|
+
type: "image",
|
|
787
|
+
imageId: anchor.medium.index,
|
|
788
|
+
range: anchor.range,
|
|
789
|
+
hyperlinks: anchor.picture.hyperlinks
|
|
790
|
+
});
|
|
791
|
+
}
|
|
665
792
|
}
|
|
666
793
|
});
|
|
667
794
|
}
|
|
@@ -35,7 +35,7 @@ import { StreamingZip, ZipDeflateFile } from "../../archive/zip/stream.js";
|
|
|
35
35
|
import { ZipParser } from "../../archive/unzip/zip-parser.js";
|
|
36
36
|
import { PassThrough } from "../../stream/index.js";
|
|
37
37
|
import { concatUint8Arrays } from "../../../utils/binary.js";
|
|
38
|
-
import { commentsPath, commentsRelTargetFromWorksheetName, ctrlPropPath, drawingPath, drawingRelsPath, OOXML_REL_TARGETS, pivotTableRelTargetFromWorksheetName, pivotCacheDefinitionRelTargetFromWorkbook, getCommentsIndexFromPath, getDrawingNameFromPath, getDrawingNameFromRelsPath, getMediaFilenameFromPath, mediaPath, getPivotCacheDefinitionNameFromPath, getPivotCacheDefinitionNameFromRelsPath, getPivotCacheRecordsNameFromPath, getPivotTableNameFromPath, getPivotTableNameFromRelsPath, pivotCacheDefinitionPath, pivotCacheDefinitionRelsPath, pivotCacheDefinitionRelTargetFromPivotTable, pivotCacheRecordsPath, pivotCacheRecordsRelTarget, pivotTablePath, pivotTableRelsPath, getTableNameFromPath, tablePath, tableRelTargetFromWorksheetName, themePath, getThemeNameFromPath, getVmlDrawingNameFromPath, getWorksheetNoFromWorksheetPath, getWorksheetNoFromWorksheetRelsPath, isBinaryEntryPath, normalizeZipPath, OOXML_PATHS, vmlDrawingRelTargetFromWorksheetName, vmlDrawingPath, worksheetPath, worksheetRelsPath, worksheetRelTarget } from "../utils/ooxml-paths.js";
|
|
38
|
+
import { commentsPath, commentsRelTargetFromWorksheetName, ctrlPropPath, drawingPath, drawingRelsPath, OOXML_REL_TARGETS, pivotTableRelTargetFromWorksheetName, pivotCacheDefinitionRelTargetFromWorkbook, getCommentsIndexFromPath, getDrawingNameFromPath, getDrawingNameFromRelsPath, getMediaFilenameFromPath, mediaPath, getPivotCacheDefinitionNameFromPath, getPivotCacheDefinitionNameFromRelsPath, getPivotCacheRecordsNameFromPath, getPivotTableNameFromPath, getPivotTableNameFromRelsPath, pivotCacheDefinitionPath, pivotCacheDefinitionRelsPath, pivotCacheDefinitionRelTargetFromPivotTable, pivotCacheRecordsPath, pivotCacheRecordsRelTarget, pivotTablePath, pivotTableRelsPath, getTableNameFromPath, tablePath, tableRelTargetFromWorksheetName, themePath, getThemeNameFromPath, getVmlDrawingNameFromPath, getVmlDrawingHFNameFromPath, getWorksheetNoFromWorksheetPath, getWorksheetNoFromWorksheetRelsPath, isBinaryEntryPath, normalizeZipPath, OOXML_PATHS, vmlDrawingRelTargetFromWorksheetName, vmlDrawingPath, vmlDrawingHFPath, vmlDrawingHFRelsPath, worksheetPath, worksheetRelsPath, worksheetRelTarget } from "../utils/ooxml-paths.js";
|
|
39
39
|
import { filterDrawingAnchors } from "../utils/drawing-utils.js";
|
|
40
40
|
import { PassthroughManager } from "../utils/passthrough-manager.js";
|
|
41
41
|
class StreamingZipWriterAdapter {
|
|
@@ -906,6 +906,17 @@ class XLSX {
|
|
|
906
906
|
const vmlDrawing = await xform.parseStream(entry);
|
|
907
907
|
model.vmlDrawings[vmlDrawingRelTargetFromWorksheetName(name)] = vmlDrawing;
|
|
908
908
|
}
|
|
909
|
+
async _processVmlDrawingHFEntry(entry, model, _name) {
|
|
910
|
+
const xform = new VmlDrawingXform();
|
|
911
|
+
const vmlDrawing = await xform.parseStream(entry);
|
|
912
|
+
// Store parsed header image info for reconciliation
|
|
913
|
+
if (vmlDrawing && vmlDrawing.headerImage) {
|
|
914
|
+
if (!model.vmlDrawingHF) {
|
|
915
|
+
model.vmlDrawingHF = {};
|
|
916
|
+
}
|
|
917
|
+
model.vmlDrawingHF[_name] = vmlDrawing.headerImage;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
909
920
|
async _processThemeEntry(stream, model, name) {
|
|
910
921
|
await new Promise((resolve, reject) => {
|
|
911
922
|
const streamBuf = this.createStreamBuf();
|
|
@@ -1020,6 +1031,13 @@ class XLSX {
|
|
|
1020
1031
|
await this._processVmlDrawingEntry(stream, model, vmlDrawingName);
|
|
1021
1032
|
return true;
|
|
1022
1033
|
}
|
|
1034
|
+
// VML header/footer drawings (watermark in header mode).
|
|
1035
|
+
// Parse to extract header image info for round-trip preservation.
|
|
1036
|
+
const vmlHFName = getVmlDrawingHFNameFromPath(entryName);
|
|
1037
|
+
if (vmlHFName) {
|
|
1038
|
+
await this._processVmlDrawingHFEntry(stream, model, vmlHFName);
|
|
1039
|
+
return true;
|
|
1040
|
+
}
|
|
1023
1041
|
const commentsIndex = getCommentsIndexFromPath(entryName);
|
|
1024
1042
|
if (commentsIndex) {
|
|
1025
1043
|
await this._processCommentEntry(stream, model, `comments${commentsIndex}`);
|
|
@@ -1226,6 +1244,35 @@ class XLSX {
|
|
|
1226
1244
|
formControls: hasFormControls ? worksheet.formControls : []
|
|
1227
1245
|
});
|
|
1228
1246
|
}
|
|
1247
|
+
// Generate VML drawing for header/footer images (watermark in header mode)
|
|
1248
|
+
if (worksheet.headerImage) {
|
|
1249
|
+
const hdrImage = worksheet.headerImage;
|
|
1250
|
+
const bookImage = hdrImage.bookImage;
|
|
1251
|
+
const imageFileName = bookImage.name &&
|
|
1252
|
+
bookImage.extension &&
|
|
1253
|
+
bookImage.name.endsWith(`.${bookImage.extension}`)
|
|
1254
|
+
? bookImage.name
|
|
1255
|
+
: `${bookImage.name}.${bookImage.extension}`;
|
|
1256
|
+
const imageRelTarget = `../media/${imageFileName}`;
|
|
1257
|
+
// Write the VML file for the header image
|
|
1258
|
+
await this._renderToZip(zip, vmlDrawingHFPath(fileIndex), vmlDrawingXform, {
|
|
1259
|
+
comments: [],
|
|
1260
|
+
formControls: [],
|
|
1261
|
+
headerImage: {
|
|
1262
|
+
imageRelId: "rId1",
|
|
1263
|
+
width: hdrImage.headerWidth,
|
|
1264
|
+
height: hdrImage.headerHeight
|
|
1265
|
+
}
|
|
1266
|
+
});
|
|
1267
|
+
// Write the VML rels file referencing the image
|
|
1268
|
+
await this._renderToZip(zip, vmlDrawingHFRelsPath(fileIndex), relationshipsXform, [
|
|
1269
|
+
{
|
|
1270
|
+
Id: "rId1",
|
|
1271
|
+
Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
|
|
1272
|
+
Target: imageRelTarget
|
|
1273
|
+
}
|
|
1274
|
+
]);
|
|
1275
|
+
}
|
|
1229
1276
|
// Generate ctrlProp files for form controls
|
|
1230
1277
|
if (hasFormControls) {
|
|
1231
1278
|
for (const control of worksheet.formControls) {
|
|
@@ -1372,6 +1419,7 @@ class XLSX {
|
|
|
1372
1419
|
worksheetOptions.drawings = model.drawings = [];
|
|
1373
1420
|
worksheetOptions.commentRefs = model.commentRefs = [];
|
|
1374
1421
|
worksheetOptions.formControlRefs = model.formControlRefs = [];
|
|
1422
|
+
model.hasHeaderWatermark = false;
|
|
1375
1423
|
let tableCount = 0;
|
|
1376
1424
|
model.tables = [];
|
|
1377
1425
|
model.worksheets.forEach((worksheet, index) => {
|
|
@@ -1390,6 +1438,10 @@ class XLSX {
|
|
|
1390
1438
|
});
|
|
1391
1439
|
// ContentTypesXform expects this flag
|
|
1392
1440
|
model.hasCheckboxes = model.styles.hasCheckboxes;
|
|
1441
|
+
// Propagate header watermark flag from worksheet prepare options
|
|
1442
|
+
if (worksheetOptions.hasHeaderWatermark) {
|
|
1443
|
+
model.hasHeaderWatermark = true;
|
|
1444
|
+
}
|
|
1393
1445
|
// Build passthroughContentTypes for ContentTypesXform using PassthroughManager
|
|
1394
1446
|
const passthrough = model.passthrough || {};
|
|
1395
1447
|
const passthroughManager = new PassthroughManager();
|
|
@@ -129,11 +129,12 @@ export class PdfWriter {
|
|
|
129
129
|
addPage(options) {
|
|
130
130
|
const objNum = this.allocObject();
|
|
131
131
|
const mediaBox = `[0 0 ${pdfNumber(options.width)} ${pdfNumber(options.height)}]`;
|
|
132
|
+
const contentsValue = typeof options.contentsRef === "string" ? options.contentsRef : pdfRef(options.contentsRef);
|
|
132
133
|
const dict = new PdfDict()
|
|
133
134
|
.set("Type", "/Page")
|
|
134
135
|
.set("Parent", pdfRef(options.parentRef))
|
|
135
136
|
.set("MediaBox", mediaBox)
|
|
136
|
-
.set("Contents",
|
|
137
|
+
.set("Contents", contentsValue)
|
|
137
138
|
.set("Resources", pdfRef(options.resourcesRef));
|
|
138
139
|
if (options.annotRefs && options.annotRefs.length > 0) {
|
|
139
140
|
dict.set("Annots", "[" + options.annotRefs.map(r => pdfRef(r)).join(" ") + "]");
|