node-pptx-templater 1.1.6 → 1.1.7
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 +1 -1
- package/src/core/PPTXTemplater.js +2 -1
- package/src/managers/TableManager.js +362 -136
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-pptx-templater",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.7",
|
|
4
4
|
"description": "High-performance, low-level PowerPoint (PPTX) OpenXML template engine for Node.js. Dynamically replace text, insert images, update charts (with Excel workbook data caching), and merge table cells without PowerPoint corruption or Repair Mode prompts.",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -80,11 +80,7 @@ class TableManager {
|
|
|
80
80
|
* @throws {TableNotFoundError} If the table is not found.
|
|
81
81
|
*/
|
|
82
82
|
updateTable(slideIndex, tableId, data, slideManager, shapeManager) {
|
|
83
|
-
const { tblObj,
|
|
84
|
-
slideIndex,
|
|
85
|
-
tableId,
|
|
86
|
-
slideManager
|
|
87
|
-
)
|
|
83
|
+
const { tblObj, resolvedTableId } = this.#getTableContext(slideIndex, tableId, slideManager)
|
|
88
84
|
|
|
89
85
|
const trs = tblObj['a:tr'] || []
|
|
90
86
|
if (trs.length === 0) {
|
|
@@ -236,6 +232,7 @@ class TableManager {
|
|
|
236
232
|
)
|
|
237
233
|
}
|
|
238
234
|
|
|
235
|
+
this.#calculateColumnWidths(slideIndex, tableId, slideManager, tblObj, true)
|
|
239
236
|
this.#calculateRowHeights(slideIndex, tableId, slideManager, tblObj, true)
|
|
240
237
|
|
|
241
238
|
if (cellShapes) {
|
|
@@ -248,11 +245,14 @@ class TableManager {
|
|
|
248
245
|
cellShapes,
|
|
249
246
|
slideManager,
|
|
250
247
|
shapeManager,
|
|
251
|
-
tblObj
|
|
252
|
-
frameObj
|
|
248
|
+
tblObj
|
|
253
249
|
)
|
|
254
250
|
}
|
|
255
251
|
|
|
252
|
+
if (shapeManager) {
|
|
253
|
+
this.#repositionAllTableCellShapes(slideIndex, tableId, slideManager, shapeManager)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
256
|
logger.debug(
|
|
257
257
|
`Updated table "${tableId}" with ${rowsData.length} rows and ${finalMerges.length} merges`
|
|
258
258
|
)
|
|
@@ -484,6 +484,13 @@ class TableManager {
|
|
|
484
484
|
)
|
|
485
485
|
}
|
|
486
486
|
}
|
|
487
|
+
|
|
488
|
+
this.#calculateColumnWidths(slideIndex, tableId, slideManager, tblObj, true)
|
|
489
|
+
this.#calculateRowHeights(slideIndex, tableId, slideManager, tblObj, true)
|
|
490
|
+
|
|
491
|
+
if (shapeManager) {
|
|
492
|
+
this.#repositionAllTableCellShapes(slideIndex, tableId, slideManager, shapeManager)
|
|
493
|
+
}
|
|
487
494
|
}
|
|
488
495
|
|
|
489
496
|
/**
|
|
@@ -506,6 +513,9 @@ class TableManager {
|
|
|
506
513
|
|
|
507
514
|
slideManager.markSlideObjDirty(slideIndex)
|
|
508
515
|
|
|
516
|
+
this.#calculateColumnWidths(slideIndex, tableId, slideManager, tblObj, true)
|
|
517
|
+
this.#calculateRowHeights(slideIndex, tableId, slideManager, tblObj, true)
|
|
518
|
+
|
|
509
519
|
if (shapeManager) {
|
|
510
520
|
this.#adjustCellShapesAfterRowShift(
|
|
511
521
|
slideIndex,
|
|
@@ -557,6 +567,9 @@ class TableManager {
|
|
|
557
567
|
|
|
558
568
|
slideManager.markSlideObjDirty(slideIndex)
|
|
559
569
|
|
|
570
|
+
this.#calculateColumnWidths(slideIndex, tableId, slideManager, tblObj, true)
|
|
571
|
+
this.#calculateRowHeights(slideIndex, tableId, slideManager, tblObj, true)
|
|
572
|
+
|
|
560
573
|
if (shapeManager) {
|
|
561
574
|
this.#adjustCellShapesAfterRowShift(
|
|
562
575
|
slideIndex,
|
|
@@ -629,7 +642,16 @@ class TableManager {
|
|
|
629
642
|
* @param {Object} options
|
|
630
643
|
* @param {SlideManager} slideManager
|
|
631
644
|
*/
|
|
632
|
-
updateCell(
|
|
645
|
+
updateCell(
|
|
646
|
+
slideIndex,
|
|
647
|
+
tableId,
|
|
648
|
+
rowIndex,
|
|
649
|
+
colIndex,
|
|
650
|
+
value,
|
|
651
|
+
options = {},
|
|
652
|
+
slideManager,
|
|
653
|
+
shapeManager = null
|
|
654
|
+
) {
|
|
633
655
|
const { tblObj } = this.#getTableContext(slideIndex, tableId, slideManager)
|
|
634
656
|
|
|
635
657
|
const row = tblObj['a:tr']?.[rowIndex]
|
|
@@ -680,6 +702,13 @@ class TableManager {
|
|
|
680
702
|
}
|
|
681
703
|
|
|
682
704
|
slideManager.markSlideObjDirty(slideIndex)
|
|
705
|
+
|
|
706
|
+
this.#calculateColumnWidths(slideIndex, tableId, slideManager, tblObj, true)
|
|
707
|
+
this.#calculateRowHeights(slideIndex, tableId, slideManager, tblObj, true)
|
|
708
|
+
|
|
709
|
+
if (shapeManager) {
|
|
710
|
+
this.#repositionAllTableCellShapes(slideIndex, tableId, slideManager, shapeManager)
|
|
711
|
+
}
|
|
683
712
|
}
|
|
684
713
|
|
|
685
714
|
/**
|
|
@@ -840,6 +869,9 @@ class TableManager {
|
|
|
840
869
|
|
|
841
870
|
slideManager.markSlideObjDirty(slideIndex)
|
|
842
871
|
|
|
872
|
+
this.#calculateColumnWidths(slideIndex, tableId, slideManager, tblObj, true)
|
|
873
|
+
this.#calculateRowHeights(slideIndex, tableId, slideManager, tblObj, true)
|
|
874
|
+
|
|
843
875
|
if (shapeManager) {
|
|
844
876
|
this.#repositionCellShapesInRegion(
|
|
845
877
|
slideIndex,
|
|
@@ -852,6 +884,7 @@ class TableManager {
|
|
|
852
884
|
slideManager,
|
|
853
885
|
shapeManager
|
|
854
886
|
)
|
|
887
|
+
this.#repositionAllTableCellShapes(slideIndex, tableId, slideManager, shapeManager)
|
|
855
888
|
}
|
|
856
889
|
}
|
|
857
890
|
|
|
@@ -957,6 +990,13 @@ class TableManager {
|
|
|
957
990
|
}
|
|
958
991
|
}
|
|
959
992
|
|
|
993
|
+
this.#calculateColumnWidths(slideIndex, tableId, actualSlideManager, tblObj, true)
|
|
994
|
+
this.#calculateRowHeights(slideIndex, tableId, actualSlideManager, tblObj, true)
|
|
995
|
+
|
|
996
|
+
if (shapeManager) {
|
|
997
|
+
this.#repositionAllTableCellShapes(slideIndex, tableId, actualSlideManager, shapeManager)
|
|
998
|
+
}
|
|
999
|
+
|
|
960
1000
|
actualSlideManager.markSlideObjDirty(slideIndex)
|
|
961
1001
|
}
|
|
962
1002
|
|
|
@@ -1126,11 +1166,8 @@ class TableManager {
|
|
|
1126
1166
|
const tableX = xfrm?.['a:off']?.['@_x'] ? parseInt(xfrm['a:off']['@_x'], 10) : 0
|
|
1127
1167
|
const tableY = xfrm?.['a:off']?.['@_y'] ? parseInt(xfrm['a:off']['@_y'], 10) : 0
|
|
1128
1168
|
|
|
1129
|
-
const
|
|
1130
|
-
const
|
|
1131
|
-
const colWidths = gridColsArr.map(col => parseInt(col['@_w'] || 0, 10))
|
|
1132
|
-
|
|
1133
|
-
const rowHeights = this.#calculateRowHeights(slideIndex, tableId, slideManager, tblObj, false)
|
|
1169
|
+
const colWidths = this.#calculateColumnWidths(slideIndex, tableId, slideManager, tblObj, true)
|
|
1170
|
+
const rowHeights = this.#calculateRowHeights(slideIndex, tableId, slideManager, tblObj, true)
|
|
1134
1171
|
|
|
1135
1172
|
const R = this.getMergeRegion(slideIndex, tableId, rowIndex, colIndex, slideManager)
|
|
1136
1173
|
let pr = rowIndex
|
|
@@ -2068,8 +2105,7 @@ class TableManager {
|
|
|
2068
2105
|
cellShapes,
|
|
2069
2106
|
slideManager,
|
|
2070
2107
|
shapeManager,
|
|
2071
|
-
tblObj
|
|
2072
|
-
frameObj
|
|
2108
|
+
tblObj
|
|
2073
2109
|
) {
|
|
2074
2110
|
if (!cellShapes || !shapeManager) return
|
|
2075
2111
|
|
|
@@ -2087,54 +2123,6 @@ class TableManager {
|
|
|
2087
2123
|
}
|
|
2088
2124
|
}
|
|
2089
2125
|
|
|
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
|
-
|
|
2138
2126
|
const shapesToCreate = []
|
|
2139
2127
|
const headerNames = (tblObj['a:tr']?.[0]?.['a:tc'] || []).map(cell =>
|
|
2140
2128
|
this.#getCellText(cell).trim()
|
|
@@ -2264,45 +2252,7 @@ class TableManager {
|
|
|
2264
2252
|
}
|
|
2265
2253
|
|
|
2266
2254
|
addCellShape(slideIndex, tableId, rowIndex, colIndex, options, slideManager, shapeManager) {
|
|
2267
|
-
const {
|
|
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
|
-
}
|
|
2255
|
+
const { resolvedTableId } = this.#getTableContext(slideIndex, tableId, slideManager)
|
|
2306
2256
|
|
|
2307
2257
|
const bounds = this.getCellBounds(slideIndex, tableId, rowIndex, colIndex, slideManager)
|
|
2308
2258
|
if (!bounds) {
|
|
@@ -2357,11 +2307,7 @@ class TableManager {
|
|
|
2357
2307
|
slideManager,
|
|
2358
2308
|
shapeManager
|
|
2359
2309
|
) {
|
|
2360
|
-
const {
|
|
2361
|
-
slideIndex,
|
|
2362
|
-
tableId,
|
|
2363
|
-
slideManager
|
|
2364
|
-
)
|
|
2310
|
+
const { resolvedTableId } = this.#getTableContext(slideIndex, tableId, slideManager)
|
|
2365
2311
|
|
|
2366
2312
|
const shapes = shapeManager.getShapes(slideIndex, slideManager)
|
|
2367
2313
|
const prefix = `cellshape_${resolvedTableId}_${rowIndex}_${colIndex}_${shapeIndex}`
|
|
@@ -2608,6 +2554,253 @@ class TableManager {
|
|
|
2608
2554
|
}
|
|
2609
2555
|
}
|
|
2610
2556
|
|
|
2557
|
+
#calculateColumnWidths(slideIndex, tableId, slideManager, tblObj, writeToXml = true) {
|
|
2558
|
+
const trsArr = tblObj['a:tr'] || []
|
|
2559
|
+
if (trsArr.length === 0) return []
|
|
2560
|
+
|
|
2561
|
+
const gridCols = tblObj['a:tblGrid']?.['a:gridCol'] || []
|
|
2562
|
+
const gridColsArr = Array.isArray(gridCols) ? gridCols : [gridCols]
|
|
2563
|
+
const originalWidths = gridColsArr.map(col => parseInt(col['@_w'] || 0, 10))
|
|
2564
|
+
const totalTableWidth = originalWidths.reduce((sum, w) => sum + w, 0)
|
|
2565
|
+
const numCols = originalWidths.length
|
|
2566
|
+
if (numCols === 0) return []
|
|
2567
|
+
|
|
2568
|
+
// Helper to get paragraph font info
|
|
2569
|
+
const getParagraphFontInfo = p => {
|
|
2570
|
+
let maxSz = null
|
|
2571
|
+
let typeface = null
|
|
2572
|
+
|
|
2573
|
+
const getTypeface = pr => {
|
|
2574
|
+
if (!pr) return null
|
|
2575
|
+
if (pr['a:latin']?.['@_typeface']) return pr['a:latin']['@_typeface']
|
|
2576
|
+
if (pr['a:ea']?.['@_typeface']) return pr['a:ea']['@_typeface']
|
|
2577
|
+
if (pr['a:cs']?.['@_typeface']) return pr['a:cs']['@_typeface']
|
|
2578
|
+
return null
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2581
|
+
if (p['a:pPr']?.['a:defRPr']) {
|
|
2582
|
+
const defRPr = p['a:pPr']['a:defRPr']
|
|
2583
|
+
if (defRPr['@_sz']) {
|
|
2584
|
+
maxSz = parseInt(defRPr['@_sz'], 10) / 100
|
|
2585
|
+
}
|
|
2586
|
+
typeface = getTypeface(defRPr)
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
if (p['a:r']) {
|
|
2590
|
+
const runs = Array.isArray(p['a:r']) ? p['a:r'] : [p['a:r']]
|
|
2591
|
+
for (const r of runs) {
|
|
2592
|
+
if (r['a:rPr']) {
|
|
2593
|
+
const rPr = r['a:rPr']
|
|
2594
|
+
if (rPr['@_sz']) {
|
|
2595
|
+
const szVal = parseInt(rPr['@_sz'], 10) / 100
|
|
2596
|
+
if (maxSz === null || szVal > maxSz) {
|
|
2597
|
+
maxSz = szVal
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
const tf = getTypeface(rPr)
|
|
2601
|
+
if (tf) {
|
|
2602
|
+
typeface = tf
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
|
|
2608
|
+
if (maxSz === null && p['a:endParaRPr']) {
|
|
2609
|
+
const endParaRPr = p['a:endParaRPr']
|
|
2610
|
+
if (endParaRPr['@_sz']) {
|
|
2611
|
+
maxSz = parseInt(endParaRPr['@_sz'], 10) / 100
|
|
2612
|
+
}
|
|
2613
|
+
const tf = getTypeface(endParaRPr)
|
|
2614
|
+
if (tf) {
|
|
2615
|
+
typeface = tf
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
|
|
2619
|
+
return {
|
|
2620
|
+
fontSize: maxSz !== null ? maxSz : 14,
|
|
2621
|
+
typeface: typeface || 'Arial',
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
const getFontAspect = tf => {
|
|
2626
|
+
if (!tf) return 0.55
|
|
2627
|
+
const name = String(tf).toLowerCase()
|
|
2628
|
+
if (name.includes('algerian')) return 0.85
|
|
2629
|
+
return 0.55
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2632
|
+
const getCellMargins = cell => {
|
|
2633
|
+
const tcPr = cell['a:tcPr']
|
|
2634
|
+
const marL = tcPr?.['@_marL'] !== undefined ? parseInt(tcPr['@_marL'], 10) : 91440
|
|
2635
|
+
const marR = tcPr?.['@_marR'] !== undefined ? parseInt(tcPr['@_marR'], 10) : 91440
|
|
2636
|
+
return { marL, marR }
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
const colWeights = new Array(numCols).fill(0)
|
|
2640
|
+
const hasText = new Array(numCols).fill(false)
|
|
2641
|
+
|
|
2642
|
+
for (let c = 0; c < numCols; c++) {
|
|
2643
|
+
let maxCellWeight = 0
|
|
2644
|
+
|
|
2645
|
+
for (let r = 0; r < trsArr.length; r++) {
|
|
2646
|
+
const row = trsArr[r]
|
|
2647
|
+
const cell = row['a:tc']?.[c]
|
|
2648
|
+
if (!cell || cell['@_hMerge']) continue
|
|
2649
|
+
|
|
2650
|
+
const gridSpan = cell['@_gridSpan'] ? parseInt(cell['@_gridSpan'], 10) : 1
|
|
2651
|
+
if (gridSpan > 1) continue
|
|
2652
|
+
|
|
2653
|
+
const { marL, marR } = getCellMargins(cell)
|
|
2654
|
+
let cellTextWidth = 0
|
|
2655
|
+
|
|
2656
|
+
const txBody = cell['a:txBody']
|
|
2657
|
+
if (txBody) {
|
|
2658
|
+
const paras = Array.isArray(txBody['a:p']) ? txBody['a:p'] : [txBody['a:p']]
|
|
2659
|
+
for (const p of paras) {
|
|
2660
|
+
const fontInfo = getParagraphFontInfo(p)
|
|
2661
|
+
const aspect = getFontAspect(fontInfo.typeface)
|
|
2662
|
+
let pText = ''
|
|
2663
|
+
if (p['a:r']) {
|
|
2664
|
+
const runs = Array.isArray(p['a:r']) ? p['a:r'] : [p['a:r']]
|
|
2665
|
+
for (const r of runs) {
|
|
2666
|
+
if (r['a:t']) {
|
|
2667
|
+
pText += String(r['a:t'])
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
|
|
2672
|
+
if (pText.trim()) {
|
|
2673
|
+
hasText[c] = true
|
|
2674
|
+
const words = pText.split(/\s+/)
|
|
2675
|
+
let longestWordLen = 0
|
|
2676
|
+
for (const w of words) {
|
|
2677
|
+
if (w.length > longestWordLen) longestWordLen = w.length
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
const minWordWidth = longestWordLen * fontInfo.fontSize * aspect * 9525 + marL + marR
|
|
2681
|
+
const idealChars = Math.min(30, pText.length)
|
|
2682
|
+
const idealTextWidth = idealChars * fontInfo.fontSize * aspect * 9525 + marL + marR
|
|
2683
|
+
|
|
2684
|
+
const cellIdeal = Math.max(minWordWidth, idealTextWidth)
|
|
2685
|
+
if (cellIdeal > cellTextWidth) {
|
|
2686
|
+
cellTextWidth = cellIdeal
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
const cellMin = cellTextWidth
|
|
2693
|
+
if (cellMin > maxCellWeight) {
|
|
2694
|
+
maxCellWeight = cellMin
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2698
|
+
colWeights[c] = maxCellWeight
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
const preferredWidths = new Array(numCols).fill(0)
|
|
2702
|
+
for (let c = 0; c < numCols; c++) {
|
|
2703
|
+
let floor = 500000
|
|
2704
|
+
if (!hasText[c]) {
|
|
2705
|
+
floor = Math.min(originalWidths[c] || 500000, 500000)
|
|
2706
|
+
}
|
|
2707
|
+
preferredWidths[c] = Math.max(colWeights[c], floor)
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2710
|
+
const sumPreferred = preferredWidths.reduce((sum, w) => sum + w, 0)
|
|
2711
|
+
|
|
2712
|
+
let finalWidths = [...preferredWidths]
|
|
2713
|
+
if (sumPreferred > 0) {
|
|
2714
|
+
const scale = totalTableWidth / sumPreferred
|
|
2715
|
+
finalWidths = preferredWidths.map((w, idx) => {
|
|
2716
|
+
const scaled = Math.round(w * scale)
|
|
2717
|
+
const minAllowed = Math.min(originalWidths[idx] || 300000, 300000)
|
|
2718
|
+
return Math.max(scaled, minAllowed)
|
|
2719
|
+
})
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
const sumFinal = finalWidths.reduce((sum, w) => sum + w, 0)
|
|
2723
|
+
const diff = totalTableWidth - sumFinal
|
|
2724
|
+
if (diff !== 0 && finalWidths.length > 0) {
|
|
2725
|
+
finalWidths[finalWidths.length - 1] += diff
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2728
|
+
if (writeToXml) {
|
|
2729
|
+
const tblGrid = tblObj['a:tblGrid']
|
|
2730
|
+
if (tblGrid) {
|
|
2731
|
+
if (Array.isArray(tblGrid['a:gridCol'])) {
|
|
2732
|
+
finalWidths.forEach((w, idx) => {
|
|
2733
|
+
if (tblGrid['a:gridCol'][idx]) {
|
|
2734
|
+
tblGrid['a:gridCol'][idx]['@_w'] = String(w)
|
|
2735
|
+
}
|
|
2736
|
+
})
|
|
2737
|
+
} else if (tblGrid['a:gridCol'] && numCols === 1) {
|
|
2738
|
+
tblGrid['a:gridCol']['@_w'] = String(finalWidths[0])
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
return finalWidths
|
|
2744
|
+
}
|
|
2745
|
+
|
|
2746
|
+
#repositionAllTableCellShapes(slideIndex, tableId, slideManager, shapeManager) {
|
|
2747
|
+
if (!shapeManager) return
|
|
2748
|
+
|
|
2749
|
+
const { resolvedTableId } = this.#getTableContext(slideIndex, tableId, slideManager)
|
|
2750
|
+
|
|
2751
|
+
const anchorsToReposition = []
|
|
2752
|
+
for (const [name, anchor] of this.#cellShapeAnchors) {
|
|
2753
|
+
if (anchor.slideIndex === slideIndex && anchor.resolvedTableId === resolvedTableId) {
|
|
2754
|
+
anchorsToReposition.push({ name, anchor })
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2758
|
+
anchorsToReposition.sort((a, b) => {
|
|
2759
|
+
if (a.anchor.rowIndex !== b.anchor.rowIndex) {
|
|
2760
|
+
return a.anchor.rowIndex - b.anchor.rowIndex
|
|
2761
|
+
}
|
|
2762
|
+
if (a.anchor.colIndex !== b.anchor.colIndex) {
|
|
2763
|
+
return a.anchor.colIndex - b.anchor.colIndex
|
|
2764
|
+
}
|
|
2765
|
+
const aBase =
|
|
2766
|
+
typeof a.anchor.shapeIndex === 'string'
|
|
2767
|
+
? parseInt(a.anchor.shapeIndex.split('_')[0], 10)
|
|
2768
|
+
: a.anchor.shapeIndex
|
|
2769
|
+
const bBase =
|
|
2770
|
+
typeof b.anchor.shapeIndex === 'string'
|
|
2771
|
+
? parseInt(b.anchor.shapeIndex.split('_')[0], 10)
|
|
2772
|
+
: b.anchor.shapeIndex
|
|
2773
|
+
return aBase - bBase
|
|
2774
|
+
})
|
|
2775
|
+
|
|
2776
|
+
for (const { name } of anchorsToReposition) {
|
|
2777
|
+
try {
|
|
2778
|
+
shapeManager.deleteShape(slideIndex, name, slideManager)
|
|
2779
|
+
} catch (e) {
|
|
2780
|
+
logger.warn(`Failed to delete cell shape "${name}" during table reposition: ${e.message}`)
|
|
2781
|
+
}
|
|
2782
|
+
this.#cellShapeAnchors.delete(name)
|
|
2783
|
+
}
|
|
2784
|
+
|
|
2785
|
+
for (const { anchor } of anchorsToReposition) {
|
|
2786
|
+
try {
|
|
2787
|
+
this.addCellShape(
|
|
2788
|
+
slideIndex,
|
|
2789
|
+
anchor.tableId,
|
|
2790
|
+
anchor.rowIndex,
|
|
2791
|
+
anchor.colIndex,
|
|
2792
|
+
anchor.config,
|
|
2793
|
+
slideManager,
|
|
2794
|
+
shapeManager
|
|
2795
|
+
)
|
|
2796
|
+
} catch (e) {
|
|
2797
|
+
logger.warn(
|
|
2798
|
+
`Failed to reposition cell shape for (${anchor.rowIndex}, ${anchor.colIndex}): ${e.message}`
|
|
2799
|
+
)
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2611
2804
|
/**
|
|
2612
2805
|
* Generates a new rowId for the given row object.
|
|
2613
2806
|
*/
|
|
@@ -2657,35 +2850,76 @@ class TableManager {
|
|
|
2657
2850
|
const numRows = trsArr.length
|
|
2658
2851
|
const numCols = colWidths.length
|
|
2659
2852
|
|
|
2660
|
-
// Initialize rowHeights with original height or a safe minimum floor of 228600 EMUs (~24px/pt)
|
|
2661
2853
|
const rowHeights = trsArr.map(row => {
|
|
2662
2854
|
const h = parseInt(row['@_h'] || 0, 10)
|
|
2663
2855
|
return h > 0 ? h : 228600
|
|
2664
2856
|
})
|
|
2665
2857
|
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
let
|
|
2669
|
-
|
|
2670
|
-
|
|
2858
|
+
const getParagraphFontInfo = p => {
|
|
2859
|
+
let maxSz = null
|
|
2860
|
+
let typeface = null
|
|
2861
|
+
|
|
2862
|
+
const getTypeface = pr => {
|
|
2863
|
+
if (!pr) return null
|
|
2864
|
+
if (pr['a:latin']?.['@_typeface']) return pr['a:latin']['@_typeface']
|
|
2865
|
+
if (pr['a:ea']?.['@_typeface']) return pr['a:ea']['@_typeface']
|
|
2866
|
+
if (pr['a:cs']?.['@_typeface']) return pr['a:cs']['@_typeface']
|
|
2867
|
+
return null
|
|
2671
2868
|
}
|
|
2869
|
+
|
|
2870
|
+
if (p['a:pPr']?.['a:defRPr']) {
|
|
2871
|
+
const defRPr = p['a:pPr']['a:defRPr']
|
|
2872
|
+
if (defRPr['@_sz']) {
|
|
2873
|
+
maxSz = parseInt(defRPr['@_sz'], 10) / 100
|
|
2874
|
+
}
|
|
2875
|
+
typeface = getTypeface(defRPr)
|
|
2876
|
+
}
|
|
2877
|
+
|
|
2672
2878
|
if (p['a:r']) {
|
|
2673
2879
|
const runs = Array.isArray(p['a:r']) ? p['a:r'] : [p['a:r']]
|
|
2674
2880
|
for (const r of runs) {
|
|
2675
|
-
if (r['a:rPr']
|
|
2676
|
-
const
|
|
2677
|
-
if (
|
|
2678
|
-
|
|
2881
|
+
if (r['a:rPr']) {
|
|
2882
|
+
const rPr = r['a:rPr']
|
|
2883
|
+
if (rPr['@_sz']) {
|
|
2884
|
+
const szVal = parseInt(rPr['@_sz'], 10) / 100
|
|
2885
|
+
if (maxSz === null || szVal > maxSz) {
|
|
2886
|
+
maxSz = szVal
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
const tf = getTypeface(rPr)
|
|
2890
|
+
if (tf) {
|
|
2891
|
+
typeface = tf
|
|
2679
2892
|
}
|
|
2680
2893
|
}
|
|
2681
2894
|
}
|
|
2682
2895
|
}
|
|
2683
|
-
|
|
2896
|
+
|
|
2897
|
+
if (maxSz === null && p['a:endParaRPr']) {
|
|
2898
|
+
const endParaRPr = p['a:endParaRPr']
|
|
2899
|
+
if (endParaRPr['@_sz']) {
|
|
2900
|
+
maxSz = parseInt(endParaRPr['@_sz'], 10) / 100
|
|
2901
|
+
}
|
|
2902
|
+
const tf = getTypeface(endParaRPr)
|
|
2903
|
+
if (tf) {
|
|
2904
|
+
typeface = tf
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
|
|
2908
|
+
return {
|
|
2909
|
+
fontSize: maxSz !== null ? maxSz : 14,
|
|
2910
|
+
typeface: typeface || 'Arial',
|
|
2911
|
+
}
|
|
2684
2912
|
}
|
|
2685
2913
|
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
const
|
|
2914
|
+
const getFontAspect = tf => {
|
|
2915
|
+
if (!tf) return 0.55
|
|
2916
|
+
const name = String(tf).toLowerCase()
|
|
2917
|
+
if (name.includes('algerian')) return 0.85
|
|
2918
|
+
return 0.55
|
|
2919
|
+
}
|
|
2920
|
+
|
|
2921
|
+
const wrapText = (text, availWidth_px, fontSize, aspect = 0.55) => {
|
|
2922
|
+
const charWidth = fontSize * aspect
|
|
2689
2923
|
const words = text.split(/(\s+)/)
|
|
2690
2924
|
let linesCount = 0
|
|
2691
2925
|
let currentLineLen = 0
|
|
@@ -2718,7 +2952,6 @@ class TableManager {
|
|
|
2718
2952
|
return linesCount
|
|
2719
2953
|
}
|
|
2720
2954
|
|
|
2721
|
-
// Helper to get cell margins
|
|
2722
2955
|
const getCellMargins = cell => {
|
|
2723
2956
|
const tcPr = cell['a:tcPr']
|
|
2724
2957
|
const marL = tcPr?.['@_marL'] !== undefined ? parseInt(tcPr['@_marL'], 10) : 91440
|
|
@@ -2728,7 +2961,6 @@ class TableManager {
|
|
|
2728
2961
|
return { marL, marR, marT, marB }
|
|
2729
2962
|
}
|
|
2730
2963
|
|
|
2731
|
-
// Calculate required height for each cell
|
|
2732
2964
|
const cellHeights = Array.from({ length: numRows }, () => new Array(numCols).fill(0))
|
|
2733
2965
|
|
|
2734
2966
|
for (let r = 0; r < numRows; r++) {
|
|
@@ -2741,7 +2973,6 @@ class TableManager {
|
|
|
2741
2973
|
const parent = this.getMergeParent(slideIndex, tableId, r, c, slideManager)
|
|
2742
2974
|
const gridSpan = cell['@_gridSpan'] ? parseInt(cell['@_gridSpan'], 10) : 1
|
|
2743
2975
|
|
|
2744
|
-
// Calculate cell width
|
|
2745
2976
|
let cellWidth = 0
|
|
2746
2977
|
for (let idx = 0; idx < gridSpan; idx++) {
|
|
2747
2978
|
cellWidth += colWidths[parent.col + idx] || 0
|
|
@@ -2751,13 +2982,15 @@ class TableManager {
|
|
|
2751
2982
|
const availWidth = cellWidth - marL - marR
|
|
2752
2983
|
const availWidth_px = Math.max(1, availWidth / 9525)
|
|
2753
2984
|
|
|
2754
|
-
// Calculate text height
|
|
2755
2985
|
const txBody = cell['a:txBody']
|
|
2756
2986
|
let textHeight_emu = 0
|
|
2757
2987
|
if (txBody) {
|
|
2758
2988
|
const paras = Array.isArray(txBody['a:p']) ? txBody['a:p'] : [txBody['a:p']]
|
|
2759
2989
|
for (const p of paras) {
|
|
2760
|
-
const
|
|
2990
|
+
const fontInfo = getParagraphFontInfo(p)
|
|
2991
|
+
const fontSize = fontInfo.fontSize
|
|
2992
|
+
const aspect = getFontAspect(fontInfo.typeface)
|
|
2993
|
+
|
|
2761
2994
|
let pText = ''
|
|
2762
2995
|
if (p['a:r']) {
|
|
2763
2996
|
const runs = Array.isArray(p['a:r']) ? p['a:r'] : [p['a:r']]
|
|
@@ -2768,8 +3001,8 @@ class TableManager {
|
|
|
2768
3001
|
}
|
|
2769
3002
|
}
|
|
2770
3003
|
|
|
2771
|
-
const linesCount = wrapText(pText, availWidth_px, fontSize)
|
|
2772
|
-
const lineHeight_emu = fontSize * 20780
|
|
3004
|
+
const linesCount = wrapText(pText, availWidth_px, fontSize, aspect)
|
|
3005
|
+
const lineHeight_emu = fontSize * 20780
|
|
2773
3006
|
|
|
2774
3007
|
let pHeight_emu = linesCount * lineHeight_emu
|
|
2775
3008
|
if (p['a:pPr']?.['a:spcBef']?.['a:spcPts']?.['@_val']) {
|
|
@@ -2789,10 +3022,8 @@ class TableManager {
|
|
|
2789
3022
|
}
|
|
2790
3023
|
}
|
|
2791
3024
|
|
|
2792
|
-
// Now resolve row heights based on required cell heights
|
|
2793
|
-
// First, non-vertically-merged cells define row heights directly
|
|
2794
3025
|
for (let r = 0; r < numRows; r++) {
|
|
2795
|
-
let maxCellHeight = rowHeights[r]
|
|
3026
|
+
let maxCellHeight = rowHeights[r]
|
|
2796
3027
|
const row = trsArr[r]
|
|
2797
3028
|
const tcs = row['a:tc'] || []
|
|
2798
3029
|
for (let c = 0; c < numCols; c++) {
|
|
@@ -2808,7 +3039,6 @@ class TableManager {
|
|
|
2808
3039
|
rowHeights[r] = maxCellHeight
|
|
2809
3040
|
}
|
|
2810
3041
|
|
|
2811
|
-
// Next, adjust for vertically merged cells (rowSpan > 1)
|
|
2812
3042
|
for (let r = 0; r < numRows; r++) {
|
|
2813
3043
|
const row = trsArr[r]
|
|
2814
3044
|
const tcs = row['a:tc'] || []
|
|
@@ -2818,13 +3048,11 @@ class TableManager {
|
|
|
2818
3048
|
const rowSpan = cell['@_rowSpan'] ? parseInt(cell['@_rowSpan'], 10) : 1
|
|
2819
3049
|
if (rowSpan > 1) {
|
|
2820
3050
|
const reqHeight = cellHeights[r][c]
|
|
2821
|
-
// Sum currently allocated row heights for spanned rows
|
|
2822
3051
|
let currentSpanHeight = 0
|
|
2823
3052
|
for (let idx = 0; idx < rowSpan; idx++) {
|
|
2824
3053
|
currentSpanHeight += rowHeights[r + idx] || 0
|
|
2825
3054
|
}
|
|
2826
3055
|
if (reqHeight > currentSpanHeight) {
|
|
2827
|
-
// Distribute the extra required height equally across all spanned rows
|
|
2828
3056
|
const diff = reqHeight - currentSpanHeight
|
|
2829
3057
|
const extraPerRow = Math.ceil(diff / rowSpan)
|
|
2830
3058
|
for (let idx = 0; idx < rowSpan; idx++) {
|
|
@@ -2835,13 +3063,11 @@ class TableManager {
|
|
|
2835
3063
|
}
|
|
2836
3064
|
}
|
|
2837
3065
|
|
|
2838
|
-
// Update row heights in XML
|
|
2839
3066
|
if (writeToXml) {
|
|
2840
3067
|
for (let r = 0; r < numRows; r++) {
|
|
2841
3068
|
trsArr[r]['@_h'] = String(rowHeights[r])
|
|
2842
3069
|
}
|
|
2843
3070
|
}
|
|
2844
|
-
|
|
2845
3071
|
return rowHeights
|
|
2846
3072
|
}
|
|
2847
3073
|
|