UNPKG

zp-figma-converter

Version:

Convert Figma designs to various code formats

307 lines 12.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.FigmaNodeParser = void 0; const types_1 = require("./types"); const path = __importStar(require("path")); /** * Parser for Figma nodes to an intermediate representation */ class FigmaNodeParser { constructor(assetsDir) { this.assetsDir = assetsDir; } /** * Parse a Figma node into an intermediate representation * @param node Figma node * @returns Intermediate node */ parse(node) { const parsedNode = this.parseWithParent(node, undefined); return parsedNode; } /** * Parse a Figma node with parent reference * @param node Figma node * @param parent Parent node (if any) * @returns Intermediate node */ parseWithParent(node, parent) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o; // Set parent reference node.parent = parent; const metadata = {}; // Parse layout properties if (node.layoutMode) { metadata.layout = { mode: node.layoutMode, paddingLeft: node.paddingLeft, paddingRight: node.paddingRight, paddingTop: node.paddingTop, paddingBottom: node.paddingBottom, itemSpacing: node.itemSpacing, counterAxisSpacing: node.counterAxisSpacing, layoutAlign: node.layoutAlign, layoutGrow: node.layoutGrow, primaryAxisSizingMode: node.primaryAxisSizingMode, counterAxisSizingMode: node.counterAxisSizingMode, }; // Add auto layout metadata for CSD converter metadata.autoLayout = { direction: node.layoutMode.toLowerCase() === 'horizontal' ? 'horizontal' : 'vertical', spacing: node.itemSpacing || 0, padding: { left: node.paddingLeft || 0, right: node.paddingRight || 0, top: node.paddingTop || 0, bottom: node.paddingBottom || 0 }, alignment: node.primaryAxisAlignItems || 'start', counterAxisAlignment: node.counterAxisAlignItems || 'start' }; } // Parse constraints if (node.constraints) { metadata.constraints = { horizontal: node.constraints.horizontal, vertical: node.constraints.vertical, // Store original parent dimensions if available originalParentWidth: (_b = (_a = node.parent) === null || _a === void 0 ? void 0 : _a.absoluteBoundingBox) === null || _b === void 0 ? void 0 : _b.width, originalParentHeight: (_d = (_c = node.parent) === null || _c === void 0 ? void 0 : _c.absoluteBoundingBox) === null || _d === void 0 ? void 0 : _d.height }; } // Parse component reference if (node.componentId) { metadata.componentId = node.componentId; } // Create intermediate node const intermediateNode = { id: node.id, name: node.name, type: node.type, x: (_f = (_e = node.absoluteBoundingBox) === null || _e === void 0 ? void 0 : _e.x) !== null && _f !== void 0 ? _f : 0, y: (_h = (_g = node.absoluteBoundingBox) === null || _g === void 0 ? void 0 : _g.y) !== null && _h !== void 0 ? _h : 0, width: (_k = (_j = node.absoluteBoundingBox) === null || _j === void 0 ? void 0 : _j.width) !== null && _k !== void 0 ? _k : 0, height: (_m = (_l = node.absoluteBoundingBox) === null || _l === void 0 ? void 0 : _l.height) !== null && _m !== void 0 ? _m : 0, rotation: node.rotation, opacity: node.opacity, children: [], // Will be filled with children later styles: this.parseStyles(node), imageRef: this.getImageReference(node), metadata }; // Parse children with parent reference if ((_o = node.children) === null || _o === void 0 ? void 0 : _o.length) { intermediateNode.children = node.children.map(child => this.parseWithParent(child, node)); } return intermediateNode; } /** * Parse styles from a Figma node * @param node Figma node * @returns Styles object */ parseStyles(node) { var _a, _b, _c, _d; const styles = {}; // Parse multiple fills if ((_a = node.fills) === null || _a === void 0 ? void 0 : _a.length) { const fillColors = node.fills.map(fill => { if (fill.type === 'SOLID') { return this.rgbToHex(fill.color, fill.opacity); } else if (fill.type === 'IMAGE') { // Mark for image export return `image:${node.id}`; } else if (fill.type === 'GRADIENT_LINEAR' || fill.type === 'GRADIENT_RADIAL') { // Basic gradient support return fill.type.toLowerCase(); } return ''; }).filter(color => color !== ''); if (fillColors.length === 1) { styles.fill = fillColors[0]; } else if (fillColors.length > 1) { styles.fill = fillColors[0]; // Use first fill, store others in metadata styles.additionalFills = fillColors.slice(1); } } // Parse multiple strokes if ((_b = node.strokes) === null || _b === void 0 ? void 0 : _b.length) { const strokeColors = node.strokes.map(stroke => { if (stroke.type === 'SOLID') { return this.rgbToHex(stroke.color, stroke.opacity); } return ''; }).filter(color => color !== ''); if (strokeColors.length === 1) { styles.stroke = strokeColors[0]; } else if (strokeColors.length > 1) { styles.stroke = strokeColors[0]; // Use first stroke, store others in metadata styles.additionalStrokes = strokeColors.slice(1); } // Add stroke weight if present if (node.strokeWeight !== undefined) { styles.strokeWeight = node.strokeWeight; } } // Parse corner radius if (node.cornerRadius !== undefined) { styles.cornerRadius = node.cornerRadius; } else if (((_c = node.rectangleCornerRadii) === null || _c === void 0 ? void 0 : _c.length) === 4) { styles.cornerRadii = node.rectangleCornerRadii; } // Parse effects (shadows, blurs, etc) if ((_d = node.effects) === null || _d === void 0 ? void 0 : _d.length) { styles.effects = this.parseEffects(node.effects); } // Parse text properties if (node.type === types_1.FigmaNodeType.TEXT && node.style) { styles.font = { family: node.style.fontFamily, size: node.style.fontSize, weight: node.style.fontWeight.toString() }; // Additional text properties if (node.style.textCase) styles.font.textCase = node.style.textCase; if (node.style.textDecoration) styles.font.textDecoration = node.style.textDecoration; if (node.style.textAlignHorizontal) styles.font.alignHorizontal = node.style.textAlignHorizontal; if (node.style.textAlignVertical) styles.font.alignVertical = node.style.textAlignVertical; if (node.style.letterSpacing) styles.font.letterSpacing = node.style.letterSpacing; if (node.style.lineHeightPx) styles.font.lineHeight = node.style.lineHeightPx; styles.textContent = node.characters; } return styles; } /** * Parse effects (shadows, blurs) * @param effects Array of Figma effects * @returns Parsed effects */ parseEffects(effects) { return effects.map(effect => { var _a; const parsedEffect = { type: effect.type, visible: (_a = effect.visible) !== null && _a !== void 0 ? _a : true }; if (effect.color) { parsedEffect.color = this.rgbToHex(effect.color); } if (effect.radius !== undefined) { parsedEffect.radius = effect.radius; } if (effect.offset) { parsedEffect.offsetX = effect.offset.x; parsedEffect.offsetY = effect.offset.y; } return parsedEffect; }); } /** * Convert RGB color to hex or rgba * @param color RGB color * @param opacity Additional opacity * @returns Hex color string or rgba string */ rgbToHex(color, opacity) { if (!color) return '#000000'; const r = Math.round(color.r * 255); const g = Math.round(color.g * 255); const b = Math.round(color.b * 255); const a = opacity !== undefined ? opacity : (color.a !== undefined ? color.a : 1); if (a < 1) { return `rgba(${r}, ${g}, ${b}, ${a})`; } return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`; } /** * Get image reference for a node (if applicable) * @param node Figma node * @returns Image reference path or undefined */ getImageReference(node) { if (!this.hasImageContent(node)) { return undefined; } const fileName = this.getFileName(node); return path.join(this.assetsDir, `${fileName}.png`); } /** * Check if node contains image content * @param node Figma node * @returns True if node has image content */ hasImageContent(node) { if (!node.fills) { return false; } const hasImageFill = node.fills.some(fill => fill.type === 'IMAGE' || fill.imageRef); return (node.type === types_1.FigmaNodeType.RECTANGLE || node.type === types_1.FigmaNodeType.FRAME) && (hasImageFill || node.fills.some(fill => fill.type === 'SOLID')); } /** * Get sanitized filename for node * @param node Figma node * @returns Sanitized filename */ getFileName(node) { var _a, _b; const imageFill = (_a = node.fills) === null || _a === void 0 ? void 0 : _a.find(fill => fill.type === 'IMAGE'); const rawFileName = (_b = imageFill === null || imageFill === void 0 ? void 0 : imageFill.imageRef) !== null && _b !== void 0 ? _b : node.id; return this.sanitizeFileName(rawFileName); } /** * Sanitize file name to remove invalid characters * @param fileName Original file name * @returns Sanitized file name */ sanitizeFileName(fileName) { return fileName.replace(/[\\/:*?"<>|;,=+]/g, "_"); } } exports.FigmaNodeParser = FigmaNodeParser; //# sourceMappingURL=figma-node-parser.js.map