/** * Figma node JSON -> HTML mapper. * * Input is a `document` subtree returned by the provider's files/nodes API (or * the file's root `document` node). Output is a self-contained HTML fragment * using absolute positioning and inline styles to preserve imported node * geometry and styling. Unsupported constructs are reported to the caller. * * This module is pure and synchronous: it never calls the network. The * caller (an action) is responsible for: * 1. Fetching the node JSON from the Figma REST API. * 2. Calling `collectFallbackNodeIds` / `collectImageFillRefs` to find out * which nodes need a rendered PNG fallback and which image fills need * resolved URLs. * 3. Fetching those via `/v1/images/:fileKey` (fallback renders) and * `/v1/files/:fileKey/images` (fill ref -> URL map). * 4. Calling `mapFigmaNodeToHtml` with the resulting maps. * * Conversion coverage varies by node property. The conversion report records * unsupported fields and fallbacks so callers can explain what was imported. * Rotation uses the provider's transform fields and the node bounds to build * a CSS transform around the bounds center. */ export interface FigmaColor { r: number; g: number; b: number; a?: number; } export interface FigmaColorStop { position: number; color: FigmaColor; } export interface FigmaImageFilter { exposure?: number; contrast?: number; saturation?: number; temperature?: number; tint?: number; highlights?: number; shadows?: number; } export interface FigmaPaint { type: "SOLID" | "GRADIENT_LINEAR" | "GRADIENT_RADIAL" | "GRADIENT_ANGULAR" | "GRADIENT_DIAMOND" | "IMAGE" | "EMOJI" | "VIDEO"; visible?: boolean; opacity?: number; color?: FigmaColor; gradientHandlePositions?: Array<{ x: number; y: number; }>; gradientStops?: FigmaColorStop[]; imageRef?: string; scaleMode?: "FILL" | "FIT" | "TILE" | "STRETCH"; imageTransform?: [[number, number, number], [number, number, number]]; filters?: FigmaImageFilter; blendMode?: string; } export interface FigmaEffect { type: "DROP_SHADOW" | "INNER_SHADOW" | "LAYER_BLUR" | "BACKGROUND_BLUR"; visible?: boolean; radius?: number; spread?: number; color?: FigmaColor; offset?: { x: number; y: number; }; blendMode?: string; showShadowBehindNode?: boolean; } export interface FigmaTypeStyle { fontFamily?: string; fontPostScriptName?: string; fontWeight?: number; fontSize?: number; italic?: boolean; letterSpacing?: number; lineHeightPx?: number; lineHeightPercent?: number; lineHeightPercentFontSize?: number; lineHeightUnit?: "PIXELS" | "FONT_SIZE_%" | "INTRINSIC_%"; textCase?: "ORIGINAL" | "UPPER" | "LOWER" | "TITLE"; textDecoration?: "NONE" | "UNDERLINE" | "STRIKETHROUGH"; textAlignHorizontal?: "LEFT" | "RIGHT" | "CENTER" | "JUSTIFIED"; textAlignVertical?: "TOP" | "CENTER" | "BOTTOM"; textAutoResize?: "NONE" | "WIDTH_AND_HEIGHT" | "HEIGHT" | "TRUNCATE"; paragraphSpacing?: number; paragraphIndent?: number; listSpacing?: number; hangingPunctuation?: boolean; hangingList?: boolean; opentypeFlags?: Record; hyperlink?: unknown; fills?: FigmaPaint[]; } export interface FigmaVectorPath { path?: string; windingRule?: "NONZERO" | "EVENODD" | "NONE"; } export interface FigmaIndividualStrokeWeights { top?: number; right?: number; bottom?: number; left?: number; } export interface FigmaBoundingBox { x: number; y: number; width: number; height: number; } export interface FigmaNode { id: string; name?: string; type: string; visible?: boolean; opacity?: number; blendMode?: string; rotation?: number; absoluteBoundingBox?: FigmaBoundingBox; relativeTransform?: [[number, number, number], [number, number, number]]; absoluteRenderBounds?: FigmaBoundingBox; size?: { x: number; y: number; }; clipsContent?: boolean; isMask?: boolean; maskType?: "ALPHA" | "VECTOR" | "LUMINANCE"; arcData?: { startingAngle?: number; endingAngle?: number; innerRadius?: number; }; characters?: string; style?: FigmaTypeStyle; characterStyleOverrides?: number[]; styleOverrideTable?: Record; lineTypes?: string[]; lineIndentations?: number[]; fills?: FigmaPaint[]; strokes?: FigmaPaint[]; strokeWeight?: number; strokeAlign?: "INSIDE" | "OUTSIDE" | "CENTER"; individualStrokeWeights?: FigmaIndividualStrokeWeights; strokeDashes?: number[]; fillGeometry?: FigmaVectorPath[]; strokeGeometry?: FigmaVectorPath[]; cornerRadius?: number; rectangleCornerRadii?: [number, number, number, number]; effects?: FigmaEffect[]; layoutMode?: "NONE" | "HORIZONTAL" | "VERTICAL" | "GRID"; layoutPositioning?: "AUTO" | "ABSOLUTE"; primaryAxisAlignItems?: "MIN" | "CENTER" | "MAX" | "SPACE_BETWEEN"; counterAxisAlignItems?: "MIN" | "CENTER" | "MAX" | "BASELINE"; layoutSizingHorizontal?: "FIXED" | "HUG" | "FILL"; layoutSizingVertical?: "FIXED" | "HUG" | "FILL"; primaryAxisSizingMode?: "FIXED" | "AUTO"; layoutWrap?: "NO_WRAP" | "WRAP"; itemSpacing?: number; counterAxisSpacing?: number; paddingLeft?: number; paddingRight?: number; paddingTop?: number; paddingBottom?: number; minWidth?: number | null; maxWidth?: number | null; minHeight?: number | null; maxHeight?: number | null; componentId?: string; componentProperties?: Record; boundVariables?: Record; interactions?: unknown[]; children?: FigmaNode[]; } export type FidelityLevel = "exact" | "approximated" | "image-fallback"; export interface FidelityEntry { nodeId: string; nodeName: string; nodeType: string; level: FidelityLevel; notes: string[]; } export interface FidelityReport { entries: FidelityEntry[]; summary: { exact: number; approximated: number; imageFallback: number; }; } export interface MapFigmaNodeOptions { imageFillUrls?: Record; imageFillSizes?: Record; fallbackImageUrls?: Record; forceImageFallbackNodeIds?: Set; } export interface MapFigmaNodeResult { html: string; fidelity: FidelityReport; } export declare function gradientAngleDegrees(paint: FigmaPaint, box: { width: number; height: number; }): number | null; export declare const FIGMA_BLUR_RADIUS_TO_CSS_BLUR = 0.45; export declare function textTransformCss(textCase: FigmaTypeStyle["textCase"]): string | undefined; export declare function textUnderlinePositionCss(decoration: FigmaTypeStyle["textDecoration"]): string | undefined; export declare function textDecorationCss(decoration: FigmaTypeStyle["textDecoration"]): string | undefined; export declare function hasPrivateUseCharacters(text: string | undefined): boolean; export declare function assertFigmaNodeTreeComplexity(node: FigmaNode): void; export declare function collectFallbackNodeIds(node: FigmaNode, options?: MapFigmaNodeOptions): string[]; export declare function collectImageFillRefs(node: FigmaNode, options?: MapFigmaNodeOptions): string[]; export interface FigmaFontUsage { family: string; weight: number; italic: boolean; } export declare function collectFontUsage(node: FigmaNode): FigmaFontUsage[]; export declare function figmaLaidOutOneLine(node: FigmaNode): boolean; export declare function figmaDrawnText(text: string, laidOutLines: number | undefined): string; export declare function mapFigmaNodeToHtml(node: FigmaNode, options?: MapFigmaNodeOptions): MapFigmaNodeResult;