openfig-cli 0.3.28 → 0.3.30
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/bin/commands/clone-slide.mjs +50 -1
- package/lib/rasterizer/svg-builder.mjs +21 -1
- package/manifest.json +1 -1
- package/package.json +1 -1
|
@@ -124,6 +124,51 @@ export async function run(args, flags) {
|
|
|
124
124
|
delete newInst.derivedSymbolDataLayoutVersion;
|
|
125
125
|
delete newInst.editInfo;
|
|
126
126
|
|
|
127
|
+
// Clone sibling nodes (non-INSTANCE children of template slide, e.g. logo vectors)
|
|
128
|
+
const tmplSlideId = nid(tmplSlide);
|
|
129
|
+
const tmplInstId = nid(tmplInst);
|
|
130
|
+
const tmplChildren = deck.childrenMap.get(tmplSlideId) || [];
|
|
131
|
+
const siblingNodes = [];
|
|
132
|
+
const idRemap = new Map();
|
|
133
|
+
|
|
134
|
+
for (const child of tmplChildren) {
|
|
135
|
+
if (nid(child) === tmplInstId) continue;
|
|
136
|
+
if (child.phase === 'REMOVED') continue;
|
|
137
|
+
|
|
138
|
+
// Collect this node and all descendants
|
|
139
|
+
const subtree = [];
|
|
140
|
+
function collectSubtree(nodeId) {
|
|
141
|
+
const node = deck.getNode(nodeId);
|
|
142
|
+
if (!node || node.phase === 'REMOVED') return;
|
|
143
|
+
subtree.push(node);
|
|
144
|
+
const kids = deck.childrenMap.get(nodeId) || [];
|
|
145
|
+
for (const kid of kids) collectSubtree(nid(kid));
|
|
146
|
+
}
|
|
147
|
+
collectSubtree(nid(child));
|
|
148
|
+
|
|
149
|
+
// Clone each node with new IDs, re-parenting to new slide
|
|
150
|
+
for (const node of subtree) {
|
|
151
|
+
const oldId = nid(node);
|
|
152
|
+
const newLocalId = nextId++;
|
|
153
|
+
idRemap.set(oldId, newLocalId);
|
|
154
|
+
|
|
155
|
+
const cloned = deepClone(node);
|
|
156
|
+
cloned.guid = { sessionID: 1, localID: newLocalId };
|
|
157
|
+
cloned.phase = 'CREATED';
|
|
158
|
+
delete cloned.editInfo;
|
|
159
|
+
|
|
160
|
+
if (cloned.parentIndex?.guid) {
|
|
161
|
+
const parentOldId = `${cloned.parentIndex.guid.sessionID}:${cloned.parentIndex.guid.localID}`;
|
|
162
|
+
if (parentOldId === tmplSlideId) {
|
|
163
|
+
cloned.parentIndex.guid = { sessionID: 1, localID: slideId };
|
|
164
|
+
} else if (idRemap.has(parentOldId)) {
|
|
165
|
+
cloned.parentIndex.guid = { sessionID: 1, localID: idRemap.get(parentOldId) };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
siblingNodes.push(cloned);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
127
172
|
// Apply text overrides
|
|
128
173
|
for (const pair of sets) {
|
|
129
174
|
const eqIdx = pair.indexOf('=');
|
|
@@ -176,10 +221,14 @@ export async function run(args, flags) {
|
|
|
176
221
|
}
|
|
177
222
|
deck.message.nodeChanges.push(newSlide);
|
|
178
223
|
deck.message.nodeChanges.push(newInst);
|
|
224
|
+
for (const sib of siblingNodes) {
|
|
225
|
+
deck.message.nodeChanges.push(sib);
|
|
226
|
+
}
|
|
179
227
|
deck.rebuildMaps();
|
|
180
228
|
|
|
181
229
|
const moduleNote = newModule ? ` + MODULE 1:${moduleId}` : '';
|
|
182
|
-
|
|
230
|
+
const sibNote = siblingNodes.length ? `, ${siblingNodes.length} sibling node(s)` : '';
|
|
231
|
+
console.log(`Cloned slide "${tmplSlide.name}" → "${newName}" (1:${slideId} + 1:${instId}${moduleNote}${sibNote})`);
|
|
183
232
|
console.log(` ${sets.length} text override(s), ${setImages.length} image override(s)`);
|
|
184
233
|
|
|
185
234
|
const bytes = await deck.saveDeck(outPath);
|
|
@@ -271,6 +271,8 @@ function esc(s) {
|
|
|
271
271
|
*/
|
|
272
272
|
function isStaleLayout(chars, baselines, glyphs) {
|
|
273
273
|
if (!chars) return false;
|
|
274
|
+
// No derivedTextData at all (e.g. programmatically created text)
|
|
275
|
+
if (!baselines?.length && !glyphs?.length) return true;
|
|
274
276
|
const len = chars.length;
|
|
275
277
|
|
|
276
278
|
if (baselines?.length) {
|
|
@@ -356,9 +358,27 @@ function fallbackTextTspans(dispChars, fontSize, node) {
|
|
|
356
358
|
: adjLineAscent;
|
|
357
359
|
}
|
|
358
360
|
|
|
361
|
+
// List marker support: read lineType from textData.lines
|
|
362
|
+
const linesMeta = node.textData?.lines ?? [];
|
|
363
|
+
let orderedCounter = 0;
|
|
364
|
+
|
|
359
365
|
const tspans = lines.map((line, i) => {
|
|
360
366
|
const y = startY + i * adjLineHeight;
|
|
361
|
-
|
|
367
|
+
const meta = linesMeta[i];
|
|
368
|
+
const lineType = meta?.lineType;
|
|
369
|
+
const indent = (meta?.indentationLevel ?? 0) * adjFontSize * 0.8;
|
|
370
|
+
let prefix = '';
|
|
371
|
+
if (lineType === 'UNORDERED_LIST') {
|
|
372
|
+
prefix = '\u2022 '; // bullet •
|
|
373
|
+
orderedCounter = 0;
|
|
374
|
+
} else if (lineType === 'ORDERED_LIST') {
|
|
375
|
+
orderedCounter++;
|
|
376
|
+
prefix = `${orderedCounter}. `;
|
|
377
|
+
} else {
|
|
378
|
+
orderedCounter = 0;
|
|
379
|
+
}
|
|
380
|
+
const x = startX + indent;
|
|
381
|
+
return `<tspan x="${x.toFixed(2)}" y="${y.toFixed(2)}" text-anchor="${anchor}">${esc(prefix + line) || ' '}</tspan>`;
|
|
362
382
|
}).join('');
|
|
363
383
|
|
|
364
384
|
return { tspans, fontSize: adjFontSize };
|
package/manifest.json
CHANGED