node-pptx-templater 1.1.1 → 1.1.3
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 +16 -0
- package/README.md +34 -10
- package/package.json +2 -1
- package/src/core/OutputWriter.js +5 -5
- package/src/core/PPTXTemplater.js +131 -24
- package/src/core/ValidationEngine.js +12 -0
- package/src/managers/HyperlinkManager.js +3 -3
- package/src/managers/TableManager.js +44 -164
- package/src/managers/ZipManager.js +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.1.3] - 2026-06-22
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **Layout Preservation in `addCellShape()`**: Fixed a critical layout bug where querying cell bounds or adding cell shapes dynamically (via `addCellShape()` or `updateCellShape()`) mutated table row heights inside the XML. The sizing/positioning logic now runs in a layout-only mode, keeping the table XML completely untouched and respecting original template custom row heights.
|
|
13
|
+
|
|
14
|
+
## [1.1.2] - 2026-06-22
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **Slide Link Relationships Target Paths**: Fixed PowerPoint "Repair Mode" errors when adding slide-to-slide hyperlinks (via `addSlideLink` or `addImageLink`). The relative target path resolves correctly to the sibling filename `slideX.xml` instead of using the redundant parent prefix `../slides/slideX.xml` (which exited and re-entered the same directory, violating PowerPoint's relative path resolution rules).
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- **Redundant Traversal Validation**: Added automated check in `ValidationEngine` to identify and error on redundant relative path traversals inside relationship files (e.g. referencing `../slides/slideX.xml` from a source slide file already located in `ppt/slides/`).
|
|
23
|
+
|
|
8
24
|
## [1.1.0] - 2026-06-12
|
|
9
25
|
|
|
10
26
|
### Added
|
package/README.md
CHANGED
|
@@ -172,27 +172,51 @@ const meta = await ppt.getTableRows('SalesTable', { includeMetadata: true });
|
|
|
172
172
|
```
|
|
173
173
|
|
|
174
174
|
### Table Cell Shapes
|
|
175
|
-
Cell shapes are overlay graphics anchored within table cells. They are positioned absolutely based on cell bounds, and **never** modify row heights, column widths, cell margins, or trigger table reflow.
|
|
175
|
+
Cell shapes are overlay graphics anchored within table cells. They are positioned absolutely based on cell bounds, and **never** modify row heights, column widths, cell margins, or trigger table reflow.
|
|
176
|
+
|
|
177
|
+
#### Merged Cell Support
|
|
178
|
+
When targeting a merged cell (spanned using `rowSpan` or `colSpan`), `addCellShape()` dynamically resolves the **actual rendered bounds** of the entire merged cell region (summing the width and height of the spanned rows/columns). The shape is positioned and aligned relative to this final merged boundary. Any offset `x` and `y` specified is interpreted relative to the top-left corner of the resolved merged cell region.
|
|
179
|
+
|
|
180
|
+
#### Alignment & Presets
|
|
181
|
+
Shapes can be aligned dynamically within the cell (or merged region) using preset positions or explicit alignments:
|
|
182
|
+
* **Presets**: Use `position` values like `'top-left'`, `'center'`, `'bottom-right'`, etc.
|
|
183
|
+
* **Alignment Options**: Set `alignX: 'left' | 'center' | 'right'` and `alignY: 'top' | 'middle' | 'bottom'`. If no alignments or offsets are provided, shapes default to true centering (`'center'`/`'middle'`).
|
|
184
|
+
* **Offsets & Padding**: Offsets (`x`, `y`) are applied relative to the resolved alignment. For instance, when `alignX: 'right'` is used, the shape is placed at `cellRight - shapeWidth - x` (defaulting to a `5px` padding if `x` is omitted).
|
|
176
185
|
|
|
177
186
|
```javascript
|
|
178
|
-
// Add a
|
|
179
|
-
await ppt.addCellShape('
|
|
187
|
+
// Add a status icon centered in a merged cell (rowSpan=2, colSpan=2)
|
|
188
|
+
await ppt.addCellShape('StatusTable', 1, 1, {
|
|
180
189
|
type: 'circle',
|
|
181
190
|
width: 12,
|
|
182
191
|
height: 12,
|
|
183
|
-
fill: '#10B981' // Green status dot
|
|
192
|
+
fill: '#10B981', // Green status dot
|
|
193
|
+
alignX: 'center',
|
|
194
|
+
alignY: 'middle' // Visually centered in the merged cell
|
|
184
195
|
});
|
|
185
196
|
|
|
186
|
-
// Add a badge with text and
|
|
187
|
-
await ppt.addCellShape('
|
|
197
|
+
// Add a badge with text and explicit offsets inside a merged cell
|
|
198
|
+
await ppt.addCellShape('StatusTable', 1, 1, {
|
|
188
199
|
type: 'badge',
|
|
189
|
-
text: '
|
|
190
|
-
fill: '#
|
|
191
|
-
|
|
192
|
-
|
|
200
|
+
text: 'Urgent',
|
|
201
|
+
fill: '#EF4444',
|
|
202
|
+
alignX: 'left',
|
|
203
|
+
alignY: 'top',
|
|
204
|
+
x: 5,
|
|
205
|
+
y: 3,
|
|
193
206
|
width: 50,
|
|
194
207
|
height: 16
|
|
195
208
|
});
|
|
209
|
+
|
|
210
|
+
// Add an indicator with bottom-right alignment and custom padding
|
|
211
|
+
await ppt.addCellShape('StatusTable', 1, 1, {
|
|
212
|
+
type: 'icon',
|
|
213
|
+
icon: 'up',
|
|
214
|
+
size: 14,
|
|
215
|
+
alignX: 'right',
|
|
216
|
+
alignY: 'bottom',
|
|
217
|
+
x: 4,
|
|
218
|
+
y: 2
|
|
219
|
+
});
|
|
196
220
|
```
|
|
197
221
|
|
|
198
222
|
### XML Folder Workflow
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-pptx-templater",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
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",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"example:table-extraction": "node examples/table-extraction.js",
|
|
38
38
|
"example:nested-rows": "node examples/nested-table-rows.js",
|
|
39
39
|
"example:xml-folder": "node examples/xml-folder-workflow.js",
|
|
40
|
+
"example:all": "node scripts/run-examples-check.js",
|
|
40
41
|
"prepublishOnly": "npm run lint && npm run test"
|
|
41
42
|
},
|
|
42
43
|
"keywords": [
|
package/src/core/OutputWriter.js
CHANGED
|
@@ -161,11 +161,11 @@ class OutputWriter {
|
|
|
161
161
|
: e.compressionMethod === 0
|
|
162
162
|
? 'STORE'
|
|
163
163
|
: `UNKNOWN(${e.compressionMethod})`
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
164
|
+
logger.info(e.name)
|
|
165
|
+
logger.info(`compressed: ${e.compressedSize}`)
|
|
166
|
+
logger.info(`uncompressed: ${e.uncompressedSize}`)
|
|
167
|
+
logger.info(`crc: ${e.crc32}`)
|
|
168
|
+
logger.info(`method: ${methodStr}`)
|
|
169
169
|
})
|
|
170
170
|
logger.info('--- End of ZIP debug output ---')
|
|
171
171
|
}
|
|
@@ -2654,6 +2654,16 @@ class PPTXTemplater {
|
|
|
2654
2654
|
return this.#shapeManager.validateShape(options)
|
|
2655
2655
|
}
|
|
2656
2656
|
|
|
2657
|
+
/**
|
|
2658
|
+
* Adds a new shape to the targeted slide(s).
|
|
2659
|
+
*
|
|
2660
|
+
* @param {string|Object} typeOrOptions Either the shape type string (e.g., 'rect', 'ellipse') or a full options object.
|
|
2661
|
+
* @param {Object} [options={}] Additional configuration options if type was specified as a string.
|
|
2662
|
+
* @returns {Promise<PPTXTemplater>} The templater instance for chaining.
|
|
2663
|
+
*
|
|
2664
|
+
* @example
|
|
2665
|
+
* await ppt.addShape('rect', { id: 'MyShape', x: 100, y: 100, width: 200, height: 100 });
|
|
2666
|
+
*/
|
|
2657
2667
|
async addShape(typeOrOptions, options = {}) {
|
|
2658
2668
|
this.#assertLoaded()
|
|
2659
2669
|
let resolvedOptions = {}
|
|
@@ -2727,6 +2737,8 @@ class PPTXTemplater {
|
|
|
2727
2737
|
|
|
2728
2738
|
/**
|
|
2729
2739
|
* Dynamically adds a shape inside a table cell based on cell coordinates.
|
|
2740
|
+
* Cell shapes are overlay graphics anchored independently of the table layout,
|
|
2741
|
+
* and adding a cell shape never modifies row heights, column widths, or table dimensions.
|
|
2730
2742
|
*
|
|
2731
2743
|
* @param {string} tableId - Table name or shape ID.
|
|
2732
2744
|
* @param {number} rowIndex - 0-based row index.
|
|
@@ -2736,14 +2748,28 @@ class PPTXTemplater {
|
|
|
2736
2748
|
*/
|
|
2737
2749
|
async addCellShape(tableId, rowIndex, colIndex, options) {
|
|
2738
2750
|
this.#assertLoaded()
|
|
2751
|
+
let row = rowIndex
|
|
2752
|
+
let col = colIndex
|
|
2753
|
+
let opts = options
|
|
2754
|
+
if (rowIndex && typeof rowIndex === 'object') {
|
|
2755
|
+
row = rowIndex.row !== undefined ? rowIndex.row : rowIndex.rowIndex
|
|
2756
|
+
col =
|
|
2757
|
+
rowIndex.column !== undefined
|
|
2758
|
+
? rowIndex.column
|
|
2759
|
+
: rowIndex.col !== undefined
|
|
2760
|
+
? rowIndex.col
|
|
2761
|
+
: rowIndex.colIndex
|
|
2762
|
+
opts = rowIndex
|
|
2763
|
+
}
|
|
2764
|
+
|
|
2739
2765
|
const targetIndices = this.#getTargetSlideIndices()
|
|
2740
2766
|
for (const idx of targetIndices) {
|
|
2741
2767
|
this.#tableManager.addCellShape(
|
|
2742
2768
|
idx,
|
|
2743
2769
|
tableId,
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2770
|
+
row,
|
|
2771
|
+
col,
|
|
2772
|
+
opts,
|
|
2747
2773
|
this.#slideManager,
|
|
2748
2774
|
this.#shapeManager
|
|
2749
2775
|
)
|
|
@@ -2763,15 +2789,36 @@ class PPTXTemplater {
|
|
|
2763
2789
|
*/
|
|
2764
2790
|
async updateCellShape(tableId, rowIndex, colIndex, shapeIndex, options) {
|
|
2765
2791
|
this.#assertLoaded()
|
|
2792
|
+
let row = rowIndex
|
|
2793
|
+
let col = colIndex
|
|
2794
|
+
let shpIdx = shapeIndex
|
|
2795
|
+
let opts = options
|
|
2796
|
+
if (rowIndex && typeof rowIndex === 'object') {
|
|
2797
|
+
row = rowIndex.row !== undefined ? rowIndex.row : rowIndex.rowIndex
|
|
2798
|
+
col =
|
|
2799
|
+
rowIndex.column !== undefined
|
|
2800
|
+
? rowIndex.column
|
|
2801
|
+
: rowIndex.col !== undefined
|
|
2802
|
+
? rowIndex.col
|
|
2803
|
+
: rowIndex.colIndex
|
|
2804
|
+
shpIdx =
|
|
2805
|
+
rowIndex.shapeIndex !== undefined
|
|
2806
|
+
? rowIndex.shapeIndex
|
|
2807
|
+
: rowIndex.shape !== undefined
|
|
2808
|
+
? rowIndex.shape
|
|
2809
|
+
: colIndex
|
|
2810
|
+
opts = options !== undefined ? options : rowIndex
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2766
2813
|
const targetIndices = this.#getTargetSlideIndices()
|
|
2767
2814
|
for (const idx of targetIndices) {
|
|
2768
2815
|
this.#tableManager.updateCellShape(
|
|
2769
2816
|
idx,
|
|
2770
2817
|
tableId,
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2818
|
+
row,
|
|
2819
|
+
col,
|
|
2820
|
+
shpIdx,
|
|
2821
|
+
opts,
|
|
2775
2822
|
this.#slideManager,
|
|
2776
2823
|
this.#shapeManager
|
|
2777
2824
|
)
|
|
@@ -2790,14 +2837,33 @@ class PPTXTemplater {
|
|
|
2790
2837
|
*/
|
|
2791
2838
|
async removeCellShape(tableId, rowIndex, colIndex, shapeIndex) {
|
|
2792
2839
|
this.#assertLoaded()
|
|
2840
|
+
let row = rowIndex
|
|
2841
|
+
let col = colIndex
|
|
2842
|
+
let shpIdx = shapeIndex
|
|
2843
|
+
if (rowIndex && typeof rowIndex === 'object') {
|
|
2844
|
+
row = rowIndex.row !== undefined ? rowIndex.row : rowIndex.rowIndex
|
|
2845
|
+
col =
|
|
2846
|
+
rowIndex.column !== undefined
|
|
2847
|
+
? rowIndex.column
|
|
2848
|
+
: rowIndex.col !== undefined
|
|
2849
|
+
? rowIndex.col
|
|
2850
|
+
: rowIndex.colIndex
|
|
2851
|
+
shpIdx =
|
|
2852
|
+
rowIndex.shapeIndex !== undefined
|
|
2853
|
+
? rowIndex.shapeIndex
|
|
2854
|
+
: rowIndex.shape !== undefined
|
|
2855
|
+
? rowIndex.shape
|
|
2856
|
+
: colIndex
|
|
2857
|
+
}
|
|
2858
|
+
|
|
2793
2859
|
const targetIndices = this.#getTargetSlideIndices()
|
|
2794
2860
|
for (const idx of targetIndices) {
|
|
2795
2861
|
this.#tableManager.removeCellShape(
|
|
2796
2862
|
idx,
|
|
2797
2863
|
tableId,
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2864
|
+
row,
|
|
2865
|
+
col,
|
|
2866
|
+
shpIdx,
|
|
2801
2867
|
this.#slideManager,
|
|
2802
2868
|
this.#shapeManager
|
|
2803
2869
|
)
|
|
@@ -2816,14 +2882,33 @@ class PPTXTemplater {
|
|
|
2816
2882
|
*/
|
|
2817
2883
|
getCellShape(tableId, rowIndex, colIndex, shapeIndex) {
|
|
2818
2884
|
this.#assertLoaded()
|
|
2885
|
+
let row = rowIndex
|
|
2886
|
+
let col = colIndex
|
|
2887
|
+
let shpIdx = shapeIndex
|
|
2888
|
+
if (rowIndex && typeof rowIndex === 'object') {
|
|
2889
|
+
row = rowIndex.row !== undefined ? rowIndex.row : rowIndex.rowIndex
|
|
2890
|
+
col =
|
|
2891
|
+
rowIndex.column !== undefined
|
|
2892
|
+
? rowIndex.column
|
|
2893
|
+
: rowIndex.col !== undefined
|
|
2894
|
+
? rowIndex.col
|
|
2895
|
+
: rowIndex.colIndex
|
|
2896
|
+
shpIdx =
|
|
2897
|
+
rowIndex.shapeIndex !== undefined
|
|
2898
|
+
? rowIndex.shapeIndex
|
|
2899
|
+
: rowIndex.shape !== undefined
|
|
2900
|
+
? rowIndex.shape
|
|
2901
|
+
: colIndex
|
|
2902
|
+
}
|
|
2903
|
+
|
|
2819
2904
|
const targetIndices = this.#getTargetSlideIndices()
|
|
2820
2905
|
for (const idx of targetIndices) {
|
|
2821
2906
|
const shape = this.#tableManager.getCellShape(
|
|
2822
2907
|
idx,
|
|
2823
2908
|
tableId,
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2909
|
+
row,
|
|
2910
|
+
col,
|
|
2911
|
+
shpIdx,
|
|
2827
2912
|
this.#slideManager,
|
|
2828
2913
|
this.#shapeManager
|
|
2829
2914
|
)
|
|
@@ -2846,16 +2931,22 @@ class PPTXTemplater {
|
|
|
2846
2931
|
typeof tableIdOrObj === 'object'
|
|
2847
2932
|
? tableIdOrObj.id || tableIdOrObj.name || tableIdOrObj.tableId
|
|
2848
2933
|
: tableIdOrObj
|
|
2934
|
+
let row = rowIndex
|
|
2935
|
+
let col = colIndex
|
|
2936
|
+
if (rowIndex && typeof rowIndex === 'object') {
|
|
2937
|
+
row = rowIndex.row !== undefined ? rowIndex.row : rowIndex.rowIndex
|
|
2938
|
+
col =
|
|
2939
|
+
rowIndex.column !== undefined
|
|
2940
|
+
? rowIndex.column
|
|
2941
|
+
: rowIndex.col !== undefined
|
|
2942
|
+
? rowIndex.col
|
|
2943
|
+
: rowIndex.colIndex
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2849
2946
|
const targetIndices = this.#getTargetSlideIndices()
|
|
2850
2947
|
for (const idx of targetIndices) {
|
|
2851
2948
|
try {
|
|
2852
|
-
const bounds = this.#tableManager.getCellBounds(
|
|
2853
|
-
idx,
|
|
2854
|
-
tableId,
|
|
2855
|
-
rowIndex,
|
|
2856
|
-
colIndex,
|
|
2857
|
-
this.#slideManager
|
|
2858
|
-
)
|
|
2949
|
+
const bounds = this.#tableManager.getCellBounds(idx, tableId, row, col, this.#slideManager)
|
|
2859
2950
|
if (bounds) return bounds
|
|
2860
2951
|
} catch (err) {
|
|
2861
2952
|
logger.debug(
|
|
@@ -2883,17 +2974,33 @@ class PPTXTemplater {
|
|
|
2883
2974
|
typeof tableIdOrObj === 'object'
|
|
2884
2975
|
? tableIdOrObj.id || tableIdOrObj.name || tableIdOrObj.tableId
|
|
2885
2976
|
: tableIdOrObj
|
|
2977
|
+
let row = rowIndex
|
|
2978
|
+
let col = colIndex
|
|
2979
|
+
let widthOrOpts = shapeWidthOrOptions
|
|
2980
|
+
let height = shapeHeight
|
|
2981
|
+
if (rowIndex && typeof rowIndex === 'object') {
|
|
2982
|
+
row = rowIndex.row !== undefined ? rowIndex.row : rowIndex.rowIndex
|
|
2983
|
+
col =
|
|
2984
|
+
rowIndex.column !== undefined
|
|
2985
|
+
? rowIndex.column
|
|
2986
|
+
: rowIndex.col !== undefined
|
|
2987
|
+
? rowIndex.col
|
|
2988
|
+
: rowIndex.colIndex
|
|
2989
|
+
widthOrOpts = colIndex
|
|
2990
|
+
height = shapeWidthOrOptions
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2886
2993
|
const targetIndices = this.#getTargetSlideIndices()
|
|
2887
2994
|
for (const idx of targetIndices) {
|
|
2888
2995
|
try {
|
|
2889
2996
|
const pos = this.#tableManager.getCellPosition(
|
|
2890
2997
|
idx,
|
|
2891
2998
|
tableId,
|
|
2892
|
-
|
|
2893
|
-
|
|
2999
|
+
row,
|
|
3000
|
+
col,
|
|
2894
3001
|
this.#slideManager,
|
|
2895
|
-
|
|
2896
|
-
|
|
3002
|
+
widthOrOpts,
|
|
3003
|
+
height
|
|
2897
3004
|
)
|
|
2898
3005
|
if (pos) return pos
|
|
2899
3006
|
} catch (err) {
|
|
@@ -237,6 +237,18 @@ class ValidationEngine {
|
|
|
237
237
|
if (!ppt.zipManager.hasFile(resolved)) {
|
|
238
238
|
errors.push(`Relationship ${rel.id} points to non-existent file: ${resolved}`)
|
|
239
239
|
}
|
|
240
|
+
|
|
241
|
+
// Catch redundant relative path traversals (e.g., '../slides/slide1.xml' from 'ppt/slides/slide2.xml')
|
|
242
|
+
const sourceDir = partPath.split('/').slice(0, -1).join('/')
|
|
243
|
+
const resolvedDir = resolved.split('/').slice(0, -1).join('/')
|
|
244
|
+
if (sourceDir === resolvedDir) {
|
|
245
|
+
const expectedTarget = resolved.split('/').pop()
|
|
246
|
+
if (rel.target !== expectedTarget) {
|
|
247
|
+
errors.push(
|
|
248
|
+
`Relationship "${rel.id}" in ${relsPath} uses redundant relative target "${rel.target}" (expected "${expectedTarget}")`
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
240
252
|
}
|
|
241
253
|
}
|
|
242
254
|
|
|
@@ -100,7 +100,7 @@ class HyperlinkManager {
|
|
|
100
100
|
const targetInfo = slideManager.getSlideInfo(targetSlideIndex)
|
|
101
101
|
|
|
102
102
|
// Build relative target path from source to target slide
|
|
103
|
-
const relativePath =
|
|
103
|
+
const relativePath = targetInfo.zipPath.split('/').pop()
|
|
104
104
|
|
|
105
105
|
// Add relationship pointing to the target slide
|
|
106
106
|
const rId = relationshipManager.addRelationship(
|
|
@@ -130,7 +130,7 @@ class HyperlinkManager {
|
|
|
130
130
|
const sourceInfo = slideManager.getSlideInfo(sourceSlideIndex)
|
|
131
131
|
const targetInfo = slideManager.getSlideInfo(targetSlideIndex)
|
|
132
132
|
|
|
133
|
-
const relativePath =
|
|
133
|
+
const relativePath = targetInfo.zipPath.split('/').pop()
|
|
134
134
|
const rId = relationshipManager.addRelationship(
|
|
135
135
|
sourceInfo.zipPath,
|
|
136
136
|
REL_TYPES.SLIDE,
|
|
@@ -167,7 +167,7 @@ class HyperlinkManager {
|
|
|
167
167
|
const sourceInfo = slideManager.getSlideInfo(sourceSlideIndex)
|
|
168
168
|
const targetInfo = slideManager.getSlideInfo(targetSlideIndex)
|
|
169
169
|
|
|
170
|
-
const relativePath =
|
|
170
|
+
const relativePath = targetInfo.zipPath.split('/').pop()
|
|
171
171
|
const rId = relationshipManager.addRelationship(
|
|
172
172
|
sourceInfo.zipPath,
|
|
173
173
|
REL_TYPES.SLIDE,
|
|
@@ -223,7 +223,7 @@ class TableManager {
|
|
|
223
223
|
)
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
-
this.#calculateRowHeights(slideIndex, tableId, slideManager, tblObj)
|
|
226
|
+
this.#calculateRowHeights(slideIndex, tableId, slideManager, tblObj, true)
|
|
227
227
|
|
|
228
228
|
if (cellShapes) {
|
|
229
229
|
this.#processCellShapes(
|
|
@@ -796,8 +796,6 @@ class TableManager {
|
|
|
796
796
|
getCellBounds(slideIndex, tableId, rowIndex, colIndex, slideManager) {
|
|
797
797
|
const { tblObj, frameObj } = this.#getTableContext(slideIndex, tableId, slideManager)
|
|
798
798
|
|
|
799
|
-
this.#calculateRowHeights(slideIndex, tableId, slideManager, tblObj)
|
|
800
|
-
|
|
801
799
|
const xfrm = frameObj['p:xfrm']
|
|
802
800
|
const tableX = xfrm?.['a:off']?.['@_x'] ? parseInt(xfrm['a:off']['@_x'], 10) : 0
|
|
803
801
|
const tableY = xfrm?.['a:off']?.['@_y'] ? parseInt(xfrm['a:off']['@_y'], 10) : 0
|
|
@@ -807,7 +805,7 @@ class TableManager {
|
|
|
807
805
|
const colWidths = gridColsArr.map(col => parseInt(col['@_w'] || 0, 10))
|
|
808
806
|
|
|
809
807
|
const trsArr = tblObj['a:tr'] || []
|
|
810
|
-
const rowHeights =
|
|
808
|
+
const rowHeights = this.#calculateRowHeights(slideIndex, tableId, slideManager, tblObj, false)
|
|
811
809
|
|
|
812
810
|
const parent = this.getMergeParent(slideIndex, tableId, rowIndex, colIndex, slideManager)
|
|
813
811
|
const pr = parent.row
|
|
@@ -1304,10 +1302,10 @@ class TableManager {
|
|
|
1304
1302
|
}
|
|
1305
1303
|
|
|
1306
1304
|
#expandCellShape(config, cellBounds) {
|
|
1307
|
-
const cellLeft_px =
|
|
1308
|
-
const cellTop_px =
|
|
1309
|
-
const cellWidth_px =
|
|
1310
|
-
const cellHeight_px =
|
|
1305
|
+
const cellLeft_px = cellBounds.x
|
|
1306
|
+
const cellTop_px = cellBounds.y
|
|
1307
|
+
const cellWidth_px = cellBounds.width
|
|
1308
|
+
const cellHeight_px = cellBounds.height
|
|
1311
1309
|
|
|
1312
1310
|
const parseLength = (val, maxVal) => {
|
|
1313
1311
|
if (typeof val === 'string' && val.endsWith('%')) {
|
|
@@ -1745,7 +1743,7 @@ class TableManager {
|
|
|
1745
1743
|
slideManager,
|
|
1746
1744
|
shapeManager,
|
|
1747
1745
|
tblObj,
|
|
1748
|
-
|
|
1746
|
+
_frameObj
|
|
1749
1747
|
) {
|
|
1750
1748
|
if (!cellShapes || !shapeManager) return
|
|
1751
1749
|
|
|
@@ -1763,62 +1761,16 @@ class TableManager {
|
|
|
1763
1761
|
}
|
|
1764
1762
|
}
|
|
1765
1763
|
|
|
1766
|
-
const xfrm = frameObj['p:xfrm']
|
|
1767
|
-
const tableX = xfrm?.['a:off']?.['@_x'] ? parseInt(xfrm['a:off']['@_x'], 10) : 0
|
|
1768
|
-
const tableY = xfrm?.['a:off']?.['@_y'] ? parseInt(xfrm['a:off']['@_y'], 10) : 0
|
|
1769
|
-
|
|
1770
|
-
const gridCols = tblObj['a:tblGrid']?.['a:gridCol'] || []
|
|
1771
|
-
const gridColsArr = Array.isArray(gridCols) ? gridCols : [gridCols]
|
|
1772
|
-
const colWidths = gridColsArr.map(col => parseInt(col['@_w'] || 0, 10))
|
|
1773
|
-
|
|
1774
|
-
const trsArr = tblObj['a:tr'] || []
|
|
1775
|
-
const rowHeights = trsArr.map(row => parseInt(row['@_h'] || 0, 10))
|
|
1776
|
-
|
|
1777
|
-
const getCellBounds = (r, c) => {
|
|
1778
|
-
const parent = this.getMergeParent(slideIndex, tableId, r, c, slideManager)
|
|
1779
|
-
const pr = parent.row
|
|
1780
|
-
const pc = parent.col
|
|
1781
|
-
|
|
1782
|
-
let cellLeft = tableX
|
|
1783
|
-
for (let idx = 0; idx < pc; idx++) {
|
|
1784
|
-
cellLeft += colWidths[idx] || 0
|
|
1785
|
-
}
|
|
1786
|
-
|
|
1787
|
-
let cellTop = tableY
|
|
1788
|
-
for (let idx = 0; idx < pr; idx++) {
|
|
1789
|
-
cellTop += rowHeights[idx] || 0
|
|
1790
|
-
}
|
|
1791
|
-
|
|
1792
|
-
const parentCell = trsArr[pr]?.['a:tc']?.[pc]
|
|
1793
|
-
const gridSpan = parentCell?.['@_gridSpan'] ? parseInt(parentCell['@_gridSpan'], 10) : 1
|
|
1794
|
-
const rowSpan = parentCell?.['@_rowSpan'] ? parseInt(parentCell['@_rowSpan'], 10) : 1
|
|
1795
|
-
|
|
1796
|
-
let cellWidth = 0
|
|
1797
|
-
for (let idx = 0; idx < gridSpan; idx++) {
|
|
1798
|
-
cellWidth += colWidths[pc + idx] || 0
|
|
1799
|
-
}
|
|
1800
|
-
|
|
1801
|
-
let cellHeight = 0
|
|
1802
|
-
for (let idx = 0; idx < rowSpan; idx++) {
|
|
1803
|
-
cellHeight += rowHeights[pr + idx] || 0
|
|
1804
|
-
}
|
|
1805
|
-
|
|
1806
|
-
return {
|
|
1807
|
-
left: cellLeft,
|
|
1808
|
-
top: cellTop,
|
|
1809
|
-
width: cellWidth,
|
|
1810
|
-
height: cellHeight,
|
|
1811
|
-
}
|
|
1812
|
-
}
|
|
1813
|
-
|
|
1814
1764
|
const shapesToCreate = []
|
|
1815
|
-
const headerNames = (
|
|
1765
|
+
const headerNames = (tblObj['a:tr']?.[0]?.['a:tc'] || []).map(cell =>
|
|
1766
|
+
this.#getCellText(cell).trim()
|
|
1767
|
+
)
|
|
1816
1768
|
|
|
1817
1769
|
for (let i = 0; i < rowsData.length; i++) {
|
|
1818
1770
|
const rowData = rowsData[i]
|
|
1819
1771
|
const finalRowIndex = isObjectRows ? i + 1 : i
|
|
1820
1772
|
|
|
1821
|
-
const numCols =
|
|
1773
|
+
const numCols = tblObj['a:tr']?.[finalRowIndex]?.['a:tc']?.length || 0
|
|
1822
1774
|
for (let j = 0; j < numCols; j++) {
|
|
1823
1775
|
const headerName = headerNames[j]
|
|
1824
1776
|
let shapeFn = null
|
|
@@ -1853,16 +1805,24 @@ class TableManager {
|
|
|
1853
1805
|
shapesToCreate.sort((a, b) => (a.config.zIndex || 0) - (b.config.zIndex || 0))
|
|
1854
1806
|
|
|
1855
1807
|
shapesToCreate.forEach(item => {
|
|
1856
|
-
const bounds = getCellBounds(
|
|
1857
|
-
|
|
1808
|
+
const bounds = this.getCellBounds(
|
|
1809
|
+
slideIndex,
|
|
1810
|
+
tableId,
|
|
1811
|
+
item.rowIndex,
|
|
1812
|
+
item.colIndex,
|
|
1813
|
+
slideManager
|
|
1814
|
+
)
|
|
1815
|
+
if (bounds) {
|
|
1816
|
+
const expandedConfigs = this.#expandCellShape(item.config, bounds)
|
|
1858
1817
|
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1818
|
+
expandedConfigs.forEach((expandedConfig, expIdx) => {
|
|
1819
|
+
const finalShapeIndex =
|
|
1820
|
+
expandedConfigs.length > 1 ? `${item.shapeIndex}_${expIdx}` : item.shapeIndex
|
|
1821
|
+
expandedConfig.id = `cellshape_${resolvedTableId}_${item.rowIndex}_${item.colIndex}_${finalShapeIndex}`
|
|
1863
1822
|
|
|
1864
|
-
|
|
1865
|
-
|
|
1823
|
+
shapeManager.addShape(slideIndex, expandedConfig, slideManager)
|
|
1824
|
+
})
|
|
1825
|
+
}
|
|
1866
1826
|
})
|
|
1867
1827
|
}
|
|
1868
1828
|
|
|
@@ -1930,55 +1890,13 @@ class TableManager {
|
|
|
1930
1890
|
}
|
|
1931
1891
|
|
|
1932
1892
|
addCellShape(slideIndex, tableId, rowIndex, colIndex, options, slideManager, shapeManager) {
|
|
1933
|
-
const {
|
|
1934
|
-
slideIndex,
|
|
1935
|
-
tableId,
|
|
1936
|
-
slideManager
|
|
1937
|
-
)
|
|
1938
|
-
|
|
1939
|
-
this.#calculateRowHeights(slideIndex, tableId, slideManager, tblObj)
|
|
1940
|
-
|
|
1941
|
-
const xfrm = frameObj['p:xfrm']
|
|
1942
|
-
const tableX = xfrm?.['a:off']?.['@_x'] ? parseInt(xfrm['a:off']['@_x'], 10) : 0
|
|
1943
|
-
const tableY = xfrm?.['a:off']?.['@_y'] ? parseInt(xfrm['a:off']['@_y'], 10) : 0
|
|
1944
|
-
|
|
1945
|
-
const gridCols = tblObj['a:tblGrid']?.['a:gridCol'] || []
|
|
1946
|
-
const gridColsArr = Array.isArray(gridCols) ? gridCols : [gridCols]
|
|
1947
|
-
const colWidths = gridColsArr.map(col => parseInt(col['@_w'] || 0, 10))
|
|
1948
|
-
|
|
1949
|
-
const trsArr = tblObj['a:tr'] || []
|
|
1950
|
-
const rowHeights = trsArr.map(row => parseInt(row['@_h'] || 0, 10))
|
|
1951
|
-
|
|
1952
|
-
const parent = this.getMergeParent(slideIndex, tableId, rowIndex, colIndex, slideManager)
|
|
1953
|
-
const pr = parent.row
|
|
1954
|
-
const pc = parent.col
|
|
1955
|
-
|
|
1956
|
-
let cellLeft = tableX
|
|
1957
|
-
for (let idx = 0; idx < pc; idx++) {
|
|
1958
|
-
cellLeft += colWidths[idx] || 0
|
|
1959
|
-
}
|
|
1960
|
-
|
|
1961
|
-
let cellTop = tableY
|
|
1962
|
-
for (let idx = 0; idx < pr; idx++) {
|
|
1963
|
-
cellTop += rowHeights[idx] || 0
|
|
1964
|
-
}
|
|
1965
|
-
|
|
1966
|
-
const parentCell = trsArr[pr]?.['a:tc']?.[pc]
|
|
1967
|
-
const gridSpan = parentCell?.['@_gridSpan'] ? parseInt(parentCell['@_gridSpan'], 10) : 1
|
|
1968
|
-
const rowSpan = parentCell?.['@_rowSpan'] ? parseInt(parentCell['@_rowSpan'], 10) : 1
|
|
1969
|
-
|
|
1970
|
-
let cellWidth = 0
|
|
1971
|
-
for (let idx = 0; idx < gridSpan; idx++) {
|
|
1972
|
-
cellWidth += colWidths[pc + idx] || 0
|
|
1973
|
-
}
|
|
1893
|
+
const { resolvedTableId } = this.#getTableContext(slideIndex, tableId, slideManager)
|
|
1974
1894
|
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1895
|
+
const bounds = this.getCellBounds(slideIndex, tableId, rowIndex, colIndex, slideManager)
|
|
1896
|
+
if (!bounds) {
|
|
1897
|
+
throw new PPTXError(`Could not calculate bounds for cell (${rowIndex}, ${colIndex})`)
|
|
1978
1898
|
}
|
|
1979
1899
|
|
|
1980
|
-
const bounds = { left: cellLeft, top: cellTop, width: cellWidth, height: cellHeight }
|
|
1981
|
-
|
|
1982
1900
|
const shapes = shapeManager.getShapes(slideIndex, slideManager)
|
|
1983
1901
|
const prefix = `cellshape_${resolvedTableId}_${rowIndex}_${colIndex}_`
|
|
1984
1902
|
let maxShapeIndex = -1
|
|
@@ -2015,13 +1933,7 @@ class TableManager {
|
|
|
2015
1933
|
slideManager,
|
|
2016
1934
|
shapeManager
|
|
2017
1935
|
) {
|
|
2018
|
-
const {
|
|
2019
|
-
slideIndex,
|
|
2020
|
-
tableId,
|
|
2021
|
-
slideManager
|
|
2022
|
-
)
|
|
2023
|
-
|
|
2024
|
-
this.#calculateRowHeights(slideIndex, tableId, slideManager, tblObj)
|
|
1936
|
+
const { resolvedTableId } = this.#getTableContext(slideIndex, tableId, slideManager)
|
|
2025
1937
|
|
|
2026
1938
|
const shapes = shapeManager.getShapes(slideIndex, slideManager)
|
|
2027
1939
|
const prefix = `cellshape_${resolvedTableId}_${rowIndex}_${colIndex}_${shapeIndex}`
|
|
@@ -2037,47 +1949,11 @@ class TableManager {
|
|
|
2037
1949
|
shapeManager.deleteShape(slideIndex, s.name, slideManager)
|
|
2038
1950
|
}
|
|
2039
1951
|
|
|
2040
|
-
const
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
const gridCols = tblObj['a:tblGrid']?.['a:gridCol'] || []
|
|
2045
|
-
const gridColsArr = Array.isArray(gridCols) ? gridCols : [gridCols]
|
|
2046
|
-
const colWidths = gridColsArr.map(col => parseInt(col['@_w'] || 0, 10))
|
|
2047
|
-
|
|
2048
|
-
const trsArr = tblObj['a:tr'] || []
|
|
2049
|
-
const rowHeights = trsArr.map(row => parseInt(row['@_h'] || 0, 10))
|
|
2050
|
-
|
|
2051
|
-
const parent = this.getMergeParent(slideIndex, tableId, rowIndex, colIndex, slideManager)
|
|
2052
|
-
const pr = parent.row
|
|
2053
|
-
const pc = parent.col
|
|
2054
|
-
|
|
2055
|
-
let cellLeft = tableX
|
|
2056
|
-
for (let idx = 0; idx < pc; idx++) {
|
|
2057
|
-
cellLeft += colWidths[idx] || 0
|
|
2058
|
-
}
|
|
2059
|
-
|
|
2060
|
-
let cellTop = tableY
|
|
2061
|
-
for (let idx = 0; idx < pr; idx++) {
|
|
2062
|
-
cellTop += rowHeights[idx] || 0
|
|
2063
|
-
}
|
|
2064
|
-
|
|
2065
|
-
const parentCell = trsArr[pr]?.['a:tc']?.[pc]
|
|
2066
|
-
const gridSpan = parentCell?.['@_gridSpan'] ? parseInt(parentCell['@_gridSpan'], 10) : 1
|
|
2067
|
-
const rowSpan = parentCell?.['@_rowSpan'] ? parseInt(parentCell['@_rowSpan'], 10) : 1
|
|
2068
|
-
|
|
2069
|
-
let cellWidth = 0
|
|
2070
|
-
for (let idx = 0; idx < gridSpan; idx++) {
|
|
2071
|
-
cellWidth += colWidths[pc + idx] || 0
|
|
2072
|
-
}
|
|
2073
|
-
|
|
2074
|
-
let cellHeight = 0
|
|
2075
|
-
for (let idx = 0; idx < rowSpan; idx++) {
|
|
2076
|
-
cellHeight += rowHeights[pr + idx] || 0
|
|
1952
|
+
const bounds = this.getCellBounds(slideIndex, tableId, rowIndex, colIndex, slideManager)
|
|
1953
|
+
if (!bounds) {
|
|
1954
|
+
throw new PPTXError(`Could not calculate bounds for cell (${rowIndex}, ${colIndex})`)
|
|
2077
1955
|
}
|
|
2078
1956
|
|
|
2079
|
-
const bounds = { left: cellLeft, top: cellTop, width: cellWidth, height: cellHeight }
|
|
2080
|
-
|
|
2081
1957
|
const expandedConfigs = this.#expandCellShape(options, bounds)
|
|
2082
1958
|
|
|
2083
1959
|
expandedConfigs.forEach((expandedConfig, expIdx) => {
|
|
@@ -2158,7 +2034,7 @@ class TableManager {
|
|
|
2158
2034
|
}
|
|
2159
2035
|
}
|
|
2160
2036
|
|
|
2161
|
-
#calculateRowHeights(slideIndex, tableId, slideManager, tblObj) {
|
|
2037
|
+
#calculateRowHeights(slideIndex, tableId, slideManager, tblObj, writeToXml = true) {
|
|
2162
2038
|
const trsArr = tblObj['a:tr'] || []
|
|
2163
2039
|
if (trsArr.length === 0) return []
|
|
2164
2040
|
|
|
@@ -2172,7 +2048,7 @@ class TableManager {
|
|
|
2172
2048
|
// Initialize rowHeights with original height or a safe minimum floor of 228600 EMUs (~24px/pt)
|
|
2173
2049
|
const rowHeights = trsArr.map(row => {
|
|
2174
2050
|
const h = parseInt(row['@_h'] || 0, 10)
|
|
2175
|
-
return
|
|
2051
|
+
return h > 0 ? h : 228600
|
|
2176
2052
|
})
|
|
2177
2053
|
|
|
2178
2054
|
// Helper to get paragraph font size
|
|
@@ -2295,7 +2171,9 @@ class TableManager {
|
|
|
2295
2171
|
}
|
|
2296
2172
|
|
|
2297
2173
|
const totalCellHeight_emu = marT + marB + textHeight_emu
|
|
2298
|
-
|
|
2174
|
+
const rowTemplateHeight = parseInt(row['@_h'] || 0, 10)
|
|
2175
|
+
const minFloor = rowTemplateHeight > 0 ? rowTemplateHeight : 228600
|
|
2176
|
+
cellHeights[r][c] = Math.max(totalCellHeight_emu, minFloor)
|
|
2299
2177
|
}
|
|
2300
2178
|
}
|
|
2301
2179
|
|
|
@@ -2346,8 +2224,10 @@ class TableManager {
|
|
|
2346
2224
|
}
|
|
2347
2225
|
|
|
2348
2226
|
// Update row heights in XML
|
|
2349
|
-
|
|
2350
|
-
|
|
2227
|
+
if (writeToXml) {
|
|
2228
|
+
for (let r = 0; r < numRows; r++) {
|
|
2229
|
+
trsArr[r]['@_h'] = String(rowHeights[r])
|
|
2230
|
+
}
|
|
2351
2231
|
}
|
|
2352
2232
|
|
|
2353
2233
|
return rowHeights
|
|
@@ -646,7 +646,11 @@ class ZipManager {
|
|
|
646
646
|
await fs.writeFile(targetPath, this.#dirtyBinaryFiles.get(relPath))
|
|
647
647
|
} else {
|
|
648
648
|
const srcPath = path.join(this.#folderRoot, relPath)
|
|
649
|
-
|
|
649
|
+
const resolvedSrc = path.resolve(srcPath)
|
|
650
|
+
const resolvedDest = path.resolve(targetPath)
|
|
651
|
+
if (resolvedSrc !== resolvedDest) {
|
|
652
|
+
await fs.copy(srcPath, targetPath)
|
|
653
|
+
}
|
|
650
654
|
}
|
|
651
655
|
}
|
|
652
656
|
} else {
|