openfig-cli 0.3.23 → 0.3.24
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/lib/core/fig-deck.mjs +65 -18
- package/manifest.json +1 -1
- package/package.json +1 -1
package/lib/core/fig-deck.mjs
CHANGED
|
@@ -366,6 +366,39 @@ export class FigDeck {
|
|
|
366
366
|
}
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
+
// Remap blob indices (commandsBlob, vectorNetworkBlob, fillGeometry etc.)
|
|
370
|
+
// Blobs in source deck reference positions in sourceDeck.message.blobs.
|
|
371
|
+
// Copy each referenced blob to target and update the index.
|
|
372
|
+
const remapBlobIndex = (idx) => {
|
|
373
|
+
if (idx == null || idx < 0) return idx;
|
|
374
|
+
if (!sourceDeck.message.blobs || idx >= sourceDeck.message.blobs.length) return idx;
|
|
375
|
+
if (!this._blobRemap) this._blobRemap = new Map();
|
|
376
|
+
if (this._blobRemap.has(idx)) return this._blobRemap.get(idx);
|
|
377
|
+
const blob = sourceDeck.message.blobs[idx];
|
|
378
|
+
if (!this.message.blobs) this.message.blobs = [];
|
|
379
|
+
const newIdx = this.message.blobs.length;
|
|
380
|
+
this.message.blobs.push(deepClone(blob));
|
|
381
|
+
this._blobRemap.set(idx, newIdx);
|
|
382
|
+
return newIdx;
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
// fillGeometry[].commandsBlob
|
|
386
|
+
if (clone.fillGeometry) {
|
|
387
|
+
for (const fg of clone.fillGeometry) {
|
|
388
|
+
if (fg.commandsBlob != null) fg.commandsBlob = remapBlobIndex(fg.commandsBlob);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
// strokeGeometry[].commandsBlob
|
|
392
|
+
if (clone.strokeGeometry) {
|
|
393
|
+
for (const sg of clone.strokeGeometry) {
|
|
394
|
+
if (sg.commandsBlob != null) sg.commandsBlob = remapBlobIndex(sg.commandsBlob);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
// vectorNetworkBlob (VECTOR nodes)
|
|
398
|
+
if (clone.vectorNetworkBlob != null) {
|
|
399
|
+
clone.vectorNetworkBlob = remapBlobIndex(clone.vectorNetworkBlob);
|
|
400
|
+
}
|
|
401
|
+
|
|
369
402
|
clone.phase = 'CREATED';
|
|
370
403
|
delete clone.slideThumbnailHash;
|
|
371
404
|
delete clone.editInfo;
|
|
@@ -374,6 +407,9 @@ export class FigDeck {
|
|
|
374
407
|
return clone;
|
|
375
408
|
});
|
|
376
409
|
|
|
410
|
+
// Clear blob remap cache after processing this symbol's subtree
|
|
411
|
+
delete this._blobRemap;
|
|
412
|
+
|
|
377
413
|
// Copy referenced images from source deck
|
|
378
414
|
if (sourceDeck.imagesDir) {
|
|
379
415
|
if (!this.imagesDir) {
|
|
@@ -555,25 +591,36 @@ export class FigDeck {
|
|
|
555
591
|
const bgColor = extractColor(bgPaints) || { r: 1, g: 1, b: 1 };
|
|
556
592
|
const bgLum = luminance(bgColor.r, bgColor.g, bgColor.b);
|
|
557
593
|
|
|
558
|
-
// Walk all descendants looking for TEXT nodes
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
594
|
+
// Walk all descendants looking for TEXT nodes — follow INSTANCE → SYMBOL
|
|
595
|
+
// links so we reach TEXT inside components (walkTree alone stops at INSTANCE).
|
|
596
|
+
const walkIntoSymbols = (rootId, visited = new Set()) => {
|
|
597
|
+
this.walkTree(rootId, (node) => {
|
|
598
|
+
if (node.type === 'TEXT') {
|
|
599
|
+
const textPaints = node.fillPaints;
|
|
600
|
+
const textColor = extractColor(textPaints);
|
|
601
|
+
if (!textColor) return;
|
|
602
|
+
const textLum = luminance(textColor.r, textColor.g, textColor.b);
|
|
603
|
+
const ratio = contrastRatio(bgLum, textLum);
|
|
604
|
+
if (ratio < 2) {
|
|
605
|
+
const nodeId = nid(node);
|
|
606
|
+
let msg = `TEXT ${nodeId} "${node.name || ''}": contrast ratio ${ratio.toFixed(2)}:1 against slide background is below 2:1`;
|
|
607
|
+
if (node.colorVar) {
|
|
608
|
+
msg += ` (uses colorVar "${node.colorVar}" — may resolve differently in Figma)`;
|
|
609
|
+
}
|
|
610
|
+
warnings.push(msg);
|
|
611
|
+
}
|
|
573
612
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
613
|
+
if (node.type === 'INSTANCE' && node.symbolData?.symbolID) {
|
|
614
|
+
const sid = node.symbolData.symbolID;
|
|
615
|
+
const symNid = `${sid.sessionID}:${sid.localID}`;
|
|
616
|
+
if (!visited.has(symNid)) {
|
|
617
|
+
visited.add(symNid);
|
|
618
|
+
walkIntoSymbols(symNid, visited);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
};
|
|
623
|
+
walkIntoSymbols(slideId);
|
|
577
624
|
}
|
|
578
625
|
|
|
579
626
|
for (const w of warnings) console.warn(`⚠️ ${w}`);
|
package/manifest.json
CHANGED