/**
* 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;