figmatk 0.3.7 → 0.3.8
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.
|
@@ -4,10 +4,6 @@
|
|
|
4
4
|
* Architecture: dispatcher pattern — each Figma node type maps to a render
|
|
5
5
|
* function. Unknown types emit a magenta placeholder rect so renders never
|
|
6
6
|
* crash. Add handlers incrementally as coverage grows.
|
|
7
|
-
*
|
|
8
|
-
* TODO: Symbol instance resolution (INSTANCE → SYMBOL + apply overrides).
|
|
9
|
-
* Until that is implemented, INSTANCE nodes render as placeholders.
|
|
10
|
-
* For slides that use direct nodes (not template instances), this works fully.
|
|
11
7
|
*/
|
|
12
8
|
|
|
13
9
|
import { readFileSync } from 'fs';
|
|
@@ -511,6 +507,67 @@ function renderPlaceholder(deck, node) {
|
|
|
511
507
|
return `<rect x="${x}" y="${y}" width="${w || 40}" height="${h || 40}" fill="none" stroke="#ff00ff" stroke-width="2" stroke-dasharray="6" opacity="0.5"/><!-- ${type} -->`;
|
|
512
508
|
}
|
|
513
509
|
|
|
510
|
+
/**
|
|
511
|
+
* INSTANCE → SYMBOL resolution.
|
|
512
|
+
*
|
|
513
|
+
* Figma templates use INSTANCE nodes that reference a SYMBOL definition.
|
|
514
|
+
* The SYMBOL's children (TEXT, shapes, frames, etc.) define the visual content.
|
|
515
|
+
* The INSTANCE may carry symbolOverrides that modify specific child properties
|
|
516
|
+
* (text content, fills, etc.).
|
|
517
|
+
*
|
|
518
|
+
* Strategy:
|
|
519
|
+
* - Resolve the SYMBOL via symbolData.symbolID
|
|
520
|
+
* - Render the SYMBOL's children tree (they live in the normal node hierarchy)
|
|
521
|
+
* - Apply symbolOverrides: text and fill overrides are temporarily applied
|
|
522
|
+
* to the target nodes, rendered, then restored.
|
|
523
|
+
*/
|
|
524
|
+
function renderInstance(deck, node) {
|
|
525
|
+
const { x, y } = pos(node);
|
|
526
|
+
const symbolId = node.symbolData?.symbolID;
|
|
527
|
+
if (!symbolId) return renderPlaceholder(deck, node);
|
|
528
|
+
|
|
529
|
+
const symNid = `${symbolId.sessionID}:${symbolId.localID}`;
|
|
530
|
+
const symbol = deck.getNode(symNid);
|
|
531
|
+
if (!symbol) return renderPlaceholder(deck, node);
|
|
532
|
+
|
|
533
|
+
// Temporarily apply symbolOverrides so rendered content reflects overrides.
|
|
534
|
+
// Only single-level guidPath overrides are handled (covers the common case).
|
|
535
|
+
const overrides = node.symbolData?.symbolOverrides ?? [];
|
|
536
|
+
const restores = [];
|
|
537
|
+
|
|
538
|
+
for (const ov of overrides) {
|
|
539
|
+
const guids = ov.guidPath?.guids;
|
|
540
|
+
if (!guids?.length || guids.length !== 1) continue;
|
|
541
|
+
const targetId = `${guids[0].sessionID}:${guids[0].localID}`;
|
|
542
|
+
const target = deck.getNode(targetId);
|
|
543
|
+
if (!target) continue;
|
|
544
|
+
|
|
545
|
+
// Text override — replace characters but keep derived glyph layout.
|
|
546
|
+
// The glyph positions come from the original text, so this is approximate
|
|
547
|
+
// when character count differs, but visually far better than a placeholder.
|
|
548
|
+
if (ov.textData?.characters != null && target.textData) {
|
|
549
|
+
const origChars = target.textData.characters;
|
|
550
|
+
restores.push(() => { target.textData.characters = origChars; });
|
|
551
|
+
target.textData.characters = ov.textData.characters;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Fill override (image swaps, color changes)
|
|
555
|
+
if (ov.fillPaints) {
|
|
556
|
+
const origFill = target.fillPaints;
|
|
557
|
+
restores.push(() => { target.fillPaints = origFill; });
|
|
558
|
+
target.fillPaints = ov.fillPaints;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const inner = childrenSvg(deck, symbol);
|
|
563
|
+
|
|
564
|
+
// Restore mutations
|
|
565
|
+
for (const fn of restores) fn();
|
|
566
|
+
|
|
567
|
+
if (!inner) return '';
|
|
568
|
+
return `<g transform="translate(${x},${y})">\n${inner}\n</g>`;
|
|
569
|
+
}
|
|
570
|
+
|
|
514
571
|
// ── Dispatcher ────────────────────────────────────────────────────────────────
|
|
515
572
|
|
|
516
573
|
const RENDERERS = {
|
|
@@ -523,13 +580,11 @@ const RENDERERS = {
|
|
|
523
580
|
GROUP: renderGroup,
|
|
524
581
|
SECTION: renderGroup,
|
|
525
582
|
BOOLEAN_OPERATION: renderGroup,
|
|
526
|
-
// Stubs — add full implementations over time:
|
|
527
583
|
VECTOR: renderPlaceholder,
|
|
528
584
|
LINE: renderLine,
|
|
529
585
|
STAR: renderPlaceholder,
|
|
530
586
|
POLYGON: renderPlaceholder,
|
|
531
|
-
|
|
532
|
-
INSTANCE: renderPlaceholder,
|
|
587
|
+
INSTANCE: renderInstance,
|
|
533
588
|
};
|
|
534
589
|
|
|
535
590
|
function renderNode(deck, node) {
|
package/lib/template-deck.mjs
CHANGED
|
@@ -298,13 +298,41 @@ function describeLayout(deck, layout, row) {
|
|
|
298
298
|
};
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
+
/**
|
|
302
|
+
* Walk the node tree like deck.walkTree, but follow INSTANCE → SYMBOL links.
|
|
303
|
+
* When an INSTANCE node is encountered, its referenced SYMBOL's children are
|
|
304
|
+
* also walked so that slots inside published template components are discovered.
|
|
305
|
+
*/
|
|
306
|
+
function walkTreeThroughInstances(deck, rootId, visitor, depth = 0, visited = new Set()) {
|
|
307
|
+
if (!rootId || visited.has(rootId)) return;
|
|
308
|
+
visited.add(rootId);
|
|
309
|
+
|
|
310
|
+
const node = deck.getNode(rootId);
|
|
311
|
+
if (!node || node.phase === 'REMOVED') return;
|
|
312
|
+
visitor(node, depth);
|
|
313
|
+
|
|
314
|
+
// Follow INSTANCE → SYMBOL: walk the SYMBOL's children
|
|
315
|
+
if (node.type === 'INSTANCE' && node.symbolData?.symbolID) {
|
|
316
|
+
const sid = node.symbolData.symbolID;
|
|
317
|
+
const symNid = `${sid.sessionID}:${sid.localID}`;
|
|
318
|
+
for (const child of deck.getChildren(symNid)) {
|
|
319
|
+
walkTreeThroughInstances(deck, nid(child), visitor, depth + 1, visited);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Walk direct children
|
|
324
|
+
for (const child of deck.getChildren(rootId)) {
|
|
325
|
+
walkTreeThroughInstances(deck, nid(child), visitor, depth + 1, visited);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
301
329
|
function discoverSlots(deck, rootId) {
|
|
302
330
|
const explicitTextSlots = [];
|
|
303
331
|
const explicitImageSlots = [];
|
|
304
332
|
const fallbackTextSlots = [];
|
|
305
333
|
const fallbackImageSlots = [];
|
|
306
334
|
|
|
307
|
-
deck
|
|
335
|
+
walkTreeThroughInstances(deck, rootId, node => {
|
|
308
336
|
const textSlotName = parsePrefixedName(node.name, TEXT_SLOT_PREFIX);
|
|
309
337
|
if (textSlotName) {
|
|
310
338
|
const slot = describeTextSlot(node, textSlotName, 'explicit');
|
package/package.json
CHANGED