node-pptx-templater 1.0.21 → 1.1.1

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.
@@ -40,7 +40,7 @@ const { ZOrderManager } = require('../managers/ZOrderManager.js')
40
40
  const { ValidationEngine } = require('./ValidationEngine.js')
41
41
  const { OutputWriter } = require('./OutputWriter.js')
42
42
  const { TemplateEngine } = require('./TemplateEngine.js')
43
- const { createLogger } = require('../utils/logger.js')
43
+ const { createLogger, setGlobalLogLevel } = require('../utils/logger.js')
44
44
  const { PPTXError } = require('../utils/errors.js')
45
45
  const { performance } = require('perf_hooks')
46
46
 
@@ -217,12 +217,46 @@ class PPTXTemplater {
217
217
  * const buffer = fs.readFileSync('./template.pptx');
218
218
  * const ppt = await PPTXTemplater.load(buffer);
219
219
  */
220
- static async load(source) {
220
+ static async load(source, options = {}) {
221
+ if (options.logLevel) {
222
+ setGlobalLogLevel(options.logLevel)
223
+ }
221
224
  const engine = new PPTXTemplater()
222
225
  await engine.#initialize(source)
223
226
  return engine
224
227
  }
225
228
 
229
+ /**
230
+ * Sets the global log level for all PPTXTemplater logger instances.
231
+ * This is equivalent to setting the PPTX_LOG_LEVEL environment variable,
232
+ * but works at runtime without restarting the process.
233
+ *
234
+ * @static
235
+ * @param {'verbose'|'debug'|'info'|'warn'|'error'|'silent'} level - Log level.
236
+ * @returns {void}
237
+ *
238
+ * @example
239
+ * PPTXTemplater.setLogLevel('debug'); // Enable full debug output
240
+ * PPTXTemplater.setLogLevel('silent'); // Suppress all output
241
+ */
242
+ static setLogLevel(level) {
243
+ setGlobalLogLevel(level)
244
+ }
245
+
246
+ /**
247
+ * Preloads a PPTX template into an in-memory cache for fast repeated generation.
248
+ * Call this once at startup; subsequent `fromCache()` calls read from memory
249
+ * with zero disk I/O.
250
+ *
251
+ * @static
252
+ * @param {string|Buffer|Object} source - File path, Buffer, or folder object.
253
+ * @returns {Promise<Map>} The cache Map keyed by file paths.
254
+ *
255
+ * @example
256
+ * await PPTXTemplater.preload('./template.pptx');
257
+ * // Later, in request handlers:
258
+ * const ppt = await PPTXTemplater.fromCache('./template.pptx');
259
+ */
226
260
  static async preload(source) {
227
261
  let key = source
228
262
  if (Buffer.isBuffer(source)) {
@@ -256,10 +290,33 @@ class PPTXTemplater {
256
290
  return cachedFiles
257
291
  }
258
292
 
293
+ /**
294
+ * Alias for `preload()`. Caches a PPTX template in memory.
295
+ *
296
+ * @static
297
+ * @param {string|Buffer|Object} source - File path, Buffer, or folder object.
298
+ * @returns {Promise<Map>} The cache Map.
299
+ *
300
+ * @example
301
+ * await PPTXTemplater.cache('./template.pptx');
302
+ */
259
303
  static async cache(source) {
260
304
  return PPTXTemplater.preload(source)
261
305
  }
262
306
 
307
+ /**
308
+ * Creates an engine instance from a previously preloaded cache entry.
309
+ * Falls back to preloading if the source has not been cached yet.
310
+ *
311
+ * @static
312
+ * @param {string|Buffer|Object} source - Same source used with `preload()`.
313
+ * @returns {Promise<PPTXTemplater>} Initialized engine from cache.
314
+ *
315
+ * @example
316
+ * await PPTXTemplater.preload('./template.pptx');
317
+ * const ppt = await PPTXTemplater.fromCache('./template.pptx');
318
+ * // ppt is ready — no disk I/O on the load
319
+ */
263
320
  static async fromCache(source) {
264
321
  let key = source
265
322
  if (Buffer.isBuffer(source)) {
@@ -279,16 +336,66 @@ class PPTXTemplater {
279
336
  return engine
280
337
  }
281
338
 
339
+ /**
340
+ * Clears all preloaded templates from the in-memory cache.
341
+ * Call this to free memory or force templates to be reloaded from disk.
342
+ *
343
+ * @static
344
+ * @returns {void}
345
+ *
346
+ * @example
347
+ * PPTXTemplater.clearCache(); // Force fresh reload on next fromCache()
348
+ */
282
349
  static clearCache() {
283
350
  PPTXTemplater.#templateCache.clear()
284
351
  }
285
352
 
353
+ /**
354
+ * Enables internal performance profiling.
355
+ * After calling this, use `getPerformanceMetrics()` to read timing data.
356
+ *
357
+ * @returns {PPTXTemplater} this (chainable)
358
+ *
359
+ * @example
360
+ * ppt.enablePerformanceProfile();
361
+ * // ... do work ...
362
+ * console.log(ppt.getPerformanceMetrics());
363
+ */
286
364
  enablePerformanceProfile() {
287
365
  this.#profiler.enabled = true
288
366
  this.#profiler.startTime = performance.now()
289
367
  return this
290
368
  }
291
369
 
370
+ /**
371
+ * Enables debug-level logging for this session.
372
+ * Shortcut for `PPTXTemplater.setLogLevel('debug')`.
373
+ *
374
+ * @returns {PPTXTemplater} this (chainable)
375
+ *
376
+ * @example
377
+ * const ppt = await PPTXTemplater.load('./template.pptx');
378
+ * ppt.enableDebug(); // Shows all debug output
379
+ */
380
+ enableDebug() {
381
+ setGlobalLogLevel('debug')
382
+ return this
383
+ }
384
+
385
+ /**
386
+ * Returns performance metrics collected since `enablePerformanceProfile()` was called.
387
+ * Includes timing for template load, XML parse, chart update, image update,
388
+ * ZIP generation, total elapsed time, and memory usage.
389
+ *
390
+ * @returns {Object} Metrics object with `templateLoadMs`, `parseMs`, `chartUpdateMs`,
391
+ * `imageUpdateMs`, `zipGenerationMs`, `totalMs`, `memoryUsedMB`.
392
+ *
393
+ * @example
394
+ * ppt.enablePerformanceProfile();
395
+ * await ppt.updateChart('chart1', data);
396
+ * const metrics = ppt.getPerformanceMetrics();
397
+ * console.log(`Total: ${metrics.totalMs}ms, Memory: ${metrics.memoryUsedMB}MB`);
398
+ */
292
399
  getPerformanceMetrics() {
293
400
  if (!this.#profiler.enabled) {
294
401
  return {
@@ -1030,13 +1137,13 @@ class PPTXTemplater {
1030
1137
  debugRelationships() {
1031
1138
  this.#assertLoaded()
1032
1139
  const files = this.#zipManager.listFiles('').filter(f => f.endsWith('.rels'))
1033
- console.log('=== Relationship Graph ===')
1140
+ logger.info('=== Relationship Graph ===')
1034
1141
  for (const file of files) {
1035
- console.log(`\n${file}:`)
1142
+ logger.info(`\n${file}:`)
1036
1143
  const rels = this.#relationshipManager.getRelationships(
1037
1144
  file.replace('_rels/', '').replace('.rels', '')
1038
1145
  )
1039
- rels.forEach(r => console.log(` - ${r.id} [${r.type.split('/').pop()}] -> ${r.target}`))
1146
+ rels.forEach(r => logger.info(` - ${r.id} [${r.type.split('/').pop()}] -> ${r.target}`))
1040
1147
  }
1041
1148
  return this
1042
1149
  }
@@ -1052,14 +1159,14 @@ class PPTXTemplater {
1052
1159
  const xml = this.#slideManager.getSlideXml(slideIndex)
1053
1160
  const rels = this.#relationshipManager.getRelationships(info.zipPath)
1054
1161
 
1055
- console.log(`=== Slide ${slideIndex} Inspection ===`)
1056
- console.log(`Path: ${info.zipPath}`)
1057
- console.log(`ID: ${info.slideId}`)
1058
- console.log(`rId: ${info.relationshipId}`)
1059
- console.log(`Title: ${info.title}`)
1060
- console.log(`XML Size: ${xml.length} characters`)
1061
- console.log(`Relationships (${rels.length}):`)
1062
- rels.forEach(r => console.log(` - ${r.id} [${r.type.split('/').pop()}] -> ${r.target}`))
1162
+ logger.info(`=== Slide ${slideIndex} Inspection ===`)
1163
+ logger.info(`Path: ${info.zipPath}`)
1164
+ logger.info(`ID: ${info.slideId}`)
1165
+ logger.info(`rId: ${info.relationshipId}`)
1166
+ logger.info(`Title: ${info.title}`)
1167
+ logger.info(`XML Size: ${xml.length} characters`)
1168
+ logger.info(`Relationships (${rels.length}):`)
1169
+ rels.forEach(r => logger.info(` - ${r.id} [${r.type.split('/').pop()}] -> ${r.target}`))
1063
1170
 
1064
1171
  return this
1065
1172
  }
@@ -1072,11 +1179,11 @@ class PPTXTemplater {
1072
1179
  async inspectXML(xmlPath) {
1073
1180
  this.#assertLoaded()
1074
1181
  const xml = await this.#zipManager.readFile(xmlPath)
1075
- console.log(`=== XML Inspection: ${xmlPath} ===`)
1182
+ logger.info(`=== XML Inspection: ${xmlPath} ===`)
1076
1183
  if (!xml) {
1077
- console.log('(File not found or empty)')
1184
+ logger.info('(File not found or empty)')
1078
1185
  } else {
1079
- console.log(xml.substring(0, 1500) + (xml.length > 1500 ? '...\n[Truncated]' : ''))
1186
+ logger.info(xml.substring(0, 1500) + (xml.length > 1500 ? '...\n[Truncated]' : ''))
1080
1187
  }
1081
1188
  return this
1082
1189
  }
@@ -1163,8 +1270,7 @@ class PPTXTemplater {
1163
1270
  */
1164
1271
  inspectChart(chartId) {
1165
1272
  this.#assertLoaded()
1166
- console.log(`=== Chart Inspection: ${chartId} ===`)
1167
- // Find chart across all slides to get info
1273
+ logger.info(`=== Chart Inspection: ${chartId} ===`)
1168
1274
  let found = false
1169
1275
  for (const i of this.#slideManager.getAllSlideIndices()) {
1170
1276
  try {
@@ -1177,15 +1283,15 @@ class PPTXTemplater {
1177
1283
  c => c.zipPath.toLowerCase().includes(chartId.toLowerCase()) || c.rId === chartId
1178
1284
  )
1179
1285
  if (chart) {
1180
- console.log(`Found on Slide ${i}`)
1181
- console.log(`ZIP Path: ${chart.zipPath}`)
1182
- console.log(`Relationship ID: ${chart.rId}`)
1286
+ logger.info(`Found on Slide ${i}`)
1287
+ logger.info(`ZIP Path: ${chart.zipPath}`)
1288
+ logger.info(`Relationship ID: ${chart.rId}`)
1183
1289
  found = true
1184
1290
  break
1185
1291
  }
1186
1292
  } catch (e) {}
1187
1293
  }
1188
- if (!found) console.log('Chart not found.')
1294
+ if (!found) logger.info('Chart not found.')
1189
1295
  return this
1190
1296
  }
1191
1297
 
@@ -1205,15 +1311,15 @@ class PPTXTemplater {
1205
1311
  */
1206
1312
  debugChartRelationships() {
1207
1313
  this.#assertLoaded()
1208
- console.log('=== Chart Relationships ===')
1314
+ logger.info('=== Chart Relationships ===')
1209
1315
  const chartFiles = this.#zipManager.listFiles('ppt/charts/').filter(f => {
1210
1316
  const name = f.split('/').pop()
1211
1317
  return name.startsWith('chart') && name.endsWith('.xml') && !f.includes('_rels')
1212
1318
  })
1213
1319
  for (const chartPath of chartFiles) {
1214
- console.log(`\n${chartPath}:`)
1320
+ logger.info(`\n${chartPath}:`)
1215
1321
  const rels = this.#relationshipManager.getRelationships(chartPath)
1216
- rels.forEach(r => console.log(` - ${r.id} [${r.type.split('/').pop()}] -> ${r.target}`))
1322
+ rels.forEach(r => logger.info(` - ${r.id} [${r.type.split('/').pop()}] -> ${r.target}`))
1217
1323
  }
1218
1324
  return this
1219
1325
  }
@@ -1345,24 +1451,64 @@ class PPTXTemplater {
1345
1451
  }
1346
1452
 
1347
1453
  // === Slide Features ===
1454
+ /**
1455
+ * Duplicates an existing slide and inserts the copy at the specified position.
1456
+ *
1457
+ * @param {number} slideIndex - 1-based index of the slide to duplicate.
1458
+ * @param {number} [atPosition] - 1-based position to insert the copy. Defaults to end.
1459
+ * @returns {PPTXTemplater} this (chainable)
1460
+ *
1461
+ * @example
1462
+ * ppt.duplicateSlide(1, 2); // Copy slide 1 and insert it as slide 2
1463
+ */
1348
1464
  duplicateSlide(slideIndex, atPosition) {
1349
1465
  this.#assertLoaded()
1350
1466
  this.#slideManager.duplicateSlide(slideIndex, atPosition, this.#relationshipManager)
1351
1467
  return this
1352
1468
  }
1353
1469
 
1470
+ /**
1471
+ * Removes a slide from the presentation. Alias for `removeSlide()`.
1472
+ *
1473
+ * @param {number} slideIndex - 1-based index of the slide to delete.
1474
+ * @returns {PPTXTemplater} this (chainable)
1475
+ *
1476
+ * @example
1477
+ * ppt.deleteSlide(3); // Remove slide 3
1478
+ */
1354
1479
  deleteSlide(slideIndex) {
1355
1480
  this.#assertLoaded()
1356
1481
  this.#slideManager.removeSlide(slideIndex)
1357
1482
  return this
1358
1483
  }
1359
1484
 
1485
+ /**
1486
+ * Moves a slide from one position to another within the presentation.
1487
+ *
1488
+ * @param {number} fromIndex - 1-based source slide index.
1489
+ * @param {number} toIndex - 1-based target slide index.
1490
+ * @returns {PPTXTemplater} this (chainable)
1491
+ *
1492
+ * @example
1493
+ * ppt.moveSlide(5, 1); // Move slide 5 to position 1 (make it first)
1494
+ */
1360
1495
  moveSlide(fromIndex, toIndex) {
1361
1496
  this.#assertLoaded()
1362
1497
  this.#slideManager.moveSlide(fromIndex, toIndex)
1363
1498
  return this
1364
1499
  }
1365
1500
 
1501
+ /**
1502
+ * Inserts a new blank slide at the specified position.
1503
+ *
1504
+ * @param {number} slideIndex - 1-based position to insert the slide at.
1505
+ * @param {Object} [options] - Insert options.
1506
+ * @param {number} [options.layoutIndex] - Slide layout index to apply.
1507
+ * @returns {PPTXTemplater} this (chainable)
1508
+ *
1509
+ * @example
1510
+ * ppt.insertSlide(2, { layoutIndex: 1 }); // Insert blank slide at position 2
1511
+ */
1366
1512
  insertSlide(slideIndex, options = {}) {
1367
1513
  this.#assertLoaded()
1368
1514
  this.#slideManager.insertSlide(
@@ -1374,12 +1520,44 @@ class PPTXTemplater {
1374
1520
  return this
1375
1521
  }
1376
1522
 
1523
+ /**
1524
+ * Returns an array of all slides in the presentation with their metadata.
1525
+ *
1526
+ * @returns {Array<{index: number, slideId: string, title: string, zipPath: string}>}
1527
+ *
1528
+ * @example
1529
+ * const slides = ppt.getSlides();
1530
+ * slides.forEach(s => console.log(`Slide ${s.index}: ${s.title}`));
1531
+ */
1377
1532
  getSlides() {
1378
1533
  this.#assertLoaded()
1379
1534
  return this.#slideManager.getSlides()
1380
1535
  }
1381
1536
 
1382
1537
  // === Table Features ===
1538
+ /**
1539
+ * Extracts table data from the active slide as structured JSON.
1540
+ * The first row of the table is treated as the header row.
1541
+ *
1542
+ * @param {string} tableId - Table name or shape ID.
1543
+ * @param {Object} [options] - Extraction options.
1544
+ * @param {boolean} [options.raw=false] - Return `string[][]` instead of object array.
1545
+ * @param {boolean} [options.includeMetadata=false] - Return `{rows, rowCount, columnCount, mergedCells}`.
1546
+ * @returns {Array<Object>|Array<Array<string>>|Object} Extracted table data.
1547
+ *
1548
+ * @example
1549
+ * // Object mode (default)
1550
+ * const rows = await ppt.getTableRows('SalesTable');
1551
+ * // → [{ region: 'North', sales: '1200' }, ...]
1552
+ *
1553
+ * // Raw mode
1554
+ * const raw = await ppt.getTableRows('SalesTable', { raw: true });
1555
+ * // → [['North', '1200'], ...]
1556
+ *
1557
+ * // Metadata mode
1558
+ * const meta = await ppt.getTableRows('SalesTable', { includeMetadata: true });
1559
+ * // → { rows: [...], rowCount: 5, columnCount: 3, mergedCells: [] }
1560
+ */
1383
1561
  getTableRows(tableId, options = {}) {
1384
1562
  this.#assertLoaded()
1385
1563
  const targetIndices = this.#getTargetSlideIndices()
@@ -1390,6 +1568,25 @@ class PPTXTemplater {
1390
1568
  return this.#tableManager.getTableRows(idx, tableId, options, this.#slideManager)
1391
1569
  }
1392
1570
 
1571
+ /**
1572
+ * Appends one or more rows to a table. Supports flat arrays and nested arrays
1573
+ * for rowspan-merged cells.
1574
+ *
1575
+ * @param {string} tableId - Table name or shape ID.
1576
+ * @param {Array<string|Array<string>>} rowData - Row data. Nested arrays create rowspan cells.
1577
+ * @param {Object} [options] - Row insertion options.
1578
+ * @param {'rowspan'|'auto'|'none'} [options.mergeStrategy='rowspan'] - How to handle nested arrays.
1579
+ * `'rowspan'` creates OpenXML vertical spans, `'auto'` merges identical adjacent values,
1580
+ * `'none'` expands nested arrays into multiple flat rows.
1581
+ * @returns {PPTXTemplater} this (chainable)
1582
+ *
1583
+ * @example
1584
+ * // Simple flat row
1585
+ * ppt.addTableRow('SalesTable', ['North', '1200', '15%']);
1586
+ *
1587
+ * // Nested row with rowspan
1588
+ * ppt.addTableRow('SalesTable', ['Region', ['Q1', 'Q2'], '$5K'], { mergeStrategy: 'rowspan' });
1589
+ */
1393
1590
  addTableRow(tableId, rowData, options = {}) {
1394
1591
  this.#assertLoaded()
1395
1592
  const targetIndices = this.#getTargetSlideIndices()
@@ -1399,6 +1596,16 @@ class PPTXTemplater {
1399
1596
  return this
1400
1597
  }
1401
1598
 
1599
+ /**
1600
+ * Removes a row from a table by its 0-based row index.
1601
+ *
1602
+ * @param {string} tableId - Table name or shape ID.
1603
+ * @param {number} rowIndex - 0-based row index to remove.
1604
+ * @returns {PPTXTemplater} this (chainable)
1605
+ *
1606
+ * @example
1607
+ * ppt.removeTableRow('SalesTable', 2); // Remove the third row (0-based)
1608
+ */
1402
1609
  removeTableRow(tableId, rowIndex) {
1403
1610
  this.#assertLoaded()
1404
1611
  const targetIndices = this.#getTargetSlideIndices()
@@ -1408,6 +1615,17 @@ class PPTXTemplater {
1408
1615
  return this
1409
1616
  }
1410
1617
 
1618
+ /**
1619
+ * Inserts a new row at the specified 0-based index, shifting existing rows down.
1620
+ *
1621
+ * @param {string} tableId - Table name or shape ID.
1622
+ * @param {number} rowIndex - 0-based index at which to insert the new row.
1623
+ * @param {Array<string>} rowData - Cell values for the new row.
1624
+ * @returns {PPTXTemplater} this (chainable)
1625
+ *
1626
+ * @example
1627
+ * ppt.insertTableRow('SalesTable', 1, ['East', '980', '8%']); // Insert at row 1
1628
+ */
1411
1629
  insertTableRow(tableId, rowIndex, rowData) {
1412
1630
  this.#assertLoaded()
1413
1631
  const targetIndices = this.#getTargetSlideIndices()
@@ -1417,6 +1635,17 @@ class PPTXTemplater {
1417
1635
  return this
1418
1636
  }
1419
1637
 
1638
+ /**
1639
+ * Clones a row and inserts the copy at a target position.
1640
+ *
1641
+ * @param {string} tableId - Table name or shape ID.
1642
+ * @param {number} sourceRowIndex - 0-based index of the row to clone.
1643
+ * @param {number} targetRowIndex - 0-based index where the clone is inserted.
1644
+ * @returns {PPTXTemplater} this (chainable)
1645
+ *
1646
+ * @example
1647
+ * ppt.cloneTableRow('SalesTable', 0, 3); // Clone row 0 to position 3
1648
+ */
1420
1649
  cloneTableRow(tableId, sourceRowIndex, targetRowIndex) {
1421
1650
  this.#assertLoaded()
1422
1651
  const targetIndices = this.#getTargetSlideIndices()
@@ -1432,6 +1661,25 @@ class PPTXTemplater {
1432
1661
  return this
1433
1662
  }
1434
1663
 
1664
+ /**
1665
+ * Updates the text and optional formatting of a single table cell.
1666
+ *
1667
+ * @param {string} tableId - Table name or shape ID.
1668
+ * @param {number} rowIndex - 0-based row index.
1669
+ * @param {number} colIndex - 0-based column index.
1670
+ * @param {string} value - New cell text content.
1671
+ * @param {Object} [options] - Cell formatting options.
1672
+ * @param {boolean} [options.bold] - Bold text.
1673
+ * @param {boolean} [options.italic] - Italic text.
1674
+ * @param {number} [options.fontSize] - Font size in points.
1675
+ * @param {'left'|'center'|'right'} [options.align] - Text alignment.
1676
+ * @param {string} [options.fill] - Cell background color (hex, e.g. '#FF0000').
1677
+ * @param {string} [options.color] - Text color (hex).
1678
+ * @returns {PPTXTemplater} this (chainable)
1679
+ *
1680
+ * @example
1681
+ * ppt.updateCell('SalesTable', 1, 2, '$9,800', { bold: true, color: '#10B981' });
1682
+ */
1435
1683
  updateCell(tableId, rowIndex, colIndex, value, options = {}) {
1436
1684
  this.#assertLoaded()
1437
1685
  const targetIndices = this.#getTargetSlideIndices()
@@ -1449,6 +1697,22 @@ class PPTXTemplater {
1449
1697
  return this
1450
1698
  }
1451
1699
 
1700
+ /**
1701
+ * Merges a rectangular region of table cells into a single merged cell.
1702
+ * Supports both positional arguments and an options object.
1703
+ *
1704
+ * @param {string|Object} tableIdOrOptions - Table ID string, or options object with all fields.
1705
+ * @param {number} [startRow] - 0-based start row.
1706
+ * @param {number} [startCol] - 0-based start column.
1707
+ * @param {number} [endRow] - 0-based end row (inclusive).
1708
+ * @param {number} [endCol] - 0-based end column (inclusive).
1709
+ * @returns {PPTXTemplater} this (chainable)
1710
+ *
1711
+ * @example
1712
+ * ppt.mergeCells('SalesTable', 0, 0, 0, 2); // Merge first 3 cells of row 0
1713
+ * // Or with options object:
1714
+ * ppt.mergeCells({ tableId: 'SalesTable', startRow: 0, startCol: 0, endRow: 0, endCol: 2 });
1715
+ */
1452
1716
  mergeCells(tableIdOrOptions, startRow, startCol, endRow, endCol) {
1453
1717
  this.#assertLoaded()
1454
1718
  let tableId = tableIdOrOptions
@@ -1476,6 +1740,22 @@ class PPTXTemplater {
1476
1740
  return this
1477
1741
  }
1478
1742
 
1743
+ /**
1744
+ * Unmerges (splits) a previously merged cell region.
1745
+ * Supports both positional arguments and an options object.
1746
+ *
1747
+ * @param {string|Object} tableIdOrOptions - Table ID string, or options object.
1748
+ * @param {number} [startRow] - 0-based start row of the merged region.
1749
+ * @param {number} [startCol] - 0-based start column.
1750
+ * @param {number} [endRow] - 0-based end row.
1751
+ * @param {number} [endCol] - 0-based end column.
1752
+ * @returns {PPTXTemplater} this (chainable)
1753
+ *
1754
+ * @example
1755
+ * ppt.unmergeCells('SalesTable', 0, 0, 0, 2); // Unmerge region
1756
+ * // Via cell coordinate:
1757
+ * ppt.unmergeCells({ tableId: 'SalesTable', row: 0, col: 0 });
1758
+ */
1479
1759
  unmergeCells(tableIdOrOptions, startRow, startCol, endRow, endCol) {
1480
1760
  this.#assertLoaded()
1481
1761
  let tableId = tableIdOrOptions
@@ -1514,12 +1794,37 @@ class PPTXTemplater {
1514
1794
  return this
1515
1795
  }
1516
1796
 
1797
+ /**
1798
+ * Returns an array of all merged cell regions in a table.
1799
+ *
1800
+ * @param {string} [tableId] - Table name or shape ID. Defaults to the first table found.
1801
+ * @returns {Array<{startRow: number, startCol: number, endRow: number, endCol: number}>}
1802
+ *
1803
+ * @example
1804
+ * const merges = ppt.getMergedCells('SalesTable');
1805
+ * merges.forEach(m => console.log(`Merged: row ${m.startRow}-${m.endRow}, col ${m.startCol}-${m.endCol}`));
1806
+ */
1517
1807
  getMergedCells(tableId) {
1518
1808
  this.#assertLoaded()
1519
1809
  const slideIndex = this.#getTargetSlideIndices()[0] || 1
1520
1810
  return this.#tableManager.getMergedCells(slideIndex, tableId || 'first', this.#slideManager)
1521
1811
  }
1522
1812
 
1813
+ /**
1814
+ * Validates whether a merge region is valid for the given table dimensions.
1815
+ * Checks for overlapping merges, out-of-bounds coordinates, etc.
1816
+ *
1817
+ * @param {string} tableId - Table name or shape ID.
1818
+ * @param {number} startRow - 0-based start row.
1819
+ * @param {number} startCol - 0-based start column.
1820
+ * @param {number} endRow - 0-based end row.
1821
+ * @param {number} endCol - 0-based end column.
1822
+ * @returns {{valid: boolean, errors: string[]}} Validation result.
1823
+ *
1824
+ * @example
1825
+ * const result = ppt.validateMergeRegion('SalesTable', 0, 0, 1, 2);
1826
+ * if (!result.valid) console.error(result.errors);
1827
+ */
1523
1828
  validateMergeRegion(tableId, startRow, startCol, endRow, endCol) {
1524
1829
  this.#assertLoaded()
1525
1830
  const slideIndex = this.#getTargetSlideIndices()[0] || 1
@@ -1534,6 +1839,19 @@ class PPTXTemplater {
1534
1839
  )
1535
1840
  }
1536
1841
 
1842
+ /**
1843
+ * Checks whether a specific table cell is part of a merged region.
1844
+ *
1845
+ * @param {string} tableId - Table name or shape ID.
1846
+ * @param {number} row - 0-based row index.
1847
+ * @param {number} col - 0-based column index.
1848
+ * @returns {boolean} `true` if the cell is merged (parent or continuation).
1849
+ *
1850
+ * @example
1851
+ * if (ppt.isMergedCell('SalesTable', 0, 0)) {
1852
+ * console.log('Cell is part of a merged region');
1853
+ * }
1854
+ */
1537
1855
  isMergedCell(tableId, row, col) {
1538
1856
  this.#assertLoaded()
1539
1857
  const slideIndex = this.#getTargetSlideIndices()[0] || 1
@@ -1546,6 +1864,19 @@ class PPTXTemplater {
1546
1864
  )
1547
1865
  }
1548
1866
 
1867
+ /**
1868
+ * Returns the anchor (parent) cell coordinates of a merged region
1869
+ * that contains the given cell.
1870
+ *
1871
+ * @param {string} tableId - Table name or shape ID.
1872
+ * @param {number} row - 0-based row index of any cell in the merged region.
1873
+ * @param {number} col - 0-based column index.
1874
+ * @returns {{row: number, col: number}|null} Anchor cell coordinates, or null if not merged.
1875
+ *
1876
+ * @example
1877
+ * const parent = ppt.getMergeParent('SalesTable', 0, 1);
1878
+ * // → { row: 0, col: 0 } if cells (0,0)-(0,2) are merged
1879
+ */
1549
1880
  getMergeParent(tableId, row, col) {
1550
1881
  this.#assertLoaded()
1551
1882
  const slideIndex = this.#getTargetSlideIndices()[0] || 1
@@ -1558,6 +1889,18 @@ class PPTXTemplater {
1558
1889
  )
1559
1890
  }
1560
1891
 
1892
+ /**
1893
+ * Returns the full extent of the merged region containing a given cell.
1894
+ *
1895
+ * @param {string} tableId - Table name or shape ID.
1896
+ * @param {number} row - 0-based row index of any cell in the merged region.
1897
+ * @param {number} col - 0-based column index.
1898
+ * @returns {{startRow: number, startCol: number, endRow: number, endCol: number}|null}
1899
+ *
1900
+ * @example
1901
+ * const region = ppt.getMergeRegion('SalesTable', 0, 1);
1902
+ * // → { startRow: 0, startCol: 0, endRow: 0, endCol: 2 }
1903
+ */
1561
1904
  getMergeRegion(tableId, row, col) {
1562
1905
  this.#assertLoaded()
1563
1906
  const slideIndex = this.#getTargetSlideIndices()[0] || 1
@@ -1570,6 +1913,17 @@ class PPTXTemplater {
1570
1913
  )
1571
1914
  }
1572
1915
 
1916
+ /**
1917
+ * Splits a previously merged cell region back into individual cells.
1918
+ *
1919
+ * @param {string} tableId - Table name or shape ID.
1920
+ * @param {number} row - 0-based row of the merged region anchor.
1921
+ * @param {number} col - 0-based column of the merged region anchor.
1922
+ * @returns {PPTXTemplater} this (chainable)
1923
+ *
1924
+ * @example
1925
+ * ppt.splitMergedRegion('SalesTable', 0, 0); // Split merge starting at row 0, col 0
1926
+ */
1573
1927
  splitMergedRegion(tableId, row, col) {
1574
1928
  this.#assertLoaded()
1575
1929
  const slideIndex = this.#getTargetSlideIndices()[0] || 1
@@ -1583,6 +1937,19 @@ class PPTXTemplater {
1583
1937
  return this
1584
1938
  }
1585
1939
 
1940
+ /**
1941
+ * Clones an existing merged region to a new anchor position in the table.
1942
+ *
1943
+ * @param {string} tableId - Table name or shape ID.
1944
+ * @param {number} row - 0-based row of the source merged region.
1945
+ * @param {number} col - 0-based column of the source merged region.
1946
+ * @param {number} targetRow - 0-based target row.
1947
+ * @param {number} targetCol - 0-based target column.
1948
+ * @returns {PPTXTemplater} this (chainable)
1949
+ *
1950
+ * @example
1951
+ * ppt.cloneMergedRegion('SalesTable', 0, 0, 4, 0);
1952
+ */
1586
1953
  cloneMergedRegion(tableId, row, col, targetRow, targetCol) {
1587
1954
  this.#assertLoaded()
1588
1955
  const slideIndex = this.#getTargetSlideIndices()[0] || 1
@@ -1598,6 +1965,15 @@ class PPTXTemplater {
1598
1965
  return this
1599
1966
  }
1600
1967
 
1968
+ /**
1969
+ * Automatically adjusts column widths to fit the content of each cell.
1970
+ *
1971
+ * @param {string} tableId - Table name or shape ID.
1972
+ * @returns {PPTXTemplater} this (chainable)
1973
+ *
1974
+ * @example
1975
+ * ppt.autoFitTable('SalesTable');
1976
+ */
1601
1977
  autoFitTable(tableId) {
1602
1978
  this.#assertLoaded()
1603
1979
  const targetIndices = this.#getTargetSlideIndices()
@@ -1607,6 +1983,18 @@ class PPTXTemplater {
1607
1983
  return this
1608
1984
  }
1609
1985
 
1986
+ /**
1987
+ * Resizes a table to the specified width and height in EMUs.
1988
+ * 1 inch = 914,400 EMUs.
1989
+ *
1990
+ * @param {string} tableId - Table name or shape ID.
1991
+ * @param {number} width - New width in EMUs.
1992
+ * @param {number} height - New height in EMUs.
1993
+ * @returns {PPTXTemplater} this (chainable)
1994
+ *
1995
+ * @example
1996
+ * ppt.resizeTable('SalesTable', 6858000, 1371600); // 7.5" wide × 1.5" tall
1997
+ */
1610
1998
  resizeTable(tableId, width, height) {
1611
1999
  this.#assertLoaded()
1612
2000
  const targetIndices = this.#getTargetSlideIndices()
@@ -1616,6 +2004,15 @@ class PPTXTemplater {
1616
2004
  return this
1617
2005
  }
1618
2006
 
2007
+ /**
2008
+ * Returns metadata for all tables on the targeted slide(s).
2009
+ *
2010
+ * @returns {Array<{name: string, rows: number, cols: number, zipPath: string}>}
2011
+ *
2012
+ * @example
2013
+ * const tables = ppt.getTables();
2014
+ * tables.forEach(t => console.log(`${t.name}: ${t.rows}×${t.cols}`));
2015
+ */
1619
2016
  getTables() {
1620
2017
  this.#assertLoaded()
1621
2018
  const targetIndices = this.#getTargetSlideIndices()
@@ -1627,10 +2024,34 @@ class PPTXTemplater {
1627
2024
  }
1628
2025
 
1629
2026
  // === Chart Features ===
2027
+ /**
2028
+ * Alias for `updateChart()`. Updates chart data for a named chart.
2029
+ *
2030
+ * @param {string} chartId - Chart name or shape ID.
2031
+ * @param {Object} data - Chart data object with `categories` and `series`.
2032
+ * @returns {PPTXTemplater} this (chainable)
2033
+ *
2034
+ * @example
2035
+ * ppt.updateChartData('revenue-chart', {
2036
+ * categories: ['Q1', 'Q2', 'Q3'],
2037
+ * series: [{ name: 'Revenue', values: [100, 150, 200] }]
2038
+ * });
2039
+ */
1630
2040
  updateChartData(chartId, data) {
1631
2041
  return this.updateChart(chartId, data)
1632
2042
  }
1633
2043
 
2044
+ /**
2045
+ * Replaces a specific data series in a chart.
2046
+ *
2047
+ * @param {string} chartId - Chart name or shape ID.
2048
+ * @param {number} seriesIndex - 0-based index of the series to replace.
2049
+ * @param {Object} newSeriesData - New series data `{ name, values }`.
2050
+ * @returns {PPTXTemplater} this (chainable)
2051
+ *
2052
+ * @example
2053
+ * ppt.replaceChartSeries('revenue-chart', 0, { name: 'Revenue', values: [100, 200, 300] });
2054
+ */
1634
2055
  replaceChartSeries(chartId, seriesIndex, newSeriesData) {
1635
2056
  this.#assertLoaded()
1636
2057
  const targetIndices = this.#getTargetSlideIndices()
@@ -1647,6 +2068,16 @@ class PPTXTemplater {
1647
2068
  return this
1648
2069
  }
1649
2070
 
2071
+ /**
2072
+ * Updates only the title text of a chart.
2073
+ *
2074
+ * @param {string} chartId - Chart name or shape ID.
2075
+ * @param {string} title - New chart title.
2076
+ * @returns {PPTXTemplater} this (chainable)
2077
+ *
2078
+ * @example
2079
+ * ppt.updateChartTitle('revenue-chart', 'Q2 2026 Revenue');
2080
+ */
1650
2081
  updateChartTitle(chartId, title) {
1651
2082
  this.#assertLoaded()
1652
2083
  const targetIndices = this.#getTargetSlideIndices()
@@ -1662,6 +2093,16 @@ class PPTXTemplater {
1662
2093
  return this
1663
2094
  }
1664
2095
 
2096
+ /**
2097
+ * Updates only the category labels (X-axis) of a chart, keeping values unchanged.
2098
+ *
2099
+ * @param {string} chartId - Chart name or shape ID.
2100
+ * @param {string[]} categories - Array of category label strings.
2101
+ * @returns {PPTXTemplater} this (chainable)
2102
+ *
2103
+ * @example
2104
+ * ppt.updateChartCategories('revenue-chart', ['Jan', 'Feb', 'Mar', 'Apr']);
2105
+ */
1665
2106
  updateChartCategories(chartId, categories) {
1666
2107
  this.#assertLoaded()
1667
2108
  const targetIndices = this.#getTargetSlideIndices()
@@ -1677,6 +2118,26 @@ class PPTXTemplater {
1677
2118
  return this
1678
2119
  }
1679
2120
 
2121
+ /**
2122
+ * Updates data labels for a specific chart series.
2123
+ * Supports custom arrays, label maps, template strings, and cell references.
2124
+ *
2125
+ * @param {string} chartId - Chart name or shape ID.
2126
+ * @param {Object} options - Data label options.
2127
+ * @param {number} [options.series=0] - 0-based series index.
2128
+ * @param {string[]} [options.labels] - Array of custom label strings.
2129
+ * @param {Object} [options.labelMap] - Map of `{ categoryValue: label }`.
2130
+ * @param {string} [options.template] - Template string with `{value}`, `{category}`, `{percentage}` tokens.
2131
+ * @param {string} [options.labelsFromCells] - Excel cell range (e.g. `'Sheet1!$C$2:$C$6'`).
2132
+ * @param {boolean} [options.showSeriesNameInBar] - Prepend series name to bar chart labels.
2133
+ * @returns {PPTXTemplater} this (chainable)
2134
+ *
2135
+ * @example
2136
+ * ppt.updateDataLabels('revenue-chart', {
2137
+ * series: 0,
2138
+ * template: '{value} ({percentage}%)',
2139
+ * });
2140
+ */
1680
2141
  updateDataLabels(chartId, options) {
1681
2142
  this.#assertLoaded()
1682
2143
  const targetIndices = this.#getTargetSlideIndices()
@@ -1692,6 +2153,18 @@ class PPTXTemplater {
1692
2153
  return this
1693
2154
  }
1694
2155
 
2156
+ /**
2157
+ * Retrieves the current data labels configuration for a specific chart series.
2158
+ *
2159
+ * @param {string} chartId - Chart name or shape ID.
2160
+ * @param {Object} [options] - Options.
2161
+ * @param {number} [options.series=0] - 0-based series index.
2162
+ * @returns {Promise<Object>} Current data label settings.
2163
+ *
2164
+ * @example
2165
+ * const labels = await ppt.getDataLabels('revenue-chart', { series: 0 });
2166
+ * console.log(labels.showValue, labels.position);
2167
+ */
1695
2168
  async getDataLabels(chartId, options = {}) {
1696
2169
  this.#assertLoaded()
1697
2170
  const targetIndices = this.#getTargetSlideIndices()
@@ -1706,6 +2179,17 @@ class PPTXTemplater {
1706
2179
  )
1707
2180
  }
1708
2181
 
2182
+ /**
2183
+ * Validates the data labels configuration for a chart series against the chart XML.
2184
+ *
2185
+ * @param {string} chartId - Chart name or shape ID.
2186
+ * @param {Object} [options] - Options (same as `updateDataLabels`).
2187
+ * @returns {Promise<{valid: boolean, errors: string[]}>}
2188
+ *
2189
+ * @example
2190
+ * const result = await ppt.validateDataLabels('revenue-chart');
2191
+ * if (!result.valid) console.error(result.errors);
2192
+ */
1709
2193
  async validateDataLabels(chartId, options = {}) {
1710
2194
  this.#assertLoaded()
1711
2195
  const targetIndices = this.#getTargetSlideIndices()
@@ -1714,6 +2198,16 @@ class PPTXTemplater {
1714
2198
  return ValidationEngine.validateDataLabels(this, idx, chartId, options)
1715
2199
  }
1716
2200
 
2201
+ /**
2202
+ * Validates chart data labels across all series, including cell reference checks.
2203
+ *
2204
+ * @param {string} chartId - Chart name or shape ID.
2205
+ * @param {Object} [options] - Options.
2206
+ * @returns {Promise<{valid: boolean, errors: string[], warnings: string[]}>}
2207
+ *
2208
+ * @example
2209
+ * const result = await ppt.validateChartLabels('revenue-chart');
2210
+ */
1717
2211
  async validateChartLabels(chartId, options = {}) {
1718
2212
  this.#assertLoaded()
1719
2213
  const targetIndices = this.#getTargetSlideIndices()
@@ -1728,6 +2222,16 @@ class PPTXTemplater {
1728
2222
  )
1729
2223
  }
1730
2224
 
2225
+ /**
2226
+ * Validates series name labels (the labels showing series names inside bar chart bars).
2227
+ *
2228
+ * @param {string} chartId - Chart name or shape ID.
2229
+ * @param {Object} [options] - Options.
2230
+ * @returns {Promise<{valid: boolean, errors: string[], warnings: string[]}>}
2231
+ *
2232
+ * @example
2233
+ * const result = await ppt.validateSeriesNameLabels('bar-chart');
2234
+ */
1731
2235
  async validateSeriesNameLabels(chartId, options = {}) {
1732
2236
  this.#assertLoaded()
1733
2237
  const targetIndices = this.#getTargetSlideIndices()
@@ -1742,6 +2246,15 @@ class PPTXTemplater {
1742
2246
  )
1743
2247
  }
1744
2248
 
2249
+ /**
2250
+ * Returns an array of all charts found on the targeted slide(s).
2251
+ *
2252
+ * @returns {Array<{rId: string, zipPath: string}>} Chart info objects.
2253
+ *
2254
+ * @example
2255
+ * const charts = ppt.getCharts();
2256
+ * charts.forEach(c => console.log(c.zipPath));
2257
+ */
1745
2258
  getCharts() {
1746
2259
  this.#assertLoaded()
1747
2260
  const targetIndices = this.#getTargetSlideIndices()
@@ -1851,6 +2364,21 @@ class PPTXTemplater {
1851
2364
  * @param {string|Object} data - String value or list configuration object.
1852
2365
  * @returns {PPTXTemplater} this (chainable)
1853
2366
  */
2367
+ /**
2368
+ * Updates text content or list items in a named shape or text box.
2369
+ * Supports plain strings, bullet lists, numbered lists, and nested lists.
2370
+ *
2371
+ * @param {string} tag - Shape name/ID or placeholder tag.
2372
+ * @param {string|Object} data - Text string, or list configuration object.
2373
+ * @returns {PPTXTemplater} this (chainable)
2374
+ *
2375
+ * @example
2376
+ * // Plain text
2377
+ * ppt.updateText('SubtitleBox', 'Updated subtitle');
2378
+ *
2379
+ * // Bullet list
2380
+ * ppt.updateText('BulletBox', { items: ['Item A', 'Item B', 'Item C'] });
2381
+ */
1854
2382
  updateText(tag, data) {
1855
2383
  this.#assertLoaded()
1856
2384
  const targetIndices = this.#getTargetSlideIndices()
@@ -1866,6 +2394,16 @@ class PPTXTemplater {
1866
2394
  * @param {string} tag - Shape name/ID or placeholder tag.
1867
2395
  * @returns {Array} Nested list structure of items.
1868
2396
  */
2397
+ /**
2398
+ * Retrieves list items from a shape or text box.
2399
+ *
2400
+ * @param {string} tag - Shape name/ID or placeholder tag.
2401
+ * @returns {Array} Nested list structure of items.
2402
+ *
2403
+ * @example
2404
+ * const items = ppt.getList('BulletBox');
2405
+ * console.log(items); // ['Item A', ['Nested', 'Sub-item'], 'Item B']
2406
+ */
1869
2407
  getList(tag) {
1870
2408
  this.#assertLoaded()
1871
2409
  const targetIndices = this.#getTargetSlideIndices()
@@ -1884,6 +2422,20 @@ class PPTXTemplater {
1884
2422
  }
1885
2423
 
1886
2424
  // === Text Features ===
2425
+ /**
2426
+ * Replaces a text placeholder tag across all targeted shapes on selected slides.
2427
+ * Finds shapes containing `{{tag}}` or `tag` and replaces the placeholder value.
2428
+ *
2429
+ * @param {string} tag - Placeholder tag name (e.g. `'{{name}}'` or `'name'`).
2430
+ * @param {string} value - Replacement value.
2431
+ * @param {Object} [options] - Options.
2432
+ * @param {number} [options.slide] - Target a specific slide index (overrides `useSlide`).
2433
+ * @returns {PPTXTemplater} this (chainable)
2434
+ *
2435
+ * @example
2436
+ * ppt.replaceTextByTag('{{company}}', 'Acme Corp');
2437
+ * ppt.replaceTextByTag('{{date}}', '2026-06-12');
2438
+ */
1887
2439
  replaceTextByTag(tag, value, options = {}) {
1888
2440
  this.#assertLoaded()
1889
2441
  const targetIndices = this.#getTargetSlideIndices()
@@ -1900,6 +2452,21 @@ class PPTXTemplater {
1900
2452
  return this
1901
2453
  }
1902
2454
 
2455
+ /**
2456
+ * Replaces multiple text placeholder tags in a single pass.
2457
+ * More efficient than calling `replaceTextByTag()` repeatedly.
2458
+ *
2459
+ * @param {Object<string, string>} replacements - Map of `{ tag: value }` pairs.
2460
+ * @param {Object} [options] - Options (same as `replaceTextByTag`).
2461
+ * @returns {PPTXTemplater} this (chainable)
2462
+ *
2463
+ * @example
2464
+ * ppt.replaceMultiple({
2465
+ * '{{title}}': 'Q2 Report',
2466
+ * '{{date}}': 'June 2026',
2467
+ * '{{company}}': 'Acme Corp'
2468
+ * });
2469
+ */
1903
2470
  replaceMultiple(replacements, options = {}) {
1904
2471
  this.#assertLoaded()
1905
2472
  const targetIndices = this.#getTargetSlideIndices()
@@ -1915,6 +2482,16 @@ class PPTXTemplater {
1915
2482
  return this
1916
2483
  }
1917
2484
 
2485
+ /**
2486
+ * Searches for all occurrences of a text string across the targeted slides.
2487
+ *
2488
+ * @param {string} text - Text to search for.
2489
+ * @returns {Array<{slideIndex: number, shapeName: string, text: string}>} Array of matches.
2490
+ *
2491
+ * @example
2492
+ * const matches = ppt.findText('Revenue');
2493
+ * matches.forEach(m => console.log(`Found on slide ${m.slideIndex} in "${m.shapeName}"`));
2494
+ */
1918
2495
  findText(text) {
1919
2496
  this.#assertLoaded()
1920
2497
  const targetIndices = this.#getTargetSlideIndices()
@@ -1925,6 +2502,15 @@ class PPTXTemplater {
1925
2502
  return matches
1926
2503
  }
1927
2504
 
2505
+ /**
2506
+ * Returns all text elements (paragraphs) across the targeted slides.
2507
+ *
2508
+ * @returns {Array<{slideIndex: number, shapeName: string, text: string}>}
2509
+ *
2510
+ * @example
2511
+ * const elements = ppt.getTextElements();
2512
+ * elements.forEach(el => console.log(`Slide ${el.slideIndex}: ${el.text}`))
2513
+ */
1928
2514
  getTextElements() {
1929
2515
  this.#assertLoaded()
1930
2516
  const targetIndices = this.#getTargetSlideIndices()
@@ -1936,6 +2522,16 @@ class PPTXTemplater {
1936
2522
  }
1937
2523
 
1938
2524
  // === Shape Features ===
2525
+ /**
2526
+ * Sets the text content of an existing shape by name or ID.
2527
+ *
2528
+ * @param {string} shapeId - Shape name or ID.
2529
+ * @param {string} text - New text content.
2530
+ * @returns {PPTXTemplater} this (chainable)
2531
+ *
2532
+ * @example
2533
+ * ppt.updateShapeText('CalloutBox', 'Important note here');
2534
+ */
1939
2535
  updateShapeText(shapeId, text) {
1940
2536
  this.#assertLoaded()
1941
2537
  const targetIndices = this.#getTargetSlideIndices()
@@ -1960,7 +2556,11 @@ class PPTXTemplater {
1960
2556
  this.#assertLoaded()
1961
2557
  const targetIndices = this.#getTargetSlideIndices()
1962
2558
  for (const idx of targetIndices) {
1963
- this.#shapeManager.updateShapePosition(idx, shapeId, options, this.#slideManager)
2559
+ let resolvedOptions = options
2560
+ if (options.alignToCell) {
2561
+ resolvedOptions = this.#resolveAlignToCell(idx, options, shapeId, true)
2562
+ }
2563
+ this.#shapeManager.updateShapePosition(idx, shapeId, resolvedOptions, this.#slideManager)
1964
2564
  }
1965
2565
  return this
1966
2566
  }
@@ -1985,6 +2585,19 @@ class PPTXTemplater {
1985
2585
  return this
1986
2586
  }
1987
2587
 
2588
+ /**
2589
+ * Duplicates an existing shape with a new ID, optionally at a different position.
2590
+ *
2591
+ * @param {string} shapeId - Source shape name or ID.
2592
+ * @param {string} newShapeId - Name/ID for the cloned shape.
2593
+ * @param {Object} [options] - Position overrides for the clone.
2594
+ * @param {number} [options.x] - X offset for the clone (EMUs).
2595
+ * @param {number} [options.y] - Y offset for the clone (EMUs).
2596
+ * @returns {PPTXTemplater} this (chainable)
2597
+ *
2598
+ * @example
2599
+ * ppt.cloneShape('logo', 'logo-copy', { x: 2000000, y: 500000 });
2600
+ */
1988
2601
  cloneShape(shapeId, newShapeId, options = {}) {
1989
2602
  this.#assertLoaded()
1990
2603
  const targetIndices = this.#getTargetSlideIndices()
@@ -1994,6 +2607,15 @@ class PPTXTemplater {
1994
2607
  return this
1995
2608
  }
1996
2609
 
2610
+ /**
2611
+ * Removes a shape from the targeted slide(s). Alias for `removeShape()`.
2612
+ *
2613
+ * @param {string} shapeId - Shape name or ID to delete.
2614
+ * @returns {PPTXTemplater} this (chainable)
2615
+ *
2616
+ * @example
2617
+ * ppt.deleteShape('temp-banner');
2618
+ */
1997
2619
  deleteShape(shapeId) {
1998
2620
  this.#assertLoaded()
1999
2621
  const targetIndices = this.#getTargetSlideIndices()
@@ -2003,6 +2625,15 @@ class PPTXTemplater {
2003
2625
  return this
2004
2626
  }
2005
2627
 
2628
+ /**
2629
+ * Returns metadata for all shapes on the targeted slide(s).
2630
+ *
2631
+ * @returns {Array<{id: string, name: string, type: string, x: number, y: number, width: number, height: number}>}
2632
+ *
2633
+ * @example
2634
+ * const shapes = ppt.getShapes();
2635
+ * shapes.forEach(s => console.log(`${s.name}: ${s.type} at (${s.x}, ${s.y})`));
2636
+ */
2006
2637
  getShapes() {
2007
2638
  this.#assertLoaded()
2008
2639
  const targetIndices = this.#getTargetSlideIndices()
@@ -2023,17 +2654,22 @@ class PPTXTemplater {
2023
2654
  return this.#shapeManager.validateShape(options)
2024
2655
  }
2025
2656
 
2026
- /**
2027
- * Adds a new shape dynamically to the targeted slide(s).
2028
- *
2029
- * @param {Object} options Shape configuration options.
2030
- * @returns {this} The chainable presentation templater instance.
2031
- */
2032
- async addShape(options) {
2657
+ async addShape(typeOrOptions, options = {}) {
2033
2658
  this.#assertLoaded()
2659
+ let resolvedOptions = {}
2660
+ if (typeof typeOrOptions === 'string') {
2661
+ resolvedOptions = { ...options, type: typeOrOptions }
2662
+ } else {
2663
+ resolvedOptions = { ...typeOrOptions }
2664
+ }
2665
+
2034
2666
  const targetIndices = this.#getTargetSlideIndices()
2035
2667
  for (const idx of targetIndices) {
2036
- this.#shapeManager.addShape(idx, options, this.#slideManager)
2668
+ let finalOptions = resolvedOptions
2669
+ if (resolvedOptions.alignToCell) {
2670
+ finalOptions = this.#resolveAlignToCell(idx, resolvedOptions)
2671
+ }
2672
+ this.#shapeManager.addShape(idx, finalOptions, this.#slideManager)
2037
2673
  }
2038
2674
  return this
2039
2675
  }
@@ -2049,7 +2685,11 @@ class PPTXTemplater {
2049
2685
  this.#assertLoaded()
2050
2686
  const targetIndices = this.#getTargetSlideIndices()
2051
2687
  for (const idx of targetIndices) {
2052
- this.#shapeManager.updateShape(idx, shapeId, options, this.#slideManager)
2688
+ let resolvedOptions = options
2689
+ if (options.alignToCell) {
2690
+ resolvedOptions = this.#resolveAlignToCell(idx, options, shapeId)
2691
+ }
2692
+ this.#shapeManager.updateShape(idx, shapeId, resolvedOptions, this.#slideManager)
2053
2693
  }
2054
2694
  return this
2055
2695
  }
@@ -2195,13 +2835,17 @@ class PPTXTemplater {
2195
2835
  /**
2196
2836
  * Retrieves final rendered bounds of a table cell in pixels.
2197
2837
  *
2198
- * @param {string} tableId - Table name or shape ID.
2838
+ * @param {string|Object} tableIdOrObj - Table name, shape ID, or table object.
2199
2839
  * @param {number} rowIndex - 0-based row index.
2200
2840
  * @param {number} colIndex - 0-based column index.
2201
2841
  * @returns {Object|null} Cell bounds { x, y, width, height } in pixels, or null.
2202
2842
  */
2203
- getCellBounds(tableId, rowIndex, colIndex) {
2843
+ getCellBounds(tableIdOrObj, rowIndex, colIndex) {
2204
2844
  this.#assertLoaded()
2845
+ const tableId =
2846
+ typeof tableIdOrObj === 'object'
2847
+ ? tableIdOrObj.id || tableIdOrObj.name || tableIdOrObj.tableId
2848
+ : tableIdOrObj
2205
2849
  const targetIndices = this.#getTargetSlideIndices()
2206
2850
  for (const idx of targetIndices) {
2207
2851
  try {
@@ -2224,14 +2868,21 @@ class PPTXTemplater {
2224
2868
 
2225
2869
  /**
2226
2870
  * Retrieves final rendered position of a table cell in pixels.
2871
+ * Optionally calculates centered top-left coordinates for a shape of given dimensions.
2227
2872
  *
2228
- * @param {string} tableId - Table name or shape ID.
2873
+ * @param {string|Object} tableIdOrObj - Table name, shape ID, or table object.
2229
2874
  * @param {number} rowIndex - 0-based row index.
2230
2875
  * @param {number} colIndex - 0-based column index.
2231
- * @returns {Object|null} Cell position { row, column, x, y } in pixels, or null.
2876
+ * @param {number|Object} [shapeWidthOrOptions] - Width of the shape in pixels, or options object.
2877
+ * @param {number} [shapeHeight] - Height of the shape in pixels.
2878
+ * @returns {Object|null} Cell position { row, column, x, y, width, height } in pixels, or null.
2232
2879
  */
2233
- getCellPosition(tableId, rowIndex, colIndex) {
2880
+ getCellPosition(tableIdOrObj, rowIndex, colIndex, shapeWidthOrOptions, shapeHeight) {
2234
2881
  this.#assertLoaded()
2882
+ const tableId =
2883
+ typeof tableIdOrObj === 'object'
2884
+ ? tableIdOrObj.id || tableIdOrObj.name || tableIdOrObj.tableId
2885
+ : tableIdOrObj
2235
2886
  const targetIndices = this.#getTargetSlideIndices()
2236
2887
  for (const idx of targetIndices) {
2237
2888
  try {
@@ -2240,7 +2891,9 @@ class PPTXTemplater {
2240
2891
  tableId,
2241
2892
  rowIndex,
2242
2893
  colIndex,
2243
- this.#slideManager
2894
+ this.#slideManager,
2895
+ shapeWidthOrOptions,
2896
+ shapeHeight
2244
2897
  )
2245
2898
  if (pos) return pos
2246
2899
  } catch (err) {
@@ -2253,6 +2906,16 @@ class PPTXTemplater {
2253
2906
  }
2254
2907
 
2255
2908
  // === Image Features ===
2909
+ /**
2910
+ * Replaces an existing image in the presentation by shape name or relationship ID.
2911
+ *
2912
+ * @param {string} imageIdOrName - Shape name, alt text, or relationship ID of the image.
2913
+ * @param {string|Buffer} sourcePathOrBuffer - Path to the replacement image file, or a Buffer.
2914
+ * @returns {Promise<PPTXTemplater>} this (chainable)
2915
+ *
2916
+ * @example
2917
+ * await ppt.replaceImage('company-logo', './new-logo.png');
2918
+ */
2256
2919
  async replaceImage(imageIdOrName, sourcePathOrBuffer) {
2257
2920
  this.#assertLoaded()
2258
2921
  const t0 = performance.now()
@@ -2271,6 +2934,30 @@ class PPTXTemplater {
2271
2934
  return this
2272
2935
  }
2273
2936
 
2937
+ /**
2938
+ * Adds a new image to the targeted slide(s) at the specified position.
2939
+ *
2940
+ * @param {string|Buffer} sourcePathOrBuffer - Path to the image file, or a Buffer.
2941
+ * @param {Object} [options] - Positioning and display options.
2942
+ * @param {number} [options.x] - X offset in EMUs (1 inch = 914,400 EMUs).
2943
+ * @param {number} [options.y] - Y offset in EMUs.
2944
+ * @param {number} [options.width] - Width in EMUs.
2945
+ * @param {number} [options.height] - Height in EMUs.
2946
+ * @param {number} [options.rotation] - Rotation in degrees (0–360).
2947
+ * @param {number} [options.opacity] - Opacity (0–100).
2948
+ * @param {string} [options.name] - Shape name for the image.
2949
+ * @param {Object} [options.cropTo] - Crop percentages `{ l, r, t, b }` (0–100000).
2950
+ * @returns {Promise<PPTXTemplater>} this (chainable)
2951
+ *
2952
+ * @example
2953
+ * await ppt.addImage('./photo.jpg', {
2954
+ * x: 914400, // 1 inch
2955
+ * y: 914400, // 1 inch
2956
+ * width: 3657600, // 4 inches
2957
+ * height: 2743200, // 3 inches
2958
+ * name: 'hero-image'
2959
+ * });
2960
+ */
2274
2961
  async addImage(sourcePathOrBuffer, options = {}) {
2275
2962
  this.#assertLoaded()
2276
2963
  const t0 = performance.now()
@@ -2289,6 +2976,15 @@ class PPTXTemplater {
2289
2976
  return this
2290
2977
  }
2291
2978
 
2979
+ /**
2980
+ * Removes an image from the targeted slide(s) by shape name or relationship ID.
2981
+ *
2982
+ * @param {string} imageIdOrName - Shape name, alt text, or relationship ID of the image.
2983
+ * @returns {PPTXTemplater} this (chainable)
2984
+ *
2985
+ * @example
2986
+ * ppt.removeImage('old-logo');
2987
+ */
2292
2988
  removeImage(imageIdOrName) {
2293
2989
  this.#assertLoaded()
2294
2990
  const targetIndices = this.#getTargetSlideIndices()
@@ -2303,6 +2999,15 @@ class PPTXTemplater {
2303
2999
  return this
2304
3000
  }
2305
3001
 
3002
+ /**
3003
+ * Returns metadata for all images found on the targeted slide(s).
3004
+ *
3005
+ * @returns {Array<{rId: string, name: string, x: number, y: number, width: number, height: number, mediaPath: string}>}
3006
+ *
3007
+ * @example
3008
+ * const images = ppt.getImages();
3009
+ * images.forEach(img => console.log(`${img.name}: ${img.width}×${img.height} EMUs`));
3010
+ */
2306
3011
  getImages() {
2307
3012
  this.#assertLoaded()
2308
3013
  const targetIndices = this.#getTargetSlideIndices()
@@ -2315,7 +3020,19 @@ class PPTXTemplater {
2315
3020
  return images
2316
3021
  }
2317
3022
 
2318
- // === Validation Features ===
3023
+ /**
3024
+ * Performs a comprehensive validation of the entire PPTX structure.
3025
+ * Checks slide XML, relationships, content types, slide masters, and layouts.
3026
+ *
3027
+ * @returns {Promise<{valid: boolean, errors: string[], warnings: string[]}>} Validation report.
3028
+ *
3029
+ * @example
3030
+ * const result = await ppt.validatePresentation();
3031
+ * if (!result.valid) {
3032
+ * console.error('Errors:', result.errors);
3033
+ * console.warn('Warnings:', result.warnings);
3034
+ * }
3035
+ */
2319
3036
  async validatePresentation() {
2320
3037
  this.#assertLoaded()
2321
3038
  return await ValidationEngine.validatePresentation(this)
@@ -2409,11 +3126,31 @@ class PPTXTemplater {
2409
3126
  }
2410
3127
  }
2411
3128
 
3129
+ /**
3130
+ * Validates the XML structure of a specific slide.
3131
+ *
3132
+ * @param {number} slideIndex - 1-based slide index to validate.
3133
+ * @returns {Promise<{valid: boolean, errors: string[], warnings: string[]}>}
3134
+ *
3135
+ * @example
3136
+ * const result = await ppt.validateSlide(1);
3137
+ * if (!result.valid) console.error(result.errors);
3138
+ */
2412
3139
  async validateSlide(slideIndex) {
2413
3140
  this.#assertLoaded()
2414
3141
  return await ValidationEngine.validateSlide(this, slideIndex)
2415
3142
  }
2416
3143
 
3144
+ /**
3145
+ * Validates the XML structure of a specific table on the active slide.
3146
+ *
3147
+ * @param {string} tableId - Table name or shape ID.
3148
+ * @returns {Promise<{valid: boolean, errors: string[], warnings: string[]}>}
3149
+ *
3150
+ * @example
3151
+ * const result = await ppt.validateTable('SalesTable');
3152
+ * if (!result.valid) console.error(result.errors);
3153
+ */
2417
3154
  async validateTable(tableId) {
2418
3155
  this.#assertLoaded()
2419
3156
  return await ValidationEngine.validateTable(
@@ -2423,17 +3160,46 @@ class PPTXTemplater {
2423
3160
  )
2424
3161
  }
2425
3162
 
3163
+ /**
3164
+ * Validates the internal ZIP archive structure of the PPTX file.
3165
+ * Checks that all files referenced in the archive are accessible and uncorrupted.
3166
+ * Throws if critical structural issues are found.
3167
+ *
3168
+ * @returns {Promise<PPTXTemplater>} this (chainable)
3169
+ *
3170
+ * @example
3171
+ * await ppt.validateArchive(); // Throws PPTXError if the ZIP is corrupt
3172
+ */
2426
3173
  async validateArchive() {
2427
3174
  this.#assertLoaded()
2428
3175
  await this.#zipManager.validateArchive()
2429
3176
  return this
2430
3177
  }
2431
3178
 
3179
+ /**
3180
+ * Enables ZIP debug output. When enabled, every call to `toBuffer()` or `toStream()`
3181
+ * will log all ZIP entries (name, compression method, sizes, CRC).
3182
+ *
3183
+ * @returns {PPTXTemplater} this (chainable)
3184
+ *
3185
+ * @example
3186
+ * ppt.enableDebugZip();
3187
+ * const buffer = await ppt.toBuffer(); // Logs ZIP entries to debug output
3188
+ */
2432
3189
  enableDebugZip() {
2433
3190
  this.#outputWriter.debugZip = true
2434
3191
  return this
2435
3192
  }
2436
3193
 
3194
+ /**
3195
+ * Validates relationships for a specific part path inside the ZIP.
3196
+ *
3197
+ * @param {string} partPath - ZIP path to validate (e.g. `'ppt/slides/slide1.xml'`).
3198
+ * @returns {Object} Validation result with `valid`, `errors`, and `warnings`.
3199
+ *
3200
+ * @example
3201
+ * const result = ppt.validateRelationships('ppt/slides/slide1.xml');
3202
+ */
2437
3203
  validateRelationships(partPath) {
2438
3204
  this.#assertLoaded()
2439
3205
  return ValidationEngine.validateRelationships(this, partPath)
@@ -2658,6 +3424,17 @@ class PPTXTemplater {
2658
3424
  /**
2659
3425
  * Gets the ordered metadata of all objects on the slide.
2660
3426
  */
3427
+ /**
3428
+ * Returns an ordered array of all slide objects (shapes, images, charts, tables)
3429
+ * from bottom to top of the stacking order.
3430
+ *
3431
+ * @param {number} [slideIndex] - 1-based slide index. Defaults to the active slide.
3432
+ * @returns {Array<{id: string, name: string, type: string, zIndex: number}>}
3433
+ *
3434
+ * @example
3435
+ * const order = ppt.getObjectOrder(1);
3436
+ * order.forEach(o => console.log(`[${o.zIndex}] ${o.name}`));
3437
+ */
2661
3438
  getObjectOrder(slideIndex) {
2662
3439
  this.#assertLoaded()
2663
3440
  const targetIdx = slideIndex !== undefined ? slideIndex : this.#getTargetSlideIndices()[0] || 1
@@ -2684,6 +3461,16 @@ class PPTXTemplater {
2684
3461
  /**
2685
3462
  * Retrieves the info of the top-most object on the slide.
2686
3463
  */
3464
+ /**
3465
+ * Returns the top-most (front) object on the slide.
3466
+ *
3467
+ * @param {number} [slideIndex] - 1-based slide index. Defaults to the active slide.
3468
+ * @returns {{id: string, name: string, type: string, zIndex: number}|null}
3469
+ *
3470
+ * @example
3471
+ * const top = ppt.getTopMostObject(1);
3472
+ * console.log('Front-most shape:', top.name);
3473
+ */
2687
3474
  getTopMostObject(slideIndex) {
2688
3475
  this.#assertLoaded()
2689
3476
  const targetIdx = slideIndex !== undefined ? slideIndex : this.#getTargetSlideIndices()[0] || 1
@@ -2693,6 +3480,16 @@ class PPTXTemplater {
2693
3480
  /**
2694
3481
  * Retrieves the info of the bottom-most object on the slide.
2695
3482
  */
3483
+ /**
3484
+ * Returns the bottom-most (back) object on the slide.
3485
+ *
3486
+ * @param {number} [slideIndex] - 1-based slide index. Defaults to the active slide.
3487
+ * @returns {{id: string, name: string, type: string, zIndex: number}|null}
3488
+ *
3489
+ * @example
3490
+ * const bottom = ppt.getBottomMostObject(1);
3491
+ * console.log('Back-most shape:', bottom.name);
3492
+ */
2696
3493
  getBottomMostObject(slideIndex) {
2697
3494
  this.#assertLoaded()
2698
3495
  const targetIdx = slideIndex !== undefined ? slideIndex : this.#getTargetSlideIndices()[0] || 1
@@ -2702,6 +3499,18 @@ class PPTXTemplater {
2702
3499
  /**
2703
3500
  * Swaps stacking positions of two slide objects.
2704
3501
  */
3502
+ /**
3503
+ * Swaps the stacking positions of two slide objects.
3504
+ *
3505
+ * @param {number|string} slideIndexOrId1 - Slide index (if 3 args) or first object ID (if 2 args).
3506
+ * @param {string} id1OrId2 - First object ID (if 3 args) or second object ID (if 2 args).
3507
+ * @param {string} [id2] - Second object ID (only if slide index is provided as first arg).
3508
+ * @returns {PPTXTemplater} this (chainable)
3509
+ *
3510
+ * @example
3511
+ * ppt.swapObjects(1, 'logo', 'background'); // On slide 1, swap 'logo' and 'background'
3512
+ * ppt.swapObjects('logo', 'background'); // On active slide
3513
+ */
2705
3514
  swapObjects(slideIndexOrId1, id1OrId2, id2) {
2706
3515
  this.#assertLoaded()
2707
3516
  let slideIndex, objectId1, objectId2
@@ -2721,6 +3530,17 @@ class PPTXTemplater {
2721
3530
  /**
2722
3531
  * Sorts stacking order using a custom comparison function.
2723
3532
  */
3533
+ /**
3534
+ * Sorts all slide objects using a custom comparison function.
3535
+ *
3536
+ * @param {number|Function} slideIndexOrCompareFn - Slide index (if 2 args) or compare function (if 1 arg).
3537
+ * @param {Function} [compareFnOption] - Compare function when slide index is provided.
3538
+ * @returns {PPTXTemplater} this (chainable)
3539
+ *
3540
+ * @example
3541
+ * // Sort alphabetically by name on the active slide
3542
+ * ppt.sortObjects((a, b) => a.name.localeCompare(b.name));
3543
+ */
2724
3544
  sortObjects(slideIndexOrCompareFn, compareFnOption) {
2725
3545
  this.#assertLoaded()
2726
3546
  let slideIndex, compareFn
@@ -2738,12 +3558,142 @@ class PPTXTemplater {
2738
3558
  /**
2739
3559
  * Cleans up and normalizes stacking order consistency.
2740
3560
  */
3561
+ /**
3562
+ * Normalizes the stacking order of all objects on a slide, removing gaps
3563
+ * and ensuring Z-index values are sequential (1, 2, 3, ...).
3564
+ *
3565
+ * @param {number} [slideIndex] - 1-based slide index. Defaults to the active slide.
3566
+ * @returns {PPTXTemplater} this (chainable)
3567
+ *
3568
+ * @example
3569
+ * ppt.normalizeZOrder(1); // Clean up stacking order on slide 1
3570
+ */
2741
3571
  normalizeZOrder(slideIndex) {
2742
3572
  this.#assertLoaded()
2743
3573
  const targetIdx = slideIndex !== undefined ? slideIndex : this.#getTargetSlideIndices()[0] || 1
2744
3574
  this.#zOrderManager.normalizeZOrder(targetIdx, this.#slideManager)
2745
3575
  return this
2746
3576
  }
3577
+
3578
+ /**
3579
+ * Aligns an existing shape to a table cell's position.
3580
+ *
3581
+ * @param {string} shapeId - Unique shape name/id in the template.
3582
+ * @param {string|Object} tableIdOrObj - Table ID string, or table object.
3583
+ * @param {number} rowIndex - 0-based row index.
3584
+ * @param {number} colIndex - 0-based column index.
3585
+ * @param {Object} [options] - Alignment options.
3586
+ * @param {'left'|'center'|'right'} [options.horizontal='center'] - Horizontal alignment.
3587
+ * @param {'top'|'middle'|'bottom'} [options.vertical='middle'] - Vertical alignment.
3588
+ * @returns {this} The chainable presentation templater instance.
3589
+ */
3590
+ alignShapeToCell(shapeId, tableIdOrObj, rowIndex, colIndex, options = {}) {
3591
+ const tableId =
3592
+ typeof tableIdOrObj === 'object'
3593
+ ? tableIdOrObj.id || tableIdOrObj.name || tableIdOrObj.tableId
3594
+ : tableIdOrObj
3595
+ this.updateShapePosition(shapeId, {
3596
+ alignToCell: {
3597
+ table: tableId,
3598
+ row: rowIndex,
3599
+ col: colIndex,
3600
+ horizontal: options.horizontal || 'center',
3601
+ vertical: options.vertical || 'middle',
3602
+ },
3603
+ })
3604
+ return this
3605
+ }
3606
+
3607
+ #resolveAlignToCell(slideIndex, options, shapeId, convertToEmus = false) {
3608
+ const align = options.alignToCell
3609
+ if (!align || !align.table) return options
3610
+
3611
+ const tableId =
3612
+ typeof align.table === 'object'
3613
+ ? align.table.id || align.table.name || align.table.tableId
3614
+ : align.table
3615
+ const row = align.row !== undefined ? align.row : 0
3616
+ const col = align.col !== undefined ? align.col : 0
3617
+
3618
+ // Get cell bounds
3619
+ const bounds = this.#tableManager.getCellBounds(
3620
+ slideIndex,
3621
+ tableId,
3622
+ row,
3623
+ col,
3624
+ this.#slideManager
3625
+ )
3626
+
3627
+ if (!bounds) return options
3628
+
3629
+ // Determine shape dimensions
3630
+ let shapeWidth = options.width
3631
+ let shapeHeight = options.height
3632
+
3633
+ if (convertToEmus) {
3634
+ if (shapeWidth !== undefined) shapeWidth = Math.round(shapeWidth / 9525)
3635
+ if (shapeHeight !== undefined) shapeHeight = Math.round(shapeHeight / 9525)
3636
+ }
3637
+
3638
+ if (shapeWidth === undefined || shapeHeight === undefined) {
3639
+ if (options.type === 'square' && options.size !== undefined) {
3640
+ shapeWidth = options.size
3641
+ shapeHeight = options.size
3642
+ } else if (options.type === 'circle' && options.radius !== undefined) {
3643
+ shapeWidth = options.radius * 2
3644
+ shapeHeight = options.radius * 2
3645
+ } else if (shapeId) {
3646
+ // Try getting existing shape dimensions
3647
+ const existing = this.#shapeManager.getShape(slideIndex, shapeId, this.#slideManager)
3648
+ if (existing) {
3649
+ shapeWidth = existing.width
3650
+ shapeHeight = existing.height
3651
+ }
3652
+ }
3653
+ }
3654
+
3655
+ // Default to fallback dimensions if still undefined
3656
+ if (shapeWidth === undefined) shapeWidth = 100
3657
+ if (shapeHeight === undefined) shapeHeight = 100
3658
+
3659
+ // Align horizontally
3660
+ let horiz = align.horizontal || align.alignX || 'center'
3661
+ horiz = String(horiz).toLowerCase()
3662
+ if (horiz === 'middle') horiz = 'center'
3663
+
3664
+ let x = bounds.x
3665
+ if (horiz === 'center') {
3666
+ x = bounds.x + (bounds.width - shapeWidth) / 2
3667
+ } else if (horiz === 'right') {
3668
+ x = bounds.x + bounds.width - shapeWidth
3669
+ }
3670
+
3671
+ // Align vertically
3672
+ let vert = align.vertical || align.alignY || 'middle'
3673
+ vert = String(vert).toLowerCase()
3674
+ if (vert === 'center') vert = 'middle'
3675
+
3676
+ let y = bounds.y
3677
+ if (vert === 'middle') {
3678
+ y = bounds.y + (bounds.height - shapeHeight) / 2
3679
+ } else if (vert === 'bottom') {
3680
+ y = bounds.y + bounds.height - shapeHeight
3681
+ }
3682
+
3683
+ const resolved = { ...options }
3684
+ if (convertToEmus) {
3685
+ resolved.x = Math.round(x * 9525)
3686
+ resolved.y = Math.round(y * 9525)
3687
+ } else {
3688
+ resolved.x = Math.round(x)
3689
+ resolved.y = Math.round(y)
3690
+ }
3691
+
3692
+ // Remove alignToCell to prevent it polluting lower levels
3693
+ delete resolved.alignToCell
3694
+
3695
+ return resolved
3696
+ }
2747
3697
  }
2748
3698
 
2749
3699
  module.exports = { PPTXTemplater }