node-pptx-templater 1.1.3 → 1.1.4
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/package.json +2 -1
- package/src/core/PPTXTemplater.js +89 -10
- package/src/managers/ShapeManager.js +288 -85
- package/src/managers/TableManager.js +643 -133
|
@@ -48,6 +48,19 @@ class TableManager {
|
|
|
48
48
|
/** @private @type {XMLParser} */
|
|
49
49
|
#xmlParser
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* In-memory registry mapping cellshape names to their original config.
|
|
53
|
+
* Used to reposition shapes after table structure mutations (row removal,
|
|
54
|
+
* insertion, merge, etc.).
|
|
55
|
+
*
|
|
56
|
+
* Key: shape name (e.g. "cellshape_Table_2_5_0")
|
|
57
|
+
* Value: { slideIndex, resolvedTableId, tableId, rowIndex, colIndex, shapeIndex, config }
|
|
58
|
+
*
|
|
59
|
+
* @private
|
|
60
|
+
* @type {Map<string, {slideIndex: number, resolvedTableId: string, tableId: string, rowIndex: number, colIndex: number, shapeIndex: string|number, config: Object}>}
|
|
61
|
+
*/
|
|
62
|
+
#cellShapeAnchors = new Map()
|
|
63
|
+
|
|
51
64
|
/**
|
|
52
65
|
* @param {XMLParser} xmlParser
|
|
53
66
|
*/
|
|
@@ -253,7 +266,15 @@ class TableManager {
|
|
|
253
266
|
* @param {string[]} rowData
|
|
254
267
|
* @param {SlideManager} slideManager
|
|
255
268
|
*/
|
|
256
|
-
addTableRow(slideIndex, tableId, rowData, slideManager, options = {}) {
|
|
269
|
+
addTableRow(slideIndex, tableId, rowData, slideManager, shapeManagerOrOptions, options = {}) {
|
|
270
|
+
let shapeManager = null
|
|
271
|
+
let actualOptions = options
|
|
272
|
+
if (shapeManagerOrOptions && typeof shapeManagerOrOptions.getShapes === 'function') {
|
|
273
|
+
shapeManager = shapeManagerOrOptions
|
|
274
|
+
} else if (shapeManagerOrOptions && typeof shapeManagerOrOptions === 'object') {
|
|
275
|
+
actualOptions = shapeManagerOrOptions
|
|
276
|
+
}
|
|
277
|
+
|
|
257
278
|
const { tblObj } = this.#getTableContext(slideIndex, tableId, slideManager)
|
|
258
279
|
|
|
259
280
|
const trs = tblObj['a:tr'] || []
|
|
@@ -261,6 +282,10 @@ class TableManager {
|
|
|
261
282
|
throw new PPTXError('No rows to clone from')
|
|
262
283
|
}
|
|
263
284
|
|
|
285
|
+
const gridCols = tblObj['a:tblGrid']?.['a:gridCol'] || []
|
|
286
|
+
const gridColsArr = Array.isArray(gridCols) ? gridCols : [gridCols]
|
|
287
|
+
const colWidths = gridColsArr.map(col => parseInt(col['@_w'] || 0, 10))
|
|
288
|
+
|
|
264
289
|
const lastRow = trs[trs.length - 1]
|
|
265
290
|
const numCols = lastRow['a:tc']?.length || 0
|
|
266
291
|
|
|
@@ -273,7 +298,7 @@ class TableManager {
|
|
|
273
298
|
|
|
274
299
|
// Expand each column value to targetHeight
|
|
275
300
|
const expandedCols = []
|
|
276
|
-
const strategy =
|
|
301
|
+
const strategy = actualOptions.mergeStrategy || 'auto'
|
|
277
302
|
for (let c = 0; c < numCols; c++) {
|
|
278
303
|
let colCells = this.#expandCellVal(rowData[c], targetHeight)
|
|
279
304
|
if (strategy === 'none') {
|
|
@@ -290,6 +315,12 @@ class TableManager {
|
|
|
290
315
|
expandedCols.push(colCells)
|
|
291
316
|
}
|
|
292
317
|
|
|
318
|
+
const startRowIndex = trs.length
|
|
319
|
+
const shapeCellsToCreate = []
|
|
320
|
+
const isShapeConfig = val => {
|
|
321
|
+
return val && typeof val === 'object' && typeof val.type === 'string'
|
|
322
|
+
}
|
|
323
|
+
|
|
293
324
|
// Clone and append rows
|
|
294
325
|
for (let r = 0; r < targetHeight; r++) {
|
|
295
326
|
const newRow = this.#xmlParser.deepClone(lastRow)
|
|
@@ -312,10 +343,117 @@ class TableManager {
|
|
|
312
343
|
} else {
|
|
313
344
|
let text = cellDef.value
|
|
314
345
|
let cellOpts = {}
|
|
315
|
-
|
|
346
|
+
|
|
347
|
+
if (isShapeConfig(cellDef.value)) {
|
|
348
|
+
const config = cellDef.value
|
|
349
|
+
const globalRowIndex = startRowIndex + r
|
|
350
|
+
|
|
351
|
+
const isStandardShape = [
|
|
352
|
+
'circle',
|
|
353
|
+
'square',
|
|
354
|
+
'rectangle',
|
|
355
|
+
'triangle',
|
|
356
|
+
'diamond',
|
|
357
|
+
'hexagon',
|
|
358
|
+
'line',
|
|
359
|
+
].includes(config.type)
|
|
360
|
+
|
|
361
|
+
const shapeConfig = { ...config }
|
|
362
|
+
|
|
363
|
+
if (isStandardShape) {
|
|
364
|
+
text =
|
|
365
|
+
config.text !== undefined
|
|
366
|
+
? config.text
|
|
367
|
+
: config.value !== undefined
|
|
368
|
+
? config.value
|
|
369
|
+
: ''
|
|
370
|
+
delete shapeConfig.text // DO NOT render text inside the shape overlay!
|
|
371
|
+
} else {
|
|
372
|
+
text = '' // for badges/icons/progressBars, cell text is empty!
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
shapeCellsToCreate.push({
|
|
376
|
+
rowIndex: globalRowIndex,
|
|
377
|
+
colIndex: c,
|
|
378
|
+
config: shapeConfig,
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
cellOpts = {}
|
|
382
|
+
if (config.cellFill) cellOpts.fill = config.cellFill
|
|
383
|
+
if (config.cellAlign) cellOpts.align = config.cellAlign
|
|
384
|
+
|
|
385
|
+
// Estimate shape dimensions to set margins
|
|
386
|
+
const colWidth_emu = colWidths[c] || 0
|
|
387
|
+
const colWidth_px = colWidth_emu / 9525
|
|
388
|
+
|
|
389
|
+
const parseLength = (val, maxVal) => {
|
|
390
|
+
if (typeof val === 'string' && val.endsWith('%')) {
|
|
391
|
+
return (parseFloat(val) / 100) * maxVal
|
|
392
|
+
}
|
|
393
|
+
return val !== undefined ? parseFloat(val) : undefined
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
let shapeWidth = 12
|
|
397
|
+
let shapeHeight = 12
|
|
398
|
+
|
|
399
|
+
if (config.width !== undefined) {
|
|
400
|
+
shapeWidth = parseLength(config.width, colWidth_px) || 12
|
|
401
|
+
} else if (config.size !== undefined) {
|
|
402
|
+
shapeWidth = parseLength(config.size, colWidth_px) || 12
|
|
403
|
+
} else if (config.radius !== undefined) {
|
|
404
|
+
shapeWidth = (parseLength(config.radius, colWidth_px) || 6) * 2
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (config.height !== undefined) {
|
|
408
|
+
shapeHeight = parseLength(config.height, 50) || 12
|
|
409
|
+
} else if (config.size !== undefined) {
|
|
410
|
+
shapeHeight = parseLength(config.size, 50) || 12
|
|
411
|
+
} else if (config.radius !== undefined) {
|
|
412
|
+
shapeHeight = (parseLength(config.radius, 25) || 6) * 2
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (isStandardShape && text !== '') {
|
|
416
|
+
const position = config.position || (config.text ? 'left' : 'center')
|
|
417
|
+
const tcPr = tcObj['a:tcPr'] || {}
|
|
418
|
+
const currentMarL =
|
|
419
|
+
tcPr['@_marL'] !== undefined ? parseInt(tcPr['@_marL'], 10) : 91440
|
|
420
|
+
const currentMarR =
|
|
421
|
+
tcPr['@_marR'] !== undefined ? parseInt(tcPr['@_marR'], 10) : 91440
|
|
422
|
+
const currentMarT =
|
|
423
|
+
tcPr['@_marT'] !== undefined ? parseInt(tcPr['@_marT'], 10) : 45720
|
|
424
|
+
const currentMarB =
|
|
425
|
+
tcPr['@_marB'] !== undefined ? parseInt(tcPr['@_marB'], 10) : 45720
|
|
426
|
+
|
|
427
|
+
tcObj['a:tcPr'] = tcObj['a:tcPr'] || {}
|
|
428
|
+
|
|
429
|
+
const isLeft = position.includes('left') || position === 'left'
|
|
430
|
+
const isRight = position.includes('right') || position === 'right'
|
|
431
|
+
const isTop = position === 'top' || position.startsWith('top-')
|
|
432
|
+
const isBottom = position === 'bottom' || position.startsWith('bottom-')
|
|
433
|
+
|
|
434
|
+
if (isLeft) {
|
|
435
|
+
tcObj['a:tcPr']['@_marL'] = String(
|
|
436
|
+
currentMarL + Math.round(shapeWidth * 9525) + 57150
|
|
437
|
+
)
|
|
438
|
+
} else if (isRight) {
|
|
439
|
+
tcObj['a:tcPr']['@_marR'] = String(
|
|
440
|
+
currentMarR + Math.round(shapeWidth * 9525) + 57150
|
|
441
|
+
)
|
|
442
|
+
} else if (isTop) {
|
|
443
|
+
tcObj['a:tcPr']['@_marT'] = String(
|
|
444
|
+
currentMarT + Math.round(shapeHeight * 9525) + 57150
|
|
445
|
+
)
|
|
446
|
+
} else if (isBottom) {
|
|
447
|
+
tcObj['a:tcPr']['@_marB'] = String(
|
|
448
|
+
currentMarB + Math.round(shapeHeight * 9525) + 57150
|
|
449
|
+
)
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
} else if (cellDef.value && typeof cellDef.value === 'object') {
|
|
316
453
|
text = cellDef.value.value !== undefined ? cellDef.value.value : ''
|
|
317
454
|
cellOpts = cellDef.value
|
|
318
455
|
}
|
|
456
|
+
|
|
319
457
|
this.#setCellTextObj(tcObj, text)
|
|
320
458
|
if (cellDef.rowSpan && cellDef.rowSpan > 1 && strategy !== 'none') {
|
|
321
459
|
tcObj['@_rowSpan'] = String(cellDef.rowSpan)
|
|
@@ -327,6 +465,25 @@ class TableManager {
|
|
|
327
465
|
}
|
|
328
466
|
|
|
329
467
|
slideManager.markSlideObjDirty(slideIndex)
|
|
468
|
+
|
|
469
|
+
if (shapeCellsToCreate.length > 0 && shapeManager) {
|
|
470
|
+
for (const item of shapeCellsToCreate) {
|
|
471
|
+
const resolvedConfig = { ...item.config }
|
|
472
|
+
if (!resolvedConfig.position) {
|
|
473
|
+
resolvedConfig.position = resolvedConfig.text ? 'left' : 'center'
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
this.addCellShape(
|
|
477
|
+
slideIndex,
|
|
478
|
+
tableId,
|
|
479
|
+
item.rowIndex,
|
|
480
|
+
item.colIndex,
|
|
481
|
+
resolvedConfig,
|
|
482
|
+
slideManager,
|
|
483
|
+
shapeManager
|
|
484
|
+
)
|
|
485
|
+
}
|
|
486
|
+
}
|
|
330
487
|
}
|
|
331
488
|
|
|
332
489
|
/**
|
|
@@ -337,8 +494,8 @@ class TableManager {
|
|
|
337
494
|
* @param {number} rowIndex - 0-based row index.
|
|
338
495
|
* @param {SlideManager} slideManager
|
|
339
496
|
*/
|
|
340
|
-
removeTableRow(slideIndex, tableId, rowIndex, slideManager) {
|
|
341
|
-
const { tblObj } = this.#getTableContext(slideIndex, tableId, slideManager)
|
|
497
|
+
removeTableRow(slideIndex, tableId, rowIndex, slideManager, shapeManager = null) {
|
|
498
|
+
const { tblObj, resolvedTableId } = this.#getTableContext(slideIndex, tableId, slideManager)
|
|
342
499
|
|
|
343
500
|
const trs = tblObj['a:tr'] || []
|
|
344
501
|
if (rowIndex < 0 || rowIndex >= trs.length) {
|
|
@@ -348,6 +505,18 @@ class TableManager {
|
|
|
348
505
|
trs.splice(rowIndex, 1)
|
|
349
506
|
|
|
350
507
|
slideManager.markSlideObjDirty(slideIndex)
|
|
508
|
+
|
|
509
|
+
if (shapeManager) {
|
|
510
|
+
this.#adjustCellShapesAfterRowShift(
|
|
511
|
+
slideIndex,
|
|
512
|
+
resolvedTableId,
|
|
513
|
+
tableId,
|
|
514
|
+
rowIndex,
|
|
515
|
+
-1,
|
|
516
|
+
slideManager,
|
|
517
|
+
shapeManager
|
|
518
|
+
)
|
|
519
|
+
}
|
|
351
520
|
}
|
|
352
521
|
|
|
353
522
|
/**
|
|
@@ -359,8 +528,8 @@ class TableManager {
|
|
|
359
528
|
* @param {string[]} rowData
|
|
360
529
|
* @param {SlideManager} slideManager
|
|
361
530
|
*/
|
|
362
|
-
insertTableRow(slideIndex, tableId, rowIndex, rowData, slideManager) {
|
|
363
|
-
const { tblObj } = this.#getTableContext(slideIndex, tableId, slideManager)
|
|
531
|
+
insertTableRow(slideIndex, tableId, rowIndex, rowData, slideManager, shapeManager = null) {
|
|
532
|
+
const { tblObj, resolvedTableId } = this.#getTableContext(slideIndex, tableId, slideManager)
|
|
364
533
|
|
|
365
534
|
const trs = tblObj['a:tr'] || []
|
|
366
535
|
if (rowIndex < 0 || rowIndex > trs.length) {
|
|
@@ -387,6 +556,18 @@ class TableManager {
|
|
|
387
556
|
trs.splice(rowIndex, 0, newRow)
|
|
388
557
|
|
|
389
558
|
slideManager.markSlideObjDirty(slideIndex)
|
|
559
|
+
|
|
560
|
+
if (shapeManager) {
|
|
561
|
+
this.#adjustCellShapesAfterRowShift(
|
|
562
|
+
slideIndex,
|
|
563
|
+
resolvedTableId,
|
|
564
|
+
tableId,
|
|
565
|
+
rowIndex,
|
|
566
|
+
+1,
|
|
567
|
+
slideManager,
|
|
568
|
+
shapeManager
|
|
569
|
+
)
|
|
570
|
+
}
|
|
390
571
|
}
|
|
391
572
|
|
|
392
573
|
/**
|
|
@@ -398,8 +579,15 @@ class TableManager {
|
|
|
398
579
|
* @param {number} targetRowIndex
|
|
399
580
|
* @param {SlideManager} slideManager
|
|
400
581
|
*/
|
|
401
|
-
cloneTableRow(
|
|
402
|
-
|
|
582
|
+
cloneTableRow(
|
|
583
|
+
slideIndex,
|
|
584
|
+
tableId,
|
|
585
|
+
sourceRowIndex,
|
|
586
|
+
targetRowIndex,
|
|
587
|
+
slideManager,
|
|
588
|
+
shapeManager = null
|
|
589
|
+
) {
|
|
590
|
+
const { tblObj, resolvedTableId } = this.#getTableContext(slideIndex, tableId, slideManager)
|
|
403
591
|
|
|
404
592
|
const trs = tblObj['a:tr'] || []
|
|
405
593
|
if (sourceRowIndex < 0 || sourceRowIndex >= trs.length) {
|
|
@@ -416,6 +604,18 @@ class TableManager {
|
|
|
416
604
|
trs.splice(targetRowIndex, 0, newRow)
|
|
417
605
|
|
|
418
606
|
slideManager.markSlideObjDirty(slideIndex)
|
|
607
|
+
|
|
608
|
+
if (shapeManager) {
|
|
609
|
+
this.#adjustCellShapesAfterRowShift(
|
|
610
|
+
slideIndex,
|
|
611
|
+
resolvedTableId,
|
|
612
|
+
tableId,
|
|
613
|
+
targetRowIndex,
|
|
614
|
+
+1,
|
|
615
|
+
slideManager,
|
|
616
|
+
shapeManager
|
|
617
|
+
)
|
|
618
|
+
}
|
|
419
619
|
}
|
|
420
620
|
|
|
421
621
|
/**
|
|
@@ -563,7 +763,16 @@ class TableManager {
|
|
|
563
763
|
* @param {number} endCol
|
|
564
764
|
* @param {SlideManager} slideManager
|
|
565
765
|
*/
|
|
566
|
-
mergeCells(
|
|
766
|
+
mergeCells(
|
|
767
|
+
slideIndex,
|
|
768
|
+
tableId,
|
|
769
|
+
startRow,
|
|
770
|
+
startCol,
|
|
771
|
+
endRow,
|
|
772
|
+
endCol,
|
|
773
|
+
slideManager,
|
|
774
|
+
shapeManager = null
|
|
775
|
+
) {
|
|
567
776
|
const validation = this.validateMergeRegion(
|
|
568
777
|
slideIndex,
|
|
569
778
|
tableId,
|
|
@@ -577,7 +786,7 @@ class TableManager {
|
|
|
577
786
|
throw new PPTXError(`Invalid merge region: ${validation.errors.join('; ')}`)
|
|
578
787
|
}
|
|
579
788
|
|
|
580
|
-
const { tblObj } = this.#getTableContext(slideIndex, tableId, slideManager)
|
|
789
|
+
const { tblObj, resolvedTableId } = this.#getTableContext(slideIndex, tableId, slideManager)
|
|
581
790
|
const trs = tblObj['a:tr'] || []
|
|
582
791
|
|
|
583
792
|
const allTexts = []
|
|
@@ -630,6 +839,20 @@ class TableManager {
|
|
|
630
839
|
this.#setCellTextObj(originCell, combinedText)
|
|
631
840
|
|
|
632
841
|
slideManager.markSlideObjDirty(slideIndex)
|
|
842
|
+
|
|
843
|
+
if (shapeManager) {
|
|
844
|
+
this.#repositionCellShapesInRegion(
|
|
845
|
+
slideIndex,
|
|
846
|
+
tableId,
|
|
847
|
+
resolvedTableId,
|
|
848
|
+
startRow,
|
|
849
|
+
startCol,
|
|
850
|
+
endRow,
|
|
851
|
+
endCol,
|
|
852
|
+
slideManager,
|
|
853
|
+
shapeManager
|
|
854
|
+
)
|
|
855
|
+
}
|
|
633
856
|
}
|
|
634
857
|
|
|
635
858
|
/**
|
|
@@ -643,7 +866,16 @@ class TableManager {
|
|
|
643
866
|
* @param {number} endCol
|
|
644
867
|
* @param {SlideManager} slideManager
|
|
645
868
|
*/
|
|
646
|
-
unmergeCells(
|
|
869
|
+
unmergeCells(
|
|
870
|
+
slideIndex,
|
|
871
|
+
tableId,
|
|
872
|
+
startRow,
|
|
873
|
+
startCol,
|
|
874
|
+
endRow,
|
|
875
|
+
endCol,
|
|
876
|
+
slideManager,
|
|
877
|
+
shapeManager = null
|
|
878
|
+
) {
|
|
647
879
|
let actualSlideManager = slideManager
|
|
648
880
|
let actualEndRow = endRow
|
|
649
881
|
let actualEndCol = endCol
|
|
@@ -679,6 +911,21 @@ class TableManager {
|
|
|
679
911
|
if (cell['@_rowSpan'] !== undefined) delete cell['@_rowSpan']
|
|
680
912
|
}
|
|
681
913
|
}
|
|
914
|
+
|
|
915
|
+
if (shapeManager) {
|
|
916
|
+
const { resolvedTableId } = this.#getTableContext(slideIndex, tableId, actualSlideManager)
|
|
917
|
+
this.#repositionCellShapesInRegion(
|
|
918
|
+
slideIndex,
|
|
919
|
+
tableId,
|
|
920
|
+
resolvedTableId,
|
|
921
|
+
R.startRow,
|
|
922
|
+
R.startCol,
|
|
923
|
+
R.endRow,
|
|
924
|
+
R.endCol,
|
|
925
|
+
actualSlideManager,
|
|
926
|
+
shapeManager
|
|
927
|
+
)
|
|
928
|
+
}
|
|
682
929
|
} else {
|
|
683
930
|
for (let r = startRow; r <= actualEndRow; r++) {
|
|
684
931
|
const rowObj = trs[r]
|
|
@@ -693,6 +940,21 @@ class TableManager {
|
|
|
693
940
|
if (cell['@_rowSpan'] !== undefined) delete cell['@_rowSpan']
|
|
694
941
|
}
|
|
695
942
|
}
|
|
943
|
+
|
|
944
|
+
if (shapeManager) {
|
|
945
|
+
const { resolvedTableId } = this.#getTableContext(slideIndex, tableId, actualSlideManager)
|
|
946
|
+
this.#repositionCellShapesInRegion(
|
|
947
|
+
slideIndex,
|
|
948
|
+
tableId,
|
|
949
|
+
resolvedTableId,
|
|
950
|
+
startRow,
|
|
951
|
+
startCol,
|
|
952
|
+
actualEndRow,
|
|
953
|
+
actualEndCol,
|
|
954
|
+
actualSlideManager,
|
|
955
|
+
shapeManager
|
|
956
|
+
)
|
|
957
|
+
}
|
|
696
958
|
}
|
|
697
959
|
|
|
698
960
|
actualSlideManager.markSlideObjDirty(slideIndex)
|
|
@@ -714,25 +976,89 @@ class TableManager {
|
|
|
714
976
|
}
|
|
715
977
|
|
|
716
978
|
const trs = tblObj['a:tr'] || []
|
|
979
|
+
const numRows = trs.length
|
|
980
|
+
if (numRows === 0) return []
|
|
981
|
+
const numCols = trs[0]['a:tc']?.length || 0
|
|
982
|
+
|
|
717
983
|
const merged = []
|
|
984
|
+
const visited = Array.from({ length: numRows }, () => Array(numCols).fill(false))
|
|
718
985
|
|
|
719
|
-
for (let r = 0; r <
|
|
986
|
+
for (let r = 0; r < numRows; r++) {
|
|
720
987
|
const row = trs[r]
|
|
721
988
|
const tcs = row['a:tc'] || []
|
|
722
989
|
for (let c = 0; c < tcs.length; c++) {
|
|
990
|
+
if (visited[r][c]) continue
|
|
723
991
|
const cell = tcs[c]
|
|
724
992
|
if (!cell) continue
|
|
725
993
|
|
|
726
|
-
const
|
|
727
|
-
|
|
994
|
+
const isVMerged =
|
|
995
|
+
cell['@_vMerge'] === '1' || cell['@_vMerge'] === 'true' || cell['@_vMerge'] === true
|
|
996
|
+
const isHMerged =
|
|
997
|
+
cell['@_hMerge'] === '1' || cell['@_hMerge'] === 'true' || cell['@_hMerge'] === true
|
|
998
|
+
|
|
999
|
+
if (isVMerged || isHMerged) {
|
|
1000
|
+
continue
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// Determine colSpan
|
|
1004
|
+
let colSpan = 1
|
|
1005
|
+
if (cell['@_gridSpan'] !== undefined) {
|
|
1006
|
+
colSpan = parseInt(cell['@_gridSpan'], 10)
|
|
1007
|
+
} else {
|
|
1008
|
+
let nextCol = c + 1
|
|
1009
|
+
while (nextCol < numCols) {
|
|
1010
|
+
const nextCell = tcs[nextCol]
|
|
1011
|
+
if (
|
|
1012
|
+
nextCell &&
|
|
1013
|
+
(nextCell['@_hMerge'] === '1' ||
|
|
1014
|
+
nextCell['@_hMerge'] === 'true' ||
|
|
1015
|
+
nextCell['@_hMerge'] === true)
|
|
1016
|
+
) {
|
|
1017
|
+
colSpan++
|
|
1018
|
+
nextCol++
|
|
1019
|
+
} else {
|
|
1020
|
+
break
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
728
1024
|
|
|
729
|
-
|
|
1025
|
+
// Determine rowSpan
|
|
1026
|
+
let rowSpan = 1
|
|
1027
|
+
if (cell['@_rowSpan'] !== undefined) {
|
|
1028
|
+
rowSpan = parseInt(cell['@_rowSpan'], 10)
|
|
1029
|
+
} else {
|
|
1030
|
+
let nextRow = r + 1
|
|
1031
|
+
while (nextRow < numRows) {
|
|
1032
|
+
const nextCell = trs[nextRow]['a:tc']?.[c]
|
|
1033
|
+
if (
|
|
1034
|
+
nextCell &&
|
|
1035
|
+
(nextCell['@_vMerge'] === '1' ||
|
|
1036
|
+
nextCell['@_vMerge'] === 'true' ||
|
|
1037
|
+
nextCell['@_vMerge'] === true)
|
|
1038
|
+
) {
|
|
1039
|
+
rowSpan++
|
|
1040
|
+
nextRow++
|
|
1041
|
+
} else {
|
|
1042
|
+
break
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
if (colSpan > 1 || rowSpan > 1) {
|
|
730
1048
|
merged.push({
|
|
731
1049
|
startRow: r,
|
|
732
1050
|
startCol: c,
|
|
733
1051
|
endRow: r + rowSpan - 1,
|
|
734
|
-
endCol: c +
|
|
1052
|
+
endCol: c + colSpan - 1,
|
|
735
1053
|
})
|
|
1054
|
+
|
|
1055
|
+
for (let i = r; i < r + rowSpan; i++) {
|
|
1056
|
+
for (let j = c; j < c + colSpan; j++) {
|
|
1057
|
+
if (i < numRows && j < numCols) {
|
|
1058
|
+
visited[i][j] = true
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
736
1062
|
}
|
|
737
1063
|
}
|
|
738
1064
|
}
|
|
@@ -804,12 +1130,20 @@ class TableManager {
|
|
|
804
1130
|
const gridColsArr = Array.isArray(gridCols) ? gridCols : [gridCols]
|
|
805
1131
|
const colWidths = gridColsArr.map(col => parseInt(col['@_w'] || 0, 10))
|
|
806
1132
|
|
|
807
|
-
const trsArr = tblObj['a:tr'] || []
|
|
808
1133
|
const rowHeights = this.#calculateRowHeights(slideIndex, tableId, slideManager, tblObj, false)
|
|
809
1134
|
|
|
810
|
-
const
|
|
811
|
-
|
|
812
|
-
|
|
1135
|
+
const R = this.getMergeRegion(slideIndex, tableId, rowIndex, colIndex, slideManager)
|
|
1136
|
+
let pr = rowIndex
|
|
1137
|
+
let pc = colIndex
|
|
1138
|
+
let gridSpan = 1
|
|
1139
|
+
let rowSpan = 1
|
|
1140
|
+
|
|
1141
|
+
if (R) {
|
|
1142
|
+
pr = R.startRow
|
|
1143
|
+
pc = R.startCol
|
|
1144
|
+
gridSpan = R.endCol - R.startCol + 1
|
|
1145
|
+
rowSpan = R.endRow - R.startRow + 1
|
|
1146
|
+
}
|
|
813
1147
|
|
|
814
1148
|
let cellLeft = tableX
|
|
815
1149
|
for (let idx = 0; idx < pc; idx++) {
|
|
@@ -821,10 +1155,6 @@ class TableManager {
|
|
|
821
1155
|
cellTop += rowHeights[idx] || 0
|
|
822
1156
|
}
|
|
823
1157
|
|
|
824
|
-
const parentCell = trsArr[pr]?.['a:tc']?.[pc]
|
|
825
|
-
const gridSpan = parentCell?.['@_gridSpan'] ? parseInt(parentCell['@_gridSpan'], 10) : 1
|
|
826
|
-
const rowSpan = parentCell?.['@_rowSpan'] ? parseInt(parentCell['@_rowSpan'], 10) : 1
|
|
827
|
-
|
|
828
1158
|
let cellWidth = 0
|
|
829
1159
|
for (let idx = 0; idx < gridSpan; idx++) {
|
|
830
1160
|
cellWidth += colWidths[pc + idx] || 0
|
|
@@ -1389,135 +1719,119 @@ class TableManager {
|
|
|
1389
1719
|
}
|
|
1390
1720
|
|
|
1391
1721
|
// 2. Determine alignment settings
|
|
1392
|
-
let alignX =
|
|
1393
|
-
let alignY =
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1722
|
+
let alignX = undefined
|
|
1723
|
+
let alignY = undefined
|
|
1724
|
+
|
|
1725
|
+
const pos = String(config.position || '')
|
|
1726
|
+
.toLowerCase()
|
|
1727
|
+
.trim()
|
|
1728
|
+
if (pos) {
|
|
1729
|
+
if (pos === 'left') {
|
|
1730
|
+
alignX = 'left'
|
|
1731
|
+
alignY = 'middle'
|
|
1732
|
+
} else if (pos === 'right') {
|
|
1733
|
+
alignX = 'right'
|
|
1734
|
+
alignY = 'middle'
|
|
1735
|
+
} else if (pos === 'center' || pos === 'middle') {
|
|
1736
|
+
alignX = 'center'
|
|
1737
|
+
alignY = 'middle'
|
|
1738
|
+
} else if (pos === 'top') {
|
|
1739
|
+
alignX = 'center'
|
|
1740
|
+
alignY = 'top'
|
|
1741
|
+
} else if (pos === 'bottom') {
|
|
1742
|
+
alignX = 'center'
|
|
1743
|
+
alignY = 'bottom'
|
|
1744
|
+
} else if (pos === 'top-left') {
|
|
1745
|
+
alignX = 'left'
|
|
1746
|
+
alignY = 'top'
|
|
1747
|
+
} else if (pos === 'top-right') {
|
|
1748
|
+
alignX = 'right'
|
|
1749
|
+
alignY = 'top'
|
|
1750
|
+
} else if (pos === 'bottom-left') {
|
|
1751
|
+
alignX = 'left'
|
|
1752
|
+
alignY = 'bottom'
|
|
1753
|
+
} else if (pos === 'bottom-right') {
|
|
1754
|
+
alignX = 'right'
|
|
1755
|
+
alignY = 'bottom'
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
if (!alignX) {
|
|
1760
|
+
const ax = config.alignX || config.horizontal
|
|
1761
|
+
if (ax) {
|
|
1762
|
+
alignX = String(ax).toLowerCase().trim()
|
|
1763
|
+
if (alignX === 'middle') alignX = 'center'
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
if (!alignY) {
|
|
1767
|
+
const ay = config.alignY || config.vertical
|
|
1768
|
+
if (ay) {
|
|
1769
|
+
alignY = String(ay).toLowerCase().trim()
|
|
1770
|
+
if (alignY === 'center') alignY = 'middle'
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
if (!alignX && !alignY) {
|
|
1775
|
+
if (config.x !== undefined || config.y !== undefined) {
|
|
1776
|
+
alignX = 'left'
|
|
1777
|
+
alignY = 'top'
|
|
1778
|
+
} else {
|
|
1779
|
+
alignX = 'center'
|
|
1780
|
+
alignY = 'middle'
|
|
1447
1781
|
}
|
|
1448
|
-
}
|
|
1449
|
-
|
|
1450
|
-
if (alignX && !alignY && config.y === undefined) {
|
|
1782
|
+
} else if (alignX && !alignY) {
|
|
1451
1783
|
alignY = 'middle'
|
|
1452
|
-
}
|
|
1453
|
-
if (alignY && !alignX && config.x === undefined) {
|
|
1784
|
+
} else if (alignY && !alignX) {
|
|
1454
1785
|
alignX = 'center'
|
|
1455
1786
|
}
|
|
1456
1787
|
|
|
1457
|
-
if (!alignX && !alignY && config.x === undefined && config.y === undefined) {
|
|
1458
|
-
alignX = 'center'
|
|
1459
|
-
alignY = 'middle'
|
|
1460
|
-
}
|
|
1461
|
-
|
|
1462
1788
|
// 3. Compute coordinates
|
|
1463
1789
|
let shapeLeft = cellLeft_px
|
|
1464
1790
|
let shapeTop = cellTop_px
|
|
1465
1791
|
|
|
1466
1792
|
if (isCellAnchored) {
|
|
1793
|
+
let dx = 0
|
|
1794
|
+
const hasOffsetValX =
|
|
1795
|
+
config.offsetX !== undefined || config.xOffset !== undefined || config.x !== undefined
|
|
1796
|
+
if (config.offsetX !== undefined) dx = parseFloat(config.offsetX)
|
|
1797
|
+
else if (config.xOffset !== undefined) dx = parseFloat(config.xOffset)
|
|
1798
|
+
else if (config.x !== undefined) dx = parseFloat(config.x)
|
|
1799
|
+
|
|
1800
|
+
let dy = 0
|
|
1801
|
+
const hasOffsetValY =
|
|
1802
|
+
config.offsetY !== undefined || config.yOffset !== undefined || config.y !== undefined
|
|
1803
|
+
if (config.offsetY !== undefined) dy = parseFloat(config.offsetY)
|
|
1804
|
+
else if (config.yOffset !== undefined) dy = parseFloat(config.yOffset)
|
|
1805
|
+
else if (config.y !== undefined) dy = parseFloat(config.y)
|
|
1806
|
+
|
|
1807
|
+
shapeLeft = cellLeft_px
|
|
1467
1808
|
if (alignX === 'left') {
|
|
1468
|
-
|
|
1809
|
+
const padding = hasOffsetValX ? dx : 5
|
|
1810
|
+
shapeLeft = cellLeft_px + padding
|
|
1469
1811
|
} else if (alignX === 'center') {
|
|
1470
|
-
shapeLeft = cellLeft_px + (cellWidth_px - shapeWidth) / 2 +
|
|
1812
|
+
shapeLeft = cellLeft_px + (cellWidth_px - shapeWidth) / 2 + dx
|
|
1471
1813
|
} else if (alignX === 'right') {
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
} else {
|
|
1475
|
-
shapeLeft =
|
|
1476
|
-
cellLeft_px + (config.x !== undefined ? config.x : (cellWidth_px - shapeWidth) / 2)
|
|
1814
|
+
const padding = hasOffsetValX ? dx : 5
|
|
1815
|
+
shapeLeft = cellLeft_px + cellWidth_px - shapeWidth - padding
|
|
1477
1816
|
}
|
|
1478
1817
|
|
|
1818
|
+
shapeTop = cellTop_px
|
|
1479
1819
|
if (alignY === 'top') {
|
|
1480
|
-
|
|
1820
|
+
const padding = hasOffsetValY ? dy : 5
|
|
1821
|
+
shapeTop = cellTop_px + padding
|
|
1481
1822
|
} else if (alignY === 'middle') {
|
|
1482
|
-
shapeTop = cellTop_px + (cellHeight_px - shapeHeight) / 2 +
|
|
1823
|
+
shapeTop = cellTop_px + (cellHeight_px - shapeHeight) / 2 + dy
|
|
1483
1824
|
} else if (alignY === 'bottom') {
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
} else {
|
|
1487
|
-
shapeTop =
|
|
1488
|
-
cellTop_px + (config.y !== undefined ? config.y : (cellHeight_px - shapeHeight) / 2)
|
|
1825
|
+
const padding = hasOffsetValY ? dy : 5
|
|
1826
|
+
shapeTop = cellTop_px + cellHeight_px - shapeHeight - padding
|
|
1489
1827
|
}
|
|
1490
1828
|
|
|
1491
1829
|
// 4. Boundary Constraints Validation/Enforcement
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
} else {
|
|
1498
|
-
shapeLeft = cellLeft_px
|
|
1499
|
-
}
|
|
1500
|
-
} else {
|
|
1501
|
-
shapeLeft = Math.max(
|
|
1502
|
-
cellLeft_px,
|
|
1503
|
-
Math.min(shapeLeft, cellLeft_px + cellWidth_px - shapeWidth)
|
|
1504
|
-
)
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
if (shapeHeight > cellHeight_px) {
|
|
1508
|
-
if (alignY === 'middle') {
|
|
1509
|
-
shapeTop = cellTop_px + (cellHeight_px - shapeHeight) / 2
|
|
1510
|
-
} else if (alignY === 'bottom') {
|
|
1511
|
-
shapeTop = cellTop_px + cellHeight_px - shapeHeight
|
|
1512
|
-
} else {
|
|
1513
|
-
shapeTop = cellTop_px
|
|
1514
|
-
}
|
|
1515
|
-
} else {
|
|
1516
|
-
shapeTop = Math.max(
|
|
1517
|
-
cellTop_px,
|
|
1518
|
-
Math.min(shapeTop, cellTop_px + cellHeight_px - shapeHeight)
|
|
1519
|
-
)
|
|
1520
|
-
}
|
|
1830
|
+
shapeLeft = Math.max(
|
|
1831
|
+
cellLeft_px,
|
|
1832
|
+
Math.min(shapeLeft, cellLeft_px + cellWidth_px - shapeWidth)
|
|
1833
|
+
)
|
|
1834
|
+
shapeTop = Math.max(cellTop_px, Math.min(shapeTop, cellTop_px + cellHeight_px - shapeHeight))
|
|
1521
1835
|
} else {
|
|
1522
1836
|
shapeLeft = config.x || 0
|
|
1523
1837
|
shapeTop = config.y || 0
|
|
@@ -1920,6 +2234,18 @@ class TableManager {
|
|
|
1920
2234
|
expandedConfig.id = `cellshape_${resolvedTableId}_${rowIndex}_${colIndex}_${finalShapeIndex}`
|
|
1921
2235
|
|
|
1922
2236
|
shapeManager.addShape(slideIndex, expandedConfig, slideManager)
|
|
2237
|
+
|
|
2238
|
+
// Register this shape's original config so it can be repositioned after
|
|
2239
|
+
// any subsequent table mutations (row removal, insertion, merge, etc.)
|
|
2240
|
+
this.#cellShapeAnchors.set(expandedConfig.id, {
|
|
2241
|
+
slideIndex,
|
|
2242
|
+
resolvedTableId,
|
|
2243
|
+
tableId,
|
|
2244
|
+
rowIndex,
|
|
2245
|
+
colIndex,
|
|
2246
|
+
shapeIndex: finalShapeIndex,
|
|
2247
|
+
config: { ...options },
|
|
2248
|
+
})
|
|
1923
2249
|
})
|
|
1924
2250
|
}
|
|
1925
2251
|
|
|
@@ -1979,6 +2305,8 @@ class TableManager {
|
|
|
1979
2305
|
|
|
1980
2306
|
for (const s of matchingShapes) {
|
|
1981
2307
|
shapeManager.deleteShape(slideIndex, s.name, slideManager)
|
|
2308
|
+
// Deregister from anchor registry
|
|
2309
|
+
this.#cellShapeAnchors.delete(s.name)
|
|
1982
2310
|
}
|
|
1983
2311
|
}
|
|
1984
2312
|
|
|
@@ -1996,6 +2324,188 @@ class TableManager {
|
|
|
1996
2324
|
return shapeManager.getShape(slideIndex, primaryShape.name, slideManager)
|
|
1997
2325
|
}
|
|
1998
2326
|
|
|
2327
|
+
/**
|
|
2328
|
+
* Adjusts all registered cell shapes for a table after a row is removed (delta=-1)
|
|
2329
|
+
* or inserted (delta=+1) at `pivotRowIndex`.
|
|
2330
|
+
*
|
|
2331
|
+
* - For delta=-1 and shapes at pivotRowIndex: the shape is deleted (its row is gone).
|
|
2332
|
+
* - For shapes at rows that shifted: delete the old shape and re-add it at the new
|
|
2333
|
+
* row index so that `getCellBounds` can compute correct coordinates from the
|
|
2334
|
+
* updated table layout.
|
|
2335
|
+
*
|
|
2336
|
+
* @private
|
|
2337
|
+
*/
|
|
2338
|
+
#adjustCellShapesAfterRowShift(
|
|
2339
|
+
slideIndex,
|
|
2340
|
+
resolvedTableId,
|
|
2341
|
+
tableId,
|
|
2342
|
+
pivotRowIndex,
|
|
2343
|
+
delta,
|
|
2344
|
+
slideManager,
|
|
2345
|
+
shapeManager
|
|
2346
|
+
) {
|
|
2347
|
+
if (!shapeManager) return
|
|
2348
|
+
|
|
2349
|
+
// Collect entries first (avoid mutating map while iterating)
|
|
2350
|
+
const toDelete = []
|
|
2351
|
+
const toReindex = []
|
|
2352
|
+
|
|
2353
|
+
for (const [name, anchor] of this.#cellShapeAnchors) {
|
|
2354
|
+
if (anchor.slideIndex !== slideIndex || anchor.resolvedTableId !== resolvedTableId) continue
|
|
2355
|
+
|
|
2356
|
+
if (delta < 0 && anchor.rowIndex === pivotRowIndex) {
|
|
2357
|
+
// Row was removed — delete the shape
|
|
2358
|
+
toDelete.push(name)
|
|
2359
|
+
} else if (delta < 0 && anchor.rowIndex > pivotRowIndex) {
|
|
2360
|
+
// Row shifted up (removal below pivot)
|
|
2361
|
+
toReindex.push({ name, anchor, newRowIndex: anchor.rowIndex + delta })
|
|
2362
|
+
} else if (delta > 0 && anchor.rowIndex >= pivotRowIndex) {
|
|
2363
|
+
// Row shifted down (insertion at or above this row)
|
|
2364
|
+
toReindex.push({ name, anchor, newRowIndex: anchor.rowIndex + delta })
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
// Delete shapes for the removed row
|
|
2369
|
+
for (const name of toDelete) {
|
|
2370
|
+
try {
|
|
2371
|
+
shapeManager.deleteShape(slideIndex, name, slideManager)
|
|
2372
|
+
} catch (e) {
|
|
2373
|
+
logger.warn(`Failed to delete cell shape "${name}": ${e.message}`)
|
|
2374
|
+
}
|
|
2375
|
+
this.#cellShapeAnchors.delete(name)
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
// Sort toReindex: first by newRowIndex, then by colIndex, then by shapeIndex (base index)
|
|
2379
|
+
toReindex.sort((a, b) => {
|
|
2380
|
+
if (a.newRowIndex !== b.newRowIndex) {
|
|
2381
|
+
return a.newRowIndex - b.newRowIndex
|
|
2382
|
+
}
|
|
2383
|
+
if (a.anchor.colIndex !== b.anchor.colIndex) {
|
|
2384
|
+
return a.anchor.colIndex - b.anchor.colIndex
|
|
2385
|
+
}
|
|
2386
|
+
const aBase =
|
|
2387
|
+
typeof a.anchor.shapeIndex === 'string'
|
|
2388
|
+
? parseInt(a.anchor.shapeIndex.split('_')[0], 10)
|
|
2389
|
+
: a.anchor.shapeIndex
|
|
2390
|
+
const bBase =
|
|
2391
|
+
typeof b.anchor.shapeIndex === 'string'
|
|
2392
|
+
? parseInt(b.anchor.shapeIndex.split('_')[0], 10)
|
|
2393
|
+
: b.anchor.shapeIndex
|
|
2394
|
+
return aBase - bBase
|
|
2395
|
+
})
|
|
2396
|
+
|
|
2397
|
+
// Phase 1: delete all old shapes to prevent collisions when re-adding
|
|
2398
|
+
for (const { name } of toReindex) {
|
|
2399
|
+
try {
|
|
2400
|
+
shapeManager.deleteShape(slideIndex, name, slideManager)
|
|
2401
|
+
} catch (e) {
|
|
2402
|
+
logger.warn(`Failed to delete cell shape "${name}" during reindex: ${e.message}`)
|
|
2403
|
+
}
|
|
2404
|
+
this.#cellShapeAnchors.delete(name)
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
// Phase 2: re-add all shapes at their newRowIndex
|
|
2408
|
+
for (const { anchor, newRowIndex } of toReindex) {
|
|
2409
|
+
try {
|
|
2410
|
+
this.addCellShape(
|
|
2411
|
+
slideIndex,
|
|
2412
|
+
anchor.tableId,
|
|
2413
|
+
newRowIndex,
|
|
2414
|
+
anchor.colIndex,
|
|
2415
|
+
anchor.config,
|
|
2416
|
+
slideManager,
|
|
2417
|
+
shapeManager
|
|
2418
|
+
)
|
|
2419
|
+
} catch (e) {
|
|
2420
|
+
logger.warn(
|
|
2421
|
+
`Failed to re-add cell shape for (${newRowIndex}, ${anchor.colIndex}): ${e.message}`
|
|
2422
|
+
)
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
/**
|
|
2428
|
+
* Repositions all registered cell shapes that fall within a table region
|
|
2429
|
+
* (e.g. after a merge or unmerge). Shapes in the region are deleted and
|
|
2430
|
+
* re-added targeting `(startRow, startCol)` so their coordinates are
|
|
2431
|
+
* recomputed against the merged cell's full bounding box.
|
|
2432
|
+
*
|
|
2433
|
+
* @private
|
|
2434
|
+
*/
|
|
2435
|
+
#repositionCellShapesInRegion(
|
|
2436
|
+
slideIndex,
|
|
2437
|
+
tableId,
|
|
2438
|
+
resolvedTableId,
|
|
2439
|
+
startRow,
|
|
2440
|
+
startCol,
|
|
2441
|
+
endRow,
|
|
2442
|
+
endCol,
|
|
2443
|
+
slideManager,
|
|
2444
|
+
shapeManager
|
|
2445
|
+
) {
|
|
2446
|
+
if (!shapeManager) return
|
|
2447
|
+
|
|
2448
|
+
const toReposition = []
|
|
2449
|
+
|
|
2450
|
+
for (const [name, anchor] of this.#cellShapeAnchors) {
|
|
2451
|
+
if (anchor.slideIndex !== slideIndex || anchor.resolvedTableId !== resolvedTableId) continue
|
|
2452
|
+
if (
|
|
2453
|
+
anchor.rowIndex >= startRow &&
|
|
2454
|
+
anchor.rowIndex <= endRow &&
|
|
2455
|
+
anchor.colIndex >= startCol &&
|
|
2456
|
+
anchor.colIndex <= endCol
|
|
2457
|
+
) {
|
|
2458
|
+
toReposition.push({ name, anchor })
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
// Sort toReposition: first by original rowIndex, then by colIndex, then by shapeIndex (base index)
|
|
2463
|
+
toReposition.sort((a, b) => {
|
|
2464
|
+
if (a.anchor.rowIndex !== b.anchor.rowIndex) {
|
|
2465
|
+
return a.anchor.rowIndex - b.anchor.rowIndex
|
|
2466
|
+
}
|
|
2467
|
+
if (a.anchor.colIndex !== b.anchor.colIndex) {
|
|
2468
|
+
return a.anchor.colIndex - b.anchor.colIndex
|
|
2469
|
+
}
|
|
2470
|
+
const aBase =
|
|
2471
|
+
typeof a.anchor.shapeIndex === 'string'
|
|
2472
|
+
? parseInt(a.anchor.shapeIndex.split('_')[0], 10)
|
|
2473
|
+
: a.anchor.shapeIndex
|
|
2474
|
+
const bBase =
|
|
2475
|
+
typeof b.anchor.shapeIndex === 'string'
|
|
2476
|
+
? parseInt(b.anchor.shapeIndex.split('_')[0], 10)
|
|
2477
|
+
: b.anchor.shapeIndex
|
|
2478
|
+
return aBase - bBase
|
|
2479
|
+
})
|
|
2480
|
+
|
|
2481
|
+
// Phase 1: delete all old shapes first
|
|
2482
|
+
for (const { name } of toReposition) {
|
|
2483
|
+
try {
|
|
2484
|
+
shapeManager.deleteShape(slideIndex, name, slideManager)
|
|
2485
|
+
} catch (e) {
|
|
2486
|
+
logger.warn(`Failed to delete cell shape "${name}" during reposition: ${e.message}`)
|
|
2487
|
+
}
|
|
2488
|
+
this.#cellShapeAnchors.delete(name)
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
// Phase 2: re-add all shapes targeting startRow, startCol
|
|
2492
|
+
for (const { anchor } of toReposition) {
|
|
2493
|
+
try {
|
|
2494
|
+
this.addCellShape(
|
|
2495
|
+
slideIndex,
|
|
2496
|
+
anchor.tableId,
|
|
2497
|
+
startRow,
|
|
2498
|
+
startCol,
|
|
2499
|
+
anchor.config,
|
|
2500
|
+
slideManager,
|
|
2501
|
+
shapeManager
|
|
2502
|
+
)
|
|
2503
|
+
} catch (e) {
|
|
2504
|
+
logger.warn(`Failed to reposition cell shape for (${startRow}, ${startCol}): ${e.message}`)
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
|
|
1999
2509
|
/**
|
|
2000
2510
|
* Generates a new rowId for the given row object.
|
|
2001
2511
|
*/
|