@cj-tech-master/excelts 6.0.0 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/browser/modules/excel/anchor.js +10 -4
  2. package/dist/browser/modules/excel/stream/workbook-writer.browser.d.ts +13 -0
  3. package/dist/browser/modules/excel/stream/workbook-writer.browser.js +32 -1
  4. package/dist/browser/modules/excel/stream/worksheet-writer.d.ts +56 -2
  5. package/dist/browser/modules/excel/stream/worksheet-writer.js +90 -3
  6. package/dist/browser/modules/excel/utils/drawing-utils.d.ts +77 -0
  7. package/dist/browser/modules/excel/utils/drawing-utils.js +113 -0
  8. package/dist/browser/modules/excel/worksheet.js +1 -1
  9. package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.d.ts +0 -1
  10. package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +43 -64
  11. package/dist/browser/modules/excel/xlsx/xlsx.browser.js +2 -19
  12. package/dist/cjs/modules/excel/anchor.js +10 -4
  13. package/dist/cjs/modules/excel/stream/workbook-writer.browser.js +31 -0
  14. package/dist/cjs/modules/excel/stream/worksheet-writer.js +89 -2
  15. package/dist/cjs/modules/excel/utils/drawing-utils.js +118 -0
  16. package/dist/cjs/modules/excel/worksheet.js +1 -1
  17. package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +42 -63
  18. package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +2 -19
  19. package/dist/esm/modules/excel/anchor.js +10 -4
  20. package/dist/esm/modules/excel/stream/workbook-writer.browser.js +32 -1
  21. package/dist/esm/modules/excel/stream/worksheet-writer.js +90 -3
  22. package/dist/esm/modules/excel/utils/drawing-utils.js +113 -0
  23. package/dist/esm/modules/excel/worksheet.js +1 -1
  24. package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +43 -64
  25. package/dist/esm/modules/excel/xlsx/xlsx.browser.js +2 -19
  26. package/dist/iife/excelts.iife.js +237 -73
  27. package/dist/iife/excelts.iife.js.map +1 -1
  28. package/dist/iife/excelts.iife.min.js +34 -34
  29. package/dist/types/modules/excel/stream/workbook-writer.browser.d.ts +13 -0
  30. package/dist/types/modules/excel/stream/worksheet-writer.d.ts +56 -2
  31. package/dist/types/modules/excel/utils/drawing-utils.d.ts +77 -0
  32. package/dist/types/modules/excel/xlsx/xform/sheet/worksheet-xform.d.ts +0 -1
  33. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @cj-tech-master/excelts v6.0.0
2
+ * @cj-tech-master/excelts v6.1.0
3
3
  * TypeScript Excel Workbook Manager - Read and Write xlsx and csv Files.
4
4
  * (c) 2026 cjnoname
5
5
  * Released under the MIT License
@@ -2520,18 +2520,20 @@ var ExcelTS = (function(exports) {
2520
2520
  return new Anchor(void 0, model);
2521
2521
  }
2522
2522
  get col() {
2523
- return this.nativeCol + Math.min(this.colWidth - 1, this.nativeColOff) / this.colWidth;
2523
+ return this.nativeColOff === 0 ? this.nativeCol : this.nativeCol + Math.min(this.colWidth - 1, this.nativeColOff) / this.colWidth;
2524
2524
  }
2525
2525
  set col(v) {
2526
2526
  this.nativeCol = Math.floor(v);
2527
- this.nativeColOff = Math.floor((v - this.nativeCol) * this.colWidth);
2527
+ const fraction = v - this.nativeCol;
2528
+ this.nativeColOff = fraction === 0 ? 0 : Math.floor(fraction * this.colWidth);
2528
2529
  }
2529
2530
  get row() {
2530
- return this.nativeRow + Math.min(this.rowHeight - 1, this.nativeRowOff) / this.rowHeight;
2531
+ return this.nativeRowOff === 0 ? this.nativeRow : this.nativeRow + Math.min(this.rowHeight - 1, this.nativeRowOff) / this.rowHeight;
2531
2532
  }
2532
2533
  set row(v) {
2533
2534
  this.nativeRow = Math.floor(v);
2534
- this.nativeRowOff = Math.floor((v - this.nativeRow) * this.rowHeight);
2535
+ const fraction = v - this.nativeRow;
2536
+ this.nativeRowOff = fraction === 0 ? 0 : Math.floor(fraction * this.rowHeight);
2535
2537
  }
2536
2538
  get colWidth() {
2537
2539
  return this.worksheet && this.worksheet.getColumn(this.nativeCol + 1) && this.worksheet.getColumn(this.nativeCol + 1).isCustomWidth ? Math.floor(this.worksheet.getColumn(this.nativeCol + 1).width * 1e4) : 64e4;
@@ -5257,7 +5259,7 @@ var ExcelTS = (function(exports) {
5257
5259
  * Duplicate rows and insert new rows
5258
5260
  */
5259
5261
  duplicateRow(rowNum, count, insert = false) {
5260
- const rSrc = this._rows[rowNum - 1];
5262
+ const rSrc = this.getRow(rowNum);
5261
5263
  const inserts = Array.from({ length: count }).fill(rSrc.values);
5262
5264
  const srcMerges = [];
5263
5265
  for (const merge of Object.values(this._merges)) if (merge.top === rowNum && merge.bottom === rowNum) srcMerges.push(merge);
@@ -13440,6 +13442,94 @@ var ExcelTS = (function(exports) {
13440
13442
  }
13441
13443
  };
13442
13444
  //#endregion
13445
+ //#region src/modules/excel/utils/drawing-utils.ts
13446
+ /**
13447
+ * Shared utilities for building drawing models (anchors + relationships)
13448
+ * used by both the streaming WorksheetWriter and the non-streaming WorkSheetXform.
13449
+ *
13450
+ * This eliminates the duplicated anchor/rel building logic and provides
13451
+ * a single, correct image-rel deduplication strategy.
13452
+ */
13453
+ /**
13454
+ * Resolves a media filename into the drawing-level relative target path.
13455
+ *
13456
+ * In the non-streaming path, media entries have separate `name` and `extension`
13457
+ * fields (e.g. name="image0", extension="png").
13458
+ * In the streaming path, `name` already includes the extension (e.g. "image0.png").
13459
+ *
13460
+ * This function accepts both forms and returns e.g. `"../media/image0.png"`.
13461
+ */
13462
+ function resolveMediaTarget(medium) {
13463
+ return mediaRelTargetFromRels(medium.name && medium.extension && medium.name.endsWith(`.${medium.extension}`) ? medium.name : `${medium.name}.${medium.extension}`);
13464
+ }
13465
+ /**
13466
+ * Build the drawing anchors and relationships from a list of image media entries.
13467
+ *
13468
+ * This is the core logic shared between:
13469
+ * - `WorksheetWriter._writeDrawing()` (streaming)
13470
+ * - `WorkSheetXform.prepare()` (non-streaming)
13471
+ *
13472
+ * It correctly deduplicates image rels: if the same `imageId` is used for
13473
+ * multiple anchors, only one image relationship is created and shared.
13474
+ */
13475
+ function buildDrawingAnchorsAndRels(media, existingRels, options) {
13476
+ const anchors = [];
13477
+ const rels = [...existingRels];
13478
+ const imageRIdMap = {};
13479
+ for (const medium of media) {
13480
+ const imageId = String(medium.imageId);
13481
+ const bookImage = options.getBookImage(medium.imageId);
13482
+ if (!bookImage) continue;
13483
+ let rIdImage = imageRIdMap[imageId];
13484
+ if (!rIdImage) {
13485
+ rIdImage = options.nextRId(rels);
13486
+ imageRIdMap[imageId] = rIdImage;
13487
+ rels.push({
13488
+ Id: rIdImage,
13489
+ Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
13490
+ Target: resolveMediaTarget(bookImage)
13491
+ });
13492
+ }
13493
+ const anchor = {
13494
+ picture: { rId: rIdImage },
13495
+ range: medium.range
13496
+ };
13497
+ if (medium.hyperlinks && medium.hyperlinks.hyperlink) {
13498
+ const rIdHyperlink = options.nextRId(rels);
13499
+ anchor.picture.hyperlinks = {
13500
+ tooltip: medium.hyperlinks.tooltip,
13501
+ rId: rIdHyperlink
13502
+ };
13503
+ rels.push({
13504
+ Id: rIdHyperlink,
13505
+ Type: RelType.Hyperlink,
13506
+ Target: medium.hyperlinks.hyperlink,
13507
+ TargetMode: "External"
13508
+ });
13509
+ }
13510
+ anchors.push(anchor);
13511
+ }
13512
+ return {
13513
+ anchors,
13514
+ rels
13515
+ };
13516
+ }
13517
+ /**
13518
+ * Filter drawing anchors to remove invalid entries before XML generation.
13519
+ *
13520
+ * Shared between streaming `WorkbookWriterBase.addDrawings()` and
13521
+ * non-streaming `XLSX.addDrawings()`.
13522
+ */
13523
+ function filterDrawingAnchors(anchors) {
13524
+ return anchors.filter((a) => {
13525
+ if (a == null) return false;
13526
+ if (a.range?.br && a.shape) return true;
13527
+ if (!a.range?.br && !a.picture) return false;
13528
+ if (a.range?.br && !a.picture && !a.shape) return false;
13529
+ return true;
13530
+ });
13531
+ }
13532
+ //#endregion
13443
13533
  //#region src/modules/excel/xlsx/xform/sheet/worksheet-xform.ts
13444
13534
  const mergeRule = (rule, extRule) => {
13445
13535
  Object.keys(extRule).forEach((key) => {
@@ -13601,68 +13691,46 @@ var ExcelTS = (function(exports) {
13601
13691
  Target: drawingRelTargetFromWorksheet(drawing.name)
13602
13692
  });
13603
13693
  }
13604
- const drawingRelsHash = [];
13605
- let bookImage;
13694
+ const backgroundMedia = [];
13695
+ const imageMedia = [];
13606
13696
  model.media.forEach((medium) => {
13607
- if (medium.type === "background") {
13608
- const rId = nextRid(rels);
13609
- bookImage = options.media[medium.imageId];
13697
+ if (medium.type === "background") backgroundMedia.push(medium);
13698
+ else if (medium.type === "image") imageMedia.push(medium);
13699
+ });
13700
+ backgroundMedia.forEach((medium) => {
13701
+ const rId = nextRid(rels);
13702
+ const bookImage = options.media[medium.imageId];
13703
+ rels.push({
13704
+ Id: rId,
13705
+ Type: RelType.Image,
13706
+ Target: resolveMediaTarget(bookImage)
13707
+ });
13708
+ model.background = { rId };
13709
+ model.image = options.media[medium.imageId];
13710
+ });
13711
+ if (imageMedia.length > 0) {
13712
+ let { drawing } = model;
13713
+ if (!drawing) {
13714
+ drawing = model.drawing = {
13715
+ rId: nextRid(rels),
13716
+ name: `drawing${++options.drawingsCount}`,
13717
+ anchors: [],
13718
+ rels: []
13719
+ };
13720
+ options.drawings.push(drawing);
13610
13721
  rels.push({
13611
- Id: rId,
13612
- Type: RelType.Image,
13613
- Target: mediaRelTargetFromRels(`${bookImage.name}.${bookImage.extension}`)
13722
+ Id: drawing.rId,
13723
+ Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
13724
+ Target: drawingRelTargetFromWorksheet(drawing.name)
13614
13725
  });
13615
- model.background = { rId };
13616
- model.image = options.media[medium.imageId];
13617
- } else if (medium.type === "image") {
13618
- let { drawing } = model;
13619
- bookImage = options.media[medium.imageId];
13620
- if (!drawing) {
13621
- drawing = model.drawing = {
13622
- rId: nextRid(rels),
13623
- name: `drawing${++options.drawingsCount}`,
13624
- anchors: [],
13625
- rels: []
13626
- };
13627
- options.drawings.push(drawing);
13628
- rels.push({
13629
- Id: drawing.rId,
13630
- Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
13631
- Target: drawingRelTargetFromWorksheet(drawing.name)
13632
- });
13633
- }
13634
- let rIdImage = this.preImageId === medium.imageId ? drawingRelsHash[medium.imageId] : drawingRelsHash[drawing.rels.length];
13635
- if (!rIdImage) {
13636
- rIdImage = nextRid(drawing.rels);
13637
- drawingRelsHash[drawing.rels.length] = rIdImage;
13638
- drawing.rels.push({
13639
- Id: rIdImage,
13640
- Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
13641
- Target: mediaRelTargetFromRels(`${bookImage.name}.${bookImage.extension}`)
13642
- });
13643
- }
13644
- const anchor = {
13645
- picture: { rId: rIdImage },
13646
- range: medium.range
13647
- };
13648
- if (medium.hyperlinks && medium.hyperlinks.hyperlink) {
13649
- const rIdHyperLink = nextRid(drawing.rels);
13650
- drawingRelsHash[drawing.rels.length] = rIdHyperLink;
13651
- anchor.picture.hyperlinks = {
13652
- tooltip: medium.hyperlinks.tooltip,
13653
- rId: rIdHyperLink
13654
- };
13655
- drawing.rels.push({
13656
- Id: rIdHyperLink,
13657
- Type: RelType.Hyperlink,
13658
- Target: medium.hyperlinks.hyperlink,
13659
- TargetMode: "External"
13660
- });
13661
- }
13662
- this.preImageId = medium.imageId;
13663
- drawing.anchors.push(anchor);
13664
13726
  }
13665
- });
13727
+ const result = buildDrawingAnchorsAndRels(imageMedia, drawing.rels, {
13728
+ getBookImage: (id) => options.media[id],
13729
+ nextRId: (currentRels) => nextRid(currentRels)
13730
+ });
13731
+ drawing.anchors.push(...result.anchors);
13732
+ drawing.rels = result.rels;
13733
+ }
13666
13734
  model.tables.forEach((table) => {
13667
13735
  const rId = nextRid(rels);
13668
13736
  table.rId = rId;
@@ -29647,13 +29715,7 @@ self.onmessage = async function(event) {
29647
29715
  if (drawing) {
29648
29716
  if (this.drawingHasChartReference(drawing) && rawDrawings[drawing.name]) zip.append(rawDrawings[drawing.name], { name: drawingPath(drawing.name) });
29649
29717
  else {
29650
- const filteredAnchors = (drawing.anchors ?? []).filter((a) => {
29651
- if (a == null) return false;
29652
- if (a.range?.br && a.shape) return true;
29653
- if (!a.br && !a.picture) return false;
29654
- if (a.br && !a.picture && !a.shape) return false;
29655
- return true;
29656
- });
29718
+ const filteredAnchors = filterDrawingAnchors(drawing.anchors ?? []);
29657
29719
  const drawingForWrite = drawing.anchors ? {
29658
29720
  ...drawing,
29659
29721
  anchors: filteredAnchors
@@ -29995,6 +30057,7 @@ self.onmessage = async function(event) {
29995
30057
  pageSeteup: new PageSetupXform(),
29996
30058
  autoFilter: new AutoFilterXform$1(),
29997
30059
  picture: new PictureXform(),
30060
+ drawing: new DrawingXform$1(),
29998
30061
  conditionalFormattings: new ConditionalFormattingsXform(),
29999
30062
  headerFooter: new HeaderFooterXform(),
30000
30063
  rowBreaks: new RowBreaksXform(),
@@ -30103,10 +30166,11 @@ self.onmessage = async function(event) {
30103
30166
  this._writeDataValidations();
30104
30167
  this._writePageMargins();
30105
30168
  this._writePageSetup();
30106
- this._writeBackground();
30107
30169
  this._writeHeaderFooter();
30108
30170
  this._writeRowBreaks();
30109
30171
  this._writeColBreaks();
30172
+ this._writeDrawing();
30173
+ this._writeBackground();
30110
30174
  this._writeLegacyData();
30111
30175
  this._writeCloseWorksheet();
30112
30176
  this.stream.end();
@@ -30253,11 +30317,62 @@ self.onmessage = async function(event) {
30253
30317
  else this.conditionalFormatting = [];
30254
30318
  }
30255
30319
  addBackgroundImage(imageId) {
30256
- this._background = { imageId };
30320
+ this._background = { imageId: Number(imageId) };
30257
30321
  }
30258
30322
  getBackgroundImageId() {
30259
30323
  return this._background && this._background.imageId;
30260
30324
  }
30325
+ /**
30326
+ * Using the image id from `WorkbookWriter.addImage`,
30327
+ * embed an image within the worksheet to cover a range.
30328
+ */
30329
+ addImage(imageId, range) {
30330
+ const model = this._parseImageRange(String(imageId), range);
30331
+ this._media.push(model);
30332
+ }
30333
+ /**
30334
+ * Return the images that have been added to this worksheet.
30335
+ * Each entry contains imageId and the normalised range (with native anchors).
30336
+ */
30337
+ getImages() {
30338
+ return this._media;
30339
+ }
30340
+ /**
30341
+ * Parse the user-supplied range into a normalised internal model
30342
+ * mirroring what the regular Worksheet / Image class does.
30343
+ */
30344
+ _parseImageRange(imageId, range) {
30345
+ if (typeof range === "string") {
30346
+ const decoded = colCache.decode(range);
30347
+ if ("top" in decoded) return {
30348
+ type: "image",
30349
+ imageId,
30350
+ range: {
30351
+ tl: new Anchor(this, {
30352
+ col: decoded.left,
30353
+ row: decoded.top
30354
+ }, -1).model,
30355
+ br: new Anchor(this, {
30356
+ col: decoded.right,
30357
+ row: decoded.bottom
30358
+ }, 0).model,
30359
+ editAs: "oneCell"
30360
+ }
30361
+ };
30362
+ throw new Error(`Invalid image range: "${range}". Expected a range like "A1:C3".`);
30363
+ }
30364
+ return {
30365
+ type: "image",
30366
+ imageId,
30367
+ range: {
30368
+ tl: new Anchor(this, range.tl, 0).model,
30369
+ br: range.br ? new Anchor(this, range.br, 0).model : void 0,
30370
+ ext: range.ext,
30371
+ editAs: range.editAs
30372
+ },
30373
+ hyperlinks: range.hyperlinks
30374
+ };
30375
+ }
30261
30376
  async protect(password, options) {
30262
30377
  this.sheetProtection = { sheet: true };
30263
30378
  if (options && "spinCount" in options) options.spinCount = Number.isFinite(options.spinCount) ? Math.round(Math.max(0, options.spinCount)) : 1e5;
@@ -30388,6 +30503,29 @@ self.onmessage = async function(event) {
30388
30503
  _writeAutoFilter() {
30389
30504
  this.stream.write(xform.autoFilter.toXml(this.autoFilter));
30390
30505
  }
30506
+ _writeDrawing() {
30507
+ if (this._media.length === 0) return;
30508
+ const drawingName = `drawing${this.id}`;
30509
+ const drawingRId = this._sheetRelsWriter.addRelationship({
30510
+ Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
30511
+ Target: drawingRelTargetFromWorksheet(drawingName)
30512
+ });
30513
+ const { anchors, rels } = buildDrawingAnchorsAndRels(this._media, [], {
30514
+ getBookImage: (id) => this._workbook.getImage(Number(id)),
30515
+ nextRId: (currentRels) => `rId${currentRels.length + 1}`
30516
+ });
30517
+ this._drawing = {
30518
+ rId: drawingRId,
30519
+ name: drawingName,
30520
+ anchors,
30521
+ rels
30522
+ };
30523
+ this.stream.write(xform.drawing.toXml({ rId: drawingRId }));
30524
+ }
30525
+ /** Returns the drawing model if images were added, for the workbook writer. */
30526
+ get drawing() {
30527
+ return this._drawing;
30528
+ }
30391
30529
  _writeBackground() {
30392
30530
  if (this._background) {
30393
30531
  if (this._background.imageId !== void 0) {
@@ -30510,6 +30648,7 @@ self.onmessage = async function(event) {
30510
30648
  await this.promise;
30511
30649
  await this._commitWorksheets();
30512
30650
  await this.addMedia();
30651
+ this.addDrawings();
30513
30652
  await Promise.all([
30514
30653
  this.addThemes(),
30515
30654
  this.addOfficeRels(),
@@ -30614,11 +30753,13 @@ self.onmessage = async function(event) {
30614
30753
  worksheets.forEach((ws) => {
30615
30754
  ws.fileIndex = ws.id;
30616
30755
  });
30756
+ const drawings = worksheets.filter((ws) => ws.drawing).map((ws) => ws.drawing);
30617
30757
  const model = {
30618
30758
  worksheets,
30619
30759
  sharedStrings: this.sharedStrings,
30620
30760
  commentRefs: this.commentRefs,
30621
30761
  media: this.media,
30762
+ drawings,
30622
30763
  hasCheckboxes: this.styles.hasCheckboxes
30623
30764
  };
30624
30765
  const xform = new ContentTypesXform();
@@ -30647,6 +30788,29 @@ self.onmessage = async function(event) {
30647
30788
  throw new ImageError("Unsupported media");
30648
30789
  }));
30649
30790
  }
30791
+ /**
30792
+ * Generate drawing XML and drawing relationship files for worksheets that have images.
30793
+ * Must be called after _commitWorksheets() so that each WorksheetWriter has built its
30794
+ * drawing model, and after addMedia() so that media files are already in the ZIP.
30795
+ */
30796
+ addDrawings() {
30797
+ const drawingXform = new DrawingXform();
30798
+ const relsXform = new RelationshipsXform();
30799
+ for (const ws of this._worksheets) {
30800
+ if (!ws?.drawing) continue;
30801
+ const { drawing } = ws;
30802
+ const filteredAnchors = filterDrawingAnchors(drawing.anchors);
30803
+ const drawingForWrite = {
30804
+ ...drawing,
30805
+ anchors: filteredAnchors
30806
+ };
30807
+ drawingXform.prepare(drawingForWrite);
30808
+ const xml = drawingXform.toXml(drawingForWrite);
30809
+ this._addFile(xml, drawingPath(drawing.name));
30810
+ const relsXml = relsXform.toXml(drawing.rels);
30811
+ this._addFile(relsXml, drawingRelsPath(drawing.name));
30812
+ }
30813
+ }
30650
30814
  addApp() {
30651
30815
  return new Promise((resolve) => {
30652
30816
  const xform = new AppXform();