@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
|
@@ -8,12 +8,13 @@
|
|
|
8
8
|
* It is used internally by the public `pdf()` and `excelToPdf()` APIs.
|
|
9
9
|
*/
|
|
10
10
|
import { PdfWriter } from "../core/pdf-writer.js";
|
|
11
|
+
import { PdfContentStream } from "../core/pdf-stream.js";
|
|
11
12
|
import { PdfDict, pdfRef, pdfNumber, pdfString as pdfStr } from "../core/pdf-object.js";
|
|
12
13
|
import { FontManager, resolvePdfFontName } from "../font/font-manager.js";
|
|
13
14
|
import { parseTtf } from "../font/ttf-parser.js";
|
|
14
15
|
import { initEncryption } from "../core/encryption.js";
|
|
15
16
|
import { layoutSheet } from "./layout-engine.js";
|
|
16
|
-
import { renderPage, alphaGsName } from "./page-renderer.js";
|
|
17
|
+
import { renderPage, alphaGsName, renderWatermark, parseImageDimensions } from "./page-renderer.js";
|
|
17
18
|
import { decodePng } from "./png-decoder.js";
|
|
18
19
|
import { PdfError, PdfRenderError } from "../errors.js";
|
|
19
20
|
import { PageSizes } from "../types.js";
|
|
@@ -83,8 +84,21 @@ async function finishExport(ctx, workbook, options) {
|
|
|
83
84
|
ensureAtLeastOnePage(allPages, documentOptions, sheets);
|
|
84
85
|
fixPageNumbers(allPages);
|
|
85
86
|
trackFontsForHeaders(allPages, fontManager);
|
|
87
|
+
// Track watermark fonts
|
|
88
|
+
const watermark = documentOptions.watermark;
|
|
89
|
+
if (watermark && watermark.type === "text") {
|
|
90
|
+
const wmFontFamily = watermark.fontFamily ?? "Helvetica";
|
|
91
|
+
const wmBold = watermark.bold ?? false;
|
|
92
|
+
const wmItalic = watermark.italic ?? false;
|
|
93
|
+
if (fontManager.hasEmbeddedFont()) {
|
|
94
|
+
fontManager.trackText(watermark.text);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
fontManager.ensureFont(resolvePdfFontName(wmFontFamily, wmBold, wmItalic));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
86
100
|
const fontObjectMap = fontManager.writeFontResources(writer);
|
|
87
|
-
const { pageObjNums, sheetFirstPage, pagesTreeObjNum } = await renderAllPages(allPages, fontManager, writer, fontObjectMap);
|
|
101
|
+
const { pageObjNums, sheetFirstPage, pagesTreeObjNum } = await renderAllPages(allPages, fontManager, writer, fontObjectMap, watermark);
|
|
88
102
|
return buildFinalPdf(writer, pageObjNums, pagesTreeObjNum, sheetFirstPage, documentOptions, workbook, options);
|
|
89
103
|
}
|
|
90
104
|
function ensureAtLeastOnePage(allPages, documentOptions, sheets) {
|
|
@@ -133,20 +147,20 @@ function trackFontsForHeaders(allPages, fontManager) {
|
|
|
133
147
|
}
|
|
134
148
|
}
|
|
135
149
|
}
|
|
136
|
-
async function renderAllPages(allPages, fontManager, writer, fontObjectMap) {
|
|
150
|
+
async function renderAllPages(allPages, fontManager, writer, fontObjectMap, watermark) {
|
|
137
151
|
const pageObjNums = [];
|
|
138
152
|
const pagesTreeObjNum = writer.allocObject();
|
|
139
153
|
const sheetFirstPage = new Map();
|
|
140
154
|
const totalPages = allPages.length;
|
|
141
155
|
for (let i = 0; i < allPages.length; i++) {
|
|
142
|
-
renderSinglePage(allPages[i], fontManager, writer, fontObjectMap, totalPages, pageObjNums, pagesTreeObjNum, sheetFirstPage);
|
|
156
|
+
renderSinglePage(allPages[i], fontManager, writer, fontObjectMap, totalPages, pageObjNums, pagesTreeObjNum, sheetFirstPage, watermark);
|
|
143
157
|
if (i < allPages.length - 1) {
|
|
144
158
|
await yieldToEventLoop();
|
|
145
159
|
}
|
|
146
160
|
}
|
|
147
161
|
return { pageObjNums, sheetFirstPage, pagesTreeObjNum };
|
|
148
162
|
}
|
|
149
|
-
function renderSinglePage(page, fontManager, writer, fontObjectMap, totalPages, pageObjNums, pagesTreeObjNum, sheetFirstPage) {
|
|
163
|
+
function renderSinglePage(page, fontManager, writer, fontObjectMap, totalPages, pageObjNums, pagesTreeObjNum, sheetFirstPage, watermark) {
|
|
150
164
|
try {
|
|
151
165
|
const { stream: contentStream, alphaValues } = renderPage(page, page.options, fontManager, totalPages);
|
|
152
166
|
// Handle images: create XObject Image entries and draw them
|
|
@@ -160,10 +174,46 @@ function renderSinglePage(page, fontManager, writer, fontObjectMap, totalPages,
|
|
|
160
174
|
contentStream.drawImage(imgName, img.rect.x, img.rect.y, img.rect.width, img.rect.height);
|
|
161
175
|
}
|
|
162
176
|
}
|
|
163
|
-
//
|
|
177
|
+
// --- Render watermark into a separate content stream ---
|
|
178
|
+
// PDF supports Contents as an array of stream references. The watermark stream
|
|
179
|
+
// is placed BEFORE the main content stream so it renders behind everything.
|
|
180
|
+
let watermarkContentObjNum;
|
|
181
|
+
const shouldApplyWatermark = watermark && isWatermarkApplicable(watermark, page);
|
|
182
|
+
if (shouldApplyWatermark) {
|
|
183
|
+
const wmContentStream = new PdfContentStream();
|
|
184
|
+
const wmResult = renderWatermark(wmContentStream, page, watermark, fontManager);
|
|
185
|
+
// Register watermark alpha values in the shared set
|
|
186
|
+
for (const alpha of wmResult.alphaValues) {
|
|
187
|
+
alphaValues.add(alpha);
|
|
188
|
+
}
|
|
189
|
+
// Register watermark image XObjects
|
|
190
|
+
for (const wmImg of wmResult.imageXObjects) {
|
|
191
|
+
const imgObjNum = writeImageXObject(writer, wmImg.data, wmImg.format);
|
|
192
|
+
imageXObjects.set(wmImg.name, imgObjNum);
|
|
193
|
+
}
|
|
194
|
+
// Write watermark content stream object
|
|
195
|
+
watermarkContentObjNum = writer.allocObject();
|
|
196
|
+
writer.addStreamObject(watermarkContentObjNum, new PdfDict(), wmContentStream);
|
|
197
|
+
}
|
|
198
|
+
// Add main content stream object
|
|
164
199
|
const contentObjNum = writer.allocObject();
|
|
165
|
-
|
|
166
|
-
|
|
200
|
+
writer.addStreamObject(contentObjNum, new PdfDict(), contentStream);
|
|
201
|
+
// Build Contents reference — array if watermark exists, single ref otherwise.
|
|
202
|
+
// placement "under" (default): watermark stream first, then content
|
|
203
|
+
// placement "over": content first, then watermark stream on top
|
|
204
|
+
let contentsRef;
|
|
205
|
+
if (watermarkContentObjNum) {
|
|
206
|
+
const placement = watermark?.placement ?? "under";
|
|
207
|
+
if (placement === "over") {
|
|
208
|
+
contentsRef = `[${pdfRef(contentObjNum)} ${pdfRef(watermarkContentObjNum)}]`;
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
contentsRef = `[${pdfRef(watermarkContentObjNum)} ${pdfRef(contentObjNum)}]`;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
contentsRef = pdfRef(contentObjNum);
|
|
216
|
+
}
|
|
167
217
|
// Add resources dictionary object
|
|
168
218
|
const resourcesObjNum = writer.allocObject();
|
|
169
219
|
const fontDictStr = fontManager.buildFontDictString(fontObjectMap);
|
|
@@ -212,7 +262,7 @@ function renderSinglePage(page, fontManager, writer, fontObjectMap, totalPages,
|
|
|
212
262
|
parentRef: pagesTreeObjNum,
|
|
213
263
|
width: page.width,
|
|
214
264
|
height: page.height,
|
|
215
|
-
contentsRef:
|
|
265
|
+
contentsRef: contentsRef,
|
|
216
266
|
resourcesRef: resourcesObjNum,
|
|
217
267
|
annotRefs: annotRefs.length > 0 ? annotRefs : undefined
|
|
218
268
|
});
|
|
@@ -337,7 +387,8 @@ function resolveOptions(options, sheet) {
|
|
|
337
387
|
title: options?.title ?? "",
|
|
338
388
|
author: options?.author ?? "",
|
|
339
389
|
subject: options?.subject ?? "",
|
|
340
|
-
creator: options?.creator ?? "excelts"
|
|
390
|
+
creator: options?.creator ?? "excelts",
|
|
391
|
+
watermark: options?.watermark
|
|
341
392
|
};
|
|
342
393
|
}
|
|
343
394
|
/** Map PaperSize enum values to PDF page sizes. */
|
|
@@ -428,6 +479,28 @@ function buildOutlines(writer, sheetFirstPage, pageObjNums) {
|
|
|
428
479
|
return outlinesObjNum;
|
|
429
480
|
}
|
|
430
481
|
// =============================================================================
|
|
482
|
+
// Watermark Filtering
|
|
483
|
+
// =============================================================================
|
|
484
|
+
/**
|
|
485
|
+
* Check if a watermark should be applied to a specific page based on
|
|
486
|
+
* optional page number and sheet name filters.
|
|
487
|
+
*/
|
|
488
|
+
function isWatermarkApplicable(watermark, page) {
|
|
489
|
+
if (watermark.pages && watermark.pages.length > 0) {
|
|
490
|
+
if (!watermark.pages.includes(page.pageNumber)) {
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
if (watermark.sheets && watermark.sheets.length > 0) {
|
|
495
|
+
// Case-insensitive sheet name matching, consistent with the rest of the API
|
|
496
|
+
const sheetLower = page.sheetName.toLowerCase();
|
|
497
|
+
if (!watermark.sheets.some(s => s.toLowerCase() === sheetLower)) {
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return true;
|
|
502
|
+
}
|
|
503
|
+
// =============================================================================
|
|
431
504
|
// Image XObject
|
|
432
505
|
// =============================================================================
|
|
433
506
|
/**
|
|
@@ -444,7 +517,7 @@ function writeImageXObject(writer, data, format) {
|
|
|
444
517
|
*/
|
|
445
518
|
function writeJpegImageXObject(writer, data) {
|
|
446
519
|
const objNum = writer.allocObject();
|
|
447
|
-
const dims =
|
|
520
|
+
const dims = parseImageDimensions(data, "jpeg");
|
|
448
521
|
const dict = new PdfDict()
|
|
449
522
|
.set("Type", "/XObject")
|
|
450
523
|
.set("Subtype", "/Image")
|
|
@@ -486,39 +559,3 @@ function writePngImageXObject(writer, data) {
|
|
|
486
559
|
writer.addStreamObject(objNum, dict, png.pixels);
|
|
487
560
|
return objNum;
|
|
488
561
|
}
|
|
489
|
-
/**
|
|
490
|
-
* Extract width and height from a JPEG file header.
|
|
491
|
-
* Scans for SOF markers (SOF0-SOF3, SOF5-SOF7, SOF9-SOF11, SOF13-SOF15)
|
|
492
|
-
* which all share the same frame header layout.
|
|
493
|
-
* Handles 0xFF padding bytes per JPEG spec.
|
|
494
|
-
*/
|
|
495
|
-
function getJpegDimensions(data) {
|
|
496
|
-
let offset = 2; // skip SOI marker (0xFFD8)
|
|
497
|
-
while (offset < data.length - 1) {
|
|
498
|
-
// Skip any 0xFF fill bytes
|
|
499
|
-
while (offset < data.length && data[offset] === 0xff && data[offset + 1] === 0xff) {
|
|
500
|
-
offset++;
|
|
501
|
-
}
|
|
502
|
-
if (offset >= data.length - 1 || data[offset] !== 0xff) {
|
|
503
|
-
break;
|
|
504
|
-
}
|
|
505
|
-
const marker = data[offset + 1];
|
|
506
|
-
// SOF markers: C0-C3, C5-C7, C9-CB, CD-CF (excluding C4=DHT, C8=JPG, CC=DAC)
|
|
507
|
-
const isSof = marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc;
|
|
508
|
-
if (isSof) {
|
|
509
|
-
if (offset + 8 < data.length) {
|
|
510
|
-
const height = (data[offset + 5] << 8) | data[offset + 6];
|
|
511
|
-
const width = (data[offset + 7] << 8) | data[offset + 8];
|
|
512
|
-
return { width, height };
|
|
513
|
-
}
|
|
514
|
-
break;
|
|
515
|
-
}
|
|
516
|
-
// Skip segment: 2 byte marker + segment length (includes the 2 length bytes)
|
|
517
|
-
if (offset + 3 >= data.length) {
|
|
518
|
-
break;
|
|
519
|
-
}
|
|
520
|
-
const segLen = (data[offset + 2] << 8) | data[offset + 3];
|
|
521
|
-
offset += 2 + segLen;
|
|
522
|
-
}
|
|
523
|
-
return { width: 1, height: 1 };
|
|
524
|
-
}
|
|
@@ -339,7 +339,241 @@ export interface PdfExportOptions {
|
|
|
339
339
|
printHighQuality: boolean;
|
|
340
340
|
}>;
|
|
341
341
|
};
|
|
342
|
+
/**
|
|
343
|
+
* Watermark to render on every page.
|
|
344
|
+
* Supports text watermarks (e.g. "CONFIDENTIAL") and image watermarks (e.g. company logo).
|
|
345
|
+
*
|
|
346
|
+
* @example Text watermark:
|
|
347
|
+
* ```typescript
|
|
348
|
+
* watermark: {
|
|
349
|
+
* type: "text",
|
|
350
|
+
* text: "DRAFT",
|
|
351
|
+
* opacity: 0.1,
|
|
352
|
+
* rotation: -45
|
|
353
|
+
* }
|
|
354
|
+
* ```
|
|
355
|
+
*
|
|
356
|
+
* @example Image watermark:
|
|
357
|
+
* ```typescript
|
|
358
|
+
* watermark: {
|
|
359
|
+
* type: "image",
|
|
360
|
+
* data: logoPng,
|
|
361
|
+
* format: "png",
|
|
362
|
+
* opacity: 0.08
|
|
363
|
+
* }
|
|
364
|
+
* ```
|
|
365
|
+
*/
|
|
366
|
+
watermark?: PdfWatermark;
|
|
342
367
|
}
|
|
368
|
+
/**
|
|
369
|
+
* Text watermark configuration for PDF export.
|
|
370
|
+
*
|
|
371
|
+
* Renders semi-transparent text (e.g. "CONFIDENTIAL", "DRAFT") on every page.
|
|
372
|
+
*
|
|
373
|
+
* @example
|
|
374
|
+
* ```typescript
|
|
375
|
+
* const bytes = await pdf(data, {
|
|
376
|
+
* watermark: {
|
|
377
|
+
* type: "text",
|
|
378
|
+
* text: "CONFIDENTIAL",
|
|
379
|
+
* color: { r: 0.8, g: 0, b: 0 },
|
|
380
|
+
* opacity: 0.1,
|
|
381
|
+
* rotation: -45
|
|
382
|
+
* }
|
|
383
|
+
* });
|
|
384
|
+
* ```
|
|
385
|
+
*/
|
|
386
|
+
export interface PdfTextWatermark {
|
|
387
|
+
type: "text";
|
|
388
|
+
/** The watermark text to display. */
|
|
389
|
+
text: string;
|
|
390
|
+
/**
|
|
391
|
+
* Font size in points.
|
|
392
|
+
* @default 54
|
|
393
|
+
*/
|
|
394
|
+
fontSize?: number;
|
|
395
|
+
/**
|
|
396
|
+
* Text color (RGB, each 0-1).
|
|
397
|
+
* @default { r: 0.75, g: 0.75, b: 0.75 }
|
|
398
|
+
*/
|
|
399
|
+
color?: PdfColor;
|
|
400
|
+
/**
|
|
401
|
+
* Opacity (0 = fully transparent, 1 = fully opaque).
|
|
402
|
+
* @default 0.15
|
|
403
|
+
*/
|
|
404
|
+
opacity?: number;
|
|
405
|
+
/**
|
|
406
|
+
* Rotation angle in degrees (positive = counter-clockwise).
|
|
407
|
+
* @default -45
|
|
408
|
+
*/
|
|
409
|
+
rotation?: number;
|
|
410
|
+
/**
|
|
411
|
+
* Font family name. Must be a standard PDF font (Type1) or the embedded font.
|
|
412
|
+
* @default "Helvetica"
|
|
413
|
+
*/
|
|
414
|
+
fontFamily?: string;
|
|
415
|
+
/**
|
|
416
|
+
* Whether to render in bold.
|
|
417
|
+
* @default false
|
|
418
|
+
*/
|
|
419
|
+
bold?: boolean;
|
|
420
|
+
/**
|
|
421
|
+
* Whether to render in italic.
|
|
422
|
+
* @default false
|
|
423
|
+
*/
|
|
424
|
+
italic?: boolean;
|
|
425
|
+
/**
|
|
426
|
+
* Position on the page. `"center"` places the watermark at the geometric center.
|
|
427
|
+
* A custom `{ x, y }` object specifies the **center point** of the watermark
|
|
428
|
+
* in PDF points (origin at bottom-left corner of the page).
|
|
429
|
+
* @default "center"
|
|
430
|
+
*/
|
|
431
|
+
position?: "center" | {
|
|
432
|
+
x: number;
|
|
433
|
+
y: number;
|
|
434
|
+
};
|
|
435
|
+
/**
|
|
436
|
+
* When true, the watermark text is tiled in a repeating grid across the entire page.
|
|
437
|
+
* @default false
|
|
438
|
+
*/
|
|
439
|
+
repeat?: boolean;
|
|
440
|
+
/**
|
|
441
|
+
* Horizontal spacing (in points) between repeated watermark tiles.
|
|
442
|
+
* Only used when `repeat` is true.
|
|
443
|
+
* @default 200
|
|
444
|
+
*/
|
|
445
|
+
repeatSpacingX?: number;
|
|
446
|
+
/**
|
|
447
|
+
* Vertical spacing (in points) between repeated watermark tiles.
|
|
448
|
+
* Only used when `repeat` is true.
|
|
449
|
+
* @default 200
|
|
450
|
+
*/
|
|
451
|
+
repeatSpacingY?: number;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Image watermark configuration for PDF export.
|
|
455
|
+
*
|
|
456
|
+
* Embeds a semi-transparent image (e.g. company logo) on every page.
|
|
457
|
+
*
|
|
458
|
+
* @example
|
|
459
|
+
* ```typescript
|
|
460
|
+
* import { readFileSync } from "fs";
|
|
461
|
+
* const logo = readFileSync("logo.png");
|
|
462
|
+
*
|
|
463
|
+
* const bytes = await pdf(data, {
|
|
464
|
+
* watermark: {
|
|
465
|
+
* type: "image",
|
|
466
|
+
* data: logo,
|
|
467
|
+
* format: "png",
|
|
468
|
+
* opacity: 0.08,
|
|
469
|
+
* scale: 0.4
|
|
470
|
+
* }
|
|
471
|
+
* });
|
|
472
|
+
* ```
|
|
473
|
+
*/
|
|
474
|
+
export interface PdfImageWatermark {
|
|
475
|
+
type: "image";
|
|
476
|
+
/** Raw image bytes (JPEG or PNG). */
|
|
477
|
+
data: Uint8Array;
|
|
478
|
+
/** Image format. */
|
|
479
|
+
format: "jpeg" | "png";
|
|
480
|
+
/**
|
|
481
|
+
* Opacity (0 = fully transparent, 1 = fully opaque).
|
|
482
|
+
* @default 0.15
|
|
483
|
+
*/
|
|
484
|
+
opacity?: number;
|
|
485
|
+
/**
|
|
486
|
+
* Rotation angle in degrees (positive = counter-clockwise).
|
|
487
|
+
* @default 0
|
|
488
|
+
*/
|
|
489
|
+
rotation?: number;
|
|
490
|
+
/**
|
|
491
|
+
* Scale factor relative to the page size.
|
|
492
|
+
* 0.5 means the image's largest dimension will be scaled to
|
|
493
|
+
* 50% of the smaller page dimension (width or height).
|
|
494
|
+
* Ignored when `width` and `height` are explicitly provided.
|
|
495
|
+
* @default 0.5
|
|
496
|
+
*/
|
|
497
|
+
scale?: number;
|
|
498
|
+
/**
|
|
499
|
+
* Explicit image width in PDF points. When set together with `height`,
|
|
500
|
+
* overrides `scale` and renders the image at the exact specified dimensions.
|
|
501
|
+
*/
|
|
502
|
+
width?: number;
|
|
503
|
+
/**
|
|
504
|
+
* Explicit image height in PDF points. When set together with `width`,
|
|
505
|
+
* overrides `scale` and renders the image at the exact specified dimensions.
|
|
506
|
+
*/
|
|
507
|
+
height?: number;
|
|
508
|
+
/**
|
|
509
|
+
* Position on the page. `"center"` places the watermark at the geometric center.
|
|
510
|
+
* A custom `{ x, y }` object specifies the **center point** of the watermark
|
|
511
|
+
* in PDF points (origin at bottom-left corner of the page).
|
|
512
|
+
* @default "center"
|
|
513
|
+
*/
|
|
514
|
+
position?: "center" | {
|
|
515
|
+
x: number;
|
|
516
|
+
y: number;
|
|
517
|
+
};
|
|
518
|
+
/**
|
|
519
|
+
* When true, the watermark image is tiled in a repeating grid across the entire page.
|
|
520
|
+
* @default false
|
|
521
|
+
*/
|
|
522
|
+
repeat?: boolean;
|
|
523
|
+
/**
|
|
524
|
+
* Horizontal spacing (in points) between repeated watermark tiles.
|
|
525
|
+
* Only used when `repeat` is true.
|
|
526
|
+
* @default 200
|
|
527
|
+
*/
|
|
528
|
+
repeatSpacingX?: number;
|
|
529
|
+
/**
|
|
530
|
+
* Vertical spacing (in points) between repeated watermark tiles.
|
|
531
|
+
* Only used when `repeat` is true.
|
|
532
|
+
* @default 200
|
|
533
|
+
*/
|
|
534
|
+
repeatSpacingY?: number;
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Common watermark filter and placement options shared by text and image watermarks.
|
|
538
|
+
*/
|
|
539
|
+
export interface PdfWatermarkFilter {
|
|
540
|
+
/**
|
|
541
|
+
* Restrict the watermark to specific page numbers (1-based, document-global).
|
|
542
|
+
* When set, only pages whose number is in this array get the watermark.
|
|
543
|
+
* If omitted, all pages receive the watermark.
|
|
544
|
+
*
|
|
545
|
+
* @example Only on the first page:
|
|
546
|
+
* ```typescript
|
|
547
|
+
* watermark: { type: "text", text: "COVER", pages: [1] }
|
|
548
|
+
* ```
|
|
549
|
+
*/
|
|
550
|
+
pages?: number[];
|
|
551
|
+
/**
|
|
552
|
+
* Restrict the watermark to specific sheet names (case-insensitive).
|
|
553
|
+
* When set, only pages belonging to the named sheets get the watermark.
|
|
554
|
+
* If omitted, all sheets receive the watermark.
|
|
555
|
+
*
|
|
556
|
+
* @example Only on the "Summary" sheet:
|
|
557
|
+
* ```typescript
|
|
558
|
+
* watermark: { type: "text", text: "DRAFT", sheets: ["Summary"] }
|
|
559
|
+
* ```
|
|
560
|
+
*/
|
|
561
|
+
sheets?: string[];
|
|
562
|
+
/**
|
|
563
|
+
* Watermark layering relative to page content.
|
|
564
|
+
*
|
|
565
|
+
* - `"under"` — watermark renders **behind** all page content including
|
|
566
|
+
* cell fills, borders, text, grid lines, headers, and footers (default)
|
|
567
|
+
* - `"over"` — watermark renders **on top of** all page content
|
|
568
|
+
*
|
|
569
|
+
* @default "under"
|
|
570
|
+
*/
|
|
571
|
+
placement?: "under" | "over";
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Watermark configuration — either text or image, with optional page/sheet filters.
|
|
575
|
+
*/
|
|
576
|
+
export type PdfWatermark = (PdfTextWatermark | PdfImageWatermark) & PdfWatermarkFilter;
|
|
343
577
|
/**
|
|
344
578
|
* Page margins in PDF points.
|
|
345
579
|
*/
|
|
@@ -369,6 +603,7 @@ export interface ResolvedPdfOptions {
|
|
|
369
603
|
author: string;
|
|
370
604
|
subject: string;
|
|
371
605
|
creator: string;
|
|
606
|
+
watermark?: PdfWatermark;
|
|
372
607
|
}
|
|
373
608
|
/**
|
|
374
609
|
* RGBA color used internally for PDF rendering.
|
package/dist/cjs/index.js
CHANGED
|
@@ -17,8 +17,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
17
17
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
18
18
|
};
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.
|
|
21
|
-
exports.MarkdownParseError = exports.MarkdownError = exports.MaxItemsExceededError = exports.ImageError = exports.TableError = exports.PivotTableError = exports.WorksheetNameError = exports.XmlParseError = exports.InvalidValueTypeError = exports.MergeConflictError = exports.RowOutOfBoundsError = exports.ColumnOutOfBoundsError = exports.InvalidAddressError = exports.ExcelStreamStateError = exports.ExcelNotSupportedError = exports.ExcelDownloadError = exports.ExcelFileError = exports.isExcelError = exports.ExcelError = exports.isPdfError = exports.PdfStructureError = exports.PdfFontError = void 0;
|
|
20
|
+
exports.PdfError = exports.PageSizes = exports.excelToPdf = exports.pdf = exports.uint8ArrayToString = exports.stringToUint8Array = exports.toUint8Array = exports.concatUint8Arrays = exports.getRootCause = exports.getErrorChain = exports.errorToJSON = exports.toError = exports.BaseError = exports.getSupportedFormats = exports.DateFormatter = exports.DateParser = exports.xmlDecode = exports.xmlEncode = exports.uint8ArrayToBase64 = exports.base64ToUint8Array = exports.excelToDate = exports.dateToExcel = exports.encodeRange = exports.decodeRange = exports.encodeCell = exports.decodeCell = exports.encodeRow = exports.decodeRow = exports.encodeCol = exports.decodeCol = exports.DefinedNames = exports.createCsvFormatterStream = exports.createCsvParserStream = exports.CsvFormatterStream = exports.CsvParserStream = exports.createTextWatermarkImage = exports.WorksheetReader = exports.WorksheetWriter = exports.WorkbookReader = exports.WorkbookWriter = exports.FormCheckbox = exports.DataValidations = exports.Table = exports.Image = exports.Range = exports.Cell = exports.Column = exports.Row = exports.Worksheet = exports.Workbook = void 0;
|
|
21
|
+
exports.MarkdownParseError = exports.MarkdownError = exports.MaxItemsExceededError = exports.ImageError = exports.TableError = exports.PivotTableError = exports.WorksheetNameError = exports.XmlParseError = exports.InvalidValueTypeError = exports.MergeConflictError = exports.RowOutOfBoundsError = exports.ColumnOutOfBoundsError = exports.InvalidAddressError = exports.ExcelStreamStateError = exports.ExcelNotSupportedError = exports.ExcelDownloadError = exports.ExcelFileError = exports.isExcelError = exports.ExcelError = exports.isPdfError = exports.PdfStructureError = exports.PdfFontError = exports.PdfRenderError = void 0;
|
|
22
22
|
var workbook_1 = require("./modules/excel/workbook.js");
|
|
23
23
|
Object.defineProperty(exports, "Workbook", { enumerable: true, get: function () { return workbook_1.Workbook; } });
|
|
24
24
|
var worksheet_1 = require("./modules/excel/worksheet.js");
|
|
@@ -61,6 +61,9 @@ __exportStar(require("./modules/excel/enums.js"), exports);
|
|
|
61
61
|
// =============================================================================
|
|
62
62
|
// Export all type definitions from types.ts
|
|
63
63
|
__exportStar(require("./modules/excel/types.js"), exports);
|
|
64
|
+
// Watermark image generator utility
|
|
65
|
+
var watermark_image_1 = require("./modules/excel/utils/watermark-image.js");
|
|
66
|
+
Object.defineProperty(exports, "createTextWatermarkImage", { enumerable: true, get: function () { return watermark_image_1.createTextWatermarkImage; } });
|
|
64
67
|
var stream_1 = require("./modules/csv/stream/index.js");
|
|
65
68
|
Object.defineProperty(exports, "CsvParserStream", { enumerable: true, get: function () { return stream_1.CsvParserStream; } });
|
|
66
69
|
Object.defineProperty(exports, "CsvFormatterStream", { enumerable: true, get: function () { return stream_1.CsvFormatterStream; } });
|
|
@@ -18,6 +18,20 @@ class Image {
|
|
|
18
18
|
type: this.type,
|
|
19
19
|
imageId: this.imageId ?? ""
|
|
20
20
|
};
|
|
21
|
+
case "watermark":
|
|
22
|
+
return {
|
|
23
|
+
type: this.type,
|
|
24
|
+
imageId: this.imageId ?? "",
|
|
25
|
+
opacity: this.opacity
|
|
26
|
+
};
|
|
27
|
+
case "headerImage":
|
|
28
|
+
return {
|
|
29
|
+
type: this.type,
|
|
30
|
+
imageId: this.imageId ?? "",
|
|
31
|
+
headerWidth: this.headerWidth,
|
|
32
|
+
headerHeight: this.headerHeight,
|
|
33
|
+
applyTo: this.applyTo
|
|
34
|
+
};
|
|
21
35
|
case "image": {
|
|
22
36
|
const range = this.range;
|
|
23
37
|
if (!range) {
|
|
@@ -39,9 +53,13 @@ class Image {
|
|
|
39
53
|
throw new errors_1.ImageError("Invalid Image Type");
|
|
40
54
|
}
|
|
41
55
|
}
|
|
42
|
-
set model({ type, imageId, range, hyperlinks }) {
|
|
56
|
+
set model({ type, imageId, range, hyperlinks, opacity, headerWidth, headerHeight, applyTo }) {
|
|
43
57
|
this.type = type;
|
|
44
58
|
this.imageId = imageId;
|
|
59
|
+
this.opacity = opacity;
|
|
60
|
+
this.headerWidth = headerWidth;
|
|
61
|
+
this.headerHeight = headerHeight;
|
|
62
|
+
this.applyTo = applyTo;
|
|
45
63
|
if (type === "image") {
|
|
46
64
|
if (typeof range === "string") {
|
|
47
65
|
const decoded = col_cache_1.colCache.decode(range);
|
|
@@ -70,6 +88,10 @@ class Image {
|
|
|
70
88
|
const cloned = new Image(target);
|
|
71
89
|
cloned.type = this.type;
|
|
72
90
|
cloned.imageId = this.imageId;
|
|
91
|
+
cloned.opacity = this.opacity;
|
|
92
|
+
cloned.headerWidth = this.headerWidth;
|
|
93
|
+
cloned.headerHeight = this.headerHeight;
|
|
94
|
+
cloned.applyTo = this.applyTo;
|
|
73
95
|
if (this.range) {
|
|
74
96
|
cloned.range = {
|
|
75
97
|
tl: this.range.tl.clone(target),
|
|
@@ -154,6 +154,8 @@ class WorksheetWriter {
|
|
|
154
154
|
// auto filter
|
|
155
155
|
this.autoFilter = options.autoFilter ?? null;
|
|
156
156
|
this._media = [];
|
|
157
|
+
// watermark
|
|
158
|
+
this._watermark = null;
|
|
157
159
|
// worksheet protection
|
|
158
160
|
this.sheetProtection = null;
|
|
159
161
|
// start writing to stream now
|
|
@@ -451,6 +453,57 @@ class WorksheetWriter {
|
|
|
451
453
|
getImages() {
|
|
452
454
|
return this._media;
|
|
453
455
|
}
|
|
456
|
+
// =========================================================================
|
|
457
|
+
// Watermark
|
|
458
|
+
/**
|
|
459
|
+
* Add a watermark to the worksheet using an image from `WorkbookWriter.addImage()`.
|
|
460
|
+
* Supports overlay mode (DrawingML with transparency) and header mode (VML behind content).
|
|
461
|
+
*/
|
|
462
|
+
addWatermark(options) {
|
|
463
|
+
// Remove existing watermark entries (both stored type tags)
|
|
464
|
+
this._media = this._media.filter(m => m._watermarkTag !== true);
|
|
465
|
+
const opacity = options.opacity !== undefined ? Math.max(0, Math.min(1, options.opacity)) : 0.15;
|
|
466
|
+
this._watermark = {
|
|
467
|
+
imageId: String(options.imageId),
|
|
468
|
+
mode: options.mode ?? "overlay",
|
|
469
|
+
opacity,
|
|
470
|
+
headerWidth: options.headerWidth,
|
|
471
|
+
headerHeight: options.headerHeight,
|
|
472
|
+
applyTo: options.applyTo
|
|
473
|
+
};
|
|
474
|
+
if (this._watermark.mode === "overlay") {
|
|
475
|
+
// Coverage range is computed lazily during commit() via _resolveWatermarkRange()
|
|
476
|
+
const entry = {
|
|
477
|
+
type: "image",
|
|
478
|
+
imageId: String(options.imageId),
|
|
479
|
+
range: {
|
|
480
|
+
tl: { nativeCol: 0, nativeColOff: 0, nativeRow: 0, nativeRowOff: 0 },
|
|
481
|
+
br: { nativeCol: 100, nativeColOff: 0, nativeRow: 200, nativeRowOff: 0 },
|
|
482
|
+
editAs: "absolute"
|
|
483
|
+
},
|
|
484
|
+
// Internal tag for dedup — not part of the WriterImageModel type
|
|
485
|
+
_watermarkTag: true,
|
|
486
|
+
opacity
|
|
487
|
+
};
|
|
488
|
+
this._media.push(entry);
|
|
489
|
+
}
|
|
490
|
+
// Note: header mode for streaming writer is limited — the VML file generation
|
|
491
|
+
// happens in WorkbookWriter.addWorksheets(), which handles worksheet.headerImage.
|
|
492
|
+
// We store the config in _watermark and it's picked up by the commit path.
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Get the current watermark configuration.
|
|
496
|
+
*/
|
|
497
|
+
getWatermark() {
|
|
498
|
+
return this._watermark;
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Remove the watermark from the worksheet.
|
|
502
|
+
*/
|
|
503
|
+
removeWatermark() {
|
|
504
|
+
this._watermark = null;
|
|
505
|
+
this._media = this._media.filter(m => m._watermarkTag !== true);
|
|
506
|
+
}
|
|
454
507
|
/**
|
|
455
508
|
* Parse the user-supplied range into a normalised internal model
|
|
456
509
|
* mirroring what the regular Worksheet / Image class does.
|
|
@@ -629,6 +682,21 @@ class WorksheetWriter {
|
|
|
629
682
|
if (this._media.length === 0) {
|
|
630
683
|
return;
|
|
631
684
|
}
|
|
685
|
+
// Resolve watermark coverage range from actual worksheet dimensions
|
|
686
|
+
// (at commit time, all rows have been flushed so _dimensions is accurate)
|
|
687
|
+
for (const entry of this._media) {
|
|
688
|
+
if (entry._watermarkTag) {
|
|
689
|
+
const dims = this._dimensions.model;
|
|
690
|
+
const maxCol = dims ? Math.max(dims.right ?? 100, 100) : 100;
|
|
691
|
+
const maxRow = dims ? Math.max(dims.bottom ?? 200, 200) : 200;
|
|
692
|
+
entry.range.br = {
|
|
693
|
+
nativeCol: maxCol,
|
|
694
|
+
nativeColOff: 0,
|
|
695
|
+
nativeRow: maxRow,
|
|
696
|
+
nativeRowOff: 0
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
}
|
|
632
700
|
// Build the drawing model from the stored images.
|
|
633
701
|
// The drawing XML will be generated later by WorkbookWriterBase.addDrawings().
|
|
634
702
|
const drawingName = `drawing${this.id}`;
|
|
@@ -69,6 +69,11 @@ function buildDrawingAnchorsAndRels(media, existingRels, options) {
|
|
|
69
69
|
},
|
|
70
70
|
range: medium.range
|
|
71
71
|
};
|
|
72
|
+
// Pass through watermark opacity as alphaModFix
|
|
73
|
+
if (medium.opacity !== undefined) {
|
|
74
|
+
const clamped = Math.max(0, Math.min(1, medium.opacity));
|
|
75
|
+
anchor.picture.alphaModFix = Math.round(clamped * 100000);
|
|
76
|
+
}
|
|
72
77
|
// Handle image hyperlinks
|
|
73
78
|
if (medium.hyperlinks && medium.hyperlinks.hyperlink) {
|
|
74
79
|
const rIdHyperlink = options.nextRId(rels);
|