node-pptx-templater 1.0.20 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/README.md +170 -2073
- package/package.json +11 -1
- package/src/core/PPTXTemplater.js +985 -28
- package/src/index.js +3 -1
- package/src/managers/TableManager.js +714 -115
- package/src/utils/logger.js +76 -15
|
@@ -223,6 +223,8 @@ class TableManager {
|
|
|
223
223
|
)
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
+
this.#calculateRowHeights(slideIndex, tableId, slideManager, tblObj)
|
|
227
|
+
|
|
226
228
|
if (cellShapes) {
|
|
227
229
|
this.#processCellShapes(
|
|
228
230
|
slideIndex,
|
|
@@ -251,7 +253,7 @@ class TableManager {
|
|
|
251
253
|
* @param {string[]} rowData
|
|
252
254
|
* @param {SlideManager} slideManager
|
|
253
255
|
*/
|
|
254
|
-
addTableRow(slideIndex, tableId, rowData, slideManager) {
|
|
256
|
+
addTableRow(slideIndex, tableId, rowData, slideManager, options = {}) {
|
|
255
257
|
const { tblObj } = this.#getTableContext(slideIndex, tableId, slideManager)
|
|
256
258
|
|
|
257
259
|
const trs = tblObj['a:tr'] || []
|
|
@@ -260,18 +262,69 @@ class TableManager {
|
|
|
260
262
|
}
|
|
261
263
|
|
|
262
264
|
const lastRow = trs[trs.length - 1]
|
|
263
|
-
const
|
|
264
|
-
this.#updateRowId(newRow)
|
|
265
|
+
const numCols = lastRow['a:tc']?.length || 0
|
|
265
266
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if (tcs[j]['@_hMerge']) delete tcs[j]['@_hMerge']
|
|
271
|
-
if (tcs[j]['@_vMerge']) delete tcs[j]['@_vMerge']
|
|
267
|
+
// Compute target generated height
|
|
268
|
+
const heights = []
|
|
269
|
+
for (let c = 0; c < numCols; c++) {
|
|
270
|
+
heights.push(this.#getNestedHeight(rowData[c]))
|
|
272
271
|
}
|
|
272
|
+
const targetHeight = Math.max(1, ...heights)
|
|
273
273
|
|
|
274
|
-
|
|
274
|
+
// Expand each column value to targetHeight
|
|
275
|
+
const expandedCols = []
|
|
276
|
+
const strategy = options.mergeStrategy || 'auto'
|
|
277
|
+
for (let c = 0; c < numCols; c++) {
|
|
278
|
+
let colCells = this.#expandCellVal(rowData[c], targetHeight)
|
|
279
|
+
if (strategy === 'none') {
|
|
280
|
+
for (let i = 0; i < colCells.length; i++) {
|
|
281
|
+
if (colCells[i].vMerge) {
|
|
282
|
+
colCells[i] = { value: '', rowSpan: 1 }
|
|
283
|
+
} else {
|
|
284
|
+
colCells[i].rowSpan = 1
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
} else if (strategy === 'auto') {
|
|
288
|
+
colCells = this.#applyAutoMerge(colCells)
|
|
289
|
+
}
|
|
290
|
+
expandedCols.push(colCells)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Clone and append rows
|
|
294
|
+
for (let r = 0; r < targetHeight; r++) {
|
|
295
|
+
const newRow = this.#xmlParser.deepClone(lastRow)
|
|
296
|
+
this.#updateRowId(newRow)
|
|
297
|
+
|
|
298
|
+
const tcs = newRow['a:tc'] || []
|
|
299
|
+
for (let c = 0; c < numCols; c++) {
|
|
300
|
+
const cellDef = expandedCols[c][r]
|
|
301
|
+
const tcObj = tcs[c]
|
|
302
|
+
|
|
303
|
+
// Clear any previous merge attributes
|
|
304
|
+
if (tcObj['@_hMerge']) delete tcObj['@_hMerge']
|
|
305
|
+
if (tcObj['@_vMerge']) delete tcObj['@_vMerge']
|
|
306
|
+
if (tcObj['@_gridSpan']) delete tcObj['@_gridSpan']
|
|
307
|
+
if (tcObj['@_rowSpan']) delete tcObj['@_rowSpan']
|
|
308
|
+
|
|
309
|
+
if (cellDef.vMerge) {
|
|
310
|
+
tcObj['@_vMerge'] = '1'
|
|
311
|
+
this.#setCellTextObj(tcObj, '')
|
|
312
|
+
} else {
|
|
313
|
+
let text = cellDef.value
|
|
314
|
+
let cellOpts = {}
|
|
315
|
+
if (cellDef.value && typeof cellDef.value === 'object') {
|
|
316
|
+
text = cellDef.value.value !== undefined ? cellDef.value.value : ''
|
|
317
|
+
cellOpts = cellDef.value
|
|
318
|
+
}
|
|
319
|
+
this.#setCellTextObj(tcObj, text)
|
|
320
|
+
if (cellDef.rowSpan && cellDef.rowSpan > 1 && strategy !== 'none') {
|
|
321
|
+
tcObj['@_rowSpan'] = String(cellDef.rowSpan)
|
|
322
|
+
}
|
|
323
|
+
this.#applyCellOptions(tcObj, cellOpts)
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
trs.push(newRow)
|
|
327
|
+
}
|
|
275
328
|
|
|
276
329
|
slideManager.markSlideObjDirty(slideIndex)
|
|
277
330
|
}
|
|
@@ -740,6 +793,66 @@ class TableManager {
|
|
|
740
793
|
return { row, col }
|
|
741
794
|
}
|
|
742
795
|
|
|
796
|
+
getCellBounds(slideIndex, tableId, rowIndex, colIndex, slideManager) {
|
|
797
|
+
const { tblObj, frameObj } = this.#getTableContext(slideIndex, tableId, slideManager)
|
|
798
|
+
|
|
799
|
+
const xfrm = frameObj['p:xfrm']
|
|
800
|
+
const tableX = xfrm?.['a:off']?.['@_x'] ? parseInt(xfrm['a:off']['@_x'], 10) : 0
|
|
801
|
+
const tableY = xfrm?.['a:off']?.['@_y'] ? parseInt(xfrm['a:off']['@_y'], 10) : 0
|
|
802
|
+
|
|
803
|
+
const gridCols = tblObj['a:tblGrid']?.['a:gridCol'] || []
|
|
804
|
+
const gridColsArr = Array.isArray(gridCols) ? gridCols : [gridCols]
|
|
805
|
+
const colWidths = gridColsArr.map(col => parseInt(col['@_w'] || 0, 10))
|
|
806
|
+
|
|
807
|
+
const trsArr = tblObj['a:tr'] || []
|
|
808
|
+
const rowHeights = trsArr.map(row => parseInt(row['@_h'] || 0, 10))
|
|
809
|
+
|
|
810
|
+
const parent = this.getMergeParent(slideIndex, tableId, rowIndex, colIndex, slideManager)
|
|
811
|
+
const pr = parent.row
|
|
812
|
+
const pc = parent.col
|
|
813
|
+
|
|
814
|
+
let cellLeft = tableX
|
|
815
|
+
for (let idx = 0; idx < pc; idx++) {
|
|
816
|
+
cellLeft += colWidths[idx] || 0
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
let cellTop = tableY
|
|
820
|
+
for (let idx = 0; idx < pr; idx++) {
|
|
821
|
+
cellTop += rowHeights[idx] || 0
|
|
822
|
+
}
|
|
823
|
+
|
|
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
|
+
let cellWidth = 0
|
|
829
|
+
for (let idx = 0; idx < gridSpan; idx++) {
|
|
830
|
+
cellWidth += colWidths[pc + idx] || 0
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
let cellHeight = 0
|
|
834
|
+
for (let idx = 0; idx < rowSpan; idx++) {
|
|
835
|
+
cellHeight += rowHeights[pr + idx] || 0
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
return {
|
|
839
|
+
x: Math.round(cellLeft / 9525),
|
|
840
|
+
y: Math.round(cellTop / 9525),
|
|
841
|
+
width: Math.round(cellWidth / 9525),
|
|
842
|
+
height: Math.round(cellHeight / 9525),
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
getCellPosition(slideIndex, tableId, rowIndex, colIndex, slideManager) {
|
|
847
|
+
const bounds = this.getCellBounds(slideIndex, tableId, rowIndex, colIndex, slideManager)
|
|
848
|
+
return {
|
|
849
|
+
row: rowIndex,
|
|
850
|
+
column: colIndex,
|
|
851
|
+
x: bounds.x,
|
|
852
|
+
y: bounds.y,
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
743
856
|
/**
|
|
744
857
|
* Splits a merged region containing cell (row, col).
|
|
745
858
|
*/
|
|
@@ -1152,45 +1265,236 @@ class TableManager {
|
|
|
1152
1265
|
}
|
|
1153
1266
|
|
|
1154
1267
|
#expandCellShape(config, cellBounds) {
|
|
1155
|
-
const cellLeft_px = cellBounds.left / 9525
|
|
1156
|
-
const cellTop_px = cellBounds.top / 9525
|
|
1157
|
-
const cellWidth_px = cellBounds.width / 9525
|
|
1158
|
-
const cellHeight_px = cellBounds.height / 9525
|
|
1268
|
+
const cellLeft_px = Math.round(cellBounds.left / 9525)
|
|
1269
|
+
const cellTop_px = Math.round(cellBounds.top / 9525)
|
|
1270
|
+
const cellWidth_px = Math.round(cellBounds.width / 9525)
|
|
1271
|
+
const cellHeight_px = Math.round(cellBounds.height / 9525)
|
|
1272
|
+
|
|
1273
|
+
const parseLength = (val, maxVal) => {
|
|
1274
|
+
if (typeof val === 'string' && val.endsWith('%')) {
|
|
1275
|
+
return (parseFloat(val) / 100) * maxVal
|
|
1276
|
+
}
|
|
1277
|
+
return val !== undefined ? parseFloat(val) : undefined
|
|
1278
|
+
}
|
|
1159
1279
|
|
|
1280
|
+
const isCellAnchored = config.anchor !== 'slide'
|
|
1281
|
+
|
|
1282
|
+
// 1. Determine bounding box width and height
|
|
1283
|
+
let shapeWidth
|
|
1284
|
+
let shapeHeight
|
|
1285
|
+
|
|
1286
|
+
if (config.type === 'progressBar') {
|
|
1287
|
+
shapeHeight = parseLength(config.height !== undefined ? config.height : 8, cellHeight_px)
|
|
1288
|
+
shapeWidth = parseLength(
|
|
1289
|
+
config.width !== undefined ? config.width : cellWidth_px - 10,
|
|
1290
|
+
cellWidth_px
|
|
1291
|
+
)
|
|
1292
|
+
} else if (config.type === 'badge') {
|
|
1293
|
+
const text = String(config.text !== undefined ? config.text : '')
|
|
1294
|
+
const fontSize = config.textStyle?.fontSize || 10
|
|
1295
|
+
const textWidth = text.length * fontSize * 0.6
|
|
1296
|
+
const paddingX = 12
|
|
1297
|
+
shapeWidth =
|
|
1298
|
+
parseLength(config.width, cellWidth_px) !== undefined
|
|
1299
|
+
? parseLength(config.width, cellWidth_px)
|
|
1300
|
+
: textWidth + paddingX * 2
|
|
1301
|
+
shapeHeight =
|
|
1302
|
+
parseLength(config.height, cellHeight_px) !== undefined
|
|
1303
|
+
? parseLength(config.height, cellHeight_px)
|
|
1304
|
+
: fontSize + 12
|
|
1305
|
+
} else if (config.type === 'icon') {
|
|
1306
|
+
const size = parseLength(
|
|
1307
|
+
config.size !== undefined ? config.size : 16,
|
|
1308
|
+
Math.min(cellWidth_px, cellHeight_px)
|
|
1309
|
+
)
|
|
1310
|
+
shapeWidth = size
|
|
1311
|
+
shapeHeight = size
|
|
1312
|
+
} else {
|
|
1313
|
+
shapeWidth = parseLength(config.width, cellWidth_px)
|
|
1314
|
+
if (shapeWidth === undefined) {
|
|
1315
|
+
const sizeVal = parseLength(config.size, Math.min(cellWidth_px, cellHeight_px))
|
|
1316
|
+
if (sizeVal !== undefined) {
|
|
1317
|
+
shapeWidth = sizeVal
|
|
1318
|
+
} else {
|
|
1319
|
+
const radiusVal = parseLength(config.radius, Math.min(cellWidth_px, cellHeight_px) / 2)
|
|
1320
|
+
if (radiusVal !== undefined) {
|
|
1321
|
+
shapeWidth = radiusVal * 2
|
|
1322
|
+
} else {
|
|
1323
|
+
shapeWidth = 12 // default
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
shapeHeight = parseLength(config.height, cellHeight_px)
|
|
1329
|
+
if (shapeHeight === undefined) {
|
|
1330
|
+
const sizeVal = parseLength(config.size, Math.min(cellWidth_px, cellHeight_px))
|
|
1331
|
+
if (sizeVal !== undefined) {
|
|
1332
|
+
shapeHeight = sizeVal
|
|
1333
|
+
} else {
|
|
1334
|
+
const radiusVal = parseLength(config.radius, Math.min(cellWidth_px, cellHeight_px) / 2)
|
|
1335
|
+
if (radiusVal !== undefined) {
|
|
1336
|
+
shapeHeight = radiusVal * 2
|
|
1337
|
+
} else {
|
|
1338
|
+
shapeHeight = 12 // default
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
// Scale shape down proportionally to fit inside the cell if it exceeds the cell dimensions
|
|
1345
|
+
if (shapeWidth > cellWidth_px || shapeHeight > cellHeight_px) {
|
|
1346
|
+
logger.warn(
|
|
1347
|
+
`Shape width (${shapeWidth}px) or height (${shapeHeight}px) exceeds cell dimensions (${cellWidth_px}px x ${cellHeight_px}px). Scaling shape to fit.`
|
|
1348
|
+
)
|
|
1349
|
+
const scale = Math.min(cellWidth_px / shapeWidth, cellHeight_px / shapeHeight)
|
|
1350
|
+
shapeWidth = Math.max(1, Math.floor(shapeWidth * scale))
|
|
1351
|
+
shapeHeight = Math.max(1, Math.floor(shapeHeight * scale))
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
// 2. Determine alignment settings
|
|
1355
|
+
let alignX = config.alignX
|
|
1356
|
+
let alignY = config.alignY
|
|
1357
|
+
|
|
1358
|
+
if (config.position) {
|
|
1359
|
+
switch (config.position) {
|
|
1360
|
+
case 'top-left':
|
|
1361
|
+
if (!alignX) alignX = 'left'
|
|
1362
|
+
if (!alignY) alignY = 'top'
|
|
1363
|
+
break
|
|
1364
|
+
case 'top-center':
|
|
1365
|
+
case 'top':
|
|
1366
|
+
if (!alignX) alignX = 'center'
|
|
1367
|
+
if (!alignY) alignY = 'top'
|
|
1368
|
+
break
|
|
1369
|
+
case 'top-right':
|
|
1370
|
+
if (!alignX) alignX = 'right'
|
|
1371
|
+
if (!alignY) alignY = 'top'
|
|
1372
|
+
break
|
|
1373
|
+
case 'middle-left':
|
|
1374
|
+
case 'left':
|
|
1375
|
+
if (!alignX) alignX = 'left'
|
|
1376
|
+
if (!alignY) alignY = 'middle'
|
|
1377
|
+
break
|
|
1378
|
+
case 'center':
|
|
1379
|
+
case 'middle-center':
|
|
1380
|
+
if (!alignX) alignX = 'center'
|
|
1381
|
+
if (!alignY) alignY = 'middle'
|
|
1382
|
+
break
|
|
1383
|
+
case 'middle-right':
|
|
1384
|
+
case 'right':
|
|
1385
|
+
if (!alignX) alignX = 'right'
|
|
1386
|
+
if (!alignY) alignY = 'middle'
|
|
1387
|
+
break
|
|
1388
|
+
case 'bottom-left':
|
|
1389
|
+
if (!alignX) alignX = 'left'
|
|
1390
|
+
if (!alignY) alignY = 'bottom'
|
|
1391
|
+
break
|
|
1392
|
+
case 'bottom-center':
|
|
1393
|
+
case 'bottom':
|
|
1394
|
+
if (!alignX) alignX = 'center'
|
|
1395
|
+
if (!alignY) alignY = 'bottom'
|
|
1396
|
+
break
|
|
1397
|
+
case 'bottom-right':
|
|
1398
|
+
if (!alignX) alignX = 'right'
|
|
1399
|
+
if (!alignY) alignY = 'bottom'
|
|
1400
|
+
break
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
if (alignX && !alignY && config.y === undefined) {
|
|
1405
|
+
alignY = 'middle'
|
|
1406
|
+
}
|
|
1407
|
+
if (alignY && !alignX && config.x === undefined) {
|
|
1408
|
+
alignX = 'center'
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
if (!alignX && !alignY && config.x === undefined && config.y === undefined) {
|
|
1412
|
+
alignX = 'center'
|
|
1413
|
+
alignY = 'middle'
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
// 3. Compute coordinates
|
|
1417
|
+
let shapeLeft = cellLeft_px
|
|
1418
|
+
let shapeTop = cellTop_px
|
|
1419
|
+
|
|
1420
|
+
if (isCellAnchored) {
|
|
1421
|
+
if (alignX === 'left') {
|
|
1422
|
+
shapeLeft = cellLeft_px + (config.x !== undefined ? config.x : 5)
|
|
1423
|
+
} else if (alignX === 'center') {
|
|
1424
|
+
shapeLeft = cellLeft_px + (cellWidth_px - shapeWidth) / 2 + (config.x || 0)
|
|
1425
|
+
} else if (alignX === 'right') {
|
|
1426
|
+
shapeLeft =
|
|
1427
|
+
cellLeft_px + cellWidth_px - shapeWidth - (config.x !== undefined ? config.x : 5)
|
|
1428
|
+
} else {
|
|
1429
|
+
shapeLeft =
|
|
1430
|
+
cellLeft_px + (config.x !== undefined ? config.x : (cellWidth_px - shapeWidth) / 2)
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
if (alignY === 'top') {
|
|
1434
|
+
shapeTop = cellTop_px + (config.y !== undefined ? config.y : 5)
|
|
1435
|
+
} else if (alignY === 'middle') {
|
|
1436
|
+
shapeTop = cellTop_px + (cellHeight_px - shapeHeight) / 2 + (config.y || 0)
|
|
1437
|
+
} else if (alignY === 'bottom') {
|
|
1438
|
+
shapeTop =
|
|
1439
|
+
cellTop_px + cellHeight_px - shapeHeight - (config.y !== undefined ? config.y : 5)
|
|
1440
|
+
} else {
|
|
1441
|
+
shapeTop =
|
|
1442
|
+
cellTop_px + (config.y !== undefined ? config.y : (cellHeight_px - shapeHeight) / 2)
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
// 4. Boundary Constraints Validation/Enforcement
|
|
1446
|
+
if (shapeWidth > cellWidth_px) {
|
|
1447
|
+
shapeLeft = cellLeft_px
|
|
1448
|
+
} else {
|
|
1449
|
+
shapeLeft = Math.max(
|
|
1450
|
+
cellLeft_px,
|
|
1451
|
+
Math.min(shapeLeft, cellLeft_px + cellWidth_px - shapeWidth)
|
|
1452
|
+
)
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
if (shapeHeight > cellHeight_px) {
|
|
1456
|
+
shapeTop = cellTop_px
|
|
1457
|
+
} else {
|
|
1458
|
+
shapeTop = Math.max(
|
|
1459
|
+
cellTop_px,
|
|
1460
|
+
Math.min(shapeTop, cellTop_px + cellHeight_px - shapeHeight)
|
|
1461
|
+
)
|
|
1462
|
+
}
|
|
1463
|
+
} else {
|
|
1464
|
+
shapeLeft = config.x || 0
|
|
1465
|
+
shapeTop = config.y || 0
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
// 5. Expand individual sub-elements / custom shapes
|
|
1160
1469
|
if (config.type === 'progressBar') {
|
|
1161
1470
|
const value = config.value !== undefined ? config.value : 0
|
|
1162
1471
|
const max = config.max !== undefined ? config.max : 100
|
|
1163
1472
|
const fill = config.fill || '#3B82F6'
|
|
1164
1473
|
const bgFill = config.backgroundFill || '#E5E7EB'
|
|
1165
|
-
const pbHeight = config.height !== undefined ? config.height : 8
|
|
1166
|
-
const pbWidth = config.width !== undefined ? config.width : cellWidth_px - 10
|
|
1167
|
-
|
|
1168
|
-
const pbX = cellLeft_px + (config.x !== undefined ? config.x : (cellWidth_px - pbWidth) / 2)
|
|
1169
|
-
const pbY = cellTop_px + (config.y !== undefined ? config.y : (cellHeight_px - pbHeight) / 2)
|
|
1170
1474
|
|
|
1171
1475
|
const shapes = []
|
|
1172
1476
|
shapes.push({
|
|
1173
1477
|
type: 'roundedRectangle',
|
|
1174
1478
|
fill: bgFill,
|
|
1175
|
-
x:
|
|
1176
|
-
y:
|
|
1177
|
-
width:
|
|
1178
|
-
height:
|
|
1179
|
-
borderRadius:
|
|
1479
|
+
x: shapeLeft,
|
|
1480
|
+
y: shapeTop,
|
|
1481
|
+
width: shapeWidth,
|
|
1482
|
+
height: shapeHeight,
|
|
1483
|
+
borderRadius: shapeHeight / 2,
|
|
1180
1484
|
zIndex: config.zIndex,
|
|
1181
1485
|
})
|
|
1182
1486
|
|
|
1183
1487
|
const pct = Math.min(1, Math.max(0, value / max))
|
|
1184
1488
|
if (pct > 0) {
|
|
1185
|
-
const filledWidth =
|
|
1489
|
+
const filledWidth = shapeWidth * pct
|
|
1186
1490
|
shapes.push({
|
|
1187
1491
|
type: 'roundedRectangle',
|
|
1188
1492
|
fill: fill,
|
|
1189
|
-
x:
|
|
1190
|
-
y:
|
|
1493
|
+
x: shapeLeft,
|
|
1494
|
+
y: shapeTop,
|
|
1191
1495
|
width: filledWidth,
|
|
1192
|
-
height:
|
|
1193
|
-
borderRadius:
|
|
1496
|
+
height: shapeHeight,
|
|
1497
|
+
borderRadius: shapeHeight / 2,
|
|
1194
1498
|
zIndex: (config.zIndex || 0) + 1,
|
|
1195
1499
|
})
|
|
1196
1500
|
}
|
|
@@ -1200,23 +1504,15 @@ class TableManager {
|
|
|
1200
1504
|
if (config.type === 'badge') {
|
|
1201
1505
|
const text = String(config.text !== undefined ? config.text : '')
|
|
1202
1506
|
const fontSize = config.textStyle?.fontSize || 10
|
|
1203
|
-
const textWidth = text.length * fontSize * 0.6
|
|
1204
|
-
const paddingX = 12
|
|
1205
|
-
const badgeWidth = config.width !== undefined ? config.width : textWidth + paddingX * 2
|
|
1206
|
-
const badgeHeight = config.height !== undefined ? config.height : fontSize + 12
|
|
1207
|
-
|
|
1208
|
-
const x = cellLeft_px + (config.x !== undefined ? config.x : (cellWidth_px - badgeWidth) / 2)
|
|
1209
|
-
const y = cellTop_px + (config.y !== undefined ? config.y : (cellHeight_px - badgeHeight) / 2)
|
|
1210
|
-
|
|
1211
1507
|
return [
|
|
1212
1508
|
{
|
|
1213
1509
|
type: 'roundedRectangle',
|
|
1214
1510
|
fill: config.fill || '#10B981',
|
|
1215
|
-
borderRadius:
|
|
1216
|
-
x:
|
|
1217
|
-
y:
|
|
1218
|
-
width:
|
|
1219
|
-
height:
|
|
1511
|
+
borderRadius: shapeHeight / 2,
|
|
1512
|
+
x: shapeLeft,
|
|
1513
|
+
y: shapeTop,
|
|
1514
|
+
width: shapeWidth,
|
|
1515
|
+
height: shapeHeight,
|
|
1220
1516
|
text: text,
|
|
1221
1517
|
textStyle: {
|
|
1222
1518
|
color: config.textStyle?.color || '#FFFFFF',
|
|
@@ -1234,9 +1530,8 @@ class TableManager {
|
|
|
1234
1530
|
}
|
|
1235
1531
|
|
|
1236
1532
|
if (config.type === 'icon') {
|
|
1237
|
-
const size = config.size || 16
|
|
1238
1533
|
const iconFill = config.fill
|
|
1239
|
-
const fontSize = Math.round(
|
|
1534
|
+
const fontSize = Math.round(shapeWidth * 0.8)
|
|
1240
1535
|
|
|
1241
1536
|
let baseConfig = null
|
|
1242
1537
|
switch (config.icon) {
|
|
@@ -1245,8 +1540,8 @@ class TableManager {
|
|
|
1245
1540
|
type: 'rectangle',
|
|
1246
1541
|
fill: 'none',
|
|
1247
1542
|
border: null,
|
|
1248
|
-
width:
|
|
1249
|
-
height:
|
|
1543
|
+
width: shapeWidth,
|
|
1544
|
+
height: shapeHeight,
|
|
1250
1545
|
text: '✔',
|
|
1251
1546
|
textStyle: {
|
|
1252
1547
|
color: iconFill || '#10B981',
|
|
@@ -1261,8 +1556,8 @@ class TableManager {
|
|
|
1261
1556
|
type: 'rectangle',
|
|
1262
1557
|
fill: 'none',
|
|
1263
1558
|
border: null,
|
|
1264
|
-
width:
|
|
1265
|
-
height:
|
|
1559
|
+
width: shapeWidth,
|
|
1560
|
+
height: shapeHeight,
|
|
1266
1561
|
text: '✘',
|
|
1267
1562
|
textStyle: {
|
|
1268
1563
|
color: iconFill || '#EF4444',
|
|
@@ -1277,8 +1572,8 @@ class TableManager {
|
|
|
1277
1572
|
type: 'triangle',
|
|
1278
1573
|
fill: iconFill || '#F59E0B',
|
|
1279
1574
|
border: null,
|
|
1280
|
-
width:
|
|
1281
|
-
height:
|
|
1575
|
+
width: shapeWidth,
|
|
1576
|
+
height: shapeHeight,
|
|
1282
1577
|
text: '!',
|
|
1283
1578
|
textStyle: {
|
|
1284
1579
|
color: '#FFFFFF',
|
|
@@ -1293,7 +1588,7 @@ class TableManager {
|
|
|
1293
1588
|
type: 'circle',
|
|
1294
1589
|
fill: iconFill || '#3B82F6',
|
|
1295
1590
|
border: null,
|
|
1296
|
-
radius:
|
|
1591
|
+
radius: shapeWidth / 2,
|
|
1297
1592
|
text: 'i',
|
|
1298
1593
|
textStyle: {
|
|
1299
1594
|
color: '#FFFFFF',
|
|
@@ -1308,8 +1603,8 @@ class TableManager {
|
|
|
1308
1603
|
type: 'star5',
|
|
1309
1604
|
fill: iconFill || '#FBBF24',
|
|
1310
1605
|
border: null,
|
|
1311
|
-
width:
|
|
1312
|
-
height:
|
|
1606
|
+
width: shapeWidth,
|
|
1607
|
+
height: shapeHeight,
|
|
1313
1608
|
}
|
|
1314
1609
|
break
|
|
1315
1610
|
case 'up':
|
|
@@ -1317,8 +1612,8 @@ class TableManager {
|
|
|
1317
1612
|
type: 'upArrow',
|
|
1318
1613
|
fill: iconFill || '#10B981',
|
|
1319
1614
|
border: null,
|
|
1320
|
-
width:
|
|
1321
|
-
height:
|
|
1615
|
+
width: shapeWidth,
|
|
1616
|
+
height: shapeHeight,
|
|
1322
1617
|
}
|
|
1323
1618
|
break
|
|
1324
1619
|
case 'down':
|
|
@@ -1326,8 +1621,8 @@ class TableManager {
|
|
|
1326
1621
|
type: 'downArrow',
|
|
1327
1622
|
fill: iconFill || '#EF4444',
|
|
1328
1623
|
border: null,
|
|
1329
|
-
width:
|
|
1330
|
-
height:
|
|
1624
|
+
width: shapeWidth,
|
|
1625
|
+
height: shapeHeight,
|
|
1331
1626
|
}
|
|
1332
1627
|
break
|
|
1333
1628
|
case 'arrow-right':
|
|
@@ -1335,8 +1630,8 @@ class TableManager {
|
|
|
1335
1630
|
type: 'rightArrow',
|
|
1336
1631
|
fill: iconFill || '#3B82F6',
|
|
1337
1632
|
border: null,
|
|
1338
|
-
width:
|
|
1339
|
-
height:
|
|
1633
|
+
width: shapeWidth,
|
|
1634
|
+
height: shapeHeight,
|
|
1340
1635
|
}
|
|
1341
1636
|
break
|
|
1342
1637
|
case 'arrow-left':
|
|
@@ -1344,19 +1639,16 @@ class TableManager {
|
|
|
1344
1639
|
type: 'leftArrow',
|
|
1345
1640
|
fill: iconFill || '#3B82F6',
|
|
1346
1641
|
border: null,
|
|
1347
|
-
width:
|
|
1348
|
-
height:
|
|
1642
|
+
width: shapeWidth,
|
|
1643
|
+
height: shapeHeight,
|
|
1349
1644
|
}
|
|
1350
1645
|
break
|
|
1351
1646
|
default:
|
|
1352
1647
|
return []
|
|
1353
1648
|
}
|
|
1354
1649
|
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
baseConfig.x = x
|
|
1359
|
-
baseConfig.y = y
|
|
1650
|
+
baseConfig.x = shapeLeft
|
|
1651
|
+
baseConfig.y = shapeTop
|
|
1360
1652
|
baseConfig.zIndex = config.zIndex
|
|
1361
1653
|
if (config.border) baseConfig.border = config.border
|
|
1362
1654
|
if (config.transparency !== undefined) baseConfig.transparency = config.transparency
|
|
@@ -1366,58 +1658,11 @@ class TableManager {
|
|
|
1366
1658
|
return [baseConfig]
|
|
1367
1659
|
}
|
|
1368
1660
|
|
|
1369
|
-
const shapeWidth =
|
|
1370
|
-
config.width !== undefined
|
|
1371
|
-
? config.width
|
|
1372
|
-
: config.size !== undefined
|
|
1373
|
-
? config.size
|
|
1374
|
-
: config.radius !== undefined
|
|
1375
|
-
? config.radius * 2
|
|
1376
|
-
: 12
|
|
1377
|
-
const shapeHeight =
|
|
1378
|
-
config.height !== undefined
|
|
1379
|
-
? config.height
|
|
1380
|
-
: config.size !== undefined
|
|
1381
|
-
? config.size
|
|
1382
|
-
: config.radius !== undefined
|
|
1383
|
-
? config.radius * 2
|
|
1384
|
-
: 12
|
|
1385
|
-
|
|
1386
|
-
let shapeLeft = cellLeft_px
|
|
1387
|
-
let shapeTop = cellTop_px
|
|
1388
|
-
|
|
1389
|
-
if (config.position === 'center') {
|
|
1390
|
-
shapeLeft = cellLeft_px + (cellWidth_px - shapeWidth) / 2
|
|
1391
|
-
shapeTop = cellTop_px + (cellHeight_px - shapeHeight) / 2
|
|
1392
|
-
} else if (config.position === 'left') {
|
|
1393
|
-
shapeLeft = cellLeft_px + 5
|
|
1394
|
-
shapeTop = cellTop_px + (cellHeight_px - shapeHeight) / 2
|
|
1395
|
-
} else if (config.position === 'right') {
|
|
1396
|
-
shapeLeft = cellLeft_px + cellWidth_px - shapeWidth - 5
|
|
1397
|
-
shapeTop = cellTop_px + (cellHeight_px - shapeHeight) / 2
|
|
1398
|
-
} else if (config.position === 'top') {
|
|
1399
|
-
shapeLeft = cellLeft_px + (cellWidth_px - shapeWidth) / 2
|
|
1400
|
-
shapeTop = cellTop_px + 5
|
|
1401
|
-
} else if (config.position === 'bottom') {
|
|
1402
|
-
shapeLeft = cellLeft_px + (cellWidth_px - shapeWidth) / 2
|
|
1403
|
-
shapeTop = cellTop_px + cellHeight_px - shapeHeight - 5
|
|
1404
|
-
} else {
|
|
1405
|
-
if (config.x === undefined && config.y === undefined) {
|
|
1406
|
-
shapeLeft = cellLeft_px + (cellWidth_px - shapeWidth) / 2
|
|
1407
|
-
shapeTop = cellTop_px + (cellHeight_px - shapeHeight) / 2
|
|
1408
|
-
}
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
|
-
if (config.x !== undefined) {
|
|
1412
|
-
shapeLeft += config.x
|
|
1413
|
-
}
|
|
1414
|
-
if (config.y !== undefined) {
|
|
1415
|
-
shapeTop += config.y
|
|
1416
|
-
}
|
|
1417
|
-
|
|
1418
1661
|
const expanded = Object.assign({}, config, {
|
|
1419
1662
|
x: shapeLeft,
|
|
1420
1663
|
y: shapeTop,
|
|
1664
|
+
width: shapeWidth,
|
|
1665
|
+
height: shapeHeight,
|
|
1421
1666
|
})
|
|
1422
1667
|
|
|
1423
1668
|
if (expanded.type === 'circle' && expanded.radius === undefined) {
|
|
@@ -1561,6 +1806,69 @@ class TableManager {
|
|
|
1561
1806
|
})
|
|
1562
1807
|
}
|
|
1563
1808
|
|
|
1809
|
+
getTableRows(slideIndex, tableId, options = {}, slideManager) {
|
|
1810
|
+
const { tblObj } = this.#getTableContext(slideIndex, tableId, slideManager)
|
|
1811
|
+
const trs = tblObj['a:tr'] || []
|
|
1812
|
+
if (trs.length === 0) {
|
|
1813
|
+
return options.includeMetadata
|
|
1814
|
+
? { rows: [], rowCount: 0, columnCount: 0, mergedCells: [] }
|
|
1815
|
+
: []
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
const numRows = trs.length
|
|
1819
|
+
const gridCols = tblObj['a:tblGrid']?.['a:gridCol'] || []
|
|
1820
|
+
const gridColsArr = Array.isArray(gridCols) ? gridCols : [gridCols]
|
|
1821
|
+
const numCols = gridColsArr.length
|
|
1822
|
+
|
|
1823
|
+
// Extract all raw cell text, resolving merges to their parent's text
|
|
1824
|
+
const matrix = []
|
|
1825
|
+
for (let r = 0; r < numRows; r++) {
|
|
1826
|
+
const rowCells = []
|
|
1827
|
+
for (let c = 0; c < numCols; c++) {
|
|
1828
|
+
const parent = this.getMergeParent(slideIndex, tableId, r, c, slideManager)
|
|
1829
|
+
const cell = trs[parent.row]?.['a:tc']?.[parent.col]
|
|
1830
|
+
const text = cell ? this.#getCellText(cell) : ''
|
|
1831
|
+
rowCells.push(text)
|
|
1832
|
+
}
|
|
1833
|
+
matrix.push(rowCells)
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
// Header names are extracted from the first row (index 0)
|
|
1837
|
+
const headerNames = matrix[0].map((hText, cIdx) => {
|
|
1838
|
+
const cleaned = hText.trim()
|
|
1839
|
+
return cleaned || `column${cIdx + 1}`
|
|
1840
|
+
})
|
|
1841
|
+
|
|
1842
|
+
// Compute the data rows (excluding the header row at index 0)
|
|
1843
|
+
const dataRows = matrix.slice(1)
|
|
1844
|
+
|
|
1845
|
+
let rowsResult = []
|
|
1846
|
+
if (options.raw) {
|
|
1847
|
+
rowsResult = dataRows
|
|
1848
|
+
} else {
|
|
1849
|
+
for (const rowCells of dataRows) {
|
|
1850
|
+
const rowObj = {}
|
|
1851
|
+
for (let c = 0; c < numCols; c++) {
|
|
1852
|
+
const key = headerNames[c]
|
|
1853
|
+
rowObj[key] = rowCells[c] || ''
|
|
1854
|
+
}
|
|
1855
|
+
rowsResult.push(rowObj)
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
if (options.includeMetadata) {
|
|
1860
|
+
const mergedCells = this.getMergedCells(slideIndex, tableId, slideManager)
|
|
1861
|
+
return {
|
|
1862
|
+
rows: rowsResult,
|
|
1863
|
+
rowCount: numRows,
|
|
1864
|
+
columnCount: numCols,
|
|
1865
|
+
mergedCells,
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
return rowsResult
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1564
1872
|
addCellShape(slideIndex, tableId, rowIndex, colIndex, options, slideManager, shapeManager) {
|
|
1565
1873
|
const { tblObj, frameObj, resolvedTableId } = this.#getTableContext(
|
|
1566
1874
|
slideIndex,
|
|
@@ -1786,6 +2094,297 @@ class TableManager {
|
|
|
1786
2094
|
}
|
|
1787
2095
|
}
|
|
1788
2096
|
|
|
2097
|
+
#calculateRowHeights(slideIndex, tableId, slideManager, tblObj) {
|
|
2098
|
+
const trsArr = tblObj['a:tr'] || []
|
|
2099
|
+
if (trsArr.length === 0) return []
|
|
2100
|
+
|
|
2101
|
+
const gridCols = tblObj['a:tblGrid']?.['a:gridCol'] || []
|
|
2102
|
+
const gridColsArr = Array.isArray(gridCols) ? gridCols : [gridCols]
|
|
2103
|
+
const colWidths = gridColsArr.map(col => parseInt(col['@_w'] || 0, 10))
|
|
2104
|
+
|
|
2105
|
+
const numRows = trsArr.length
|
|
2106
|
+
const numCols = colWidths.length
|
|
2107
|
+
|
|
2108
|
+
// Initialize rowHeights with original height or 0
|
|
2109
|
+
const rowHeights = trsArr.map(row => parseInt(row['@_h'] || 0, 10))
|
|
2110
|
+
|
|
2111
|
+
// Helper to get paragraph font size
|
|
2112
|
+
const getParagraphFontSize = p => {
|
|
2113
|
+
let maxSz = 14 // default 14pt
|
|
2114
|
+
if (p['a:pPr']?.['a:defRPr']?.['@_sz']) {
|
|
2115
|
+
maxSz = parseInt(p['a:pPr']['a:defRPr']['@_sz'], 10) / 100
|
|
2116
|
+
}
|
|
2117
|
+
if (p['a:r']) {
|
|
2118
|
+
const runs = Array.isArray(p['a:r']) ? p['a:r'] : [p['a:r']]
|
|
2119
|
+
for (const r of runs) {
|
|
2120
|
+
if (r['a:rPr']?.['@_sz']) {
|
|
2121
|
+
const szVal = parseInt(r['a:rPr']['@_sz'], 10) / 100
|
|
2122
|
+
if (szVal > maxSz) {
|
|
2123
|
+
maxSz = szVal
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
return maxSz
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
// Helper to wrap text
|
|
2132
|
+
const wrapText = (text, availWidth_px, fontSize) => {
|
|
2133
|
+
const charWidth = fontSize * 0.65
|
|
2134
|
+
const words = text.split(/(\s+)/)
|
|
2135
|
+
let linesCount = 0
|
|
2136
|
+
let currentLineLen = 0
|
|
2137
|
+
|
|
2138
|
+
for (const word of words) {
|
|
2139
|
+
if (!word) continue
|
|
2140
|
+
const wordWidth = word.length * charWidth
|
|
2141
|
+
if (wordWidth > availWidth_px) {
|
|
2142
|
+
if (currentLineLen > 0) {
|
|
2143
|
+
linesCount++
|
|
2144
|
+
currentLineLen = 0
|
|
2145
|
+
}
|
|
2146
|
+
let remainingWidth = wordWidth
|
|
2147
|
+
while (remainingWidth > 0) {
|
|
2148
|
+
linesCount++
|
|
2149
|
+
remainingWidth -= availWidth_px
|
|
2150
|
+
}
|
|
2151
|
+
} else {
|
|
2152
|
+
if (currentLineLen + wordWidth > availWidth_px) {
|
|
2153
|
+
linesCount++
|
|
2154
|
+
currentLineLen = word.trim() ? wordWidth : 0
|
|
2155
|
+
} else {
|
|
2156
|
+
currentLineLen += wordWidth
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
if (currentLineLen > 0 || linesCount === 0) {
|
|
2161
|
+
linesCount++
|
|
2162
|
+
}
|
|
2163
|
+
return linesCount
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
// Helper to get cell margins
|
|
2167
|
+
const getCellMargins = cell => {
|
|
2168
|
+
const tcPr = cell['a:tcPr']
|
|
2169
|
+
const marL = tcPr?.['@_marL'] !== undefined ? parseInt(tcPr['@_marL'], 10) : 91440
|
|
2170
|
+
const marR = tcPr?.['@_marR'] !== undefined ? parseInt(tcPr['@_marR'], 10) : 91440
|
|
2171
|
+
const marT = tcPr?.['@_marT'] !== undefined ? parseInt(tcPr['@_marT'], 10) : 45720
|
|
2172
|
+
const marB = tcPr?.['@_marB'] !== undefined ? parseInt(tcPr['@_marB'], 10) : 45720
|
|
2173
|
+
return { marL, marR, marT, marB }
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
// Calculate required height for each cell
|
|
2177
|
+
const cellHeights = Array.from({ length: numRows }, () => new Array(numCols).fill(0))
|
|
2178
|
+
|
|
2179
|
+
for (let r = 0; r < numRows; r++) {
|
|
2180
|
+
const row = trsArr[r]
|
|
2181
|
+
const tcs = row['a:tc'] || []
|
|
2182
|
+
for (let c = 0; c < numCols; c++) {
|
|
2183
|
+
const cell = tcs[c]
|
|
2184
|
+
if (!cell || cell['@_hMerge'] || cell['@_vMerge']) continue
|
|
2185
|
+
|
|
2186
|
+
const parent = this.getMergeParent(slideIndex, tableId, r, c, slideManager)
|
|
2187
|
+
const gridSpan = cell['@_gridSpan'] ? parseInt(cell['@_gridSpan'], 10) : 1
|
|
2188
|
+
|
|
2189
|
+
// Calculate cell width
|
|
2190
|
+
let cellWidth = 0
|
|
2191
|
+
for (let idx = 0; idx < gridSpan; idx++) {
|
|
2192
|
+
cellWidth += colWidths[parent.col + idx] || 0
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
const { marL, marR, marT, marB } = getCellMargins(cell)
|
|
2196
|
+
const availWidth = cellWidth - marL - marR
|
|
2197
|
+
const availWidth_px = Math.max(1, availWidth / 9525)
|
|
2198
|
+
|
|
2199
|
+
// Calculate text height
|
|
2200
|
+
const txBody = cell['a:txBody']
|
|
2201
|
+
let textHeight_emu = 0
|
|
2202
|
+
if (txBody) {
|
|
2203
|
+
const paras = Array.isArray(txBody['a:p']) ? txBody['a:p'] : [txBody['a:p']]
|
|
2204
|
+
for (const p of paras) {
|
|
2205
|
+
const fontSize = getParagraphFontSize(p)
|
|
2206
|
+
let pText = ''
|
|
2207
|
+
if (p['a:r']) {
|
|
2208
|
+
const runs = Array.isArray(p['a:r']) ? p['a:r'] : [p['a:r']]
|
|
2209
|
+
for (const r of runs) {
|
|
2210
|
+
if (r['a:t']) {
|
|
2211
|
+
pText += String(r['a:t'])
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
const linesCount = wrapText(pText, availWidth_px, fontSize)
|
|
2217
|
+
const lineHeight_emu = fontSize * 20780 // 1.4 line height multiplier
|
|
2218
|
+
|
|
2219
|
+
let pHeight_emu = linesCount * lineHeight_emu
|
|
2220
|
+
if (p['a:pPr']?.['a:spcBef']?.['a:spcPts']?.['@_val']) {
|
|
2221
|
+
pHeight_emu += parseInt(p['a:pPr']['a:spcBef']['a:spcPts']['@_val'], 10) * 127
|
|
2222
|
+
}
|
|
2223
|
+
if (p['a:pPr']?.['a:spcAft']?.['a:spcPts']?.['@_val']) {
|
|
2224
|
+
pHeight_emu += parseInt(p['a:pPr']['a:spcAft']['a:spcPts']['@_val'], 10) * 127
|
|
2225
|
+
}
|
|
2226
|
+
textHeight_emu += pHeight_emu
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
const totalCellHeight_emu = marT + marB + textHeight_emu
|
|
2231
|
+
cellHeights[r][c] = totalCellHeight_emu
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
// Now resolve row heights based on required cell heights
|
|
2236
|
+
// First, non-vertically-merged cells define row heights directly
|
|
2237
|
+
for (let r = 0; r < numRows; r++) {
|
|
2238
|
+
let maxCellHeight = rowHeights[r] // Start with original template height as floor
|
|
2239
|
+
const row = trsArr[r]
|
|
2240
|
+
const tcs = row['a:tc'] || []
|
|
2241
|
+
for (let c = 0; c < numCols; c++) {
|
|
2242
|
+
const cell = tcs[c]
|
|
2243
|
+
if (!cell || cell['@_vMerge'] || cell['@_hMerge']) continue
|
|
2244
|
+
const rowSpan = cell['@_rowSpan'] ? parseInt(cell['@_rowSpan'], 10) : 1
|
|
2245
|
+
if (rowSpan === 1) {
|
|
2246
|
+
if (cellHeights[r][c] > maxCellHeight) {
|
|
2247
|
+
maxCellHeight = cellHeights[r][c]
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
rowHeights[r] = maxCellHeight
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
// Next, adjust for vertically merged cells (rowSpan > 1)
|
|
2255
|
+
for (let r = 0; r < numRows; r++) {
|
|
2256
|
+
const row = trsArr[r]
|
|
2257
|
+
const tcs = row['a:tc'] || []
|
|
2258
|
+
for (let c = 0; c < numCols; c++) {
|
|
2259
|
+
const cell = tcs[c]
|
|
2260
|
+
if (!cell || cell['@_vMerge'] || cell['@_hMerge']) continue
|
|
2261
|
+
const rowSpan = cell['@_rowSpan'] ? parseInt(cell['@_rowSpan'], 10) : 1
|
|
2262
|
+
if (rowSpan > 1) {
|
|
2263
|
+
const reqHeight = cellHeights[r][c]
|
|
2264
|
+
// Sum currently allocated row heights for spanned rows
|
|
2265
|
+
let currentSpanHeight = 0
|
|
2266
|
+
for (let idx = 0; idx < rowSpan; idx++) {
|
|
2267
|
+
currentSpanHeight += rowHeights[r + idx] || 0
|
|
2268
|
+
}
|
|
2269
|
+
if (reqHeight > currentSpanHeight) {
|
|
2270
|
+
// Distribute the extra required height equally across all spanned rows
|
|
2271
|
+
const diff = reqHeight - currentSpanHeight
|
|
2272
|
+
const extraPerRow = Math.ceil(diff / rowSpan)
|
|
2273
|
+
for (let idx = 0; idx < rowSpan; idx++) {
|
|
2274
|
+
rowHeights[r + idx] += extraPerRow
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
// Update row heights in XML
|
|
2282
|
+
for (let r = 0; r < numRows; r++) {
|
|
2283
|
+
trsArr[r]['@_h'] = String(rowHeights[r])
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
return rowHeights
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
#getNestedHeight(val) {
|
|
2290
|
+
if (Array.isArray(val)) {
|
|
2291
|
+
if (val.length === 0) return 1
|
|
2292
|
+
return val.reduce((sum, item) => sum + this.#getNestedHeight(item), 0)
|
|
2293
|
+
}
|
|
2294
|
+
return 1
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2297
|
+
#expandCellVal(val, targetHeight) {
|
|
2298
|
+
if (!Array.isArray(val)) {
|
|
2299
|
+
const res = []
|
|
2300
|
+
res.push({ value: val !== undefined ? val : '', rowSpan: targetHeight })
|
|
2301
|
+
for (let i = 1; i < targetHeight; i++) {
|
|
2302
|
+
res.push({ vMerge: true })
|
|
2303
|
+
}
|
|
2304
|
+
return res
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
if (val.length === 0) {
|
|
2308
|
+
const res = []
|
|
2309
|
+
res.push({ value: '', rowSpan: targetHeight })
|
|
2310
|
+
for (let i = 1; i < targetHeight; i++) {
|
|
2311
|
+
res.push({ vMerge: true })
|
|
2312
|
+
}
|
|
2313
|
+
return res
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
const itemHeights = val.map(item => this.#getNestedHeight(item))
|
|
2317
|
+
const currentSum = itemHeights.reduce((a, b) => a + b, 0)
|
|
2318
|
+
|
|
2319
|
+
const allocatedHeights = []
|
|
2320
|
+
let remaining = targetHeight
|
|
2321
|
+
for (let i = 0; i < val.length; i++) {
|
|
2322
|
+
const share = Math.round((itemHeights[i] / currentSum) * targetHeight)
|
|
2323
|
+
allocatedHeights.push(share)
|
|
2324
|
+
remaining -= share
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
if (remaining !== 0) {
|
|
2328
|
+
let idx = 0
|
|
2329
|
+
while (remaining > 0) {
|
|
2330
|
+
allocatedHeights[idx % allocatedHeights.length]++
|
|
2331
|
+
remaining--
|
|
2332
|
+
idx++
|
|
2333
|
+
}
|
|
2334
|
+
while (remaining < 0) {
|
|
2335
|
+
let reduced = false
|
|
2336
|
+
for (let i = 0; i < allocatedHeights.length; i++) {
|
|
2337
|
+
const actualIdx = (idx + i) % allocatedHeights.length
|
|
2338
|
+
if (allocatedHeights[actualIdx] > 1) {
|
|
2339
|
+
allocatedHeights[actualIdx]--
|
|
2340
|
+
remaining++
|
|
2341
|
+
reduced = true
|
|
2342
|
+
break
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
if (!reduced) break
|
|
2346
|
+
idx++
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
const result = []
|
|
2351
|
+
for (let i = 0; i < val.length; i++) {
|
|
2352
|
+
result.push(...this.#expandCellVal(val[i], allocatedHeights[i]))
|
|
2353
|
+
}
|
|
2354
|
+
return result
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
#applyAutoMerge(cells) {
|
|
2358
|
+
const result = [...cells]
|
|
2359
|
+
let i = 0
|
|
2360
|
+
while (i < result.length) {
|
|
2361
|
+
const cell = result[i]
|
|
2362
|
+
if (cell.vMerge) {
|
|
2363
|
+
i++
|
|
2364
|
+
continue
|
|
2365
|
+
}
|
|
2366
|
+
let count = 1
|
|
2367
|
+
let j = i + 1
|
|
2368
|
+
while (
|
|
2369
|
+
j < result.length &&
|
|
2370
|
+
!result[j].vMerge &&
|
|
2371
|
+
result[j].value === cell.value &&
|
|
2372
|
+
cell.value !== ''
|
|
2373
|
+
) {
|
|
2374
|
+
count++
|
|
2375
|
+
j++
|
|
2376
|
+
}
|
|
2377
|
+
if (count > 1) {
|
|
2378
|
+
cell.rowSpan = count
|
|
2379
|
+
for (let k = i + 1; k < j; k++) {
|
|
2380
|
+
result[k] = { vMerge: true }
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
i = j
|
|
2384
|
+
}
|
|
2385
|
+
return result
|
|
2386
|
+
}
|
|
2387
|
+
|
|
1789
2388
|
#generateRandomUint32() {
|
|
1790
2389
|
return Math.floor(Math.random() * 4294967296)
|
|
1791
2390
|
}
|