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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-pptx-templater",
3
- "version": "1.1.6",
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",
@@ -1716,7 +1716,8 @@ class PPTXTemplater {
1716
1716
  colIndex,
1717
1717
  value,
1718
1718
  options,
1719
- this.#slideManager
1719
+ this.#slideManager,
1720
+ this.#shapeManager
1720
1721
  )
1721
1722
  }
1722
1723
  return this
@@ -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, frameObj, resolvedTableId } = this.#getTableContext(
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(slideIndex, tableId, rowIndex, colIndex, value, options = {}, slideManager) {
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 gridCols = tblObj['a:tblGrid']?.['a:gridCol'] || []
1130
- const gridColsArr = Array.isArray(gridCols) ? gridCols : [gridCols]
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 { 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
- }
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 { tblObj, frameObj, resolvedTableId } = this.#getTableContext(
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
- // Helper to get paragraph font size
2667
- const getParagraphFontSize = p => {
2668
- let maxSz = 14 // default 14pt
2669
- if (p['a:pPr']?.['a:defRPr']?.['@_sz']) {
2670
- maxSz = parseInt(p['a:pPr']['a:defRPr']['@_sz'], 10) / 100
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']?.['@_sz']) {
2676
- const szVal = parseInt(r['a:rPr']['@_sz'], 10) / 100
2677
- if (szVal > maxSz) {
2678
- maxSz = szVal
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
- return maxSz
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
- // Helper to wrap text
2687
- const wrapText = (text, availWidth_px, fontSize) => {
2688
- const charWidth = fontSize * 0.65
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 fontSize = getParagraphFontSize(p)
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 // 1.4 line height multiplier
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] // Start with original template height as floor
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