node-pptx-templater 1.1.3 → 1.1.6
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/CHANGELOG.md +0 -16
- package/README.md +10 -34
- package/package.json +1 -2
- package/src/core/PPTXTemplater.js +61 -11
- package/src/managers/ShapeManager.js +296 -85
- package/src/managers/TableManager.js +700 -88
|
@@ -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
|
|
728
998
|
|
|
729
|
-
if (
|
|
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
|
+
}
|
|
1024
|
+
|
|
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,17 +1719,8 @@ class TableManager {
|
|
|
1389
1719
|
}
|
|
1390
1720
|
|
|
1391
1721
|
// 2. Determine alignment settings
|
|
1392
|
-
let alignX = config.alignX
|
|
1393
|
-
let alignY = config.alignY
|
|
1394
|
-
|
|
1395
|
-
if (alignX) {
|
|
1396
|
-
alignX = String(alignX).toLowerCase()
|
|
1397
|
-
if (alignX === 'middle') alignX = 'center'
|
|
1398
|
-
}
|
|
1399
|
-
if (alignY) {
|
|
1400
|
-
alignY = String(alignY).toLowerCase()
|
|
1401
|
-
if (alignY === 'center') alignY = 'middle'
|
|
1402
|
-
}
|
|
1722
|
+
let alignX = config.alignX
|
|
1723
|
+
let alignY = config.alignY
|
|
1403
1724
|
|
|
1404
1725
|
if (config.position) {
|
|
1405
1726
|
switch (config.position) {
|
|
@@ -1447,16 +1768,33 @@ class TableManager {
|
|
|
1447
1768
|
}
|
|
1448
1769
|
}
|
|
1449
1770
|
|
|
1450
|
-
if (alignX
|
|
1451
|
-
|
|
1771
|
+
if (!alignX) {
|
|
1772
|
+
const ax = config.alignX || config.horizontal
|
|
1773
|
+
if (ax) {
|
|
1774
|
+
alignX = String(ax).toLowerCase().trim()
|
|
1775
|
+
if (alignX === 'middle') alignX = 'center'
|
|
1776
|
+
}
|
|
1452
1777
|
}
|
|
1453
|
-
if (alignY
|
|
1454
|
-
|
|
1778
|
+
if (!alignY) {
|
|
1779
|
+
const ay = config.alignY || config.vertical
|
|
1780
|
+
if (ay) {
|
|
1781
|
+
alignY = String(ay).toLowerCase().trim()
|
|
1782
|
+
if (alignY === 'center') alignY = 'middle'
|
|
1783
|
+
}
|
|
1455
1784
|
}
|
|
1456
1785
|
|
|
1457
|
-
if (!alignX && !alignY
|
|
1458
|
-
|
|
1786
|
+
if (!alignX && !alignY) {
|
|
1787
|
+
if (config.x !== undefined || config.y !== undefined) {
|
|
1788
|
+
alignX = 'left'
|
|
1789
|
+
alignY = 'top'
|
|
1790
|
+
} else {
|
|
1791
|
+
alignX = 'center'
|
|
1792
|
+
alignY = 'middle'
|
|
1793
|
+
}
|
|
1794
|
+
} else if (alignX && !alignY) {
|
|
1459
1795
|
alignY = 'middle'
|
|
1796
|
+
} else if (alignY && !alignX) {
|
|
1797
|
+
alignX = 'center'
|
|
1460
1798
|
}
|
|
1461
1799
|
|
|
1462
1800
|
// 3. Compute coordinates
|
|
@@ -1464,60 +1802,48 @@ class TableManager {
|
|
|
1464
1802
|
let shapeTop = cellTop_px
|
|
1465
1803
|
|
|
1466
1804
|
if (isCellAnchored) {
|
|
1805
|
+
let dx = 0
|
|
1806
|
+
const hasOffsetValX =
|
|
1807
|
+
config.offsetX !== undefined || config.xOffset !== undefined || config.x !== undefined
|
|
1808
|
+
if (config.offsetX !== undefined) dx = parseFloat(config.offsetX)
|
|
1809
|
+
else if (config.xOffset !== undefined) dx = parseFloat(config.xOffset)
|
|
1810
|
+
else if (config.x !== undefined) dx = parseFloat(config.x)
|
|
1811
|
+
|
|
1812
|
+
let dy = 0
|
|
1813
|
+
const hasOffsetValY =
|
|
1814
|
+
config.offsetY !== undefined || config.yOffset !== undefined || config.y !== undefined
|
|
1815
|
+
if (config.offsetY !== undefined) dy = parseFloat(config.offsetY)
|
|
1816
|
+
else if (config.yOffset !== undefined) dy = parseFloat(config.yOffset)
|
|
1817
|
+
else if (config.y !== undefined) dy = parseFloat(config.y)
|
|
1818
|
+
|
|
1819
|
+
shapeLeft = cellLeft_px
|
|
1467
1820
|
if (alignX === 'left') {
|
|
1468
|
-
|
|
1821
|
+
const padding = hasOffsetValX ? dx : 5
|
|
1822
|
+
shapeLeft = cellLeft_px + padding
|
|
1469
1823
|
} else if (alignX === 'center') {
|
|
1470
|
-
shapeLeft = cellLeft_px + (cellWidth_px - shapeWidth) / 2 +
|
|
1824
|
+
shapeLeft = cellLeft_px + (cellWidth_px - shapeWidth) / 2 + dx
|
|
1471
1825
|
} else if (alignX === 'right') {
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
} else {
|
|
1475
|
-
shapeLeft =
|
|
1476
|
-
cellLeft_px + (config.x !== undefined ? config.x : (cellWidth_px - shapeWidth) / 2)
|
|
1826
|
+
const padding = hasOffsetValX ? dx : 5
|
|
1827
|
+
shapeLeft = cellLeft_px + cellWidth_px - shapeWidth - padding
|
|
1477
1828
|
}
|
|
1478
1829
|
|
|
1830
|
+
shapeTop = cellTop_px
|
|
1479
1831
|
if (alignY === 'top') {
|
|
1480
|
-
|
|
1832
|
+
const padding = hasOffsetValY ? dy : 5
|
|
1833
|
+
shapeTop = cellTop_px + padding
|
|
1481
1834
|
} else if (alignY === 'middle') {
|
|
1482
|
-
shapeTop = cellTop_px + (cellHeight_px - shapeHeight) / 2 +
|
|
1835
|
+
shapeTop = cellTop_px + (cellHeight_px - shapeHeight) / 2 + dy
|
|
1483
1836
|
} else if (alignY === 'bottom') {
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
} else {
|
|
1487
|
-
shapeTop =
|
|
1488
|
-
cellTop_px + (config.y !== undefined ? config.y : (cellHeight_px - shapeHeight) / 2)
|
|
1837
|
+
const padding = hasOffsetValY ? dy : 5
|
|
1838
|
+
shapeTop = cellTop_px + cellHeight_px - shapeHeight - padding
|
|
1489
1839
|
}
|
|
1490
1840
|
|
|
1491
1841
|
// 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
|
-
}
|
|
1842
|
+
shapeLeft = Math.max(
|
|
1843
|
+
cellLeft_px,
|
|
1844
|
+
Math.min(shapeLeft, cellLeft_px + cellWidth_px - shapeWidth)
|
|
1845
|
+
)
|
|
1846
|
+
shapeTop = Math.max(cellTop_px, Math.min(shapeTop, cellTop_px + cellHeight_px - shapeHeight))
|
|
1521
1847
|
} else {
|
|
1522
1848
|
shapeLeft = config.x || 0
|
|
1523
1849
|
shapeTop = config.y || 0
|
|
@@ -1743,7 +2069,7 @@ class TableManager {
|
|
|
1743
2069
|
slideManager,
|
|
1744
2070
|
shapeManager,
|
|
1745
2071
|
tblObj,
|
|
1746
|
-
|
|
2072
|
+
frameObj
|
|
1747
2073
|
) {
|
|
1748
2074
|
if (!cellShapes || !shapeManager) return
|
|
1749
2075
|
|
|
@@ -1761,6 +2087,54 @@ class TableManager {
|
|
|
1761
2087
|
}
|
|
1762
2088
|
}
|
|
1763
2089
|
|
|
2090
|
+
const xfrm = frameObj['p:xfrm']
|
|
2091
|
+
const tableX = xfrm?.['a:off']?.['@_x'] ? parseInt(xfrm['a:off']['@_x'], 10) : 0
|
|
2092
|
+
const tableY = xfrm?.['a:off']?.['@_y'] ? parseInt(xfrm['a:off']['@_y'], 10) : 0
|
|
2093
|
+
|
|
2094
|
+
const gridCols = tblObj['a:tblGrid']?.['a:gridCol'] || []
|
|
2095
|
+
const gridColsArr = Array.isArray(gridCols) ? gridCols : [gridCols]
|
|
2096
|
+
const colWidths = gridColsArr.map(col => parseInt(col['@_w'] || 0, 10))
|
|
2097
|
+
|
|
2098
|
+
const trsArr = tblObj['a:tr'] || []
|
|
2099
|
+
const rowHeights = trsArr.map(row => parseInt(row['@_h'] || 0, 10))
|
|
2100
|
+
|
|
2101
|
+
const getCellBounds = (r, c) => {
|
|
2102
|
+
const parent = this.getMergeParent(slideIndex, tableId, r, c, slideManager)
|
|
2103
|
+
const pr = parent.row
|
|
2104
|
+
const pc = parent.col
|
|
2105
|
+
|
|
2106
|
+
let cellLeft = tableX
|
|
2107
|
+
for (let idx = 0; idx < pc; idx++) {
|
|
2108
|
+
cellLeft += colWidths[idx] || 0
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
let cellTop = tableY
|
|
2112
|
+
for (let idx = 0; idx < pr; idx++) {
|
|
2113
|
+
cellTop += rowHeights[idx] || 0
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
const parentCell = trsArr[pr]?.['a:tc']?.[pc]
|
|
2117
|
+
const gridSpan = parentCell?.['@_gridSpan'] ? parseInt(parentCell['@_gridSpan'], 10) : 1
|
|
2118
|
+
const rowSpan = parentCell?.['@_rowSpan'] ? parseInt(parentCell['@_rowSpan'], 10) : 1
|
|
2119
|
+
|
|
2120
|
+
let cellWidth = 0
|
|
2121
|
+
for (let idx = 0; idx < gridSpan; idx++) {
|
|
2122
|
+
cellWidth += colWidths[pc + idx] || 0
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
let cellHeight = 0
|
|
2126
|
+
for (let idx = 0; idx < rowSpan; idx++) {
|
|
2127
|
+
cellHeight += rowHeights[pr + idx] || 0
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
return {
|
|
2131
|
+
left: cellLeft,
|
|
2132
|
+
top: cellTop,
|
|
2133
|
+
width: cellWidth,
|
|
2134
|
+
height: cellHeight,
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
|
|
1764
2138
|
const shapesToCreate = []
|
|
1765
2139
|
const headerNames = (tblObj['a:tr']?.[0]?.['a:tc'] || []).map(cell =>
|
|
1766
2140
|
this.#getCellText(cell).trim()
|
|
@@ -1890,7 +2264,45 @@ class TableManager {
|
|
|
1890
2264
|
}
|
|
1891
2265
|
|
|
1892
2266
|
addCellShape(slideIndex, tableId, rowIndex, colIndex, options, slideManager, shapeManager) {
|
|
1893
|
-
const { resolvedTableId } = this.#getTableContext(
|
|
2267
|
+
const { tblObj, frameObj, resolvedTableId } = this.#getTableContext(
|
|
2268
|
+
slideIndex,
|
|
2269
|
+
tableId,
|
|
2270
|
+
slideManager
|
|
2271
|
+
)
|
|
2272
|
+
|
|
2273
|
+
const xfrm = frameObj['p:xfrm']
|
|
2274
|
+
const tableX = xfrm?.['a:off']?.['@_x'] ? parseInt(xfrm['a:off']['@_x'], 10) : 0
|
|
2275
|
+
const tableY = xfrm?.['a:off']?.['@_y'] ? parseInt(xfrm['a:off']['@_y'], 10) : 0
|
|
2276
|
+
|
|
2277
|
+
const gridCols = tblObj['a:tblGrid']?.['a:gridCol'] || []
|
|
2278
|
+
const gridColsArr = Array.isArray(gridCols) ? gridCols : [gridCols]
|
|
2279
|
+
const colWidths = gridColsArr.map(col => parseInt(col['@_w'] || 0, 10))
|
|
2280
|
+
|
|
2281
|
+
const trsArr = tblObj['a:tr'] || []
|
|
2282
|
+
const rowHeights = trsArr.map(row => parseInt(row['@_h'] || 0, 10))
|
|
2283
|
+
|
|
2284
|
+
const parent = this.getMergeParent(slideIndex, tableId, rowIndex, colIndex, slideManager)
|
|
2285
|
+
const pr = parent.row
|
|
2286
|
+
const pc = parent.col
|
|
2287
|
+
|
|
2288
|
+
let cellLeft = tableX
|
|
2289
|
+
for (let idx = 0; idx < pc; idx++) {
|
|
2290
|
+
cellLeft += colWidths[idx] || 0
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
let cellTop = tableY
|
|
2294
|
+
for (let idx = 0; idx < pr; idx++) {
|
|
2295
|
+
cellTop += rowHeights[idx] || 0
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
const parentCell = trsArr[pr]?.['a:tc']?.[pc]
|
|
2299
|
+
const gridSpan = parentCell?.['@_gridSpan'] ? parseInt(parentCell['@_gridSpan'], 10) : 1
|
|
2300
|
+
const rowSpan = parentCell?.['@_rowSpan'] ? parseInt(parentCell['@_rowSpan'], 10) : 1
|
|
2301
|
+
|
|
2302
|
+
let cellWidth = 0
|
|
2303
|
+
for (let idx = 0; idx < gridSpan; idx++) {
|
|
2304
|
+
cellWidth += colWidths[pc + idx] || 0
|
|
2305
|
+
}
|
|
1894
2306
|
|
|
1895
2307
|
const bounds = this.getCellBounds(slideIndex, tableId, rowIndex, colIndex, slideManager)
|
|
1896
2308
|
if (!bounds) {
|
|
@@ -1920,6 +2332,18 @@ class TableManager {
|
|
|
1920
2332
|
expandedConfig.id = `cellshape_${resolvedTableId}_${rowIndex}_${colIndex}_${finalShapeIndex}`
|
|
1921
2333
|
|
|
1922
2334
|
shapeManager.addShape(slideIndex, expandedConfig, slideManager)
|
|
2335
|
+
|
|
2336
|
+
// Register this shape's original config so it can be repositioned after
|
|
2337
|
+
// any subsequent table mutations (row removal, insertion, merge, etc.)
|
|
2338
|
+
this.#cellShapeAnchors.set(expandedConfig.id, {
|
|
2339
|
+
slideIndex,
|
|
2340
|
+
resolvedTableId,
|
|
2341
|
+
tableId,
|
|
2342
|
+
rowIndex,
|
|
2343
|
+
colIndex,
|
|
2344
|
+
shapeIndex: finalShapeIndex,
|
|
2345
|
+
config: { ...options },
|
|
2346
|
+
})
|
|
1923
2347
|
})
|
|
1924
2348
|
}
|
|
1925
2349
|
|
|
@@ -1933,7 +2357,11 @@ class TableManager {
|
|
|
1933
2357
|
slideManager,
|
|
1934
2358
|
shapeManager
|
|
1935
2359
|
) {
|
|
1936
|
-
const { resolvedTableId } = this.#getTableContext(
|
|
2360
|
+
const { tblObj, frameObj, resolvedTableId } = this.#getTableContext(
|
|
2361
|
+
slideIndex,
|
|
2362
|
+
tableId,
|
|
2363
|
+
slideManager
|
|
2364
|
+
)
|
|
1937
2365
|
|
|
1938
2366
|
const shapes = shapeManager.getShapes(slideIndex, slideManager)
|
|
1939
2367
|
const prefix = `cellshape_${resolvedTableId}_${rowIndex}_${colIndex}_${shapeIndex}`
|
|
@@ -1979,6 +2407,8 @@ class TableManager {
|
|
|
1979
2407
|
|
|
1980
2408
|
for (const s of matchingShapes) {
|
|
1981
2409
|
shapeManager.deleteShape(slideIndex, s.name, slideManager)
|
|
2410
|
+
// Deregister from anchor registry
|
|
2411
|
+
this.#cellShapeAnchors.delete(s.name)
|
|
1982
2412
|
}
|
|
1983
2413
|
}
|
|
1984
2414
|
|
|
@@ -1996,6 +2426,188 @@ class TableManager {
|
|
|
1996
2426
|
return shapeManager.getShape(slideIndex, primaryShape.name, slideManager)
|
|
1997
2427
|
}
|
|
1998
2428
|
|
|
2429
|
+
/**
|
|
2430
|
+
* Adjusts all registered cell shapes for a table after a row is removed (delta=-1)
|
|
2431
|
+
* or inserted (delta=+1) at `pivotRowIndex`.
|
|
2432
|
+
*
|
|
2433
|
+
* - For delta=-1 and shapes at pivotRowIndex: the shape is deleted (its row is gone).
|
|
2434
|
+
* - For shapes at rows that shifted: delete the old shape and re-add it at the new
|
|
2435
|
+
* row index so that `getCellBounds` can compute correct coordinates from the
|
|
2436
|
+
* updated table layout.
|
|
2437
|
+
*
|
|
2438
|
+
* @private
|
|
2439
|
+
*/
|
|
2440
|
+
#adjustCellShapesAfterRowShift(
|
|
2441
|
+
slideIndex,
|
|
2442
|
+
resolvedTableId,
|
|
2443
|
+
tableId,
|
|
2444
|
+
pivotRowIndex,
|
|
2445
|
+
delta,
|
|
2446
|
+
slideManager,
|
|
2447
|
+
shapeManager
|
|
2448
|
+
) {
|
|
2449
|
+
if (!shapeManager) return
|
|
2450
|
+
|
|
2451
|
+
// Collect entries first (avoid mutating map while iterating)
|
|
2452
|
+
const toDelete = []
|
|
2453
|
+
const toReindex = []
|
|
2454
|
+
|
|
2455
|
+
for (const [name, anchor] of this.#cellShapeAnchors) {
|
|
2456
|
+
if (anchor.slideIndex !== slideIndex || anchor.resolvedTableId !== resolvedTableId) continue
|
|
2457
|
+
|
|
2458
|
+
if (delta < 0 && anchor.rowIndex === pivotRowIndex) {
|
|
2459
|
+
// Row was removed — delete the shape
|
|
2460
|
+
toDelete.push(name)
|
|
2461
|
+
} else if (delta < 0 && anchor.rowIndex > pivotRowIndex) {
|
|
2462
|
+
// Row shifted up (removal below pivot)
|
|
2463
|
+
toReindex.push({ name, anchor, newRowIndex: anchor.rowIndex + delta })
|
|
2464
|
+
} else if (delta > 0 && anchor.rowIndex >= pivotRowIndex) {
|
|
2465
|
+
// Row shifted down (insertion at or above this row)
|
|
2466
|
+
toReindex.push({ name, anchor, newRowIndex: anchor.rowIndex + delta })
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
// Delete shapes for the removed row
|
|
2471
|
+
for (const name of toDelete) {
|
|
2472
|
+
try {
|
|
2473
|
+
shapeManager.deleteShape(slideIndex, name, slideManager)
|
|
2474
|
+
} catch (e) {
|
|
2475
|
+
logger.warn(`Failed to delete cell shape "${name}": ${e.message}`)
|
|
2476
|
+
}
|
|
2477
|
+
this.#cellShapeAnchors.delete(name)
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
// Sort toReindex: first by newRowIndex, then by colIndex, then by shapeIndex (base index)
|
|
2481
|
+
toReindex.sort((a, b) => {
|
|
2482
|
+
if (a.newRowIndex !== b.newRowIndex) {
|
|
2483
|
+
return a.newRowIndex - b.newRowIndex
|
|
2484
|
+
}
|
|
2485
|
+
if (a.anchor.colIndex !== b.anchor.colIndex) {
|
|
2486
|
+
return a.anchor.colIndex - b.anchor.colIndex
|
|
2487
|
+
}
|
|
2488
|
+
const aBase =
|
|
2489
|
+
typeof a.anchor.shapeIndex === 'string'
|
|
2490
|
+
? parseInt(a.anchor.shapeIndex.split('_')[0], 10)
|
|
2491
|
+
: a.anchor.shapeIndex
|
|
2492
|
+
const bBase =
|
|
2493
|
+
typeof b.anchor.shapeIndex === 'string'
|
|
2494
|
+
? parseInt(b.anchor.shapeIndex.split('_')[0], 10)
|
|
2495
|
+
: b.anchor.shapeIndex
|
|
2496
|
+
return aBase - bBase
|
|
2497
|
+
})
|
|
2498
|
+
|
|
2499
|
+
// Phase 1: delete all old shapes to prevent collisions when re-adding
|
|
2500
|
+
for (const { name } of toReindex) {
|
|
2501
|
+
try {
|
|
2502
|
+
shapeManager.deleteShape(slideIndex, name, slideManager)
|
|
2503
|
+
} catch (e) {
|
|
2504
|
+
logger.warn(`Failed to delete cell shape "${name}" during reindex: ${e.message}`)
|
|
2505
|
+
}
|
|
2506
|
+
this.#cellShapeAnchors.delete(name)
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
// Phase 2: re-add all shapes at their newRowIndex
|
|
2510
|
+
for (const { anchor, newRowIndex } of toReindex) {
|
|
2511
|
+
try {
|
|
2512
|
+
this.addCellShape(
|
|
2513
|
+
slideIndex,
|
|
2514
|
+
anchor.tableId,
|
|
2515
|
+
newRowIndex,
|
|
2516
|
+
anchor.colIndex,
|
|
2517
|
+
anchor.config,
|
|
2518
|
+
slideManager,
|
|
2519
|
+
shapeManager
|
|
2520
|
+
)
|
|
2521
|
+
} catch (e) {
|
|
2522
|
+
logger.warn(
|
|
2523
|
+
`Failed to re-add cell shape for (${newRowIndex}, ${anchor.colIndex}): ${e.message}`
|
|
2524
|
+
)
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
/**
|
|
2530
|
+
* Repositions all registered cell shapes that fall within a table region
|
|
2531
|
+
* (e.g. after a merge or unmerge). Shapes in the region are deleted and
|
|
2532
|
+
* re-added targeting `(startRow, startCol)` so their coordinates are
|
|
2533
|
+
* recomputed against the merged cell's full bounding box.
|
|
2534
|
+
*
|
|
2535
|
+
* @private
|
|
2536
|
+
*/
|
|
2537
|
+
#repositionCellShapesInRegion(
|
|
2538
|
+
slideIndex,
|
|
2539
|
+
tableId,
|
|
2540
|
+
resolvedTableId,
|
|
2541
|
+
startRow,
|
|
2542
|
+
startCol,
|
|
2543
|
+
endRow,
|
|
2544
|
+
endCol,
|
|
2545
|
+
slideManager,
|
|
2546
|
+
shapeManager
|
|
2547
|
+
) {
|
|
2548
|
+
if (!shapeManager) return
|
|
2549
|
+
|
|
2550
|
+
const toReposition = []
|
|
2551
|
+
|
|
2552
|
+
for (const [name, anchor] of this.#cellShapeAnchors) {
|
|
2553
|
+
if (anchor.slideIndex !== slideIndex || anchor.resolvedTableId !== resolvedTableId) continue
|
|
2554
|
+
if (
|
|
2555
|
+
anchor.rowIndex >= startRow &&
|
|
2556
|
+
anchor.rowIndex <= endRow &&
|
|
2557
|
+
anchor.colIndex >= startCol &&
|
|
2558
|
+
anchor.colIndex <= endCol
|
|
2559
|
+
) {
|
|
2560
|
+
toReposition.push({ name, anchor })
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2564
|
+
// Sort toReposition: first by original rowIndex, then by colIndex, then by shapeIndex (base index)
|
|
2565
|
+
toReposition.sort((a, b) => {
|
|
2566
|
+
if (a.anchor.rowIndex !== b.anchor.rowIndex) {
|
|
2567
|
+
return a.anchor.rowIndex - b.anchor.rowIndex
|
|
2568
|
+
}
|
|
2569
|
+
if (a.anchor.colIndex !== b.anchor.colIndex) {
|
|
2570
|
+
return a.anchor.colIndex - b.anchor.colIndex
|
|
2571
|
+
}
|
|
2572
|
+
const aBase =
|
|
2573
|
+
typeof a.anchor.shapeIndex === 'string'
|
|
2574
|
+
? parseInt(a.anchor.shapeIndex.split('_')[0], 10)
|
|
2575
|
+
: a.anchor.shapeIndex
|
|
2576
|
+
const bBase =
|
|
2577
|
+
typeof b.anchor.shapeIndex === 'string'
|
|
2578
|
+
? parseInt(b.anchor.shapeIndex.split('_')[0], 10)
|
|
2579
|
+
: b.anchor.shapeIndex
|
|
2580
|
+
return aBase - bBase
|
|
2581
|
+
})
|
|
2582
|
+
|
|
2583
|
+
// Phase 1: delete all old shapes first
|
|
2584
|
+
for (const { name } of toReposition) {
|
|
2585
|
+
try {
|
|
2586
|
+
shapeManager.deleteShape(slideIndex, name, slideManager)
|
|
2587
|
+
} catch (e) {
|
|
2588
|
+
logger.warn(`Failed to delete cell shape "${name}" during reposition: ${e.message}`)
|
|
2589
|
+
}
|
|
2590
|
+
this.#cellShapeAnchors.delete(name)
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
// Phase 2: re-add all shapes targeting startRow, startCol
|
|
2594
|
+
for (const { anchor } of toReposition) {
|
|
2595
|
+
try {
|
|
2596
|
+
this.addCellShape(
|
|
2597
|
+
slideIndex,
|
|
2598
|
+
anchor.tableId,
|
|
2599
|
+
startRow,
|
|
2600
|
+
startCol,
|
|
2601
|
+
anchor.config,
|
|
2602
|
+
slideManager,
|
|
2603
|
+
shapeManager
|
|
2604
|
+
)
|
|
2605
|
+
} catch (e) {
|
|
2606
|
+
logger.warn(`Failed to reposition cell shape for (${startRow}, ${startCol}): ${e.message}`)
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
|
|
1999
2611
|
/**
|
|
2000
2612
|
* Generates a new rowId for the given row object.
|
|
2001
2613
|
*/
|