@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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* @cj-tech-master/excelts v9.
|
|
2
|
+
* @cj-tech-master/excelts v9.1.0
|
|
3
3
|
* Zero-dependency TypeScript toolkit — Excel (XLSX), PDF, CSV, Markdown, XML, ZIP/TAR, and streaming.
|
|
4
4
|
* (c) 2026 cjnoname
|
|
5
5
|
* Released under the MIT License
|
|
@@ -2590,6 +2590,18 @@ var ExcelTS = (function(exports) {
|
|
|
2590
2590
|
type: this.type,
|
|
2591
2591
|
imageId: this.imageId ?? ""
|
|
2592
2592
|
};
|
|
2593
|
+
case "watermark": return {
|
|
2594
|
+
type: this.type,
|
|
2595
|
+
imageId: this.imageId ?? "",
|
|
2596
|
+
opacity: this.opacity
|
|
2597
|
+
};
|
|
2598
|
+
case "headerImage": return {
|
|
2599
|
+
type: this.type,
|
|
2600
|
+
imageId: this.imageId ?? "",
|
|
2601
|
+
headerWidth: this.headerWidth,
|
|
2602
|
+
headerHeight: this.headerHeight,
|
|
2603
|
+
applyTo: this.applyTo
|
|
2604
|
+
};
|
|
2593
2605
|
case "image": {
|
|
2594
2606
|
const range = this.range;
|
|
2595
2607
|
if (!range) throw new ImageError("Image has no range");
|
|
@@ -2608,9 +2620,13 @@ var ExcelTS = (function(exports) {
|
|
|
2608
2620
|
default: throw new ImageError("Invalid Image Type");
|
|
2609
2621
|
}
|
|
2610
2622
|
}
|
|
2611
|
-
set model({ type, imageId, range, hyperlinks }) {
|
|
2623
|
+
set model({ type, imageId, range, hyperlinks, opacity, headerWidth, headerHeight, applyTo }) {
|
|
2612
2624
|
this.type = type;
|
|
2613
2625
|
this.imageId = imageId;
|
|
2626
|
+
this.opacity = opacity;
|
|
2627
|
+
this.headerWidth = headerWidth;
|
|
2628
|
+
this.headerHeight = headerHeight;
|
|
2629
|
+
this.applyTo = applyTo;
|
|
2614
2630
|
if (type === "image") {
|
|
2615
2631
|
if (typeof range === "string") {
|
|
2616
2632
|
const decoded = colCache.decode(range);
|
|
@@ -2639,6 +2655,10 @@ var ExcelTS = (function(exports) {
|
|
|
2639
2655
|
const cloned = new Image(target);
|
|
2640
2656
|
cloned.type = this.type;
|
|
2641
2657
|
cloned.imageId = this.imageId;
|
|
2658
|
+
cloned.opacity = this.opacity;
|
|
2659
|
+
cloned.headerWidth = this.headerWidth;
|
|
2660
|
+
cloned.headerHeight = this.headerHeight;
|
|
2661
|
+
cloned.applyTo = this.applyTo;
|
|
2642
2662
|
if (this.range) cloned.range = {
|
|
2643
2663
|
tl: this.range.tl.clone(target),
|
|
2644
2664
|
br: this.range.br ? this.range.br.clone(target) : void 0,
|
|
@@ -9693,6 +9713,7 @@ var ExcelTS = (function(exports) {
|
|
|
9693
9713
|
this.pivotTables = [];
|
|
9694
9714
|
this.conditionalFormattings = [];
|
|
9695
9715
|
this.formControls = [];
|
|
9716
|
+
this._watermark = null;
|
|
9696
9717
|
}
|
|
9697
9718
|
get name() {
|
|
9698
9719
|
return this._name;
|
|
@@ -10276,6 +10297,73 @@ var ExcelTS = (function(exports) {
|
|
|
10276
10297
|
return image && image.imageId;
|
|
10277
10298
|
}
|
|
10278
10299
|
/**
|
|
10300
|
+
* Add a watermark to the worksheet using an image from `workbook.addImage()`.
|
|
10301
|
+
*
|
|
10302
|
+
* The watermark can be placed in one of two modes:
|
|
10303
|
+
*
|
|
10304
|
+
* - **overlay** (default): Places the watermark image as a drawing on top of cells.
|
|
10305
|
+
* Visible on screen AND when printed. Supports transparency via DrawingML `alphaModFix`.
|
|
10306
|
+
*
|
|
10307
|
+
* - **header**: Places the watermark image in the page header using VML.
|
|
10308
|
+
* Visible in Page Layout view and when printed. Renders behind cell content.
|
|
10309
|
+
* Transparency must be baked into the image (PNG with alpha channel).
|
|
10310
|
+
*
|
|
10311
|
+
* @param options - Watermark configuration
|
|
10312
|
+
*
|
|
10313
|
+
* @example Overlay watermark with transparency:
|
|
10314
|
+
* ```typescript
|
|
10315
|
+
* const imgId = workbook.addImage({ buffer: pngData, extension: "png" });
|
|
10316
|
+
* worksheet.addWatermark({ imageId: imgId, opacity: 0.15 });
|
|
10317
|
+
* ```
|
|
10318
|
+
*
|
|
10319
|
+
* @example Header watermark (behind content):
|
|
10320
|
+
* ```typescript
|
|
10321
|
+
* const imgId = workbook.addImage({ buffer: pngData, extension: "png" });
|
|
10322
|
+
* worksheet.addWatermark({ imageId: imgId, mode: "header" });
|
|
10323
|
+
* ```
|
|
10324
|
+
*/
|
|
10325
|
+
addWatermark(options) {
|
|
10326
|
+
this._media = this._media.filter((m) => m.type !== "watermark" && m.type !== "headerImage");
|
|
10327
|
+
this._watermark = {
|
|
10328
|
+
imageId: String(options.imageId),
|
|
10329
|
+
mode: options.mode ?? "overlay",
|
|
10330
|
+
opacity: options.opacity,
|
|
10331
|
+
headerWidth: options.headerWidth,
|
|
10332
|
+
headerHeight: options.headerHeight,
|
|
10333
|
+
applyTo: options.applyTo
|
|
10334
|
+
};
|
|
10335
|
+
if (this._watermark.mode === "overlay") {
|
|
10336
|
+
const model = {
|
|
10337
|
+
type: "watermark",
|
|
10338
|
+
imageId: String(options.imageId),
|
|
10339
|
+
opacity: options.opacity
|
|
10340
|
+
};
|
|
10341
|
+
this._media.push(new Image(this, model));
|
|
10342
|
+
} else {
|
|
10343
|
+
const model = {
|
|
10344
|
+
type: "headerImage",
|
|
10345
|
+
imageId: String(options.imageId),
|
|
10346
|
+
headerWidth: options.headerWidth,
|
|
10347
|
+
headerHeight: options.headerHeight,
|
|
10348
|
+
applyTo: options.applyTo
|
|
10349
|
+
};
|
|
10350
|
+
this._media.push(new Image(this, model));
|
|
10351
|
+
}
|
|
10352
|
+
}
|
|
10353
|
+
/**
|
|
10354
|
+
* Get the current watermark configuration, or null if none is set.
|
|
10355
|
+
*/
|
|
10356
|
+
getWatermark() {
|
|
10357
|
+
return this._watermark;
|
|
10358
|
+
}
|
|
10359
|
+
/**
|
|
10360
|
+
* Remove the watermark from the worksheet.
|
|
10361
|
+
*/
|
|
10362
|
+
removeWatermark() {
|
|
10363
|
+
this._watermark = null;
|
|
10364
|
+
this._media = this._media.filter((m) => m.type !== "watermark" && m.type !== "headerImage");
|
|
10365
|
+
}
|
|
10366
|
+
/**
|
|
10279
10367
|
* Add a form control checkbox to the worksheet.
|
|
10280
10368
|
*
|
|
10281
10369
|
* Form control checkboxes are the legacy style that work in Office 2007+,
|
|
@@ -10511,6 +10599,7 @@ var ExcelTS = (function(exports) {
|
|
|
10511
10599
|
pivotTables: this.pivotTables,
|
|
10512
10600
|
conditionalFormattings: this.conditionalFormattings,
|
|
10513
10601
|
formControls: this.formControls.map((fc) => fc.model),
|
|
10602
|
+
watermark: this._watermark,
|
|
10514
10603
|
drawing: this._drawing
|
|
10515
10604
|
};
|
|
10516
10605
|
model.cols = Column.toModel(this.columns);
|
|
@@ -10554,6 +10643,25 @@ var ExcelTS = (function(exports) {
|
|
|
10554
10643
|
this.views = value.views;
|
|
10555
10644
|
this.autoFilter = value.autoFilter;
|
|
10556
10645
|
this._media = value.media.map((medium) => new Image(this, medium));
|
|
10646
|
+
this._watermark = value.watermark ?? null;
|
|
10647
|
+
if (!this._watermark) {
|
|
10648
|
+
for (const medium of this._media) if (medium.type === "watermark") {
|
|
10649
|
+
this._watermark = {
|
|
10650
|
+
imageId: medium.imageId ?? "",
|
|
10651
|
+
mode: "overlay",
|
|
10652
|
+
opacity: medium.opacity
|
|
10653
|
+
};
|
|
10654
|
+
break;
|
|
10655
|
+
} else if (medium.type === "headerImage") {
|
|
10656
|
+
this._watermark = {
|
|
10657
|
+
imageId: medium.imageId ?? "",
|
|
10658
|
+
mode: "header",
|
|
10659
|
+
headerWidth: medium.headerWidth,
|
|
10660
|
+
headerHeight: medium.headerHeight
|
|
10661
|
+
};
|
|
10662
|
+
break;
|
|
10663
|
+
}
|
|
10664
|
+
}
|
|
10557
10665
|
this.sheetProtection = value.sheetProtection;
|
|
10558
10666
|
this.tables = value.tables.reduce((tables, table) => {
|
|
10559
10667
|
const t = new Table(this, table);
|
|
@@ -15419,6 +15527,7 @@ var ExcelTS = (function(exports) {
|
|
|
15419
15527
|
const drawingXmlRegex = /^xl\/drawings\/(drawing\d+)[.]xml$/;
|
|
15420
15528
|
const drawingRelsXmlRegex = /^xl\/drawings\/_rels\/(drawing\d+)[.]xml[.]rels$/;
|
|
15421
15529
|
const vmlDrawingRegex = /^xl\/drawings\/(vmlDrawing\d+)[.]vml$/;
|
|
15530
|
+
const vmlDrawingHFRegex = /^xl\/drawings\/(vmlDrawingHF\d+)[.]vml$/;
|
|
15422
15531
|
const commentsXmlRegex = /^xl\/comments(\d+)[.]xml$/;
|
|
15423
15532
|
const tableXmlRegex = /^xl\/tables\/(table\d+)[.]xml$/;
|
|
15424
15533
|
const pivotTableXmlRegex = /^xl\/pivotTables\/(pivotTable\d+)[.]xml$/;
|
|
@@ -15468,6 +15577,10 @@ var ExcelTS = (function(exports) {
|
|
|
15468
15577
|
const match = vmlDrawingRegex.exec(path);
|
|
15469
15578
|
return match ? match[1] : void 0;
|
|
15470
15579
|
}
|
|
15580
|
+
function getVmlDrawingHFNameFromPath(path) {
|
|
15581
|
+
const match = vmlDrawingHFRegex.exec(path);
|
|
15582
|
+
return match ? match[1] : void 0;
|
|
15583
|
+
}
|
|
15471
15584
|
function getCommentsIndexFromPath(path) {
|
|
15472
15585
|
const match = commentsXmlRegex.exec(path);
|
|
15473
15586
|
return match ? match[1] : void 0;
|
|
@@ -15523,6 +15636,12 @@ var ExcelTS = (function(exports) {
|
|
|
15523
15636
|
function vmlDrawingPath(sheetId) {
|
|
15524
15637
|
return `xl/drawings/vmlDrawing${sheetId}.vml`;
|
|
15525
15638
|
}
|
|
15639
|
+
function vmlDrawingHFPath(sheetId) {
|
|
15640
|
+
return `xl/drawings/vmlDrawingHF${sheetId}.vml`;
|
|
15641
|
+
}
|
|
15642
|
+
function vmlDrawingHFRelsPath(sheetId) {
|
|
15643
|
+
return `xl/drawings/_rels/vmlDrawingHF${sheetId}.vml.rels`;
|
|
15644
|
+
}
|
|
15526
15645
|
function tablePath(target) {
|
|
15527
15646
|
return `xl/tables/${target}`;
|
|
15528
15647
|
}
|
|
@@ -15568,6 +15687,9 @@ var ExcelTS = (function(exports) {
|
|
|
15568
15687
|
function vmlDrawingRelTargetFromWorksheet(sheetId) {
|
|
15569
15688
|
return `../drawings/vmlDrawing${sheetId}.vml`;
|
|
15570
15689
|
}
|
|
15690
|
+
function vmlDrawingHFRelTargetFromWorksheet(sheetId) {
|
|
15691
|
+
return `../drawings/vmlDrawingHF${sheetId}.vml`;
|
|
15692
|
+
}
|
|
15571
15693
|
function drawingRelTargetFromWorksheet(drawingName) {
|
|
15572
15694
|
return `../drawings/${drawingName}.xml`;
|
|
15573
15695
|
}
|
|
@@ -15687,7 +15809,8 @@ var ExcelTS = (function(exports) {
|
|
|
15687
15809
|
});
|
|
15688
15810
|
const hasComments = model.commentRefs && model.commentRefs.length > 0;
|
|
15689
15811
|
const hasFormControls = model.formControlRefs && model.formControlRefs.length > 0;
|
|
15690
|
-
|
|
15812
|
+
const hasHeaderWatermark = model.hasHeaderWatermark === true;
|
|
15813
|
+
if (hasComments || hasFormControls || hasHeaderWatermark) xmlStream.leafNode("Default", {
|
|
15691
15814
|
Extension: "vml",
|
|
15692
15815
|
ContentType: "application/vnd.openxmlformats-officedocument.vmlDrawing"
|
|
15693
15816
|
});
|
|
@@ -19025,6 +19148,10 @@ var ExcelTS = (function(exports) {
|
|
|
19025
19148
|
picture: { rId: rIdImage },
|
|
19026
19149
|
range: medium.range
|
|
19027
19150
|
};
|
|
19151
|
+
if (medium.opacity !== void 0) {
|
|
19152
|
+
const clamped = Math.max(0, Math.min(1, medium.opacity));
|
|
19153
|
+
anchor.picture.alphaModFix = Math.round(clamped * 1e5);
|
|
19154
|
+
}
|
|
19028
19155
|
if (medium.hyperlinks && medium.hyperlinks.hyperlink) {
|
|
19029
19156
|
const rIdHyperlink = options.nextRId(rels);
|
|
19030
19157
|
anchor.picture.hyperlinks = {
|
|
@@ -19225,9 +19352,13 @@ var ExcelTS = (function(exports) {
|
|
|
19225
19352
|
}
|
|
19226
19353
|
const backgroundMedia = [];
|
|
19227
19354
|
const imageMedia = [];
|
|
19355
|
+
const watermarkMedia = [];
|
|
19356
|
+
const headerImageMedia = [];
|
|
19228
19357
|
model.media.forEach((medium) => {
|
|
19229
19358
|
if (medium.type === "background") backgroundMedia.push(medium);
|
|
19230
19359
|
else if (medium.type === "image") imageMedia.push(medium);
|
|
19360
|
+
else if (medium.type === "watermark") watermarkMedia.push(medium);
|
|
19361
|
+
else if (medium.type === "headerImage") headerImageMedia.push(medium);
|
|
19231
19362
|
});
|
|
19232
19363
|
backgroundMedia.forEach((medium) => {
|
|
19233
19364
|
const rId = nextRid(rels);
|
|
@@ -19263,6 +19394,96 @@ var ExcelTS = (function(exports) {
|
|
|
19263
19394
|
drawing.anchors.push(...result.anchors);
|
|
19264
19395
|
drawing.rels = result.rels;
|
|
19265
19396
|
}
|
|
19397
|
+
if (watermarkMedia.length > 0) {
|
|
19398
|
+
let { drawing } = model;
|
|
19399
|
+
if (!drawing) {
|
|
19400
|
+
drawing = model.drawing = {
|
|
19401
|
+
rId: nextRid(rels),
|
|
19402
|
+
name: `drawing${++options.drawingsCount}`,
|
|
19403
|
+
anchors: [],
|
|
19404
|
+
rels: []
|
|
19405
|
+
};
|
|
19406
|
+
options.drawings.push(drawing);
|
|
19407
|
+
rels.push({
|
|
19408
|
+
Id: drawing.rId,
|
|
19409
|
+
Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
|
|
19410
|
+
Target: drawingRelTargetFromWorksheet(drawing.name)
|
|
19411
|
+
});
|
|
19412
|
+
}
|
|
19413
|
+
for (const medium of watermarkMedia) {
|
|
19414
|
+
const bookImage = options.media[medium.imageId];
|
|
19415
|
+
if (!bookImage) continue;
|
|
19416
|
+
const rIdImage = nextRid(drawing.rels);
|
|
19417
|
+
drawing.rels.push({
|
|
19418
|
+
Id: rIdImage,
|
|
19419
|
+
Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
|
|
19420
|
+
Target: resolveMediaTarget(bookImage)
|
|
19421
|
+
});
|
|
19422
|
+
const rawOpacity = medium.opacity !== void 0 ? medium.opacity : .15;
|
|
19423
|
+
const alphaModFix = Math.round(Math.max(0, Math.min(1, rawOpacity)) * 1e5);
|
|
19424
|
+
const dims = model.dimensions;
|
|
19425
|
+
const maxCol = dims ? Math.max(dims.model?.right ?? 100, 100) : 100;
|
|
19426
|
+
const maxRow = dims ? Math.max(dims.model?.bottom ?? 200, 200) : 200;
|
|
19427
|
+
drawing.anchors.push({
|
|
19428
|
+
picture: {
|
|
19429
|
+
rId: rIdImage,
|
|
19430
|
+
alphaModFix
|
|
19431
|
+
},
|
|
19432
|
+
range: {
|
|
19433
|
+
editAs: "absolute",
|
|
19434
|
+
tl: {
|
|
19435
|
+
nativeCol: 0,
|
|
19436
|
+
nativeColOff: 0,
|
|
19437
|
+
nativeRow: 0,
|
|
19438
|
+
nativeRowOff: 0
|
|
19439
|
+
},
|
|
19440
|
+
br: {
|
|
19441
|
+
nativeCol: maxCol,
|
|
19442
|
+
nativeColOff: 0,
|
|
19443
|
+
nativeRow: maxRow,
|
|
19444
|
+
nativeRowOff: 0
|
|
19445
|
+
}
|
|
19446
|
+
}
|
|
19447
|
+
});
|
|
19448
|
+
}
|
|
19449
|
+
}
|
|
19450
|
+
if (headerImageMedia.length > 0) {
|
|
19451
|
+
const medium = headerImageMedia[0];
|
|
19452
|
+
const bookImage = options.media[medium.imageId];
|
|
19453
|
+
if (bookImage) {
|
|
19454
|
+
const rIdVml = nextRid(rels);
|
|
19455
|
+
rels.push({
|
|
19456
|
+
Id: rIdVml,
|
|
19457
|
+
Type: RelType.VmlDrawing,
|
|
19458
|
+
Target: vmlDrawingHFRelTargetFromWorksheet(fileIndex)
|
|
19459
|
+
});
|
|
19460
|
+
model.headerImage = {
|
|
19461
|
+
vmlRelId: rIdVml,
|
|
19462
|
+
imageId: medium.imageId,
|
|
19463
|
+
bookImage,
|
|
19464
|
+
headerWidth: medium.headerWidth,
|
|
19465
|
+
headerHeight: medium.headerHeight
|
|
19466
|
+
};
|
|
19467
|
+
options.hasHeaderWatermark = true;
|
|
19468
|
+
if (!model.headerFooter) model.headerFooter = {};
|
|
19469
|
+
const applyTo = medium.applyTo || "all";
|
|
19470
|
+
const insertG = (field) => {
|
|
19471
|
+
const existing = model.headerFooter[field] || "";
|
|
19472
|
+
if (existing.includes("&G")) return existing;
|
|
19473
|
+
if (existing.includes("&C")) return existing.replace("&C", "&C&G");
|
|
19474
|
+
return existing + "&C&G";
|
|
19475
|
+
};
|
|
19476
|
+
if (applyTo === "all" || applyTo === "odd") model.headerFooter.oddHeader = insertG("oddHeader");
|
|
19477
|
+
if (applyTo === "all" || applyTo === "even") {
|
|
19478
|
+
model.headerFooter.evenHeader = insertG("evenHeader");
|
|
19479
|
+
model.headerFooter.differentOddEven = true;
|
|
19480
|
+
}
|
|
19481
|
+
if (applyTo === "all" || applyTo === "first") {
|
|
19482
|
+
model.headerFooter.firstHeader = insertG("firstHeader");
|
|
19483
|
+
model.headerFooter.differentFirst = true;
|
|
19484
|
+
}
|
|
19485
|
+
}
|
|
19486
|
+
}
|
|
19266
19487
|
model.tables.forEach((table) => {
|
|
19267
19488
|
const rId = nextRid(rels);
|
|
19268
19489
|
table.rId = rId;
|
|
@@ -19392,8 +19613,12 @@ var ExcelTS = (function(exports) {
|
|
|
19392
19613
|
this.map.drawing.render(xmlStream, model.drawing);
|
|
19393
19614
|
this.map.picture.render(xmlStream, model.background);
|
|
19394
19615
|
if (model.rels) model.rels.forEach((rel) => {
|
|
19395
|
-
if (rel.Type === RelType.VmlDrawing)
|
|
19616
|
+
if (rel.Type === RelType.VmlDrawing) {
|
|
19617
|
+
if (model.headerImage && rel.Id === model.headerImage.vmlRelId) return;
|
|
19618
|
+
xmlStream.leafNode("legacyDrawing", { "r:id": rel.Id });
|
|
19619
|
+
}
|
|
19396
19620
|
});
|
|
19621
|
+
if (model.headerImage) xmlStream.leafNode("legacyDrawingHF", { "r:id": model.headerImage.vmlRelId });
|
|
19397
19622
|
if (model.formControls && model.formControls.length > 0) {
|
|
19398
19623
|
xmlStream.openNode("mc:AlternateContent");
|
|
19399
19624
|
xmlStream.openNode("mc:Choice", { Requires: "x14" });
|
|
@@ -19561,15 +19786,17 @@ var ExcelTS = (function(exports) {
|
|
|
19561
19786
|
rels: options.drawingRels?.[drawingName] ?? drawing.rels ?? []
|
|
19562
19787
|
};
|
|
19563
19788
|
drawing.anchors.forEach((anchor) => {
|
|
19564
|
-
if (anchor.medium) {
|
|
19565
|
-
|
|
19566
|
-
|
|
19567
|
-
|
|
19568
|
-
|
|
19569
|
-
|
|
19570
|
-
|
|
19571
|
-
|
|
19572
|
-
|
|
19789
|
+
if (anchor.medium) if (anchor.medium.alphaModFix !== void 0 && anchor.medium.alphaModFix < 1e5) model.media.push({
|
|
19790
|
+
type: "watermark",
|
|
19791
|
+
imageId: anchor.medium.index,
|
|
19792
|
+
opacity: anchor.medium.alphaModFix / 1e5
|
|
19793
|
+
});
|
|
19794
|
+
else model.media.push({
|
|
19795
|
+
type: "image",
|
|
19796
|
+
imageId: anchor.medium.index,
|
|
19797
|
+
range: anchor.range,
|
|
19798
|
+
hyperlinks: anchor.picture.hyperlinks
|
|
19799
|
+
});
|
|
19573
19800
|
});
|
|
19574
19801
|
} else model.drawing = void 0;
|
|
19575
19802
|
} else model.drawing = void 0;
|
|
@@ -19668,7 +19895,12 @@ var ExcelTS = (function(exports) {
|
|
|
19668
19895
|
if (match) {
|
|
19669
19896
|
const name = match[1];
|
|
19670
19897
|
const mediaId = options.mediaIndex[name];
|
|
19671
|
-
|
|
19898
|
+
const medium = options.media[mediaId];
|
|
19899
|
+
if (medium && model.alphaModFix !== void 0) return {
|
|
19900
|
+
...medium,
|
|
19901
|
+
alphaModFix: model.alphaModFix
|
|
19902
|
+
};
|
|
19903
|
+
return medium;
|
|
19672
19904
|
}
|
|
19673
19905
|
}
|
|
19674
19906
|
}
|
|
@@ -19760,7 +19992,15 @@ var ExcelTS = (function(exports) {
|
|
|
19760
19992
|
return "a:blip";
|
|
19761
19993
|
}
|
|
19762
19994
|
render(xmlStream, model) {
|
|
19763
|
-
|
|
19995
|
+
if (model.alphaModFix !== void 0 && model.alphaModFix < 1e5) {
|
|
19996
|
+
xmlStream.openNode(this.tag, {
|
|
19997
|
+
"xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
|
|
19998
|
+
"r:embed": model.rId,
|
|
19999
|
+
cstate: "print"
|
|
20000
|
+
});
|
|
20001
|
+
xmlStream.leafNode("a:alphaModFix", { amt: String(model.alphaModFix) });
|
|
20002
|
+
xmlStream.closeNode();
|
|
20003
|
+
} else xmlStream.leafNode(this.tag, {
|
|
19764
20004
|
"xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
|
|
19765
20005
|
"r:embed": model.rId,
|
|
19766
20006
|
cstate: "print"
|
|
@@ -19771,6 +20011,9 @@ var ExcelTS = (function(exports) {
|
|
|
19771
20011
|
case this.tag:
|
|
19772
20012
|
this.model = { rId: node.attributes["r:embed"] };
|
|
19773
20013
|
return true;
|
|
20014
|
+
case "a:alphaModFix":
|
|
20015
|
+
if (node.attributes.amt) this.model.alphaModFix = parseInt(node.attributes.amt, 10);
|
|
20016
|
+
return true;
|
|
19774
20017
|
default: return true;
|
|
19775
20018
|
}
|
|
19776
20019
|
}
|
|
@@ -20065,7 +20308,10 @@ var ExcelTS = (function(exports) {
|
|
|
20065
20308
|
render(xmlStream, model) {
|
|
20066
20309
|
xmlStream.openNode(this.tag);
|
|
20067
20310
|
this.map["xdr:nvPicPr"].render(xmlStream, model);
|
|
20068
|
-
this.map["xdr:blipFill"].render(xmlStream,
|
|
20311
|
+
this.map["xdr:blipFill"].render(xmlStream, {
|
|
20312
|
+
rId: model.rId,
|
|
20313
|
+
alphaModFix: model.alphaModFix
|
|
20314
|
+
});
|
|
20069
20315
|
this.map["xdr:spPr"].render(xmlStream, model);
|
|
20070
20316
|
xmlStream.closeNode();
|
|
20071
20317
|
}
|
|
@@ -22980,6 +23226,7 @@ var ExcelTS = (function(exports) {
|
|
|
22980
23226
|
var VmlDrawingXform = class VmlDrawingXform extends BaseXform {
|
|
22981
23227
|
constructor() {
|
|
22982
23228
|
super();
|
|
23229
|
+
this._parsingHeaderImage = false;
|
|
22983
23230
|
this.map = { "v:shape": new VmlShapeXform() };
|
|
22984
23231
|
this.model = {
|
|
22985
23232
|
comments: [],
|
|
@@ -22996,8 +23243,10 @@ var ExcelTS = (function(exports) {
|
|
|
22996
23243
|
const renderModel = model || this.model;
|
|
22997
23244
|
const comments = renderModel.comments;
|
|
22998
23245
|
const formControls = renderModel.formControls;
|
|
23246
|
+
const headerImage = renderModel.headerImage;
|
|
22999
23247
|
const hasComments = comments && comments.length > 0;
|
|
23000
23248
|
const hasFormControls = formControls && formControls.length > 0;
|
|
23249
|
+
const hasHeaderImage = !!headerImage;
|
|
23001
23250
|
xmlStream.openXml(StdDocAttributes);
|
|
23002
23251
|
xmlStream.openNode(this.tag, VmlDrawingXform.DRAWING_ATTRIBUTES);
|
|
23003
23252
|
xmlStream.openNode("o:shapelayout", { "v:ext": "edit" });
|
|
@@ -23041,8 +23290,67 @@ var ExcelTS = (function(exports) {
|
|
|
23041
23290
|
});
|
|
23042
23291
|
xmlStream.closeNode();
|
|
23043
23292
|
}
|
|
23293
|
+
if (hasHeaderImage) {
|
|
23294
|
+
xmlStream.openNode("v:shapetype", {
|
|
23295
|
+
id: "_x0000_t75",
|
|
23296
|
+
coordsize: "21600,21600",
|
|
23297
|
+
"o:spt": "75",
|
|
23298
|
+
"o:preferrelative": "t",
|
|
23299
|
+
path: "m@4@5l@4@11@9@11@9@5xe",
|
|
23300
|
+
filled: "f",
|
|
23301
|
+
stroked: "f"
|
|
23302
|
+
});
|
|
23303
|
+
xmlStream.leafNode("v:stroke", { joinstyle: "miter" });
|
|
23304
|
+
xmlStream.openNode("v:formulas");
|
|
23305
|
+
xmlStream.leafNode("v:f", { eqn: "if lineDrawn pixelLineWidth 0" });
|
|
23306
|
+
xmlStream.leafNode("v:f", { eqn: "sum @0 1 0" });
|
|
23307
|
+
xmlStream.leafNode("v:f", { eqn: "sum 0 0 @1" });
|
|
23308
|
+
xmlStream.leafNode("v:f", { eqn: "prod @2 1 2" });
|
|
23309
|
+
xmlStream.leafNode("v:f", { eqn: "prod @3 21600 pixelWidth" });
|
|
23310
|
+
xmlStream.leafNode("v:f", { eqn: "prod @3 21600 pixelHeight" });
|
|
23311
|
+
xmlStream.leafNode("v:f", { eqn: "sum @0 0 1" });
|
|
23312
|
+
xmlStream.leafNode("v:f", { eqn: "prod @6 1 2" });
|
|
23313
|
+
xmlStream.leafNode("v:f", { eqn: "prod @7 21600 pixelWidth" });
|
|
23314
|
+
xmlStream.leafNode("v:f", { eqn: "sum @8 21600 0" });
|
|
23315
|
+
xmlStream.leafNode("v:f", { eqn: "prod @7 21600 pixelHeight" });
|
|
23316
|
+
xmlStream.leafNode("v:f", { eqn: "sum @10 21600 0" });
|
|
23317
|
+
xmlStream.closeNode();
|
|
23318
|
+
xmlStream.leafNode("v:path", {
|
|
23319
|
+
"o:extrusionok": "f",
|
|
23320
|
+
gradientshapeok: "t",
|
|
23321
|
+
"o:connecttype": "rect"
|
|
23322
|
+
});
|
|
23323
|
+
xmlStream.leafNode("o:lock", {
|
|
23324
|
+
"v:ext": "edit",
|
|
23325
|
+
aspectratio: "t"
|
|
23326
|
+
});
|
|
23327
|
+
xmlStream.closeNode();
|
|
23328
|
+
}
|
|
23044
23329
|
if (hasComments) for (let i = 0; i < comments.length; i++) this.map["v:shape"].render(xmlStream, comments[i], i);
|
|
23045
23330
|
if (hasFormControls) for (const control of formControls) this._renderCheckboxShape(xmlStream, control);
|
|
23331
|
+
if (hasHeaderImage) this._renderHeaderImageShape(xmlStream, headerImage);
|
|
23332
|
+
xmlStream.closeNode();
|
|
23333
|
+
}
|
|
23334
|
+
/**
|
|
23335
|
+
* Render a header/footer image shape for watermark
|
|
23336
|
+
*/
|
|
23337
|
+
_renderHeaderImageShape(xmlStream, headerImage) {
|
|
23338
|
+
const width = headerImage.width ?? 467.25;
|
|
23339
|
+
const height = headerImage.height ?? 311.25;
|
|
23340
|
+
xmlStream.openNode("v:shape", {
|
|
23341
|
+
id: "CH",
|
|
23342
|
+
"o:spid": "_x0000_s2049",
|
|
23343
|
+
type: "#_x0000_t75",
|
|
23344
|
+
style: `position:absolute;margin-left:0;margin-top:0;width:${width}pt;height:${height}pt;z-index:1`
|
|
23345
|
+
});
|
|
23346
|
+
xmlStream.leafNode("v:imagedata", {
|
|
23347
|
+
"o:relid": headerImage.imageRelId,
|
|
23348
|
+
"o:title": "watermark"
|
|
23349
|
+
});
|
|
23350
|
+
xmlStream.leafNode("o:lock", {
|
|
23351
|
+
"v:ext": "edit",
|
|
23352
|
+
rotation: "t"
|
|
23353
|
+
});
|
|
23046
23354
|
xmlStream.closeNode();
|
|
23047
23355
|
}
|
|
23048
23356
|
/**
|
|
@@ -23116,9 +23424,25 @@ var ExcelTS = (function(exports) {
|
|
|
23116
23424
|
formControls: []
|
|
23117
23425
|
};
|
|
23118
23426
|
break;
|
|
23427
|
+
case "v:shape":
|
|
23428
|
+
if (node.attributes.type === "#_x0000_t75") {
|
|
23429
|
+
this._parsingHeaderImage = true;
|
|
23430
|
+
const style = node.attributes.style || "";
|
|
23431
|
+
const widthMatch = /width:([0-9.]+)pt/.exec(style);
|
|
23432
|
+
const heightMatch = /height:([0-9.]+)pt/.exec(style);
|
|
23433
|
+
this._headerImageWidth = widthMatch ? parseFloat(widthMatch[1]) : void 0;
|
|
23434
|
+
this._headerImageHeight = heightMatch ? parseFloat(heightMatch[1]) : void 0;
|
|
23435
|
+
} else {
|
|
23436
|
+
this.parser = this.map[node.name];
|
|
23437
|
+
if (this.parser) this.parser.parseOpen(node);
|
|
23438
|
+
}
|
|
23439
|
+
break;
|
|
23119
23440
|
default:
|
|
23120
|
-
this.
|
|
23121
|
-
|
|
23441
|
+
if (this._parsingHeaderImage && node.name === "v:imagedata") this._headerImageRelId = node.attributes["o:relid"];
|
|
23442
|
+
else {
|
|
23443
|
+
this.parser = this.map[node.name];
|
|
23444
|
+
if (this.parser) this.parser.parseOpen(node);
|
|
23445
|
+
}
|
|
23122
23446
|
break;
|
|
23123
23447
|
}
|
|
23124
23448
|
return true;
|
|
@@ -23135,6 +23459,17 @@ var ExcelTS = (function(exports) {
|
|
|
23135
23459
|
return true;
|
|
23136
23460
|
}
|
|
23137
23461
|
switch (name) {
|
|
23462
|
+
case "v:shape":
|
|
23463
|
+
if (this._parsingHeaderImage && this._headerImageRelId) this.model.headerImage = {
|
|
23464
|
+
imageRelId: this._headerImageRelId,
|
|
23465
|
+
width: this._headerImageWidth,
|
|
23466
|
+
height: this._headerImageHeight
|
|
23467
|
+
};
|
|
23468
|
+
this._parsingHeaderImage = false;
|
|
23469
|
+
this._headerImageRelId = void 0;
|
|
23470
|
+
this._headerImageWidth = void 0;
|
|
23471
|
+
this._headerImageHeight = void 0;
|
|
23472
|
+
return true;
|
|
23138
23473
|
case this.tag: return false;
|
|
23139
23474
|
default: return true;
|
|
23140
23475
|
}
|
|
@@ -25658,7 +25993,7 @@ var ExcelTS = (function(exports) {
|
|
|
25658
25993
|
* @param data - Input data
|
|
25659
25994
|
* @returns 32-bit Adler-32 checksum
|
|
25660
25995
|
*/
|
|
25661
|
-
function adler32(data) {
|
|
25996
|
+
function adler32$1(data) {
|
|
25662
25997
|
let a = 1;
|
|
25663
25998
|
let b = 0;
|
|
25664
25999
|
const chunkSize = 5552;
|
|
@@ -27200,7 +27535,7 @@ self.onmessage = async function(event) {
|
|
|
27200
27535
|
return concatUint8Arrays([
|
|
27201
27536
|
getZlibHeader(level),
|
|
27202
27537
|
deflated,
|
|
27203
|
-
buildZlibTrailer(adler32(original))
|
|
27538
|
+
buildZlibTrailer(adler32$1(original))
|
|
27204
27539
|
]);
|
|
27205
27540
|
}
|
|
27206
27541
|
/**
|
|
@@ -35661,6 +35996,13 @@ self.onmessage = async function(event) {
|
|
|
35661
35996
|
const vmlDrawing = await new VmlDrawingXform().parseStream(entry);
|
|
35662
35997
|
model.vmlDrawings[vmlDrawingRelTargetFromWorksheetName(name)] = vmlDrawing;
|
|
35663
35998
|
}
|
|
35999
|
+
async _processVmlDrawingHFEntry(entry, model, _name) {
|
|
36000
|
+
const vmlDrawing = await new VmlDrawingXform().parseStream(entry);
|
|
36001
|
+
if (vmlDrawing && vmlDrawing.headerImage) {
|
|
36002
|
+
if (!model.vmlDrawingHF) model.vmlDrawingHF = {};
|
|
36003
|
+
model.vmlDrawingHF[_name] = vmlDrawing.headerImage;
|
|
36004
|
+
}
|
|
36005
|
+
}
|
|
35664
36006
|
async _processThemeEntry(stream, model, name) {
|
|
35665
36007
|
await new Promise((resolve, reject) => {
|
|
35666
36008
|
const streamBuf = this.createStreamBuf();
|
|
@@ -35747,6 +36089,11 @@ self.onmessage = async function(event) {
|
|
|
35747
36089
|
await this._processVmlDrawingEntry(stream, model, vmlDrawingName);
|
|
35748
36090
|
return true;
|
|
35749
36091
|
}
|
|
36092
|
+
const vmlHFName = getVmlDrawingHFNameFromPath(entryName);
|
|
36093
|
+
if (vmlHFName) {
|
|
36094
|
+
await this._processVmlDrawingHFEntry(stream, model, vmlHFName);
|
|
36095
|
+
return true;
|
|
36096
|
+
}
|
|
35750
36097
|
const commentsIndex = getCommentsIndexFromPath(entryName);
|
|
35751
36098
|
if (commentsIndex) {
|
|
35752
36099
|
await this._processCommentEntry(stream, model, `comments${commentsIndex}`);
|
|
@@ -35925,6 +36272,25 @@ self.onmessage = async function(event) {
|
|
|
35925
36272
|
comments: hasComments ? worksheet.comments : [],
|
|
35926
36273
|
formControls: hasFormControls ? worksheet.formControls : []
|
|
35927
36274
|
});
|
|
36275
|
+
if (worksheet.headerImage) {
|
|
36276
|
+
const hdrImage = worksheet.headerImage;
|
|
36277
|
+
const bookImage = hdrImage.bookImage;
|
|
36278
|
+
const imageRelTarget = `../media/${bookImage.name && bookImage.extension && bookImage.name.endsWith(`.${bookImage.extension}`) ? bookImage.name : `${bookImage.name}.${bookImage.extension}`}`;
|
|
36279
|
+
await this._renderToZip(zip, vmlDrawingHFPath(fileIndex), vmlDrawingXform, {
|
|
36280
|
+
comments: [],
|
|
36281
|
+
formControls: [],
|
|
36282
|
+
headerImage: {
|
|
36283
|
+
imageRelId: "rId1",
|
|
36284
|
+
width: hdrImage.headerWidth,
|
|
36285
|
+
height: hdrImage.headerHeight
|
|
36286
|
+
}
|
|
36287
|
+
});
|
|
36288
|
+
await this._renderToZip(zip, vmlDrawingHFRelsPath(fileIndex), relationshipsXform, [{
|
|
36289
|
+
Id: "rId1",
|
|
36290
|
+
Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
|
|
36291
|
+
Target: imageRelTarget
|
|
36292
|
+
}]);
|
|
36293
|
+
}
|
|
35928
36294
|
if (hasFormControls) for (const control of worksheet.formControls) await this._renderToZip(zip, ctrlPropPath(control.ctrlPropId), ctrlPropXform, control);
|
|
35929
36295
|
}
|
|
35930
36296
|
}
|
|
@@ -36036,6 +36402,7 @@ self.onmessage = async function(event) {
|
|
|
36036
36402
|
worksheetOptions.drawings = model.drawings = [];
|
|
36037
36403
|
worksheetOptions.commentRefs = model.commentRefs = [];
|
|
36038
36404
|
worksheetOptions.formControlRefs = model.formControlRefs = [];
|
|
36405
|
+
model.hasHeaderWatermark = false;
|
|
36039
36406
|
let tableCount = 0;
|
|
36040
36407
|
model.tables = [];
|
|
36041
36408
|
model.worksheets.forEach((worksheet, index) => {
|
|
@@ -36049,6 +36416,7 @@ self.onmessage = async function(event) {
|
|
|
36049
36416
|
worksheetXform.prepare(worksheet, worksheetOptions);
|
|
36050
36417
|
});
|
|
36051
36418
|
model.hasCheckboxes = model.styles.hasCheckboxes;
|
|
36419
|
+
if (worksheetOptions.hasHeaderWatermark) model.hasHeaderWatermark = true;
|
|
36052
36420
|
const passthrough = model.passthrough || {};
|
|
36053
36421
|
const passthroughManager = new PassthroughManager();
|
|
36054
36422
|
passthroughManager.fromRecord(passthrough);
|
|
@@ -36347,6 +36715,7 @@ self.onmessage = async function(event) {
|
|
|
36347
36715
|
this._views = options.views ?? [];
|
|
36348
36716
|
this.autoFilter = options.autoFilter ?? null;
|
|
36349
36717
|
this._media = [];
|
|
36718
|
+
this._watermark = null;
|
|
36350
36719
|
this.sheetProtection = null;
|
|
36351
36720
|
this._writeOpenWorksheet();
|
|
36352
36721
|
this.startedData = false;
|
|
@@ -36550,6 +36919,59 @@ self.onmessage = async function(event) {
|
|
|
36550
36919
|
return this._media;
|
|
36551
36920
|
}
|
|
36552
36921
|
/**
|
|
36922
|
+
* Add a watermark to the worksheet using an image from `WorkbookWriter.addImage()`.
|
|
36923
|
+
* Supports overlay mode (DrawingML with transparency) and header mode (VML behind content).
|
|
36924
|
+
*/
|
|
36925
|
+
addWatermark(options) {
|
|
36926
|
+
this._media = this._media.filter((m) => m._watermarkTag !== true);
|
|
36927
|
+
const opacity = options.opacity !== void 0 ? Math.max(0, Math.min(1, options.opacity)) : .15;
|
|
36928
|
+
this._watermark = {
|
|
36929
|
+
imageId: String(options.imageId),
|
|
36930
|
+
mode: options.mode ?? "overlay",
|
|
36931
|
+
opacity,
|
|
36932
|
+
headerWidth: options.headerWidth,
|
|
36933
|
+
headerHeight: options.headerHeight,
|
|
36934
|
+
applyTo: options.applyTo
|
|
36935
|
+
};
|
|
36936
|
+
if (this._watermark.mode === "overlay") {
|
|
36937
|
+
const entry = {
|
|
36938
|
+
type: "image",
|
|
36939
|
+
imageId: String(options.imageId),
|
|
36940
|
+
range: {
|
|
36941
|
+
tl: {
|
|
36942
|
+
nativeCol: 0,
|
|
36943
|
+
nativeColOff: 0,
|
|
36944
|
+
nativeRow: 0,
|
|
36945
|
+
nativeRowOff: 0
|
|
36946
|
+
},
|
|
36947
|
+
br: {
|
|
36948
|
+
nativeCol: 100,
|
|
36949
|
+
nativeColOff: 0,
|
|
36950
|
+
nativeRow: 200,
|
|
36951
|
+
nativeRowOff: 0
|
|
36952
|
+
},
|
|
36953
|
+
editAs: "absolute"
|
|
36954
|
+
},
|
|
36955
|
+
_watermarkTag: true,
|
|
36956
|
+
opacity
|
|
36957
|
+
};
|
|
36958
|
+
this._media.push(entry);
|
|
36959
|
+
}
|
|
36960
|
+
}
|
|
36961
|
+
/**
|
|
36962
|
+
* Get the current watermark configuration.
|
|
36963
|
+
*/
|
|
36964
|
+
getWatermark() {
|
|
36965
|
+
return this._watermark;
|
|
36966
|
+
}
|
|
36967
|
+
/**
|
|
36968
|
+
* Remove the watermark from the worksheet.
|
|
36969
|
+
*/
|
|
36970
|
+
removeWatermark() {
|
|
36971
|
+
this._watermark = null;
|
|
36972
|
+
this._media = this._media.filter((m) => m._watermarkTag !== true);
|
|
36973
|
+
}
|
|
36974
|
+
/**
|
|
36553
36975
|
* Parse the user-supplied range into a normalised internal model
|
|
36554
36976
|
* mirroring what the regular Worksheet / Image class does.
|
|
36555
36977
|
*/
|
|
@@ -36707,6 +37129,17 @@ self.onmessage = async function(event) {
|
|
|
36707
37129
|
}
|
|
36708
37130
|
_writeDrawing() {
|
|
36709
37131
|
if (this._media.length === 0) return;
|
|
37132
|
+
for (const entry of this._media) if (entry._watermarkTag) {
|
|
37133
|
+
const dims = this._dimensions.model;
|
|
37134
|
+
const maxCol = dims ? Math.max(dims.right ?? 100, 100) : 100;
|
|
37135
|
+
const maxRow = dims ? Math.max(dims.bottom ?? 200, 200) : 200;
|
|
37136
|
+
entry.range.br = {
|
|
37137
|
+
nativeCol: maxCol,
|
|
37138
|
+
nativeColOff: 0,
|
|
37139
|
+
nativeRow: maxRow,
|
|
37140
|
+
nativeRowOff: 0
|
|
37141
|
+
};
|
|
37142
|
+
}
|
|
36710
37143
|
const drawingName = `drawing${this.id}`;
|
|
36711
37144
|
const drawingRId = this._sheetRelsWriter.addRelationship({
|
|
36712
37145
|
Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
|
|
@@ -44520,6 +44953,1198 @@ onmessage = async (ev) => {
|
|
|
44520
44953
|
return PaperSize;
|
|
44521
44954
|
}({});
|
|
44522
44955
|
//#endregion
|
|
44956
|
+
//#region src/modules/excel/utils/watermark-image.ts
|
|
44957
|
+
/**
|
|
44958
|
+
* Zero-dependency text-to-PNG watermark image generator.
|
|
44959
|
+
*
|
|
44960
|
+
* Renders text into a semi-transparent PNG suitable for use as an Excel watermark.
|
|
44961
|
+
* Uses a built-in bitmap font for ASCII characters — no Canvas or external fonts required.
|
|
44962
|
+
* PNG data is deflate-compressed using the archive module's built-in compressor.
|
|
44963
|
+
*
|
|
44964
|
+
* @example
|
|
44965
|
+
* ```typescript
|
|
44966
|
+
* const png = createTextWatermarkImage("CONFIDENTIAL", {
|
|
44967
|
+
* fontSize: 48,
|
|
44968
|
+
* color: { r: 128, g: 128, b: 128 },
|
|
44969
|
+
* opacity: 40,
|
|
44970
|
+
* rotation: -45
|
|
44971
|
+
* });
|
|
44972
|
+
* const imgId = workbook.addImage({ buffer: png, extension: "png" });
|
|
44973
|
+
* worksheet.addWatermark({ imageId: imgId });
|
|
44974
|
+
* ```
|
|
44975
|
+
*/
|
|
44976
|
+
/**
|
|
44977
|
+
* Generate a PNG image containing watermark text.
|
|
44978
|
+
*
|
|
44979
|
+
* The image has an alpha channel so the watermark is semi-transparent.
|
|
44980
|
+
* Works in both Node.js and browsers with zero dependencies.
|
|
44981
|
+
*/
|
|
44982
|
+
function createTextWatermarkImage(text, options) {
|
|
44983
|
+
const fontSize = options?.fontSize ?? 48;
|
|
44984
|
+
const color = options?.color ?? {
|
|
44985
|
+
r: 128,
|
|
44986
|
+
g: 128,
|
|
44987
|
+
b: 128
|
|
44988
|
+
};
|
|
44989
|
+
const opacity = Math.max(0, Math.min(100, options?.opacity ?? 40));
|
|
44990
|
+
const rotation = options?.rotation ?? -45;
|
|
44991
|
+
const padding = options?.padding ?? 20;
|
|
44992
|
+
const { width: textW, height: textH, pixels: textPixels } = renderTextBitmap(text, Math.max(1, Math.round(fontSize / GLYPH_HEIGHT)));
|
|
44993
|
+
const paddedW = textW + padding * 2;
|
|
44994
|
+
const paddedH = textH + padding * 2;
|
|
44995
|
+
const paddedPixels = new Uint8Array(paddedW * paddedH);
|
|
44996
|
+
for (let y = 0; y < textH; y++) for (let x = 0; x < textW; x++) paddedPixels[(y + padding) * paddedW + (x + padding)] = textPixels[y * textW + x];
|
|
44997
|
+
const { width: rotW, height: rotH, pixels: rotPixels } = rotateBitmap(paddedPixels, paddedW, paddedH, rotation);
|
|
44998
|
+
const alpha = Math.round(opacity / 100 * 255);
|
|
44999
|
+
const rgba = new Uint8Array(rotW * rotH * 4);
|
|
45000
|
+
for (let i = 0; i < rotW * rotH; i++) {
|
|
45001
|
+
const a = rotPixels[i];
|
|
45002
|
+
if (a > 0) {
|
|
45003
|
+
rgba[i * 4] = color.r;
|
|
45004
|
+
rgba[i * 4 + 1] = color.g;
|
|
45005
|
+
rgba[i * 4 + 2] = color.b;
|
|
45006
|
+
rgba[i * 4 + 3] = Math.round(a / 255 * alpha);
|
|
45007
|
+
}
|
|
45008
|
+
}
|
|
45009
|
+
return encodePng(rgba, rotW, rotH);
|
|
45010
|
+
}
|
|
45011
|
+
const GLYPH_WIDTH = 6;
|
|
45012
|
+
const GLYPH_HEIGHT = 8;
|
|
45013
|
+
/**
|
|
45014
|
+
* Compact glyph data: each character is 8 bytes (one byte per row, 6 bits used).
|
|
45015
|
+
* Bit 5 = leftmost pixel, bit 0 = rightmost pixel.
|
|
45016
|
+
*/
|
|
45017
|
+
const FONT_DATA = {
|
|
45018
|
+
32: [
|
|
45019
|
+
0,
|
|
45020
|
+
0,
|
|
45021
|
+
0,
|
|
45022
|
+
0,
|
|
45023
|
+
0,
|
|
45024
|
+
0,
|
|
45025
|
+
0,
|
|
45026
|
+
0
|
|
45027
|
+
],
|
|
45028
|
+
33: [
|
|
45029
|
+
4,
|
|
45030
|
+
4,
|
|
45031
|
+
4,
|
|
45032
|
+
4,
|
|
45033
|
+
4,
|
|
45034
|
+
0,
|
|
45035
|
+
4,
|
|
45036
|
+
0
|
|
45037
|
+
],
|
|
45038
|
+
34: [
|
|
45039
|
+
10,
|
|
45040
|
+
10,
|
|
45041
|
+
10,
|
|
45042
|
+
0,
|
|
45043
|
+
0,
|
|
45044
|
+
0,
|
|
45045
|
+
0,
|
|
45046
|
+
0
|
|
45047
|
+
],
|
|
45048
|
+
35: [
|
|
45049
|
+
10,
|
|
45050
|
+
10,
|
|
45051
|
+
31,
|
|
45052
|
+
10,
|
|
45053
|
+
31,
|
|
45054
|
+
10,
|
|
45055
|
+
10,
|
|
45056
|
+
0
|
|
45057
|
+
],
|
|
45058
|
+
36: [
|
|
45059
|
+
4,
|
|
45060
|
+
15,
|
|
45061
|
+
20,
|
|
45062
|
+
14,
|
|
45063
|
+
5,
|
|
45064
|
+
30,
|
|
45065
|
+
4,
|
|
45066
|
+
0
|
|
45067
|
+
],
|
|
45068
|
+
37: [
|
|
45069
|
+
24,
|
|
45070
|
+
25,
|
|
45071
|
+
2,
|
|
45072
|
+
4,
|
|
45073
|
+
8,
|
|
45074
|
+
19,
|
|
45075
|
+
3,
|
|
45076
|
+
0
|
|
45077
|
+
],
|
|
45078
|
+
38: [
|
|
45079
|
+
12,
|
|
45080
|
+
18,
|
|
45081
|
+
20,
|
|
45082
|
+
8,
|
|
45083
|
+
21,
|
|
45084
|
+
18,
|
|
45085
|
+
13,
|
|
45086
|
+
0
|
|
45087
|
+
],
|
|
45088
|
+
39: [
|
|
45089
|
+
4,
|
|
45090
|
+
4,
|
|
45091
|
+
8,
|
|
45092
|
+
0,
|
|
45093
|
+
0,
|
|
45094
|
+
0,
|
|
45095
|
+
0,
|
|
45096
|
+
0
|
|
45097
|
+
],
|
|
45098
|
+
40: [
|
|
45099
|
+
2,
|
|
45100
|
+
4,
|
|
45101
|
+
8,
|
|
45102
|
+
8,
|
|
45103
|
+
8,
|
|
45104
|
+
4,
|
|
45105
|
+
2,
|
|
45106
|
+
0
|
|
45107
|
+
],
|
|
45108
|
+
41: [
|
|
45109
|
+
8,
|
|
45110
|
+
4,
|
|
45111
|
+
2,
|
|
45112
|
+
2,
|
|
45113
|
+
2,
|
|
45114
|
+
4,
|
|
45115
|
+
8,
|
|
45116
|
+
0
|
|
45117
|
+
],
|
|
45118
|
+
42: [
|
|
45119
|
+
0,
|
|
45120
|
+
4,
|
|
45121
|
+
21,
|
|
45122
|
+
14,
|
|
45123
|
+
21,
|
|
45124
|
+
4,
|
|
45125
|
+
0,
|
|
45126
|
+
0
|
|
45127
|
+
],
|
|
45128
|
+
43: [
|
|
45129
|
+
0,
|
|
45130
|
+
4,
|
|
45131
|
+
4,
|
|
45132
|
+
31,
|
|
45133
|
+
4,
|
|
45134
|
+
4,
|
|
45135
|
+
0,
|
|
45136
|
+
0
|
|
45137
|
+
],
|
|
45138
|
+
44: [
|
|
45139
|
+
0,
|
|
45140
|
+
0,
|
|
45141
|
+
0,
|
|
45142
|
+
0,
|
|
45143
|
+
0,
|
|
45144
|
+
4,
|
|
45145
|
+
4,
|
|
45146
|
+
8
|
|
45147
|
+
],
|
|
45148
|
+
45: [
|
|
45149
|
+
0,
|
|
45150
|
+
0,
|
|
45151
|
+
0,
|
|
45152
|
+
31,
|
|
45153
|
+
0,
|
|
45154
|
+
0,
|
|
45155
|
+
0,
|
|
45156
|
+
0
|
|
45157
|
+
],
|
|
45158
|
+
46: [
|
|
45159
|
+
0,
|
|
45160
|
+
0,
|
|
45161
|
+
0,
|
|
45162
|
+
0,
|
|
45163
|
+
0,
|
|
45164
|
+
0,
|
|
45165
|
+
4,
|
|
45166
|
+
0
|
|
45167
|
+
],
|
|
45168
|
+
47: [
|
|
45169
|
+
0,
|
|
45170
|
+
1,
|
|
45171
|
+
2,
|
|
45172
|
+
4,
|
|
45173
|
+
8,
|
|
45174
|
+
16,
|
|
45175
|
+
0,
|
|
45176
|
+
0
|
|
45177
|
+
],
|
|
45178
|
+
48: [
|
|
45179
|
+
14,
|
|
45180
|
+
17,
|
|
45181
|
+
19,
|
|
45182
|
+
21,
|
|
45183
|
+
25,
|
|
45184
|
+
17,
|
|
45185
|
+
14,
|
|
45186
|
+
0
|
|
45187
|
+
],
|
|
45188
|
+
49: [
|
|
45189
|
+
4,
|
|
45190
|
+
12,
|
|
45191
|
+
4,
|
|
45192
|
+
4,
|
|
45193
|
+
4,
|
|
45194
|
+
4,
|
|
45195
|
+
14,
|
|
45196
|
+
0
|
|
45197
|
+
],
|
|
45198
|
+
50: [
|
|
45199
|
+
14,
|
|
45200
|
+
17,
|
|
45201
|
+
1,
|
|
45202
|
+
2,
|
|
45203
|
+
4,
|
|
45204
|
+
8,
|
|
45205
|
+
31,
|
|
45206
|
+
0
|
|
45207
|
+
],
|
|
45208
|
+
51: [
|
|
45209
|
+
31,
|
|
45210
|
+
2,
|
|
45211
|
+
4,
|
|
45212
|
+
2,
|
|
45213
|
+
1,
|
|
45214
|
+
17,
|
|
45215
|
+
14,
|
|
45216
|
+
0
|
|
45217
|
+
],
|
|
45218
|
+
52: [
|
|
45219
|
+
2,
|
|
45220
|
+
6,
|
|
45221
|
+
10,
|
|
45222
|
+
18,
|
|
45223
|
+
31,
|
|
45224
|
+
2,
|
|
45225
|
+
2,
|
|
45226
|
+
0
|
|
45227
|
+
],
|
|
45228
|
+
53: [
|
|
45229
|
+
31,
|
|
45230
|
+
16,
|
|
45231
|
+
30,
|
|
45232
|
+
1,
|
|
45233
|
+
1,
|
|
45234
|
+
17,
|
|
45235
|
+
14,
|
|
45236
|
+
0
|
|
45237
|
+
],
|
|
45238
|
+
54: [
|
|
45239
|
+
6,
|
|
45240
|
+
8,
|
|
45241
|
+
16,
|
|
45242
|
+
30,
|
|
45243
|
+
17,
|
|
45244
|
+
17,
|
|
45245
|
+
14,
|
|
45246
|
+
0
|
|
45247
|
+
],
|
|
45248
|
+
55: [
|
|
45249
|
+
31,
|
|
45250
|
+
1,
|
|
45251
|
+
2,
|
|
45252
|
+
4,
|
|
45253
|
+
8,
|
|
45254
|
+
8,
|
|
45255
|
+
8,
|
|
45256
|
+
0
|
|
45257
|
+
],
|
|
45258
|
+
56: [
|
|
45259
|
+
14,
|
|
45260
|
+
17,
|
|
45261
|
+
17,
|
|
45262
|
+
14,
|
|
45263
|
+
17,
|
|
45264
|
+
17,
|
|
45265
|
+
14,
|
|
45266
|
+
0
|
|
45267
|
+
],
|
|
45268
|
+
57: [
|
|
45269
|
+
14,
|
|
45270
|
+
17,
|
|
45271
|
+
17,
|
|
45272
|
+
15,
|
|
45273
|
+
1,
|
|
45274
|
+
2,
|
|
45275
|
+
12,
|
|
45276
|
+
0
|
|
45277
|
+
],
|
|
45278
|
+
58: [
|
|
45279
|
+
0,
|
|
45280
|
+
0,
|
|
45281
|
+
4,
|
|
45282
|
+
0,
|
|
45283
|
+
0,
|
|
45284
|
+
4,
|
|
45285
|
+
0,
|
|
45286
|
+
0
|
|
45287
|
+
],
|
|
45288
|
+
59: [
|
|
45289
|
+
0,
|
|
45290
|
+
0,
|
|
45291
|
+
4,
|
|
45292
|
+
0,
|
|
45293
|
+
0,
|
|
45294
|
+
4,
|
|
45295
|
+
4,
|
|
45296
|
+
8
|
|
45297
|
+
],
|
|
45298
|
+
60: [
|
|
45299
|
+
2,
|
|
45300
|
+
4,
|
|
45301
|
+
8,
|
|
45302
|
+
16,
|
|
45303
|
+
8,
|
|
45304
|
+
4,
|
|
45305
|
+
2,
|
|
45306
|
+
0
|
|
45307
|
+
],
|
|
45308
|
+
61: [
|
|
45309
|
+
0,
|
|
45310
|
+
0,
|
|
45311
|
+
31,
|
|
45312
|
+
0,
|
|
45313
|
+
31,
|
|
45314
|
+
0,
|
|
45315
|
+
0,
|
|
45316
|
+
0
|
|
45317
|
+
],
|
|
45318
|
+
62: [
|
|
45319
|
+
8,
|
|
45320
|
+
4,
|
|
45321
|
+
2,
|
|
45322
|
+
1,
|
|
45323
|
+
2,
|
|
45324
|
+
4,
|
|
45325
|
+
8,
|
|
45326
|
+
0
|
|
45327
|
+
],
|
|
45328
|
+
63: [
|
|
45329
|
+
14,
|
|
45330
|
+
17,
|
|
45331
|
+
1,
|
|
45332
|
+
2,
|
|
45333
|
+
4,
|
|
45334
|
+
0,
|
|
45335
|
+
4,
|
|
45336
|
+
0
|
|
45337
|
+
],
|
|
45338
|
+
64: [
|
|
45339
|
+
14,
|
|
45340
|
+
17,
|
|
45341
|
+
23,
|
|
45342
|
+
21,
|
|
45343
|
+
23,
|
|
45344
|
+
16,
|
|
45345
|
+
14,
|
|
45346
|
+
0
|
|
45347
|
+
],
|
|
45348
|
+
65: [
|
|
45349
|
+
14,
|
|
45350
|
+
17,
|
|
45351
|
+
17,
|
|
45352
|
+
31,
|
|
45353
|
+
17,
|
|
45354
|
+
17,
|
|
45355
|
+
17,
|
|
45356
|
+
0
|
|
45357
|
+
],
|
|
45358
|
+
66: [
|
|
45359
|
+
30,
|
|
45360
|
+
17,
|
|
45361
|
+
17,
|
|
45362
|
+
30,
|
|
45363
|
+
17,
|
|
45364
|
+
17,
|
|
45365
|
+
30,
|
|
45366
|
+
0
|
|
45367
|
+
],
|
|
45368
|
+
67: [
|
|
45369
|
+
14,
|
|
45370
|
+
17,
|
|
45371
|
+
16,
|
|
45372
|
+
16,
|
|
45373
|
+
16,
|
|
45374
|
+
17,
|
|
45375
|
+
14,
|
|
45376
|
+
0
|
|
45377
|
+
],
|
|
45378
|
+
68: [
|
|
45379
|
+
28,
|
|
45380
|
+
18,
|
|
45381
|
+
17,
|
|
45382
|
+
17,
|
|
45383
|
+
17,
|
|
45384
|
+
18,
|
|
45385
|
+
28,
|
|
45386
|
+
0
|
|
45387
|
+
],
|
|
45388
|
+
69: [
|
|
45389
|
+
31,
|
|
45390
|
+
16,
|
|
45391
|
+
16,
|
|
45392
|
+
30,
|
|
45393
|
+
16,
|
|
45394
|
+
16,
|
|
45395
|
+
31,
|
|
45396
|
+
0
|
|
45397
|
+
],
|
|
45398
|
+
70: [
|
|
45399
|
+
31,
|
|
45400
|
+
16,
|
|
45401
|
+
16,
|
|
45402
|
+
30,
|
|
45403
|
+
16,
|
|
45404
|
+
16,
|
|
45405
|
+
16,
|
|
45406
|
+
0
|
|
45407
|
+
],
|
|
45408
|
+
71: [
|
|
45409
|
+
14,
|
|
45410
|
+
17,
|
|
45411
|
+
16,
|
|
45412
|
+
23,
|
|
45413
|
+
17,
|
|
45414
|
+
17,
|
|
45415
|
+
15,
|
|
45416
|
+
0
|
|
45417
|
+
],
|
|
45418
|
+
72: [
|
|
45419
|
+
17,
|
|
45420
|
+
17,
|
|
45421
|
+
17,
|
|
45422
|
+
31,
|
|
45423
|
+
17,
|
|
45424
|
+
17,
|
|
45425
|
+
17,
|
|
45426
|
+
0
|
|
45427
|
+
],
|
|
45428
|
+
73: [
|
|
45429
|
+
14,
|
|
45430
|
+
4,
|
|
45431
|
+
4,
|
|
45432
|
+
4,
|
|
45433
|
+
4,
|
|
45434
|
+
4,
|
|
45435
|
+
14,
|
|
45436
|
+
0
|
|
45437
|
+
],
|
|
45438
|
+
74: [
|
|
45439
|
+
7,
|
|
45440
|
+
2,
|
|
45441
|
+
2,
|
|
45442
|
+
2,
|
|
45443
|
+
2,
|
|
45444
|
+
18,
|
|
45445
|
+
12,
|
|
45446
|
+
0
|
|
45447
|
+
],
|
|
45448
|
+
75: [
|
|
45449
|
+
17,
|
|
45450
|
+
18,
|
|
45451
|
+
20,
|
|
45452
|
+
24,
|
|
45453
|
+
20,
|
|
45454
|
+
18,
|
|
45455
|
+
17,
|
|
45456
|
+
0
|
|
45457
|
+
],
|
|
45458
|
+
76: [
|
|
45459
|
+
16,
|
|
45460
|
+
16,
|
|
45461
|
+
16,
|
|
45462
|
+
16,
|
|
45463
|
+
16,
|
|
45464
|
+
16,
|
|
45465
|
+
31,
|
|
45466
|
+
0
|
|
45467
|
+
],
|
|
45468
|
+
77: [
|
|
45469
|
+
17,
|
|
45470
|
+
27,
|
|
45471
|
+
21,
|
|
45472
|
+
21,
|
|
45473
|
+
17,
|
|
45474
|
+
17,
|
|
45475
|
+
17,
|
|
45476
|
+
0
|
|
45477
|
+
],
|
|
45478
|
+
78: [
|
|
45479
|
+
17,
|
|
45480
|
+
25,
|
|
45481
|
+
21,
|
|
45482
|
+
19,
|
|
45483
|
+
17,
|
|
45484
|
+
17,
|
|
45485
|
+
17,
|
|
45486
|
+
0
|
|
45487
|
+
],
|
|
45488
|
+
79: [
|
|
45489
|
+
14,
|
|
45490
|
+
17,
|
|
45491
|
+
17,
|
|
45492
|
+
17,
|
|
45493
|
+
17,
|
|
45494
|
+
17,
|
|
45495
|
+
14,
|
|
45496
|
+
0
|
|
45497
|
+
],
|
|
45498
|
+
80: [
|
|
45499
|
+
30,
|
|
45500
|
+
17,
|
|
45501
|
+
17,
|
|
45502
|
+
30,
|
|
45503
|
+
16,
|
|
45504
|
+
16,
|
|
45505
|
+
16,
|
|
45506
|
+
0
|
|
45507
|
+
],
|
|
45508
|
+
81: [
|
|
45509
|
+
14,
|
|
45510
|
+
17,
|
|
45511
|
+
17,
|
|
45512
|
+
17,
|
|
45513
|
+
21,
|
|
45514
|
+
18,
|
|
45515
|
+
13,
|
|
45516
|
+
0
|
|
45517
|
+
],
|
|
45518
|
+
82: [
|
|
45519
|
+
30,
|
|
45520
|
+
17,
|
|
45521
|
+
17,
|
|
45522
|
+
30,
|
|
45523
|
+
20,
|
|
45524
|
+
18,
|
|
45525
|
+
17,
|
|
45526
|
+
0
|
|
45527
|
+
],
|
|
45528
|
+
83: [
|
|
45529
|
+
15,
|
|
45530
|
+
16,
|
|
45531
|
+
16,
|
|
45532
|
+
14,
|
|
45533
|
+
1,
|
|
45534
|
+
1,
|
|
45535
|
+
30,
|
|
45536
|
+
0
|
|
45537
|
+
],
|
|
45538
|
+
84: [
|
|
45539
|
+
31,
|
|
45540
|
+
4,
|
|
45541
|
+
4,
|
|
45542
|
+
4,
|
|
45543
|
+
4,
|
|
45544
|
+
4,
|
|
45545
|
+
4,
|
|
45546
|
+
0
|
|
45547
|
+
],
|
|
45548
|
+
85: [
|
|
45549
|
+
17,
|
|
45550
|
+
17,
|
|
45551
|
+
17,
|
|
45552
|
+
17,
|
|
45553
|
+
17,
|
|
45554
|
+
17,
|
|
45555
|
+
14,
|
|
45556
|
+
0
|
|
45557
|
+
],
|
|
45558
|
+
86: [
|
|
45559
|
+
17,
|
|
45560
|
+
17,
|
|
45561
|
+
17,
|
|
45562
|
+
17,
|
|
45563
|
+
17,
|
|
45564
|
+
10,
|
|
45565
|
+
4,
|
|
45566
|
+
0
|
|
45567
|
+
],
|
|
45568
|
+
87: [
|
|
45569
|
+
17,
|
|
45570
|
+
17,
|
|
45571
|
+
17,
|
|
45572
|
+
21,
|
|
45573
|
+
21,
|
|
45574
|
+
27,
|
|
45575
|
+
17,
|
|
45576
|
+
0
|
|
45577
|
+
],
|
|
45578
|
+
88: [
|
|
45579
|
+
17,
|
|
45580
|
+
17,
|
|
45581
|
+
10,
|
|
45582
|
+
4,
|
|
45583
|
+
10,
|
|
45584
|
+
17,
|
|
45585
|
+
17,
|
|
45586
|
+
0
|
|
45587
|
+
],
|
|
45588
|
+
89: [
|
|
45589
|
+
17,
|
|
45590
|
+
17,
|
|
45591
|
+
10,
|
|
45592
|
+
4,
|
|
45593
|
+
4,
|
|
45594
|
+
4,
|
|
45595
|
+
4,
|
|
45596
|
+
0
|
|
45597
|
+
],
|
|
45598
|
+
90: [
|
|
45599
|
+
31,
|
|
45600
|
+
1,
|
|
45601
|
+
2,
|
|
45602
|
+
4,
|
|
45603
|
+
8,
|
|
45604
|
+
16,
|
|
45605
|
+
31,
|
|
45606
|
+
0
|
|
45607
|
+
],
|
|
45608
|
+
91: [
|
|
45609
|
+
14,
|
|
45610
|
+
8,
|
|
45611
|
+
8,
|
|
45612
|
+
8,
|
|
45613
|
+
8,
|
|
45614
|
+
8,
|
|
45615
|
+
14,
|
|
45616
|
+
0
|
|
45617
|
+
],
|
|
45618
|
+
92: [
|
|
45619
|
+
0,
|
|
45620
|
+
16,
|
|
45621
|
+
8,
|
|
45622
|
+
4,
|
|
45623
|
+
2,
|
|
45624
|
+
1,
|
|
45625
|
+
0,
|
|
45626
|
+
0
|
|
45627
|
+
],
|
|
45628
|
+
93: [
|
|
45629
|
+
14,
|
|
45630
|
+
2,
|
|
45631
|
+
2,
|
|
45632
|
+
2,
|
|
45633
|
+
2,
|
|
45634
|
+
2,
|
|
45635
|
+
14,
|
|
45636
|
+
0
|
|
45637
|
+
],
|
|
45638
|
+
94: [
|
|
45639
|
+
4,
|
|
45640
|
+
10,
|
|
45641
|
+
17,
|
|
45642
|
+
0,
|
|
45643
|
+
0,
|
|
45644
|
+
0,
|
|
45645
|
+
0,
|
|
45646
|
+
0
|
|
45647
|
+
],
|
|
45648
|
+
95: [
|
|
45649
|
+
0,
|
|
45650
|
+
0,
|
|
45651
|
+
0,
|
|
45652
|
+
0,
|
|
45653
|
+
0,
|
|
45654
|
+
0,
|
|
45655
|
+
31,
|
|
45656
|
+
0
|
|
45657
|
+
],
|
|
45658
|
+
96: [
|
|
45659
|
+
8,
|
|
45660
|
+
4,
|
|
45661
|
+
2,
|
|
45662
|
+
0,
|
|
45663
|
+
0,
|
|
45664
|
+
0,
|
|
45665
|
+
0,
|
|
45666
|
+
0
|
|
45667
|
+
],
|
|
45668
|
+
97: [
|
|
45669
|
+
0,
|
|
45670
|
+
0,
|
|
45671
|
+
14,
|
|
45672
|
+
1,
|
|
45673
|
+
15,
|
|
45674
|
+
17,
|
|
45675
|
+
15,
|
|
45676
|
+
0
|
|
45677
|
+
],
|
|
45678
|
+
98: [
|
|
45679
|
+
16,
|
|
45680
|
+
16,
|
|
45681
|
+
22,
|
|
45682
|
+
25,
|
|
45683
|
+
17,
|
|
45684
|
+
17,
|
|
45685
|
+
30,
|
|
45686
|
+
0
|
|
45687
|
+
],
|
|
45688
|
+
99: [
|
|
45689
|
+
0,
|
|
45690
|
+
0,
|
|
45691
|
+
14,
|
|
45692
|
+
16,
|
|
45693
|
+
16,
|
|
45694
|
+
17,
|
|
45695
|
+
14,
|
|
45696
|
+
0
|
|
45697
|
+
],
|
|
45698
|
+
100: [
|
|
45699
|
+
1,
|
|
45700
|
+
1,
|
|
45701
|
+
13,
|
|
45702
|
+
19,
|
|
45703
|
+
17,
|
|
45704
|
+
17,
|
|
45705
|
+
15,
|
|
45706
|
+
0
|
|
45707
|
+
],
|
|
45708
|
+
101: [
|
|
45709
|
+
0,
|
|
45710
|
+
0,
|
|
45711
|
+
14,
|
|
45712
|
+
17,
|
|
45713
|
+
31,
|
|
45714
|
+
16,
|
|
45715
|
+
14,
|
|
45716
|
+
0
|
|
45717
|
+
],
|
|
45718
|
+
102: [
|
|
45719
|
+
6,
|
|
45720
|
+
9,
|
|
45721
|
+
8,
|
|
45722
|
+
28,
|
|
45723
|
+
8,
|
|
45724
|
+
8,
|
|
45725
|
+
8,
|
|
45726
|
+
0
|
|
45727
|
+
],
|
|
45728
|
+
103: [
|
|
45729
|
+
0,
|
|
45730
|
+
0,
|
|
45731
|
+
15,
|
|
45732
|
+
17,
|
|
45733
|
+
15,
|
|
45734
|
+
1,
|
|
45735
|
+
14,
|
|
45736
|
+
0
|
|
45737
|
+
],
|
|
45738
|
+
104: [
|
|
45739
|
+
16,
|
|
45740
|
+
16,
|
|
45741
|
+
22,
|
|
45742
|
+
25,
|
|
45743
|
+
17,
|
|
45744
|
+
17,
|
|
45745
|
+
17,
|
|
45746
|
+
0
|
|
45747
|
+
],
|
|
45748
|
+
105: [
|
|
45749
|
+
4,
|
|
45750
|
+
0,
|
|
45751
|
+
12,
|
|
45752
|
+
4,
|
|
45753
|
+
4,
|
|
45754
|
+
4,
|
|
45755
|
+
14,
|
|
45756
|
+
0
|
|
45757
|
+
],
|
|
45758
|
+
106: [
|
|
45759
|
+
2,
|
|
45760
|
+
0,
|
|
45761
|
+
6,
|
|
45762
|
+
2,
|
|
45763
|
+
2,
|
|
45764
|
+
18,
|
|
45765
|
+
12,
|
|
45766
|
+
0
|
|
45767
|
+
],
|
|
45768
|
+
107: [
|
|
45769
|
+
16,
|
|
45770
|
+
16,
|
|
45771
|
+
18,
|
|
45772
|
+
20,
|
|
45773
|
+
24,
|
|
45774
|
+
20,
|
|
45775
|
+
18,
|
|
45776
|
+
0
|
|
45777
|
+
],
|
|
45778
|
+
108: [
|
|
45779
|
+
12,
|
|
45780
|
+
4,
|
|
45781
|
+
4,
|
|
45782
|
+
4,
|
|
45783
|
+
4,
|
|
45784
|
+
4,
|
|
45785
|
+
14,
|
|
45786
|
+
0
|
|
45787
|
+
],
|
|
45788
|
+
109: [
|
|
45789
|
+
0,
|
|
45790
|
+
0,
|
|
45791
|
+
26,
|
|
45792
|
+
21,
|
|
45793
|
+
21,
|
|
45794
|
+
17,
|
|
45795
|
+
17,
|
|
45796
|
+
0
|
|
45797
|
+
],
|
|
45798
|
+
110: [
|
|
45799
|
+
0,
|
|
45800
|
+
0,
|
|
45801
|
+
22,
|
|
45802
|
+
25,
|
|
45803
|
+
17,
|
|
45804
|
+
17,
|
|
45805
|
+
17,
|
|
45806
|
+
0
|
|
45807
|
+
],
|
|
45808
|
+
111: [
|
|
45809
|
+
0,
|
|
45810
|
+
0,
|
|
45811
|
+
14,
|
|
45812
|
+
17,
|
|
45813
|
+
17,
|
|
45814
|
+
17,
|
|
45815
|
+
14,
|
|
45816
|
+
0
|
|
45817
|
+
],
|
|
45818
|
+
112: [
|
|
45819
|
+
0,
|
|
45820
|
+
0,
|
|
45821
|
+
30,
|
|
45822
|
+
17,
|
|
45823
|
+
30,
|
|
45824
|
+
16,
|
|
45825
|
+
16,
|
|
45826
|
+
0
|
|
45827
|
+
],
|
|
45828
|
+
113: [
|
|
45829
|
+
0,
|
|
45830
|
+
0,
|
|
45831
|
+
13,
|
|
45832
|
+
19,
|
|
45833
|
+
15,
|
|
45834
|
+
1,
|
|
45835
|
+
1,
|
|
45836
|
+
0
|
|
45837
|
+
],
|
|
45838
|
+
114: [
|
|
45839
|
+
0,
|
|
45840
|
+
0,
|
|
45841
|
+
22,
|
|
45842
|
+
25,
|
|
45843
|
+
16,
|
|
45844
|
+
16,
|
|
45845
|
+
16,
|
|
45846
|
+
0
|
|
45847
|
+
],
|
|
45848
|
+
115: [
|
|
45849
|
+
0,
|
|
45850
|
+
0,
|
|
45851
|
+
14,
|
|
45852
|
+
16,
|
|
45853
|
+
14,
|
|
45854
|
+
1,
|
|
45855
|
+
30,
|
|
45856
|
+
0
|
|
45857
|
+
],
|
|
45858
|
+
116: [
|
|
45859
|
+
8,
|
|
45860
|
+
8,
|
|
45861
|
+
28,
|
|
45862
|
+
8,
|
|
45863
|
+
8,
|
|
45864
|
+
9,
|
|
45865
|
+
6,
|
|
45866
|
+
0
|
|
45867
|
+
],
|
|
45868
|
+
117: [
|
|
45869
|
+
0,
|
|
45870
|
+
0,
|
|
45871
|
+
17,
|
|
45872
|
+
17,
|
|
45873
|
+
17,
|
|
45874
|
+
19,
|
|
45875
|
+
13,
|
|
45876
|
+
0
|
|
45877
|
+
],
|
|
45878
|
+
118: [
|
|
45879
|
+
0,
|
|
45880
|
+
0,
|
|
45881
|
+
17,
|
|
45882
|
+
17,
|
|
45883
|
+
17,
|
|
45884
|
+
10,
|
|
45885
|
+
4,
|
|
45886
|
+
0
|
|
45887
|
+
],
|
|
45888
|
+
119: [
|
|
45889
|
+
0,
|
|
45890
|
+
0,
|
|
45891
|
+
17,
|
|
45892
|
+
17,
|
|
45893
|
+
21,
|
|
45894
|
+
21,
|
|
45895
|
+
10,
|
|
45896
|
+
0
|
|
45897
|
+
],
|
|
45898
|
+
120: [
|
|
45899
|
+
0,
|
|
45900
|
+
0,
|
|
45901
|
+
17,
|
|
45902
|
+
10,
|
|
45903
|
+
4,
|
|
45904
|
+
10,
|
|
45905
|
+
17,
|
|
45906
|
+
0
|
|
45907
|
+
],
|
|
45908
|
+
121: [
|
|
45909
|
+
0,
|
|
45910
|
+
0,
|
|
45911
|
+
17,
|
|
45912
|
+
17,
|
|
45913
|
+
15,
|
|
45914
|
+
1,
|
|
45915
|
+
14,
|
|
45916
|
+
0
|
|
45917
|
+
],
|
|
45918
|
+
122: [
|
|
45919
|
+
0,
|
|
45920
|
+
0,
|
|
45921
|
+
31,
|
|
45922
|
+
2,
|
|
45923
|
+
4,
|
|
45924
|
+
8,
|
|
45925
|
+
31,
|
|
45926
|
+
0
|
|
45927
|
+
],
|
|
45928
|
+
123: [
|
|
45929
|
+
2,
|
|
45930
|
+
4,
|
|
45931
|
+
4,
|
|
45932
|
+
8,
|
|
45933
|
+
4,
|
|
45934
|
+
4,
|
|
45935
|
+
2,
|
|
45936
|
+
0
|
|
45937
|
+
],
|
|
45938
|
+
124: [
|
|
45939
|
+
4,
|
|
45940
|
+
4,
|
|
45941
|
+
4,
|
|
45942
|
+
4,
|
|
45943
|
+
4,
|
|
45944
|
+
4,
|
|
45945
|
+
4,
|
|
45946
|
+
0
|
|
45947
|
+
],
|
|
45948
|
+
125: [
|
|
45949
|
+
8,
|
|
45950
|
+
4,
|
|
45951
|
+
4,
|
|
45952
|
+
2,
|
|
45953
|
+
4,
|
|
45954
|
+
4,
|
|
45955
|
+
8,
|
|
45956
|
+
0
|
|
45957
|
+
],
|
|
45958
|
+
126: [
|
|
45959
|
+
0,
|
|
45960
|
+
0,
|
|
45961
|
+
8,
|
|
45962
|
+
21,
|
|
45963
|
+
2,
|
|
45964
|
+
0,
|
|
45965
|
+
0,
|
|
45966
|
+
0
|
|
45967
|
+
]
|
|
45968
|
+
};
|
|
45969
|
+
/** Render text string to a grayscale bitmap (0 = transparent, 255 = opaque). */
|
|
45970
|
+
function renderTextBitmap(text, scale) {
|
|
45971
|
+
const charW = GLYPH_WIDTH * scale;
|
|
45972
|
+
const charH = GLYPH_HEIGHT * scale;
|
|
45973
|
+
const width = text.length * charW;
|
|
45974
|
+
const height = charH;
|
|
45975
|
+
const pixels = new Uint8Array(width * height);
|
|
45976
|
+
for (let ci = 0; ci < text.length; ci++) {
|
|
45977
|
+
const glyph = FONT_DATA[text.charCodeAt(ci)] ?? FONT_DATA[63];
|
|
45978
|
+
const xOff = ci * charW;
|
|
45979
|
+
for (let row = 0; row < GLYPH_HEIGHT; row++) {
|
|
45980
|
+
const bits = glyph[row];
|
|
45981
|
+
for (let col = 0; col < GLYPH_WIDTH; col++) if (bits & 1 << GLYPH_WIDTH - 1 - col) for (let sy = 0; sy < scale; sy++) for (let sx = 0; sx < scale; sx++) {
|
|
45982
|
+
const px = xOff + col * scale + sx;
|
|
45983
|
+
const py = row * scale + sy;
|
|
45984
|
+
if (px < width && py < height) pixels[py * width + px] = 255;
|
|
45985
|
+
}
|
|
45986
|
+
}
|
|
45987
|
+
}
|
|
45988
|
+
return {
|
|
45989
|
+
width,
|
|
45990
|
+
height,
|
|
45991
|
+
pixels
|
|
45992
|
+
};
|
|
45993
|
+
}
|
|
45994
|
+
/** Rotate a grayscale bitmap by the given angle in degrees. */
|
|
45995
|
+
function rotateBitmap(pixels, srcW, srcH, angleDeg) {
|
|
45996
|
+
if (angleDeg === 0) return {
|
|
45997
|
+
width: srcW,
|
|
45998
|
+
height: srcH,
|
|
45999
|
+
pixels
|
|
46000
|
+
};
|
|
46001
|
+
const rad = angleDeg * Math.PI / 180;
|
|
46002
|
+
const cos = Math.cos(rad);
|
|
46003
|
+
const sin = Math.sin(rad);
|
|
46004
|
+
const corners = [
|
|
46005
|
+
{
|
|
46006
|
+
x: 0,
|
|
46007
|
+
y: 0
|
|
46008
|
+
},
|
|
46009
|
+
{
|
|
46010
|
+
x: srcW,
|
|
46011
|
+
y: 0
|
|
46012
|
+
},
|
|
46013
|
+
{
|
|
46014
|
+
x: srcW,
|
|
46015
|
+
y: srcH
|
|
46016
|
+
},
|
|
46017
|
+
{
|
|
46018
|
+
x: 0,
|
|
46019
|
+
y: srcH
|
|
46020
|
+
}
|
|
46021
|
+
];
|
|
46022
|
+
let minX = Infinity;
|
|
46023
|
+
let minY = Infinity;
|
|
46024
|
+
let maxX = -Infinity;
|
|
46025
|
+
let maxY = -Infinity;
|
|
46026
|
+
for (const c of corners) {
|
|
46027
|
+
const rx = c.x * cos - c.y * sin;
|
|
46028
|
+
const ry = c.x * sin + c.y * cos;
|
|
46029
|
+
minX = Math.min(minX, rx);
|
|
46030
|
+
minY = Math.min(minY, ry);
|
|
46031
|
+
maxX = Math.max(maxX, rx);
|
|
46032
|
+
maxY = Math.max(maxY, ry);
|
|
46033
|
+
}
|
|
46034
|
+
const dstW = Math.ceil(maxX - minX);
|
|
46035
|
+
const dstH = Math.ceil(maxY - minY);
|
|
46036
|
+
const dst = new Uint8Array(dstW * dstH);
|
|
46037
|
+
const invCos = cos;
|
|
46038
|
+
const invSin = -sin;
|
|
46039
|
+
for (let dy = 0; dy < dstH; dy++) for (let dx = 0; dx < dstW; dx++) {
|
|
46040
|
+
const wx = dx + minX;
|
|
46041
|
+
const wy = dy + minY;
|
|
46042
|
+
const sx = Math.round(wx * invCos - wy * invSin);
|
|
46043
|
+
const sy = Math.round(wx * invSin + wy * invCos);
|
|
46044
|
+
if (sx >= 0 && sx < srcW && sy >= 0 && sy < srcH) dst[dy * dstW + dx] = pixels[sy * srcW + sx];
|
|
46045
|
+
}
|
|
46046
|
+
return {
|
|
46047
|
+
width: dstW,
|
|
46048
|
+
height: dstH,
|
|
46049
|
+
pixels: dst
|
|
46050
|
+
};
|
|
46051
|
+
}
|
|
46052
|
+
/** Encode RGBA pixel data to a PNG file. */
|
|
46053
|
+
function encodePng(rgba, width, height) {
|
|
46054
|
+
const rawRowSize = 1 + width * 4;
|
|
46055
|
+
const rawData = new Uint8Array(rawRowSize * height);
|
|
46056
|
+
for (let y = 0; y < height; y++) {
|
|
46057
|
+
rawData[y * rawRowSize] = 0;
|
|
46058
|
+
rawData.set(rgba.subarray(y * width * 4, (y + 1) * width * 4), y * rawRowSize + 1);
|
|
46059
|
+
}
|
|
46060
|
+
const deflated = zlibCompress(rawData);
|
|
46061
|
+
const sig = new Uint8Array([
|
|
46062
|
+
137,
|
|
46063
|
+
80,
|
|
46064
|
+
78,
|
|
46065
|
+
71,
|
|
46066
|
+
13,
|
|
46067
|
+
10,
|
|
46068
|
+
26,
|
|
46069
|
+
10
|
|
46070
|
+
]);
|
|
46071
|
+
const ihdr = new Uint8Array(13);
|
|
46072
|
+
writeU32BE(ihdr, 0, width);
|
|
46073
|
+
writeU32BE(ihdr, 4, height);
|
|
46074
|
+
ihdr[8] = 8;
|
|
46075
|
+
ihdr[9] = 6;
|
|
46076
|
+
ihdr[10] = 0;
|
|
46077
|
+
ihdr[11] = 0;
|
|
46078
|
+
ihdr[12] = 0;
|
|
46079
|
+
const ihdrChunk = pngChunk(1229472850, ihdr);
|
|
46080
|
+
const idatChunk = pngChunk(1229209940, deflated);
|
|
46081
|
+
const iendChunk = pngChunk(1229278788, new Uint8Array(0));
|
|
46082
|
+
const result = new Uint8Array(sig.length + ihdrChunk.length + idatChunk.length + iendChunk.length);
|
|
46083
|
+
let offset = 0;
|
|
46084
|
+
result.set(sig, offset);
|
|
46085
|
+
offset += sig.length;
|
|
46086
|
+
result.set(ihdrChunk, offset);
|
|
46087
|
+
offset += ihdrChunk.length;
|
|
46088
|
+
result.set(idatChunk, offset);
|
|
46089
|
+
offset += idatChunk.length;
|
|
46090
|
+
result.set(iendChunk, offset);
|
|
46091
|
+
return result;
|
|
46092
|
+
}
|
|
46093
|
+
/** Build a PNG chunk: length(4) + type(4) + data + crc32(4). */
|
|
46094
|
+
function pngChunk(type, data) {
|
|
46095
|
+
const chunk = new Uint8Array(12 + data.length);
|
|
46096
|
+
writeU32BE(chunk, 0, data.length);
|
|
46097
|
+
writeU32BE(chunk, 4, type);
|
|
46098
|
+
chunk.set(data, 8);
|
|
46099
|
+
const crc = crc32(chunk.subarray(4, 8 + data.length));
|
|
46100
|
+
writeU32BE(chunk, 8 + data.length, crc);
|
|
46101
|
+
return chunk;
|
|
46102
|
+
}
|
|
46103
|
+
/** Write a 32-bit big-endian unsigned int. */
|
|
46104
|
+
function writeU32BE(buf, offset, value) {
|
|
46105
|
+
buf[offset] = value >>> 24 & 255;
|
|
46106
|
+
buf[offset + 1] = value >>> 16 & 255;
|
|
46107
|
+
buf[offset + 2] = value >>> 8 & 255;
|
|
46108
|
+
buf[offset + 3] = value & 255;
|
|
46109
|
+
}
|
|
46110
|
+
/** Wrap raw data in a zlib stream with deflate compression. */
|
|
46111
|
+
function zlibCompress(data) {
|
|
46112
|
+
const deflated = deflateRawCompressed(data, 6);
|
|
46113
|
+
const adler = adler32(data);
|
|
46114
|
+
const result = new Uint8Array(2 + deflated.length + 4);
|
|
46115
|
+
result[0] = 120;
|
|
46116
|
+
result[1] = 1;
|
|
46117
|
+
result.set(deflated, 2);
|
|
46118
|
+
writeU32BE(result, 2 + deflated.length, adler);
|
|
46119
|
+
return result;
|
|
46120
|
+
}
|
|
46121
|
+
/** Compute Adler-32 checksum. */
|
|
46122
|
+
function adler32(data) {
|
|
46123
|
+
let a = 1;
|
|
46124
|
+
let b = 0;
|
|
46125
|
+
for (let i = 0; i < data.length; i++) {
|
|
46126
|
+
a = (a + data[i]) % 65521;
|
|
46127
|
+
b = (b + a) % 65521;
|
|
46128
|
+
}
|
|
46129
|
+
return b << 16 | a;
|
|
46130
|
+
}
|
|
46131
|
+
/** CRC32 lookup table. */
|
|
46132
|
+
const CRC_TABLE = /* @__PURE__ */ (() => {
|
|
46133
|
+
const table = new Uint32Array(256);
|
|
46134
|
+
for (let n = 0; n < 256; n++) {
|
|
46135
|
+
let c = n;
|
|
46136
|
+
for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
46137
|
+
table[n] = c;
|
|
46138
|
+
}
|
|
46139
|
+
return table;
|
|
46140
|
+
})();
|
|
46141
|
+
/** Compute CRC32 checksum. */
|
|
46142
|
+
function crc32(data) {
|
|
46143
|
+
let crc = 4294967295;
|
|
46144
|
+
for (let i = 0; i < data.length; i++) crc = CRC_TABLE[(crc ^ data[i]) & 255] ^ crc >>> 8;
|
|
46145
|
+
return (crc ^ 4294967295) >>> 0;
|
|
46146
|
+
}
|
|
46147
|
+
//#endregion
|
|
44523
46148
|
//#region src/modules/pdf/types.ts
|
|
44524
46149
|
/**
|
|
44525
46150
|
* Type definitions for the PDF module.
|
|
@@ -45612,7 +47237,8 @@ onmessage = async (ev) => {
|
|
|
45612
47237
|
addPage(options) {
|
|
45613
47238
|
const objNum = this.allocObject();
|
|
45614
47239
|
const mediaBox = `[0 0 ${pdfNumber(options.width)} ${pdfNumber(options.height)}]`;
|
|
45615
|
-
const
|
|
47240
|
+
const contentsValue = typeof options.contentsRef === "string" ? options.contentsRef : pdfRef(options.contentsRef);
|
|
47241
|
+
const dict = new PdfDict().set("Type", "/Page").set("Parent", pdfRef(options.parentRef)).set("MediaBox", mediaBox).set("Contents", contentsValue).set("Resources", pdfRef(options.resourcesRef));
|
|
45616
47242
|
if (options.annotRefs && options.annotRefs.length > 0) dict.set("Annots", "[" + options.annotRefs.map((r) => pdfRef(r)).join(" ") + "]");
|
|
45617
47243
|
this.addObject(objNum, dict);
|
|
45618
47244
|
return objNum;
|
|
@@ -45861,6 +47487,396 @@ onmessage = async (ev) => {
|
|
|
45861
47487
|
return result;
|
|
45862
47488
|
}
|
|
45863
47489
|
//#endregion
|
|
47490
|
+
//#region src/modules/pdf/core/pdf-stream.ts
|
|
47491
|
+
/**
|
|
47492
|
+
* PDF content stream builder.
|
|
47493
|
+
*
|
|
47494
|
+
* Provides a high-level API for constructing PDF content streams using
|
|
47495
|
+
* PDF graphics operators. Content streams control what is drawn on a page:
|
|
47496
|
+
* text, lines, rectangles, colors, etc.
|
|
47497
|
+
*
|
|
47498
|
+
* @see PDF Reference 1.7, Chapter 4 - Graphics
|
|
47499
|
+
* @see PDF Reference 1.7, Chapter 5 - Text
|
|
47500
|
+
*/
|
|
47501
|
+
/**
|
|
47502
|
+
* Builds a PDF content stream using graphics and text operators.
|
|
47503
|
+
*
|
|
47504
|
+
* PDF uses a postfix notation where operands precede the operator.
|
|
47505
|
+
* For example: `100 200 m` means "move to point (100, 200)".
|
|
47506
|
+
*
|
|
47507
|
+
* Color model: PDF uses separate color state for stroking (lines/borders)
|
|
47508
|
+
* and non-stroking (fills/text). We provide methods for both.
|
|
47509
|
+
*/
|
|
47510
|
+
var PdfContentStream = class {
|
|
47511
|
+
constructor() {
|
|
47512
|
+
this.parts = [];
|
|
47513
|
+
}
|
|
47514
|
+
/**
|
|
47515
|
+
* Save the current graphics state (push onto state stack).
|
|
47516
|
+
* Must be balanced with a corresponding restore().
|
|
47517
|
+
*/
|
|
47518
|
+
save() {
|
|
47519
|
+
this.parts.push("q");
|
|
47520
|
+
return this;
|
|
47521
|
+
}
|
|
47522
|
+
/**
|
|
47523
|
+
* Restore the previously saved graphics state (pop from state stack).
|
|
47524
|
+
*/
|
|
47525
|
+
restore() {
|
|
47526
|
+
this.parts.push("Q");
|
|
47527
|
+
return this;
|
|
47528
|
+
}
|
|
47529
|
+
/**
|
|
47530
|
+
* Set the current graphics state from an ExtGState resource.
|
|
47531
|
+
* Used for transparency (alpha), blend modes, etc.
|
|
47532
|
+
*/
|
|
47533
|
+
setGraphicsState(name) {
|
|
47534
|
+
this.parts.push(`/${name} gs`);
|
|
47535
|
+
return this;
|
|
47536
|
+
}
|
|
47537
|
+
/**
|
|
47538
|
+
* Set the stroking color (used for lines, borders).
|
|
47539
|
+
*/
|
|
47540
|
+
setStrokeColor(color) {
|
|
47541
|
+
this.parts.push(`${pdfNumber(color.r)} ${pdfNumber(color.g)} ${pdfNumber(color.b)} RG`);
|
|
47542
|
+
return this;
|
|
47543
|
+
}
|
|
47544
|
+
/**
|
|
47545
|
+
* Set the non-stroking color (used for fills, text).
|
|
47546
|
+
*/
|
|
47547
|
+
setFillColor(color) {
|
|
47548
|
+
this.parts.push(`${pdfNumber(color.r)} ${pdfNumber(color.g)} ${pdfNumber(color.b)} rg`);
|
|
47549
|
+
return this;
|
|
47550
|
+
}
|
|
47551
|
+
/**
|
|
47552
|
+
* Set the line width for stroking operations.
|
|
47553
|
+
*/
|
|
47554
|
+
setLineWidth(width) {
|
|
47555
|
+
this.parts.push(`${pdfNumber(width)} w`);
|
|
47556
|
+
return this;
|
|
47557
|
+
}
|
|
47558
|
+
/**
|
|
47559
|
+
* Set the line dash pattern.
|
|
47560
|
+
* @param dashArray - Array of dash/gap lengths. Empty = solid line.
|
|
47561
|
+
* @param phase - Starting phase offset.
|
|
47562
|
+
*/
|
|
47563
|
+
setDashPattern(dashArray, phase = 0) {
|
|
47564
|
+
const arr = dashArray.map(pdfNumber).join(" ");
|
|
47565
|
+
this.parts.push(`[${arr}] ${pdfNumber(phase)} d`);
|
|
47566
|
+
return this;
|
|
47567
|
+
}
|
|
47568
|
+
/**
|
|
47569
|
+
* Set the line cap style.
|
|
47570
|
+
* 0 = butt cap, 1 = round cap, 2 = projecting square cap
|
|
47571
|
+
*/
|
|
47572
|
+
setLineCap(style) {
|
|
47573
|
+
this.parts.push(`${style} J`);
|
|
47574
|
+
return this;
|
|
47575
|
+
}
|
|
47576
|
+
/**
|
|
47577
|
+
* Set the line join style.
|
|
47578
|
+
* 0 = miter join, 1 = round join, 2 = bevel join
|
|
47579
|
+
*/
|
|
47580
|
+
setLineJoin(style) {
|
|
47581
|
+
this.parts.push(`${style} j`);
|
|
47582
|
+
return this;
|
|
47583
|
+
}
|
|
47584
|
+
/**
|
|
47585
|
+
* Begin a new subpath by moving to the given point.
|
|
47586
|
+
*/
|
|
47587
|
+
moveTo(x, y) {
|
|
47588
|
+
this.parts.push(`${pdfNumber(x)} ${pdfNumber(y)} m`);
|
|
47589
|
+
return this;
|
|
47590
|
+
}
|
|
47591
|
+
/**
|
|
47592
|
+
* Append a straight line segment from the current point to (x, y).
|
|
47593
|
+
*/
|
|
47594
|
+
lineTo(x, y) {
|
|
47595
|
+
this.parts.push(`${pdfNumber(x)} ${pdfNumber(y)} l`);
|
|
47596
|
+
return this;
|
|
47597
|
+
}
|
|
47598
|
+
/**
|
|
47599
|
+
* Append a rectangle to the current path.
|
|
47600
|
+
* PDF convention: (x, y) is the lower-left corner.
|
|
47601
|
+
*/
|
|
47602
|
+
rect(x, y, width, height) {
|
|
47603
|
+
this.parts.push(`${pdfNumber(x)} ${pdfNumber(y)} ${pdfNumber(width)} ${pdfNumber(height)} re`);
|
|
47604
|
+
return this;
|
|
47605
|
+
}
|
|
47606
|
+
/**
|
|
47607
|
+
* Stroke the current path.
|
|
47608
|
+
*/
|
|
47609
|
+
stroke() {
|
|
47610
|
+
this.parts.push("S");
|
|
47611
|
+
return this;
|
|
47612
|
+
}
|
|
47613
|
+
/**
|
|
47614
|
+
* Fill the current path using the nonzero winding number rule.
|
|
47615
|
+
*/
|
|
47616
|
+
fill() {
|
|
47617
|
+
this.parts.push("f");
|
|
47618
|
+
return this;
|
|
47619
|
+
}
|
|
47620
|
+
/**
|
|
47621
|
+
* Fill and then stroke the current path.
|
|
47622
|
+
*/
|
|
47623
|
+
fillAndStroke() {
|
|
47624
|
+
this.parts.push("B");
|
|
47625
|
+
return this;
|
|
47626
|
+
}
|
|
47627
|
+
/**
|
|
47628
|
+
* Close the current subpath by appending a line from current point to start.
|
|
47629
|
+
*/
|
|
47630
|
+
closePath() {
|
|
47631
|
+
this.parts.push("h");
|
|
47632
|
+
return this;
|
|
47633
|
+
}
|
|
47634
|
+
/**
|
|
47635
|
+
* End the path without filling or stroking (used for clipping).
|
|
47636
|
+
*/
|
|
47637
|
+
endPath() {
|
|
47638
|
+
this.parts.push("n");
|
|
47639
|
+
return this;
|
|
47640
|
+
}
|
|
47641
|
+
/**
|
|
47642
|
+
* Set the current path as the clipping boundary (nonzero winding rule).
|
|
47643
|
+
* Must be followed by endPath() or a painting operator.
|
|
47644
|
+
*/
|
|
47645
|
+
clip() {
|
|
47646
|
+
this.parts.push("W");
|
|
47647
|
+
return this;
|
|
47648
|
+
}
|
|
47649
|
+
/**
|
|
47650
|
+
* Begin a text object.
|
|
47651
|
+
*/
|
|
47652
|
+
beginText() {
|
|
47653
|
+
this.parts.push("BT");
|
|
47654
|
+
return this;
|
|
47655
|
+
}
|
|
47656
|
+
/**
|
|
47657
|
+
* End the current text object.
|
|
47658
|
+
*/
|
|
47659
|
+
endText() {
|
|
47660
|
+
this.parts.push("ET");
|
|
47661
|
+
return this;
|
|
47662
|
+
}
|
|
47663
|
+
/**
|
|
47664
|
+
* Set the font and size for subsequent text operations.
|
|
47665
|
+
* @param fontName - The font resource name (e.g., "F1")
|
|
47666
|
+
* @param size - Font size in points
|
|
47667
|
+
*/
|
|
47668
|
+
setFont(fontName, size) {
|
|
47669
|
+
this.parts.push(`/${fontName} ${pdfNumber(size)} Tf`);
|
|
47670
|
+
return this;
|
|
47671
|
+
}
|
|
47672
|
+
/**
|
|
47673
|
+
* Set the text matrix (position and transform for text).
|
|
47674
|
+
* For simple positioning, use Td instead.
|
|
47675
|
+
* @param a - Horizontal scaling
|
|
47676
|
+
* @param b - Vertical skew
|
|
47677
|
+
* @param c - Horizontal skew
|
|
47678
|
+
* @param d - Vertical scaling
|
|
47679
|
+
* @param e - Horizontal translation
|
|
47680
|
+
* @param f - Vertical translation
|
|
47681
|
+
*/
|
|
47682
|
+
setTextMatrix(a, b, c, d, e, f) {
|
|
47683
|
+
this.parts.push(`${pdfNumber(a)} ${pdfNumber(b)} ${pdfNumber(c)} ${pdfNumber(d)} ${pdfNumber(e)} ${pdfNumber(f)} Tm`);
|
|
47684
|
+
return this;
|
|
47685
|
+
}
|
|
47686
|
+
/**
|
|
47687
|
+
* Move to the start of the next line, offset from the start of the current line.
|
|
47688
|
+
*/
|
|
47689
|
+
moveText(tx, ty) {
|
|
47690
|
+
this.parts.push(`${pdfNumber(tx)} ${pdfNumber(ty)} Td`);
|
|
47691
|
+
return this;
|
|
47692
|
+
}
|
|
47693
|
+
/**
|
|
47694
|
+
* Set the text leading (line spacing) for T* operator.
|
|
47695
|
+
*/
|
|
47696
|
+
setTextLeading(leading) {
|
|
47697
|
+
this.parts.push(`${pdfNumber(leading)} TL`);
|
|
47698
|
+
return this;
|
|
47699
|
+
}
|
|
47700
|
+
/**
|
|
47701
|
+
* Show a text string. The string is escaped for PDF.
|
|
47702
|
+
*/
|
|
47703
|
+
showText(text) {
|
|
47704
|
+
if (hasNonAscii(text)) return this.showTextWinAnsi(text);
|
|
47705
|
+
this.parts.push(`(${escapeTextForPdf(text)}) Tj`);
|
|
47706
|
+
return this;
|
|
47707
|
+
}
|
|
47708
|
+
/**
|
|
47709
|
+
* Show a text string encoded as WinAnsi hex string.
|
|
47710
|
+
* Used for Type1 fonts where non-ASCII characters need single-byte encoding.
|
|
47711
|
+
*/
|
|
47712
|
+
showTextWinAnsi(text) {
|
|
47713
|
+
let hex = "<";
|
|
47714
|
+
for (let i = 0; i < text.length; i++) {
|
|
47715
|
+
const cp = text.codePointAt(i);
|
|
47716
|
+
if (cp > 65535) i++;
|
|
47717
|
+
const byte = unicodeToWinAnsi(cp);
|
|
47718
|
+
hex += byte.toString(16).padStart(2, "0");
|
|
47719
|
+
}
|
|
47720
|
+
hex += ">";
|
|
47721
|
+
this.parts.push(`${hex} Tj`);
|
|
47722
|
+
return this;
|
|
47723
|
+
}
|
|
47724
|
+
/**
|
|
47725
|
+
* Show a text string using a pre-encoded hex string (for CIDFonts).
|
|
47726
|
+
* The hexString should be in the format `<0012003A...>`.
|
|
47727
|
+
*/
|
|
47728
|
+
showTextHex(hexString) {
|
|
47729
|
+
this.parts.push(`${hexString} Tj`);
|
|
47730
|
+
return this;
|
|
47731
|
+
}
|
|
47732
|
+
/**
|
|
47733
|
+
* Move to the next line and show a text string.
|
|
47734
|
+
*/
|
|
47735
|
+
nextLineShowText(text) {
|
|
47736
|
+
if (hasNonAscii(text)) {
|
|
47737
|
+
let hex = "<";
|
|
47738
|
+
for (let i = 0; i < text.length; i++) {
|
|
47739
|
+
const cp = text.codePointAt(i);
|
|
47740
|
+
if (cp > 65535) i++;
|
|
47741
|
+
const byte = unicodeToWinAnsi(cp);
|
|
47742
|
+
hex += byte.toString(16).padStart(2, "0");
|
|
47743
|
+
}
|
|
47744
|
+
hex += ">";
|
|
47745
|
+
this.parts.push(`${hex} '`);
|
|
47746
|
+
return this;
|
|
47747
|
+
}
|
|
47748
|
+
this.parts.push(`(${escapeTextForPdf(text)}) '`);
|
|
47749
|
+
return this;
|
|
47750
|
+
}
|
|
47751
|
+
/**
|
|
47752
|
+
* Set the text rise (baseline offset), used for superscript/subscript.
|
|
47753
|
+
*/
|
|
47754
|
+
setTextRise(rise) {
|
|
47755
|
+
this.parts.push(`${pdfNumber(rise)} Ts`);
|
|
47756
|
+
return this;
|
|
47757
|
+
}
|
|
47758
|
+
/**
|
|
47759
|
+
* Set character spacing (extra space between characters).
|
|
47760
|
+
*/
|
|
47761
|
+
setCharacterSpacing(spacing) {
|
|
47762
|
+
this.parts.push(`${pdfNumber(spacing)} Tc`);
|
|
47763
|
+
return this;
|
|
47764
|
+
}
|
|
47765
|
+
/**
|
|
47766
|
+
* Set word spacing (extra space for space character).
|
|
47767
|
+
*/
|
|
47768
|
+
setWordSpacing(spacing) {
|
|
47769
|
+
this.parts.push(`${pdfNumber(spacing)} Tw`);
|
|
47770
|
+
return this;
|
|
47771
|
+
}
|
|
47772
|
+
/**
|
|
47773
|
+
* Draw an XObject (image) at the given position and size.
|
|
47774
|
+
* The image must be registered as a resource with the given name.
|
|
47775
|
+
*/
|
|
47776
|
+
drawImage(name, x, y, width, height) {
|
|
47777
|
+
return this.save().concat(width, 0, 0, height, x, y).doXObject(name).restore();
|
|
47778
|
+
}
|
|
47779
|
+
/**
|
|
47780
|
+
* Apply a transformation matrix (cm operator).
|
|
47781
|
+
*/
|
|
47782
|
+
concat(a, b, c, d, e, f) {
|
|
47783
|
+
this.parts.push(`${pdfNumber(a)} ${pdfNumber(b)} ${pdfNumber(c)} ${pdfNumber(d)} ${pdfNumber(e)} ${pdfNumber(f)} cm`);
|
|
47784
|
+
return this;
|
|
47785
|
+
}
|
|
47786
|
+
/**
|
|
47787
|
+
* Invoke a named XObject (Do operator).
|
|
47788
|
+
*/
|
|
47789
|
+
doXObject(name) {
|
|
47790
|
+
this.parts.push(`/${name} Do`);
|
|
47791
|
+
return this;
|
|
47792
|
+
}
|
|
47793
|
+
/**
|
|
47794
|
+
* Draw a filled rectangle.
|
|
47795
|
+
*/
|
|
47796
|
+
fillRect(x, y, width, height, color) {
|
|
47797
|
+
return this.save().setFillColor(color).rect(x, y, width, height).fill().restore();
|
|
47798
|
+
}
|
|
47799
|
+
/**
|
|
47800
|
+
* Draw a stroked line.
|
|
47801
|
+
*/
|
|
47802
|
+
drawLine(x1, y1, x2, y2, color, lineWidth, dashPattern = []) {
|
|
47803
|
+
this.save().setStrokeColor(color).setLineWidth(lineWidth);
|
|
47804
|
+
if (dashPattern.length > 0) this.setDashPattern(dashPattern);
|
|
47805
|
+
return this.moveTo(x1, y1).lineTo(x2, y2).stroke().restore();
|
|
47806
|
+
}
|
|
47807
|
+
/**
|
|
47808
|
+
* Get the content stream as a string.
|
|
47809
|
+
*/
|
|
47810
|
+
toString() {
|
|
47811
|
+
return this.parts.join("\n");
|
|
47812
|
+
}
|
|
47813
|
+
/**
|
|
47814
|
+
* Get the content stream as a Uint8Array (UTF-8 encoded).
|
|
47815
|
+
*/
|
|
47816
|
+
toUint8Array() {
|
|
47817
|
+
return new TextEncoder().encode(this.toString());
|
|
47818
|
+
}
|
|
47819
|
+
};
|
|
47820
|
+
/**
|
|
47821
|
+
* Escape a text string for use inside a PDF text operator.
|
|
47822
|
+
* PDF text strings are delimited by parentheses.
|
|
47823
|
+
*/
|
|
47824
|
+
function escapeTextForPdf(text) {
|
|
47825
|
+
return text.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)").replace(/\r\n/g, "\\n").replace(/\r/g, "\\n").replace(/\n/g, "\\n");
|
|
47826
|
+
}
|
|
47827
|
+
/**
|
|
47828
|
+
* Check if a string contains any non-ASCII characters (code point > 127).
|
|
47829
|
+
*/
|
|
47830
|
+
function hasNonAscii(text) {
|
|
47831
|
+
for (let i = 0; i < text.length; i++) if (text.charCodeAt(i) > 127) return true;
|
|
47832
|
+
return false;
|
|
47833
|
+
}
|
|
47834
|
+
/**
|
|
47835
|
+
* Map from Unicode code point to WinAnsi (Windows-1252) byte value.
|
|
47836
|
+
* Only the 0x80-0x9F range differs from Latin-1; everything else maps 1:1
|
|
47837
|
+
* for code points 0x00-0xFF.
|
|
47838
|
+
*/
|
|
47839
|
+
const UNICODE_TO_WINANSI = new Map([
|
|
47840
|
+
[8364, 128],
|
|
47841
|
+
[8218, 130],
|
|
47842
|
+
[402, 131],
|
|
47843
|
+
[8222, 132],
|
|
47844
|
+
[8230, 133],
|
|
47845
|
+
[8224, 134],
|
|
47846
|
+
[8225, 135],
|
|
47847
|
+
[710, 136],
|
|
47848
|
+
[8240, 137],
|
|
47849
|
+
[352, 138],
|
|
47850
|
+
[8249, 139],
|
|
47851
|
+
[338, 140],
|
|
47852
|
+
[381, 142],
|
|
47853
|
+
[8216, 145],
|
|
47854
|
+
[8217, 146],
|
|
47855
|
+
[8220, 147],
|
|
47856
|
+
[8221, 148],
|
|
47857
|
+
[8226, 149],
|
|
47858
|
+
[8211, 150],
|
|
47859
|
+
[8212, 151],
|
|
47860
|
+
[732, 152],
|
|
47861
|
+
[8482, 153],
|
|
47862
|
+
[353, 154],
|
|
47863
|
+
[8250, 155],
|
|
47864
|
+
[339, 156],
|
|
47865
|
+
[382, 158],
|
|
47866
|
+
[376, 159]
|
|
47867
|
+
]);
|
|
47868
|
+
/**
|
|
47869
|
+
* Convert a Unicode code point to a WinAnsi byte value.
|
|
47870
|
+
* Returns 0x3F ('?') for unmappable characters.
|
|
47871
|
+
*/
|
|
47872
|
+
function unicodeToWinAnsi(cp) {
|
|
47873
|
+
if (cp < 128) return cp;
|
|
47874
|
+
if (cp >= 160 && cp <= 255) return cp;
|
|
47875
|
+
const mapped = UNICODE_TO_WINANSI.get(cp);
|
|
47876
|
+
if (mapped !== void 0) return mapped;
|
|
47877
|
+
return 63;
|
|
47878
|
+
}
|
|
47879
|
+
//#endregion
|
|
45864
47880
|
//#region src/modules/pdf/font/metrics.ts
|
|
45865
47881
|
/**
|
|
45866
47882
|
* Character width metrics for the 14 PDF standard Type 1 fonts.
|
|
@@ -47778,396 +49794,6 @@ onmessage = async (ev) => {
|
|
|
47778
49794
|
}
|
|
47779
49795
|
}
|
|
47780
49796
|
//#endregion
|
|
47781
|
-
//#region src/modules/pdf/core/pdf-stream.ts
|
|
47782
|
-
/**
|
|
47783
|
-
* PDF content stream builder.
|
|
47784
|
-
*
|
|
47785
|
-
* Provides a high-level API for constructing PDF content streams using
|
|
47786
|
-
* PDF graphics operators. Content streams control what is drawn on a page:
|
|
47787
|
-
* text, lines, rectangles, colors, etc.
|
|
47788
|
-
*
|
|
47789
|
-
* @see PDF Reference 1.7, Chapter 4 - Graphics
|
|
47790
|
-
* @see PDF Reference 1.7, Chapter 5 - Text
|
|
47791
|
-
*/
|
|
47792
|
-
/**
|
|
47793
|
-
* Builds a PDF content stream using graphics and text operators.
|
|
47794
|
-
*
|
|
47795
|
-
* PDF uses a postfix notation where operands precede the operator.
|
|
47796
|
-
* For example: `100 200 m` means "move to point (100, 200)".
|
|
47797
|
-
*
|
|
47798
|
-
* Color model: PDF uses separate color state for stroking (lines/borders)
|
|
47799
|
-
* and non-stroking (fills/text). We provide methods for both.
|
|
47800
|
-
*/
|
|
47801
|
-
var PdfContentStream = class {
|
|
47802
|
-
constructor() {
|
|
47803
|
-
this.parts = [];
|
|
47804
|
-
}
|
|
47805
|
-
/**
|
|
47806
|
-
* Save the current graphics state (push onto state stack).
|
|
47807
|
-
* Must be balanced with a corresponding restore().
|
|
47808
|
-
*/
|
|
47809
|
-
save() {
|
|
47810
|
-
this.parts.push("q");
|
|
47811
|
-
return this;
|
|
47812
|
-
}
|
|
47813
|
-
/**
|
|
47814
|
-
* Restore the previously saved graphics state (pop from state stack).
|
|
47815
|
-
*/
|
|
47816
|
-
restore() {
|
|
47817
|
-
this.parts.push("Q");
|
|
47818
|
-
return this;
|
|
47819
|
-
}
|
|
47820
|
-
/**
|
|
47821
|
-
* Set the current graphics state from an ExtGState resource.
|
|
47822
|
-
* Used for transparency (alpha), blend modes, etc.
|
|
47823
|
-
*/
|
|
47824
|
-
setGraphicsState(name) {
|
|
47825
|
-
this.parts.push(`/${name} gs`);
|
|
47826
|
-
return this;
|
|
47827
|
-
}
|
|
47828
|
-
/**
|
|
47829
|
-
* Set the stroking color (used for lines, borders).
|
|
47830
|
-
*/
|
|
47831
|
-
setStrokeColor(color) {
|
|
47832
|
-
this.parts.push(`${pdfNumber(color.r)} ${pdfNumber(color.g)} ${pdfNumber(color.b)} RG`);
|
|
47833
|
-
return this;
|
|
47834
|
-
}
|
|
47835
|
-
/**
|
|
47836
|
-
* Set the non-stroking color (used for fills, text).
|
|
47837
|
-
*/
|
|
47838
|
-
setFillColor(color) {
|
|
47839
|
-
this.parts.push(`${pdfNumber(color.r)} ${pdfNumber(color.g)} ${pdfNumber(color.b)} rg`);
|
|
47840
|
-
return this;
|
|
47841
|
-
}
|
|
47842
|
-
/**
|
|
47843
|
-
* Set the line width for stroking operations.
|
|
47844
|
-
*/
|
|
47845
|
-
setLineWidth(width) {
|
|
47846
|
-
this.parts.push(`${pdfNumber(width)} w`);
|
|
47847
|
-
return this;
|
|
47848
|
-
}
|
|
47849
|
-
/**
|
|
47850
|
-
* Set the line dash pattern.
|
|
47851
|
-
* @param dashArray - Array of dash/gap lengths. Empty = solid line.
|
|
47852
|
-
* @param phase - Starting phase offset.
|
|
47853
|
-
*/
|
|
47854
|
-
setDashPattern(dashArray, phase = 0) {
|
|
47855
|
-
const arr = dashArray.map(pdfNumber).join(" ");
|
|
47856
|
-
this.parts.push(`[${arr}] ${pdfNumber(phase)} d`);
|
|
47857
|
-
return this;
|
|
47858
|
-
}
|
|
47859
|
-
/**
|
|
47860
|
-
* Set the line cap style.
|
|
47861
|
-
* 0 = butt cap, 1 = round cap, 2 = projecting square cap
|
|
47862
|
-
*/
|
|
47863
|
-
setLineCap(style) {
|
|
47864
|
-
this.parts.push(`${style} J`);
|
|
47865
|
-
return this;
|
|
47866
|
-
}
|
|
47867
|
-
/**
|
|
47868
|
-
* Set the line join style.
|
|
47869
|
-
* 0 = miter join, 1 = round join, 2 = bevel join
|
|
47870
|
-
*/
|
|
47871
|
-
setLineJoin(style) {
|
|
47872
|
-
this.parts.push(`${style} j`);
|
|
47873
|
-
return this;
|
|
47874
|
-
}
|
|
47875
|
-
/**
|
|
47876
|
-
* Begin a new subpath by moving to the given point.
|
|
47877
|
-
*/
|
|
47878
|
-
moveTo(x, y) {
|
|
47879
|
-
this.parts.push(`${pdfNumber(x)} ${pdfNumber(y)} m`);
|
|
47880
|
-
return this;
|
|
47881
|
-
}
|
|
47882
|
-
/**
|
|
47883
|
-
* Append a straight line segment from the current point to (x, y).
|
|
47884
|
-
*/
|
|
47885
|
-
lineTo(x, y) {
|
|
47886
|
-
this.parts.push(`${pdfNumber(x)} ${pdfNumber(y)} l`);
|
|
47887
|
-
return this;
|
|
47888
|
-
}
|
|
47889
|
-
/**
|
|
47890
|
-
* Append a rectangle to the current path.
|
|
47891
|
-
* PDF convention: (x, y) is the lower-left corner.
|
|
47892
|
-
*/
|
|
47893
|
-
rect(x, y, width, height) {
|
|
47894
|
-
this.parts.push(`${pdfNumber(x)} ${pdfNumber(y)} ${pdfNumber(width)} ${pdfNumber(height)} re`);
|
|
47895
|
-
return this;
|
|
47896
|
-
}
|
|
47897
|
-
/**
|
|
47898
|
-
* Stroke the current path.
|
|
47899
|
-
*/
|
|
47900
|
-
stroke() {
|
|
47901
|
-
this.parts.push("S");
|
|
47902
|
-
return this;
|
|
47903
|
-
}
|
|
47904
|
-
/**
|
|
47905
|
-
* Fill the current path using the nonzero winding number rule.
|
|
47906
|
-
*/
|
|
47907
|
-
fill() {
|
|
47908
|
-
this.parts.push("f");
|
|
47909
|
-
return this;
|
|
47910
|
-
}
|
|
47911
|
-
/**
|
|
47912
|
-
* Fill and then stroke the current path.
|
|
47913
|
-
*/
|
|
47914
|
-
fillAndStroke() {
|
|
47915
|
-
this.parts.push("B");
|
|
47916
|
-
return this;
|
|
47917
|
-
}
|
|
47918
|
-
/**
|
|
47919
|
-
* Close the current subpath by appending a line from current point to start.
|
|
47920
|
-
*/
|
|
47921
|
-
closePath() {
|
|
47922
|
-
this.parts.push("h");
|
|
47923
|
-
return this;
|
|
47924
|
-
}
|
|
47925
|
-
/**
|
|
47926
|
-
* End the path without filling or stroking (used for clipping).
|
|
47927
|
-
*/
|
|
47928
|
-
endPath() {
|
|
47929
|
-
this.parts.push("n");
|
|
47930
|
-
return this;
|
|
47931
|
-
}
|
|
47932
|
-
/**
|
|
47933
|
-
* Set the current path as the clipping boundary (nonzero winding rule).
|
|
47934
|
-
* Must be followed by endPath() or a painting operator.
|
|
47935
|
-
*/
|
|
47936
|
-
clip() {
|
|
47937
|
-
this.parts.push("W");
|
|
47938
|
-
return this;
|
|
47939
|
-
}
|
|
47940
|
-
/**
|
|
47941
|
-
* Begin a text object.
|
|
47942
|
-
*/
|
|
47943
|
-
beginText() {
|
|
47944
|
-
this.parts.push("BT");
|
|
47945
|
-
return this;
|
|
47946
|
-
}
|
|
47947
|
-
/**
|
|
47948
|
-
* End the current text object.
|
|
47949
|
-
*/
|
|
47950
|
-
endText() {
|
|
47951
|
-
this.parts.push("ET");
|
|
47952
|
-
return this;
|
|
47953
|
-
}
|
|
47954
|
-
/**
|
|
47955
|
-
* Set the font and size for subsequent text operations.
|
|
47956
|
-
* @param fontName - The font resource name (e.g., "F1")
|
|
47957
|
-
* @param size - Font size in points
|
|
47958
|
-
*/
|
|
47959
|
-
setFont(fontName, size) {
|
|
47960
|
-
this.parts.push(`/${fontName} ${pdfNumber(size)} Tf`);
|
|
47961
|
-
return this;
|
|
47962
|
-
}
|
|
47963
|
-
/**
|
|
47964
|
-
* Set the text matrix (position and transform for text).
|
|
47965
|
-
* For simple positioning, use Td instead.
|
|
47966
|
-
* @param a - Horizontal scaling
|
|
47967
|
-
* @param b - Vertical skew
|
|
47968
|
-
* @param c - Horizontal skew
|
|
47969
|
-
* @param d - Vertical scaling
|
|
47970
|
-
* @param e - Horizontal translation
|
|
47971
|
-
* @param f - Vertical translation
|
|
47972
|
-
*/
|
|
47973
|
-
setTextMatrix(a, b, c, d, e, f) {
|
|
47974
|
-
this.parts.push(`${pdfNumber(a)} ${pdfNumber(b)} ${pdfNumber(c)} ${pdfNumber(d)} ${pdfNumber(e)} ${pdfNumber(f)} Tm`);
|
|
47975
|
-
return this;
|
|
47976
|
-
}
|
|
47977
|
-
/**
|
|
47978
|
-
* Move to the start of the next line, offset from the start of the current line.
|
|
47979
|
-
*/
|
|
47980
|
-
moveText(tx, ty) {
|
|
47981
|
-
this.parts.push(`${pdfNumber(tx)} ${pdfNumber(ty)} Td`);
|
|
47982
|
-
return this;
|
|
47983
|
-
}
|
|
47984
|
-
/**
|
|
47985
|
-
* Set the text leading (line spacing) for T* operator.
|
|
47986
|
-
*/
|
|
47987
|
-
setTextLeading(leading) {
|
|
47988
|
-
this.parts.push(`${pdfNumber(leading)} TL`);
|
|
47989
|
-
return this;
|
|
47990
|
-
}
|
|
47991
|
-
/**
|
|
47992
|
-
* Show a text string. The string is escaped for PDF.
|
|
47993
|
-
*/
|
|
47994
|
-
showText(text) {
|
|
47995
|
-
if (hasNonAscii(text)) return this.showTextWinAnsi(text);
|
|
47996
|
-
this.parts.push(`(${escapeTextForPdf(text)}) Tj`);
|
|
47997
|
-
return this;
|
|
47998
|
-
}
|
|
47999
|
-
/**
|
|
48000
|
-
* Show a text string encoded as WinAnsi hex string.
|
|
48001
|
-
* Used for Type1 fonts where non-ASCII characters need single-byte encoding.
|
|
48002
|
-
*/
|
|
48003
|
-
showTextWinAnsi(text) {
|
|
48004
|
-
let hex = "<";
|
|
48005
|
-
for (let i = 0; i < text.length; i++) {
|
|
48006
|
-
const cp = text.codePointAt(i);
|
|
48007
|
-
if (cp > 65535) i++;
|
|
48008
|
-
const byte = unicodeToWinAnsi(cp);
|
|
48009
|
-
hex += byte.toString(16).padStart(2, "0");
|
|
48010
|
-
}
|
|
48011
|
-
hex += ">";
|
|
48012
|
-
this.parts.push(`${hex} Tj`);
|
|
48013
|
-
return this;
|
|
48014
|
-
}
|
|
48015
|
-
/**
|
|
48016
|
-
* Show a text string using a pre-encoded hex string (for CIDFonts).
|
|
48017
|
-
* The hexString should be in the format `<0012003A...>`.
|
|
48018
|
-
*/
|
|
48019
|
-
showTextHex(hexString) {
|
|
48020
|
-
this.parts.push(`${hexString} Tj`);
|
|
48021
|
-
return this;
|
|
48022
|
-
}
|
|
48023
|
-
/**
|
|
48024
|
-
* Move to the next line and show a text string.
|
|
48025
|
-
*/
|
|
48026
|
-
nextLineShowText(text) {
|
|
48027
|
-
if (hasNonAscii(text)) {
|
|
48028
|
-
let hex = "<";
|
|
48029
|
-
for (let i = 0; i < text.length; i++) {
|
|
48030
|
-
const cp = text.codePointAt(i);
|
|
48031
|
-
if (cp > 65535) i++;
|
|
48032
|
-
const byte = unicodeToWinAnsi(cp);
|
|
48033
|
-
hex += byte.toString(16).padStart(2, "0");
|
|
48034
|
-
}
|
|
48035
|
-
hex += ">";
|
|
48036
|
-
this.parts.push(`${hex} '`);
|
|
48037
|
-
return this;
|
|
48038
|
-
}
|
|
48039
|
-
this.parts.push(`(${escapeTextForPdf(text)}) '`);
|
|
48040
|
-
return this;
|
|
48041
|
-
}
|
|
48042
|
-
/**
|
|
48043
|
-
* Set the text rise (baseline offset), used for superscript/subscript.
|
|
48044
|
-
*/
|
|
48045
|
-
setTextRise(rise) {
|
|
48046
|
-
this.parts.push(`${pdfNumber(rise)} Ts`);
|
|
48047
|
-
return this;
|
|
48048
|
-
}
|
|
48049
|
-
/**
|
|
48050
|
-
* Set character spacing (extra space between characters).
|
|
48051
|
-
*/
|
|
48052
|
-
setCharacterSpacing(spacing) {
|
|
48053
|
-
this.parts.push(`${pdfNumber(spacing)} Tc`);
|
|
48054
|
-
return this;
|
|
48055
|
-
}
|
|
48056
|
-
/**
|
|
48057
|
-
* Set word spacing (extra space for space character).
|
|
48058
|
-
*/
|
|
48059
|
-
setWordSpacing(spacing) {
|
|
48060
|
-
this.parts.push(`${pdfNumber(spacing)} Tw`);
|
|
48061
|
-
return this;
|
|
48062
|
-
}
|
|
48063
|
-
/**
|
|
48064
|
-
* Draw an XObject (image) at the given position and size.
|
|
48065
|
-
* The image must be registered as a resource with the given name.
|
|
48066
|
-
*/
|
|
48067
|
-
drawImage(name, x, y, width, height) {
|
|
48068
|
-
return this.save().concat(width, 0, 0, height, x, y).doXObject(name).restore();
|
|
48069
|
-
}
|
|
48070
|
-
/**
|
|
48071
|
-
* Apply a transformation matrix (cm operator).
|
|
48072
|
-
*/
|
|
48073
|
-
concat(a, b, c, d, e, f) {
|
|
48074
|
-
this.parts.push(`${pdfNumber(a)} ${pdfNumber(b)} ${pdfNumber(c)} ${pdfNumber(d)} ${pdfNumber(e)} ${pdfNumber(f)} cm`);
|
|
48075
|
-
return this;
|
|
48076
|
-
}
|
|
48077
|
-
/**
|
|
48078
|
-
* Invoke a named XObject (Do operator).
|
|
48079
|
-
*/
|
|
48080
|
-
doXObject(name) {
|
|
48081
|
-
this.parts.push(`/${name} Do`);
|
|
48082
|
-
return this;
|
|
48083
|
-
}
|
|
48084
|
-
/**
|
|
48085
|
-
* Draw a filled rectangle.
|
|
48086
|
-
*/
|
|
48087
|
-
fillRect(x, y, width, height, color) {
|
|
48088
|
-
return this.save().setFillColor(color).rect(x, y, width, height).fill().restore();
|
|
48089
|
-
}
|
|
48090
|
-
/**
|
|
48091
|
-
* Draw a stroked line.
|
|
48092
|
-
*/
|
|
48093
|
-
drawLine(x1, y1, x2, y2, color, lineWidth, dashPattern = []) {
|
|
48094
|
-
this.save().setStrokeColor(color).setLineWidth(lineWidth);
|
|
48095
|
-
if (dashPattern.length > 0) this.setDashPattern(dashPattern);
|
|
48096
|
-
return this.moveTo(x1, y1).lineTo(x2, y2).stroke().restore();
|
|
48097
|
-
}
|
|
48098
|
-
/**
|
|
48099
|
-
* Get the content stream as a string.
|
|
48100
|
-
*/
|
|
48101
|
-
toString() {
|
|
48102
|
-
return this.parts.join("\n");
|
|
48103
|
-
}
|
|
48104
|
-
/**
|
|
48105
|
-
* Get the content stream as a Uint8Array (UTF-8 encoded).
|
|
48106
|
-
*/
|
|
48107
|
-
toUint8Array() {
|
|
48108
|
-
return new TextEncoder().encode(this.toString());
|
|
48109
|
-
}
|
|
48110
|
-
};
|
|
48111
|
-
/**
|
|
48112
|
-
* Escape a text string for use inside a PDF text operator.
|
|
48113
|
-
* PDF text strings are delimited by parentheses.
|
|
48114
|
-
*/
|
|
48115
|
-
function escapeTextForPdf(text) {
|
|
48116
|
-
return text.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)").replace(/\r\n/g, "\\n").replace(/\r/g, "\\n").replace(/\n/g, "\\n");
|
|
48117
|
-
}
|
|
48118
|
-
/**
|
|
48119
|
-
* Check if a string contains any non-ASCII characters (code point > 127).
|
|
48120
|
-
*/
|
|
48121
|
-
function hasNonAscii(text) {
|
|
48122
|
-
for (let i = 0; i < text.length; i++) if (text.charCodeAt(i) > 127) return true;
|
|
48123
|
-
return false;
|
|
48124
|
-
}
|
|
48125
|
-
/**
|
|
48126
|
-
* Map from Unicode code point to WinAnsi (Windows-1252) byte value.
|
|
48127
|
-
* Only the 0x80-0x9F range differs from Latin-1; everything else maps 1:1
|
|
48128
|
-
* for code points 0x00-0xFF.
|
|
48129
|
-
*/
|
|
48130
|
-
const UNICODE_TO_WINANSI = new Map([
|
|
48131
|
-
[8364, 128],
|
|
48132
|
-
[8218, 130],
|
|
48133
|
-
[402, 131],
|
|
48134
|
-
[8222, 132],
|
|
48135
|
-
[8230, 133],
|
|
48136
|
-
[8224, 134],
|
|
48137
|
-
[8225, 135],
|
|
48138
|
-
[710, 136],
|
|
48139
|
-
[8240, 137],
|
|
48140
|
-
[352, 138],
|
|
48141
|
-
[8249, 139],
|
|
48142
|
-
[338, 140],
|
|
48143
|
-
[381, 142],
|
|
48144
|
-
[8216, 145],
|
|
48145
|
-
[8217, 146],
|
|
48146
|
-
[8220, 147],
|
|
48147
|
-
[8221, 148],
|
|
48148
|
-
[8226, 149],
|
|
48149
|
-
[8211, 150],
|
|
48150
|
-
[8212, 151],
|
|
48151
|
-
[732, 152],
|
|
48152
|
-
[8482, 153],
|
|
48153
|
-
[353, 154],
|
|
48154
|
-
[8250, 155],
|
|
48155
|
-
[339, 156],
|
|
48156
|
-
[382, 158],
|
|
48157
|
-
[376, 159]
|
|
48158
|
-
]);
|
|
48159
|
-
/**
|
|
48160
|
-
* Convert a Unicode code point to a WinAnsi byte value.
|
|
48161
|
-
* Returns 0x3F ('?') for unmappable characters.
|
|
48162
|
-
*/
|
|
48163
|
-
function unicodeToWinAnsi(cp) {
|
|
48164
|
-
if (cp < 128) return cp;
|
|
48165
|
-
if (cp >= 160 && cp <= 255) return cp;
|
|
48166
|
-
const mapped = UNICODE_TO_WINANSI.get(cp);
|
|
48167
|
-
if (mapped !== void 0) return mapped;
|
|
48168
|
-
return 63;
|
|
48169
|
-
}
|
|
48170
|
-
//#endregion
|
|
48171
49797
|
//#region src/modules/pdf/render/constants.ts
|
|
48172
49798
|
/**
|
|
48173
49799
|
* Line-height multiplier applied to the font size.
|
|
@@ -48248,13 +49874,38 @@ onmessage = async (ev) => {
|
|
|
48248
49874
|
stream.restore();
|
|
48249
49875
|
} else stream.fillRect(cell.rect.x, cell.rect.y, cell.rect.width, cell.rect.height, cell.fillColor);
|
|
48250
49876
|
}
|
|
49877
|
+
/**
|
|
49878
|
+
* Convert Excel textRotation to standard signed degrees.
|
|
49879
|
+
* Excel uses 1-90 for CCW and 91-180 for CW (where 91 = -1°, 180 = -90°).
|
|
49880
|
+
* Returns 0 for non-numeric values (e.g. "vertical").
|
|
49881
|
+
*/
|
|
49882
|
+
function excelRotationToDegrees(textRotation) {
|
|
49883
|
+
if (typeof textRotation !== "number") return 0;
|
|
49884
|
+
return textRotation <= 90 ? textRotation : -(textRotation - 90);
|
|
49885
|
+
}
|
|
49886
|
+
/**
|
|
49887
|
+
* Compute the horizontal slant offset for parallelogram borders.
|
|
49888
|
+
* For general rotation angles (not 0°/90°), Excel renders cell borders as a
|
|
49889
|
+
* parallelogram whose left/right edges tilt to match the text rotation angle.
|
|
49890
|
+
* Returns 0 for straight borders (no rotation, 90°, -90°, or vertical stacked).
|
|
49891
|
+
*/
|
|
49892
|
+
function computeSlantOffset(textRotation, height) {
|
|
49893
|
+
const degrees = excelRotationToDegrees(textRotation);
|
|
49894
|
+
if (degrees === 0) return 0;
|
|
49895
|
+
const absDeg = Math.abs(degrees);
|
|
49896
|
+
if (absDeg < .01 || absDeg > 89.99) return 0;
|
|
49897
|
+
const radians = absDeg * Math.PI / 180;
|
|
49898
|
+
const offset = height * Math.cos(radians) / Math.sin(radians);
|
|
49899
|
+
return degrees < 0 ? -offset : offset;
|
|
49900
|
+
}
|
|
48251
49901
|
function drawCellBorders(stream, cell) {
|
|
48252
|
-
const { rect, borders } = cell;
|
|
49902
|
+
const { rect, borders, textRotation } = cell;
|
|
48253
49903
|
const { x, y, width, height } = rect;
|
|
48254
|
-
|
|
49904
|
+
const slant = computeSlantOffset(textRotation, height);
|
|
49905
|
+
if (borders.top) drawBorderLine(stream, borders.top, x + slant, y + height, x + width + slant, y + height, true);
|
|
48255
49906
|
if (borders.bottom) drawBorderLine(stream, borders.bottom, x, y, x + width, y, true);
|
|
48256
|
-
if (borders.left) drawBorderLine(stream, borders.left, x, y, x, y + height, false);
|
|
48257
|
-
if (borders.right) drawBorderLine(stream, borders.right, x + width, y, x + width, y + height, false);
|
|
49907
|
+
if (borders.left) drawBorderLine(stream, borders.left, x, y, x + slant, y + height, false);
|
|
49908
|
+
if (borders.right) drawBorderLine(stream, borders.right, x + width, y, x + width + slant, y + height, false);
|
|
48258
49909
|
}
|
|
48259
49910
|
function drawBorderLine(stream, border, x1, y1, x2, y2, isHorizontal) {
|
|
48260
49911
|
if (border.isDouble) {
|
|
@@ -48280,7 +49931,14 @@ onmessage = async (ev) => {
|
|
|
48280
49931
|
const indentPts = cell.indent * 10 * scaleFactor;
|
|
48281
49932
|
const clipWidth = rect.width + (cell.textOverflowWidth || 0);
|
|
48282
49933
|
stream.save();
|
|
48283
|
-
|
|
49934
|
+
const slantClip = computeSlantOffset(cell.textRotation, rect.height);
|
|
49935
|
+
if (slantClip !== 0) {
|
|
49936
|
+
stream.moveTo(rect.x, rect.y);
|
|
49937
|
+
stream.lineTo(rect.x + clipWidth, rect.y);
|
|
49938
|
+
stream.lineTo(rect.x + clipWidth + slantClip, rect.y + rect.height);
|
|
49939
|
+
stream.lineTo(rect.x + slantClip, rect.y + rect.height);
|
|
49940
|
+
stream.closePath();
|
|
49941
|
+
} else stream.rect(rect.x, rect.y, clipWidth, rect.height);
|
|
48284
49942
|
stream.clip();
|
|
48285
49943
|
stream.endPath();
|
|
48286
49944
|
const textAlpha = cell.textColor.a;
|
|
@@ -48444,9 +50102,7 @@ onmessage = async (ev) => {
|
|
|
48444
50102
|
const padH = 3 * scaleFactor;
|
|
48445
50103
|
const padV = 2 * scaleFactor;
|
|
48446
50104
|
const resourceName = fontManager.hasEmbeddedFont() ? fontManager.getEmbeddedResourceName() : fontManager.ensureFont(resolvePdfFontName(cell.fontFamily, cell.bold, cell.italic));
|
|
48447
|
-
|
|
48448
|
-
if (typeof cell.textRotation === "number") degrees = cell.textRotation <= 90 ? cell.textRotation : -(cell.textRotation - 90);
|
|
48449
|
-
else degrees = 0;
|
|
50105
|
+
const degrees = excelRotationToDegrees(cell.textRotation);
|
|
48450
50106
|
const radians = degrees * Math.PI / 180;
|
|
48451
50107
|
const cos = Math.cos(radians);
|
|
48452
50108
|
const sin = Math.sin(radians);
|
|
@@ -48491,7 +50147,7 @@ onmessage = async (ev) => {
|
|
|
48491
50147
|
const { rect, horizontalAlign, verticalAlign } = cell;
|
|
48492
50148
|
const totalColumnsWidth = lines.length * lineHeight;
|
|
48493
50149
|
let startX;
|
|
48494
|
-
if (horizontalAlign === "center"
|
|
50150
|
+
if (horizontalAlign === "center") startX = rect.x + rect.width / 2 - totalColumnsWidth / 2 + ascent;
|
|
48495
50151
|
else if (horizontalAlign === "right") startX = rect.x + rect.width - padH - totalColumnsWidth + ascent;
|
|
48496
50152
|
else startX = rect.x + padH + ascent;
|
|
48497
50153
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -48499,9 +50155,9 @@ onmessage = async (ev) => {
|
|
|
48499
50155
|
const lineWidth = fontManager.measureText(line, resourceName, fontSize);
|
|
48500
50156
|
const colX = startX + i * lineHeight;
|
|
48501
50157
|
let ty;
|
|
48502
|
-
if (verticalAlign === "top") ty = rect.y + padV;
|
|
50158
|
+
if (verticalAlign === "top") ty = rect.y + rect.height - padV - lineWidth;
|
|
48503
50159
|
else if (verticalAlign === "middle") ty = rect.y + (rect.height - lineWidth) / 2;
|
|
48504
|
-
else ty = rect.y +
|
|
50160
|
+
else ty = rect.y + padV;
|
|
48505
50161
|
ty = Math.max(ty, rect.y + padV);
|
|
48506
50162
|
stream.beginText();
|
|
48507
50163
|
stream.setFont(resourceName, fontSize);
|
|
@@ -48515,7 +50171,7 @@ onmessage = async (ev) => {
|
|
|
48515
50171
|
const { rect, horizontalAlign, verticalAlign } = cell;
|
|
48516
50172
|
const totalColumnsWidth = lines.length * lineHeight;
|
|
48517
50173
|
let startX;
|
|
48518
|
-
if (horizontalAlign === "center"
|
|
50174
|
+
if (horizontalAlign === "center") startX = rect.x + rect.width / 2 + totalColumnsWidth / 2 - lineHeight + ascent;
|
|
48519
50175
|
else if (horizontalAlign === "right") startX = rect.x + rect.width - padH - lineHeight + ascent;
|
|
48520
50176
|
else startX = rect.x + padH + totalColumnsWidth - lineHeight + ascent;
|
|
48521
50177
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -48536,10 +50192,31 @@ onmessage = async (ev) => {
|
|
|
48536
50192
|
}
|
|
48537
50193
|
/** General rotation — center a multi-line text block in the cell. */
|
|
48538
50194
|
function drawRotatedGeneral(stream, cell, lines, fontManager, resourceName, fontSize, lineHeight, ascent, cos, sin, indentPts) {
|
|
48539
|
-
const { rect, horizontalAlign } = cell;
|
|
50195
|
+
const { rect, horizontalAlign, verticalAlign } = cell;
|
|
50196
|
+
const padH = 3;
|
|
50197
|
+
const padV = 2;
|
|
50198
|
+
let maxLineWidth = 0;
|
|
50199
|
+
for (const line of lines) {
|
|
50200
|
+
const w = fontManager.measureText(line, resourceName, fontSize);
|
|
50201
|
+
if (w > maxLineWidth) maxLineWidth = w;
|
|
50202
|
+
}
|
|
50203
|
+
const totalTextHeight = lines.length * lineHeight;
|
|
50204
|
+
const absSin = Math.abs(sin);
|
|
50205
|
+
const absCos = Math.abs(cos);
|
|
50206
|
+
const rotatedWidth = maxLineWidth * absCos + totalTextHeight * absSin;
|
|
50207
|
+
const rotatedHeight = maxLineWidth * absSin + totalTextHeight * absCos;
|
|
50208
|
+
const slantShift = computeSlantOffset(cell.textRotation, rect.height) / 2;
|
|
48540
50209
|
const indentOffset = horizontalAlign === "left" ? indentPts / 2 : horizontalAlign === "right" ? -indentPts / 2 : 0;
|
|
48541
|
-
|
|
48542
|
-
|
|
50210
|
+
let cy;
|
|
50211
|
+
if (verticalAlign === "top") cy = rect.y + rect.height - padV - rotatedHeight / 2;
|
|
50212
|
+
else if (verticalAlign === "bottom") cy = rect.y + padV + rotatedHeight / 2;
|
|
50213
|
+
else cy = rect.y + rect.height / 2;
|
|
50214
|
+
const verticalRatio = rect.height > 0 ? (cy - rect.y) / rect.height : .5;
|
|
50215
|
+
const slantAtCy = slantShift * 2 * verticalRatio;
|
|
50216
|
+
let cx;
|
|
50217
|
+
if (horizontalAlign === "right") cx = rect.x + rect.width - padH - rotatedWidth / 2 + indentOffset + slantAtCy;
|
|
50218
|
+
else if (horizontalAlign === "left") cx = rect.x + padH + rotatedWidth / 2 + indentOffset + slantAtCy;
|
|
50219
|
+
else cx = rect.x + rect.width / 2 + indentOffset + slantAtCy;
|
|
48543
50220
|
for (let i = 0; i < lines.length; i++) {
|
|
48544
50221
|
const line = lines[i];
|
|
48545
50222
|
const lineWidth = fontManager.measureText(line, resourceName, fontSize);
|
|
@@ -48566,7 +50243,8 @@ onmessage = async (ev) => {
|
|
|
48566
50243
|
* Newlines (\n) start a new column to the right.
|
|
48567
50244
|
*/
|
|
48568
50245
|
function drawVerticalStackedText(stream, cell, fontManager, _indentPts, scaleFactor = 1) {
|
|
48569
|
-
const { rect, text, fontSize } = cell;
|
|
50246
|
+
const { rect, text, fontSize, horizontalAlign, verticalAlign } = cell;
|
|
50247
|
+
const padH = 3 * scaleFactor;
|
|
48570
50248
|
const padV = 2 * scaleFactor;
|
|
48571
50249
|
const resourceName = fontManager.hasEmbeddedFont() ? fontManager.getEmbeddedResourceName() : fontManager.ensureFont(resolvePdfFontName(cell.fontFamily, cell.bold, cell.italic));
|
|
48572
50250
|
const charHeight = fontSize * 1.3;
|
|
@@ -48574,12 +50252,19 @@ onmessage = async (ev) => {
|
|
|
48574
50252
|
const columns = text.split(/\r?\n/);
|
|
48575
50253
|
const columnWidth = fontSize * 1.4;
|
|
48576
50254
|
const totalColumnsWidth = columns.length * columnWidth;
|
|
48577
|
-
|
|
50255
|
+
let startX;
|
|
50256
|
+
if (horizontalAlign === "center") startX = rect.x + rect.width / 2 - totalColumnsWidth / 2 + columnWidth / 2;
|
|
50257
|
+
else if (horizontalAlign === "right") startX = rect.x + rect.width - padH - totalColumnsWidth + columnWidth / 2;
|
|
50258
|
+
else startX = rect.x + padH + columnWidth / 2;
|
|
48578
50259
|
stream.setFillColor(cell.textColor);
|
|
48579
50260
|
for (let colIdx = 0; colIdx < columns.length; colIdx++) {
|
|
48580
50261
|
const colText = columns[colIdx];
|
|
48581
50262
|
const colX = startX + colIdx * columnWidth;
|
|
48582
|
-
|
|
50263
|
+
const totalTextHeight = colText.length * charHeight;
|
|
50264
|
+
let currentY;
|
|
50265
|
+
if (verticalAlign === "middle") currentY = rect.y + rect.height / 2 + totalTextHeight / 2 - ascent;
|
|
50266
|
+
else if (verticalAlign === "bottom") currentY = rect.y + padV + totalTextHeight - ascent;
|
|
50267
|
+
else currentY = rect.y + rect.height - padV - ascent;
|
|
48583
50268
|
for (const ch of colText) {
|
|
48584
50269
|
if (currentY < rect.y + padV) break;
|
|
48585
50270
|
const charWidth = fontManager.measureText(ch, resourceName, fontSize);
|
|
@@ -48729,6 +50414,234 @@ onmessage = async (ev) => {
|
|
|
48729
50414
|
stream.endText();
|
|
48730
50415
|
stream.restore();
|
|
48731
50416
|
}
|
|
50417
|
+
/** Default values for text watermarks. */
|
|
50418
|
+
const TEXT_WM_DEFAULTS = {
|
|
50419
|
+
fontSize: 54,
|
|
50420
|
+
color: {
|
|
50421
|
+
r: .75,
|
|
50422
|
+
g: .75,
|
|
50423
|
+
b: .75
|
|
50424
|
+
},
|
|
50425
|
+
opacity: .15,
|
|
50426
|
+
rotation: -45,
|
|
50427
|
+
fontFamily: "Helvetica",
|
|
50428
|
+
bold: false,
|
|
50429
|
+
italic: false,
|
|
50430
|
+
repeatSpacingX: 200,
|
|
50431
|
+
repeatSpacingY: 200
|
|
50432
|
+
};
|
|
50433
|
+
/** Default values for image watermarks. */
|
|
50434
|
+
const IMAGE_WM_DEFAULTS = {
|
|
50435
|
+
opacity: .15,
|
|
50436
|
+
rotation: 0,
|
|
50437
|
+
scale: .5,
|
|
50438
|
+
repeatSpacingX: 200,
|
|
50439
|
+
repeatSpacingY: 200
|
|
50440
|
+
};
|
|
50441
|
+
/** Minimum allowed spacing for repeat patterns (prevents infinite loops). */
|
|
50442
|
+
const MIN_REPEAT_SPACING = 10;
|
|
50443
|
+
/**
|
|
50444
|
+
* Render a watermark onto a PDF content stream.
|
|
50445
|
+
* This should be called BEFORE the cell/grid content is rendered so the
|
|
50446
|
+
* watermark sits behind everything (under-content).
|
|
50447
|
+
*/
|
|
50448
|
+
function renderWatermark(stream, page, watermark, fontManager) {
|
|
50449
|
+
if (watermark.type === "text") return renderTextWatermark(stream, page, normalizeTextWatermark(watermark), fontManager);
|
|
50450
|
+
return renderImageWatermark(stream, page, normalizeImageWatermark(watermark));
|
|
50451
|
+
}
|
|
50452
|
+
/** Clamp/normalize text watermark options to safe ranges. */
|
|
50453
|
+
function normalizeTextWatermark(wm) {
|
|
50454
|
+
return {
|
|
50455
|
+
...wm,
|
|
50456
|
+
opacity: clamp01(wm.opacity ?? TEXT_WM_DEFAULTS.opacity),
|
|
50457
|
+
fontSize: Math.max(1, wm.fontSize ?? TEXT_WM_DEFAULTS.fontSize),
|
|
50458
|
+
repeatSpacingX: Math.max(MIN_REPEAT_SPACING, wm.repeatSpacingX ?? TEXT_WM_DEFAULTS.repeatSpacingX),
|
|
50459
|
+
repeatSpacingY: Math.max(MIN_REPEAT_SPACING, wm.repeatSpacingY ?? TEXT_WM_DEFAULTS.repeatSpacingY)
|
|
50460
|
+
};
|
|
50461
|
+
}
|
|
50462
|
+
/** Clamp/normalize image watermark options to safe ranges. */
|
|
50463
|
+
function normalizeImageWatermark(wm) {
|
|
50464
|
+
return {
|
|
50465
|
+
...wm,
|
|
50466
|
+
opacity: clamp01(wm.opacity ?? IMAGE_WM_DEFAULTS.opacity),
|
|
50467
|
+
scale: Math.max(.01, wm.scale ?? IMAGE_WM_DEFAULTS.scale),
|
|
50468
|
+
width: wm.width !== void 0 ? Math.max(1, wm.width) : void 0,
|
|
50469
|
+
height: wm.height !== void 0 ? Math.max(1, wm.height) : void 0,
|
|
50470
|
+
repeatSpacingX: Math.max(MIN_REPEAT_SPACING, wm.repeatSpacingX ?? IMAGE_WM_DEFAULTS.repeatSpacingX),
|
|
50471
|
+
repeatSpacingY: Math.max(MIN_REPEAT_SPACING, wm.repeatSpacingY ?? IMAGE_WM_DEFAULTS.repeatSpacingY)
|
|
50472
|
+
};
|
|
50473
|
+
}
|
|
50474
|
+
/** Clamp a number to the 0..1 range. */
|
|
50475
|
+
function clamp01(v) {
|
|
50476
|
+
return Math.max(0, Math.min(1, v));
|
|
50477
|
+
}
|
|
50478
|
+
/**
|
|
50479
|
+
* Render a text watermark on a single page.
|
|
50480
|
+
*/
|
|
50481
|
+
function renderTextWatermark(stream, page, watermark, fontManager) {
|
|
50482
|
+
const fontSize = watermark.fontSize ?? TEXT_WM_DEFAULTS.fontSize;
|
|
50483
|
+
const color = watermark.color ?? TEXT_WM_DEFAULTS.color;
|
|
50484
|
+
const opacity = watermark.opacity ?? TEXT_WM_DEFAULTS.opacity;
|
|
50485
|
+
const rotation = watermark.rotation ?? TEXT_WM_DEFAULTS.rotation;
|
|
50486
|
+
const fontFamily = watermark.fontFamily ?? TEXT_WM_DEFAULTS.fontFamily;
|
|
50487
|
+
const bold = watermark.bold ?? TEXT_WM_DEFAULTS.bold;
|
|
50488
|
+
const italic = watermark.italic ?? TEXT_WM_DEFAULTS.italic;
|
|
50489
|
+
const resourceName = fontManager.hasEmbeddedFont() ? fontManager.getEmbeddedResourceName() : fontManager.ensureFont(resolvePdfFontName(fontFamily, bold, italic));
|
|
50490
|
+
const textWidth = fontManager.measureText(watermark.text, resourceName, fontSize);
|
|
50491
|
+
const textHeight = fontSize * .7;
|
|
50492
|
+
const radians = rotation * Math.PI / 180;
|
|
50493
|
+
const cos = Math.cos(radians);
|
|
50494
|
+
const sin = Math.sin(radians);
|
|
50495
|
+
const needsAlpha = opacity < 1;
|
|
50496
|
+
const gsName = needsAlpha ? alphaGsName(opacity) : "";
|
|
50497
|
+
const drawSingleWatermark = (cx, cy) => {
|
|
50498
|
+
const halfW = textWidth / 2;
|
|
50499
|
+
const halfH = textHeight / 2;
|
|
50500
|
+
const tx = cx - halfW * cos + halfH * sin;
|
|
50501
|
+
const ty = cy - halfW * sin - halfH * cos;
|
|
50502
|
+
stream.save();
|
|
50503
|
+
if (needsAlpha) stream.setGraphicsState(gsName);
|
|
50504
|
+
stream.setFillColor(color);
|
|
50505
|
+
stream.beginText();
|
|
50506
|
+
stream.setFont(resourceName, fontSize);
|
|
50507
|
+
stream.setTextMatrix(cos, sin, -sin, cos, tx, ty);
|
|
50508
|
+
const hex = fontManager.encodeText(watermark.text, resourceName);
|
|
50509
|
+
if (hex) stream.showTextHex(hex);
|
|
50510
|
+
else stream.showText(watermark.text);
|
|
50511
|
+
stream.endText();
|
|
50512
|
+
stream.restore();
|
|
50513
|
+
};
|
|
50514
|
+
if (watermark.repeat) {
|
|
50515
|
+
const spacingX = watermark.repeatSpacingX ?? TEXT_WM_DEFAULTS.repeatSpacingX;
|
|
50516
|
+
const spacingY = watermark.repeatSpacingY ?? TEXT_WM_DEFAULTS.repeatSpacingY;
|
|
50517
|
+
renderRepeatedPattern(page.width, page.height, spacingX, spacingY, drawSingleWatermark);
|
|
50518
|
+
} else {
|
|
50519
|
+
const { cx, cy } = resolveWatermarkCenter(page, watermark.position);
|
|
50520
|
+
drawSingleWatermark(cx, cy);
|
|
50521
|
+
}
|
|
50522
|
+
return {
|
|
50523
|
+
alphaValues: needsAlpha ? [opacity] : [],
|
|
50524
|
+
imageXObjects: []
|
|
50525
|
+
};
|
|
50526
|
+
}
|
|
50527
|
+
/**
|
|
50528
|
+
* Render an image watermark on a single page.
|
|
50529
|
+
*/
|
|
50530
|
+
function renderImageWatermark(stream, page, watermark) {
|
|
50531
|
+
const opacity = watermark.opacity ?? IMAGE_WM_DEFAULTS.opacity;
|
|
50532
|
+
const rotation = watermark.rotation ?? IMAGE_WM_DEFAULTS.rotation;
|
|
50533
|
+
const scale = watermark.scale ?? IMAGE_WM_DEFAULTS.scale;
|
|
50534
|
+
const needsAlpha = opacity < 1;
|
|
50535
|
+
let imgWidth;
|
|
50536
|
+
let imgHeight;
|
|
50537
|
+
if (watermark.width !== void 0 && watermark.height !== void 0) {
|
|
50538
|
+
imgWidth = watermark.width;
|
|
50539
|
+
imgHeight = watermark.height;
|
|
50540
|
+
} else {
|
|
50541
|
+
const dims = parseImageDimensions(watermark.data, watermark.format);
|
|
50542
|
+
const targetSize = Math.min(page.width, page.height) * scale;
|
|
50543
|
+
const maxDim = Math.max(dims.width, dims.height);
|
|
50544
|
+
const ratio = maxDim > 0 ? targetSize / maxDim : 1;
|
|
50545
|
+
imgWidth = dims.width * ratio;
|
|
50546
|
+
imgHeight = dims.height * ratio;
|
|
50547
|
+
}
|
|
50548
|
+
const radians = rotation * Math.PI / 180;
|
|
50549
|
+
const cos = Math.cos(radians);
|
|
50550
|
+
const sin = Math.sin(radians);
|
|
50551
|
+
const gsName = needsAlpha ? alphaGsName(opacity) : "";
|
|
50552
|
+
const imgName = "WmImg";
|
|
50553
|
+
const drawSingleWatermark = (cx, cy) => {
|
|
50554
|
+
stream.save();
|
|
50555
|
+
if (needsAlpha) stream.setGraphicsState(gsName);
|
|
50556
|
+
const halfW = imgWidth / 2;
|
|
50557
|
+
const halfH = imgHeight / 2;
|
|
50558
|
+
const tx = cx - halfW * cos + halfH * sin;
|
|
50559
|
+
const ty = cy - halfW * sin - halfH * cos;
|
|
50560
|
+
stream.concat(imgWidth * cos, imgWidth * sin, -imgHeight * sin, imgHeight * cos, tx, ty);
|
|
50561
|
+
stream.doXObject(imgName);
|
|
50562
|
+
stream.restore();
|
|
50563
|
+
};
|
|
50564
|
+
if (watermark.repeat) {
|
|
50565
|
+
const spacingX = watermark.repeatSpacingX ?? IMAGE_WM_DEFAULTS.repeatSpacingX;
|
|
50566
|
+
const spacingY = watermark.repeatSpacingY ?? IMAGE_WM_DEFAULTS.repeatSpacingY;
|
|
50567
|
+
renderRepeatedPattern(page.width, page.height, spacingX, spacingY, drawSingleWatermark);
|
|
50568
|
+
} else {
|
|
50569
|
+
const { cx, cy } = resolveWatermarkCenter(page, watermark.position);
|
|
50570
|
+
drawSingleWatermark(cx, cy);
|
|
50571
|
+
}
|
|
50572
|
+
return {
|
|
50573
|
+
alphaValues: needsAlpha ? [opacity] : [],
|
|
50574
|
+
imageXObjects: [{
|
|
50575
|
+
name: imgName,
|
|
50576
|
+
data: watermark.data,
|
|
50577
|
+
format: watermark.format
|
|
50578
|
+
}]
|
|
50579
|
+
};
|
|
50580
|
+
}
|
|
50581
|
+
/**
|
|
50582
|
+
* Parse image dimensions from raw JPEG or PNG data without a full decode.
|
|
50583
|
+
*/
|
|
50584
|
+
function parseImageDimensions(data, format) {
|
|
50585
|
+
if (format === "png") return parsePngDimensions(data);
|
|
50586
|
+
return parseJpegDimensions(data);
|
|
50587
|
+
}
|
|
50588
|
+
/** Read width/height from a PNG IHDR chunk (bytes 16-23). */
|
|
50589
|
+
function parsePngDimensions(data) {
|
|
50590
|
+
if (data.length >= 24 && data[12] === 73 && data[13] === 72 && data[14] === 68 && data[15] === 82) return {
|
|
50591
|
+
width: data[16] << 24 | data[17] << 16 | data[18] << 8 | data[19],
|
|
50592
|
+
height: data[20] << 24 | data[21] << 16 | data[22] << 8 | data[23]
|
|
50593
|
+
};
|
|
50594
|
+
return {
|
|
50595
|
+
width: 1,
|
|
50596
|
+
height: 1
|
|
50597
|
+
};
|
|
50598
|
+
}
|
|
50599
|
+
/** Read width/height from JPEG SOF marker. */
|
|
50600
|
+
function parseJpegDimensions(data) {
|
|
50601
|
+
let offset = 2;
|
|
50602
|
+
while (offset < data.length - 1) {
|
|
50603
|
+
while (offset < data.length && data[offset] === 255 && data[offset + 1] === 255) offset++;
|
|
50604
|
+
if (offset >= data.length - 1 || data[offset] !== 255) break;
|
|
50605
|
+
const marker = data[offset + 1];
|
|
50606
|
+
if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204 && offset + 8 < data.length) return {
|
|
50607
|
+
width: data[offset + 7] << 8 | data[offset + 8],
|
|
50608
|
+
height: data[offset + 5] << 8 | data[offset + 6]
|
|
50609
|
+
};
|
|
50610
|
+
if (offset + 3 >= data.length) break;
|
|
50611
|
+
const segLen = data[offset + 2] << 8 | data[offset + 3];
|
|
50612
|
+
offset += 2 + segLen;
|
|
50613
|
+
}
|
|
50614
|
+
return {
|
|
50615
|
+
width: 1,
|
|
50616
|
+
height: 1
|
|
50617
|
+
};
|
|
50618
|
+
}
|
|
50619
|
+
/**
|
|
50620
|
+
* Resolve the center position for a watermark on a given page.
|
|
50621
|
+
*/
|
|
50622
|
+
function resolveWatermarkCenter(page, position) {
|
|
50623
|
+
if (!position || position === "center") return {
|
|
50624
|
+
cx: page.width / 2,
|
|
50625
|
+
cy: page.height / 2
|
|
50626
|
+
};
|
|
50627
|
+
return {
|
|
50628
|
+
cx: position.x,
|
|
50629
|
+
cy: position.y
|
|
50630
|
+
};
|
|
50631
|
+
}
|
|
50632
|
+
/**
|
|
50633
|
+
* Render a repeated pattern of watermarks across the entire page.
|
|
50634
|
+
* Uses a staggered grid for a natural diagonal tiling effect.
|
|
50635
|
+
*/
|
|
50636
|
+
function renderRepeatedPattern(pageWidth, pageHeight, spacingX, spacingY, drawFn) {
|
|
50637
|
+
const margin = Math.max(pageWidth, pageHeight) * .5;
|
|
50638
|
+
let rowIndex = 0;
|
|
50639
|
+
for (let y = -margin; y < pageHeight + margin; y += spacingY) {
|
|
50640
|
+
const offsetX = rowIndex % 2 === 1 ? spacingX / 2 : 0;
|
|
50641
|
+
for (let x = -margin; x < pageWidth + margin; x += spacingX) drawFn(x + offsetX, y);
|
|
50642
|
+
rowIndex++;
|
|
50643
|
+
}
|
|
50644
|
+
}
|
|
48732
50645
|
//#endregion
|
|
48733
50646
|
//#region src/modules/pdf/render/layout-engine.ts
|
|
48734
50647
|
const DEFAULT_COLUMN_WIDTH = 8.43;
|
|
@@ -49631,7 +51544,15 @@ onmessage = async (ev) => {
|
|
|
49631
51544
|
ensureAtLeastOnePage(allPages, documentOptions, sheets);
|
|
49632
51545
|
fixPageNumbers(allPages);
|
|
49633
51546
|
trackFontsForHeaders(allPages, fontManager);
|
|
49634
|
-
const
|
|
51547
|
+
const watermark = documentOptions.watermark;
|
|
51548
|
+
if (watermark && watermark.type === "text") {
|
|
51549
|
+
const wmFontFamily = watermark.fontFamily ?? "Helvetica";
|
|
51550
|
+
const wmBold = watermark.bold ?? false;
|
|
51551
|
+
const wmItalic = watermark.italic ?? false;
|
|
51552
|
+
if (fontManager.hasEmbeddedFont()) fontManager.trackText(watermark.text);
|
|
51553
|
+
else fontManager.ensureFont(resolvePdfFontName(wmFontFamily, wmBold, wmItalic));
|
|
51554
|
+
}
|
|
51555
|
+
const { pageObjNums, sheetFirstPage, pagesTreeObjNum } = await renderAllPages(allPages, fontManager, writer, fontManager.writeFontResources(writer), watermark);
|
|
49635
51556
|
return buildFinalPdf(writer, pageObjNums, pagesTreeObjNum, sheetFirstPage, documentOptions, workbook, options);
|
|
49636
51557
|
}
|
|
49637
51558
|
function ensureAtLeastOnePage(allPages, documentOptions, sheets) {
|
|
@@ -49664,13 +51585,13 @@ onmessage = async (ev) => {
|
|
|
49664
51585
|
for (const page of allPages) if (page.options.showSheetNames) fontManager.ensureFont(resolvePdfFontName(page.options.defaultFontFamily, true, false));
|
|
49665
51586
|
}
|
|
49666
51587
|
}
|
|
49667
|
-
async function renderAllPages(allPages, fontManager, writer, fontObjectMap) {
|
|
51588
|
+
async function renderAllPages(allPages, fontManager, writer, fontObjectMap, watermark) {
|
|
49668
51589
|
const pageObjNums = [];
|
|
49669
51590
|
const pagesTreeObjNum = writer.allocObject();
|
|
49670
51591
|
const sheetFirstPage = /* @__PURE__ */ new Map();
|
|
49671
51592
|
const totalPages = allPages.length;
|
|
49672
51593
|
for (let i = 0; i < allPages.length; i++) {
|
|
49673
|
-
renderSinglePage(allPages[i], fontManager, writer, fontObjectMap, totalPages, pageObjNums, pagesTreeObjNum, sheetFirstPage);
|
|
51594
|
+
renderSinglePage(allPages[i], fontManager, writer, fontObjectMap, totalPages, pageObjNums, pagesTreeObjNum, sheetFirstPage, watermark);
|
|
49674
51595
|
if (i < allPages.length - 1) await yieldToEventLoop();
|
|
49675
51596
|
}
|
|
49676
51597
|
return {
|
|
@@ -49679,7 +51600,7 @@ onmessage = async (ev) => {
|
|
|
49679
51600
|
pagesTreeObjNum
|
|
49680
51601
|
};
|
|
49681
51602
|
}
|
|
49682
|
-
function renderSinglePage(page, fontManager, writer, fontObjectMap, totalPages, pageObjNums, pagesTreeObjNum, sheetFirstPage) {
|
|
51603
|
+
function renderSinglePage(page, fontManager, writer, fontObjectMap, totalPages, pageObjNums, pagesTreeObjNum, sheetFirstPage, watermark) {
|
|
49683
51604
|
try {
|
|
49684
51605
|
const { stream: contentStream, alphaValues } = renderPage(page, page.options, fontManager, totalPages);
|
|
49685
51606
|
const imageXObjects = /* @__PURE__ */ new Map();
|
|
@@ -49690,9 +51611,24 @@ onmessage = async (ev) => {
|
|
|
49690
51611
|
imageXObjects.set(imgName, imgObjNum);
|
|
49691
51612
|
contentStream.drawImage(imgName, img.rect.x, img.rect.y, img.rect.width, img.rect.height);
|
|
49692
51613
|
}
|
|
51614
|
+
let watermarkContentObjNum;
|
|
51615
|
+
if (watermark && isWatermarkApplicable(watermark, page)) {
|
|
51616
|
+
const wmContentStream = new PdfContentStream();
|
|
51617
|
+
const wmResult = renderWatermark(wmContentStream, page, watermark, fontManager);
|
|
51618
|
+
for (const alpha of wmResult.alphaValues) alphaValues.add(alpha);
|
|
51619
|
+
for (const wmImg of wmResult.imageXObjects) {
|
|
51620
|
+
const imgObjNum = writeImageXObject(writer, wmImg.data, wmImg.format);
|
|
51621
|
+
imageXObjects.set(wmImg.name, imgObjNum);
|
|
51622
|
+
}
|
|
51623
|
+
watermarkContentObjNum = writer.allocObject();
|
|
51624
|
+
writer.addStreamObject(watermarkContentObjNum, new PdfDict(), wmContentStream);
|
|
51625
|
+
}
|
|
49693
51626
|
const contentObjNum = writer.allocObject();
|
|
49694
|
-
|
|
49695
|
-
|
|
51627
|
+
writer.addStreamObject(contentObjNum, new PdfDict(), contentStream);
|
|
51628
|
+
let contentsRef;
|
|
51629
|
+
if (watermarkContentObjNum) if ((watermark?.placement ?? "under") === "over") contentsRef = `[${pdfRef(contentObjNum)} ${pdfRef(watermarkContentObjNum)}]`;
|
|
51630
|
+
else contentsRef = `[${pdfRef(watermarkContentObjNum)} ${pdfRef(contentObjNum)}]`;
|
|
51631
|
+
else contentsRef = pdfRef(contentObjNum);
|
|
49696
51632
|
const resourcesObjNum = writer.allocObject();
|
|
49697
51633
|
const fontDictStr = fontManager.buildFontDictString(fontObjectMap);
|
|
49698
51634
|
const resourcesDict = new PdfDict().set("Font", fontDictStr);
|
|
@@ -49726,7 +51662,7 @@ onmessage = async (ev) => {
|
|
|
49726
51662
|
parentRef: pagesTreeObjNum,
|
|
49727
51663
|
width: page.width,
|
|
49728
51664
|
height: page.height,
|
|
49729
|
-
contentsRef
|
|
51665
|
+
contentsRef,
|
|
49730
51666
|
resourcesRef: resourcesObjNum,
|
|
49731
51667
|
annotRefs: annotRefs.length > 0 ? annotRefs : void 0
|
|
49732
51668
|
});
|
|
@@ -49805,7 +51741,8 @@ onmessage = async (ev) => {
|
|
|
49805
51741
|
title: options?.title ?? "",
|
|
49806
51742
|
author: options?.author ?? "",
|
|
49807
51743
|
subject: options?.subject ?? "",
|
|
49808
|
-
creator: options?.creator ?? "excelts"
|
|
51744
|
+
creator: options?.creator ?? "excelts",
|
|
51745
|
+
watermark: options?.watermark
|
|
49809
51746
|
};
|
|
49810
51747
|
}
|
|
49811
51748
|
/** Map PaperSize enum values to PDF page sizes. */
|
|
@@ -49872,6 +51809,20 @@ onmessage = async (ev) => {
|
|
|
49872
51809
|
return outlinesObjNum;
|
|
49873
51810
|
}
|
|
49874
51811
|
/**
|
|
51812
|
+
* Check if a watermark should be applied to a specific page based on
|
|
51813
|
+
* optional page number and sheet name filters.
|
|
51814
|
+
*/
|
|
51815
|
+
function isWatermarkApplicable(watermark, page) {
|
|
51816
|
+
if (watermark.pages && watermark.pages.length > 0) {
|
|
51817
|
+
if (!watermark.pages.includes(page.pageNumber)) return false;
|
|
51818
|
+
}
|
|
51819
|
+
if (watermark.sheets && watermark.sheets.length > 0) {
|
|
51820
|
+
const sheetLower = page.sheetName.toLowerCase();
|
|
51821
|
+
if (!watermark.sheets.some((s) => s.toLowerCase() === sheetLower)) return false;
|
|
51822
|
+
}
|
|
51823
|
+
return true;
|
|
51824
|
+
}
|
|
51825
|
+
/**
|
|
49875
51826
|
* Write a JPEG or PNG image as a PDF XObject Image.
|
|
49876
51827
|
*/
|
|
49877
51828
|
function writeImageXObject(writer, data, format) {
|
|
@@ -49883,7 +51834,7 @@ onmessage = async (ev) => {
|
|
|
49883
51834
|
*/
|
|
49884
51835
|
function writeJpegImageXObject(writer, data) {
|
|
49885
51836
|
const objNum = writer.allocObject();
|
|
49886
|
-
const dims =
|
|
51837
|
+
const dims = parseImageDimensions(data, "jpeg");
|
|
49887
51838
|
const dict = new PdfDict().set("Type", "/XObject").set("Subtype", "/Image").set("Width", pdfNumber(dims.width)).set("Height", pdfNumber(dims.height)).set("ColorSpace", "/DeviceRGB").set("BitsPerComponent", "8").set("Filter", "/DCTDecode");
|
|
49888
51839
|
writer.addStreamObject(objNum, dict, data);
|
|
49889
51840
|
return objNum;
|
|
@@ -49904,37 +51855,6 @@ onmessage = async (ev) => {
|
|
|
49904
51855
|
writer.addStreamObject(objNum, dict, png.pixels);
|
|
49905
51856
|
return objNum;
|
|
49906
51857
|
}
|
|
49907
|
-
/**
|
|
49908
|
-
* Extract width and height from a JPEG file header.
|
|
49909
|
-
* Scans for SOF markers (SOF0-SOF3, SOF5-SOF7, SOF9-SOF11, SOF13-SOF15)
|
|
49910
|
-
* which all share the same frame header layout.
|
|
49911
|
-
* Handles 0xFF padding bytes per JPEG spec.
|
|
49912
|
-
*/
|
|
49913
|
-
function getJpegDimensions(data) {
|
|
49914
|
-
let offset = 2;
|
|
49915
|
-
while (offset < data.length - 1) {
|
|
49916
|
-
while (offset < data.length && data[offset] === 255 && data[offset + 1] === 255) offset++;
|
|
49917
|
-
if (offset >= data.length - 1 || data[offset] !== 255) break;
|
|
49918
|
-
const marker = data[offset + 1];
|
|
49919
|
-
if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
|
|
49920
|
-
if (offset + 8 < data.length) {
|
|
49921
|
-
const height = data[offset + 5] << 8 | data[offset + 6];
|
|
49922
|
-
return {
|
|
49923
|
-
width: data[offset + 7] << 8 | data[offset + 8],
|
|
49924
|
-
height
|
|
49925
|
-
};
|
|
49926
|
-
}
|
|
49927
|
-
break;
|
|
49928
|
-
}
|
|
49929
|
-
if (offset + 3 >= data.length) break;
|
|
49930
|
-
const segLen = data[offset + 2] << 8 | data[offset + 3];
|
|
49931
|
-
offset += 2 + segLen;
|
|
49932
|
-
}
|
|
49933
|
-
return {
|
|
49934
|
-
width: 1,
|
|
49935
|
-
height: 1
|
|
49936
|
-
};
|
|
49937
|
-
}
|
|
49938
51858
|
//#endregion
|
|
49939
51859
|
//#region src/modules/pdf/pdf.ts
|
|
49940
51860
|
/**
|
|
@@ -50509,6 +52429,7 @@ onmessage = async (ev) => {
|
|
|
50509
52429
|
exports.concatUint8Arrays = concatUint8Arrays;
|
|
50510
52430
|
exports.createCsvFormatterStream = createCsvFormatterStream;
|
|
50511
52431
|
exports.createCsvParserStream = createCsvParserStream;
|
|
52432
|
+
exports.createTextWatermarkImage = createTextWatermarkImage;
|
|
50512
52433
|
exports.dateToExcel = dateToExcel;
|
|
50513
52434
|
exports.decodeCell = decodeCell;
|
|
50514
52435
|
exports.decodeCol = decodeCol;
|