node-pptx-templater 1.0.19 → 1.0.21

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/README.md CHANGED
@@ -173,6 +173,22 @@ if (!report.valid) {
173
173
  }
174
174
  ```
175
175
 
176
+ ### 5. PPTX Extraction & Rebuilding Utilities
177
+
178
+ You can programmatically extract standard zipped `.pptx` archives into OpenXML folder templates or compile them back. Target directories are created automatically:
179
+
180
+ #### Extract PPTX to Folder Template
181
+ ```javascript
182
+ const { PPTXTemplate } = require('node-pptx-templater');
183
+
184
+ await PPTXTemplate.extractPptx('./sample.pptx', './output/template', { overwrite: true });
185
+ ```
186
+
187
+ #### Rebuild PPTX from Folder Template
188
+ ```javascript
189
+ await PPTXTemplate.buildPptx('./templates/sample', './output.pptx');
190
+ ```
191
+
176
192
  ---
177
193
 
178
194
  ## 📋 OpenXML Presentation Architecture
@@ -208,6 +224,95 @@ Naively duplicating rows in slide tables can leave duplicate `rowId` values or b
208
224
 
209
225
  ---
210
226
 
227
+ ## 📊 Reading Table Data & JSON Extraction
228
+
229
+ PPTXForge allows you to read table data from your template and return it as structured JSON objects or raw arrays. This is extremely useful for reverse-engineering data or verifying layouts.
230
+
231
+ ### 1. Object-Based Extraction (Default)
232
+ By default, the first row is treated as headers, and subsequent rows are returned as objects keyed by header names. Merged cells automatically resolve to their parent cell's value:
233
+ ```javascript
234
+ const rows = await ppt.getTableRows('SalesTable');
235
+ // Returns:
236
+ // [
237
+ // { region: 'North', sales: '1200', growth: '15%' },
238
+ // { region: 'South', sales: '1800', growth: '22%' }
239
+ // ]
240
+ ```
241
+
242
+ ### 2. Raw Extraction
243
+ Return a raw 2D array of string values (excluding the header row) by passing `{ raw: true }`:
244
+ ```javascript
245
+ const rows = await ppt.getTableRows('SalesTable', { raw: true });
246
+ // Returns:
247
+ // [
248
+ // ['North', '1200', '15%'],
249
+ // ['South', '1800', '22%']
250
+ // ]
251
+ ```
252
+
253
+ ### 3. Including Table Metadata
254
+ Pass `{ includeMetadata: true }` to retrieve row counts, column counts, and the list of merged cell ranges alongside the rows:
255
+ ```javascript
256
+ const result = await ppt.getTableRows('SalesTable', { includeMetadata: true });
257
+ // Returns:
258
+ // {
259
+ // rows: [...],
260
+ // rowCount: 10,
261
+ // columnCount: 5,
262
+ // mergedCells: [ { startRow: 1, startCol: 0, endRow: 2, endCol: 0 }, ... ]
263
+ // }
264
+ ```
265
+
266
+ ---
267
+
268
+ ## 📈 Nested Table Rows & Rowspan Support
269
+
270
+ When building financial sheets, invoices, or hierarchical dashboards, you often need to stack multiple rows vertically inside a single column while spanning cells in other columns. PPTXForge supports nested arrays in `addTableRow()` to automatically handle:
271
+ - Proportional height scaling.
272
+ - Vertical merge (`vMerge`) and row span (`rowSpan`) generation.
273
+ - Dynamic layout adjustments.
274
+
275
+ ### 1. Vertical Row Nesting Example
276
+ ```javascript
277
+ await ppt.addTableRow('Table1', [
278
+ 'Region',
279
+ ['Sales', '1200'],
280
+ ['Growth', '15%']
281
+ ]);
282
+ ```
283
+ Generates:
284
+ ```text
285
+ +---------+---------+---------+
286
+ | Region | Sales | Growth |
287
+ | | 1200 | 15% |
288
+ +---------+---------+---------+
289
+ ```
290
+
291
+ ### 2. Deep Nesting Example
292
+ ```javascript
293
+ await ppt.addTableRow('Table1', [
294
+ 'Parent',
295
+ [
296
+ 'Child 1',
297
+ 'Child 2',
298
+ 'Child 3'
299
+ ]
300
+ ]);
301
+ ```
302
+ Generates 3 sub-rows where column 0 automatically spans all 3 rows.
303
+
304
+ ### 3. Merge Strategies
305
+ Configure merge behavior via `options.mergeStrategy`:
306
+ - `'auto'` (default): Creates structural merges from nested arrays, and additionally merges consecutive duplicate values in the same column.
307
+ - `'rowspan'`: Creates structural rowspan/merges strictly from the nested array structure.
308
+ - `'none'`: Pads columns to match the target generated height but does not merge cells (leaving them as individual cells).
309
+
310
+ ```javascript
311
+ await ppt.addTableRow('Table1', data, { mergeStrategy: 'rowspan' });
312
+ ```
313
+
314
+ ---
315
+
211
316
  ## 📊 Feature Comparison Matrix
212
317
 
213
318
  Compare PPTXForge with other popular PowerPoint automation libraries:
@@ -243,6 +348,98 @@ ppt.useSlide(1).updateTable('summary-table', [
243
348
  ]);
244
349
  ```
245
350
 
351
+ #### `addCellShape(tableId, rowIndex, colIndex, options)`
352
+ Dynamically adds a shape inside a table cell based on cell coordinates.
353
+
354
+ * **Arguments**:
355
+ * `tableId` (`string`): Table name or shape ID.
356
+ * `rowIndex` (`number`): 0-based row index.
357
+ * `colIndex` (`number`): 0-based column index.
358
+ * `options` (`Object`): Shape configuration options.
359
+ * **Returns**: `this` - The chainable presentation templater instance.
360
+
361
+ ```javascript
362
+ await ppt.addCellShape('Table', 1, 2, { type: 'circle', fill: '#10B981' });
363
+ ```
364
+
365
+ #### `updateCellShape(tableId, rowIndex, colIndex, shapeIndex, options)`
366
+ Updates an existing shape inside a table cell.
367
+
368
+ * **Arguments**:
369
+ * `tableId` (`string`): Table name or shape ID.
370
+ * `rowIndex` (`number`): 0-based row index.
371
+ * `colIndex` (`number`): 0-based column index.
372
+ * `shapeIndex` (`number`): 0-based shape index in the cell.
373
+ * `options` (`Object`): Shape configuration properties to update.
374
+ * **Returns**: `this` - The chainable presentation templater instance.
375
+
376
+ ```javascript
377
+ await ppt.updateCellShape('Table', 1, 2, 0, { fill: '#EF4444' });
378
+ ```
379
+
380
+ #### `removeCellShape(tableId, rowIndex, colIndex, shapeIndex)`
381
+ Removes a shape from a table cell.
382
+
383
+ * **Arguments**:
384
+ * `tableId` (`string`): Table name or shape ID.
385
+ * `rowIndex` (`number`): 0-based row index.
386
+ * `colIndex` (`number`): 0-based column index.
387
+ * `shapeIndex` (`number`): 0-based shape index in the cell.
388
+ * **Returns**: `this` - The chainable presentation templater instance.
389
+
390
+ ```javascript
391
+ await ppt.removeCellShape('Table', 1, 2, 0);
392
+ ```
393
+
394
+ #### `getCellShape(tableId, rowIndex, colIndex, shapeIndex)`
395
+ Discovers and retrieves details of an existing cell shape on the targeted slide.
396
+
397
+ * **Arguments**:
398
+ * `tableId` (`string`): Table name or shape ID.
399
+ * `rowIndex` (`number`): 0-based row index.
400
+ * `colIndex` (`number`): 0-based column index.
401
+ * `shapeIndex` (`number`): 0-based shape index in the cell.
402
+ * **Returns**: `Object|null` - Shape details object, or null if not found.
403
+
404
+ ```javascript
405
+ const shape = ppt.getCellShape('Table', 1, 2, 0);
406
+ ```
407
+
408
+ #### `getCellBounds(tableId, rowIndex, colIndex)`
409
+ Retrieves final rendered bounds of a table cell in pixels.
410
+
411
+ * **Arguments**:
412
+ * `tableId` (`string`): Table name or shape ID.
413
+ * `rowIndex` (`number`): 0-based row index.
414
+ * `colIndex` (`number`): 0-based column index.
415
+ * **Returns**: `Object|null` - Cell bounds { x, y, width, height } in pixels, or null.
416
+
417
+ ```javascript
418
+ const bounds = ppt.getCellBounds('summary-table', 1, 1);
419
+ ```
420
+
421
+ #### `getCellPosition(tableId, rowIndex, colIndex)`
422
+ Retrieves final rendered position of a table cell in pixels.
423
+
424
+ * **Arguments**:
425
+ * `tableId` (`string`): Table name or shape ID.
426
+ * `rowIndex` (`number`): 0-based row index.
427
+ * `colIndex` (`number`): 0-based column index.
428
+ * **Returns**: `Object|null` - Cell position { row, column, x, y } in pixels, or null.
429
+
430
+ ```javascript
431
+ const pos = ppt.getCellPosition('summary-table', 1, 1);
432
+ ```
433
+
434
+ #### `getTableRows(())`
435
+ Delegates core actions to slide element sub-managers.
436
+
437
+ * **Returns**: `PPTXTemplater` - The fluent engine instance.
438
+
439
+ ```javascript
440
+ const rows = await ppt.getTableRows('SalesTable');
441
+ ```
442
+
246
443
  #### `addTableRow(())`
247
444
  Delegates core actions to slide element sub-managers.
248
445
 
@@ -1016,6 +1213,70 @@ Updates the position and/or dimensions of an existing textbox on targeted slides
1016
1213
  ppt.useSlide(1).updateTextBoxPosition('TextBox 2', { x: 1000000, y: 1500000 });
1017
1214
  ```
1018
1215
 
1216
+ #### `validateShape(options)`
1217
+ Validates shape options configuration.
1218
+
1219
+ * **Arguments**:
1220
+ * `options` (`Object`):
1221
+ * **Returns**: `string[]` - List of validation error messages.
1222
+
1223
+ ```javascript
1224
+ const errors = ppt.validateShape(shapeOptions);
1225
+ ```
1226
+
1227
+ #### `addShape(options)`
1228
+ Adds a new shape dynamically to the targeted slide(s).
1229
+
1230
+ * **Arguments**:
1231
+ * `options` (`Object`):
1232
+ * **Returns**: `this` - The chainable presentation templater instance.
1233
+
1234
+ ```javascript
1235
+ await ppt.useSlide(1).addShape({
1236
+ type: 'rectangle',
1237
+ id: 'sales-box',
1238
+ x: 100,
1239
+ y: 100,
1240
+ width: 200,
1241
+ height: 100,
1242
+ fill: '#2563EB'
1243
+ });
1244
+ ```
1245
+
1246
+ #### `updateShape(shapeId, options)`
1247
+ Updates an existing shape in-place.
1248
+
1249
+ * **Arguments**:
1250
+ * `shapeId` (`string`):
1251
+ * `options` (`Object`):
1252
+ * **Returns**: `this` - The chainable presentation templater instance.
1253
+
1254
+ ```javascript
1255
+ await ppt.useSlide(1).updateShape('sales-box', { fill: '#10B981' });
1256
+ ```
1257
+
1258
+ #### `removeShape(shapeId)`
1259
+ Removes a shape from the targeted slide(s).
1260
+
1261
+ * **Arguments**:
1262
+ * `shapeId` (`string`):
1263
+ * **Returns**: `this` - The chainable presentation templater instance.
1264
+
1265
+ ```javascript
1266
+ await ppt.useSlide(1).removeShape('sales-box');
1267
+ ```
1268
+
1269
+ #### `getShape(shapeId)`
1270
+ Discovers and retrieves details of an existing shape on the targeted slides.
1271
+
1272
+ * **Arguments**:
1273
+ * `shapeId` (`string`):
1274
+ * **Returns**: `Object|null` - Shape details object, or null if not found.
1275
+
1276
+ ```javascript
1277
+ const shape = ppt.getShape('sales-box');
1278
+ ```
1279
+
1019
1280
  #### `updateShapeText(())`
1020
1281
  Delegates core actions to slide element sub-managers.
1021
1282
 
@@ -1466,6 +1727,24 @@ Delegates core actions to slide element sub-managers.
1466
1727
  const ppt = await PPTXTemplate.fromPresentationXml('./template-folder');
1467
1728
  ```
1468
1729
 
1730
+ #### `extractPptx(())`
1731
+ Delegates core actions to slide element sub-managers.
1732
+
1733
+ * **Returns**: `PPTXTemplater` - The fluent engine instance.
1734
+
1735
+ ```javascript
1736
+ await PPTXTemplater.extractPptx('sample.pptx', './extracted');
1737
+ ```
1738
+
1739
+ #### `buildPptx(())`
1740
+ Delegates core actions to slide element sub-managers.
1741
+
1742
+ * **Returns**: `PPTXTemplater` - The fluent engine instance.
1743
+
1744
+ ```javascript
1745
+ await PPTXTemplater.buildPptx('./extracted', 'output.pptx');
1746
+ ```
1747
+
1469
1748
  #### `validatePresentation(())`
1470
1749
  Delegates core actions to slide element sub-managers.
1471
1750
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-pptx-templater",
3
- "version": "1.0.19",
3
+ "version": "1.0.21",
4
4
  "description": "High-performance, low-level PowerPoint (PPTX) OpenXML template engine for Node.js. Dynamically replace text, insert images, update charts (with Excel workbook data caching), and merge table cells without PowerPoint corruption or Repair Mode prompts.",
5
5
  "main": "./src/index.js",
6
6
  "type": "commonjs",
@@ -340,6 +340,89 @@ class PPTXTemplater {
340
340
  return engine
341
341
  }
342
342
 
343
+ /**
344
+ * Extracts a PPTX file into an unzipped OpenXML folder structure.
345
+ *
346
+ * @static
347
+ * @param {string} pptxPath - Path to the source PPTX file.
348
+ * @param {string} outputPath - Path to the destination folder.
349
+ * @param {Object} [options] - Options (e.g. { overwrite: true }).
350
+ * @returns {Promise<void>}
351
+ */
352
+ static async extractPptx(pptxPath, outputPath, options = {}) {
353
+ const fs = require('fs-extra')
354
+ const path = require('path')
355
+
356
+ const resolvedPptx = path.resolve(pptxPath)
357
+ const resolvedOut = path.resolve(outputPath)
358
+
359
+ if (!fs.existsSync(resolvedPptx)) {
360
+ throw new PPTXError(`Source PPTX file not found: ${pptxPath}`)
361
+ }
362
+
363
+ if (fs.existsSync(resolvedOut)) {
364
+ const stats = fs.statSync(resolvedOut)
365
+ if (stats.isFile()) {
366
+ throw new PPTXError(`Destination is a file: ${outputPath}`)
367
+ }
368
+ const files = fs.readdirSync(resolvedOut)
369
+ if (files.length > 0 && !options.overwrite) {
370
+ throw new PPTXError(
371
+ `Destination directory "${outputPath}" is not empty. Set overwrite: true to overwrite.`
372
+ )
373
+ }
374
+ } else {
375
+ await fs.ensureDir(resolvedOut)
376
+ }
377
+
378
+ const engine = await PPTXTemplater.load(resolvedPptx)
379
+ await engine.#zipManager.toFolder(resolvedOut)
380
+
381
+ // Validation
382
+ const criticalParts = ['ppt/presentation.xml', 'ppt/slides', 'ppt/_rels', '[Content_Types].xml']
383
+
384
+ for (const part of criticalParts) {
385
+ const p = path.join(resolvedOut, part)
386
+ if (!fs.existsSync(p)) {
387
+ throw new PPTXError(`Extracted structure is missing critical part: ${part}`)
388
+ }
389
+ }
390
+ }
391
+
392
+ /**
393
+ * Rebuilds a PPTX file from an unzipped OpenXML folder structure.
394
+ *
395
+ * @static
396
+ * @param {string} folderPath - Path to the source folder structure.
397
+ * @param {string} pptxPath - Path to the destination PPTX file.
398
+ * @returns {Promise<void>}
399
+ */
400
+ static async buildPptx(folderPath, pptxPath) {
401
+ const fs = require('fs-extra')
402
+ const path = require('path')
403
+
404
+ const resolvedFolder = path.resolve(folderPath)
405
+ const resolvedPptx = path.resolve(pptxPath)
406
+
407
+ if (!fs.existsSync(resolvedFolder)) {
408
+ throw new PPTXError(`Source folder not found: ${folderPath}`)
409
+ }
410
+
411
+ // Validation of the source folder
412
+ const criticalParts = ['ppt/presentation.xml', 'ppt/slides', 'ppt/_rels', '[Content_Types].xml']
413
+
414
+ for (const part of criticalParts) {
415
+ const p = path.join(resolvedFolder, part)
416
+ if (!fs.existsSync(p)) {
417
+ throw new PPTXError(`Source folder is missing critical OpenXML part: ${part}`)
418
+ }
419
+ }
420
+
421
+ const engine = await PPTXTemplater.load(resolvedFolder)
422
+ await fs.ensureDir(path.dirname(resolvedPptx))
423
+ await engine.saveToFile(resolvedPptx)
424
+ }
425
+
343
426
  /**
344
427
  * Initializes the engine by loading a PPTX file/buffer.
345
428
  * @private
@@ -588,7 +671,13 @@ class PPTXTemplater {
588
671
  const targetIndices = this.#getTargetSlideIndices()
589
672
 
590
673
  for (const slideIndex of targetIndices) {
591
- this.#tableManager.updateTable(slideIndex, tableId, rows, this.#slideManager)
674
+ this.#tableManager.updateTable(
675
+ slideIndex,
676
+ tableId,
677
+ rows,
678
+ this.#slideManager,
679
+ this.#shapeManager
680
+ )
592
681
  }
593
682
 
594
683
  logger.debug(`Updated table "${tableId}" in ${targetIndices.length} slide(s)`)
@@ -1291,11 +1380,21 @@ class PPTXTemplater {
1291
1380
  }
1292
1381
 
1293
1382
  // === Table Features ===
1294
- addTableRow(tableId, rowData) {
1383
+ getTableRows(tableId, options = {}) {
1384
+ this.#assertLoaded()
1385
+ const targetIndices = this.#getTargetSlideIndices()
1386
+ if (targetIndices.length === 0) {
1387
+ throw new PPTXError('No slides active/loaded')
1388
+ }
1389
+ const idx = targetIndices[0]
1390
+ return this.#tableManager.getTableRows(idx, tableId, options, this.#slideManager)
1391
+ }
1392
+
1393
+ addTableRow(tableId, rowData, options = {}) {
1295
1394
  this.#assertLoaded()
1296
1395
  const targetIndices = this.#getTargetSlideIndices()
1297
1396
  for (const idx of targetIndices) {
1298
- this.#tableManager.addTableRow(idx, tableId, rowData, this.#slideManager)
1397
+ this.#tableManager.addTableRow(idx, tableId, rowData, this.#slideManager, options)
1299
1398
  }
1300
1399
  return this
1301
1400
  }
@@ -1914,6 +2013,245 @@ class PPTXTemplater {
1914
2013
  return shapes
1915
2014
  }
1916
2015
 
2016
+ /**
2017
+ * Validates shape options configuration.
2018
+ *
2019
+ * @param {Object} options Shape creation/update options.
2020
+ * @returns {string[]} List of validation error messages.
2021
+ */
2022
+ validateShape(options) {
2023
+ return this.#shapeManager.validateShape(options)
2024
+ }
2025
+
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) {
2033
+ this.#assertLoaded()
2034
+ const targetIndices = this.#getTargetSlideIndices()
2035
+ for (const idx of targetIndices) {
2036
+ this.#shapeManager.addShape(idx, options, this.#slideManager)
2037
+ }
2038
+ return this
2039
+ }
2040
+
2041
+ /**
2042
+ * Updates an existing shape in-place.
2043
+ *
2044
+ * @param {string} shapeId Shape ID or template name to update.
2045
+ * @param {Object} options Configuration properties to update.
2046
+ * @returns {this} The chainable presentation templater instance.
2047
+ */
2048
+ async updateShape(shapeId, options) {
2049
+ this.#assertLoaded()
2050
+ const targetIndices = this.#getTargetSlideIndices()
2051
+ for (const idx of targetIndices) {
2052
+ this.#shapeManager.updateShape(idx, shapeId, options, this.#slideManager)
2053
+ }
2054
+ return this
2055
+ }
2056
+
2057
+ /**
2058
+ * Removes a shape from the targeted slide(s).
2059
+ *
2060
+ * @param {string} shapeId Shape ID or template name to remove.
2061
+ * @returns {this} The chainable presentation templater instance.
2062
+ */
2063
+ async removeShape(shapeId) {
2064
+ this.#assertLoaded()
2065
+ const targetIndices = this.#getTargetSlideIndices()
2066
+ for (const idx of targetIndices) {
2067
+ this.#shapeManager.removeShape(idx, shapeId, this.#slideManager)
2068
+ }
2069
+ return this
2070
+ }
2071
+
2072
+ /**
2073
+ * Discovers and retrieves details of an existing shape on the targeted slides.
2074
+ *
2075
+ * @param {string} shapeId Shape ID or template name to locate.
2076
+ * @returns {Object|null} Shape details object, or null if not found.
2077
+ */
2078
+ getShape(shapeId) {
2079
+ this.#assertLoaded()
2080
+ const targetIndices = this.#getTargetSlideIndices()
2081
+ for (const idx of targetIndices) {
2082
+ const shape = this.#shapeManager.getShape(idx, shapeId, this.#slideManager)
2083
+ if (shape) return shape
2084
+ }
2085
+ return null
2086
+ }
2087
+
2088
+ /**
2089
+ * Dynamically adds a shape inside a table cell based on cell coordinates.
2090
+ *
2091
+ * @param {string} tableId - Table name or shape ID.
2092
+ * @param {number} rowIndex - 0-based row index.
2093
+ * @param {number} colIndex - 0-based column index.
2094
+ * @param {Object} options - Shape configuration options.
2095
+ * @returns {this} The chainable presentation templater instance.
2096
+ */
2097
+ async addCellShape(tableId, rowIndex, colIndex, options) {
2098
+ this.#assertLoaded()
2099
+ const targetIndices = this.#getTargetSlideIndices()
2100
+ for (const idx of targetIndices) {
2101
+ this.#tableManager.addCellShape(
2102
+ idx,
2103
+ tableId,
2104
+ rowIndex,
2105
+ colIndex,
2106
+ options,
2107
+ this.#slideManager,
2108
+ this.#shapeManager
2109
+ )
2110
+ }
2111
+ return this
2112
+ }
2113
+
2114
+ /**
2115
+ * Updates an existing shape inside a table cell.
2116
+ *
2117
+ * @param {string} tableId - Table name or shape ID.
2118
+ * @param {number} rowIndex - 0-based row index.
2119
+ * @param {number} colIndex - 0-based column index.
2120
+ * @param {number} shapeIndex - 0-based shape index in the cell.
2121
+ * @param {Object} options - Shape configuration properties to update.
2122
+ * @returns {this} The chainable presentation templater instance.
2123
+ */
2124
+ async updateCellShape(tableId, rowIndex, colIndex, shapeIndex, options) {
2125
+ this.#assertLoaded()
2126
+ const targetIndices = this.#getTargetSlideIndices()
2127
+ for (const idx of targetIndices) {
2128
+ this.#tableManager.updateCellShape(
2129
+ idx,
2130
+ tableId,
2131
+ rowIndex,
2132
+ colIndex,
2133
+ shapeIndex,
2134
+ options,
2135
+ this.#slideManager,
2136
+ this.#shapeManager
2137
+ )
2138
+ }
2139
+ return this
2140
+ }
2141
+
2142
+ /**
2143
+ * Removes a shape from a table cell.
2144
+ *
2145
+ * @param {string} tableId - Table name or shape ID.
2146
+ * @param {number} rowIndex - 0-based row index.
2147
+ * @param {number} colIndex - 0-based column index.
2148
+ * @param {number} shapeIndex - 0-based shape index in the cell.
2149
+ * @returns {this} The chainable presentation templater instance.
2150
+ */
2151
+ async removeCellShape(tableId, rowIndex, colIndex, shapeIndex) {
2152
+ this.#assertLoaded()
2153
+ const targetIndices = this.#getTargetSlideIndices()
2154
+ for (const idx of targetIndices) {
2155
+ this.#tableManager.removeCellShape(
2156
+ idx,
2157
+ tableId,
2158
+ rowIndex,
2159
+ colIndex,
2160
+ shapeIndex,
2161
+ this.#slideManager,
2162
+ this.#shapeManager
2163
+ )
2164
+ }
2165
+ return this
2166
+ }
2167
+
2168
+ /**
2169
+ * Discovers and retrieves details of an existing cell shape on the targeted slide.
2170
+ *
2171
+ * @param {string} tableId - Table name or shape ID.
2172
+ * @param {number} rowIndex - 0-based row index.
2173
+ * @param {number} colIndex - 0-based column index.
2174
+ * @param {number} shapeIndex - 0-based shape index in the cell.
2175
+ * @returns {Object|null} Shape details object, or null if not found.
2176
+ */
2177
+ getCellShape(tableId, rowIndex, colIndex, shapeIndex) {
2178
+ this.#assertLoaded()
2179
+ const targetIndices = this.#getTargetSlideIndices()
2180
+ for (const idx of targetIndices) {
2181
+ const shape = this.#tableManager.getCellShape(
2182
+ idx,
2183
+ tableId,
2184
+ rowIndex,
2185
+ colIndex,
2186
+ shapeIndex,
2187
+ this.#slideManager,
2188
+ this.#shapeManager
2189
+ )
2190
+ if (shape) return shape
2191
+ }
2192
+ return null
2193
+ }
2194
+
2195
+ /**
2196
+ * Retrieves final rendered bounds of a table cell in pixels.
2197
+ *
2198
+ * @param {string} tableId - Table name or shape ID.
2199
+ * @param {number} rowIndex - 0-based row index.
2200
+ * @param {number} colIndex - 0-based column index.
2201
+ * @returns {Object|null} Cell bounds { x, y, width, height } in pixels, or null.
2202
+ */
2203
+ getCellBounds(tableId, rowIndex, colIndex) {
2204
+ this.#assertLoaded()
2205
+ const targetIndices = this.#getTargetSlideIndices()
2206
+ for (const idx of targetIndices) {
2207
+ try {
2208
+ const bounds = this.#tableManager.getCellBounds(
2209
+ idx,
2210
+ tableId,
2211
+ rowIndex,
2212
+ colIndex,
2213
+ this.#slideManager
2214
+ )
2215
+ if (bounds) return bounds
2216
+ } catch (err) {
2217
+ logger.debug(
2218
+ `Could not get cell bounds for table ${tableId} on slide ${idx}: ${err.message}`
2219
+ )
2220
+ }
2221
+ }
2222
+ return null
2223
+ }
2224
+
2225
+ /**
2226
+ * Retrieves final rendered position of a table cell in pixels.
2227
+ *
2228
+ * @param {string} tableId - Table name or shape ID.
2229
+ * @param {number} rowIndex - 0-based row index.
2230
+ * @param {number} colIndex - 0-based column index.
2231
+ * @returns {Object|null} Cell position { row, column, x, y } in pixels, or null.
2232
+ */
2233
+ getCellPosition(tableId, rowIndex, colIndex) {
2234
+ this.#assertLoaded()
2235
+ const targetIndices = this.#getTargetSlideIndices()
2236
+ for (const idx of targetIndices) {
2237
+ try {
2238
+ const pos = this.#tableManager.getCellPosition(
2239
+ idx,
2240
+ tableId,
2241
+ rowIndex,
2242
+ colIndex,
2243
+ this.#slideManager
2244
+ )
2245
+ if (pos) return pos
2246
+ } catch (err) {
2247
+ logger.debug(
2248
+ `Could not get cell position for table ${tableId} on slide ${idx}: ${err.message}`
2249
+ )
2250
+ }
2251
+ }
2252
+ return null
2253
+ }
2254
+
1917
2255
  // === Image Features ===
1918
2256
  async replaceImage(imageIdOrName, sourcePathOrBuffer) {
1919
2257
  this.#assertLoaded()