@charz/dom-to-figma 0.0.0-stage → 0.2.5
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/LICENSE +21 -0
- package/README.md +172 -2
- package/dist/figma.d.mts +566 -0
- package/dist/figma.d.mts.map +1 -0
- package/dist/figma.mjs +7191 -0
- package/dist/figma.mjs.map +1 -0
- package/package.json +69 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"figma.mjs","names":["parseDashArray","scaleVectorNetwork","applyScaling","DASH_LENGTH_RATIO","polyline","polygon","path"],"sources":["../src/converter/blob-manager.ts","../src/converter/dedup-cache.ts","../src/converter/nodes/text/primitives/font/metrics.ts","../src/converter/nodes/text/primitives/font/loader.ts","../src/converter/font-cache.ts","../src/converter/nodes/image/loader.ts","../src/converter/image-cache.ts","../src/converter/nodes/root/converter.ts","../src/converter/styles/color.ts","../src/converter/classify.ts","../src/converter/dom.ts","../src/converter/layout/infer.ts","../src/converter/naming.ts","../src/converter/styles/shadow.ts","../src/converter/styles/blur.ts","../src/converter/styles/border.ts","../src/converter/styles/filter-color.ts","../src/converter/styles/gradient.ts","../src/converter/styles/opacity.ts","../src/converter/styles/outline.ts","../src/converter/styles/border-3d.ts","../src/converter/nodes/vector/path-parser/normalizer.ts","../src/converter/nodes/vector/path-parser/tokenizer.ts","../src/converter/nodes/vector/path-parser/index.ts","../src/converter/nodes/vector/path-to-network/arc-converter.ts","../src/converter/nodes/vector/path-to-network/curve-builder.ts","../src/converter/nodes/vector/path-to-network/index.ts","../src/converter/nodes/vector/scaling/bounds.ts","../src/converter/nodes/vector/scaling/dash-scaler.ts","../src/converter/nodes/vector/scaling/network-scaler.ts","../src/converter/nodes/vector/scaling/scale-factors.ts","../src/converter/nodes/vector/scaling/index.ts","../src/converter/nodes/vector/vector-networks/index.ts","../src/converter/nodes/vector/vector-networks/encoder.ts","../src/converter/nodes/frame/rounded-dash-border.ts","../src/converter/nodes/frame/border-decomposition.ts","../src/converter/nodes/frame/converter.ts","../src/converter/nodes/text/builders/baselines.ts","../src/converter/nodes/text/builders/character-offsets.ts","../src/converter/nodes/text/builders/decorations.ts","../src/converter/nodes/text/primitives/font/properties.ts","../src/converter/nodes/text/primitives/css/parser.ts","../src/converter/nodes/text/primitives/glyph/encoder.ts","../src/converter/nodes/text/primitives/glyph/processor.ts","../src/converter/nodes/text/primitives/layout/kerning.ts","../src/converter/nodes/text/primitives/layout/scaling.ts","../src/converter/nodes/text/primitives/layout/positioning.ts","../src/converter/nodes/text/primitives/layout/wrapping.ts","../src/converter/nodes/text/primitives/layout/processor.ts","../src/converter/nodes/text/converter.ts","../src/converter/nodes/form/converter.ts","../src/converter/nodes/group/converter.ts","../src/converter/nodes/image/converter.ts","../src/converter/nodes/vector/shapes/basic/circle.ts","../src/converter/nodes/vector/shapes/basic/ellipse.ts","../src/converter/nodes/vector/shapes/basic/line.ts","../src/converter/nodes/vector/shapes/basic/polygon.ts","../src/converter/nodes/vector/shapes/basic/rect.ts","../src/converter/nodes/vector/shapes/index.ts","../src/converter/nodes/vector/converter.ts","../src/converter/convert.ts","../src/converter/trace.ts","../src/converter/walk.ts","../src/figma.ts"],"sourcesContent":["import type { FigmaBlob } from \"./types\";\n\nexport class BlobManager {\n private blobs: Array<FigmaBlob> = [];\n private readonly blobDataToIndexMap = new Map<string, number>();\n\n registerBlob(blob: FigmaBlob): number {\n const blobKey = JSON.stringify(blob.bytes);\n\n const existingIndex = this.blobDataToIndexMap.get(blobKey);\n if (existingIndex !== undefined) {\n return existingIndex;\n }\n\n const index = this.blobs.length;\n this.blobs.push(blob);\n this.blobDataToIndexMap.set(blobKey, index);\n return index;\n }\n\n getBlobs(): Array<FigmaBlob> {\n return this.blobs;\n }\n\n clear(): void {\n this.blobs = [];\n this.blobDataToIndexMap.clear();\n }\n}\n","export type DedupCacheOptions<TKey, TLoaded> = {\n load: (key: TKey) => Promise<TLoaded>;\n toCacheKey: (key: TKey) => string;\n};\n\n/**\n * In-memory cache that deduplicates concurrent loads by key. Each unique key\n * is resolved at most once per cache instance: cache hits return immediately,\n * in-flight loads share a single promise, and failures clear the slot so the\n * next request can retry.\n */\nexport class DedupCache<TKey, TLoaded> {\n private readonly cache = new Map<string, TLoaded>();\n private readonly inFlight = new Map<string, Promise<TLoaded>>();\n private readonly load: (key: TKey) => Promise<TLoaded>;\n private readonly toCacheKey: (key: TKey) => string;\n\n constructor({ load, toCacheKey }: DedupCacheOptions<TKey, TLoaded>) {\n this.load = load;\n this.toCacheKey = toCacheKey;\n }\n\n get(key: TKey): Promise<TLoaded> {\n const cacheKey = this.toCacheKey(key);\n\n const cached = this.cache.get(cacheKey);\n if (cached) {\n return Promise.resolve(cached);\n }\n\n const inFlight = this.inFlight.get(cacheKey);\n if (inFlight) {\n return inFlight;\n }\n\n const promise = this.load(key)\n .then((value) => {\n this.cache.set(cacheKey, value);\n this.inFlight.delete(cacheKey);\n return value;\n })\n .catch((error: unknown) => {\n this.inFlight.delete(cacheKey);\n throw error;\n });\n\n this.inFlight.set(cacheKey, promise);\n return promise;\n }\n\n clear(): void {\n this.cache.clear();\n this.inFlight.clear();\n }\n}\n","/**\n * Font Metrics Primitives\n *\n * Low-level font metrics extraction and conversion utilities.\n * These primitives handle the core font measurement operations without\n * any higher-level dependencies.\n *\n * @module FontMetricsPrimitives\n */\n\nimport type { OpenTypeFont } from \"../../types\";\n\n/**\n * Comprehensive font metrics extracted from OpenType fonts\n *\n * Contains all essential measurements needed for text layout and rendering,\n * normalized to a consistent format regardless of font implementation details.\n */\nexport type FontMetrics = {\n /** Units per EM square - the fundamental unit of font measurement */\n unitsPerEm: number;\n /** Distance from baseline to top of ascenders (positive) */\n ascender: number;\n /** Distance from baseline to bottom of descenders (negative) */\n descender: number;\n /** Additional spacing between lines */\n lineGap: number;\n\n /** Height of capital letters */\n capHeight: number;\n /** Height of lowercase letters (typically 'x') */\n xHeight: number;\n\n /** Total line height in font units (ascender - descender + lineGap) */\n lineHeight: number;\n /** Distance from origin to baseline */\n baseline: number;\n\n /**\n * Intrinsic line-height as an em ratio: `lineHeight / unitsPerEm`. Equal to\n * Figma's `FontMetaData.fontLineHeight`. Pre-divided here so callers don't\n * each have to repeat the division (and don't have to repeat the\n * `unitsPerEm > 0` guard either).\n */\n lineHeightRatio: number;\n\n /** Font family name */\n familyName: string;\n /** Font style name (e.g., \"Regular\", \"Bold\") */\n styleName: string;\n};\n\n/**\n * Extract comprehensive font metrics from a fontkit font.\n *\n * Provides robust metrics extraction with fallbacks for missing data.\n * Normalizes metrics across font formats.\n *\n * @throws {Error} When the font reports a non-positive `unitsPerEm`, which\n * would silently produce `Infinity` / `NaN` ratios downstream.\n */\nexport function extractFontMetrics(font: OpenTypeFont): FontMetrics {\n const unitsPerEm = font.unitsPerEm;\n\n // Treat a degenerate `unitsPerEm` as a hard error here, at the boundary,\n // rather than letting NaN/Infinity leak into glyph positions, baselines,\n // and Kiwi `float` fields where the wire format is undefined.\n if (!Number.isFinite(unitsPerEm) || unitsPerEm <= 0) {\n const fontLabel = font.familyName ?? font.fullName ?? \"unknown\";\n throw new Error(\n `Font \"${fontLabel}\" has invalid unitsPerEm: ${unitsPerEm}. Cannot compute metrics.`\n );\n }\n\n const ascender = font.ascent;\n const descender = font.descent;\n const lineGap = font.lineGap ?? 0;\n\n // fontkit exposes capHeight/xHeight as top-level getters with a built-in\n // fallback to the closest available metric, so no manual `os2?.sCapHeight`\n // chain is needed.\n const capHeight = font.capHeight ?? Math.round(unitsPerEm * 0.7);\n const xHeight = font.xHeight ?? Math.round(unitsPerEm * 0.5);\n\n const lineHeight = ascender - descender + lineGap;\n const baseline = Math.abs(descender);\n\n return {\n unitsPerEm,\n ascender,\n descender,\n lineGap,\n capHeight,\n xHeight,\n lineHeight,\n lineHeightRatio: lineHeight / unitsPerEm,\n baseline,\n familyName: font.familyName ?? font.fullName ?? \"Unknown\",\n styleName: font.subfamilyName ?? \"Regular\",\n };\n}\n\n/**\n * Convert font units to pixel units at a given font size\n *\n * Essential utility for converting between font coordinate space and\n * pixel coordinate space. All font measurements are in font units\n * and must be scaled to match the desired pixel size.\n *\n * @param fontUnits - Value in font units\n * @param fontSize - Target font size in pixels\n * @param unitsPerEm - Font's units per EM value\n * @returns Value converted to pixels\n * @throws {Error} When unitsPerEm is invalid\n *\n * @example\n * ```typescript\n * const pixelWidth = fontUnitsToPixels(500, 24, 2048); // ~5.86px\n * const lineHeightPx = fontUnitsToPixels(metrics.lineHeight, 16, metrics.unitsPerEm);\n * ```\n */\nexport function fontUnitsToPixels(\n fontUnits: number,\n fontSize: number,\n unitsPerEm: number\n): number {\n if (unitsPerEm <= 0) {\n throw new Error(\"unitsPerEm must be positive for font unit conversion\");\n }\n return (fontUnits / unitsPerEm) * fontSize;\n}\n","import type { Font } from \"fontkit\";\nimport { create } from \"fontkit\";\nimport type { OpenTypeFont } from \"../../types\";\nimport type { FontMetrics } from \"./metrics\";\nimport { extractFontMetrics } from \"./metrics\";\n\n/**\n * Font properties used to request a font file. The loader is free to apply\n * fallbacks (closest available weight, drop italic when not available, etc.)\n * and reports the resolved values back via `FontFile`.\n */\nexport type FontProperties = {\n family: string;\n weight: number;\n italic: boolean;\n};\n\n/**\n * Result of loading a font file. `bytes` is the raw font file (TTF, OTF,\n * WOFF, or WOFF2 — fontkit detects the format). The optional `resolved*`\n * fields let the loader signal that a fallback was applied.\n *\n * `resolvedFamily` is set when the loader could not honour the requested\n * family and substituted bytes from a different family entirely (e.g. when\n * the requested family isn't in the loader's catalog). The conversion still\n * uses the *requested* family in the Figma payload — the substituted bytes\n * exist so dom-to-figma has metrics to lay out the text. Figma resolves\n * fonts at paste time via the user's system fonts, so a system-installed\n * family like Verdana renders correctly even if the loader had to feed\n * Inter bytes through the converter.\n */\nexport type FontFile = {\n bytes: ArrayBuffer;\n resolvedWeight?: number;\n resolvedItalic?: boolean;\n resolvedFamily?: string;\n};\n\n/**\n * A function that resolves font properties to a font file. The loader owns\n * any catalog or fallback logic; the converter only consumes the bytes.\n */\nexport type FontLoader = (request: FontProperties) => Promise<FontFile>;\n\nexport type LoadedFont = {\n font: OpenTypeFont;\n metrics: FontMetrics;\n /** What was requested. */\n properties: FontProperties;\n /** What was actually loaded (after the loader's fallbacks). */\n actualWeight?: number;\n actualItalic: boolean;\n fontStyleName: string;\n postScriptName: string;\n};\n\nconst WEIGHT_TO_STYLE_NAME: Record<number, string> = {\n 100: \"Thin\",\n 200: \"ExtraLight\",\n 300: \"Light\",\n 400: \"Regular\",\n 500: \"Medium\",\n 600: \"SemiBold\",\n 700: \"Bold\",\n 800: \"ExtraBold\",\n 900: \"Black\",\n};\n\nexport async function loadFont(\n fontLoader: FontLoader,\n properties: FontProperties\n): Promise<LoadedFont> {\n const file = await fontLoader(properties);\n\n // fontkit's `create` accepts any `Uint8Array`-backed buffer at runtime; the\n // `Buffer` parameter type in the published `.d.ts` is misleading. Browser-safe.\n const parsed = create(new Uint8Array(file.bytes) as unknown as Buffer);\n if (!isFont(parsed)) {\n throw new Error(\n \"fontkit returned a font collection; collection inputs are not supported\"\n );\n }\n const font = parsed;\n const metrics = extractFontMetrics(font);\n\n const actualWeight = file.resolvedWeight ?? properties.weight;\n const actualItalic = file.resolvedItalic ?? properties.italic;\n const fontStyleName = buildFontStyleName(actualWeight, actualItalic);\n // When the loader substituted a different family, the loaded bytes' name\n // table belongs to the substitute (e.g. \"Inter-Regular\"). Synthesize the\n // postScriptName from the *requested* family so the Figma payload asks for\n // \"Verdana-Regular\", letting Figma render it from the destination's system\n // fonts instead of the substitute.\n const familyWasSubstituted =\n file.resolvedFamily !== undefined &&\n file.resolvedFamily !== properties.family;\n const synthesizedPostScriptName = `${properties.family.replace(/\\s+/g, \"\")}-${fontStyleName.replace(/\\s+/g, \"\")}`;\n const postScriptName = familyWasSubstituted\n ? synthesizedPostScriptName\n : (font.postscriptName ?? synthesizedPostScriptName);\n\n return {\n font,\n metrics,\n properties,\n actualWeight,\n actualItalic,\n fontStyleName,\n postScriptName,\n };\n}\n\n/**\n * fontkit's `create` returns either a `Font` or a `FontCollection` (TTC/DFont).\n * We discriminate on the `type` field — collections expose `'TTC' | 'DFont'`,\n * everything else is a single font.\n */\nfunction isFont(value: unknown): value is Font {\n if (typeof value !== \"object\" || value === null || !(\"type\" in value)) {\n return false;\n }\n const type = (value as { type: unknown }).type;\n return type !== \"TTC\" && type !== \"DFont\";\n}\n\nfunction buildFontStyleName(weight: number, italic: boolean): string {\n const baseName = WEIGHT_TO_STYLE_NAME[weight] ?? \"Regular\";\n if (!italic) {\n return baseName;\n }\n if (baseName === \"Regular\") {\n return \"Italic\";\n }\n return `${baseName} Italic`;\n}\n\n/**\n * Options for the default fontsource-backed loader.\n */\nexport type FontsourceLoaderOptions = {\n /** Subset to request. Defaults to \"latin\". Common alternatives: \"latin-ext\", \"cyrillic\", \"greek\". */\n subset?: string;\n /**\n * Family to substitute when the requested family isn't in fontsource (web-safe\n * fonts like Verdana, Tahoma, Georgia, Times New Roman, etc. — fontsource\n * mirrors Google Fonts only). The substitute must itself be on fontsource.\n * The Figma payload still claims the *requested* family name, so destinations\n * with the system font installed render it correctly — the substituted bytes\n * only feed conversion-time metrics.\n *\n * Defaults to `\"Inter\"`. Pass `null` to disable substitution and throw on\n * missing families instead — the converter's per-node try/catch will then\n * silently drop affected text nodes.\n */\n fallbackFamily?: string | null;\n};\n\nconst FONTSOURCE_BASE_URL = \"https://cdn.jsdelivr.net/fontsource/fonts\";\nconst FONTSOURCE_VERSION = \"5\";\nconst FONTSOURCE_FALLBACK_WEIGHT = 400;\nconst FONTSOURCE_DEFAULT_SUBSET = \"latin\";\nconst DEFAULT_FALLBACK_FAMILY = \"Inter\";\n\n/**\n * Build a `FontLoader` that pulls Google Fonts as static `.woff2` files from\n * fontsource via jsDelivr's CDN. No API key, no UA tricks, browser-friendly.\n * fontkit decompresses WOFF2 transparently.\n *\n * Falls back through a small chain when the exact (weight, italic) combo\n * isn't available: drop italic, then regular 400, then throw.\n *\n * Families that fontsource genuinely doesn't carry (Verdana, Tahoma, etc. —\n * fontsource mirrors Google Fonts only) are memoized after the first\n * exhausted-with-404s attempt, so subsequent requests for the same family\n * fail fast instead of re-hitting jsDelivr for every text node on a page.\n * Transient failures (network, 5xx) are not memoized.\n */\nexport function createFontsourceLoader(\n options: FontsourceLoaderOptions = {}\n): FontLoader {\n const subset = options.subset ?? FONTSOURCE_DEFAULT_SUBSET;\n // `undefined` → use the default; `null` → strict-fail; a string overrides.\n const fallbackFamily =\n options.fallbackFamily === undefined\n ? DEFAULT_FALLBACK_FAMILY\n : options.fallbackFamily;\n const fallbackKey = fallbackFamily ? familyToSlug(fallbackFamily) : null;\n const knownMissingFamilies = new Set<string>();\n\n return async (request: FontProperties): Promise<FontFile> => {\n const familyKey = familyToSlug(request.family);\n const isFallbackRequest = fallbackKey === familyKey;\n\n if (knownMissingFamilies.has(familyKey)) {\n if (fallbackFamily && !isFallbackRequest) {\n return await loadAsFallback(fallbackFamily, request, subset);\n }\n throw new Error(\n `fontsource: ${request.family} is not in the catalog (cached)`\n );\n }\n\n const outcome = await fetchFromFontsource(request, subset);\n if (outcome.kind === \"ok\") {\n return {\n bytes: outcome.bytes,\n resolvedWeight: outcome.resolvedWeight,\n resolvedItalic: outcome.resolvedItalic,\n };\n }\n\n if (outcome.kind === \"not-found\") {\n knownMissingFamilies.add(familyKey);\n if (fallbackFamily && !isFallbackRequest) {\n return await loadAsFallback(fallbackFamily, request, subset);\n }\n }\n\n throw new Error(\n `fontsource: no variant found for ${formatRequest(request)} (subset=${subset})`\n );\n };\n}\n\nasync function loadAsFallback(\n fallbackFamily: string,\n originalRequest: FontProperties,\n subset: string\n): Promise<FontFile> {\n const outcome = await fetchFromFontsource(\n { ...originalRequest, family: fallbackFamily },\n subset\n );\n if (outcome.kind !== \"ok\") {\n throw new Error(\n `fontsource: fallback family \"${fallbackFamily}\" is not available (subset=${subset})`\n );\n }\n return {\n bytes: outcome.bytes,\n resolvedWeight: outcome.resolvedWeight,\n resolvedItalic: outcome.resolvedItalic,\n resolvedFamily: fallbackFamily,\n };\n}\n\ntype FontsourceFetchOutcome =\n | {\n kind: \"ok\";\n bytes: ArrayBuffer;\n resolvedWeight: number;\n resolvedItalic: boolean;\n }\n | { kind: \"not-found\" }\n | { kind: \"transient\" };\n\nasync function fetchFromFontsource(\n request: FontProperties,\n subset: string\n): Promise<FontsourceFetchOutcome> {\n let onlySawNotFound = true;\n for (const candidate of buildFallbackChain(request)) {\n const url = buildFontsourceUrl(candidate, subset);\n const result = await tryFetchFont(url);\n if (result.kind === \"ok\") {\n return {\n kind: \"ok\",\n bytes: result.bytes,\n resolvedWeight: candidate.weight,\n resolvedItalic: candidate.italic,\n };\n }\n if (result.kind !== \"not-found\") {\n onlySawNotFound = false;\n }\n }\n return { kind: onlySawNotFound ? \"not-found\" : \"transient\" };\n}\n\nfunction buildFallbackChain(request: FontProperties): Array<FontProperties> {\n const chain: Array<FontProperties> = [request];\n if (request.italic) {\n chain.push({ ...request, italic: false });\n }\n if (request.weight !== FONTSOURCE_FALLBACK_WEIGHT) {\n chain.push({\n ...request,\n weight: FONTSOURCE_FALLBACK_WEIGHT,\n italic: false,\n });\n }\n return chain;\n}\n\nfunction buildFontsourceUrl(props: FontProperties, subset: string): string {\n const slug = familyToSlug(props.family);\n const style = props.italic ? \"italic\" : \"normal\";\n return `${FONTSOURCE_BASE_URL}/${slug}@${FONTSOURCE_VERSION}/${subset}-${props.weight}-${style}.woff2`;\n}\n\nfunction familyToSlug(family: string): string {\n return family.replace(/['\"]/g, \"\").trim().toLowerCase().replace(/\\s+/g, \"-\");\n}\n\nconst HTTP_NOT_FOUND = 404;\n\ntype FetchResult =\n | { kind: \"ok\"; bytes: ArrayBuffer }\n | { kind: \"not-found\" }\n | { kind: \"transient\" };\n\nasync function tryFetchFont(url: string): Promise<FetchResult> {\n try {\n const response = await fetch(url);\n if (response.status === HTTP_NOT_FOUND) {\n return { kind: \"not-found\" };\n }\n if (!response.ok) {\n return { kind: \"transient\" };\n }\n return { kind: \"ok\", bytes: await response.arrayBuffer() };\n } catch {\n return { kind: \"transient\" };\n }\n}\n\nfunction formatRequest(request: FontProperties): string {\n return `${request.family} ${request.weight} ${request.italic ? \"italic\" : \"normal\"}`;\n}\n","import { DedupCache } from \"./dedup-cache\";\nimport type {\n FontLoader,\n FontProperties,\n LoadedFont,\n} from \"./nodes/text/primitives/font/loader\";\nimport { loadFont } from \"./nodes/text/primitives/font/loader\";\n\nexport type FontCache = DedupCache<FontProperties, LoadedFont>;\n\nexport function createFontCache(fontLoader: FontLoader): FontCache {\n return new DedupCache({\n load: (properties) => loadFont(fontLoader, properties),\n toCacheKey: ({ family, weight, italic }) => `${family}:${weight}:${italic}`,\n });\n}\n","/**\n * Identifier for the image being loaded. The element is included so loaders\n * can read auxiliary attributes (`crossOrigin`, `referrerPolicy`, etc.) when\n * choosing a fetch strategy, but `src` is the canonical key.\n */\nexport type ImageRequest = {\n src: string;\n element: HTMLImageElement;\n};\n\n/**\n * Bytes returned from the loader along with the actual content type. The\n * package re-encodes to PNG when the type isn't directly supported by Figma's\n * clipboard format.\n */\nexport type ImageFile = {\n bytes: ArrayBuffer;\n mimeType: string;\n};\n\nexport type ImageLoader = (request: ImageRequest) => Promise<ImageFile>;\n\n/**\n * Result of processing an `ImageFile` for Figma blob registration.\n */\nexport type ImageBlobInfo = {\n /** SHA-1 of the (possibly re-encoded) bytes — Figma's blob identifier. */\n hash: Array<number>;\n /** Bytes ready for Figma blob registration (PNG/JPEG/GIF). */\n bytes: Array<number>;\n};\n\nconst FIGMA_SUPPORTED_FORMATS = [\n \"image/png\",\n \"image/jpeg\",\n \"image/jpg\",\n \"image/gif\",\n] as const;\n\nconst PNG_QUALITY = 1.0;\n\n/**\n * Build an `ImageLoader` that performs a single direct `fetch(src)`. Works\n * for same-origin images and remote images that send permissive CORS headers.\n * Cross-origin images without CORS will throw — consumers that need a proxy\n * chain should inject their own `ImageLoader`.\n */\nexport function createDirectImageLoader(): ImageLoader {\n return async ({ src }) => {\n const response = await fetch(src);\n if (!response.ok) {\n throw new Error(`Image fetch failed (${response.status}): ${src}`);\n }\n const blob = await response.blob();\n return {\n bytes: await blob.arrayBuffer(),\n mimeType: blob.type,\n };\n };\n}\n\n/**\n * Convert raw loader output into Figma-ready blob info: re-encode to PNG when\n * the mime type isn't supported, then SHA-1 hash the final bytes.\n */\nexport async function processImageFile(\n file: ImageFile\n): Promise<ImageBlobInfo> {\n const finalBytes = isFigmaSupportedFormat(file.mimeType)\n ? file.bytes\n : await convertToPng(file);\n\n const hash = await sha1(finalBytes);\n return {\n hash,\n bytes: Array.from(new Uint8Array(finalBytes)),\n };\n}\n\nfunction isFigmaSupportedFormat(mimeType: string): boolean {\n return FIGMA_SUPPORTED_FORMATS.includes(\n mimeType.toLowerCase() as (typeof FIGMA_SUPPORTED_FORMATS)[number]\n );\n}\n\nasync function convertToPng(file: ImageFile): Promise<ArrayBuffer> {\n const sourceBlob = new Blob([file.bytes], { type: file.mimeType });\n const objectUrl = URL.createObjectURL(sourceBlob);\n try {\n const img = await loadImageElement(objectUrl);\n const canvas = createCanvasFromImage(img);\n if (!canvas) {\n throw new Error(\"Failed to create canvas for PNG conversion\");\n }\n const pngBlob = await canvasToBlob(canvas, \"image/png\", PNG_QUALITY);\n return await pngBlob.arrayBuffer();\n } finally {\n URL.revokeObjectURL(objectUrl);\n }\n}\n\nfunction loadImageElement(src: string): Promise<HTMLImageElement> {\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.onload = () => resolve(img);\n img.onerror = () => reject(new Error(`Failed to load image: ${src}`));\n img.src = src;\n });\n}\n\nfunction createCanvasFromImage(\n img: HTMLImageElement\n): HTMLCanvasElement | null {\n const canvas = document.createElement(\"canvas\");\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) {\n return null;\n }\n canvas.width = img.naturalWidth;\n canvas.height = img.naturalHeight;\n ctx.drawImage(img, 0, 0);\n return canvas;\n}\n\nfunction canvasToBlob(\n canvas: HTMLCanvasElement,\n type: string,\n quality: number\n): Promise<Blob> {\n return new Promise((resolve, reject) => {\n canvas.toBlob(\n (blob) => {\n if (blob) {\n resolve(blob);\n } else {\n reject(new Error(\"Failed to convert canvas to blob\"));\n }\n },\n type,\n quality\n );\n });\n}\n\n/**\n * SHA-1 the bytes for Figma's blob identifier. Prefers `crypto.subtle` when\n * present, but that API is gated to secure contexts — it is absent on plain\n * `about:blank`/`http:` pages — so fall back to a pure-JS digest to keep image\n * conversion working everywhere the converter runs.\n */\nasync function sha1(buffer: ArrayBuffer): Promise<Array<number>> {\n const subtle = globalThis.crypto?.subtle;\n if (subtle) {\n const hashBuffer = await subtle.digest(\"SHA-1\", buffer);\n return Array.from(new Uint8Array(hashBuffer));\n }\n return sha1Fallback(buffer);\n}\n\nfunction sha1Fallback(buffer: ArrayBuffer): Array<number> {\n const bytes = new Uint8Array(buffer);\n const bitLen = bytes.length * 8;\n // Pad to a multiple of 64 bytes: 0x80, zeros, then the 64-bit big-endian length.\n const totalLen = (((bytes.length + 8) >> 6) + 1) << 6;\n const block = new Uint8Array(totalLen);\n block.set(bytes);\n block[bytes.length] = 0x80;\n const view = new DataView(block.buffer);\n view.setUint32(totalLen - 8, Math.floor(bitLen / 2 ** 32), false);\n view.setUint32(totalLen - 4, bitLen >>> 0, false);\n\n let h0 = 0x67_45_23_01;\n let h1 = 0xef_cd_ab_89;\n let h2 = 0x98_ba_dc_fe;\n let h3 = 0x10_32_54_76;\n let h4 = 0xc3_d2_e1_f0;\n const w = new Uint32Array(80);\n\n for (let offset = 0; offset < totalLen; offset += 64) {\n for (let t = 0; t < 16; t += 1) {\n w[t] = view.getUint32(offset + t * 4, false);\n }\n for (let t = 16; t < 80; t += 1) {\n const x =\n (w[t - 3] ?? 0) ^ (w[t - 8] ?? 0) ^ (w[t - 14] ?? 0) ^ (w[t - 16] ?? 0);\n w[t] = (x << 1) | (x >>> 31);\n }\n\n let a = h0;\n let b = h1;\n let c = h2;\n let d = h3;\n let e = h4;\n for (let t = 0; t < 80; t += 1) {\n let f: number;\n let k: number;\n if (t < 20) {\n f = (b & c) | (~b & d);\n k = 0x5a_82_79_99;\n } else if (t < 40) {\n f = b ^ c ^ d;\n k = 0x6e_d9_eb_a1;\n } else if (t < 60) {\n f = (b & c) | (b & d) | (c & d);\n k = 0x8f_1b_bc_dc;\n } else {\n f = b ^ c ^ d;\n k = 0xca_62_c1_d6;\n }\n const tmp = (((a << 5) | (a >>> 27)) + f + e + k + (w[t] ?? 0)) | 0;\n e = d;\n d = c;\n c = (b << 30) | (b >>> 2);\n b = a;\n a = tmp;\n }\n\n h0 = (h0 + a) | 0;\n h1 = (h1 + b) | 0;\n h2 = (h2 + c) | 0;\n h3 = (h3 + d) | 0;\n h4 = (h4 + e) | 0;\n }\n\n const out = new Uint8Array(20);\n const outView = new DataView(out.buffer);\n outView.setUint32(0, h0 >>> 0, false);\n outView.setUint32(4, h1 >>> 0, false);\n outView.setUint32(8, h2 >>> 0, false);\n outView.setUint32(12, h3 >>> 0, false);\n outView.setUint32(16, h4 >>> 0, false);\n return Array.from(out);\n}\n","import { DedupCache } from \"./dedup-cache\";\nimport type { ImageBlobInfo, ImageLoader } from \"./nodes/image/loader\";\nimport { processImageFile } from \"./nodes/image/loader\";\n\nexport type ImageCache = DedupCache<HTMLImageElement, ImageBlobInfo>;\n\nexport function createImageCache(imageLoader: ImageLoader): ImageCache {\n return new DedupCache({\n load: (element) =>\n imageLoader({ src: element.src, element }).then(processImageFile),\n toCacheKey: (element) => element.src,\n });\n}\n","import type {\n FigmaBlob,\n FigmaCanvasNodeChange,\n FigmaClipboard,\n FigmaDocumentNodeChange,\n FigmaFrameNodeChange,\n FigmaGuid,\n} from \"../../types\";\n\nconst DEFAULT_FRAME_NAME = \"Frame\";\n\nfunction getGuid(id: number): FigmaGuid {\n return {\n sessionID: 0,\n localID: id,\n };\n}\n\nconst DOCUMENT_LOCAL_ID = 0;\nconst CANVAS_LOCAL_ID = 1;\nconst FRAME_LOCAL_ID = 2;\n\nconst createDocumentNodeChange: FigmaDocumentNodeChange = {\n guid: getGuid(DOCUMENT_LOCAL_ID),\n phase: \"CREATED\",\n type: \"DOCUMENT\",\n name: \"Unnamed\",\n visible: true,\n opacity: 1.0,\n blendMode: \"PASS_THROUGH\",\n mask: false,\n maskType: \"ALPHA\",\n};\n\nconst getCanvasNodeChange = (name: string): FigmaCanvasNodeChange => ({\n guid: getGuid(CANVAS_LOCAL_ID),\n phase: \"CREATED\",\n parentIndex: {\n guid: {\n sessionID: 0,\n localID: 0,\n },\n position: \"!\",\n },\n type: \"CANVAS\",\n name,\n visible: true,\n opacity: 1.0,\n blendMode: \"PASS_THROUGH\",\n transform: {\n m00: 1.0,\n m01: 0.0,\n m02: 0.0,\n m10: 0.0,\n m11: 1.0,\n m12: 0.0,\n },\n mask: false,\n maskType: \"ALPHA\",\n backgroundOpacity: 1.0,\n backgroundEnabled: true,\n});\n\nexport const ROOT_FRAME_GUID = getGuid(FRAME_LOCAL_ID);\n\n// Shared helper to create frame node changes for both single and multi-frame exports\nconst createFrameNodeChange = ({\n guid,\n width,\n height,\n name,\n x = 0,\n y = 0,\n}: {\n guid: FigmaGuid;\n width: number;\n height: number;\n name: string;\n x?: number;\n y?: number;\n}): FigmaFrameNodeChange => ({\n guid,\n phase: \"CREATED\",\n parentIndex: {\n guid: {\n sessionID: 0,\n localID: CANVAS_LOCAL_ID,\n },\n position: \"!\",\n },\n type: \"FRAME\",\n name,\n visible: true,\n opacity: 1.0,\n blendMode: \"PASS_THROUGH\",\n size: {\n x: width,\n y: height,\n },\n transform: {\n m00: 1.0,\n m01: 0.0,\n m02: x,\n m10: 0.0,\n m11: 1.0,\n m12: y,\n },\n strokeWeight: 1.0,\n strokeAlign: \"INSIDE\",\n strokeJoin: \"MITER\",\n fillPaints: [\n {\n type: \"SOLID\",\n color: {\n r: 1.0,\n g: 1.0,\n b: 1.0,\n a: 1.0,\n },\n opacity: 1.0,\n visible: true,\n blendMode: \"NORMAL\",\n },\n ],\n frameMaskDisabled: false,\n stackMode: \"VERTICAL\",\n});\n\nexport const getRootTemplate = ({\n width,\n height,\n blobs,\n name = DEFAULT_FRAME_NAME,\n}: {\n width: number;\n height: number;\n blobs: Array<FigmaBlob>;\n name?: string;\n}): FigmaClipboard => ({\n type: \"NODE_CHANGES\",\n sessionID: 0,\n ackID: 0,\n nodeChanges: [\n createDocumentNodeChange,\n getCanvasNodeChange(name),\n createFrameNodeChange({ guid: ROOT_FRAME_GUID, width, height, name }),\n ],\n blobs,\n pasteID: 777,\n pasteFileKey: \"IAMA_DUMMY_FILE_KEY_AMA\",\n pasteIsPartiallyOutsideEnclosingFrame: false,\n isCut: false,\n pasteEditorType: \"DESIGN\",\n publishedAssetGuids: [],\n});\n\ntype FrameConfig = {\n width: number;\n height: number;\n x: number;\n y: number;\n name: string;\n localId: number;\n};\n\nexport const getMultiFrameRootTemplate = ({\n frames,\n blobs,\n canvasName = DEFAULT_FRAME_NAME,\n}: {\n frames: Array<FrameConfig>;\n blobs: Array<FigmaBlob>;\n canvasName?: string;\n}): FigmaClipboard => {\n const frameNodeChanges: Array<FigmaFrameNodeChange> = frames.map((frame) =>\n createFrameNodeChange({\n guid: getGuid(frame.localId),\n width: frame.width,\n height: frame.height,\n name: frame.name,\n x: frame.x,\n y: frame.y,\n })\n );\n\n return {\n type: \"NODE_CHANGES\",\n sessionID: 0,\n ackID: 0,\n nodeChanges: [\n createDocumentNodeChange,\n getCanvasNodeChange(canvasName),\n ...frameNodeChanges,\n ],\n blobs,\n pasteID: 777,\n pasteFileKey: \"IAMA_DUMMY_FILE_KEY_AMA\",\n pasteIsPartiallyOutsideEnclosingFrame: false,\n isCut: false,\n pasteEditorType: \"DESIGN\",\n publishedAssetGuids: [],\n };\n};\n","import Color from \"colorjs.io\";\n\nimport type { FigmaColor, FigmaPaint } from \"../types\";\n\nexport const TRANSPARENT_COLOR_VALUES = [\n \"none\",\n \"transparent\",\n \"rgba(0, 0, 0, 0)\",\n];\n\nexport function cssColorToFigmaColor(cssColor: string): {\n color: FigmaColor;\n opacity: number;\n} | null {\n // Check if the color is transparent\n if (TRANSPARENT_COLOR_VALUES.includes(cssColor)) {\n return null;\n }\n\n const color = new Color(cssColor);\n\n return {\n color: {\n r: color.srgb[0] ?? 0,\n g: color.srgb[1] ?? 0,\n b: color.srgb[2] ?? 0,\n a: 1,\n },\n opacity: Number(color.alpha),\n };\n}\n\nexport function createSolidPaint(color: FigmaColor, opacity = 1): FigmaPaint {\n return {\n type: \"SOLID\",\n color,\n visible: true,\n opacity,\n blendMode: \"NORMAL\",\n // transform: {\n // m00: 1.0,\n // m01: 0.0,\n // m02: 0.0,\n // m10: 0.0,\n // m11: 1.0,\n // m12: 0.0,\n // },\n };\n}\n","import { TRANSPARENT_COLOR_VALUES } from \"./styles/color\";\n\nexport type ElementKind =\n | \"skip\"\n | \"group\"\n | \"frame\"\n | \"vector\"\n | \"image\"\n | \"text\"\n | \"form-with-placeholder\";\n\nexport function defaultClassify(element: Element): ElementKind {\n if (isNonVisualElement(element)) {\n return \"skip\";\n }\n if (isHiddenElement(element)) {\n return \"skip\";\n }\n if (isGroupElement(element)) {\n return \"group\";\n }\n if (isSvgShapeElement(element)) {\n return \"vector\";\n }\n if (isImageElement(element)) {\n return \"image\";\n }\n if (isPlainTextElement(element)) {\n return \"text\";\n }\n if (isFormElementWithPlaceholder(element) && hasPlaceholderText(element)) {\n return \"form-with-placeholder\";\n }\n return \"frame\";\n}\n\nfunction isNonVisualElement(element: Element): boolean {\n const tagName = element.tagName.toLowerCase();\n return (\n tagName === \"script\" ||\n tagName === \"style\" ||\n tagName === \"head\" ||\n tagName === \"meta\" ||\n tagName === \"title\" ||\n tagName === \"link\" ||\n tagName === \"noscript\" ||\n tagName === \"template\" ||\n tagName === \"comment\" ||\n tagName === \"defs\" ||\n tagName === \"desc\" ||\n tagName === \"clipPath\"\n );\n}\n\nfunction isHiddenElement(element: Element): boolean {\n const computedStyle = window.getComputedStyle(element);\n\n if (computedStyle.display === \"none\") {\n return true;\n }\n\n const clip = computedStyle.clip;\n if (clip === \"rect(0px, 0px, 0px, 0px)\" || clip === \"rect(0, 0, 0, 0)\") {\n return true;\n }\n\n return false;\n}\n\nfunction isGroupElement(element: Element): boolean {\n return element.tagName.toLowerCase() === \"g\";\n}\n\nfunction isSvgShapeElement(element: Element): boolean {\n const tagName = element.tagName.toLowerCase();\n return (\n tagName === \"path\" ||\n tagName === \"circle\" ||\n tagName === \"rect\" ||\n tagName === \"ellipse\" ||\n tagName === \"line\" ||\n tagName === \"polyline\" ||\n tagName === \"polygon\"\n );\n}\n\nfunction isImageElement(element: Element): boolean {\n return element.tagName.toLowerCase() === \"img\";\n}\n\n/**\n * A plain text element is a leaf with text content and no painted box of its\n * own (no padding, border, or background). The whole element is treated as\n * text rather than a frame containing text.\n */\nfunction isPlainTextElement(element: Element): boolean {\n const computedStyle = window.getComputedStyle(element);\n const hasText = !!(element.textContent || \"\").trim().length;\n const isTransparent = TRANSPARENT_COLOR_VALUES.includes(\n computedStyle.backgroundColor\n );\n const hasNoPadding = computedStyle.padding === \"0px\";\n const hasNoBorder = computedStyle.borderWidth === \"0px\";\n\n return (\n hasText &&\n element.children.length === 0 &&\n isTransparent &&\n hasNoPadding &&\n hasNoBorder\n );\n}\n\nconst FORM_PLACEHOLDER_EXCLUDED_TYPES = [\n \"checkbox\",\n \"radio\",\n \"submit\",\n \"button\",\n \"file\",\n \"hidden\",\n];\n\nfunction isFormElementWithPlaceholder(element: Element): boolean {\n const tagName = element.tagName.toLowerCase();\n const inputType = (element as HTMLInputElement).type?.toLowerCase() || \"\";\n\n return (\n (tagName === \"input\" &&\n !FORM_PLACEHOLDER_EXCLUDED_TYPES.includes(inputType)) ||\n tagName === \"textarea\"\n );\n}\n\nfunction hasPlaceholderText(element: Element): boolean {\n const placeholder = element.getAttribute(\"placeholder\");\n return !!(placeholder && placeholder.trim().length > 0);\n}\n","export type Position = {\n x: number;\n y: number;\n};\n\nexport type Size = {\n width: number;\n height: number;\n};\n\nexport function isTextNode(node: Node): node is Text {\n return node.nodeType === Node.TEXT_NODE;\n}\n\nexport function isElementNode(node: Node): node is Element {\n return node.nodeType === Node.ELEMENT_NODE;\n}\n\nexport function isTextEmpty(text: Text): boolean {\n return !(text.textContent || \"\").trim().length;\n}\n\nexport function isSvgElement(element: Element): boolean {\n return element.tagName.toLowerCase() === \"svg\";\n}\n\n/**\n * Gets the position of an element relative to its parent.\n */\nexport function getElementPositionRelativeToParent(element: Element): Position {\n const parentElement = element.parentElement;\n const elementRect = element.getBoundingClientRect();\n\n if (!parentElement) {\n return { x: elementRect.left, y: elementRect.top };\n }\n\n const parentRect = parentElement.getBoundingClientRect();\n return {\n x: elementRect.left - parentRect.left,\n y: elementRect.top - parentRect.top,\n };\n}\n\nexport function getElementSize(element: Element): Size {\n const elementRect = element.getBoundingClientRect();\n return { width: elementRect.width, height: elementRect.height };\n}\n\nfunction getTextRect(textNode: Text) {\n const range = document.createRange();\n range.selectNodeContents(textNode);\n return range.getBoundingClientRect();\n}\n\nexport function getTextPositionRelativeToParent(textNode: Text): Position {\n const parentElement = textNode.parentElement;\n if (!parentElement) {\n return { x: 0, y: 0 };\n }\n\n const textNodeRect = getTextRect(textNode);\n const parentElementRect = parentElement.getBoundingClientRect();\n\n return {\n x: textNodeRect.left - parentElementRect.left,\n y: textNodeRect.top - parentElementRect.top,\n };\n}\n\n/**\n * The browser's measured box for a text node — no inflation.\n *\n * Figma remeasures text with its own font engine and can come out a little\n * wider than the browser did, but the slack that absorbs that belongs to the\n * line-breaking container (see `wrappingContainerWidth` in the text converter),\n * not to the size we report for the node. Inflating here made every text node\n * ship a box up to 2px wider than the browser's, which showed up as a standing\n * `geometry.width` discrepancy and let text overflow a bordered inline parent's\n * content box.\n */\nexport function getTextSize(textNode: Text): Size {\n const textNodeRect = getTextRect(textNode);\n return { width: textNodeRect.width, height: textNodeRect.height };\n}\n\nexport type TextLineSegment = {\n text: string;\n position: Position;\n size: Size;\n};\n\n/**\n * Split a text node into per-line segments — but only for the one shape a\n * single Figma text box cannot represent: a node that CONTINUES a line\n * started by a preceding inline sibling and then wraps (e.g. the tail text\n * in `<h1>a <span>b</span> tail that wraps</h1>`). Its first line starts at\n * an indent the box cannot encode, so the union box renders a line too high.\n *\n * Returns `null` for every other shape (single line, standalone multi-line\n * runs) so regular paragraphs keep one editable text layer.\n */\nexport function splitMidLineWrappedText(\n textNode: Text,\n options?: {\n /** Node whose previous sibling defines \"continues a line\" — the inline\n * element itself when splitting an element's inner text. */\n siblingContext?: Node;\n /** Element the segment positions are relative to. */\n relativeTo?: Element;\n }\n): Array<TextLineSegment> | null {\n const relativeTo = options?.relativeTo ?? textNode.parentElement;\n const siblingContext = options?.siblingContext ?? textNode;\n const doc = textNode.ownerDocument;\n const content = textNode.textContent ?? \"\";\n if (!relativeTo || content.trim().length === 0) {\n return null;\n }\n\n const range = doc.createRange();\n range.selectNodeContents(textNode);\n const lineRects = Array.from(range.getClientRects()).filter(\n (rect) => rect.width > 1\n );\n if (lineRects.length < 2) {\n return null;\n }\n\n // Only split when the first line genuinely continues a sibling's line.\n if (!sharesLineWithPreviousSibling(siblingContext, lineRects[0] as DOMRect)) {\n return null;\n }\n\n // Group characters into lines by their rendered top edge.\n const parentRect = relativeTo.getBoundingClientRect();\n const segments: Array<TextLineSegment> = [];\n let start = 0;\n let lineTop: number | null = null;\n const flush = (end: number) => {\n // Measure without surrounding whitespace so boxes stay tight — the\n // emitted text is trimmed, and space widths would bleed into neighbors.\n let from = start;\n while (from < end && /\\s/.test(content[from] as string)) {\n from += 1;\n }\n let to = end;\n while (to > from && /\\s/.test(content[to - 1] as string)) {\n to -= 1;\n }\n if (to <= from) {\n return;\n }\n const segmentRange = doc.createRange();\n segmentRange.setStart(textNode, from);\n segmentRange.setEnd(textNode, to);\n const rect = segmentRange.getBoundingClientRect();\n segments.push({\n text: content.slice(from, to),\n position: {\n x: rect.left - parentRect.left,\n y: rect.top - parentRect.top,\n },\n size: { width: rect.width, height: rect.height },\n });\n };\n\n for (let i = 0; i < content.length; i += 1) {\n const charRange = doc.createRange();\n charRange.setStart(textNode, i);\n charRange.setEnd(textNode, i + 1);\n const rect = charRange.getBoundingClientRect();\n if (rect.width <= 0.5 && rect.height <= 0.5) {\n continue; // Collapsed whitespace at a wrap point.\n }\n if (lineTop === null) {\n lineTop = rect.top;\n } else if (Math.abs(rect.top - lineTop) > rect.height / 2) {\n flush(i);\n start = i;\n lineTop = rect.top;\n }\n }\n flush(content.length);\n\n return segments.length >= 2 ? segments : null;\n}\n\nfunction sharesLineWithPreviousSibling(\n contextNode: Node,\n firstLineRect: DOMRect\n): boolean {\n let sibling = contextNode.previousSibling;\n while (sibling && isTextNode(sibling) && isTextEmpty(sibling)) {\n sibling = sibling.previousSibling;\n }\n if (!sibling) {\n return false;\n }\n\n const doc = contextNode.ownerDocument;\n if (!doc) {\n return false;\n }\n const range = doc.createRange();\n range.selectNodeContents(sibling);\n const rects = Array.from(range.getClientRects()).filter((r) => r.width > 1);\n const last = rects.at(-1);\n if (!last) {\n return false;\n }\n const firstCenter = firstLineRect.top + firstLineRect.height / 2;\n return last.top < firstCenter && firstCenter < last.bottom;\n}\n\nexport function sortNodesByStackingOrder(nodes: Array<Node>) {\n const nonPositioned: Array<Node> = [];\n const negativeZIndex: Array<{ node: Element; zIndex: number }> = [];\n const positionedAuto: Array<Element> = [];\n const positiveZIndex: Array<{ node: Element; zIndex: number }> = [];\n\n for (const node of nodes) {\n if (!isElementNode(node)) {\n nonPositioned.push(node);\n continue;\n }\n\n const style = window.getComputedStyle(node);\n const position = style.position;\n const isPositioned =\n position === \"absolute\" ||\n position === \"relative\" ||\n position === \"fixed\" ||\n position === \"sticky\";\n\n if (!isPositioned) {\n nonPositioned.push(node);\n continue;\n }\n\n const zIndex =\n style.zIndex === \"auto\" ? \"auto\" : Number.parseInt(style.zIndex, 10) || 0;\n\n if (zIndex === \"auto\") {\n positionedAuto.push(node);\n } else if (zIndex < 0) {\n negativeZIndex.push({ node, zIndex });\n } else {\n positiveZIndex.push({ node, zIndex });\n }\n }\n\n negativeZIndex.sort((a, b) => a.zIndex - b.zIndex);\n positiveZIndex.sort((a, b) => a.zIndex - b.zIndex);\n\n return [\n ...nonPositioned,\n ...negativeZIndex.map((item) => item.node),\n ...positionedAuto,\n ...positiveZIndex.map((item) => item.node),\n ];\n}\n","import { sortNodesByStackingOrder } from \"../dom\";\n\n/**\n * Auto-layout properties inferred from a flex container, phrased directly in\n * kiwi NodeChange fields. Returned only when the reconstructed geometry\n * matches what the browser actually laid out (see `verifyGeometry`), so\n * applying these to a frame never moves pixels — callers fall back to\n * absolute positioning (`stackMode: \"NONE\"`) on `null`.\n */\ntype InferredStack = {\n stackMode: \"HORIZONTAL\" | \"VERTICAL\";\n stackSpacing: number;\n stackPrimaryAlignItems: StackJustifyValue;\n stackCounterAlignItems: StackAlignValue;\n /** Sizing modes must always be explicit: pasting a stack without them makes\n * Figma hug-to-content on the primary axis, shrinking the frame\n * (established by oracle batch-01). RESIZE_TO_FIT (hug) is emitted only\n * when the CSS declares a content-driven size AND the measured frame size\n * equals the content size, so the paste-time hug is a no-op. */\n stackPrimarySizing: StackSizingValue;\n stackCounterSizing: StackSizingValue;\n /** Left padding. Includes the border width: Figma lays out from the frame\n * edge while CSS offsets children by border + padding. */\n stackHorizontalPadding: number;\n /** Top padding, border included. */\n stackVerticalPadding: number;\n stackPaddingRight: number;\n stackPaddingBottom: number;\n /** Multi-line rows (flex-wrap / uniform grids). */\n stackWrap?: \"WRAP\";\n /** Gap between wrapped rows. */\n stackCounterSpacing?: number;\n /** Set for reversed flex directions: children are emitted in visual order,\n * so Figma's z-order must flip to preserve the browser's paint order. */\n stackReverseZIndex?: boolean;\n};\n\n/** Auto-layout child overrides, keyed by child element by `inferAutoLayout`. */\nexport type InferredChildStack = {\n /** flex-grow child whose size matches Figma's fill-container distribution. */\n stackChildPrimaryGrow?: 1;\n /** Child stretched across the counter axis with no explicit cross size. */\n stackChildAlignSelf?: \"STRETCH\";\n /** Absolutely positioned child inside a stack: excluded from layout, kept\n * at its transform with its constraints — mirroring CSS semantics. */\n stackPositioning?: \"ABSOLUTE\";\n};\n\nexport type InferredAutoLayout = {\n stack: InferredStack;\n /** Per-child overrides; children not in the map get fixed sizing. */\n children: ReadonlyMap<Element, InferredChildStack>;\n /** Reversed flex direction: the walker must emit children in reverse\n * (visual) order, paired with `stackReverseZIndex` on the stack. */\n reverseChildren?: boolean;\n};\n\ntype StackJustifyValue = \"MIN\" | \"CENTER\" | \"MAX\" | \"SPACE_BETWEEN\";\ntype StackAlignValue = \"MIN\" | \"CENTER\" | \"MAX\";\ntype StackSizingValue = \"FIXED\" | \"RESIZE_TO_FIT\";\n\n/** Max deviation (px) between reconstructed and measured child positions. */\nconst GEOMETRY_TOLERANCE = 0.6;\n\nconst JUSTIFY_MAP: Record<string, StackJustifyValue> = {\n normal: \"MIN\",\n \"flex-start\": \"MIN\",\n start: \"MIN\",\n left: \"MIN\",\n center: \"CENTER\",\n \"flex-end\": \"MAX\",\n end: \"MAX\",\n right: \"MAX\",\n \"space-between\": \"SPACE_BETWEEN\",\n // The kiwi enum has SPACE_EVENLY but Figma's engine renders it as\n // space-between (oracle batch-01). With spacing measured from real rects,\n // CENTER reproduces both space-evenly (leading gap = g) and space-around\n // (leading gap = g/2) exactly; verifyGeometry guards the equivalence.\n \"space-evenly\": \"CENTER\",\n \"space-around\": \"CENTER\",\n};\n\nconst ALIGN_MAP: Record<string, StackAlignValue> = {\n // `stretch`/`normal` only differ from `start` when a child has no explicit\n // cross size; geometry verification rejects the container in that case.\n normal: \"MIN\",\n stretch: \"MIN\",\n \"flex-start\": \"MIN\",\n start: \"MIN\",\n center: \"CENTER\",\n \"flex-end\": \"MAX\",\n end: \"MAX\",\n // baseline needs font-metric math we don't model yet -> bail.\n};\n\ntype Rect = { x: number; y: number; width: number; height: number };\n\n/** Block-level outer displays that can participate in vertical block flow. */\nconst BLOCK_LEVEL_DISPLAYS = new Set([\n \"block\",\n \"flex\",\n \"grid\",\n \"flow-root\",\n \"table\",\n \"list-item\",\n]);\n\n/**\n * Infer Figma auto-layout properties for an element, or return `null` when\n * the container's layout can't be reproduced exactly (then it stays\n * absolutely positioned, which is always safe).\n *\n * Covers flex containers (HORIZONTAL/VERTICAL by direction) and plain block\n * flow (VERTICAL when spacing is uniform). Absolutely positioned children\n * ride along with `stackPositioning: \"ABSOLUTE\"` instead of blocking the\n * container. Out of scope (bails): wrapping, reverse directions, text-node\n * flow items, `order`, z-index reordering, baseline alignment, floats,\n * inline flow.\n */\nexport function inferAutoLayout(element: Element): InferredAutoLayout | null {\n const style = window.getComputedStyle(element);\n const display = style.display;\n const isFlex = display === \"flex\" || display === \"inline-flex\";\n const isBlock = display === \"block\" || display === \"flow-root\";\n const isGrid = display === \"grid\" || display === \"inline-grid\";\n if (!(isFlex || isBlock || isGrid)) {\n return null;\n }\n\n const collected = collectChildren(element);\n if (!collected || collected.flow.length === 0) {\n return null;\n }\n const { flow, absolute } = collected;\n\n // The walker emits children in stacking order and Figma lays a stack out\n // in child order, so a z-index reshuffle among FLOW children would change\n // the layout order. Absolute children may move freely: they are excluded\n // from layout on both sides and stacking order only affects z, which the\n // walker preserves.\n const flowSet = new Set<Node>(flow);\n const domFlow = Array.from(element.childNodes).filter((n) => flowSet.has(n));\n const stackedFlow = sortNodesByStackingOrder(\n Array.from(element.childNodes)\n ).filter((n) => flowSet.has(n));\n if (domFlow.some((node, i) => node !== stackedFlow[i])) {\n return null;\n }\n\n const parentRect = element.getBoundingClientRect();\n const childRects: Array<Rect> = flow.map((child) => {\n const rect = child.getBoundingClientRect();\n return {\n x: rect.left - parentRect.left,\n y: rect.top - parentRect.top,\n width: rect.width,\n height: rect.height,\n };\n });\n const parentSize = { width: parentRect.width, height: parentRect.height };\n\n const input: StackInferenceInput = {\n element,\n style,\n flow,\n childRects,\n parentSize,\n };\n let inferred: StackInference | null = null;\n if (isFlex) {\n inferred =\n style.flexWrap === \"nowrap\"\n ? inferFlexStack(input)\n : inferWrapStack(input, \"flex\");\n } else if (isBlock) {\n inferred = inferBlockStack(input);\n } else {\n // Single-column grids are semantically vertical stacks (the block path\n // covers them); multi-column uniform grids match Figma's greedy wrap\n // layout exactly, gated by the packing simulation.\n inferred = inferBlockStack(input) ?? inferWrapStack(input, \"grid\");\n }\n if (!inferred) {\n return null;\n }\n\n // Reversal reorders emission; mixing that with absolute children (which\n // keep their stacking positions) isn't modeled yet.\n if (inferred.reverseChildren && absolute.length > 0) {\n return null;\n }\n\n for (const child of absolute) {\n inferred.childOverrides.set(child, { stackPositioning: \"ABSOLUTE\" });\n }\n\n return {\n stack: inferred.stack,\n children: inferred.childOverrides,\n reverseChildren: inferred.reverseChildren,\n };\n}\n\ntype StackInferenceInput = {\n element: Element;\n style: CSSStyleDeclaration;\n flow: ReadonlyArray<Element>;\n childRects: ReadonlyArray<Rect>;\n parentSize: { width: number; height: number };\n};\n\ntype StackInference = {\n stack: InferredStack;\n childOverrides: Map<Element, InferredChildStack>;\n reverseChildren?: boolean;\n};\n\nfunction inferFlexStack(input: StackInferenceInput): StackInference | null {\n const { element, style, parentSize } = input;\n\n const direction = style.flexDirection;\n const isRow = direction === \"row\" || direction === \"row-reverse\";\n const reversed = direction.endsWith(\"-reverse\");\n\n // Reversed directions: work in visual order (reversed DOM order) — the\n // walker will emit children the same way.\n const flow = reversed ? [...input.flow].reverse() : input.flow;\n const childRects = reversed\n ? [...input.childRects].reverse()\n : input.childRects;\n\n let justify = JUSTIFY_MAP[style.justifyContent];\n const align = ALIGN_MAP[style.alignItems];\n if (!(justify && align)) {\n return null;\n }\n // In reversed flow the main-axis start/end swap in visual terms.\n if (reversed && justify === \"MIN\") {\n justify = \"MAX\";\n } else if (reversed && justify === \"MAX\") {\n justify = \"MIN\";\n }\n\n const spacing = uniformGap(childRects, isRow);\n if (spacing === null) {\n return null;\n }\n\n const spec: InferredStack = {\n stackMode: isRow ? \"HORIZONTAL\" : \"VERTICAL\",\n stackSpacing: spacing,\n stackPrimaryAlignItems: justify,\n stackCounterAlignItems: align,\n stackPrimarySizing: \"FIXED\",\n stackCounterSizing: \"FIXED\",\n stackHorizontalPadding: round2(\n edge(style.borderLeftWidth) + edge(style.paddingLeft)\n ),\n stackVerticalPadding: round2(\n edge(style.borderTopWidth) + edge(style.paddingTop)\n ),\n stackPaddingRight: round2(\n edge(style.borderRightWidth) + edge(style.paddingRight)\n ),\n stackPaddingBottom: round2(\n edge(style.borderBottomWidth) + edge(style.paddingBottom)\n ),\n ...(reversed && { stackReverseZIndex: true }),\n };\n\n if (!verifyGeometry(spec, parentSize, childRects)) {\n return null;\n }\n\n applySizingModes({\n element,\n style,\n spec,\n parent: parentSize,\n children: flow,\n childRects,\n isRow,\n });\n\n return {\n stack: spec,\n childOverrides: inferChildOverrides({\n element,\n children: flow,\n childRects,\n parentStyle: style,\n spec,\n parentSize,\n isRow,\n }),\n reverseChildren: reversed || undefined,\n };\n}\n\n/**\n * Multi-line rows as a wrapped HORIZONTAL stack (`flex-wrap: wrap`, and\n * uniform CSS grids, which lay out identically when every child fits the\n * same greedy packing). Figma re-flows wrapped stacks with its own greedy\n * row-breaking on paste, so conversion requires that a simulation of that\n * packing reproduces the browser's exact row assignments and positions —\n * otherwise the container falls back to absolute positioning.\n */\nfunction inferWrapStack(\n input: StackInferenceInput,\n source: \"flex\" | \"grid\"\n): StackInference | null {\n const { element, style, flow, childRects, parentSize } = input;\n\n if (source === \"flex\") {\n if (style.flexDirection !== \"row\" || style.flexWrap !== \"wrap\") {\n return null; // wrap-reverse / column wrap aren't modeled.\n }\n // Row-internal distribution other than left-packed changes Figma's\n // re-flow in ways the simulation below doesn't model.\n if (JUSTIFY_MAP[style.justifyContent] !== \"MIN\") {\n return null;\n }\n if (ALIGN_MAP[style.alignItems] !== \"MIN\") {\n return null;\n }\n }\n for (const child of flow) {\n if (Number.parseFloat(window.getComputedStyle(child).flexGrow) > 0) {\n return null; // fill-in-wrap is unverified against Figma.\n }\n }\n\n const padLeft = edge(style.borderLeftWidth) + edge(style.paddingLeft);\n const padTop = edge(style.borderTopWidth) + edge(style.paddingTop);\n const padRight = edge(style.borderRightWidth) + edge(style.paddingRight);\n const padBottom = edge(style.borderBottomWidth) + edge(style.paddingBottom);\n\n // Group measured rects into rows by top edge (children of a row share it\n // for the start/stretch alignments accepted above).\n const rows: Array<Array<number>> = [];\n let currentTop = Number.NEGATIVE_INFINITY;\n childRects.forEach((rect, i) => {\n if (Math.abs(rect.y - currentTop) > GEOMETRY_TOLERANCE) {\n rows.push([]);\n currentTop = rect.y;\n }\n (rows.at(-1) as Array<number>).push(i);\n });\n\n const inRowGaps: Array<number> = [];\n for (const row of rows) {\n for (let k = 1; k < row.length; k += 1) {\n const prev = childRects[row[k - 1] as number] as Rect;\n const cur = childRects[row[k] as number] as Rect;\n inRowGaps.push(cur.x - (prev.x + prev.width));\n }\n }\n const spacing = round2(inRowGaps[0] ?? 0);\n if (inRowGaps.some((g) => Math.abs(g - spacing) > GEOMETRY_TOLERANCE)) {\n return null;\n }\n\n const rowTops = rows.map((r) => (childRects[r[0] as number] as Rect).y);\n const rowHeights = rows.map((r) =>\n Math.max(...r.map((i) => (childRects[i] as Rect).height))\n );\n const rowGaps: Array<number> = [];\n for (let r = 1; r < rows.length; r += 1) {\n rowGaps.push(\n (rowTops[r] as number) -\n ((rowTops[r - 1] as number) + (rowHeights[r - 1] as number))\n );\n }\n const counterSpacing = round2(rowGaps[0] ?? 0);\n if (\n counterSpacing < -GEOMETRY_TOLERANCE ||\n rowGaps.some((g) => Math.abs(g - counterSpacing) > GEOMETRY_TOLERANCE)\n ) {\n return null;\n }\n\n // Simulate Figma's greedy packing; every child must land exactly where the\n // browser put it.\n const rightLimit = parentSize.width - padRight;\n let x = padLeft;\n let y = padTop;\n let simRowHeight = 0;\n let rowStart = true;\n for (const rect of childRects) {\n if (!rowStart && x + rect.width - rightLimit > GEOMETRY_TOLERANCE) {\n y += simRowHeight + counterSpacing;\n x = padLeft;\n simRowHeight = 0;\n }\n if (\n Math.abs(rect.x - x) > GEOMETRY_TOLERANCE ||\n Math.abs(rect.y - y) > GEOMETRY_TOLERANCE\n ) {\n return null;\n }\n x += rect.width + spacing;\n simRowHeight = Math.max(simRowHeight, rect.height);\n rowStart = false;\n }\n\n const spec: InferredStack = {\n stackMode: \"HORIZONTAL\",\n stackSpacing: spacing,\n stackPrimaryAlignItems: \"MIN\",\n stackCounterAlignItems: \"MIN\",\n stackPrimarySizing: \"FIXED\",\n stackCounterSizing: \"FIXED\",\n stackHorizontalPadding: round2(padLeft),\n stackVerticalPadding: round2(padTop),\n stackPaddingRight: round2(padRight),\n stackPaddingBottom: round2(padBottom),\n stackWrap: \"WRAP\",\n stackCounterSpacing: counterSpacing,\n };\n\n // Counter-axis hug: rows content height equals the frame height.\n const contentHeight =\n padTop +\n padBottom +\n rowHeights.reduce((n, h) => n + h, 0) +\n counterSpacing * (rows.length - 1);\n if (\n isContentDrivenSize(element, style, \"height\") &&\n Math.abs(parentSize.height - contentHeight) <= GEOMETRY_TOLERANCE\n ) {\n spec.stackCounterSizing = \"RESIZE_TO_FIT\";\n }\n\n return { stack: spec, childOverrides: new Map() };\n}\n\n/**\n * Plain block flow as a VERTICAL stack: block-level children stacked top to\n * bottom with uniform (margin-driven) spacing.\n *\n * Vertical paddings are measured (the first/last child margins fold into\n * them, and margin collapse is already baked into the rects); horizontal\n * paddings come from CSS. The counter alignment is whichever of MIN /\n * CENTER / MAX reproduces the measured geometry — CENTER covers\n * `margin: 0 auto` centering.\n */\nfunction inferBlockStack(input: StackInferenceInput): StackInference | null {\n const { element, style, flow, childRects, parentSize } = input;\n\n for (const child of flow) {\n const childStyle = window.getComputedStyle(child);\n if (!BLOCK_LEVEL_DISPLAYS.has(childStyle.display)) {\n return null; // Inline flow — not representable as a stack.\n }\n if (childStyle.float !== \"none\") {\n return null;\n }\n }\n\n const spacing = uniformGap(childRects, false);\n if (spacing === null) {\n return null;\n }\n\n const first = childRects[0] as Rect;\n const last = childRects.at(-1) as Rect;\n const padTop = first.y;\n const padBottom = parentSize.height - (last.y + last.height);\n if (padTop < -GEOMETRY_TOLERANCE || padBottom < -GEOMETRY_TOLERANCE) {\n return null; // Content overflows the frame; a stack would clip/shift it.\n }\n\n const padLeft = edge(style.borderLeftWidth) + edge(style.paddingLeft);\n const padRight = edge(style.borderRightWidth) + edge(style.paddingRight);\n\n for (const align of [\"MIN\", \"CENTER\", \"MAX\"] as const) {\n const spec: InferredStack = {\n stackMode: \"VERTICAL\",\n stackSpacing: spacing,\n stackPrimaryAlignItems: \"MIN\",\n stackCounterAlignItems: align,\n stackPrimarySizing: \"FIXED\",\n stackCounterSizing: \"FIXED\",\n stackHorizontalPadding: round2(padLeft),\n stackVerticalPadding: round2(Math.max(padTop, 0)),\n stackPaddingRight: round2(padRight),\n stackPaddingBottom: round2(Math.max(padBottom, 0)),\n };\n if (!verifyGeometry(spec, parentSize, childRects)) {\n continue;\n }\n\n applySizingModes({\n element,\n style,\n spec,\n parent: parentSize,\n children: flow,\n childRects,\n isRow: false,\n });\n\n // Block children with `width: auto` fill the content box — exactly\n // Figma's STRETCH on the counter axis of a vertical stack.\n const childOverrides = new Map<Element, InferredChildStack>();\n const innerWidth = parentSize.width - padLeft - padRight;\n flow.forEach((child, i) => {\n if (\n hasContentSizedKeyword(child, \"width\") &&\n Math.abs((childRects[i] as Rect).width - innerWidth) <=\n GEOMETRY_TOLERANCE\n ) {\n childOverrides.set(child, { stackChildAlignSelf: \"STRETCH\" });\n }\n });\n\n return { stack: spec, childOverrides };\n }\n\n return null;\n}\n\n/** Uniform inter-child gap along the given axis, or null when non-uniform.\n * Measured from rects, so CSS gaps and margins (incl. collapse) are covered;\n * negative values are fine — Figma supports them. */\nfunction uniformGap(\n childRects: ReadonlyArray<Rect>,\n isRow: boolean\n): number | null {\n const gaps: Array<number> = [];\n for (let i = 1; i < childRects.length; i += 1) {\n const prev = childRects[i - 1] as Rect;\n const next = childRects[i] as Rect;\n gaps.push(\n isRow ? next.x - (prev.x + prev.width) : next.y - (prev.y + prev.height)\n );\n }\n if (gaps.some((gap) => Math.abs(gap - (gaps[0] ?? 0)) > GEOMETRY_TOLERANCE)) {\n return null;\n }\n return round2(gaps[0] ?? 0);\n}\n\n/**\n * Upgrade FIXED to RESIZE_TO_FIT (hug) per axis when the CSS declares a\n * content-driven size AND the measured frame size equals the content size —\n * so Figma's paste-time hug is provably a no-op.\n *\n * Primary hug is never emitted for SPACE_BETWEEN containers or when a child\n * grows: in both cases the measured spacing/child sizes embed the free\n * space, making \"content == frame\" hold vacuously.\n */\nfunction applySizingModes(options: {\n element: Element;\n style: CSSStyleDeclaration;\n spec: InferredStack;\n parent: { width: number; height: number };\n children: ReadonlyArray<Element>;\n childRects: ReadonlyArray<Rect>;\n isRow: boolean;\n}) {\n const { element, style, spec, parent, children, childRects, isRow } = options;\n const spacing = spec.stackSpacing;\n const count = childRects.length;\n const primaryContent =\n childRects.reduce((n, r) => n + (isRow ? r.width : r.height), 0) +\n spacing * (count - 1) +\n (isRow\n ? spec.stackHorizontalPadding + spec.stackPaddingRight\n : spec.stackVerticalPadding + spec.stackPaddingBottom);\n const crossContent =\n Math.max(...childRects.map((r) => (isRow ? r.height : r.width))) +\n (isRow\n ? spec.stackVerticalPadding + spec.stackPaddingBottom\n : spec.stackHorizontalPadding + spec.stackPaddingRight);\n\n const primaryDriven = isContentDrivenSize(\n element,\n style,\n isRow ? \"width\" : \"height\"\n );\n const crossDriven = isContentDrivenSize(\n element,\n style,\n isRow ? \"height\" : \"width\"\n );\n const primarySize = isRow ? parent.width : parent.height;\n const crossSize = isRow ? parent.height : parent.width;\n\n const hasGrower = children.some(\n (child) => Number.parseFloat(window.getComputedStyle(child).flexGrow) > 0\n );\n const distributesFreeSpace =\n spec.stackPrimaryAlignItems === \"SPACE_BETWEEN\" || hasGrower;\n\n if (\n primaryDriven &&\n !distributesFreeSpace &&\n Math.abs(primarySize - primaryContent) <= GEOMETRY_TOLERANCE\n ) {\n spec.stackPrimarySizing = \"RESIZE_TO_FIT\";\n }\n if (crossDriven && Math.abs(crossSize - crossContent) <= GEOMETRY_TOLERANCE) {\n spec.stackCounterSizing = \"RESIZE_TO_FIT\";\n }\n}\n\n/**\n * Per-child fill/stretch overrides.\n *\n * Grow: flex-grow children map to Figma's fill-container only when their\n * measured sizes match Figma's model (fill children split the leftover space\n * equally, with no flex-basis notion). Otherwise they stay fixed at their\n * final size — geometry is identical either way; only resize behavior\n * differs.\n *\n * Stretch: children whose resolved align-self is stretch, with no explicit\n * cross size, and whose measured cross size fills the container.\n */\nfunction inferChildOverrides(options: {\n element: Element;\n children: ReadonlyArray<Element>;\n childRects: ReadonlyArray<Rect>;\n parentStyle: CSSStyleDeclaration;\n spec: InferredStack;\n parentSize: { width: number; height: number };\n isRow: boolean;\n}): Map<Element, InferredChildStack> {\n const { children, childRects, parentStyle, spec, parentSize, isRow } =\n options;\n const overrides = new Map<Element, InferredChildStack>();\n\n const inner =\n (isRow ? parentSize.width : parentSize.height) -\n (isRow\n ? spec.stackHorizontalPadding + spec.stackPaddingRight\n : spec.stackVerticalPadding + spec.stackPaddingBottom);\n const innerCross =\n (isRow ? parentSize.height : parentSize.width) -\n (isRow\n ? spec.stackVerticalPadding + spec.stackPaddingBottom\n : spec.stackHorizontalPadding + spec.stackPaddingRight);\n\n const primaryOf = (rect: Rect) => (isRow ? rect.width : rect.height);\n const crossOf = (rect: Rect) => (isRow ? rect.height : rect.width);\n\n const styles = children.map((child) => window.getComputedStyle(child));\n const growers = children.filter(\n (_, i) => Number.parseFloat((styles[i] as CSSStyleDeclaration).flexGrow) > 0\n );\n\n if (growers.length > 0) {\n const fixedTotal = children.reduce(\n (n, child, i) =>\n growers.includes(child) ? n : n + primaryOf(childRects[i] as Rect),\n 0\n );\n const fillShare =\n (inner - fixedTotal - spec.stackSpacing * (children.length - 1)) /\n growers.length;\n const matchesFigmaFill = children.every(\n (child, i) =>\n !growers.includes(child) ||\n Math.abs(primaryOf(childRects[i] as Rect) - fillShare) <=\n GEOMETRY_TOLERANCE\n );\n if (matchesFigmaFill) {\n for (const child of growers) {\n overrides.set(child, { stackChildPrimaryGrow: 1 });\n }\n }\n }\n\n children.forEach((child, i) => {\n const childStyle = styles[i] as CSSStyleDeclaration;\n const alignSelf =\n childStyle.alignSelf === \"auto\" || childStyle.alignSelf === \"normal\"\n ? parentStyle.alignItems\n : childStyle.alignSelf;\n const stretches = alignSelf === \"stretch\" || alignSelf === \"normal\";\n const crossProp = isRow ? \"height\" : \"width\";\n if (\n stretches &&\n hasContentSizedKeyword(child, crossProp) &&\n Math.abs(crossOf(childRects[i] as Rect) - innerCross) <=\n GEOMETRY_TOLERANCE\n ) {\n overrides.set(child, {\n ...overrides.get(child),\n stackChildAlignSelf: \"STRETCH\",\n });\n }\n });\n\n return overrides;\n}\n\nconst CONTENT_SIZED_KEYWORDS = new Set([\n \"auto\",\n \"min-content\",\n \"max-content\",\n \"fit-content\",\n]);\n\n/**\n * Whether the CSS declares this axis as content-sized (`auto`,\n * `fit-content`, ...) rather than a length/percentage. Uses the Typed OM\n * (`computedStyleMap`), which preserves keywords that `getComputedStyle`\n * resolves to used pixel values; browsers without it get `false`, degrading\n * to FIXED sizing everywhere.\n */\nfunction hasContentSizedKeyword(\n element: Element,\n property: \"width\" | \"height\"\n): boolean {\n if (typeof element.computedStyleMap !== \"function\") {\n return false;\n }\n const value = element.computedStyleMap().get(property);\n // The keyword class must come from the element's own realm: for elements\n // inside iframes, an instanceof against this window's CSSKeywordValue is\n // always false.\n const KeywordValue = element.ownerDocument?.defaultView?.CSSKeywordValue;\n return (\n KeywordValue !== undefined &&\n value instanceof KeywordValue &&\n CONTENT_SIZED_KEYWORDS.has(value.value)\n );\n}\n\n/**\n * Whether `auto` on this axis actually means shrink-to-content:\n * - `width: auto` on a block fills the parent; on a flex item it is\n * content-sized along the parent's main axis but STRETCH on the cross axis\n * (unless align-self opts out). Shrink-wrap contexts (inline-flex, floats,\n * absolute positioning) are content-sized.\n * - `height: auto` is content-driven in normal flow; inside a flex row it is\n * the cross axis and stretches by default, like width in a column.\n */\nfunction isContentDrivenSize(\n element: Element,\n style: CSSStyleDeclaration,\n property: \"width\" | \"height\"\n): boolean {\n if (!hasContentSizedKeyword(element, property)) {\n return false;\n }\n\n const parent = element.parentElement;\n const parentStyle = parent ? window.getComputedStyle(parent) : null;\n const parentIsFlex = parentStyle?.display.includes(\"flex\") ?? false;\n\n if (parentIsFlex && parentStyle) {\n const parentIsRow = parentStyle.flexDirection.startsWith(\"row\");\n const isMainAxis = (property === \"width\") === parentIsRow;\n if (isMainAxis) {\n return true;\n }\n // Cross axis: `auto` means stretch unless align-self opts out.\n const alignSelf = style.alignSelf;\n const resolved =\n alignSelf === \"auto\" || alignSelf === \"normal\"\n ? parentStyle.alignItems\n : alignSelf;\n return resolved !== \"stretch\" && resolved !== \"normal\";\n }\n\n if (property === \"height\") {\n return true; // Content-driven in normal flow.\n }\n return (\n style.display === \"inline-flex\" ||\n style.position === \"absolute\" ||\n style.position === \"fixed\" ||\n style.float !== \"none\"\n );\n}\n\n/**\n * Split element children into layout participants (`flow`) and absolutely\n * positioned ones (which become `stackPositioning: \"ABSOLUTE\"`), or `null`\n * when the container holds something we don't model yet (text-node flow\n * items, `order`).\n */\nfunction collectChildren(\n element: Element\n): { flow: Array<Element>; absolute: Array<Element> } | null {\n // Non-empty direct text nodes become anonymous flow items we can't map to\n // a converted node yet.\n for (const node of element.childNodes) {\n if (node.nodeType === Node.TEXT_NODE && (node.textContent ?? \"\").trim()) {\n return null;\n }\n }\n\n const flow: Array<Element> = [];\n const absolute: Array<Element> = [];\n for (const child of element.children) {\n const style = window.getComputedStyle(child);\n if (style.display === \"none\") {\n continue; // Takes no space and the walker skips it too.\n }\n if (style.position === \"absolute\" || style.position === \"fixed\") {\n absolute.push(child);\n continue;\n }\n if (style.order !== \"0\") {\n return null; // Visual order differs from DOM order.\n }\n flow.push(child);\n }\n return { flow, absolute };\n}\n\n/**\n * Reconstruct where Figma's auto-layout would place each child and compare\n * with the browser's actual geometry. Positions are relative to the parent\n * border box, matching how the converter positions children.\n */\nfunction verifyGeometry(\n spec: InferredStack,\n parent: { width: number; height: number },\n childRects: ReadonlyArray<Rect>\n): boolean {\n const isRow = spec.stackMode === \"HORIZONTAL\";\n const padLeading = isRow\n ? spec.stackHorizontalPadding\n : spec.stackVerticalPadding;\n const padTrailing = isRow ? spec.stackPaddingRight : spec.stackPaddingBottom;\n const padCross = isRow\n ? spec.stackVerticalPadding\n : spec.stackHorizontalPadding;\n const padCrossTrailing = isRow\n ? spec.stackPaddingBottom\n : spec.stackPaddingRight;\n\n const primarySize = (rect: Rect) => (isRow ? rect.width : rect.height);\n const crossSize = (rect: Rect) => (isRow ? rect.height : rect.width);\n\n const inner =\n (isRow ? parent.width : parent.height) - padLeading - padTrailing;\n const innerCross =\n (isRow ? parent.height : parent.width) - padCross - padCrossTrailing;\n const totalChildren = childRects.reduce((n, r) => n + primarySize(r), 0);\n const count = childRects.length;\n\n let spacing = spec.stackSpacing;\n let cursor = padLeading;\n switch (spec.stackPrimaryAlignItems) {\n case \"CENTER\":\n cursor += (inner - totalChildren - spacing * (count - 1)) / 2;\n break;\n case \"MAX\":\n cursor += inner - totalChildren - spacing * (count - 1);\n break;\n case \"SPACE_BETWEEN\":\n spacing = count > 1 ? (inner - totalChildren) / (count - 1) : 0;\n break;\n default:\n break;\n }\n\n for (const rect of childRects) {\n const expectedPrimary = cursor;\n let expectedCross = padCross;\n if (spec.stackCounterAlignItems === \"CENTER\") {\n expectedCross += (innerCross - crossSize(rect)) / 2;\n } else if (spec.stackCounterAlignItems === \"MAX\") {\n expectedCross += innerCross - crossSize(rect);\n }\n\n const actualPrimary = isRow ? rect.x : rect.y;\n const actualCross = isRow ? rect.y : rect.x;\n if (\n Math.abs(actualPrimary - expectedPrimary) > GEOMETRY_TOLERANCE ||\n Math.abs(actualCross - expectedCross) > GEOMETRY_TOLERANCE\n ) {\n return false;\n }\n cursor += primarySize(rect) + spacing;\n }\n\n return true;\n}\n\nfunction edge(value: string): number {\n return Number.parseFloat(value || \"0\") || 0;\n}\n\nfunction round2(value: number): number {\n return Math.round(value * 100) / 100;\n}\n","/**\n * Get an appropriate Figma node name based on HTML element tag\n */\nexport function getNodeNameFromElement(element: Element): string {\n const tagName = element.tagName.toLowerCase();\n\n const tagNameMap: Record<string, string> = {\n section: \"Section\",\n nav: \"Navigation\",\n aside: \"Sidebar\",\n header: \"Header\",\n footer: \"Footer\",\n button: \"Button\",\n img: \"Image\",\n table: \"Table\",\n tr: \"Table Row\",\n th: \"Table Head\",\n tbody: \"Table Body\",\n thead: \"Table Header\",\n tfoot: \"Table Footer\",\n td: \"Table Cell\",\n };\n\n return tagNameMap[tagName] ?? \"Container\";\n}\n","import type { FigmaEffect, FigmaPaint } from \"../types\";\nimport { createSolidPaint, cssColorToFigmaColor } from \"./color\";\n\n/**\n * Parse a CSS box-shadow value and convert it to Figma effects\n * Supports multiple shadows separated by commas\n * Format: [inset] <offset-x> <offset-y> [blur-radius] [spread-radius] [color]\n *\n * @param boxShadow - The CSS box-shadow value to parse.\n * @returns An array of Figma effects.\n */\nexport function cssBoxShadowToFigmaEffects(\n boxShadow: string\n): Array<FigmaEffect> {\n if (!boxShadow || boxShadow === \"none\") {\n return [];\n }\n\n const effects: Array<FigmaEffect> = [];\n\n // Properly split by comma, respecting parentheses\n const shadows: Array<string> = [];\n let currentShadow = \"\";\n let parenDepth = 0;\n\n for (const char of boxShadow) {\n if (char === \"(\") {\n parenDepth += 1;\n currentShadow += char;\n } else if (char === \")\") {\n parenDepth -= 1;\n currentShadow += char;\n } else if (char === \",\" && parenDepth === 0) {\n if (currentShadow.trim()) {\n shadows.push(currentShadow.trim());\n }\n currentShadow = \"\";\n } else {\n currentShadow += char;\n }\n }\n if (currentShadow.trim()) {\n shadows.push(currentShadow.trim());\n }\n\n for (const shadow of shadows) {\n const effect = parseSingleBoxShadow(shadow);\n\n if (effect) {\n effects.push(effect);\n }\n }\n\n return effects;\n}\n\n/**\n * Parse a comma-separated CSS shadow list (`<offset-x> <offset-y> <blur>?\n * <color>?`) into Figma DROP_SHADOW effects. This is the shared core behind\n * both `text-shadow` and `filter: drop-shadow()` — neither has an `inset`\n * keyword or a spread radius, so it reuses the box-shadow parser for the\n * grammar and normalizes every result to an offset drop shadow with zero\n * spread.\n *\n * @param value - A CSS shadow list, e.g. a `text-shadow` value or the argument\n * of a `drop-shadow()` filter.\n * @returns An array of Figma DROP_SHADOW effects.\n */\nexport function cssShadowListToDropShadows(value: string): Array<FigmaEffect> {\n const effects: Array<FigmaEffect> = [];\n for (const effect of cssBoxShadowToFigmaEffects(value)) {\n // Box-shadow parsing only yields shadow effects; text-shadow and\n // drop-shadow are never inset, so coerce to DROP_SHADOW and discard any\n // spread they never have.\n if (effect.type === \"DROP_SHADOW\" || effect.type === \"INNER_SHADOW\") {\n effects.push({ ...effect, type: \"DROP_SHADOW\", spread: 0 });\n }\n }\n return effects;\n}\n\n/**\n * Parse a CSS `text-shadow` value into Figma DROP_SHADOW effects. On a Figma\n * TEXT node a DROP_SHADOW follows the glyph outlines, matching how CSS paints\n * `text-shadow` behind the text. See {@link cssShadowListToDropShadows}.\n *\n * @param textShadow - The CSS text-shadow value to parse.\n * @returns An array of Figma DROP_SHADOW effects.\n */\nexport function cssTextShadowToFigmaEffects(\n textShadow: string\n): Array<FigmaEffect> {\n return cssShadowListToDropShadows(textShadow);\n}\n\n/**\n * Parses a single box shadow string and converts it to a Figma effect.\n * @param shadow - The box shadow string to parse.\n * @returns A Figma effect, or null if the string is invalid.\n */\nfunction parseSingleBoxShadow(shadow: string): FigmaEffect | null {\n // Check if it's an inset shadow\n const isInset = shadow.includes(\"inset\");\n const cleanShadow = shadow.replace(/inset/gi, \"\").trim();\n\n // Regular expression to match shadow values\n // Matches: offset-x offset-y [blur] [spread] [color]\n const parts: Array<string> = [];\n let currentPart = \"\";\n let inFunction = 0;\n\n for (const char of cleanShadow) {\n if (char === \"(\") {\n inFunction += 1;\n currentPart += char;\n } else if (char === \")\") {\n inFunction -= 1;\n currentPart += char;\n } else if (char === \" \" && inFunction === 0 && currentPart) {\n parts.push(currentPart);\n currentPart = \"\";\n } else {\n currentPart += char;\n }\n }\n // biome-ignore lint/nursery/noUnnecessaryConditions: false positive\n if (currentPart) {\n parts.push(currentPart);\n }\n\n // Parse the parts\n const values: Array<number> = [];\n let colorString = \"\";\n\n for (const part of parts) {\n // Try to parse as number with unit\n const numMatch = /^(-?\\d*\\.?\\d+)(px|em|rem)?$/.exec(part);\n if (numMatch?.[1] && values.length < 4) {\n values.push(Number.parseFloat(numMatch[1]));\n } else {\n // Assume it's a color\n // biome-ignore lint/nursery/noUnnecessaryConditions: false positive\n colorString = colorString ? `${colorString} ${part}` : part;\n }\n }\n\n if (values.length < 2) {\n return null; // Need at least offset-x and offset-y\n }\n\n const offsetX = values[0] ?? 0;\n const offsetY = values[1] ?? 0;\n const blurRadius = values[2] ?? 0;\n const spreadRadius = values[3] ?? 0;\n const color = cssColorToFigmaColor(colorString);\n\n // Skip shadows with no visual effect (0 offset, 0 blur, 0 spread, and transparent)\n if (\n offsetX === 0 &&\n offsetY === 0 &&\n blurRadius === 0 &&\n spreadRadius === 0 &&\n !color\n ) {\n return null;\n }\n\n return {\n type: isInset ? \"INNER_SHADOW\" : \"DROP_SHADOW\",\n visible: true,\n blendMode: \"NORMAL\",\n color: {\n r: color?.color.r ?? 0,\n g: color?.color.g ?? 0,\n b: color?.color.b ?? 0,\n a: color?.opacity ?? 1,\n },\n offset: {\n x: offsetX,\n y: offsetY,\n },\n radius: Math.max(0, blurRadius),\n spread: spreadRadius,\n showShadowBehindNode: false,\n };\n}\n\n/**\n * A \"pure ring\" box-shadow (`0 0 0 <spread> <color>`) is a crisp solid outline\n * around the box in CSS. Figma does not draw a zero-blur/zero-offset spread\n * DROP_SHADOW at all, so such a shadow renders as nothing. These are better\n * represented as an OUTSIDE stroke (see {@link ringShadowToStroke}).\n *\n * @param effect - A Figma effect produced from a CSS box-shadow.\n * @returns True when the effect is a visible, offset-less, blur-less, positive\n * spread drop shadow.\n */\nexport function isPureRingShadow(effect: FigmaEffect): boolean {\n if (effect.type !== \"DROP_SHADOW\") {\n return false;\n }\n return (\n effect.offset.x === 0 &&\n effect.offset.y === 0 &&\n effect.radius === 0 &&\n (effect.spread ?? 0) > 0 &&\n effect.color.a > 0\n );\n}\n\n/**\n * Convert a pure-ring drop shadow (see {@link isPureRingShadow}) into the Figma\n * stroke fields that reproduce it: an OUTSIDE stroke whose weight equals the\n * CSS spread and whose paint is the shadow color. With `strokeAlign: OUTSIDE`\n * the stroke sits entirely outside the node without changing its size and\n * follows the node's corner radius, matching the CSS ring.\n *\n * @param effect - A pure-ring drop shadow effect.\n * @returns Stroke fields to spread onto the frame node change.\n */\nexport function ringShadowToStroke(effect: FigmaEffect): {\n strokeWeight: number;\n strokePaints: Array<FigmaPaint>;\n strokeAlign: \"OUTSIDE\";\n} {\n const spread = effect.spread ?? 0;\n const color = effect.color ?? { r: 0, g: 0, b: 0, a: 1 };\n return {\n strokeWeight: spread,\n strokePaints: [\n createSolidPaint({ r: color.r, g: color.g, b: color.b, a: 1 }, color.a),\n ],\n strokeAlign: \"OUTSIDE\",\n };\n}\n","import type { FigmaBlurEffect, FigmaEffect } from \"../types/effects\";\nimport { cssShadowListToDropShadows } from \"./shadow\";\n\n/**\n * CSS `blur(<length>)` is a Gaussian whose *standard deviation* is the length;\n * Figma's blur `radius` is twice that sigma. Passing the CSS length straight\n * through renders half as soft as the browser — measured on\n * `fx/fx-07-filter-blur`, whose 10–90% edge falloff was 14px in Chrome and 7px\n * in Figma for the same `blur(6px)`.\n */\nconst FIGMA_BLUR_RADIUS_PER_CSS_SIGMA = 2;\n\n/** Every top-level `blur()` length in a filter value, as Figma blur effects. */\nfunction extractBlurEffects(\n value: string,\n type: FigmaBlurEffect[\"type\"]\n): Array<FigmaBlurEffect> {\n const effects: Array<FigmaBlurEffect> = [];\n for (const match of value.matchAll(/blur\\(([^)]+)\\)/g)) {\n const sigma = Number.parseFloat(match[1] ?? \"\");\n if (Number.isNaN(sigma) || sigma <= 0) {\n continue;\n }\n effects.push({\n type,\n visible: true,\n radius: sigma * FIGMA_BLUR_RADIUS_PER_CSS_SIGMA,\n });\n }\n return effects;\n}\n\n/**\n * Extract the argument of each top-level `drop-shadow(...)` in a CSS filter,\n * scanning paren depth so nested color functions (`rgba(...)`, `hsl(...)`) do\n * not terminate the match early the way a naive regex would.\n *\n * @param filter - The CSS filter value to scan.\n * @returns The raw argument string of each `drop-shadow()`, in source order.\n */\nfunction extractDropShadowArgs(filter: string): Array<string> {\n const token = \"drop-shadow(\";\n const args: Array<string> = [];\n let start = filter.indexOf(token);\n while (start !== -1) {\n let depth = 1;\n let i = start + token.length;\n for (; i < filter.length && depth > 0; i += 1) {\n if (filter[i] === \"(\") {\n depth += 1;\n } else if (filter[i] === \")\") {\n depth -= 1;\n }\n }\n args.push(filter.slice(start + token.length, i - 1).trim());\n start = filter.indexOf(token, i);\n }\n return args;\n}\n\n/**\n * Parse the CSS `filter` property into Figma effects: `blur()` becomes a\n * FOREGROUND_BLUR and each `drop-shadow()` becomes a DROP_SHADOW. Other filter\n * functions (color-matrix `grayscale`/`brightness`/`contrast`, etc.) have no\n * Figma effect equivalent and are ignored.\n *\n * @param filter - The CSS filter value to parse.\n * @returns An array of Figma effects.\n */\nexport function cssFilterToFigmaEffects(filter: string): Array<FigmaEffect> {\n if (!filter || filter === \"none\") {\n return [];\n }\n\n const effects: Array<FigmaEffect> = [\n ...extractBlurEffects(filter, \"FOREGROUND_BLUR\"),\n ];\n\n // Each drop-shadow() maps to a DROP_SHADOW — same grammar as a single\n // text-shadow (offset + blur + color, no inset or spread).\n for (const arg of extractDropShadowArgs(filter)) {\n effects.push(...cssShadowListToDropShadows(arg));\n }\n\n return effects;\n}\n\n/**\n * Parse CSS backdrop-filter property and convert blur effects to Figma effects\n * @param backdropFilter - The CSS backdrop-filter value to parse.\n * @returns An array of Figma effects.\n */\nexport function cssBackdropFilterToFigmaEffects(\n backdropFilter: string\n): Array<FigmaBlurEffect> {\n if (!backdropFilter || backdropFilter === \"none\") {\n return [];\n }\n\n return extractBlurEffects(backdropFilter, \"BACKGROUND_BLUR\");\n}\n","import type { FigmaColor, FigmaPaint } from \"../types\";\nimport { cssColorToFigmaColor } from \"./color\";\n\n// Empirical fudge factor on top of the geometric `(1 + smoothing)` divisor —\n// Figma renders softer than CSS at equal radius. Higher = tighter corners.\nconst SQUIRCLE_RADIUS_TIGHTEN = 1.125;\n\n/**\n * A CSS `double` border drawn as two concentric lines. Figma has no\n * double-border primitive, so the frame's own stroke draws the OUTER line and\n * a synthetic inset child (see the frame converter) draws the INNER line, with\n * the frame's fill (the element background) showing through the gap between\n * them. Only present for uniform `double` borders wide enough to show a gap.\n */\nexport type DoubleBorderSpec = {\n /** Distance from each frame edge to the inner line's outer edge (px). */\n inset: number;\n /** Stroke weight of each line — outer and inner are equal (px). */\n lineWeight: number;\n /** Paints for the inner line: the same color as the frame's outer stroke. */\n strokePaints: Array<FigmaPaint>;\n};\n\nexport type BorderProperties = {\n strokeWeight: number;\n strokePaints: Array<FigmaPaint>;\n cornerRadius?: number;\n cornerSmoothing?: number;\n // Conditional properties - only included when needed\n borderTopWeight?: number;\n borderRightWeight?: number;\n borderBottomWeight?: number;\n borderLeftWeight?: number;\n borderStrokeWeightsIndependent?: boolean;\n rectangleTopLeftCornerRadius?: number;\n rectangleTopRightCornerRadius?: number;\n rectangleBottomLeftCornerRadius?: number;\n rectangleBottomRightCornerRadius?: number;\n rectangleCornerRadiiIndependent?: boolean;\n /** `[dash, gap]` in px for a uniform CSS `dashed` or `dotted` border. */\n dashPattern?: Array<number>;\n /** `\"ROUND\"` for a dotted border, so each dash renders as a round dot. */\n strokeCap?: string;\n /** Present only for a uniform CSS `double` border. Not a Figma node field —\n * the frame converter consumes it and must not spread it onto a node. */\n doubleBorder?: DoubleBorderSpec;\n};\n\n/**\n * A CSS `double` border is two solid lines separated by a gap, each roughly a\n * third of the border width (Chrome renders 8px as 3px line / 2px gap / 3px\n * line). Detect the uniform case and split the width into equal lines; the\n * frame's stroke becomes the outer line and the returned spec drives the inner\n * line. Returns `undefined` for non-`double`, mixed, or too-narrow borders\n * (which Chrome itself collapses to a single solid line, so the existing solid\n * stroke already matches).\n */\nfunction parseUniformDoubleBorder(\n computedStyle: CSSStyleDeclaration,\n width: number,\n strokePaints: Array<FigmaPaint>\n): DoubleBorderSpec | undefined {\n const styles = [\n computedStyle.borderTopStyle,\n computedStyle.borderRightStyle,\n computedStyle.borderBottomStyle,\n computedStyle.borderLeftStyle,\n ];\n if (!styles.every((s) => s === \"double\") || strokePaints.length === 0) {\n return;\n }\n const lineWeight = Math.round(width / 3);\n const gap = width - 2 * lineWeight;\n if (lineWeight < 1 || gap < 1) {\n return;\n }\n return { inset: width - lineWeight, lineWeight, strokePaints };\n}\n\n/**\n * The `[dash, gap]` pattern for a uniform CSS `dotted` border — dots of the\n * border width separated by an equal gap, measured off Chrome's own raster.\n *\n * Figma carries a single dash pattern per node, so this only fires when all\n * four sides agree; mixed styles keep the existing solid stroke rather than\n * imposing one side's pattern on the rest.\n *\n * `dashed` is deliberately left solid. Chrome fits a whole number of dashes to\n * each side, nudging the gaps so a dash lands in both corners; Figma instead\n * runs one pattern continuously around the path, so the dashes drift out of\n * phase and land in the browser's gaps — measured at 3.69% against the 2.00% of\n * simply drawing the border solid.\n *\n * Both patterns here are fallbacks. `border-decomposition.ts` normally takes\n * over for `dashed` and `dotted` borders and paints the fitted dashes as\n * geometry, dropping this stroke entirely; what stays is the case it declines\n * (a box needing more dashes than its node budget, say).\n */\nfunction parseUniformDashPattern(\n computedStyle: CSSStyleDeclaration,\n width: number\n): { dashPattern: Array<number>; strokeCap?: string } | undefined {\n if (width <= 0) {\n return;\n }\n const styles = [\n computedStyle.borderTopStyle,\n computedStyle.borderRightStyle,\n computedStyle.borderBottomStyle,\n computedStyle.borderLeftStyle,\n ];\n const style = styles[0];\n if (!(style && styles.every((s) => s === style))) {\n return;\n }\n if (style === \"dotted\") {\n return { dashPattern: [width, width] };\n }\n return;\n}\n\n// `squircle` ≡ `superellipse(4)` ≈ Figma's iOS smoothing (0.6).\n// `superellipse(n)` interpolates: n=2 → 0, n=4 → 0.6, clamped to 1.\nfunction cornerShapeToSmoothing(value: string): number | undefined {\n const v = value.trim().toLowerCase();\n if (!v || v === \"round\") {\n return 0;\n }\n if (v === \"squircle\") {\n return 0.6;\n }\n const m = v.match(/^superellipse\\(\\s*([\\d.]+)\\s*\\)$/);\n if (m?.[1]) {\n const n = Number.parseFloat(m[1]);\n if (!Number.isFinite(n) || n <= 2) {\n return 0;\n }\n return Math.min(1, ((n - 2) / 2) * 0.6);\n }\n return;\n}\n\n// Figma's `cornerSmoothing` is a single per-node value, so we only emit one\n// when all four CSS corners resolve to the same Figma-representable shape.\nfunction parseCornerSmoothing(\n computedStyle: CSSStyleDeclaration\n): number | undefined {\n const sides = [\n \"corner-top-left-shape\",\n \"corner-top-right-shape\",\n \"corner-bottom-right-shape\",\n \"corner-bottom-left-shape\",\n ];\n const values = sides.map((p) => computedStyle.getPropertyValue(p));\n if (values.every((v) => !v)) {\n return;\n }\n\n const smoothings = values.map((v) => cornerShapeToSmoothing(v));\n const first = smoothings[0];\n if (first === undefined) {\n return;\n }\n if (!smoothings.every((s) => s === first)) {\n return;\n }\n return first > 0 ? first : undefined;\n}\n\n/**\n * Parse CSS border properties and convert them to Figma border format\n *\n * @param computedStyle - The computed style of the element.\n * @param dimensions - Element box size, used to clamp squircle radii so\n * `cornerSmoothing` has straight edge to extend into below Figma's pill cap.\n * @returns The border properties.\n */\nexport function parseBorderFromComputedStyle(\n computedStyle: CSSStyleDeclaration,\n dimensions?: { width: number; height: number }\n): BorderProperties {\n // Check for borders on all sides\n const borderTopWidth = Number.parseFloat(computedStyle.borderTopWidth || \"0\");\n const borderRightWidth = Number.parseFloat(\n computedStyle.borderRightWidth || \"0\"\n );\n const borderBottomWidth = Number.parseFloat(\n computedStyle.borderBottomWidth || \"0\"\n );\n const borderLeftWidth = Number.parseFloat(\n computedStyle.borderLeftWidth || \"0\"\n );\n\n // Check if borders are independent (different widths on different sides)\n const hasIndependentBorders =\n borderTopWidth !== borderRightWidth ||\n borderTopWidth !== borderBottomWidth ||\n borderTopWidth !== borderLeftWidth;\n\n // Use the maximum border width as the base stroke weight for color\n const maxBorderWidth = Math.max(\n borderTopWidth,\n borderRightWidth,\n borderBottomWidth,\n borderLeftWidth\n );\n let strokeWeight = maxBorderWidth;\n\n // Get border color - prioritize the side with the largest border (Figma only supports one color for the stroke)\n const strokePaints: Array<FigmaPaint> = [];\n if (maxBorderWidth > 0) {\n let strokeColor: {\n color: FigmaColor;\n opacity: number;\n } | null = null;\n\n if (borderTopWidth === maxBorderWidth) {\n strokeColor = cssColorToFigmaColor(\n computedStyle.borderTopColor || computedStyle.borderColor\n );\n } else if (borderRightWidth === maxBorderWidth) {\n strokeColor = cssColorToFigmaColor(\n computedStyle.borderRightColor || computedStyle.borderColor\n );\n } else if (borderBottomWidth === maxBorderWidth) {\n strokeColor = cssColorToFigmaColor(\n computedStyle.borderBottomColor || computedStyle.borderColor\n );\n } else if (borderLeftWidth === maxBorderWidth) {\n strokeColor = cssColorToFigmaColor(\n computedStyle.borderLeftColor || computedStyle.borderColor\n );\n }\n\n if (strokeColor) {\n strokePaints.push({\n type: \"SOLID\",\n color: strokeColor.color,\n opacity: strokeColor.opacity,\n visible: true,\n blendMode: \"NORMAL\",\n });\n }\n }\n\n // A uniform CSS `double` border reduces the frame's own stroke to just the\n // outer line; the returned spec drives a synthetic inner-line child node.\n const doubleBorder =\n hasIndependentBorders || maxBorderWidth <= 0\n ? undefined\n : parseUniformDoubleBorder(computedStyle, maxBorderWidth, strokePaints);\n if (doubleBorder) {\n strokeWeight = doubleBorder.lineWeight;\n }\n\n // Parse border radius properties\n const rawBorderRadiusTopLeft = Number.parseFloat(\n computedStyle.borderTopLeftRadius || \"0\"\n );\n const rawBorderRadiusTopRight = Number.parseFloat(\n computedStyle.borderTopRightRadius || \"0\"\n );\n const rawBorderRadiusBottomLeft = Number.parseFloat(\n computedStyle.borderBottomLeftRadius || \"0\"\n );\n const rawBorderRadiusBottomRight = Number.parseFloat(\n computedStyle.borderBottomRightRadius || \"0\"\n );\n\n const cornerSmoothing = parseCornerSmoothing(computedStyle);\n\n // Scale the radius down so Figma's smoothed curve, which extends roughly\n // `r * (1 + s)` into each side, matches the CSS visual extent. Clamp at the\n // pill cap first so smoothing always has straight edge to extend into.\n const compensateRadius = (raw: number): number => {\n if (cornerSmoothing === undefined || cornerSmoothing <= 0) {\n return raw;\n }\n const clamped = dimensions\n ? Math.min(raw, Math.min(dimensions.width, dimensions.height) / 2)\n : raw;\n return clamped / ((1 + cornerSmoothing) * SQUIRCLE_RADIUS_TIGHTEN);\n };\n\n const borderRadiusTopLeft = compensateRadius(rawBorderRadiusTopLeft);\n const borderRadiusTopRight = compensateRadius(rawBorderRadiusTopRight);\n const borderRadiusBottomLeft = compensateRadius(rawBorderRadiusBottomLeft);\n const borderRadiusBottomRight = compensateRadius(rawBorderRadiusBottomRight);\n\n const hasIndependentCorners =\n borderRadiusTopLeft !== borderRadiusTopRight ||\n borderRadiusTopLeft !== borderRadiusBottomLeft ||\n borderRadiusTopLeft !== borderRadiusBottomRight;\n\n const dashes = parseUniformDashPattern(computedStyle, maxBorderWidth);\n\n const borderProps: BorderProperties = {\n strokeWeight,\n strokePaints,\n cornerRadius: hasIndependentCorners ? undefined : borderRadiusTopLeft,\n ...(cornerSmoothing !== undefined && { cornerSmoothing }),\n ...(dashes && {\n dashPattern: dashes.dashPattern,\n ...(dashes.strokeCap && { strokeCap: dashes.strokeCap }),\n }),\n ...(doubleBorder && { doubleBorder }),\n };\n\n // Only include individual border weights if they're different\n if (hasIndependentBorders) {\n borderProps.borderTopWeight = borderTopWidth;\n borderProps.borderRightWeight = borderRightWidth;\n borderProps.borderBottomWeight = borderBottomWidth;\n borderProps.borderLeftWeight = borderLeftWidth;\n borderProps.borderStrokeWeightsIndependent = hasIndependentBorders;\n }\n\n // Only include individual corner radii if they're different\n if (hasIndependentCorners) {\n borderProps.rectangleTopLeftCornerRadius = borderRadiusTopLeft;\n borderProps.rectangleTopRightCornerRadius = borderRadiusTopRight;\n borderProps.rectangleBottomLeftCornerRadius = borderRadiusBottomLeft;\n borderProps.rectangleBottomRightCornerRadius = borderRadiusBottomRight;\n borderProps.rectangleCornerRadiiIndependent = hasIndependentCorners;\n }\n\n return borderProps;\n}\n","import type { FigmaColor } from \"../types/core\";\n\n// Rec. 709 luma coefficients — the weights CSS uses for grayscale()/saturate().\nconst LUMA_R = 0.2126;\nconst LUMA_G = 0.7152;\nconst LUMA_B = 0.0722;\n\nconst clamp01 = (v: number): number => Math.min(1, Math.max(0, v));\n\ntype Rgb = { r: number; g: number; b: number };\n\n/** CSS color-matrix / component-transfer filter functions this module can bake\n * into a solid color. Spatial (`blur`) and shadow (`drop-shadow`) filters are\n * effects, handled elsewhere; `hue-rotate`/`opacity` are intentionally omitted. */\nconst COLOR_MATRIX_FN =\n /(grayscale|sepia|invert|saturate|brightness|contrast)\\(\\s*([\\d.]+%?)\\s*\\)/g;\n\n/** Parse a CSS filter amount: `1`, `0.5`, `100%`, `50%` → a unit-relative number. */\nfunction parseAmount(raw: string): number {\n return raw.endsWith(\"%\")\n ? Number.parseFloat(raw) / 100\n : Number.parseFloat(raw);\n}\n\nfunction grayscale(c: Rgb, amount: number): Rgb {\n const a = Math.min(1, amount);\n const luma = LUMA_R * c.r + LUMA_G * c.g + LUMA_B * c.b;\n return {\n r: c.r + a * (luma - c.r),\n g: c.g + a * (luma - c.g),\n b: c.b + a * (luma - c.b),\n };\n}\n\nfunction brightness(c: Rgb, amount: number): Rgb {\n return { r: c.r * amount, g: c.g * amount, b: c.b * amount };\n}\n\nfunction contrast(c: Rgb, amount: number): Rgb {\n const shift = 0.5 - 0.5 * amount;\n return {\n r: c.r * amount + shift,\n g: c.g * amount + shift,\n b: c.b * amount + shift,\n };\n}\n\nfunction invert(c: Rgb, amount: number): Rgb {\n const a = Math.min(1, amount);\n return {\n r: c.r + a * (1 - 2 * c.r),\n g: c.g + a * (1 - 2 * c.g),\n b: c.b + a * (1 - 2 * c.b),\n };\n}\n\nfunction sepia(c: Rgb, amount: number): Rgb {\n const a = Math.min(1, amount);\n const r = 0.393 * c.r + 0.769 * c.g + 0.189 * c.b;\n const g = 0.349 * c.r + 0.686 * c.g + 0.168 * c.b;\n const b = 0.272 * c.r + 0.534 * c.g + 0.131 * c.b;\n return {\n r: c.r + a * (r - c.r),\n g: c.g + a * (g - c.g),\n b: c.b + a * (b - c.b),\n };\n}\n\nfunction saturate(c: Rgb, a: number): Rgb {\n return {\n r:\n (0.213 + 0.787 * a) * c.r +\n (0.715 - 0.715 * a) * c.g +\n (0.072 - 0.072 * a) * c.b,\n g:\n (0.213 - 0.213 * a) * c.r +\n (0.715 + 0.285 * a) * c.g +\n (0.072 - 0.072 * a) * c.b,\n b:\n (0.213 - 0.213 * a) * c.r +\n (0.715 - 0.715 * a) * c.g +\n (0.072 + 0.928 * a) * c.b,\n };\n}\n\nfunction applyOne(fn: string, amount: number, c: Rgb): Rgb {\n switch (fn) {\n case \"grayscale\":\n return grayscale(c, amount);\n case \"brightness\":\n return brightness(c, amount);\n case \"contrast\":\n return contrast(c, amount);\n case \"invert\":\n return invert(c, amount);\n case \"sepia\":\n return sepia(c, amount);\n case \"saturate\":\n return saturate(c, amount);\n default:\n return c;\n }\n}\n\n/**\n * Detect whether a CSS `filter` value contains any color-matrix function this\n * module can bake — used to gate the frame converter before touching the fill.\n *\n * @param filter - The CSS filter value.\n * @returns True if at least one bakeable color function is present.\n */\nexport function hasColorMatrixFilter(filter: string): boolean {\n if (!filter || filter === \"none\") {\n return false;\n }\n COLOR_MATRIX_FN.lastIndex = 0;\n return COLOR_MATRIX_FN.test(filter);\n}\n\n/**\n * Bake CSS color-matrix filters (`grayscale`/`brightness`/`contrast`/`invert`/\n * `sepia`/`saturate`) into a solid color by applying each function to the RGB\n * channels in source order, clamping to [0,1] after each step to mirror how the\n * browser pipes one filter primitive into the next. Alpha is untouched; `blur`\n * and `drop-shadow` in the same value are ignored (they are Figma effects).\n *\n * Figma has no color-filter effect, so this only reproduces the filter when the\n * element's whole appearance is that one solid fill — the caller must gate on\n * that (leaf, no image/border/text). See the frame converter.\n *\n * @param color - The unfiltered fill color.\n * @param filter - The CSS filter value.\n * @returns The filtered color (same `a`).\n */\nexport function applyCssColorMatrixFilters(\n color: FigmaColor,\n filter: string\n): FigmaColor {\n if (!filter || filter === \"none\") {\n return color;\n }\n let rgb: Rgb = { r: color.r, g: color.g, b: color.b };\n COLOR_MATRIX_FN.lastIndex = 0;\n for (const match of filter.matchAll(COLOR_MATRIX_FN)) {\n const fn = match[1];\n const amount = parseAmount(match[2] ?? \"\");\n if (!fn || Number.isNaN(amount)) {\n continue;\n }\n rgb = applyOne(fn, amount, rgb);\n rgb = { r: clamp01(rgb.r), g: clamp01(rgb.g), b: clamp01(rgb.b) };\n }\n return { r: rgb.r, g: rgb.g, b: rgb.b, a: color.a };\n}\n","import type { FigmaColor, FigmaPaint, FigmaTransform } from \"../types\";\nimport { cssColorToFigmaColor } from \"./color\";\n\ntype GradientStop = {\n color: FigmaColor;\n position: number;\n};\n\n/**\n * How a stop's explicit position is measured.\n *\n * `length` covers linear and radial gradients, where CSS writes offsets in `px`\n * along a gradient line whose length depends on the box; `angle` covers conic\n * gradients, where offsets are angles around the centre.\n */\ntype StopUnits = { kind: \"length\"; lineLengthPx: number } | { kind: \"angle\" };\n\n/** Trailing `<length>`/`<percentage>`/`<angle>` that positions a colour stop. */\nconst STOP_POSITION = /\\s+(?=[-+]?[\\d.]+(?:%|px|deg|rad|grad|turn)?\\s*$)/;\n\n/**\n * Resolves an explicit stop position to a fraction of the gradient line.\n * @param positionString - The trailing position token, e.g. `10px` or `90deg`.\n * @param units - How lengths and angles map onto the line for this gradient.\n * @returns The fraction, or null if the token is not a position we understand.\n */\nfunction resolveStopPosition(\n positionString: string,\n units: StopUnits\n): number | null {\n const value = Number.parseFloat(positionString);\n if (Number.isNaN(value)) {\n return null;\n }\n\n if (positionString.endsWith(\"%\")) {\n return value / 100;\n }\n\n if (units.kind === \"angle\") {\n if (positionString.endsWith(\"turn\")) {\n return value;\n }\n if (positionString.endsWith(\"rad\")) {\n return value / (2 * Math.PI);\n }\n if (positionString.endsWith(\"grad\")) {\n return value / 400;\n }\n // Bare numbers are not valid here, but `deg` is the overwhelming default.\n return value / 360;\n }\n\n if (positionString.endsWith(\"px\")) {\n // A degenerate line has no length to measure against; let the caller fall\n // back to even distribution rather than dividing by zero.\n return units.lineLengthPx > 0 ? value / units.lineLengthPx : null;\n }\n\n return null;\n}\n\n/**\n * Parses a gradient stop string and returns a GradientStop object.\n *\n * The returned position is *not* clamped — repeating gradients need the raw\n * offsets to work out their period before the ramp is tiled across the line.\n *\n * @param stopString - The string to parse.\n * @param index - The index of the stop.\n * @param totalStops - The total number of stops.\n * @param units - How to measure an explicit position on this gradient's line.\n * @returns A GradientStop object, or null if the string is invalid.\n */\nfunction parseGradientStop(\n stopString: string,\n index: number,\n totalStops: number,\n units: StopUnits\n): GradientStop | null {\n const parts = stopString.trim().split(STOP_POSITION);\n\n if (parts.length === 0) {\n return null;\n }\n\n const colorString = parts[0];\n const evenPosition = totalStops > 1 ? index / (totalStops - 1) : 0;\n const explicit =\n parts.length > 1 && parts[1] ? resolveStopPosition(parts[1], units) : null;\n const position = explicit ?? evenPosition;\n\n try {\n const result = cssColorToFigmaColor(colorString ?? \"\");\n const color: FigmaColor = result\n ? {\n r: result.color.r,\n g: result.color.g,\n b: result.color.b,\n a: result.opacity,\n }\n : { r: 0, g: 0, b: 0, a: 0 };\n\n return {\n color,\n position,\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Parses a linear gradient angle string and returns the angle in degrees.\n * @param angleString - The string to parse.\n * @returns The angle in degrees.\n */\nfunction parseLinearGradientAngle(angleString: string): number {\n const trimmedAngleString = angleString.trim();\n\n if (trimmedAngleString.includes(\"to \")) {\n const direction = trimmedAngleString.replace(\"to \", \"\").trim();\n switch (direction) {\n case \"top\":\n return 0;\n case \"right\":\n return 90;\n case \"bottom\":\n return 180;\n case \"left\":\n return 270;\n case \"top right\":\n return 45;\n case \"bottom right\":\n return 135;\n case \"bottom left\":\n return 225;\n case \"top left\":\n return 315;\n default:\n return 180;\n }\n }\n\n if (trimmedAngleString.endsWith(\"deg\")) {\n return Number.parseFloat(trimmedAngleString);\n }\n\n if (trimmedAngleString.endsWith(\"rad\")) {\n return Number.parseFloat(trimmedAngleString) * (180 / Math.PI);\n }\n\n if (trimmedAngleString.endsWith(\"turn\")) {\n return Number.parseFloat(trimmedAngleString) * 360;\n }\n\n return 180;\n}\n\n/** The box a gradient paints into. Needed because CSS sizes a gradient against\n * the element's real dimensions, while Figma's transform is in 0..1 space. */\nexport type GradientBox = {\n width: number;\n height: number;\n};\n\nconst DEFAULT_BOX: GradientBox = { width: 1, height: 1 };\n\n/**\n * Calculates the gradient transform for a given angle.\n *\n * Figma reads the gradient's progress off the first row: `t = m00·x + m01·y +\n * m02`, with `x`/`y` normalised to 0..1 across the node. CSS measures 0deg as\n * \"to top\" and increases clockwise, which in Figma's y-down space is the unit\n * vector `(sin, -cos)`.\n *\n * The box matters: CSS runs the gradient along a line of length\n * `|w·sin| + |h·cos|`, so the end stops land exactly on the corners the browser\n * uses. Normalising by that length reproduces the browser's ramp instead of an\n * approximation that only agrees on the vertical axis.\n *\n * @param angleDegrees - The angle in degrees.\n * @param box - The element's box in CSS pixels.\n * @returns The gradient transform.\n */\nfunction linearLineLength(\n angleDegrees: number,\n box: GradientBox = DEFAULT_BOX\n): number {\n const radians = angleDegrees * (Math.PI / 180);\n const width = box.width > 0 ? box.width : 1;\n const height = box.height > 0 ? box.height : 1;\n\n return (\n Math.abs(width * Math.sin(radians)) + Math.abs(height * Math.cos(radians))\n );\n}\n\nfunction calculateGradientTransform(\n angleDegrees: number,\n box: GradientBox = DEFAULT_BOX\n): FigmaTransform {\n const radians = angleDegrees * (Math.PI / 180);\n const dirX = Math.sin(radians);\n const dirY = -Math.cos(radians);\n\n const width = box.width > 0 ? box.width : 1;\n const height = box.height > 0 ? box.height : 1;\n const lineLength = linearLineLength(angleDegrees, box);\n\n // Degenerate box: fall back to a top-to-bottom ramp rather than dividing by 0.\n if (lineLength === 0) {\n return { m00: 0, m01: 1, m02: 0, m10: -1, m11: 0, m12: 1 };\n }\n\n const m00 = (width * dirX) / lineLength;\n const m01 = (height * dirY) / lineLength;\n\n return {\n m00,\n m01,\n m02: 0.5 - 0.5 * (m00 + m01),\n // Perpendicular axis: unused for the colour ramp, but keeps the matrix\n // invertible so Figma can round-trip the paint.\n m10: -m01,\n m11: m00,\n m12: 0.5 - 0.5 * (m00 - m01),\n };\n}\n\n/**\n * Transform for a CSS `radial-gradient`, which Figma reads as the ellipse\n * inscribed in gradient space: progress is the distance from `(0.5, 0.5)`\n * measured in units of `0.5`.\n *\n * Only the default `ellipse farthest-corner at center` geometry is modelled —\n * that ellipse has radii `(√2/2)·w` and `(√2/2)·h`, i.e. `√2/2` once\n * normalised, independent of the box's aspect ratio.\n */\nfunction calculateRadialTransform(): FigmaTransform {\n const radius = Math.SQRT2 / 2;\n const scale = 1 / (2 * radius);\n const offset = 0.5 - 0.5 * scale;\n\n return {\n m00: scale,\n m01: 0,\n m02: offset,\n m10: 0,\n m11: scale,\n m12: offset,\n };\n}\n\n/** Ceiling on the stop list a repeat may expand to, so a 1px period on a wide\n * box degrades to a coarse ramp instead of a pathological paint. */\nconst MAX_TILED_STOPS = 256;\n\n/**\n * Tiles one period of a repeating gradient's ramp across the whole line.\n *\n * Figma has no repeating gradient, so the repetition is baked into the stop\n * list: CSS repeats the ramp every `period`, and replaying it at each multiple\n * reproduces that exactly — hard stops included, because a stop shared by two\n * tiles lands on the same position twice.\n *\n * @param stops - One period of the ramp, sorted, with raw (unclamped) offsets.\n * @returns The tiled stops covering 0..1, or null if the ramp cannot repeat.\n */\nfunction tileRepeatingStops(\n stops: Array<GradientStop>\n): Array<GradientStop> | null {\n const start = stops[0]?.position ?? 0;\n const end = stops.at(-1)?.position ?? 0;\n const period = end - start;\n\n // A zero-length or line-spanning period repeats exactly once: nothing to do.\n if (!(period > 0) || period >= 1) {\n return null;\n }\n\n const maxTiles = Math.floor(MAX_TILED_STOPS / stops.length);\n // Tiles are laid both ways from the first stop so a ramp that starts part-way\n // down the line still paints the run before it, as CSS does. The epsilon\n // keeps an exact division (a 20px period on a 120px line) from spilling a\n // redundant tile off the end, since the line length carries trig rounding.\n const epsilon = 1e-9;\n const before = Math.ceil(Math.max(0, start) / period - epsilon);\n const after = Math.ceil((1 - start) / period - epsilon);\n if (before + after > maxTiles) {\n return null;\n }\n\n const tiled: Array<GradientStop> = [];\n for (let tile = -before; tile < after; tile += 1) {\n const offset = tile * period;\n for (const stop of stops) {\n const position = stop.position + offset;\n // Keep one stop of overhang each side so the visible band's end colours\n // are interpolated from a real neighbour rather than clamped flat.\n if (position < -period || position > 1 + period) {\n continue;\n }\n tiled.push({ color: stop.color, position });\n }\n }\n\n return tiled.length >= 2 ? tiled : null;\n}\n\n/**\n * Transform for a CSS `conic-gradient`, whose sweep Figma reads out of gradient\n * space as the angle around `(0.5, 0.5)`.\n *\n * Figma's angular ramp starts at three o'clock; CSS starts at twelve. Rotating\n * gradient space a quarter turn lines the two origins up, so the stop offsets —\n * already fractions of a turn — carry over untouched.\n *\n * Only the default `from 0deg at center` geometry is modelled.\n */\nfunction calculateAngularTransform(): FigmaTransform {\n return { m00: 0, m01: -1, m02: 1, m10: 1, m11: 0, m12: 0 };\n}\n\n/** Gap forced between stops that CSS places at the same offset. Far above\n * float32's resolution, far below one pixel on any realistic gradient line. */\nconst COINCIDENT_STOP_GAP = 1e-5;\n\n/**\n * Pushes coincident stops a hair apart so the list strictly increases.\n *\n * CSS writes a hard stop as two stops at the same offset, and the pair only\n * reads as a hard edge if the colours stay in source order. Figma reorders\n * equal offsets — a 24-stop ramp comes back with the pair at the array's\n * midpoint swapped, turning two flat bands into two ramps — so the order has to\n * be carried by the offsets themselves rather than by position in the array.\n *\n * @param stops - The stops to separate, in source order and already clamped.\n */\nfunction separateCoincidentStops(stops: Array<GradientStop>): void {\n for (let i = 1; i < stops.length; i += 1) {\n const previous = stops[i - 1];\n const current = stops[i];\n if (previous && current && current.position <= previous.position) {\n current.position = previous.position + COINCIDENT_STOP_GAP;\n }\n }\n\n // The forward pass can push the tail past the end of the ramp; walk back to\n // pull it inside without collapsing the gaps it just opened.\n for (let i = stops.length - 1; i > 0; i -= 1) {\n const current = stops[i];\n const previous = stops[i - 1];\n if (!(current && previous)) {\n continue;\n }\n current.position = Math.min(1, current.position);\n if (previous.position >= current.position) {\n previous.position = Math.max(0, current.position - COINCIDENT_STOP_GAP);\n }\n }\n}\n\n/**\n * Parses the colour-stop list shared by every gradient flavour.\n * @param colorStops - The raw stop strings, in source order.\n * @param units - How to measure explicit stop positions on this gradient.\n * @param repeating - Whether the ramp should be tiled across the line.\n * @returns The stops sorted by position, or null if fewer than two parsed.\n */\nfunction buildStops(\n colorStops: Array<string>,\n units: StopUnits,\n repeating = false\n): Array<GradientStop> | null {\n const parsed: Array<GradientStop> = [];\n for (let i = 0; i < colorStops.length; i += 1) {\n const stopString = colorStops[i];\n if (!stopString) {\n continue;\n }\n\n const stop = parseGradientStop(stopString, i, colorStops.length, units);\n if (stop) {\n parsed.push(stop);\n }\n }\n\n if (parsed.length < 2) {\n return null;\n }\n\n parsed.sort((a, b) => a.position - b.position);\n\n const stops = (repeating ? tileRepeatingStops(parsed) : null) ?? parsed;\n\n // Figma reads stop offsets in 0..1; anything outside was only meaningful\n // while the repeat period was being worked out.\n for (const stop of stops) {\n stop.position = Math.max(0, Math.min(1, stop.position));\n }\n\n separateCoincidentStops(stops);\n\n // Handle transparent colors by making them the same as a visible color but with 0 opacity\n const visibleStops = stops.filter((stop) => stop.color.a > 0);\n if (visibleStops.length > 0) {\n const referenceColor = visibleStops[0]?.color ?? { r: 0, g: 0, b: 0, a: 0 };\n for (const stop of stops) {\n if (\n stop.color.a === 0 &&\n stop.color.r === 0 &&\n stop.color.g === 0 &&\n stop.color.b === 0\n ) {\n // This is a transparent color, use the reference color with 0 opacity\n stop.color = {\n r: referenceColor.r,\n g: referenceColor.g,\n b: referenceColor.b,\n a: 0,\n };\n }\n }\n }\n\n return stops;\n}\n\n/** Splits gradient arguments on top-level commas, keeping `rgb(a, b, c)` whole. */\nfunction splitGradientArgs(content: string): Array<string> {\n return content.split(/,(?![^(]*\\))/);\n}\n\n/**\n * Parses a linear gradient string and returns a FigmaPaint object.\n * @param cssGradient - The string to parse.\n * @param box - The element's box, used to size the gradient line.\n * @returns A FigmaPaint object, or null if the string is invalid.\n */\nfunction parseLinearGradient(\n cssGradient: string,\n box: GradientBox,\n repeating = false\n): FigmaPaint | null {\n const match = /linear-gradient\\s*\\((.*)\\)/.exec(cssGradient);\n if (!match?.[1]) {\n return null;\n }\n\n const parts = splitGradientArgs(match[1].trim());\n\n let angle = 180;\n let colorStops: Array<string> = [];\n\n if (parts.length > 0 && parts[0]) {\n const firstPart = parts[0].trim();\n\n const hasAngle =\n firstPart.includes(\"deg\") ||\n firstPart.includes(\"rad\") ||\n firstPart.includes(\"turn\") ||\n firstPart.includes(\"to \");\n\n if (hasAngle) {\n angle = parseLinearGradientAngle(firstPart);\n colorStops = parts.slice(1);\n } else {\n colorStops = parts;\n }\n }\n\n const stops = buildStops(\n colorStops,\n { kind: \"length\", lineLengthPx: linearLineLength(angle, box) },\n repeating\n );\n if (!stops) {\n return null;\n }\n\n return {\n type: \"GRADIENT_LINEAR\",\n stops,\n opacity: 1,\n visible: true,\n blendMode: \"NORMAL\",\n transform: calculateGradientTransform(angle, box),\n };\n}\n\n// A leading `circle`/`ellipse`/`closest-side`/`at 20% 30%` argument describes the\n// gradient's geometry rather than a colour stop.\nconst RADIAL_GEOMETRY = /^(circle|ellipse|closest-|farthest-)|(^|\\s)at\\s/i;\n\n/**\n * Parses a radial gradient string and returns a FigmaPaint object.\n *\n * Geometry arguments are recognised so they are not mistaken for a colour, but\n * only the default centred `farthest-corner` ellipse is reproduced; explicit\n * sizes and positions still paint from the centre.\n *\n * @param cssGradient - The string to parse.\n * @returns A FigmaPaint object, or null if the string is invalid.\n */\nfunction parseRadialGradient(\n cssGradient: string,\n box: GradientBox,\n repeating = false\n): FigmaPaint | null {\n const match = /radial-gradient\\s*\\((.*)\\)/.exec(cssGradient);\n if (!match?.[1]) {\n return null;\n }\n\n const parts = splitGradientArgs(match[1].trim());\n const first = parts[0]?.trim() ?? \"\";\n const colorStops = RADIAL_GEOMETRY.test(first) ? parts.slice(1) : parts;\n\n // CSS measures a radial stop along the horizontal radius, which for the\n // default farthest-corner ellipse is `(√2/2)·w` — the same edge Figma's\n // transform maps to position 1.\n const stops = buildStops(\n colorStops,\n {\n kind: \"length\",\n lineLengthPx: (Math.SQRT2 / 2) * (box.width > 0 ? box.width : 1),\n },\n repeating\n );\n if (!stops) {\n return null;\n }\n\n return {\n type: \"GRADIENT_RADIAL\",\n stops,\n opacity: 1,\n visible: true,\n blendMode: \"NORMAL\",\n transform: calculateRadialTransform(),\n };\n}\n\n// A leading `from 45deg` / `at 30% 70%` argument describes the sweep's start\n// angle and centre rather than a colour stop.\nconst CONIC_GEOMETRY = /^from\\s|(^|\\s)at\\s/i;\n\n/**\n * Parses a conic gradient string and returns a FigmaPaint object.\n *\n * Figma's angular paint sweeps clockwise from the same twelve-o'clock origin\n * CSS uses, so the stop offsets carry over as fractions of a full turn and the\n * transform only has to centre the sweep. `from`/`at` are recognised so they\n * are not read as colours, but only the default centred sweep is reproduced.\n *\n * @param cssGradient - The string to parse.\n * @param repeating - Whether the ramp should be tiled around the circle.\n * @returns A FigmaPaint object, or null if the string is invalid.\n */\nfunction parseConicGradient(\n cssGradient: string,\n repeating = false\n): FigmaPaint | null {\n const match = /conic-gradient\\s*\\((.*)\\)/.exec(cssGradient);\n if (!match?.[1]) {\n return null;\n }\n\n const parts = splitGradientArgs(match[1].trim());\n const first = parts[0]?.trim() ?? \"\";\n const colorStops = CONIC_GEOMETRY.test(first) ? parts.slice(1) : parts;\n\n const stops = buildStops(colorStops, { kind: \"angle\" }, repeating);\n if (!stops) {\n return null;\n }\n\n return {\n type: \"GRADIENT_ANGULAR\",\n stops,\n opacity: 1,\n visible: true,\n blendMode: \"NORMAL\",\n transform: calculateAngularTransform(),\n };\n}\n\n/**\n * Converts a CSS background string to an array of FigmaPaint objects.\n * @param cssBackground - The string to convert.\n * @param box - The element's box in CSS pixels; sizes the linear gradient line.\n * @returns An array of FigmaPaint objects.\n */\nexport function cssBackgroundToFigmaPaints(\n cssBackground: string,\n box: GradientBox = DEFAULT_BOX\n): Array<FigmaPaint> {\n if (!cssBackground || cssBackground === \"none\") {\n return [];\n }\n\n // Figma has no repeating gradient; the repeat is baked into the stop list, so\n // the only thing the flavour changes here is how the stops are built.\n const repeating = cssBackground.includes(\"repeating-\");\n\n if (cssBackground.includes(\"linear-gradient\")) {\n const gradientPaint = parseLinearGradient(cssBackground, box, repeating);\n if (gradientPaint) {\n return [gradientPaint];\n }\n }\n\n if (cssBackground.includes(\"radial-gradient\")) {\n const gradientPaint = parseRadialGradient(cssBackground, box, repeating);\n if (gradientPaint) {\n return [gradientPaint];\n }\n }\n\n if (cssBackground.includes(\"conic-gradient\")) {\n const gradientPaint = parseConicGradient(cssBackground, repeating);\n if (gradientPaint) {\n return [gradientPaint];\n }\n }\n\n return [];\n}\n","export function parseOpacity(value: string | undefined | null): number {\n if (!value) {\n return 1;\n }\n const parsed = Number.parseFloat(value);\n if (!Number.isFinite(parsed)) {\n return 1;\n }\n return Math.min(1, Math.max(0, parsed));\n}\n","import type { FigmaPaint } from \"../types\";\nimport { createSolidPaint, cssColorToFigmaColor } from \"./color\";\n\n/**\n * CSS `outline` — a ring drawn *outside* the border box, `outline-offset` away\n * from it, taking up no layout space.\n *\n * A Figma node carries one stroke, which the element's border (or a promoted\n * ring shadow) already owns, and that stroke hugs the box rather than standing\n * off from it. So the outline becomes its own node: an oversized child frame\n * with an INSIDE stroke, whose edges sit exactly where CSS paints the ring. The\n * frame converter builds the node; this module only reads the CSS.\n */\nexport type OutlineSpec = {\n /** Ring thickness (px) — `outline-width`. */\n width: number;\n /** Gap between the border box and the ring's inner edge (px). May be negative. */\n offset: number;\n strokePaints: Array<FigmaPaint>;\n /** `[dash, gap]` for a `dotted` outline, matching the border convention. */\n dashPattern?: Array<number>;\n /** `\"ROUND\"` for a `dotted` outline, so each dash renders as a round dot. */\n strokeCap?: string;\n};\n\n/**\n * Styles drawn as a plain ring. `auto` is Chrome's focus ring, which rasterizes\n * as a solid rounded outline, so it joins `solid` here. `dashed` is drawn solid\n * for the same reason borders are (see `parseUniformDashPattern`): Figma's\n * continuously-phased dashes drift out of step with Chrome's per-side fitting.\n * The remaining styles (`double`, `groove`, `ridge`, `inset`, `outset`) need\n * more than one ring or per-side shading and stay unconverted.\n */\nconst SOLID_OUTLINE_STYLES: ReadonlyArray<string> = [\"auto\", \"solid\", \"dashed\"];\n\n/**\n * The outline ring for an element, or `undefined` when it has none (or one we\n * cannot draw as a single ring).\n */\nexport function parseOutlineFromComputedStyle(\n computedStyle: CSSStyleDeclaration\n): OutlineSpec | undefined {\n const style = (computedStyle.outlineStyle || \"none\").trim();\n const isDotted = style === \"dotted\";\n if (!(isDotted || SOLID_OUTLINE_STYLES.includes(style))) {\n return;\n }\n\n const width = Number.parseFloat(computedStyle.outlineWidth || \"0\");\n if (!(Number.isFinite(width) && width > 0)) {\n return;\n }\n\n const color = cssColorToFigmaColor(computedStyle.outlineColor);\n if (!color || color.opacity === 0) {\n return;\n }\n\n const offset = Number.parseFloat(computedStyle.outlineOffset || \"0\") || 0;\n\n return {\n width,\n offset,\n strokePaints: [createSolidPaint(color.color, color.opacity)],\n ...(isDotted && {\n dashPattern: [width, width],\n strokeCap: \"ROUND\",\n }),\n };\n}\n","import Color from \"colorjs.io\";\n\n/**\n * CSS 3D border styles (`groove`, `ridge`, `inset`, `outset`).\n *\n * Chrome paints these by shading each side of the border relative to a light\n * source in the top-left: the sides facing the light keep the declared color\n * and the sides facing away are darkened. `groove`/`ridge` split every side in\n * half and shade the two halves in opposite directions, which is what produces\n * the carved/raised bevel.\n *\n * None of that is expressible as a Figma stroke, so `border-decomposition.ts`\n * paints the shaded bands as filled trapezoids. This module is the color half:\n * it reproduces Blink's `BoxBorderPainter` shading exactly, so the trapezoids\n * carry the same colors Chrome rasterizes.\n */\n\nexport type Border3dStyle = \"groove\" | \"ridge\" | \"inset\" | \"outset\";\n\ntype SideKey = \"top\" | \"right\" | \"bottom\" | \"left\";\n\ntype Rgb255 = { r: number; g: number; b: number };\n\nconst BORDER_3D_STYLES: ReadonlyArray<string> = [\n \"groove\",\n \"ridge\",\n \"inset\",\n \"outset\",\n];\n\n/**\n * Blink converts the shaded float channels back to bytes through\n * `nextafterf(256, 0)` and truncates, not `* 255` and rounds — the difference\n * is a whole byte on dark colors (`#202060` darkens to `#03030b`, not\n * `#04040c`), so the exact constant matters. `nextafterf` steps by one float32\n * ULP, which just below 256 (= 2^8, 24-bit mantissa) is 2^-16.\n */\nconst CHANNEL_SCALE = 256 - 2 ** -16;\n\n/** Blink's `Color::Dark()`: pull every channel down until the brightest one\n * loses 0.33, so the shading is proportional and hue-preserving. */\nconst SHADE_DELTA = 0.33;\n\n/** Blink's `Color::Light()` special case for pure black, which has no channel\n * to scale up — it lightens to a fixed mid grey instead. */\nconst LIGHTENED_BLACK: Rgb255 = { r: 0x54, g: 0x54, b: 0x54 };\n\n/**\n * Below this WCAG contrast ratio between a color and its darkened self, the\n * bevel would be invisible, so Chrome lightens the lit sides instead of relying\n * on the darkened ones to carry the contrast. Blink's\n * `kMinimumBorderEdgeContrastRatio`.\n */\nconst MINIMUM_EDGE_CONTRAST_RATIO = 1.75;\n\nexport function isBorder3dStyle(style: string): style is Border3dStyle {\n return BORDER_3D_STYLES.includes(style);\n}\n\nfunction toByte(channel: number, multiplier: number): number {\n return Math.trunc(channel * multiplier * CHANNEL_SCALE);\n}\n\nfunction darken(rgb: Rgb255): Rgb255 {\n const r = rgb.r / 255;\n const g = rgb.g / 255;\n const b = rgb.b / 255;\n const v = Math.max(r, g, b);\n const multiplier = v <= 0 ? 0 : Math.max(0, (v - SHADE_DELTA) / v);\n return {\n r: toByte(r, multiplier),\n g: toByte(g, multiplier),\n b: toByte(b, multiplier),\n };\n}\n\nfunction lighten(rgb: Rgb255): Rgb255 {\n const r = rgb.r / 255;\n const g = rgb.g / 255;\n const b = rgb.b / 255;\n const v = Math.max(r, g, b);\n if (v <= 0) {\n return LIGHTENED_BLACK;\n }\n const multiplier = Math.min(1, v + SHADE_DELTA) / v;\n return {\n r: toByte(r, multiplier),\n g: toByte(g, multiplier),\n b: toByte(b, multiplier),\n };\n}\n\nfunction relativeLuminance(rgb: Rgb255): number {\n const channel = (byte: number) => {\n const s = byte / 255;\n return s <= 0.040_45 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;\n };\n return (\n 0.2126 * channel(rgb.r) + 0.7152 * channel(rgb.g) + 0.0722 * channel(rgb.b)\n );\n}\n\nfunction contrastRatio(a: Rgb255, b: Rgb255): number {\n const la = relativeLuminance(a);\n const lb = relativeLuminance(b);\n const lighter = Math.max(la, lb);\n const darker = Math.min(la, lb);\n return (lighter + 0.05) / (darker + 0.05);\n}\n\n/**\n * `groove` and `ridge` are each two bevels stacked: one half of the border is\n * painted as `inset` and the other as `outset`, which is what makes a groove\n * read as carved-in and a ridge as raised-out. `inset`/`outset` shade both\n * halves the same way.\n */\nfunction halfStyle(\n style: Border3dStyle,\n half: \"outer\" | \"inner\"\n): \"inset\" | \"outset\" {\n if (style === \"groove\") {\n return half === \"outer\" ? \"inset\" : \"outset\";\n }\n if (style === \"ridge\") {\n return half === \"outer\" ? \"outset\" : \"inset\";\n }\n return style;\n}\n\n/**\n * The color Chrome rasterizes for one half of one side of a 3D border.\n *\n * Returns a CSS `rgb()`/`rgba()` string so it can flow through the same\n * `cssColorToFigmaColor` path as any other border color. Falls back to the\n * declared color when it cannot be parsed.\n */\nexport function border3dSideColor(params: {\n style: Border3dStyle;\n side: SideKey;\n half: \"outer\" | \"inner\";\n color: string;\n}): string {\n const { style, side, half, color } = params;\n\n let parsed: Color;\n try {\n parsed = new Color(color);\n } catch {\n return color;\n }\n const alpha = Number(parsed.alpha);\n const base: Rgb255 = {\n r: Math.round((parsed.srgb[0] ?? 0) * 255),\n g: Math.round((parsed.srgb[1] ?? 0) * 255),\n b: Math.round((parsed.srgb[2] ?? 0) * 255),\n };\n\n // The light source sits top-left: `outset` lights the top and left sides,\n // `inset` lights the bottom and right ones.\n const isTopLeft = side === \"top\" || side === \"left\";\n const shaded = darken(base);\n const lit =\n contrastRatio(base, shaded) >= MINIMUM_EDGE_CONTRAST_RATIO\n ? base\n : lighten(base);\n const rgb = isTopLeft === (halfStyle(style, half) === \"inset\") ? shaded : lit;\n\n return alpha >= 1\n ? `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`\n : `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})`;\n}\n","import type { PathCommand } from \"./tokenizer\";\n\n/**\n * Normalizes relative SVG path commands to absolute coordinates.\n *\n * SVG paths can use relative commands (lowercase letters) where coordinates\n * are relative to the current position, or absolute commands (uppercase letters)\n * where coordinates are relative to the origin. This function converts all\n * relative commands to their absolute equivalents for easier processing.\n *\n * @param commands - Array of raw path commands from the tokenizer\n * @returns Array of normalized commands with absolute coordinates\n *\n * @example\n * ```typescript\n * const commands = [\n * { type: \"M\", args: [10, 20] },\n * { type: \"l\", args: [5, 10] }, // relative line\n * { type: \"h\", args: [15] } // relative horizontal line\n * ];\n * const normalized = normalizeCommands(commands);\n * // Returns: [\n * // { type: \"M\", args: [10, 20] },\n * // { type: \"L\", args: [15, 30] }, // converted to absolute\n * // { type: \"L\", args: [30, 30] } // converted to absolute line\n * // ]\n * ```\n *\n * @remarks\n * The normalizer:\n * - Tracks current pen position throughout the path\n * - Converts relative commands to absolute equivalents\n * - Handles special cases like H/V commands becoming L commands\n * - Maintains path start position for Z command handling\n * - Processes multiple coordinate pairs in move commands (treated as line commands)\n */\nexport function normalizeCommands(\n commands: Array<PathCommand>\n): Array<PathCommand> {\n const normalized: Array<PathCommand> = [];\n let currentX = 0;\n let currentY = 0;\n let pathStartX = 0;\n let pathStartY = 0;\n\n for (const cmd of commands) {\n const { type, args } = cmd;\n const upperType = type.toUpperCase();\n const isRelative = type !== upperType;\n\n switch (upperType) {\n case \"M\": {\n // First pair is the move command\n const arg0 = args[0] ?? 0;\n const arg1 = args[1] ?? 0;\n\n const x = isRelative ? currentX + arg0 : arg0;\n const y = isRelative ? currentY + arg1 : arg1;\n\n normalized.push({ type: \"M\", args: [x, y] });\n currentX = x;\n currentY = y;\n pathStartX = x;\n pathStartY = y;\n\n // Additional coordinate pairs after moveto are treated as lineto\n for (let i = 2; i < args.length; i += 2) {\n if (i + 1 >= args.length) {\n break;\n }\n\n const currentArg = args[i] ?? 0;\n const nextArg = args[i + 1] ?? 0;\n\n const lineX = isRelative ? currentX + currentArg : currentArg;\n const lineY = isRelative ? currentY + nextArg : nextArg;\n\n normalized.push({ type: \"L\", args: [lineX, lineY] });\n currentX = lineX;\n currentY = lineY;\n }\n break;\n }\n\n case \"L\": {\n for (let i = 0; i < args.length; i += 2) {\n if (i + 1 >= args.length) {\n break;\n }\n\n const currentArg = args[i] ?? 0;\n const nextArg = args[i + 1] ?? 0;\n\n const x = isRelative ? currentX + currentArg : currentArg;\n const y = isRelative ? currentY + nextArg : nextArg;\n\n normalized.push({ type: \"L\", args: [x, y] });\n currentX = x;\n currentY = y;\n }\n break;\n }\n\n case \"H\": {\n for (const arg of args) {\n const x = isRelative ? currentX + arg : arg;\n normalized.push({ type: \"L\", args: [x, currentY] });\n currentX = x;\n }\n break;\n }\n\n case \"V\": {\n for (const arg of args) {\n const y = isRelative ? currentY + arg : arg;\n normalized.push({ type: \"L\", args: [currentX, y] });\n currentY = y;\n }\n break;\n }\n\n case \"C\": {\n for (let i = 0; i < args.length; i += 6) {\n if (i + 5 >= args.length) {\n break;\n }\n\n const cp1x = isRelative ? currentX + (args[i] ?? 0) : (args[i] ?? 0);\n const cp1y = isRelative\n ? currentY + (args[i + 1] ?? 0)\n : (args[i + 1] ?? 0);\n const cp2x = isRelative\n ? currentX + (args[i + 2] ?? 0)\n : (args[i + 2] ?? 0);\n const cp2y = isRelative\n ? currentY + (args[i + 3] ?? 0)\n : (args[i + 3] ?? 0);\n const x = isRelative\n ? currentX + (args[i + 4] ?? 0)\n : (args[i + 4] ?? 0);\n const y = isRelative\n ? currentY + (args[i + 5] ?? 0)\n : (args[i + 5] ?? 0);\n\n normalized.push({ type: \"C\", args: [cp1x, cp1y, cp2x, cp2y, x, y] });\n currentX = x;\n currentY = y;\n }\n break;\n }\n\n case \"Q\": {\n for (let i = 0; i < args.length; i += 4) {\n if (i + 3 >= args.length) {\n break;\n }\n const cpx = isRelative ? currentX + (args[i] ?? 0) : (args[i] ?? 0);\n const cpy = isRelative\n ? currentY + (args[i + 1] ?? 0)\n : (args[i + 1] ?? 0);\n const x = isRelative\n ? currentX + (args[i + 2] ?? 0)\n : (args[i + 2] ?? 0);\n const y = isRelative\n ? currentY + (args[i + 3] ?? 0)\n : (args[i + 3] ?? 0);\n\n normalized.push({ type: \"Q\", args: [cpx, cpy, x, y] });\n currentX = x;\n currentY = y;\n }\n break;\n }\n\n case \"A\": {\n for (let i = 0; i < args.length; i += 7) {\n if (i + 6 >= args.length) {\n break;\n }\n\n const rx = Math.abs(args[i] ?? 0);\n const ry = Math.abs(args[i + 1] ?? 0);\n const xAxisRotation = args[i + 2] ?? 0;\n const largeArcFlag = args[i + 3] ?? 0;\n const sweepFlag = args[i + 4] ?? 0;\n const x = isRelative\n ? currentX + (args[i + 5] ?? 0)\n : (args[i + 5] ?? 0);\n const y = isRelative\n ? currentY + (args[i + 6] ?? 0)\n : (args[i + 6] ?? 0);\n\n normalized.push({\n type: \"A\",\n args: [rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y],\n });\n currentX = x;\n currentY = y;\n }\n break;\n }\n\n case \"S\": {\n for (let i = 0; i < args.length; i += 4) {\n if (i + 3 >= args.length) {\n break;\n }\n const cp2x = isRelative ? currentX + (args[i] ?? 0) : (args[i] ?? 0);\n const cp2y = isRelative\n ? currentY + (args[i + 1] ?? 0)\n : (args[i + 1] ?? 0);\n const x = isRelative\n ? currentX + (args[i + 2] ?? 0)\n : (args[i + 2] ?? 0);\n const y = isRelative\n ? currentY + (args[i + 3] ?? 0)\n : (args[i + 3] ?? 0);\n\n normalized.push({ type: \"S\", args: [cp2x, cp2y, x, y] });\n currentX = x;\n currentY = y;\n }\n break;\n }\n\n case \"T\": {\n for (let i = 0; i < args.length; i += 2) {\n if (i + 1 >= args.length) {\n break;\n }\n const x = isRelative ? currentX + (args[i] ?? 0) : (args[i] ?? 0);\n const y = isRelative\n ? currentY + (args[i + 1] ?? 0)\n : (args[i + 1] ?? 0);\n\n normalized.push({ type: \"T\", args: [x, y] });\n currentX = x;\n currentY = y;\n }\n break;\n }\n\n case \"Z\": {\n normalized.push({ type: \"Z\", args: [] });\n currentX = pathStartX;\n currentY = pathStartY;\n break;\n }\n default: {\n break;\n }\n }\n }\n\n return normalized;\n}\n","/**\n * Represents a single SVG path command with its type and numeric arguments.\n */\nexport type PathCommand = {\n /** The command type (M, L, C, A, Z, etc.) - preserves original case for relative/absolute distinction */\n type: string;\n /** Numeric arguments for the command (coordinates, radii, flags, etc.) */\n args: Array<number>;\n};\n\n/**\n * Parses an SVG path data string into an array of structured command objects.\n *\n * This function tokenizes the path string by identifying command letters and extracting\n * their associated numeric arguments. It handles various number formats including\n * scientific notation and deals with edge cases like consecutive minus signs.\n *\n * @param d - The SVG path data string (value of the 'd' attribute)\n * @returns Array of PathCommand objects representing the parsed path\n *\n * @example\n * ```typescript\n * const commands = parseSVGPath(\"M10,20 L30,40 C50,60 70,80 90,100 Z\");\n * // Returns: [\n * // { type: \"M\", args: [10, 20] },\n * // { type: \"L\", args: [30, 40] },\n * // { type: \"C\", args: [50, 60, 70, 80, 90, 100] },\n * // { type: \"Z\", args: [] }\n * // ]\n *\n * const complexPath = parseSVGPath(\"M1.5e2-1.2e1\");\n * // Handles scientific notation and consecutive minus signs\n * // Returns: [{ type: \"M\", args: [150, -12] }]\n * ```\n *\n * @remarks\n * The parser:\n * - Preserves original command case (M vs m) for relative/absolute distinction\n * - Handles comma and whitespace separators\n * - Supports scientific notation (e.g., 1.5e2)\n * - Correctly parses consecutive minus signs (e.g., \"10-5\" becomes [10, -5])\n * - Includes Z/z commands even when they have no arguments\n * - Filters out empty or invalid commands\n */\nexport function parseSVGPath(d: string): Array<PathCommand> {\n const commands: Array<PathCommand> = [];\n // Regex to match command letters followed by their arguments\n const regex = /([ACHLMQSTVZachlmqstvz])([^ACHLMQSTVZachlmqstvz]*)/g;\n let match = regex.exec(d);\n\n while (match !== null) {\n const type = match[1];\n if (!type) {\n throw new Error(\"Invalid path data\");\n }\n\n let argsString = (match[2] ?? \"\").trim();\n // Normalize separators: replace commas with spaces\n argsString = argsString.replace(/,/g, \" \");\n\n // Handle consecutive minus signs by adding spaces before them\n argsString = argsString.replace(/(\\d)-/g, \"$1 -\");\n argsString = argsString.replace(/(\\.)-/g, \"$1 -\");\n\n // Extract numbers including scientific notation\n const numberRegex = /-?\\d*\\.?\\d+(?:[Ee][+-]?\\d+)?/g;\n const args: Array<number> = [];\n let numMatch = numberRegex.exec(argsString);\n while (numMatch !== null) {\n args.push(Number.parseFloat(numMatch[0]));\n numMatch = numberRegex.exec(argsString);\n }\n\n // Include command if it has arguments or is a close command\n if (args.length > 0 || type.toUpperCase() === \"Z\") {\n commands.push({ type, args });\n }\n\n match = regex.exec(d);\n }\n\n return commands;\n}\n","/**\n * @fileoverview SVG path parsing and normalization utilities.\n *\n * This module provides the complete pipeline for parsing SVG path data strings\n * into normalized, absolute-coordinate command objects. It combines tokenization\n * and normalization into a single convenient interface.\n */\n\nimport { normalizeCommands } from \"./normalizer\";\nimport type { PathCommand } from \"./tokenizer\";\nimport { parseSVGPath } from \"./tokenizer\";\n\nexport type { PathCommand } from \"./tokenizer\";\n\n/**\n * Parses and normalizes an SVG path data string in one operation.\n *\n * This is the main entry point for path parsing, combining tokenization\n * and normalization into a single convenient function. It handles the\n * complete conversion from raw path string to processable command objects.\n *\n * @param pathData - The SVG path data string (value of 'd' attribute)\n * @returns Array of normalized PathCommand objects with absolute coordinates\n *\n * @example\n * ```typescript\n * const commands = parseAndNormalizePath(\"M10,20 l5,10 h15 Z\");\n * // Returns normalized commands with absolute coordinates\n * ```\n */\nexport function parseAndNormalizePath(pathData: string): Array<PathCommand> {\n const rawCommands = parseSVGPath(pathData);\n return normalizeCommands(rawCommands);\n}\n","/**\n * Represents a cubic Bézier curve with start point, end point, and two control points.\n */\nexport type BezierCurve = {\n /** Starting x-coordinate of the curve */\n x1: number;\n /** Starting y-coordinate of the curve */\n y1: number;\n /** First control point x-coordinate */\n cp1x: number;\n /** First control point y-coordinate */\n cp1y: number;\n /** Second control point x-coordinate */\n cp2x: number;\n /** Second control point y-coordinate */\n cp2y: number;\n /** Ending x-coordinate of the curve */\n x2: number;\n /** Ending y-coordinate of the curve */\n y2: number;\n};\n\n/**\n * Converts an SVG elliptical arc to one or more cubic Bézier curves.\n *\n * SVG arc commands (A/a) define elliptical arc segments, but many graphics systems\n * (including Figma's vector networks) work with cubic Bézier curves. This function\n * implements the standard algorithm for converting arc segments to mathematically\n * equivalent Bézier approximations.\n *\n * The conversion follows the SVG specification's arc-to-Bézier algorithm, handling\n * complex cases like large arcs that need to be split into multiple segments.\n *\n * @param x1 - Starting x-coordinate of the arc\n * @param y1 - Starting y-coordinate of the arc\n * @param x2 - Ending x-coordinate of the arc\n * @param y2 - Ending y-coordinate of the arc\n * @param rx - X-radius of the ellipse\n * @param ry - Y-radius of the ellipse\n * @param angle - Rotation angle of the ellipse in degrees\n * @param largeArcFlag - 0 for small arc, 1 for large arc\n * @param sweepFlag - 0 for negative direction, 1 for positive direction\n * @returns Array of BezierCurve objects approximating the arc\n *\n * @example\n * ```typescript\n * // Convert a quarter-circle arc\n * const curves = arcToBezier(0, 0, 100, 100, 50, 50, 0, 0, 1);\n * // Returns array of Bézier curves that approximate the arc\n * ```\n *\n * @remarks\n * - Large arcs (>90°) are automatically split into multiple Bézier segments for accuracy\n * - Degenerate arcs (rx=0 or ry=0) return straight line segments\n * - The algorithm ensures C1 continuity between multiple segments\n * - Follows the SVG 1.1 specification for arc parameterization\n */\nexport function arcToBezier(\n x1: number,\n y1: number,\n x2: number,\n y2: number,\n rx: number,\n ry: number,\n angle: number,\n largeArcFlag: number,\n sweepFlag: number\n): Array<BezierCurve> {\n // Handle edge cases\n if (rx === 0 || ry === 0) {\n return [\n {\n x1,\n y1,\n x2,\n y2,\n cp1x: x1,\n cp1y: y1,\n cp2x: x2,\n cp2y: y2,\n },\n ];\n }\n\n // Convert angle to radians\n const phi = (angle * Math.PI) / 180;\n const cosPhi = Math.cos(phi);\n const sinPhi = Math.sin(phi);\n\n // Step 1: Compute (x1', y1')\n const dx = (x1 - x2) / 2;\n const dy = (y1 - y2) / 2;\n const x1Prime = cosPhi * dx + sinPhi * dy;\n const y1Prime = -sinPhi * dx + cosPhi * dy;\n\n // Ensure radii are large enough\n let localRx = rx;\n let localRy = ry;\n const lambda =\n (x1Prime * x1Prime) / (localRx * localRx) +\n (y1Prime * y1Prime) / (localRy * localRy);\n if (lambda > 1) {\n localRx *= Math.sqrt(lambda);\n localRy *= Math.sqrt(lambda);\n }\n\n // Step 2: Compute (cx', cy')\n const sign = largeArcFlag === sweepFlag ? -1 : 1;\n const sq = Math.max(\n 0,\n (localRx * localRx * localRy * localRy -\n localRx * localRx * y1Prime * y1Prime -\n localRy * localRy * x1Prime * x1Prime) /\n (localRx * localRx * y1Prime * y1Prime +\n localRy * localRy * x1Prime * x1Prime)\n );\n const coeff = sign * Math.sqrt(sq);\n const cxPrime = coeff * ((localRx * y1Prime) / localRy);\n const cyPrime = coeff * (-(localRy * x1Prime) / localRx);\n\n // Step 3: Compute (cx, cy)\n const cx = cosPhi * cxPrime - sinPhi * cyPrime + (x1 + x2) / 2;\n const cy = sinPhi * cxPrime + cosPhi * cyPrime + (y1 + y2) / 2;\n\n // Step 4: Compute angles\n const theta1 = Math.atan2(\n (y1Prime - cyPrime) / localRy,\n (x1Prime - cxPrime) / localRx\n );\n const dtheta =\n Math.atan2((-y1Prime - cyPrime) / localRy, (-x1Prime - cxPrime) / localRx) -\n theta1;\n\n let deltaTheta = dtheta;\n if (sweepFlag === 0 && deltaTheta > 0) {\n deltaTheta -= 2 * Math.PI;\n } else if (sweepFlag === 1 && deltaTheta < 0) {\n deltaTheta += 2 * Math.PI;\n }\n\n // Convert arc to bezier curves (split if needed)\n const segments = Math.max(1, Math.ceil(Math.abs(deltaTheta) / (Math.PI / 2)));\n const delta = deltaTheta / segments;\n const t =\n ((8 / 3) * Math.sin(delta / 4) * Math.sin(delta / 4)) / Math.sin(delta / 2);\n\n const curves: Array<BezierCurve> = [];\n let currentTheta = theta1;\n\n for (let i = 0; i < segments; i += 1) {\n const startTheta = currentTheta;\n const endTheta = currentTheta + delta;\n\n const cosStart = Math.cos(startTheta);\n const sinStart = Math.sin(startTheta);\n const cosEnd = Math.cos(endTheta);\n const sinEnd = Math.sin(endTheta);\n\n const ex = cosPhi * localRx * cosStart - sinPhi * localRy * sinStart + cx;\n const ey = sinPhi * localRx * cosStart + cosPhi * localRy * sinStart + cy;\n const ex2 = cosPhi * localRx * cosEnd - sinPhi * localRy * sinEnd + cx;\n const ey2 = sinPhi * localRx * cosEnd + cosPhi * localRy * sinEnd + cy;\n\n const q1x = -cosPhi * localRx * sinStart - sinPhi * localRy * cosStart;\n const q1y = -sinPhi * localRx * sinStart + cosPhi * localRy * cosStart;\n const q2x = -cosPhi * localRx * sinEnd - sinPhi * localRy * cosEnd;\n const q2y = -sinPhi * localRx * sinEnd + cosPhi * localRy * cosEnd;\n\n curves.push({\n x1: ex,\n y1: ey,\n cp1x: ex + t * q1x,\n cp1y: ey + t * q1y,\n cp2x: ex2 - t * q2x,\n cp2y: ey2 - t * q2y,\n x2: ex2,\n y2: ey2,\n });\n\n currentTheta = endTheta;\n }\n\n return curves;\n}\n","import type { PathCommand } from \"../path-parser\";\nimport type { VectorSegment, VectorVertex } from \"../vector-networks/types\";\nimport { arcToBezier } from \"./arc-converter\";\n\n// Constants for bezier curve conversions\nconst QUADRATIC_TO_CUBIC_RATIO = 2 / 3;\nconst VERTEX_TOLERANCE = 0.001;\n\nexport type CurveBuilderState = {\n vertices: Array<VectorVertex>;\n segments: Array<VectorSegment>;\n currentX: number;\n currentY: number;\n pathStartX: number;\n pathStartY: number;\n currentPath: Array<number>;\n // Track last control point for smooth curve commands\n lastControlPointX?: number;\n lastControlPointY?: number;\n lastCommand?: string;\n};\n\nexport function createInitialState(): CurveBuilderState {\n return {\n vertices: [],\n segments: [],\n currentX: 0,\n currentY: 0,\n pathStartX: 0,\n pathStartY: 0,\n currentPath: [],\n };\n}\n\nfunction addVertex(state: CurveBuilderState, x: number, y: number): number {\n const existingIndex = state.vertices.findIndex(\n (v) =>\n Math.abs(v.x - x) < VERTEX_TOLERANCE &&\n Math.abs(v.y - y) < VERTEX_TOLERANCE\n );\n\n if (existingIndex !== -1) {\n return existingIndex;\n }\n\n state.vertices.push({\n styleID: 0,\n x,\n y,\n });\n return state.vertices.length - 1;\n}\n\nfunction addSegment(\n state: CurveBuilderState,\n startIdx: number,\n endIdx: number,\n tangentStartX = 0,\n tangentStartY = 0,\n tangentEndX = 0,\n tangentEndY = 0\n): number {\n const segment: VectorSegment = {\n styleID: 0,\n start: {\n vertex: startIdx,\n dx: tangentStartX,\n dy: tangentStartY,\n },\n end: {\n vertex: endIdx,\n dx: tangentEndX,\n dy: tangentEndY,\n },\n };\n state.segments.push(segment);\n return state.segments.length - 1;\n}\n\nexport function processCommand(\n state: CurveBuilderState,\n cmd: PathCommand\n): void {\n const { type, args } = cmd;\n\n switch (type) {\n case \"M\": {\n const x = args[0] ?? 0;\n const y = args[1] ?? 0;\n\n addVertex(state, x, y);\n state.currentPath = [];\n state.currentX = x;\n state.currentY = y;\n state.pathStartX = x;\n state.pathStartY = y;\n // Reset control point tracking on new path\n state.lastCommand = \"M\";\n break;\n }\n\n case \"L\": {\n const x = args[0] ?? 0;\n const y = args[1] ?? 0;\n\n const startIdx = addVertex(state, state.currentX, state.currentY);\n const endIdx = addVertex(state, x, y);\n const segmentIdx = addSegment(state, startIdx, endIdx);\n state.currentPath.push(segmentIdx);\n\n // Reset control point tracking for non-curve commands\n state.lastCommand = \"L\";\n\n state.currentX = x;\n state.currentY = y;\n break;\n }\n\n case \"C\": {\n const cp1x = args[0] ?? 0;\n const cp1y = args[1] ?? 0;\n const cp2x = args[2] ?? 0;\n const cp2y = args[3] ?? 0;\n const x = args[4] ?? 0;\n const y = args[5] ?? 0;\n\n const startIdx = addVertex(state, state.currentX, state.currentY);\n const endIdx = addVertex(state, x, y);\n\n const segmentIdx = addSegment(\n state,\n startIdx,\n endIdx,\n cp1x - state.currentX,\n cp1y - state.currentY,\n cp2x - x,\n cp2y - y\n );\n state.currentPath.push(segmentIdx);\n\n // Store last control point for smooth commands\n state.lastControlPointX = cp2x;\n state.lastControlPointY = cp2y;\n state.lastCommand = \"C\";\n\n state.currentX = x;\n state.currentY = y;\n break;\n }\n\n case \"Q\": {\n const cpx = args[0] ?? 0;\n const cpy = args[1] ?? 0;\n const x = args[2] ?? 0;\n const y = args[3] ?? 0;\n\n // Convert quadratic to cubic bezier\n const cp1x =\n state.currentX + QUADRATIC_TO_CUBIC_RATIO * (cpx - state.currentX);\n const cp1y =\n state.currentY + QUADRATIC_TO_CUBIC_RATIO * (cpy - state.currentY);\n const cp2x = x + QUADRATIC_TO_CUBIC_RATIO * (cpx - x);\n const cp2y = y + QUADRATIC_TO_CUBIC_RATIO * (cpy - y);\n\n const startIdx = addVertex(state, state.currentX, state.currentY);\n const endIdx = addVertex(state, x, y);\n\n const segmentIdx = addSegment(\n state,\n startIdx,\n endIdx,\n cp1x - state.currentX,\n cp1y - state.currentY,\n cp2x - x,\n cp2y - y\n );\n state.currentPath.push(segmentIdx);\n\n // Store last control point for smooth commands\n state.lastControlPointX = cpx;\n state.lastControlPointY = cpy;\n state.lastCommand = \"Q\";\n\n state.currentX = x;\n state.currentY = y;\n break;\n }\n\n case \"A\": {\n const rx = args[0] ?? 0;\n const ry = args[1] ?? 0;\n const xAxisRotation = args[2] ?? 0;\n const largeArcFlag = args[3] ?? 0;\n const sweepFlag = args[4] ?? 0;\n const x = args[5] ?? 0;\n const y = args[6] ?? 0;\n\n // Convert arc to cubic bezier curve(s)\n const bezierCurves = arcToBezier(\n state.currentX,\n state.currentY,\n x,\n y,\n rx,\n ry,\n xAxisRotation,\n largeArcFlag,\n sweepFlag\n );\n\n for (const curve of bezierCurves) {\n const startIdx = addVertex(state, curve.x1, curve.y1);\n const endIdx = addVertex(state, curve.x2, curve.y2);\n\n // Calculate tangent vectors\n const tangentStartX = curve.cp1x - curve.x1;\n const tangentStartY = curve.cp1y - curve.y1;\n const tangentEndX = curve.cp2x - curve.x2;\n const tangentEndY = curve.cp2y - curve.y2;\n\n const segmentIdx = addSegment(\n state,\n startIdx,\n endIdx,\n tangentStartX,\n tangentStartY,\n tangentEndX,\n tangentEndY\n );\n state.currentPath.push(segmentIdx);\n }\n\n state.currentX = x;\n state.currentY = y;\n break;\n }\n\n case \"S\": {\n // Smooth cubic bezier - first control point is reflection of previous second control point\n const cp2x = args[0] ?? 0;\n const cp2y = args[1] ?? 0;\n const x = args[2] ?? 0;\n const y = args[3] ?? 0;\n\n // Calculate reflected control point\n let cp1x = state.currentX;\n let cp1y = state.currentY;\n\n if (\n (state.lastCommand === \"C\" || state.lastCommand === \"S\") &&\n state.lastControlPointX !== undefined &&\n state.lastControlPointY !== undefined\n ) {\n // Reflect the last control point across the current point\n cp1x = 2 * state.currentX - state.lastControlPointX;\n cp1y = 2 * state.currentY - state.lastControlPointY;\n }\n\n const startIdx = addVertex(state, state.currentX, state.currentY);\n const endIdx = addVertex(state, x, y);\n\n const segmentIdx = addSegment(\n state,\n startIdx,\n endIdx,\n cp1x - state.currentX,\n cp1y - state.currentY,\n cp2x - x,\n cp2y - y\n );\n state.currentPath.push(segmentIdx);\n\n // Store last control point for next smooth command\n state.lastControlPointX = cp2x;\n state.lastControlPointY = cp2y;\n state.lastCommand = \"S\";\n\n state.currentX = x;\n state.currentY = y;\n break;\n }\n\n case \"T\": {\n // Smooth quadratic bezier - control point is reflection of previous control point\n const x = args[0] ?? 0;\n const y = args[1] ?? 0;\n\n // Calculate reflected control point\n let cpx = state.currentX;\n let cpy = state.currentY;\n\n if (\n (state.lastCommand === \"Q\" || state.lastCommand === \"T\") &&\n state.lastControlPointX !== undefined &&\n state.lastControlPointY !== undefined\n ) {\n // Reflect the last control point across the current point\n cpx = 2 * state.currentX - state.lastControlPointX;\n cpy = 2 * state.currentY - state.lastControlPointY;\n }\n\n // Convert quadratic to cubic bezier\n const cp1x =\n state.currentX + QUADRATIC_TO_CUBIC_RATIO * (cpx - state.currentX);\n const cp1y =\n state.currentY + QUADRATIC_TO_CUBIC_RATIO * (cpy - state.currentY);\n const cp2x = x + QUADRATIC_TO_CUBIC_RATIO * (cpx - x);\n const cp2y = y + QUADRATIC_TO_CUBIC_RATIO * (cpy - y);\n\n const startIdx = addVertex(state, state.currentX, state.currentY);\n const endIdx = addVertex(state, x, y);\n\n const segmentIdx = addSegment(\n state,\n startIdx,\n endIdx,\n cp1x - state.currentX,\n cp1y - state.currentY,\n cp2x - x,\n cp2y - y\n );\n state.currentPath.push(segmentIdx);\n\n // Store last control point for next smooth command\n state.lastControlPointX = cpx;\n state.lastControlPointY = cpy;\n state.lastCommand = \"T\";\n\n state.currentX = x;\n state.currentY = y;\n break;\n }\n\n case \"Z\": {\n if (\n Math.abs(state.currentX - state.pathStartX) > VERTEX_TOLERANCE ||\n Math.abs(state.currentY - state.pathStartY) > VERTEX_TOLERANCE\n ) {\n const startIdx = addVertex(state, state.currentX, state.currentY);\n const endIdx = addVertex(state, state.pathStartX, state.pathStartY);\n const segmentIdx = addSegment(state, startIdx, endIdx);\n state.currentPath.push(segmentIdx);\n }\n\n state.currentX = state.pathStartX;\n state.currentY = state.pathStartY;\n break;\n }\n default: {\n break;\n }\n }\n}\n","import type { PathCommand } from \"../path-parser\";\nimport type {\n VectorNetwork,\n VectorRegion,\n WindingRule,\n} from \"../vector-networks/types\";\nimport type { CurveBuilderState } from \"./curve-builder\";\nimport { createInitialState, processCommand } from \"./curve-builder\";\n\nexport type PathToVectorResult = {\n vectorNetwork: VectorNetwork;\n normalizationOffset?: { x: number; y: number };\n};\n\nexport function convertPathToVectorNetwork(\n commands: Array<PathCommand>,\n params: { normalize: boolean; fillRule?: WindingRule } = { normalize: true }\n): PathToVectorResult {\n const state = createInitialState();\n const fillRule = params.fillRule ?? \"NONZERO\";\n let normalizationOffset: { x: number; y: number } | undefined;\n\n // Collect each subpath as its own loop and emit a single region whose\n // windingRule matches the SVG fill-rule. With NONZERO, opposite winding\n // directions between loops cut holes (e.g. Phosphor outline icons whose\n // inner subpaths are wound counter to the outer outline). Splitting into\n // separate regions instead would render every subpath as a solid fill.\n const loops: Array<Array<number>> = [];\n let currentLoop: Array<number> = [];\n\n for (const cmd of commands) {\n if (cmd.type === \"M\" && currentLoop.length > 0) {\n loops.push(currentLoop);\n currentLoop = [];\n }\n\n processCommand(state, cmd);\n\n if (state.currentPath.length > currentLoop.length) {\n currentLoop.push(...state.currentPath.slice(currentLoop.length));\n }\n\n if (cmd.type === \"Z\") {\n if (currentLoop.length > 0) {\n loops.push(currentLoop);\n currentLoop = [];\n }\n state.currentPath = [];\n }\n }\n\n if (currentLoop.length > 0) {\n loops.push(currentLoop);\n }\n\n const regions: Array<VectorRegion> = [];\n if (loops.length > 0) {\n regions.push({\n styleID: 0,\n windingRule: fillRule,\n loops: loops.map((segments) => ({\n segments,\n windingRule: fillRule,\n })),\n });\n }\n\n if (params.normalize && state.vertices.length > 0) {\n normalizationOffset = normalizeVertices(state);\n }\n\n return {\n vectorNetwork: {\n vertices: state.vertices,\n segments: state.segments,\n regions,\n },\n normalizationOffset,\n };\n}\n\nfunction normalizeVertices(state: CurveBuilderState): { x: number; y: number } {\n // Find bounding box\n const firstVertex = state.vertices[0];\n if (!firstVertex) {\n return { x: 0, y: 0 };\n }\n\n let minX = firstVertex.x;\n let minY = firstVertex.y;\n let maxX = firstVertex.x;\n let maxY = firstVertex.y;\n\n for (const vertex of state.vertices) {\n minX = Math.min(minX, vertex.x);\n minY = Math.min(minY, vertex.y);\n maxX = Math.max(maxX, vertex.x);\n maxY = Math.max(maxY, vertex.y);\n }\n\n // Translate all vertices to start from (0,0)\n const offsetX = minX;\n const offsetY = minY;\n\n for (const vertex of state.vertices) {\n vertex.x -= offsetX;\n vertex.y -= offsetY;\n }\n\n // Return the offset that was applied\n return { x: offsetX, y: offsetY };\n}\n","import type { VectorVertex } from \"../vector-networks/types\";\n\nexport type Bounds = {\n minX: number;\n minY: number;\n maxX: number;\n maxY: number;\n width: number;\n height: number;\n};\n\n/**\n * Calculates the bounding box of a set of vertices\n */\nexport function calculateBounds(vertices: Array<VectorVertex>): Bounds {\n if (vertices.length === 0) {\n return {\n minX: 0,\n minY: 0,\n maxX: 0,\n maxY: 0,\n width: 0,\n height: 0,\n };\n }\n\n const firstVertex = vertices[0];\n if (!firstVertex) {\n return {\n minX: 0,\n minY: 0,\n maxX: 0,\n maxY: 0,\n width: 0,\n height: 0,\n };\n }\n\n let minX = firstVertex.x;\n let minY = firstVertex.y;\n let maxX = firstVertex.x;\n let maxY = firstVertex.y;\n\n for (const vertex of vertices) {\n minX = Math.min(minX, vertex.x);\n minY = Math.min(minY, vertex.y);\n maxX = Math.max(maxX, vertex.x);\n maxY = Math.max(maxY, vertex.y);\n }\n\n return {\n minX,\n minY,\n maxX,\n maxY,\n width: maxX - minX,\n height: maxY - minY,\n };\n}\n","/**\n * Scales a stroke weight based on the average scale factor\n */\nexport function scaleStrokeWeight(\n originalWeight: number,\n avgScaleFactor: number\n): number {\n // Ensure minimum stroke weight of 0.1\n return Math.max(0.1, originalWeight * avgScaleFactor);\n}\n\n/**\n * Scales a dash array pattern based on the average scale factor\n */\nexport function scaleDashArray(\n originalDashArray: string,\n avgScaleFactor: number\n): string | undefined {\n if (!originalDashArray || originalDashArray === \"none\") {\n return;\n }\n\n // Parse the dash array values\n const dashValues = parseDashArray(originalDashArray);\n\n if (dashValues.length === 0) {\n return;\n }\n\n // Scale each dash value\n const scaledValues = dashValues.map((value) =>\n (value * avgScaleFactor).toFixed(2)\n );\n\n return scaledValues.join(\",\");\n}\n\n/**\n * Parses a dash array string into numeric values\n */\nfunction parseDashArray(dashArray: string): Array<number> {\n return dashArray\n .trim()\n .split(/[\\s,]+/)\n .map((v) => Number.parseFloat(v.trim()))\n .filter((v) => !Number.isNaN(v) && v >= 0);\n}\n","import type { VectorNetwork } from \"../vector-networks/types\";\nimport type { ScaleFactors } from \"./scale-factors\";\n\n/**\n * Applies scale factors to a vector network\n */\nexport function scaleVectorNetwork(\n network: VectorNetwork,\n scaleFactors: ScaleFactors\n): VectorNetwork {\n const { x: scaleX, y: scaleY } = scaleFactors;\n\n // Scale vertices\n const scaledVertices = network.vertices.map((vertex) => ({\n ...vertex,\n x: vertex.x * scaleX,\n y: vertex.y * scaleY,\n }));\n\n // Scale segments (including control point deltas)\n const scaledSegments = network.segments.map((segment) => ({\n ...segment,\n start: {\n ...segment.start,\n dx: segment.start.dx * scaleX,\n dy: segment.start.dy * scaleY,\n },\n end: {\n ...segment.end,\n dx: segment.end.dx * scaleX,\n dy: segment.end.dy * scaleY,\n },\n }));\n\n // Regions don't need coordinate scaling\n const scaledRegions = [...network.regions];\n\n return {\n vertices: scaledVertices,\n segments: scaledSegments,\n regions: scaledRegions,\n };\n}\n\n/**\n * Creates a deep copy of a vector network\n */\nexport function copyVectorNetwork(network: VectorNetwork): VectorNetwork {\n return JSON.parse(JSON.stringify(network)) as VectorNetwork;\n}\n","import type { Bounds } from \"./bounds\";\n\nexport type ScaleFactors = {\n x: number;\n y: number;\n};\n\nexport type ScalingDimensions = {\n targetWidth?: number;\n targetHeight?: number;\n preserveAspectRatio?: boolean;\n};\n\n/**\n * Calculates scale factors based on original bounds and target dimensions\n */\nexport function calculateScaleFactors(\n originalBounds: Bounds,\n options: ScalingDimensions\n): ScaleFactors {\n const { targetWidth, targetHeight, preserveAspectRatio = true } = options;\n\n // If no scaling needed, return identity scale\n if (!(targetWidth || targetHeight)) {\n return { x: 1, y: 1 };\n }\n\n // Calculate initial scale factors\n let scaleX = 1;\n let scaleY = 1;\n\n if (targetWidth && originalBounds.width > 0) {\n scaleX = targetWidth / originalBounds.width;\n }\n if (targetHeight && originalBounds.height > 0) {\n scaleY = targetHeight / originalBounds.height;\n }\n\n // Handle aspect ratio preservation\n if (preserveAspectRatio) {\n if (targetWidth && targetHeight) {\n // Use the smaller scale to fit within both dimensions\n const scale = Math.min(scaleX, scaleY);\n scaleX = scale;\n scaleY = scale;\n } else if (targetWidth && !targetHeight) {\n // Only width specified - use same scale for height\n scaleY = scaleX;\n } else if (targetHeight && !targetWidth) {\n // Only height specified - use same scale for width\n scaleX = scaleY;\n }\n }\n\n return { x: scaleX, y: scaleY };\n}\n\n/**\n * Calculates the average scale factor (used for stroke weights and dash arrays)\n */\nexport function getAverageScaleFactor(scaleFactors: ScaleFactors): number {\n return (scaleFactors.x + scaleFactors.y) / 2;\n}\n\n/**\n * Adjusts target dimensions based on viewBox information\n * Ensures the aspect ratio from viewBox is preserved\n */\nexport function adjustTargetDimensions(\n targetWidth: number | undefined,\n targetHeight: number | undefined,\n viewBoxWidth: number | undefined,\n viewBoxHeight: number | undefined\n): { width: number | undefined; height: number | undefined } {\n let adjustedWidth = targetWidth;\n let adjustedHeight = targetHeight;\n\n if (viewBoxWidth && viewBoxHeight && (targetWidth || targetHeight)) {\n const viewBoxAspect = viewBoxWidth / viewBoxHeight;\n\n if (targetWidth && !targetHeight) {\n // Calculate height based on viewBox aspect ratio\n adjustedHeight = targetWidth / viewBoxAspect;\n } else if (targetHeight && !targetWidth) {\n // Calculate width based on viewBox aspect ratio\n adjustedWidth = targetHeight * viewBoxAspect;\n } else if (targetWidth && targetHeight) {\n // Both dimensions provided - adjust to match viewBox aspect ratio\n // Use the smaller dimension to ensure the shape fits\n const targetAspect = targetWidth / targetHeight;\n\n if (targetAspect > viewBoxAspect) {\n // Target is wider than viewBox - constrain by height\n adjustedWidth = targetHeight * viewBoxAspect;\n } else {\n // Target is taller than viewBox - constrain by width\n adjustedHeight = targetWidth / viewBoxAspect;\n }\n }\n }\n\n return { width: adjustedWidth, height: adjustedHeight };\n}\n","/**\n * @fileoverview Vector network scaling utilities.\n *\n * This module provides comprehensive scaling functionality for vector networks,\n * similar to SVG viewBox behavior. It handles proportional scaling while\n * maintaining visual fidelity of strokes, dash patterns, and geometric proportions.\n */\n\nimport type { VectorNetwork } from \"../vector-networks/types\";\nimport type { Bounds } from \"./bounds\";\nimport { calculateBounds } from \"./bounds\";\nimport { scaleDashArray, scaleStrokeWeight } from \"./dash-scaler\";\nimport {\n scaleVectorNetwork as applyScaling,\n copyVectorNetwork,\n} from \"./network-scaler\";\nimport type { ScaleFactors } from \"./scale-factors\";\nimport { calculateScaleFactors, getAverageScaleFactor } from \"./scale-factors\";\n\nexport type ScalingOptions = {\n /** Target width for the scaled vector */\n targetWidth?: number;\n /** Target height for the scaled vector */\n targetHeight?: number;\n /** Maintain aspect ratio when scaling */\n preserveAspectRatio?: boolean;\n /** Original stroke weight to scale (if provided) */\n originalStrokeWeight?: number;\n /** Original stroke dash array to scale (if provided) */\n originalStrokeDashArray?: string;\n};\n\nexport type ScaledVectorResult = {\n /** The scaled vector network */\n vectorNetwork: VectorNetwork;\n /** Original bounding box */\n originalBounds: Bounds;\n /** Final size for Figma node */\n figmaSize: {\n x: number;\n y: number;\n };\n /** Scale factors applied */\n scaleFactors: ScaleFactors;\n /** Scaled stroke weight (if provided) */\n scaledStrokeWeight?: number;\n /** Scaled stroke dash array (if provided) */\n scaledStrokeDashArray?: string;\n};\n\n/**\n * Scales a vector network to fit target dimensions\n * Similar to how SVG viewBox scaling works\n */\nexport function scaleVectorNetwork(\n vectorNetwork: VectorNetwork,\n options: ScalingOptions\n): ScaledVectorResult {\n const {\n targetWidth,\n targetHeight,\n preserveAspectRatio = true,\n originalStrokeWeight,\n originalStrokeDashArray,\n } = options;\n\n // Calculate original bounds\n const originalBounds = calculateBounds(vectorNetwork.vertices);\n\n // If no scaling needed, return as-is\n if (!(targetWidth || targetHeight)) {\n return {\n vectorNetwork: copyVectorNetwork(vectorNetwork),\n originalBounds,\n figmaSize: { x: originalBounds.width, y: originalBounds.height },\n scaleFactors: { x: 1, y: 1 },\n };\n }\n\n // Calculate scale factors\n const scaleFactors = calculateScaleFactors(originalBounds, {\n targetWidth,\n targetHeight,\n preserveAspectRatio,\n });\n\n // Apply scaling to the vector network\n const scaledNetwork = applyScaling(vectorNetwork, scaleFactors);\n\n // Calculate final bounds after scaling\n const finalBounds = calculateBounds(scaledNetwork.vertices);\n\n // Scale stroke properties if provided\n const avgScaleFactor = getAverageScaleFactor(scaleFactors);\n\n let scaledStrokeWeight: number | undefined;\n if (originalStrokeWeight !== undefined) {\n scaledStrokeWeight = scaleStrokeWeight(\n originalStrokeWeight,\n avgScaleFactor\n );\n }\n\n let scaledStrokeDashArray: string | undefined;\n if (originalStrokeDashArray) {\n scaledStrokeDashArray = scaleDashArray(\n originalStrokeDashArray,\n avgScaleFactor\n );\n }\n\n return {\n vectorNetwork: scaledNetwork,\n originalBounds,\n figmaSize: {\n x: finalBounds.width,\n y: finalBounds.height,\n },\n scaleFactors,\n scaledStrokeWeight,\n scaledStrokeDashArray,\n };\n}\n","/**\n * @fileoverview Main entry point for SVG path to vector network conversion.\n *\n * This module orchestrates the complete pipeline from SVG path strings to\n * Figma-compatible vector network structures. It combines path parsing,\n * normalization, and vector network construction into a single convenient API.\n */\n\nimport { parseAndNormalizePath } from \"../path-parser\";\nimport { convertPathToVectorNetwork } from \"../path-to-network\";\nimport type { ScaledVectorResult } from \"../scaling\";\nimport { scaleVectorNetwork } from \"../scaling\";\nimport { adjustTargetDimensions } from \"../scaling/scale-factors\";\nimport type { VectorNetwork, WindingRule } from \"./types\";\n\n/**\n * Converts an SVG path data string to a Figma vector network structure.\n *\n * This is the main function that orchestrates the complete conversion pipeline:\n * 1. Parse the SVG path string into command objects\n * 2. Normalize relative commands to absolute coordinates\n * 3. Convert path commands to vector network vertices, segments, and regions\n * 4. Optionally normalize coordinates to start from (0,0)\n *\n * @param pathData - The SVG path data string (value of the 'd' attribute)\n * @param params - Configuration options for the conversion\n * @param params.normalize - Whether to normalize coordinates to start from (0,0)\n * @returns VectorNetwork structure compatible with Figma's format\n *\n * @example\n * ```typescript\n * // Convert a simple path\n * const network = svgPathToVectorNetwork(\"M10,20 L30,40 Z\");\n *\n * // Convert without coordinate normalization\n * const network = svgPathToVectorNetwork(\"M100,200 L300,400 Z\", { normalize: false });\n * ```\n *\n * @remarks\n * The resulting vector network contains:\n * - vertices: Point coordinates for path endpoints and control points\n * - segments: Curve definitions connecting vertices (lines, Bézier curves)\n * - regions: Closed areas defined by collections of segments\n */\nfunction svgPathToVectorNetwork(\n pathData: string,\n params: { normalize: boolean; fillRule?: WindingRule } = { normalize: true }\n): VectorNetwork {\n const normalizedCommands = parseAndNormalizePath(pathData);\n const result = convertPathToVectorNetwork(normalizedCommands, params);\n return result.vectorNetwork;\n}\n\n/**\n * Convenience function that handles SVG-style scaling\n * Converts SVG path with viewBox-like behavior to scaled Figma vector\n */\nexport function svgPathToVectorNetworkWithScaling(\n pathData: string,\n options: {\n /** Original SVG viewBox width (if known) */\n viewBoxWidth?: number;\n /** Original SVG viewBox height (if known) */\n viewBoxHeight?: number;\n /** Target width in Figma */\n targetWidth?: number;\n /** Target height in Figma */\n targetHeight?: number;\n /** Whether to normalize coordinates first */\n normalize?: boolean;\n /** Preserve aspect ratio when scaling */\n preserveAspectRatio?: boolean;\n /** Original stroke weight to scale (if provided) */\n originalStrokeWeight?: number;\n /** Original stroke dash array to scale (if provided) */\n originalStrokeDashArray?: string;\n /** Fill rule for the path (\"NONZERO\" or \"ODD\") */\n fillRule?: WindingRule;\n }\n): ScaledVectorResult {\n const {\n viewBoxWidth,\n viewBoxHeight,\n targetWidth,\n targetHeight,\n normalize = true,\n preserveAspectRatio = true,\n originalStrokeWeight,\n originalStrokeDashArray,\n fillRule = \"NONZERO\",\n } = options;\n\n // First convert SVG path to vector network\n const vectorNetwork = svgPathToVectorNetwork(pathData, {\n normalize,\n fillRule,\n });\n\n // Adjust target dimensions based on viewBox if available\n const { width: adjustedWidth, height: adjustedHeight } =\n adjustTargetDimensions(\n targetWidth,\n targetHeight,\n viewBoxWidth,\n viewBoxHeight\n );\n\n // Scale the vector network\n return scaleVectorNetwork(vectorNetwork, {\n targetWidth: adjustedWidth,\n targetHeight: adjustedHeight,\n preserveAspectRatio,\n originalStrokeWeight,\n originalStrokeDashArray,\n });\n}\n","import type { VectorNetwork } from \"./types\";\n\nfunction writeFloat32(buffer: Array<number>, value: number): void {\n const view = new DataView(new ArrayBuffer(4));\n view.setFloat32(0, value, true);\n for (let i = 0; i < 4; i += 1) {\n buffer.push(view.getUint8(i));\n }\n}\n\nfunction writeUint32(buffer: Array<number>, value: number): void {\n const view = new DataView(new ArrayBuffer(4));\n view.setUint32(0, value, true);\n for (let i = 0; i < 4; i += 1) {\n buffer.push(view.getUint8(i));\n }\n}\n\nexport function vectorNetworkToBytes(vectorNetwork: VectorNetwork): Uint8Array {\n const { vertices, segments, regions } = vectorNetwork;\n const buffer: Array<number> = [];\n\n // Write header: counts of vertices, segments, regions\n writeUint32(buffer, vertices.length);\n writeUint32(buffer, segments.length);\n writeUint32(buffer, regions.length);\n\n // Write vertices\n for (const vertex of vertices) {\n writeUint32(buffer, vertex.styleID);\n writeFloat32(buffer, vertex.x);\n writeFloat32(buffer, vertex.y);\n }\n\n // Write segments\n for (const segment of segments) {\n writeUint32(buffer, segment.styleID);\n writeUint32(buffer, segment.start.vertex);\n writeFloat32(buffer, segment.start.dx);\n writeFloat32(buffer, segment.start.dy);\n writeUint32(buffer, segment.end.vertex);\n writeFloat32(buffer, segment.end.dx);\n writeFloat32(buffer, segment.end.dy);\n }\n\n // Write regions\n for (const region of regions) {\n // Combine styleID and winding rule into one uint32.\n // Figma packs bit 0 as 0 for ODD/EVENODD and 1 for NONZERO; bits 1-31 hold\n // the styleID shifted left by 1.\n let styleIDWithWindingRule = region.styleID << 1;\n if (region.windingRule === \"NONZERO\") {\n styleIDWithWindingRule |= 1;\n }\n writeUint32(buffer, styleIDWithWindingRule);\n\n // Write number of loops\n writeUint32(buffer, region.loops.length);\n\n // Write each loop\n for (const loop of region.loops) {\n writeUint32(buffer, loop.segments.length);\n for (const segmentIndex of loop.segments) {\n writeUint32(buffer, segmentIndex);\n }\n }\n }\n\n return new Uint8Array(buffer);\n}\n","/**\n * A `dashed` border on a **rounded** box, cut into explicit dash geometry.\n *\n * On a square box Chrome fits the dashes to each side independently, which\n * `border-decomposition.ts` reproduces by clipping a side's trapezoid. A rounded\n * box has no independent sides: Chrome runs one continuously-phased pattern\n * around the whole rounded-rect path, starting where the top-left corner's arc\n * ends and fitting it so a whole number of dashes closes the loop.\n *\n * Figma's own `dashPattern` cannot follow that. It re-fits whatever pattern it\n * is handed against its own measure of the path, and probing it with known\n * patterns produced dashes 4% long with no stable relation to the request. So\n * the dashes are painted as geometry instead: a band across each straight run,\n * an annular sector across each corner arc.\n */\n\n/** Chrome draws a `dashed` dash at twice the border width. */\nconst DASH_LENGTH_RATIO = 2;\n\n/** Cap on emitted nodes — a hairline dash on a long path would flood the scene. */\nconst MAX_DASHES = 96;\n\nconst HALF_PI = Math.PI / 2;\n\n/** One painted piece: an SVG path plus its frame-local bounding box. */\nexport type BorderPathPiece = {\n path: string;\n minX: number;\n minY: number;\n spanX: number;\n spanY: number;\n};\n\ntype Point = readonly [number, number];\n\n/**\n * A run of the border path, measured along its centerline. `pieceFor` cuts out\n * the band between two distances into it.\n */\ntype PathRun = {\n length: number;\n pieceFor: (from: number, to: number) => BorderPathPiece | null;\n};\n\nfunction boundsOf(points: ReadonlyArray<Point>): {\n minX: number;\n minY: number;\n spanX: number;\n spanY: number;\n} {\n const xs = points.map((p) => p[0]);\n const ys = points.map((p) => p[1]);\n const minX = Math.min(...xs);\n const minY = Math.min(...ys);\n return {\n minX,\n minY,\n spanX: Math.max(...xs) - minX,\n spanY: Math.max(...ys) - minY,\n };\n}\n\nfunction rectPiece(\n x0: number,\n y0: number,\n x1: number,\n y1: number\n): BorderPathPiece | null {\n const points: Array<Point> = [\n [x0, y0],\n [x1, y0],\n [x1, y1],\n [x0, y1],\n ];\n const bounds = boundsOf(points);\n if (bounds.spanX <= 0 || bounds.spanY <= 0) {\n return null;\n }\n return {\n path: `M${x0} ${y0} L${x1} ${y0} L${x1} ${y1} L${x0} ${y1} Z`,\n ...bounds,\n };\n}\n\n/**\n * The slice of a corner's border band between two angles: out along the outer\n * radius, in across the band, back along the inner radius.\n *\n * Every arc here stays inside one quadrant, where both coordinates are monotone\n * in the angle, so the four corner points bound the sector exactly.\n */\nfunction sectorPiece(params: {\n cx: number;\n cy: number;\n outer: number;\n inner: number;\n from: number;\n to: number;\n}): BorderPathPiece | null {\n const { cx, cy, outer, inner, from, to } = params;\n const at = (radius: number, angle: number): Point => [\n cx + radius * Math.cos(angle),\n cy + radius * Math.sin(angle),\n ];\n const outerFrom = at(outer, from);\n const outerTo = at(outer, to);\n // Angles increase clockwise on screen (y grows downward), which is SVG's\n // positive sweep; the inner arc runs back the other way.\n const head = `M${outerFrom[0]} ${outerFrom[1]} A${outer} ${outer} 0 0 1 ${outerTo[0]} ${outerTo[1]}`;\n\n if (inner <= 0) {\n const points = [outerFrom, outerTo, [cx, cy] as Point];\n const bounds = boundsOf(points);\n if (bounds.spanX <= 0 || bounds.spanY <= 0) {\n return null;\n }\n return { path: `${head} L${cx} ${cy} Z`, ...bounds };\n }\n\n const innerFrom = at(inner, from);\n const innerTo = at(inner, to);\n const bounds = boundsOf([outerFrom, outerTo, innerFrom, innerTo]);\n if (bounds.spanX <= 0 || bounds.spanY <= 0) {\n return null;\n }\n return {\n path: `${head} L${innerTo[0]} ${innerTo[1]} A${inner} ${inner} 0 0 0 ${innerFrom[0]} ${innerFrom[1]} Z`,\n ...bounds,\n };\n}\n\n/**\n * The border path as eight runs, clockwise from where the top-left arc ends —\n * the point Chrome starts its dash phase at, measured off its raster.\n */\nfunction borderRuns(params: {\n boxWidth: number;\n boxHeight: number;\n radius: number;\n width: number;\n}): Array<PathRun> | null {\n const { boxWidth, boxHeight, radius, width } = params;\n const straightX = boxWidth - 2 * radius;\n const straightY = boxHeight - 2 * radius;\n const centerRadius = radius - width / 2;\n if (straightX < 0 || straightY < 0 || centerRadius <= 0) {\n return null;\n }\n const arcLength = HALF_PI * centerRadius;\n const inner = radius - width;\n\n const arc = (cx: number, cy: number, startAngle: number): PathRun => ({\n length: arcLength,\n pieceFor: (from, to) =>\n sectorPiece({\n cx,\n cy,\n outer: radius,\n inner,\n from: startAngle + from / centerRadius,\n to: startAngle + to / centerRadius,\n }),\n });\n\n return [\n {\n length: straightX,\n pieceFor: (from, to) => rectPiece(radius + from, 0, radius + to, width),\n },\n arc(boxWidth - radius, radius, -HALF_PI),\n {\n length: straightY,\n pieceFor: (from, to) =>\n rectPiece(boxWidth - width, radius + from, boxWidth, radius + to),\n },\n arc(boxWidth - radius, boxHeight - radius, 0),\n {\n length: straightX,\n pieceFor: (from, to) =>\n rectPiece(\n boxWidth - radius - to,\n boxHeight - width,\n boxWidth - radius - from,\n boxHeight\n ),\n },\n arc(radius, boxHeight - radius, HALF_PI),\n {\n length: straightY,\n pieceFor: (from, to) =>\n rectPiece(0, boxHeight - radius - to, width, boxHeight - radius - from),\n },\n arc(radius, radius, Math.PI),\n ];\n}\n\n/**\n * How many dashes close the loop. Same rule as the per-side fit in\n * `border-decomposition.ts`: the dash length is fixed and the count is the one\n * whose resulting gap comes closest to its nominal `width`.\n */\nfunction fitDashCount(\n perimeter: number,\n dashLength: number,\n width: number\n): number | null {\n const gapFor = (count: number) => perimeter / count - dashLength;\n const exact = perimeter / (dashLength + width);\n const dense = Math.ceil(exact);\n const sparse = Math.max(2, dense - 1);\n const count =\n Math.abs(gapFor(dense) - width) <= Math.abs(gapFor(sparse) - width)\n ? dense\n : sparse;\n if (count < 2 || count > MAX_DASHES || gapFor(count) <= 0) {\n return null;\n }\n return count;\n}\n\n/** The pieces of one dash, split wherever it crosses from one run to the next. */\nfunction dashPieces(\n runs: ReadonlyArray<PathRun>,\n perimeter: number,\n start: number,\n end: number\n): Array<BorderPathPiece> {\n const pieces: Array<BorderPathPiece> = [];\n let runStart = 0;\n for (const run of runs) {\n const runEnd = runStart + run.length;\n // The dash may wrap past the end of the path, so test it in both positions.\n for (const offset of [0, perimeter]) {\n const from = Math.max(start - offset, runStart);\n const to = Math.min(end - offset, runEnd);\n if (to - from > 0) {\n const piece = run.pieceFor(from - runStart, to - runStart);\n if (piece) {\n pieces.push(piece);\n }\n }\n }\n runStart = runEnd;\n }\n return pieces;\n}\n\n/**\n * The dash geometry for a uniform `dashed` border on a box with a uniform\n * corner radius, or `null` when the box cannot carry one (no radius to speak\n * of, a radius swallowed by the border width, or more dashes than the node\n * budget allows).\n */\nexport function roundedDashedBorderPieces(params: {\n boxWidth: number;\n boxHeight: number;\n radius: number;\n width: number;\n}): Array<BorderPathPiece> | null {\n const { radius, width } = params;\n if (width <= 0 || radius <= 0) {\n return null;\n }\n const runs = borderRuns(params);\n if (!runs) {\n return null;\n }\n const perimeter = runs.reduce((total, run) => total + run.length, 0);\n const dashLength = width * DASH_LENGTH_RATIO;\n const count = fitDashCount(perimeter, dashLength, width);\n if (!count) {\n return null;\n }\n\n const period = perimeter / count;\n const pieces: Array<BorderPathPiece> = [];\n for (let i = 0; i < count; i++) {\n const start = i * period;\n pieces.push(...dashPieces(runs, perimeter, start, start + dashLength));\n }\n return pieces.length > 0 ? pieces : null;\n}\n","import type { Border3dStyle } from \"../../styles/border-3d\";\nimport { border3dSideColor, isBorder3dStyle } from \"../../styles/border-3d\";\nimport { createSolidPaint, cssColorToFigmaColor } from \"../../styles/color\";\nimport type { FigmaBlob, FigmaGuid, FigmaVectorNodeChange } from \"../../types\";\nimport { svgPathToVectorNetworkWithScaling } from \"../vector/vector-networks\";\nimport { vectorNetworkToBytes } from \"../vector/vector-networks/encoder\";\nimport type { BorderPathPiece } from \"./rounded-dash-border\";\nimport { roundedDashedBorderPieces } from \"./rounded-dash-border\";\n\n/**\n * Per-side border color/style decomposition.\n *\n * A Figma frame carries a single `strokePaints` color and a single dash\n * pattern, so one stroke cannot express four different border colors — nor a\n * border that is `solid` on one side and `dashed` on the next. When the visible\n * sides disagree on either, we drop the frame stroke and paint each side as its\n * own filled VECTOR geometry — matching CSS's 45° mitered corners exactly — so\n * red/green/blue/orange edges survive the round-trip instead of collapsing to\n * the top color. Uniform borders keep the single-stroke fast path.\n *\n * `dashed` and `dotted` sides are cut into their individual dashes, fitted the\n * way Chrome fits them: per side, landing a dash in each corner. That applies\n * to uniform patterned borders too, which Figma would otherwise phase\n * continuously around the box and drift out of step. Rounded boxes have no\n * independent sides and go to `rounded-dash-border.ts` instead.\n *\n * The same trapezoids also carry the 3D styles (`groove`/`ridge`/`inset`/\n * `outset`): those are uniform in the declared color but *painted* with a\n * per-side shade, and `groove`/`ridge` additionally split each side in half.\n * See `styles/border-3d.ts` for the shading; here we only cut the bands.\n */\n\ntype SideKey = \"top\" | \"right\" | \"bottom\" | \"left\";\n\nconst SIDE_KEYS: ReadonlyArray<SideKey> = [\"top\", \"right\", \"bottom\", \"left\"];\n\ntype BorderSide = {\n width: number;\n color: string;\n style: string;\n};\n\ntype BorderSides = Record<SideKey, BorderSide>;\n\n/** A convex polygon (frame-local coordinates) plus the CSS color to fill it. */\ntype SideQuad = {\n points: ReadonlyArray<readonly [number, number]>;\n color: string;\n};\n\n/** A pre-built path (frame-local coordinates) plus the CSS color to fill it. */\ntype SidePath = BorderPathPiece & { color: string };\n\n/** A round dot (frame-local coordinates) plus the CSS color to fill it. */\ntype SideDot = {\n cx: number;\n cy: number;\n radius: number;\n color: string;\n};\n\ntype SidePiece = SideQuad | SideDot | SidePath;\n\nfunction isDot(piece: SidePiece): piece is SideDot {\n return \"radius\" in piece;\n}\n\nfunction isPath(piece: SidePiece): piece is SidePath {\n return \"path\" in piece;\n}\n\n/** The styles the per-side painter below can draw. */\nconst PAINTABLE_STYLES: ReadonlyArray<string> = [\n \"solid\",\n \"dashed\",\n \"dotted\",\n \"double\",\n];\n\n/** Chrome draws a `dashed` dash at twice the border width; a `dotted` dot at one. */\nconst DASH_LENGTH_RATIO = 2;\n\n/**\n * Every dash becomes its own VECTOR node, so a hairline border on a long edge\n * could emit hundreds. Past this the side falls back to a solid trapezoid,\n * which is what the single-stroke path would have drawn anyway.\n */\nconst MAX_DASHES_PER_SIDE = 64;\n\nfunction parseBorderSides(style: CSSStyleDeclaration): BorderSides {\n const fallbackColor = style.borderColor;\n const read = (widthProp: string, colorProp: string, styleProp: string) => ({\n width: Number.parseFloat(style.getPropertyValue(widthProp) || \"0\"),\n color: style.getPropertyValue(colorProp) || fallbackColor || \"\",\n style: style.getPropertyValue(styleProp) || \"solid\",\n });\n return {\n top: read(\"border-top-width\", \"border-top-color\", \"border-top-style\"),\n right: read(\n \"border-right-width\",\n \"border-right-color\",\n \"border-right-style\"\n ),\n bottom: read(\n \"border-bottom-width\",\n \"border-bottom-color\",\n \"border-bottom-style\"\n ),\n left: read(\"border-left-width\", \"border-left-color\", \"border-left-style\"),\n };\n}\n\n/**\n * True when at least two visible sides (width > 0) disagree on color or style —\n * the single frame stroke carries one color and one dash pattern, so either\n * disagreement collapses them onto whichever side happens to win.\n *\n * Uniform borders (and single-visible-side borders) return false and keep the\n * single-stroke fast path, as do borders using a style the per-side painter\n * cannot draw (the 3D styles, which {@link uniformBorder3dStyle} handles, and\n * anything else CSS may add).\n */\nfunction borderSidesRequireDecomposition(sides: BorderSides): boolean {\n const visible = SIDE_KEYS.map((key) => sides[key]).filter((s) => s.width > 0);\n const first = visible[0];\n if (visible.length < 2 || !first) {\n return false;\n }\n if (visible.some((side) => !PAINTABLE_STYLES.includes(side.style))) {\n return false;\n }\n return visible.some(\n (side) => side.color !== first.color || side.style !== first.style\n );\n}\n\n/**\n * The single corner radius shared by all four corners, or `undefined` when they\n * disagree (or there is no meaningful radius). Only the uniform case has a\n * rounded-rect path simple enough to walk dashes along.\n */\nfunction uniformCornerRadius(style: CSSStyleDeclaration): number | undefined {\n const radii = [\n \"border-top-left-radius\",\n \"border-top-right-radius\",\n \"border-bottom-right-radius\",\n \"border-bottom-left-radius\",\n ].map((prop) => Number.parseFloat(style.getPropertyValue(prop) || \"0\"));\n const first = radii[0];\n if (!first || first <= 0.5) {\n return;\n }\n return radii.every((radius) => radius === first) ? first : undefined;\n}\n\n/**\n * True when every visible side is the same `dashed` style, color and width —\n * the only shape the rounded-path dash walker knows how to paint.\n */\nfunction isUniformDashedBorder(sides: BorderSides): boolean {\n const visible = SIDE_KEYS.map((key) => sides[key]).filter((s) => s.width > 0);\n const first = visible[0];\n if (visible.length < 4 || !first || first.style !== \"dashed\") {\n return false;\n }\n return visible.every(\n (side) =>\n side.style === first.style &&\n side.color === first.color &&\n side.width === first.width\n );\n}\n\n/**\n * The 3D style shared by every visible side, or `undefined` when the border is\n * not a uniform 3D one. Mixed styles and mixed colors keep the existing\n * behavior: shading is only well-defined relative to a single declared color,\n * and a per-side mix of 3D and flat styles is BORD-04's problem, not this one.\n */\nfunction uniformBorder3dStyle(sides: BorderSides): Border3dStyle | undefined {\n const visible = SIDE_KEYS.map((key) => sides[key]).filter((s) => s.width > 0);\n const first = visible[0];\n if (visible.length < 2 || !first || !isBorder3dStyle(first.style)) {\n return;\n }\n const style = first.style;\n if (\n !visible.every((side) => side.style === style && side.color === first.color)\n ) {\n return;\n }\n return style;\n}\n\nfunction hasCornerRadius(style: CSSStyleDeclaration): boolean {\n const radii = [\n \"border-top-left-radius\",\n \"border-top-right-radius\",\n \"border-bottom-right-radius\",\n \"border-bottom-left-radius\",\n ];\n return radii.some(\n (prop) => Number.parseFloat(style.getPropertyValue(prop) || \"0\") > 0.5\n );\n}\n\n/**\n * The CSS border trapezoids for a `width`×`height` border-box (INSIDE stroke),\n * for the band between two proportional depths through the border.\n *\n * `from`/`to` are fractions of each side's own width, so `0 → 1` is the whole\n * border (the plain per-side-color case) and `0 → 0.5` / `0.5 → 1` are the\n * outer and inner halves a `groove`/`ridge` bevel needs. Each quad spans its\n * side's slice of the band, and the corner vertices are taken at the band's own\n * depths — which keeps CSS's 45° miters at every depth, including between the\n * two halves. Sides with zero width are omitted.\n */\nfunction bandPoints(params: {\n sides: BorderSides;\n width: number;\n height: number;\n side: SideKey;\n from: number;\n to: number;\n}): ReadonlyArray<readonly [number, number]> {\n const { sides, width, height, side, from, to } = params;\n // Corner miters at the band's outer depth (`f`) and inner depth (`n`).\n const tf = sides.top.width * from;\n const rf = sides.right.width * from;\n const bf = sides.bottom.width * from;\n const lf = sides.left.width * from;\n const tn = sides.top.width * to;\n const rn = sides.right.width * to;\n const bn = sides.bottom.width * to;\n const ln = sides.left.width * to;\n\n switch (side) {\n case \"top\":\n return [\n [lf, tf],\n [width - rf, tf],\n [width - rn, tn],\n [ln, tn],\n ];\n case \"right\":\n return [\n [width - rf, tf],\n [width - rf, height - bf],\n [width - rn, height - bn],\n [width - rn, tn],\n ];\n case \"bottom\":\n return [\n [ln, height - bn],\n [width - rn, height - bn],\n [width - rf, height - bf],\n [lf, height - bf],\n ];\n default:\n return [\n [lf, tf],\n [ln, tn],\n [ln, height - bn],\n [lf, height - bf],\n ];\n }\n}\n\nfunction bandQuads(params: {\n sides: BorderSides;\n width: number;\n height: number;\n from: number;\n to: number;\n colorFor: (side: SideKey) => string;\n}): Array<SideQuad> {\n const { sides, width, height, from, to, colorFor } = params;\n return SIDE_KEYS.filter((side) => sides[side].width > 0).map((side) => ({\n color: colorFor(side),\n points: bandPoints({ sides, width, height, side, from, to }),\n }));\n}\n\n/**\n * Chrome's dash fitting, measured off its raster: every dash is `dashLength`\n * long (`2 × width` for `dashed`, `width` for `dotted`), a dash lands in each\n * corner, and the gaps take up the slack — so the whole number of dashes is\n * chosen to leave a gap as close as possible to its nominal `width`.\n *\n * The exact fit `(length + width) / (dashLength + width)` is the count whose gap\n * would be exactly nominal; the gap shrinks as the count grows, so the winner is\n * whichever side of it lands closer, ties going to the denser one (measured: a\n * 92px side of a 6px `dashed` border takes 6 dashes, where 5 and 6 are equally\n * far off, and 8 dots where 9 would be nearer the nominal count but further from\n * the nominal gap).\n *\n * Returns each dash's `[start, end]` along the side's own axis, or `null` when\n * the side cannot fit two dashes or would need more than the node budget.\n * `length` is the side's *outer* extent, corner to corner — Chrome runs the\n * dashed line across the full box edge and clips it to the mitered trapezoid,\n * which is what {@link clipToHalfPlane} does below.\n */\nfunction fitDashes(\n length: number,\n dashLength: number,\n width: number\n): Array<readonly [number, number]> | null {\n if (dashLength <= 0 || length <= 0) {\n return null;\n }\n const gapFor = (count: number) => (length - count * dashLength) / (count - 1);\n const exact = (length + width) / (dashLength + width);\n const dense = Math.ceil(exact);\n const sparse = Math.max(2, dense - 1);\n const count =\n Math.abs(gapFor(dense) - width) <= Math.abs(gapFor(sparse) - width)\n ? dense\n : sparse;\n if (count < 2 || count > MAX_DASHES_PER_SIDE) {\n return null;\n }\n const gap = gapFor(count);\n if (gap <= 0) {\n return null;\n }\n const runs: Array<readonly [number, number]> = [];\n for (let i = 0; i < count; i++) {\n const start = i * (dashLength + gap);\n runs.push([start, start + dashLength]);\n }\n return runs;\n}\n\n/**\n * Sutherland–Hodgman clip of a convex polygon against the half-plane where\n * `distance` is non-negative, interpolating new vertices on the crossings.\n */\nfunction clipToHalfPlane(\n points: ReadonlyArray<readonly [number, number]>,\n distance: (point: readonly [number, number]) => number\n): Array<readonly [number, number]> {\n const out: Array<readonly [number, number]> = [];\n for (const [index, current] of points.entries()) {\n const previous = points[(index + points.length - 1) % points.length];\n if (!previous) {\n continue;\n }\n const dCurrent = distance(current);\n const dPrevious = distance(previous);\n if (dCurrent >= 0 !== dPrevious >= 0) {\n const t = dPrevious / (dPrevious - dCurrent);\n out.push([\n previous[0] + (current[0] - previous[0]) * t,\n previous[1] + (current[1] - previous[1]) * t,\n ]);\n }\n if (dCurrent >= 0) {\n out.push(current);\n }\n }\n return out;\n}\n\n/** Which axis a side's dashes run along, and how long that run is. */\nfunction sideAxis(\n side: SideKey,\n width: number,\n height: number\n): { index: 0 | 1; length: number } {\n return side === \"top\" || side === \"bottom\"\n ? { index: 0, length: width }\n : { index: 1, length: height };\n}\n\n/** The center of a side's band, across its thickness. */\nfunction sideBandCenter(\n sides: BorderSides,\n width: number,\n height: number,\n side: SideKey\n): number {\n switch (side) {\n case \"top\":\n return sides.top.width / 2;\n case \"bottom\":\n return height - sides.bottom.width / 2;\n case \"left\":\n return sides.left.width / 2;\n default:\n return width - sides.right.width / 2;\n }\n}\n\n/**\n * One side of the border, painted according to its own style: a single\n * trapezoid for `solid`, two thirds-deep trapezoids for `double`, the trapezoid\n * cut into fitted dashes for `dashed`, and a row of round dots for `dotted`.\n *\n * A dash is the side's whole trapezoid clipped to the dash's span, so it keeps\n * CSS's 45° miter wherever it reaches a corner. Dots are drawn as circles\n * instead — Chrome rasterizes `dotted` with round caps — and are left unclipped,\n * since a dot inscribed in the corner square barely crosses the miter.\n */\nfunction sidePieces(\n sides: BorderSides,\n width: number,\n height: number,\n side: SideKey\n): Array<SidePiece> {\n const { color, style, width: sideWidth } = sides[side];\n const band = (from: number, to: number): SideQuad => ({\n color,\n points: bandPoints({ sides, width, height, side, from, to }),\n });\n\n if (style === \"double\") {\n return [band(0, 1 / 3), band(2 / 3, 1)];\n }\n if (style !== \"dashed\" && style !== \"dotted\") {\n return [band(0, 1)];\n }\n\n const axis = sideAxis(side, width, height);\n const dashLength =\n style === \"dashed\" ? sideWidth * DASH_LENGTH_RATIO : sideWidth;\n const runs = fitDashes(axis.length, dashLength, sideWidth);\n if (!runs) {\n return [band(0, 1)];\n }\n\n if (style === \"dotted\") {\n const center = sideBandCenter(sides, width, height, side);\n const radius = sideWidth / 2;\n return runs.map(([start]) => ({\n color,\n radius,\n cx: axis.index === 0 ? start + radius : center,\n cy: axis.index === 0 ? center : start + radius,\n }));\n }\n\n const full = band(0, 1).points;\n return runs\n .map(([start, end]) => ({\n color,\n points: clipToHalfPlane(\n clipToHalfPlane(full, (p) => p[axis.index] - start),\n (p) => end - p[axis.index]\n ),\n }))\n .filter((quad) => quad.points.length >= 3);\n}\n\n/**\n * True when every visible `dashed`/`dotted` side fits inside the node budget.\n *\n * Guards the uniform case: a border the frame's own dash pattern already draws\n * only benefits from being cut into per-side geometry if the geometry actually\n * gets drawn, and {@link fitDashes} falls back to a solid band when it does not.\n */\nfunction everyPatternedSideFits(\n sides: BorderSides,\n width: number,\n height: number\n): boolean {\n return SIDE_KEYS.filter((key) => sides[key].width > 0).every((key) => {\n const side = sides[key];\n if (side.style !== \"dashed\" && side.style !== \"dotted\") {\n return true;\n }\n const dashLength =\n side.style === \"dashed\" ? side.width * DASH_LENGTH_RATIO : side.width;\n const axis = sideAxis(key, width, height);\n return fitDashes(axis.length, dashLength, side.width) !== null;\n });\n}\n\n/**\n * True for a border every visible side of which is the same `dashed` or\n * `dotted` style in the same color. The frame's own dash pattern can draw those\n * — but Figma phases one pattern continuously around the whole box while Chrome\n * fits the dashes to each side, so the two drift apart. Painting the fitted\n * dashes as geometry keeps them in step.\n */\nfunction isUniformPatternedBorder(sides: BorderSides): boolean {\n const visible = SIDE_KEYS.map((key) => sides[key]).filter((s) => s.width > 0);\n const first = visible[0];\n if (visible.length < 2 || !first) {\n return false;\n }\n if (first.style !== \"dashed\" && first.style !== \"dotted\") {\n return false;\n }\n return visible.every(\n (side) => side.style === first.style && side.color === first.color\n );\n}\n\n/** The whole border, painted side by side in each side's declared style. */\nfunction borderPieces(\n sides: BorderSides,\n width: number,\n height: number\n): Array<SidePiece> {\n return SIDE_KEYS.filter((side) => sides[side].width > 0).flatMap((side) =>\n sidePieces(sides, width, height, side)\n );\n}\n\n/**\n * The shaded bands of a 3D border. `inset`/`outset` shade the border as a\n * single band; `groove`/`ridge` cut it in half and shade the halves in opposite\n * directions, so they emit two trapezoids per side.\n */\nfunction border3dQuads(\n sides: BorderSides,\n width: number,\n height: number,\n style: Border3dStyle\n): Array<SideQuad> {\n const halves: ReadonlyArray<{\n half: \"outer\" | \"inner\";\n from: number;\n to: number;\n }> =\n style === \"groove\" || style === \"ridge\"\n ? [\n { half: \"outer\", from: 0, to: 0.5 },\n { half: \"inner\", from: 0.5, to: 1 },\n ]\n : [{ half: \"outer\", from: 0, to: 1 }];\n\n return halves.flatMap(({ half, from, to }) =>\n bandQuads({\n sides,\n width,\n height,\n from,\n to,\n colorFor: (side) =>\n border3dSideColor({ style, side, half, color: sides[side].color }),\n })\n );\n}\n\n/** The SVG path and frame-local bounding box of one painted piece. */\nfunction pieceGeometry(piece: SidePiece): BorderPathPiece | null {\n if (isPath(piece)) {\n return piece;\n }\n if (isDot(piece)) {\n const { cx, cy, radius } = piece;\n // Two half-arcs: SVG cannot close a full circle in a single arc command.\n const path =\n `M${cx - radius} ${cy} ` +\n `A${radius} ${radius} 0 0 1 ${cx + radius} ${cy} ` +\n `A${radius} ${radius} 0 0 1 ${cx - radius} ${cy} Z`;\n return {\n path,\n minX: cx - radius,\n minY: cy - radius,\n spanX: radius * 2,\n spanY: radius * 2,\n };\n }\n\n const first = piece.points[0];\n if (!first) {\n return null;\n }\n const xs = piece.points.map((p) => p[0]);\n const ys = piece.points.map((p) => p[1]);\n const minX = Math.min(...xs);\n const minY = Math.min(...ys);\n const path = `M${first[0]} ${first[1]} ${piece.points\n .slice(1)\n .map((p) => `L${p[0]} ${p[1]}`)\n .join(\" \")} Z`;\n return {\n path,\n minX,\n minY,\n spanX: Math.max(...xs) - minX,\n spanY: Math.max(...ys) - minY,\n };\n}\n\nfunction pieceToVectorNode(params: {\n piece: SidePiece;\n guid: FigmaGuid;\n frameGuid: FigmaGuid;\n childIndex: number;\n registerBlob: (blob: FigmaBlob) => number;\n}): FigmaVectorNodeChange | null {\n const { piece, guid, frameGuid, childIndex, registerBlob } = params;\n const fill = cssColorToFigmaColor(piece.color);\n if (!fill) {\n return null;\n }\n\n const geometry = pieceGeometry(piece);\n if (!geometry || geometry.spanX <= 0 || geometry.spanY <= 0) {\n return null;\n }\n const { path, minX, minY } = geometry;\n\n // `normalize` re-origins the network to (0,0); with no target dimensions the\n // network keeps its 1:1 pixel scale, so `figmaSize` is the quad's bbox and we\n // position the node at the bbox's frame-local top-left.\n const scaling = svgPathToVectorNetworkWithScaling(path, { normalize: true });\n const bytes = vectorNetworkToBytes(scaling.vectorNetwork);\n const blobIndex = registerBlob({ bytes: Array.from(bytes) });\n\n return {\n guid,\n phase: \"CREATED\",\n parentIndex: { guid: frameGuid, position: childIndex.toString() },\n type: \"VECTOR\",\n name: \"Border\",\n visible: true,\n opacity: 1,\n size: { x: scaling.figmaSize.x, y: scaling.figmaSize.y },\n transform: {\n m00: 1.0,\n m01: 0.0,\n m02: minX,\n m10: 0.0,\n m11: 1.0,\n m12: minY,\n },\n strokeWeight: 0,\n strokeAlign: \"CENTER\",\n strokePaints: [],\n dashPattern: [],\n fillPaints: [createSolidPaint(fill.color, fill.opacity)],\n vectorData: {\n vectorNetworkBlob: blobIndex,\n normalizedSize: { x: scaling.figmaSize.x, y: scaling.figmaSize.y },\n },\n horizontalConstraint: \"MIN\",\n verticalConstraint: \"MIN\",\n };\n}\n\n/** Paint every piece as its own VECTOR child, or `null` if fewer than two land. */\nfunction toVectorNodes(\n pieces: ReadonlyArray<SidePiece>,\n context: {\n frameGuid: FigmaGuid;\n createGuid: () => FigmaGuid;\n registerBlob: (blob: FigmaBlob) => number;\n }\n): Array<FigmaVectorNodeChange> | null {\n const nodes: Array<FigmaVectorNodeChange> = [];\n for (const piece of pieces) {\n const node = pieceToVectorNode({\n piece,\n guid: context.createGuid(),\n frameGuid: context.frameGuid,\n childIndex: nodes.length,\n registerBlob: context.registerBlob,\n });\n if (node) {\n nodes.push(node);\n }\n }\n return nodes.length >= 2 ? nodes : null;\n}\n\n/**\n * Build per-side border VECTOR children, or `null` to keep the frame's single\n * stroke. Returns `null` when the sides share color and style and no 3D shading\n * applies, when fewer than two sides are visible, or when a corner radius is\n * present (rounded per-side borders are not representable as flat trapezoids).\n */\nexport function decomposePerSideBorder(params: {\n computedStyle: CSSStyleDeclaration;\n width: number;\n height: number;\n frameGuid: FigmaGuid;\n createGuid: () => FigmaGuid;\n registerBlob: (blob: FigmaBlob) => number;\n}): Array<FigmaVectorNodeChange> | null {\n const { computedStyle, width, height, frameGuid, createGuid, registerBlob } =\n params;\n\n const sides = parseBorderSides(computedStyle);\n if (hasCornerRadius(computedStyle)) {\n const radius = uniformCornerRadius(computedStyle);\n const first = sides.top;\n if (!(radius && isUniformDashedBorder(sides))) {\n return null;\n }\n const rounded = roundedDashedBorderPieces({\n boxWidth: width,\n boxHeight: height,\n radius,\n width: first.width,\n });\n return rounded\n ? toVectorNodes(\n rounded.map((piece) => ({ ...piece, color: first.color })),\n { frameGuid, createGuid, registerBlob }\n )\n : null;\n }\n const style3d = uniformBorder3dStyle(sides);\n const patterned =\n isUniformPatternedBorder(sides) &&\n everyPatternedSideFits(sides, width, height);\n if (!(style3d || patterned || borderSidesRequireDecomposition(sides))) {\n return null;\n }\n const pieces = style3d\n ? border3dQuads(sides, width, height, style3d)\n : borderPieces(sides, width, height);\n\n return toVectorNodes(pieces, { frameGuid, createGuid, registerBlob });\n}\n","import type { Position } from \"../../dom\";\nimport { isElementNode, isTextEmpty, isTextNode } from \"../../dom\";\nimport type { InferredChildStack } from \"../../layout/infer\";\nimport { inferAutoLayout } from \"../../layout/infer\";\nimport { getNodeNameFromElement } from \"../../naming\";\nimport {\n cssBackdropFilterToFigmaEffects,\n cssFilterToFigmaEffects,\n} from \"../../styles/blur\";\nimport type { BorderProperties, DoubleBorderSpec } from \"../../styles/border\";\nimport { parseBorderFromComputedStyle } from \"../../styles/border\";\nimport { createSolidPaint, cssColorToFigmaColor } from \"../../styles/color\";\nimport {\n applyCssColorMatrixFilters,\n hasColorMatrixFilter,\n} from \"../../styles/filter-color\";\nimport { cssBackgroundToFigmaPaints } from \"../../styles/gradient\";\nimport { parseOpacity } from \"../../styles/opacity\";\nimport type { OutlineSpec } from \"../../styles/outline\";\nimport { parseOutlineFromComputedStyle } from \"../../styles/outline\";\nimport {\n cssBoxShadowToFigmaEffects,\n isPureRingShadow,\n ringShadowToStroke,\n} from \"../../styles/shadow\";\nimport type {\n FigmaBlob,\n FigmaEffect,\n FigmaFrameNodeChange,\n FigmaGuid,\n FigmaNodeChange,\n FigmaPaint,\n FigmaVectorNodeChange,\n} from \"../../types\";\nimport type { FigmaSize, FigmaTransform } from \"../../types/core\";\nimport type { ConverterLayout } from \"../../walk\";\nimport { decomposePerSideBorder } from \"./border-decomposition\";\n\ntype PositioningResult = {\n horizontalConstraint?: string;\n verticalConstraint?: string;\n positionOverride?: Position;\n};\n\n/**\n * The element's real CSS containing block for `position: absolute` — the\n * nearest positioned ancestor, which `offsetParent` already resolves\n * correctly (it accounts for `transform`/`filter`/`perspective`/`contain`\n * creating a containing block too, not just `position`). `offsetParent` is\n * `null` both when nothing up the tree is positioned (containing block is\n * the initial containing block, i.e. the viewport/document) and when the\n * element itself is `display: none` (unreachable here — hidden elements are\n * classified \"skip\" before this runs) or `position: fixed` (handled by its\n * own branch below, never reaches this function for `absolute`).\n */\nfunction getAbsoluteContainingBlockRect(element: Element): DOMRect | null {\n const offsetParent = (element as HTMLElement).offsetParent;\n return offsetParent ? offsetParent.getBoundingClientRect() : null;\n}\n\nfunction getPositioningInfo(\n element: Element,\n elementRect: DOMRect,\n computedStyle: CSSStyleDeclaration\n): PositioningResult {\n const position = computedStyle.position;\n const isPositioned = position === \"fixed\" || position === \"absolute\";\n\n if (!isPositioned) {\n return {};\n }\n\n // Get the containing block: the viewport for fixed, the nearest positioned\n // ancestor for absolute (NOT necessarily the DOM parent — a non-positioned\n // wrapper between an absolutely-positioned element and its real containing\n // block is common, e.g. a floating action button nested a few levels below\n // the `position: relative` container it's actually anchored to). For fixed\n // elements we need the viewport of the element's *own* window (e.g. when\n // converting an iframe's body, the iframe has its own innerWidth/innerHeight\n // that differ from the outer window).\n const isFixed = position === \"fixed\";\n const view = element.ownerDocument?.defaultView ?? window;\n const containingRect = isFixed\n ? { top: 0, bottom: view.innerHeight, left: 0, right: view.innerWidth }\n : getAbsoluteContainingBlockRect(element);\n\n if (!containingRect) {\n return {};\n }\n\n // Calculate distances from each edge\n const distanceFromTop = elementRect.top - containingRect.top;\n const distanceFromBottom = containingRect.bottom - elementRect.bottom;\n const distanceFromLeft = elementRect.left - containingRect.left;\n const distanceFromRight = containingRect.right - elementRect.right;\n\n // Anchor to whichever edge is closer\n const verticalConstraint =\n distanceFromBottom < distanceFromTop ? \"MAX\" : \"MIN\";\n const horizontalConstraint =\n distanceFromRight < distanceFromLeft ? \"MAX\" : \"MIN\";\n\n // The emitted Figma node stays nested under its real DOM parent (the walk\n // never reparents a node to match its CSS containing block), so x/y must\n // always be relative to `element.parentElement`'s rect — that part is\n // already correct as plain screen-coordinate subtraction and needs no\n // containing-block math at all (`elementRect.left - parentRect.left` is\n // geometrically exact regardless of what the element's containing block\n // happens to be). `containingRect` above exists only to decide WHICH edge\n // the element is actually anchored to for the MIN/MAX constraint metadata.\n //\n // The one real exception is `fixed`, where the containing block (the\n // viewport) and the DOM parent are two different coordinate spaces by\n // construction — a fixed element's raw `elementRect.top - parentRect.top`\n // places it at its viewport position, which is wrong once the Figma frame\n // represents the full document scroll height (e.g. a bottom navbar ends up\n // stuck mid-frame instead of at the frame's bottom edge). Only `fixed`\n // needs realigning to the far edge of its parent frame so a MAX constraint\n // pins it correctly after paste; `absolute`'s containing block is still a\n // real ancestor element with the same coordinate space as its DOM parent,\n // so its raw parent-relative position never had this problem.\n let positionOverride: Position | undefined;\n if (isFixed) {\n const parentRect = element.parentElement?.getBoundingClientRect();\n if (parentRect) {\n const x =\n horizontalConstraint === \"MAX\"\n ? parentRect.width - elementRect.width - distanceFromRight\n : elementRect.left - parentRect.left;\n const y =\n verticalConstraint === \"MAX\"\n ? parentRect.height - elementRect.height - distanceFromBottom\n : elementRect.top - parentRect.top;\n positionOverride = { x, y };\n }\n }\n\n return { horizontalConstraint, verticalConstraint, positionOverride };\n}\n\n/**\n * A CSS `transform` (rotate/skew/scale) is invisible to `getBoundingClientRect`,\n * which returns the axis-aligned bounding box — so without this the node would\n * be a flattened, enlarged rectangle. Rebuild the node from its untransformed\n * size (`offsetWidth/Height`) plus a Figma matrix derived from the computed\n * transform, positioning the node's center at the element's center. Assumes the\n * default `transform-origin: center` (the untransformed center equals the\n * rendered bbox center for center-origin transforms).\n *\n * Leaf elements only: in Figma the matrix applies to the whole subtree, but\n * descendants are measured in transformed screen space (bounding-box diffs),\n * so a matrix on a parent would transform them a second time and their sizes\n * would be the enlarged bboxes. Until descendants are measured in the parent's\n * untransformed local space, a transformed element with convertible children\n * keeps the flattened-bbox behavior.\n */\nfunction getTransformOverride(\n element: Element,\n rect: DOMRect,\n computedStyle: CSSStyleDeclaration\n): { size: FigmaSize; transform: FigmaTransform } | null {\n const value = computedStyle.transform;\n if (!value || value === \"none\") {\n return null;\n }\n const matrix = new DOMMatrix(value);\n const { a, b, c, d } = matrix;\n // Pure translation (or identity) is already handled by positioning.\n if (!matrix.is2D || (a === 1 && b === 0 && c === 0 && d === 1)) {\n return null;\n }\n for (const child of element.childNodes) {\n if (isElementNode(child) || (isTextNode(child) && !isTextEmpty(child))) {\n return null;\n }\n }\n const el = element as HTMLElement;\n const width = el.offsetWidth;\n const height = el.offsetHeight;\n if (!(width && height)) {\n return null;\n }\n const parentRect = element.parentElement?.getBoundingClientRect();\n const parentLeft = parentRect?.left ?? 0;\n const parentTop = parentRect?.top ?? 0;\n const centerX = rect.left + rect.width / 2 - parentLeft;\n const centerY = rect.top + rect.height / 2 - parentTop;\n const halfW = width / 2;\n const halfH = height / 2;\n return {\n size: { x: width, y: height },\n transform: {\n m00: a,\n m01: c,\n m02: centerX - (a * halfW + c * halfH),\n m10: b,\n m11: d,\n m12: centerY - (b * halfW + d * halfH),\n },\n };\n}\n\n/**\n * Drop the single-stroke border fields so the frame paints no stroke — used\n * when a per-side-colored border is decomposed into child vectors instead.\n * Corner-radius fields are preserved (decomposition only runs when there is no\n * radius, so they are already zero/absent).\n */\nfunction withoutFrameStroke(props: BorderProperties): BorderProperties {\n const {\n strokePaints: _paints,\n strokeWeight: _weight,\n borderTopWeight: _top,\n borderRightWeight: _right,\n borderBottomWeight: _bottom,\n borderLeftWeight: _left,\n borderStrokeWeightsIndependent: _independent,\n ...rest\n } = props;\n return { ...rest, strokeWeight: 0, strokePaints: [] };\n}\n\nfunction getFillProperties(element: Element, rect: DOMRect) {\n const parentElement = element.parentElement;\n const parentRect = parentElement?.getBoundingClientRect();\n\n return {\n fillsParentHeight:\n parentRect && Math.abs(rect.height - parentRect.height) < 1,\n fillsParentWidth: parentRect && Math.abs(rect.width - parentRect.width) < 1,\n };\n}\n\ntype Params = {\n guid: FigmaGuid;\n parentGuid: FigmaGuid;\n childIndex: number;\n position: Position;\n layout?: ConverterLayout;\n /** True when the parent frame became an inferred auto-layout stack. */\n parentIsAutoLayout?: boolean;\n /** Fill/stretch overrides computed by the parent stack's inference. */\n childStackSpec?: InferredChildStack;\n /** Set for the converted root element only: the size of the paste-template\n * frame (a VERTICAL stack) that this element is a fill child of. */\n rootFill?: { width: number; height: number };\n /** Allocates guids/blobs for synthetic child nodes — per-side-colored border\n * vectors and a `double` border's inner line. Absent callers skip them. */\n createGuid?: () => FigmaGuid;\n registerBlob?: (blob: FigmaBlob) => number;\n};\n\ntype FrameResult = {\n nodeChange: FigmaFrameNodeChange;\n /** Per-side border trapezoids emitted when the sides differ in color/style;\n * the walker paints them below the frame's real children. */\n borderChildren?: Array<FigmaVectorNodeChange>;\n textGradient?: Array<FigmaPaint>;\n /** Set when the frame became an inferred auto-layout stack, so the walker\n * can tell its children. */\n isAutoLayout: boolean;\n /** Per-child overrides from stack inference, for the walker to hand down. */\n childStackSpecs?: ReadonlyMap<Element, InferredChildStack>;\n /** Reversed flex direction: the walker emits children in visual order. */\n reverseChildren?: boolean;\n /** Synthetic child nodes the walker must emit as children of this frame\n * (e.g. a `double` border's inner line). */\n extraChildren?: Array<FigmaNodeChange>;\n};\n\n/**\n * The inner line of a CSS `double` border, as a stroked child frame inset from\n * the parent frame's edges. The parent's own stroke draws the outer line and\n * its fill shows through the gap; this node adds only the inner line, via an\n * INSIDE stroke over an empty fill. See {@link DoubleBorderSpec}.\n */\nfunction buildDoubleBorderInnerLine(\n guid: FigmaGuid,\n parentGuid: FigmaGuid,\n parentSize: FigmaSize,\n parentCornerRadius: number | undefined,\n spec: DoubleBorderSpec\n): FigmaFrameNodeChange {\n const { inset, lineWeight, strokePaints } = spec;\n const innerRadius =\n parentCornerRadius && parentCornerRadius > inset\n ? parentCornerRadius - inset\n : 0;\n return {\n guid,\n phase: \"CREATED\",\n parentIndex: { guid: parentGuid, position: \"0\" },\n type: \"FRAME\",\n name: \"Border (inner)\",\n visible: true,\n opacity: 1,\n frameMaskDisabled: true,\n size: { x: parentSize.x - inset * 2, y: parentSize.y - inset * 2 },\n transform: { m00: 1, m01: 0, m02: inset, m10: 0, m11: 1, m12: inset },\n stackMode: \"NONE\",\n fillPaints: [],\n strokeAlign: \"INSIDE\",\n strokeJoin: \"MITER\",\n strokeWeight: lineWeight,\n strokePaints,\n ...(innerRadius > 0 && { cornerRadius: innerRadius }),\n effects: [],\n stackHorizontalPadding: 0,\n stackVerticalPadding: 0,\n stackPaddingRight: 0,\n stackPaddingBottom: 0,\n };\n}\n\n/**\n * A CSS `outline` as a child frame that overhangs its parent on every side.\n * The ring's inner edge sits `outline-offset` outside the border box, so the\n * child is grown by `offset + width` per side and draws an INSIDE stroke of\n * `width` over an empty fill — leaving the parent's own stroke free for the\n * border. See {@link OutlineSpec}.\n *\n * CSS keeps a sharp outline sharp and otherwise grows the radius with the ring,\n * so the outer corner radius is the border radius plus the ring's full standoff.\n */\nfunction buildOutlineRing(\n guid: FigmaGuid,\n parentGuid: FigmaGuid,\n parentSize: FigmaSize,\n parentCornerRadius: number | undefined,\n spec: OutlineSpec\n): FigmaFrameNodeChange {\n const { width, offset, strokePaints, dashPattern, strokeCap } = spec;\n const standoff = offset + width;\n const outerRadius =\n parentCornerRadius && parentCornerRadius > 0\n ? parentCornerRadius + standoff\n : 0;\n return {\n guid,\n phase: \"CREATED\",\n parentIndex: { guid: parentGuid, position: \"0\" },\n type: \"FRAME\",\n name: \"Outline\",\n visible: true,\n opacity: 1,\n frameMaskDisabled: true,\n size: {\n x: parentSize.x + standoff * 2,\n y: parentSize.y + standoff * 2,\n },\n transform: {\n m00: 1,\n m01: 0,\n m02: -standoff,\n m10: 0,\n m11: 1,\n m12: -standoff,\n },\n stackMode: \"NONE\",\n fillPaints: [],\n strokeAlign: \"INSIDE\",\n strokeJoin: \"MITER\",\n strokeWeight: width,\n strokePaints,\n ...(dashPattern && { dashPattern }),\n ...(strokeCap && { strokeCap }),\n ...(outerRadius > 0 && { cornerRadius: outerRadius }),\n effects: [],\n stackHorizontalPadding: 0,\n stackVerticalPadding: 0,\n stackPaddingRight: 0,\n stackPaddingBottom: 0,\n };\n}\n\nexport function elementToFrameNodeChange(\n element: Element,\n options: Params\n): FrameResult {\n const {\n guid,\n parentGuid,\n childIndex,\n position,\n layout,\n parentIsAutoLayout,\n childStackSpec,\n rootFill,\n createGuid,\n registerBlob,\n } = options;\n\n // Inferred auto-layout, spread onto the node change last so it overrides\n // `stackMode: \"NONE\"` and the CSS-padding fields (inference folds borders\n // into padding). `null` means \"keep absolute positioning\" — always safe.\n const inferred = layout === \"auto\" ? inferAutoLayout(element) : null;\n\n const rect = element.getBoundingClientRect();\n const computedStyle = window.getComputedStyle(element);\n\n // The measured box, fractions included. Block boxes get integer widths from\n // CSS anyway, so rounding up only ever bit the text-driven fractional case —\n // an inline element sized to its content shipped up to 1px wider than the\n // browser laid it out, which is visible as a displaced border edge.\n const width = rect.width;\n const height = rect.height;\n\n const backgroundImage = computedStyle.backgroundImage;\n const backgroundColor = cssColorToFigmaColor(computedStyle.backgroundColor);\n const backgroundClip = computedStyle.backgroundClip;\n const isTextClipped = backgroundClip === \"text\";\n const overflow =\n computedStyle.overflow ||\n computedStyle.overflowX ||\n +computedStyle.overflowY;\n const hasOverflowHidden = overflow === \"hidden\";\n\n // Parse border information. `doubleBorder` is metadata for the synthetic\n // inner-line child below, not a Figma node field, so keep it out of the\n // properties spread onto the node change.\n const { doubleBorder, ...borderProperties } = parseBorderFromComputedStyle(\n computedStyle,\n { width, height }\n );\n\n const paddingTop = Number.parseFloat(computedStyle.paddingTop || \"0\");\n const paddingRight = Number.parseFloat(computedStyle.paddingRight || \"0\");\n const paddingBottom = Number.parseFloat(computedStyle.paddingBottom || \"0\");\n const paddingLeft = Number.parseFloat(computedStyle.paddingLeft || \"0\");\n\n const boxShadow = computedStyle.boxShadow;\n const backdropFilter = computedStyle.backdropFilter;\n const filter = computedStyle.filter;\n const opacity = parseOpacity(computedStyle.opacity);\n\n const shadowEffects = cssBoxShadowToFigmaEffects(boxShadow);\n const filterEffects = cssFilterToFigmaEffects(filter);\n const backdropEffects = cssBackdropFilterToFigmaEffects(backdropFilter);\n\n // A pure-ring box-shadow (`0 0 0 <spread>`) renders as nothing in Figma as a\n // DROP_SHADOW, but CSS draws a crisp solid ring. Promote the dominant (widest)\n // ring to an OUTSIDE stroke so Figma draws it following the corner radius.\n // A Figma node has a single stroke alignment/weight, so this only applies\n // when the element has no real CSS border (which owns the stroke); rings on\n // bordered elements stay DROP_SHADOWs. Any non-promoted shadows (blur/offset\n // shadows, narrower concentric rings) keep rendering as effects.\n const hasCssBorder = borderProperties.strokePaints.length > 0;\n let ringForStroke: FigmaEffect | null = null;\n if (!hasCssBorder) {\n for (const effect of shadowEffects) {\n if (\n isPureRingShadow(effect) &&\n (ringForStroke === null ||\n (effect.spread ?? 0) > (ringForStroke.spread ?? 0))\n ) {\n ringForStroke = effect;\n }\n }\n }\n const ringStroke = ringForStroke ? ringShadowToStroke(ringForStroke) : null;\n\n // Combine all effects, dropping only the ring promoted to a stroke.\n const effects = [\n ...shadowEffects.filter((effect) => effect !== ringForStroke),\n ...filterEffects,\n ...backdropEffects,\n ];\n\n const { horizontalConstraint, verticalConstraint, positionOverride } =\n getPositioningInfo(element, rect, computedStyle);\n const finalPosition = positionOverride ?? position;\n const transformOverride = getTransformOverride(element, rect, computedStyle);\n const frameSize: FigmaSize = transformOverride\n ? transformOverride.size\n : { x: width, y: height };\n\n // A frame carries a single stroke color, so four different border colors\n // collapse to one. When the sides disagree, decompose the border into a\n // filled trapezoid per side (exact CSS miters) and drop the frame stroke.\n // Skipped for inferred stacks (children would be laid out by the stack) and\n // transformed frames (size differs from the measured rect used for geometry).\n const borderChildren =\n createGuid && registerBlob && !(inferred || transformOverride)\n ? decomposePerSideBorder({\n computedStyle,\n width,\n height,\n frameGuid: guid,\n createGuid,\n registerBlob,\n })\n : null;\n const effectiveBorderProperties = borderChildren\n ? withoutFrameStroke(borderProperties)\n : borderProperties;\n\n const { fillsParentHeight, fillsParentWidth } = getFillProperties(\n element,\n rect\n );\n\n // Fill beats hug — but only when the fill actually resizes the node:\n // Figma keeps RESIZE_TO_FIT on a filled axis whose sizes already agree and\n // normalizes it to FIXED when they disagree (oracle batch-03). Children of\n // inferred stacks agree by construction (fill is only assigned when sizes\n // match), so the sole disagreement source is the converted root element\n // versus the paste-template frame it fills.\n if (inferred && rootFill) {\n const filledVertical =\n Boolean(fillsParentHeight) && Math.abs(rect.height - rootFill.height) > 1;\n const filledHorizontal =\n Boolean(fillsParentWidth) && Math.abs(rect.width - rootFill.width) > 1;\n const primaryIsHorizontal = inferred.stack.stackMode === \"HORIZONTAL\";\n if (\n inferred.stack.stackPrimarySizing === \"RESIZE_TO_FIT\" &&\n (primaryIsHorizontal ? filledHorizontal : filledVertical)\n ) {\n inferred.stack.stackPrimarySizing = \"FIXED\";\n }\n if (\n inferred.stack.stackCounterSizing === \"RESIZE_TO_FIT\" &&\n (primaryIsHorizontal ? filledVertical : filledHorizontal)\n ) {\n inferred.stack.stackCounterSizing = \"FIXED\";\n }\n }\n\n const fillPaints: Array<FigmaPaint> = [];\n let textGradient: Array<FigmaPaint> | undefined;\n\n // Add background-color first (bottom layer). CSS color-matrix filters\n // (grayscale/brightness/contrast/…) have no Figma effect, so bake them into\n // the fill — but only when the element's whole appearance IS that one solid\n // fill (a leaf with no children/text/background-image/border). A filter also\n // recolors content Figma paints separately (children, images, borders), so a\n // partial bake would be wrong; there, leaving it unfiltered is safer.\n if (backgroundColor) {\n const canBakeFilter =\n element.childElementCount === 0 &&\n !element.textContent?.trim() &&\n (!backgroundImage || backgroundImage === \"none\") &&\n borderProperties.strokePaints.length === 0 &&\n hasColorMatrixFilter(filter);\n const fillColor = canBakeFilter\n ? applyCssColorMatrixFilters(backgroundColor.color, filter)\n : backgroundColor.color;\n fillPaints.push(createSolidPaint(fillColor, backgroundColor.opacity));\n }\n\n // Add background-image on top (top layer)\n if (backgroundImage && backgroundImage !== \"none\") {\n const gradientPaints = cssBackgroundToFigmaPaints(backgroundImage, {\n width,\n height,\n });\n\n if (isTextClipped) {\n textGradient = gradientPaints;\n } else {\n fillPaints.push(...gradientPaints);\n }\n }\n\n // If there are shadows but no fills, add an almost transparent white fill\n // This is needed because Figma only shows shadows on frames with visible fills\n if (effects.length > 0 && fillPaints.length === 0) {\n fillPaints.push(createSolidPaint({ r: 1, g: 1, b: 1, a: 0.01 }, 0.01));\n }\n\n const nodeChange: FigmaNodeChange = {\n /* General Info */\n guid,\n phase: \"CREATED\",\n parentIndex: {\n guid: parentGuid,\n position: childIndex.toString(),\n },\n type: \"FRAME\",\n name: getNodeNameFromElement(element),\n visible: true,\n opacity,\n frameMaskDisabled: !hasOverflowHidden,\n\n /* Size and Position */\n size: frameSize,\n transform: transformOverride\n ? transformOverride.transform\n : {\n m00: 1.0,\n m01: 0.0,\n m02: finalPosition.x,\n m10: 0.0,\n m11: 1.0,\n m12: finalPosition.y,\n },\n\n /* Layout */\n stackMode: \"NONE\",\n\n /* Fill, Stroke And Corner Radius */\n fillPaints,\n strokeAlign: \"INSIDE\",\n strokeJoin: \"MITER\",\n ...effectiveBorderProperties,\n // A promoted pure-ring shadow overrides the (absent) border stroke with an\n // OUTSIDE stroke; keeps INSIDE/border strokes untouched when there is none.\n ...(ringStroke ?? {}),\n\n /* Effects */\n effects,\n\n /* Padding */\n stackHorizontalPadding: paddingLeft,\n stackVerticalPadding: paddingTop,\n stackPaddingRight: paddingRight,\n stackPaddingBottom: paddingBottom,\n\n /* Constraints */\n ...(horizontalConstraint && { horizontalConstraint }),\n ...(verticalConstraint && { verticalConstraint }),\n\n /* Auto Layout Child Properties. Inside an inferred stack the legacy fill\n heuristics are replaced by the parent inference's fill/stretch\n decisions (childStackSpec); applying the heuristics there would make\n Figma grow/stretch children and shift the layout. */\n ...(fillsParentHeight &&\n !parentIsAutoLayout && { stackChildPrimaryGrow: 1 }),\n ...(fillsParentWidth &&\n !parentIsAutoLayout && { stackChildAlignSelf: \"STRETCH\" }),\n ...(parentIsAutoLayout ? childStackSpec : undefined),\n\n /* Inferred Auto Layout (overrides stackMode and padding fields above) */\n ...inferred?.stack,\n };\n\n // Synthetic children the frame's single stroke can't express: a `double`\n // border's second concentric line, and an `outline` standing off outside the\n // box. Both are skipped when no guid allocator is available (e.g. the form\n // converter's frame).\n const extraChildren: Array<FigmaNodeChange> = [];\n if (doubleBorder && createGuid) {\n extraChildren.push(\n buildDoubleBorderInnerLine(\n createGuid(),\n guid,\n frameSize,\n borderProperties.cornerRadius,\n doubleBorder\n )\n );\n }\n const outline = parseOutlineFromComputedStyle(computedStyle);\n if (outline && createGuid) {\n extraChildren.push(\n buildOutlineRing(\n createGuid(),\n guid,\n frameSize,\n borderProperties.cornerRadius,\n outline\n )\n );\n }\n\n return {\n nodeChange,\n ...(borderChildren && { borderChildren }),\n textGradient,\n isAutoLayout: inferred !== null,\n childStackSpecs: inferred?.children,\n reverseChildren: inferred?.reverseChildren,\n ...(extraChildren.length > 0 && { extraChildren }),\n };\n}\n","/**\n * Figma Baseline Builder\n *\n * Creates baseline data structures required by Figma text nodes.\n * Handles both single-line and multi-line text layouts.\n *\n * @module BaselineBuilder\n */\n\nimport type { FontMetrics } from \"../primitives/font\";\nimport type { ProcessedTextLayout } from \"../types\";\n\n/**\n * Figma baseline data structure\n */\nexport type FigmaBaseline = {\n /** Y position of the baseline */\n baseline: number;\n /** Start character index for this baseline */\n characterStart: number;\n /** End character index for this baseline */\n characterEnd: number;\n};\n\n/**\n * Build baselines array for Figma text positioning\n *\n * Creates the baseline data required by Figma for proper text rendering\n * and editing. Handles both single-line and multi-line layouts.\n *\n * @param layout - Processed text layout with glyph positions\n * @param fontSize - Font size in pixels\n * @param alignment - Text alignment ('left', 'center', 'right')\n * @param containerWidth - Container width for alignment (optional)\n * @param metrics - Font metrics for baseline calculations\n * @returns Array of baseline objects for Figma\n *\n * @example\n * ```typescript\n * const baselines = buildBaselines(layout, 16, 'left', 200, fontMetrics);\n * console.log(`Generated ${baselines.length} baselines`);\n * ```\n */\nexport function buildBaselines(\n layout: ProcessedTextLayout,\n fontSize: number,\n alignment: \"left\" | \"center\" | \"right\",\n containerWidth: number | undefined,\n metrics: FontMetrics\n): Array<FigmaBaseline> {\n if (layout.isMultiLine && layout.multiLineLayout) {\n const result = buildMultiLineBaselines(\n layout,\n fontSize,\n alignment,\n containerWidth,\n metrics\n );\n return result;\n }\n const result = buildSingleLineBaseline(\n layout,\n fontSize,\n alignment,\n containerWidth,\n metrics\n );\n return result;\n}\n\n/**\n * Build baselines for multi-line text\n *\n * Creates a baseline for each line in multi-line text layouts.\n * Each baseline corresponds to one line of text with its own character range.\n *\n * @param layout - Multi-line text layout\n * @param fontSize - Font size in pixels\n * @param _alignment - Text alignment (unused in current implementation)\n * @param _containerWidth - Container width (unused in current implementation)\n * @param metrics - Font metrics\n * @returns Array of baselines, one per line\n *\n * @internal\n */\nfunction buildMultiLineBaselines(\n layout: ProcessedTextLayout,\n fontSize: number,\n _alignment: \"left\" | \"center\" | \"right\",\n _containerWidth: number | undefined,\n metrics: FontMetrics\n): Array<FigmaBaseline> {\n const baselines: Array<FigmaBaseline> = [];\n let characterIndex = 0;\n\n if (!layout.multiLineLayout) {\n return []; // No multi-line data available\n }\n\n for (\n let lineIndex = 0;\n lineIndex < layout.multiLineLayout.lines.length;\n lineIndex += 1\n ) {\n const line = layout.multiLineLayout.lines[lineIndex];\n if (!line) {\n continue;\n }\n\n // Calculate baseline Y position for this line\n const lineHeight =\n layout.options.lineHeight ??\n calculateDefaultLineHeight(fontSize, metrics);\n const lineY = lineIndex * lineHeight;\n const baselineOffset = calculateBaselineOffset(\n fontSize,\n metrics,\n layout.options.lineHeight\n );\n const baselineY = lineY + baselineOffset;\n\n // Determine character range for this line. Figma uses [start, end)\n // half-open intervals here — endCharacter is one past the last glyph.\n const lineLength = line.positions.length || 0;\n const characterStart = characterIndex;\n const characterEnd = characterIndex + lineLength;\n\n baselines.push({\n baseline: baselineY,\n characterStart,\n characterEnd,\n });\n\n characterIndex += lineLength;\n }\n\n return baselines;\n}\n\n/**\n * Build baseline for single-line text\n *\n * Creates a single baseline that spans the entire text content.\n * Used for non-wrapping text or text that fits on one line.\n *\n * @param layout - Single-line text layout\n * @param fontSize - Font size in pixels\n * @param _alignment - Text alignment (unused in current implementation)\n * @param _containerWidth - Container width (unused in current implementation)\n * @param metrics - Font metrics\n * @returns Array with single baseline object\n *\n * @internal\n */\nfunction buildSingleLineBaseline(\n layout: ProcessedTextLayout,\n fontSize: number,\n _alignment: \"left\" | \"center\" | \"right\",\n _containerWidth: number | undefined,\n metrics: FontMetrics\n): Array<FigmaBaseline> {\n const baselineY = calculateBaselineOffset(\n fontSize,\n metrics,\n layout.options.lineHeight\n );\n const characterCount = layout.positions.length;\n\n return [\n {\n baseline: baselineY,\n characterStart: 0,\n // Half-open interval — endCharacter is one past the last glyph index,\n // i.e. equal to the character count.\n characterEnd: characterCount,\n },\n ];\n}\n\n/**\n * Calculate baseline offset from the top of the text bounding box\n *\n * Determines the Y position where the text baseline should be positioned\n * relative to the top of the text container.\n *\n * @param fontSize - Font size in pixels\n * @param metrics - Font metrics\n * @returns Baseline offset in pixels from top\n *\n * @example\n * ```typescript\n * const offset = calculateBaselineOffset(16, fontMetrics);\n * // Text baseline will be positioned at containerTop + offset\n * ```\n */\nfunction calculateBaselineOffset(\n fontSize: number,\n metrics: FontMetrics,\n htmlLineHeight?: number\n): number {\n if (htmlLineHeight) {\n // CSS half-leading model: distribute extra leading equally above and below,\n // then place the baseline at halfLeading + ascender\n const ascenderPx = (metrics.ascender / metrics.unitsPerEm) * fontSize;\n const descenderPx = (metrics.descender / metrics.unitsPerEm) * fontSize;\n const textHeight = ascenderPx - descenderPx;\n const halfLeading = (htmlLineHeight - textHeight) / 2;\n return halfLeading + ascenderPx;\n }\n\n // Fallback to font metrics if no HTML line height\n const result = (metrics.baseline / metrics.unitsPerEm) * fontSize;\n return result;\n}\n\n/**\n * Calculate default line height when not specified\n *\n * @param fontSize - Font size in pixels\n * @param metrics - Font metrics\n * @returns Default line height in pixels\n *\n * @internal\n */\nfunction calculateDefaultLineHeight(\n fontSize: number,\n metrics: FontMetrics\n): number {\n // Use font's line height or fall back to 1.2x font size\n const fontLineHeight = (metrics.lineHeight / metrics.unitsPerEm) * fontSize;\n return fontLineHeight > 0 ? fontLineHeight : fontSize * 1.2;\n}\n","/**\n * Figma Character Offsets Builder\n *\n * Creates character offset mappings required by Figma text nodes for\n * text selection, cursor positioning, and editing operations.\n *\n * @module CharacterOffsetsBuilder\n */\n\nimport type { ProcessedTextLayout } from \"../types\";\n\n/**\n * Build character offset mapping for Figma text selection\n *\n * Creates an array of character positions that Figma uses for text editing\n * operations like cursor positioning, text selection, and character indexing.\n *\n * @param layout - Processed text layout with glyph positions\n * @returns Array of character offsets in pixels from the start of each line\n *\n * @example\n * ```typescript\n * const offsets = buildCharacterOffsets(layout);\n * console.log(`Generated ${offsets.length} character offsets`);\n * // offsets[5] gives the X position of the 6th character\n * ```\n */\nexport function buildCharacterOffsets(\n layout: ProcessedTextLayout\n): Array<number> {\n if (layout.isMultiLine && layout.multiLineLayout) {\n return buildMultiLineOffsets(layout);\n }\n return buildSingleLineOffsets(layout);\n}\n\n/**\n * Calculate character offsets for multi-line text\n *\n * For multi-line text, character offsets are calculated relative to the\n * start of each line, not the absolute text start. This allows Figma to\n * properly handle line breaks and text wrapping.\n *\n * @param layout - Multi-line text layout\n * @returns Array of character offsets with line-relative positioning\n *\n * @internal\n */\nfunction buildMultiLineOffsets(layout: ProcessedTextLayout): Array<number> {\n const offsets: Array<number> = [];\n let positionIndex = 0;\n\n if (!layout.multiLineLayout) {\n return []; // No multi-line data available\n }\n\n for (const line of layout.multiLineLayout.lines) {\n // First character of each line starts at X position 0 (relative to line start)\n const lineStartOffset = layout.positions[positionIndex]?.x ?? 0;\n offsets.push(0);\n\n // Calculate relative positions for remaining characters in the line\n for (let j = 1; j < line.positions.length; j += 1) {\n const currentPos = layout.positions[positionIndex + j];\n if (currentPos) {\n const relativeX = currentPos.x - lineStartOffset;\n offsets.push(relativeX);\n }\n }\n\n positionIndex += line.positions.length;\n }\n\n return offsets;\n}\n\n/**\n * Calculate character offsets for single-line text\n *\n * For single-line text, character offsets are simply the X positions\n * of each glyph relative to the text start position.\n *\n * @param layout - Single-line text layout\n * @returns Array of character offsets with absolute positioning\n *\n * @internal\n */\nfunction buildSingleLineOffsets(layout: ProcessedTextLayout): Array<number> {\n const offsets: Array<number> = [];\n const textStartX = layout.positions[0]?.x ?? 0;\n\n // Calculate offset for each character position\n for (const position of layout.positions) {\n const relativeX = position.x - textStartX;\n offsets.push(relativeX);\n }\n\n return offsets;\n}\n","/**\n * Font Decorations Primitives\n *\n * Low-level text decoration processing for creating underlines\n * and other text decorations in Figma-compatible format.\n *\n * @module FontDecorationsPrimitives\n */\n\nimport type { FontMetrics } from \"../primitives/font/metrics\";\nimport type { OpenTypeFont, ProcessedTextLayout } from \"../types\";\n\n/**\n * Decoration rectangle definition\n */\ntype DecorationRect = {\n /** X position in pixels */\n x: number;\n /** Y position in pixels */\n y: number;\n /** Width in pixels */\n w: number;\n /** Height in pixels (thickness) */\n h: number;\n};\n\n/**\n * Complete decoration definition for Figma\n */\nexport type TextDecoration = {\n /** Array of rectangles that make up this decoration */\n rects: Array<DecorationRect>;\n /** Style ID for the decoration */\n styleID: number;\n};\n\n/**\n * Decoration processing options\n */\nexport type DecorationOptions = {\n /** CSS text-decoration-line value */\n decorationType: \"none\" | \"underline\" | \"line-through\" | \"overline\";\n /** Font size in pixels */\n fontSize: number;\n /** Whether to adjust for glyph descent */\n respectGlyphDescent: boolean;\n};\n\n/**\n * Process text decorations for the given layout\n *\n * Generates Figma-compatible decoration data based on text layout\n * and CSS decoration properties. Handles word wrapping, glyph descent,\n * and proper positioning.\n *\n * @param layout - Processed text layout with glyph positions\n * @param font - OpenType font for glyph metrics\n * @param text - Original text string\n * @param options - Decoration processing options\n * @returns Array of decoration objects ready for Figma\n *\n * @example\n * ```typescript\n * const decorations = processTextDecorations(layout, font, \"Hello world\", {\n * decorationType: \"underline\",\n * fontSize: 16,\n * respectGlyphDescent: true\n * });\n * ```\n */\nexport function processTextDecorations(\n layout: ProcessedTextLayout,\n font: OpenTypeFont,\n text: string,\n options: DecorationOptions\n): Array<TextDecoration> {\n if (options.decorationType === \"none\") {\n return [];\n }\n\n const decorations: Array<TextDecoration> = [];\n\n if (options.decorationType === \"underline\") {\n const underlineDecoration = createUnderlineDecoration(\n layout,\n font,\n text,\n options\n );\n if (underlineDecoration) {\n decorations.push(underlineDecoration);\n }\n }\n\n // Future: Add support for line-through, overline, etc.\n\n return decorations;\n}\n\n/**\n * Create underline decoration for text layout\n *\n * Generates underline rectangles based on glyph positions and font metrics.\n * Handles multi-line text with proper positioning and spacing.\n *\n * @param layout - Text layout with glyph positions\n * @param font - OpenType font for metrics\n * @param text - Original text string\n * @param options - Decoration options\n * @returns Underline decoration object or null if not applicable\n *\n * @internal\n */\nfunction createUnderlineDecoration(\n layout: ProcessedTextLayout,\n font: OpenTypeFont,\n text: string,\n options: DecorationOptions\n): TextDecoration | null {\n if (layout.positions.length === 0) {\n return null;\n }\n\n // We need line height for proper positioning - get it from layout options or estimate\n // const lineHeight = layout.options.lineHeight ?? options.fontSize * 1.2;\n const underlineY = calculateUnderlinePosition(\n layout.metrics,\n options.fontSize\n );\n const underlineThickness = calculateUnderlineThickness(options.fontSize);\n\n // Always use multi-line approach as it's more general and handles single lines too\n const rects = createMultiLineUnderlineRects(\n layout,\n underlineY,\n underlineThickness,\n font,\n text,\n options\n );\n\n return rects.length > 0\n ? {\n rects,\n styleID: 0,\n }\n : null;\n}\n\n/**\n * Calculate underline Y position based on font metrics\n *\n * Positions the underline below the baseline, accounting for font metrics\n * and potential glyph descent.\n *\n * @param metrics - Font metrics\n * @param fontSize - Font size in pixels\n * @returns Y position for underline in pixels\n *\n * @internal\n */\nfunction calculateUnderlinePosition(\n _metrics: FontMetrics,\n fontSize: number\n): number {\n // Standard underline position is typically 1-2px below baseline\n // Based on ground truth: baseline=14.106, underline=15.316, diff=1.21px\n // lineHeight=19, fontSize=14, so underline is positioned relative to both\n\n // Either this or we can try with lineHeight, but this seems to work ok\n const underlineOffset = fontSize * 0.086; // ~1.21px for 14px font (matches ground truth)\n\n return underlineOffset;\n}\n\n/**\n * Calculate underline thickness based on font size\n *\n * Standard practice is to make underline thickness proportional to font size,\n * typically around 1/16 to 1/12 of the font size.\n *\n * @param fontSize - Font size in pixels\n * @returns Thickness in pixels\n *\n * @internal\n */\nfunction calculateUnderlineThickness(fontSize: number): number {\n // Based on ground truth: thickness=0.392 for fontSize=14, lineHeight=19\n // This is about fontSize * 0.028 (0.392/14 ≈ 0.028)\n const thickness = fontSize * 0.028;\n\n return Math.max(thickness, 0.25); // Minimum thickness for visibility\n}\n\n/**\n * Create underline rectangles for multi-line text\n *\n * Generates separate underline rectangles for each line of wrapped text,\n * maintaining proper alignment and positioning.\n *\n * @param layout - Multi-line text layout\n * @param underlineY - Base Y position for underline\n * @param thickness - Underline thickness\n * @param font - OpenType font for analysis\n * @param text - Original text\n * @param options - Decoration options\n * @returns Array of underline rectangles\n *\n * @internal\n */\nfunction createMultiLineUnderlineRects(\n layout: ProcessedTextLayout,\n underlineY: number,\n thickness: number,\n _font: OpenTypeFont,\n _text: string,\n options: DecorationOptions\n): Array<DecorationRect> {\n const rects: Array<DecorationRect> = [];\n\n // Handle both single line (no multiLineLayout) and multi-line cases\n if (!layout.multiLineLayout?.lines) {\n // Create a single line from all positions\n const nonSpacePositions = layout.positions.filter(\n (pos) => pos.character !== \" \"\n );\n\n if (nonSpacePositions.length === 0) {\n return rects;\n }\n\n const firstPos = nonSpacePositions[0];\n const lastPos = nonSpacePositions.at(-1);\n\n if (firstPos && lastPos) {\n const lineWidth =\n lastPos.x - firstPos.x + lastPos.advance * options.fontSize;\n const singleLineRect: DecorationRect = {\n x: firstPos.x,\n y: firstPos.y + underlineY,\n w: lineWidth,\n h: thickness,\n };\n\n rects.push(singleLineRect);\n }\n\n return rects;\n }\n\n // Process each line\n for (\n let lineIndex = 0;\n lineIndex < layout.multiLineLayout.lines.length;\n lineIndex += 1\n ) {\n const line = layout.multiLineLayout.lines[lineIndex];\n if (!line) {\n continue;\n }\n\n // Get positions for this line - prefer layout.positions which have correct absolute Y\n // line.positions might be relative, so use layout.positions as primary source\n const linePositions = layout.positions.slice(\n lineIndex === 0\n ? 0\n : layout.multiLineLayout.lines\n .slice(0, lineIndex)\n .reduce((sum, l) => sum + l.characters.length, 0),\n layout.multiLineLayout.lines\n .slice(0, lineIndex + 1)\n .reduce((sum, l) => sum + l.characters.length, 0)\n );\n\n if (linePositions.length === 0) {\n continue;\n }\n\n // Find non-space characters for this line\n const nonSpacePositions = linePositions.filter(\n (pos) => pos.character !== \" \"\n );\n\n if (nonSpacePositions.length === 0) {\n continue;\n }\n\n // Calculate line underline position - add underline offset to the glyph baseline Y\n const firstGlyphY = linePositions[0]?.y ?? 0;\n const lineUnderlineY = firstGlyphY + underlineY;\n\n // Create underline rect for this line\n const firstPos = nonSpacePositions[0];\n const lastPos = nonSpacePositions.at(-1);\n\n if (firstPos && lastPos) {\n // Use the line's reported width from layout instead of calculating from positions\n // This should be more accurate and match the ground truth better\n const lineWidth = line.width;\n\n const lineRect: DecorationRect = {\n x: firstPos.x,\n y: lineUnderlineY,\n w: lineWidth,\n h: thickness,\n };\n\n rects.push(lineRect);\n }\n }\n\n return rects;\n}\n","/**\n * Font Properties Primitives\n *\n * Low-level CSS font property parsing and normalization utilities.\n * Handles conversion of CSS font values to standardized font properties.\n *\n * @module FontPropertiesPrimitives\n */\n\nexport type { FontProperties } from \"./loader\";\n\n/**\n * CSS font weight keyword mappings\n *\n * Maps CSS font-weight keywords to their numeric equivalents\n * according to CSS specifications.\n *\n * @internal\n */\nconst CSS_WEIGHT_KEYWORDS: Record<string, number> = {\n normal: 400,\n bold: 700,\n lighter: 300,\n bolder: 600,\n thin: 100,\n light: 300,\n medium: 500,\n semibold: 600,\n heavy: 800,\n black: 900,\n // Additional weight variations\n ultralight: 100,\n extralight: 200,\n semilight: 350,\n demibold: 600,\n semiheavy: 750,\n extrabold: 800,\n ultrabold: 900,\n};\n\n/**\n * Parse CSS font properties from computed styles\n *\n * Converts CSS font property values to normalized FontProperties object.\n * Handles all CSS font-weight formats (keywords and numeric values),\n * font-family lists, and font-style variations.\n *\n * @param fontFamily - CSS font-family value (comma-separated list)\n * @param fontWeight - CSS font-weight value (keyword or number)\n * @param fontStyle - CSS font-style value (defaults to \"normal\")\n * @returns Normalized font properties ready for font loading\n *\n * @example\n * ```typescript\n * // Parse from computed styles\n * const props1 = parseFontProperties('Nunito, sans-serif', '700', 'italic');\n * // Returns: { family: 'Nunito', weight: 700, italic: true }\n *\n * // Handle keyword weights\n * const props2 = parseFontProperties('Arial', 'bold', 'normal');\n * // Returns: { family: 'Arial', weight: 700, italic: false }\n *\n * // Handle numeric weights\n * const props3 = parseFontProperties('\"Times New Roman\"', 600);\n * // Returns: { family: 'Times New Roman', weight: 600, italic: false }\n * ```\n */\nexport function parseFontProperties(\n fontFamily: string,\n fontWeight: string | number,\n fontStyle: string\n) {\n return {\n family: parseFontFamily(fontFamily),\n weight: parseFontWeight(fontWeight),\n italic: parseFontStyle(fontStyle),\n };\n}\n\n/**\n * Parse CSS font-family value\n *\n * Extracts the first font family from a CSS font-family list,\n * handling quoted names and fallback families correctly.\n *\n * @param fontFamily - CSS font-family value\n * @returns Primary font family name, cleaned of quotes\n *\n * @example\n * ```typescript\n * parseFontFamily('Nunito, sans-serif'); // 'Nunito'\n * parseFontFamily('\"Times New Roman\", serif'); // 'Times New Roman'\n * parseFontFamily('Arial'); // 'Arial'\n * parseFontFamily(''); // 'Nunito' (fallback)\n * ```\n */\nfunction parseFontFamily(fontFamily: string): string {\n if (!fontFamily || fontFamily.trim() === \"\") {\n return \"Nunito\"; // Default fallback\n }\n\n // Split by comma to handle font stacks\n const families = fontFamily.split(\",\");\n\n // Take the first family and clean it up\n const primaryFamily = families[0]?.trim() ?? \"Nunito\";\n\n // Remove quotes (single or double) and extra whitespace\n return primaryFamily.replace(/^[\"']|[\"']$/g, \"\").trim() || \"Nunito\";\n}\n\n/**\n * Parse CSS font-weight value\n *\n * Converts CSS font-weight values (both keywords and numbers) to\n * normalized numeric weight values. Handles all CSS font-weight\n * keywords and validates numeric ranges.\n *\n * @param fontWeight - CSS font-weight value (keyword, number, or string number)\n * @returns Numeric font weight (100-900)\n *\n * @example\n * ```typescript\n * parseFontWeight('normal'); // 400\n * parseFontWeight('bold'); // 700\n * parseFontWeight('600'); // 600\n * parseFontWeight(500); // 500\n * parseFontWeight('invalid'); // 400 (fallback)\n * ```\n */\nfunction parseFontWeight(fontWeight: string | number): number {\n // Handle numeric input\n if (typeof fontWeight === \"number\") {\n return clampFontWeight(fontWeight);\n }\n\n // Handle string input\n if (typeof fontWeight === \"string\") {\n // Try to parse as number first\n const numericWeight = Number.parseInt(fontWeight.trim(), 10);\n if (!Number.isNaN(numericWeight)) {\n return clampFontWeight(numericWeight);\n }\n\n // Handle keyword weights\n const keyword = fontWeight.toLowerCase().trim();\n const keywordWeight = CSS_WEIGHT_KEYWORDS[keyword];\n\n if (keywordWeight !== undefined) {\n return keywordWeight;\n }\n }\n\n // Fallback to normal weight\n return 400;\n}\n\n/**\n * Parse CSS font-style value\n *\n * Determines if font style indicates italic rendering.\n * Handles all CSS font-style values including italic and oblique.\n *\n * @param fontStyle - CSS font-style value\n * @returns True if font should be rendered in italic\n *\n * @example\n * ```typescript\n * parseFontStyle('normal'); // false\n * parseFontStyle('italic'); // true\n * parseFontStyle('oblique'); // true\n * parseFontStyle('oblique 15deg'); // true\n * ```\n */\nfunction parseFontStyle(fontStyle: string): boolean {\n if (!fontStyle || typeof fontStyle !== \"string\") {\n return false;\n }\n\n const style = fontStyle.toLowerCase().trim();\n\n // Handle italic variations\n return style.includes(\"italic\") || style.includes(\"oblique\");\n}\n\n/**\n * Clamp font weight to valid CSS range\n *\n * Ensures font weight values are within the valid CSS range (100-900)\n * and rounds to the nearest valid weight step.\n *\n * @param weight - Raw numeric weight value\n * @returns Clamped and rounded weight value\n *\n * @internal\n */\nfunction clampFontWeight(weight: number): number {\n // Clamp to valid range\n const clamped = Math.max(100, Math.min(900, weight));\n\n // Round to nearest 100 for standard weights\n return Math.round(clamped / 100) * 100;\n}\n","/**\n * CSS Parsing Primitives\n *\n * Low-level CSS property parsing and normalization utilities.\n * Handles extraction and conversion of CSS properties relevant to text rendering.\n *\n * @module CSSParserPrimitives\n */\n\nimport type { FontProperties } from \"../font/properties\";\nimport { parseFontProperties } from \"../font/properties\";\n\n/**\n * CSS line-height value parsing options\n */\ntype LineHeightOptions = {\n /** Font size in pixels for relative calculations */\n fontSize: number;\n /** Root font size in pixels for rem calculations (defaults to 16px) */\n rootFontSize?: number;\n};\n\n/**\n * Parsed CSS properties relevant to text rendering\n */\nexport type ParsedTextProperties = {\n /** Font properties (family, weight, style) */\n font: FontProperties;\n /** Font size in pixels */\n fontSize: number;\n /** Line height in pixels */\n lineHeight: number;\n /** Letter spacing in pixels */\n letterSpacing: number;\n /** Word spacing in pixels */\n wordSpacing: number;\n /** Text alignment */\n textAlign: \"left\" | \"center\" | \"right\";\n /** Text color (CSS color value) */\n color: string;\n /** Element dimensions */\n dimensions: {\n width: number;\n height: number;\n };\n};\n\n/**\n * Parse all text-relevant CSS properties from an HTML element\n *\n * Extracts computed styles and normalizes them to a consistent format\n * suitable for text processing. Handles fallbacks and edge cases.\n *\n * @param element - HTML element to parse\n * @returns Parsed and normalized text properties\n *\n * @example\n * ```typescript\n * const props = parseTextProperties(textElement);\n * console.log(`Font: ${props.font.family} ${props.font.weight}`);\n * console.log(`Size: ${props.fontSize}px, Line height: ${props.lineHeight}px`);\n * ```\n */\nexport function parseTextProperties(element: Element): ParsedTextProperties {\n const computedStyle = window.getComputedStyle(element);\n const rect = element.getBoundingClientRect();\n\n // Parse font size first (needed for relative calculations)\n const fontSize = parseFontSize(computedStyle.fontSize);\n\n // Parse font properties\n const font = parseFontProperties(\n computedStyle.fontFamily,\n computedStyle.fontWeight,\n computedStyle.fontStyle\n );\n\n // Parse line height (depends on font size)\n const lineHeight = parseLineHeight(computedStyle.lineHeight, { fontSize });\n\n // Parse spacing properties\n const letterSpacing = parseLetterSpacing(computedStyle.letterSpacing);\n const wordSpacing = parseWordSpacing(computedStyle.wordSpacing);\n\n // Parse alignment\n const textAlign = parseTextAlign(computedStyle.textAlign);\n\n // Get color\n const color = computedStyle.color || \"rgb(0, 0, 0)\";\n\n return {\n font,\n fontSize,\n lineHeight,\n letterSpacing,\n wordSpacing,\n textAlign,\n color,\n dimensions: {\n width: Math.ceil(rect.width),\n height: Math.ceil(rect.height),\n },\n };\n}\n\n/**\n * Parse CSS font-size value to pixels\n *\n * Converts various CSS font-size formats to pixel values.\n * Handles px, em, rem, %, and keyword values.\n *\n * @param fontSizeValue - CSS font-size value\n * @param baseFontSize - Base font size for relative calculations (defaults to 16px)\n * @returns Font size in pixels\n *\n * @example\n * ```typescript\n * parseFontSize('24px'); // 24\n * parseFontSize('1.5em', 16); // 24\n * parseFontSize('150%', 16); // 24\n * parseFontSize('large'); // 18\n * ```\n */\nfunction parseFontSize(fontSizeValue: string, baseFontSize = 16): number {\n if (!fontSizeValue || fontSizeValue.trim() === \"\") {\n return 16; // Default font size\n }\n\n const value = fontSizeValue.trim();\n\n // Handle pixel values\n const pxMatch = /^(\\d*\\.?\\d+)px$/.exec(value);\n if (pxMatch) {\n return pxMatch[1] ? Number.parseFloat(pxMatch[1]) : 16;\n }\n\n // Handle em values\n const emMatch = /^(\\d*\\.?\\d+)em$/.exec(value);\n if (emMatch) {\n return (Number.parseFloat(emMatch[1] ?? \"1\") || 1) * baseFontSize;\n }\n\n // Handle rem values\n const remMatch = /^(\\d*\\.?\\d+)rem$/.exec(value);\n if (remMatch) {\n return (Number.parseFloat(remMatch[1] ?? \"1\") || 1) * 16; // rem is always relative to root (16px default)\n }\n\n // Handle percentage values\n const percentMatch = /^(\\d*\\.?\\d+)%$/.exec(value);\n if (percentMatch) {\n return (\n ((Number.parseFloat(percentMatch[1] ?? \"100\") || 100) / 100) *\n baseFontSize\n );\n }\n\n // Handle keyword values\n const keywordSizes: Record<string, number> = {\n \"xx-small\": 9,\n \"x-small\": 10,\n small: 13,\n medium: 16,\n large: 18,\n \"x-large\": 24,\n \"xx-large\": 32,\n smaller: baseFontSize * 0.85,\n larger: baseFontSize * 1.2,\n };\n\n if (value in keywordSizes && keywordSizes[value]) {\n return keywordSizes[value];\n }\n\n // Try to parse as a number\n const numValue = Number.parseFloat(value);\n if (!Number.isNaN(numValue) && numValue > 0) {\n return numValue;\n }\n\n // Fallback\n return 16;\n}\n\n/**\n * Parse CSS line-height value to pixels\n *\n * Handles all CSS line-height formats: unitless numbers, pixels,\n * percentages, keywords, em/rem values.\n *\n * @param lineHeightValue - CSS line-height value\n * @param options - Parsing options including font size\n * @returns Line height in pixels\n *\n * @example\n * ```typescript\n * parseLineHeight('1.5', { fontSize: 16 }); // 24\n * parseLineHeight('24px', { fontSize: 16 }); // 24\n * parseLineHeight('150%', { fontSize: 16 }); // 24\n * parseLineHeight('normal', { fontSize: 16 }); // 19.2\n * ```\n */\nfunction parseLineHeight(\n lineHeightValue: string,\n options: LineHeightOptions\n): number {\n const { fontSize, rootFontSize = 16 } = options;\n\n if (!lineHeightValue || lineHeightValue.trim() === \"\") {\n return fontSize * 1.2; // Default line height\n }\n\n const value = lineHeightValue.trim();\n\n // Handle \"normal\" keyword - typically 1.2x font size\n if (value === \"normal\") {\n return fontSize * 1.2;\n }\n\n // Handle unitless numbers (multipliers)\n const unitlessMatch = /^(\\d*\\.?\\d+)$/.exec(value);\n if (unitlessMatch) {\n const multiplier = unitlessMatch[1]\n ? Number.parseFloat(unitlessMatch[1])\n : 1;\n if (!Number.isNaN(multiplier)) {\n return fontSize * multiplier;\n }\n }\n\n // Handle pixels\n const pxMatch = /^(\\d*\\.?\\d+)px$/.exec(value);\n if (pxMatch) {\n const pixels = pxMatch[1] ? Number.parseFloat(pxMatch[1]) : 1;\n if (!Number.isNaN(pixels)) {\n return pixels;\n }\n }\n\n // Handle percentages\n const percentMatch = /^(\\d*\\.?\\d+)%$/.exec(value);\n if (percentMatch) {\n const percent = percentMatch[1] ? Number.parseFloat(percentMatch[1]) : 1;\n if (!Number.isNaN(percent)) {\n return fontSize * (percent / 100);\n }\n }\n\n // Handle em units\n const emMatch = /^(\\d*\\.?\\d+)em$/.exec(value);\n if (emMatch) {\n const emValue = emMatch[1] ? Number.parseFloat(emMatch[1]) : 1;\n if (!Number.isNaN(emValue)) {\n return fontSize * emValue;\n }\n }\n\n // Handle rem units\n const remMatch = /^(\\d*\\.?\\d+)rem$/.exec(value);\n if (remMatch) {\n const remValue = remMatch[1] ? Number.parseFloat(remMatch[1]) : 1;\n if (!Number.isNaN(remValue)) {\n return rootFontSize * remValue;\n }\n }\n\n // Fallback: try to parse as a number and use as multiplier\n const fallbackNumber = Number.parseFloat(value);\n if (!Number.isNaN(fallbackNumber)) {\n return fontSize * fallbackNumber;\n }\n\n // Final fallback: return normal line height\n return fontSize * 1.2;\n}\n\n/**\n * Parse CSS letter-spacing value to pixels\n *\n * @param letterSpacingValue - CSS letter-spacing value\n * @returns Letter spacing in pixels\n */\nfunction parseLetterSpacing(letterSpacingValue: string): number {\n if (!letterSpacingValue || letterSpacingValue === \"normal\") {\n return 0;\n }\n\n const pxMatch = /^(-?\\d*\\.?\\d+)px$/.exec(letterSpacingValue);\n if (pxMatch) {\n return pxMatch[1] ? Number.parseFloat(pxMatch[1]) : 0;\n }\n\n // Handle em values (would need font size, but we'll approximate)\n const emMatch = /^(-?\\d*\\.?\\d+)em$/.exec(letterSpacingValue);\n if (emMatch) {\n return (emMatch[1] ? Number.parseFloat(emMatch[1]) : 0) * 16; // Approximate with 16px base\n }\n\n return 0;\n}\n\n/**\n * Parse CSS word-spacing value to pixels\n *\n * @param wordSpacingValue - CSS word-spacing value\n * @returns Word spacing in pixels\n */\nfunction parseWordSpacing(wordSpacingValue: string): number {\n if (!wordSpacingValue || wordSpacingValue === \"normal\") {\n return 0;\n }\n\n const pxMatch = /^(-?\\d*\\.?\\d+)px$/.exec(wordSpacingValue);\n if (pxMatch) {\n return pxMatch[1] ? Number.parseFloat(pxMatch[1]) : 0;\n }\n\n // Handle em values\n const emMatch = /^(-?\\d*\\.?\\d+)em$/.exec(wordSpacingValue);\n if (emMatch) {\n return (emMatch[1] ? Number.parseFloat(emMatch[1]) : 0) * 16; // Approximate with 16px base\n }\n\n return 0;\n}\n\n/**\n * Parse CSS text-align value\n *\n * @param textAlignValue - CSS text-align value\n * @returns Normalized text alignment\n */\nfunction parseTextAlign(textAlignValue: string): \"left\" | \"center\" | \"right\" {\n if (!textAlignValue) {\n return \"left\";\n }\n\n switch (textAlignValue.toLowerCase().trim()) {\n case \"center\":\n return \"center\";\n case \"right\":\n case \"end\":\n return \"right\";\n default:\n return \"left\";\n }\n}\n","/**\n * Glyph Encoding Primitives\n *\n * Converts fontkit `Path.commands` into Figma's binary glyph blob format.\n * Glyphs are normalized against the em-square so all glyphs in a font share\n * the same coordinate system.\n *\n * @module GlyphEncodingPrimitives\n */\n\nimport type { Path } from \"fontkit\";\n\n/**\n * Glyph encoding configuration\n */\nexport type EncodingOptions = {\n /** Units per EM for normalizing coordinates (defaults to 2048). */\n unitsPerEm?: number;\n};\n\n/** Figma command opcodes for the glyph blob format. */\nconst COMMAND_OPCODES: Record<string, number> = {\n closePath: 0,\n moveTo: 1,\n lineTo: 2,\n quadraticCurveTo: 3,\n bezierCurveTo: 4,\n};\n\n/**\n * Encode a fontkit path command stream as Figma glyph bytes.\n *\n * Coordinates are emitted in normalized em-square units (`value / unitsPerEm`).\n * fontkit produces font-natural Y (positive = up); Figma stores it the same\n * way, so no flip is applied here.\n */\nexport function pathCommandsToGlyphBytes(\n commands: Path[\"commands\"],\n options: EncodingOptions = {}\n): Array<number> {\n if (commands.length === 0) {\n return [0]; // empty path → close command only\n }\n\n const { unitsPerEm = 2048 } = options;\n const bytes: Array<number> = [];\n\n const writeFloat = (value: number): void => {\n const buffer = new ArrayBuffer(4);\n new DataView(buffer).setFloat32(0, value, true);\n const u = new Uint8Array(buffer);\n bytes.push(u[0] ?? 0, u[1] ?? 0, u[2] ?? 0, u[3] ?? 0);\n };\n\n for (const { command, args } of commands) {\n const opcode = COMMAND_OPCODES[command];\n if (opcode === undefined) {\n throw new Error(`Unknown path command: ${command}`);\n }\n bytes.push(opcode);\n for (let i = 0; i < args.length; i += 2) {\n writeFloat((args[i] ?? 0) / unitsPerEm);\n writeFloat((args[i + 1] ?? 0) / unitsPerEm);\n }\n }\n\n return bytes;\n}\n","/**\n * Glyph Processing Primitives\n *\n * Low-level glyph processing operations for converting text characters\n * to Figma-compatible glyph data with proper metrics and positioning.\n *\n * @module GlyphProcessorPrimitives\n */\n\nimport type { FigmaBlob, OpenTypeFont, OpenTypeGlyph } from \"../../types\";\nimport type { FontMetrics, LoadedFont } from \"../font\";\nimport { pathCommandsToGlyphBytes } from \"./encoder\";\n\n/**\n * Processed glyph data with path and byte information.\n *\n * Complete glyph representation ready for Figma text node construction.\n */\ntype GlyphData = {\n /** The character this glyph represents */\n character: string;\n /** Unicode code point */\n unicode: number;\n /** Encoded byte data for Figma blob registration */\n bytes: Array<number>;\n /** Advance width in pixels */\n advance: number;\n /** Registered blob index from registerBlob function */\n registeredBlobIndex: number;\n};\n\n/**\n * Collection of processed glyphs with registered blob indices\n *\n * Maps characters to their complete glyph data, including blob registrations\n * ready for use in Figma text node construction.\n */\nexport type ProcessedGlyphs = {\n /** Map of character to glyph data with registered blob indices */\n glyphDataMap: Map<string, GlyphData>;\n};\n\n/**\n * Glyph processing options\n */\nexport type GlyphProcessingOptions = {\n /** Font size in pixels for advance width calculation */\n fontSize: number;\n /** Whether to process whitespace characters */\n includeWhitespace?: boolean;\n /** Custom character substitutions */\n characterSubstitutions?: Map<string, string>;\n};\n\n/**\n * Extract and process glyphs for a given text string\n *\n * Processes all unique characters in the text string, generating complete\n * glyph data including SVG paths, encoded bytes, and registered blob indices.\n * Handles special characters like spaces and missing glyphs gracefully.\n *\n * @param loadedFont - Font with metrics and OpenType data\n * @param text - Text to process for glyph extraction\n * @param options - Processing options including font size\n * @param registerBlob - Function to register blob data and get index\n * @returns Processed glyph data with registered blob indices\n *\n * @example\n * ```typescript\n * const processed = processGlyphs(loadedFont, \"Hello World!\", {\n * fontSize: 16,\n * includeWhitespace: true\n * }, registerBlob);\n *\n * console.log(`Processed ${processed.glyphDataMap.size} unique glyphs`);\n * // Access specific glyph data\n * const hGlyph = processed.glyphDataMap.get('H');\n * ```\n */\nexport function processGlyphs(\n loadedFont: LoadedFont,\n text: string,\n options: GlyphProcessingOptions,\n registerBlob: (blob: FigmaBlob) => number\n): ProcessedGlyphs {\n const { font, metrics } = loadedFont;\n const {\n fontSize,\n includeWhitespace = true,\n characterSubstitutions,\n } = options;\n\n // Get unique characters, applying substitutions if provided\n const processedText = applyCharacterSubstitutions(\n text,\n characterSubstitutions\n );\n const uniqueChars = [...new Set(processedText)];\n\n const glyphDataMap = new Map<string, GlyphData>();\n\n // Process each unique character and register its blob\n for (const char of uniqueChars) {\n // Skip whitespace if not requested\n if (!includeWhitespace && /\\s/.test(char)) {\n continue;\n }\n\n const glyphData = processSingleGlyph(\n font,\n char,\n metrics,\n fontSize,\n registerBlob\n );\n if (glyphData) {\n glyphDataMap.set(char, glyphData);\n }\n }\n\n return { glyphDataMap };\n}\n\n/**\n * Process a single character into complete glyph data\n *\n * Handles the complete pipeline for a single character:\n * 1. Maps character to font glyph\n * 2. Generates SVG path data\n * 3. Encodes to Figma-compatible bytes\n * 4. Registers blob and stores index\n * 5. Calculates metrics and advance width\n *\n * @param font - OpenType.js font object\n * @param char - Character to process\n * @param metrics - Font metrics for calculations\n * @param fontSize - Font size in pixels for advance width\n * @param registerBlob - Function to register blob data and get index\n * @returns Processed glyph data or null if character not supported\n *\n * @example\n * ```typescript\n * const glyphData = processSingleGlyph(font, 'A', metrics, 16, registerBlob);\n * if (glyphData) {\n * console.log(`Advance width: ${glyphData.advance}px`);\n * }\n * ```\n */\nfunction processSingleGlyph(\n font: OpenTypeFont,\n char: string,\n metrics: FontMetrics,\n fontSize: number,\n registerBlob: (blob: FigmaBlob) => number\n): GlyphData | null {\n const codePoint = char.codePointAt(0);\n if (codePoint === undefined) {\n return null;\n }\n const glyph = font.glyphForCodePoint(codePoint);\n\n // glyph.id 0 is the .notdef glyph for unmapped characters. Treat space as a\n // valid glyph even when the cmap returns .notdef, since we substitute a\n // minimal blob for it anyway.\n if (glyph.id === 0 && char !== \" \") {\n console.warn(\n `No glyph found for character: '${char}' (U+${codePoint.toString(16).toUpperCase()})`\n );\n return null;\n }\n\n if (char === \" \") {\n return createSpaceGlyph(\n char,\n glyph,\n fontSize,\n metrics.unitsPerEm,\n registerBlob\n );\n }\n\n const glyphBytes = pathCommandsToGlyphBytes(glyph.path.commands, {\n unitsPerEm: metrics.unitsPerEm,\n });\n\n if (glyphBytes.length <= 1) {\n // Path was empty (only the close opcode) — skip rather than emit a noop.\n console.warn(`Empty path for character: '${char}'`);\n return null;\n }\n\n const registeredBlobIndex = registerBlob({ bytes: glyphBytes });\n const advance = calculateAdvanceWidth(glyph, fontSize, metrics.unitsPerEm);\n\n return {\n character: char,\n unicode: codePoint,\n bytes: glyphBytes,\n advance,\n registeredBlobIndex,\n };\n}\n\n/**\n * Create glyph data for space character.\n *\n * Spaces don't have visible paths but need a proper advance width and a\n * minimal blob (the lone close opcode) so Figma can render text runs.\n */\nfunction createSpaceGlyph(\n char: string,\n glyph: OpenTypeGlyph,\n fontSize: number,\n unitsPerEm: number,\n registerBlob: (blob: FigmaBlob) => number\n): GlyphData {\n const spaceBytes = [0]; // close command only\n const registeredBlobIndex = registerBlob({ bytes: spaceBytes });\n const advance = calculateAdvanceWidth(glyph, fontSize, unitsPerEm);\n\n return {\n character: char,\n unicode: char.codePointAt(0) ?? 0,\n bytes: spaceBytes,\n advance,\n registeredBlobIndex,\n };\n}\n\n/**\n * Calculate advance width in pixels for a glyph\n *\n * Converts font unit advance width to pixel units at the specified font size.\n * Handles cases where advance width might be undefined.\n *\n * @param glyph - OpenType glyph object\n * @param fontSize - Font size in pixels\n * @param unitsPerEm - Font's units per EM value\n * @returns Advance width in pixels\n *\n * @internal\n */\nfunction calculateAdvanceWidth(\n glyph: OpenTypeGlyph,\n fontSize: number,\n unitsPerEm: number\n): number {\n const advanceWidth = glyph.advanceWidth ?? 0;\n return (advanceWidth / unitsPerEm) * fontSize;\n}\n\n/**\n * Apply character substitutions to text\n *\n * Handles character-level replacements before glyph processing.\n * Useful for font fallbacks or special character handling.\n *\n * @param text - Original text\n * @param substitutions - Optional character substitution map\n * @returns Text with substitutions applied\n *\n * @internal\n */\nfunction applyCharacterSubstitutions(\n text: string,\n substitutions?: Map<string, string>\n): string {\n if (!substitutions) {\n return text;\n }\n\n let result = text;\n for (const [from, to] of substitutions) {\n result = result.replace(new RegExp(from, \"g\"), to);\n }\n\n return result;\n}\n","/**\n * Kerning Processing Primitives\n *\n * Resolves per-pair kerning deltas by asking fontkit to lay out the entire\n * text run with shaping disabled. fontkit applies both the legacy `kern`\n * table and modern GPOS pair-adjustment lookups automatically, so the kern\n * delta between glyphs i and i+1 is `position[i].xAdvance − glyph[i].advanceWidth`.\n *\n * One layout call per run replaces what used to be N pairwise calls.\n */\n\nimport { fontUnitsToPixels } from \"../../primitives/font/metrics\";\nimport type { FontMetrics, OpenTypeFont, OpenTypeGlyph } from \"../../types\";\n\n/**\n * Features we explicitly disable so the layout output is one glyph per code\n * point. Common-ligature features collapse pairs like \"fi\" into a single\n * glyph; contextual alternates and discretionary ligatures do similar things.\n * `kern` stays on (default) so the position deltas reflect kerning.\n */\nconst NO_SHAPING_FEATURES = {\n liga: false,\n dlig: false,\n clig: false,\n hlig: false,\n rlig: false,\n calt: false,\n} as const;\n\n/**\n * Compute per-pair kerning deltas (in pixels) for a text run.\n *\n * Returns an array of length `max(0, glyphs.length - 1)` where index `i` holds\n * the kerning between `glyphs[i]` and `glyphs[i + 1]`. Falls back to all\n * zeros if shaping unexpectedly substituted the input (e.g. unsupported\n * script) or fontkit throws.\n */\nexport function computeKernings(\n font: OpenTypeFont,\n text: string,\n glyphs: ReadonlyArray<OpenTypeGlyph>,\n metrics: FontMetrics,\n fontSize: number\n): Array<number> {\n const result = new Array<number>(Math.max(0, glyphs.length - 1)).fill(0);\n if (glyphs.length < 2) {\n return result;\n }\n\n try {\n const run = font.layout(text, NO_SHAPING_FEATURES);\n // If shaping merged or split clusters, the per-character contract is\n // broken; fall back to no kerning rather than misattributing deltas.\n if (run.glyphs.length !== glyphs.length) {\n return result;\n }\n for (let i = 0; i < run.glyphs.length - 1; i += 1) {\n const laidGlyph = run.glyphs[i];\n const laidPos = run.positions[i];\n if (!(laidGlyph && laidPos)) {\n continue;\n }\n const kernUnits = laidPos.xAdvance - laidGlyph.advanceWidth;\n if (kernUnits === 0) {\n continue;\n }\n result[i] = fontUnitsToPixels(kernUnits, fontSize, metrics.unitsPerEm);\n }\n } catch {\n /* fall through to zeros */\n }\n\n return result;\n}\n","/**\n * Font Scaling Primitives\n *\n * Handles glyph coordinate transformations based on font metrics.\n * Provides functions for scaling coordinates from font units to pixels.\n *\n * @module ScalingPrimitives\n */\n\nimport { fontUnitsToPixels } from \"../../primitives/font/metrics\";\n\nimport type { FontMetrics, OpenTypeGlyph } from \"../../types\";\n\n/**\n * Scale glyph advance width to pixels\n *\n * @param glyph - OpenType.js glyph object\n * @param metrics - Font metrics\n * @param fontSize - Font size in pixels\n * @returns Advance width scaled to pixels\n *\n * @example\n * ```typescript\n * const advance = scaleAdvanceWidth(glyph, fontMetrics, 16);\n * console.log(`Character advance: ${advance}px`);\n * ```\n */\nexport function scaleAdvanceWidth(\n glyph: OpenTypeGlyph,\n metrics: FontMetrics,\n fontSize: number\n): number {\n const advanceWidth = glyph.advanceWidth ?? 0;\n return fontUnitsToPixels(advanceWidth, fontSize, metrics.unitsPerEm);\n}\n","/**\n * Layout Positioning Primitives\n *\n * Low-level text positioning operations including glyph positioning,\n * alignment calculations, and coordinate transformations.\n *\n * @module LayoutPositioningPrimitives\n */\n\nimport type { OpenTypeFont } from \"../../types\";\nimport type { FontMetrics } from \"../font\";\nimport { computeKernings } from \"./kerning\";\nimport { scaleAdvanceWidth } from \"./scaling\";\n\n/**\n * Supported horizontal text alignment options\n */\nexport type HorizontalAlignment = \"left\" | \"center\" | \"right\";\n\n/**\n * Configuration options for text spacing\n */\nexport type SpacingOptions = {\n /** Additional letter spacing in pixels */\n letterSpacing?: number;\n /** Additional word spacing in pixels */\n wordSpacing?: number;\n};\n\n/**\n * Configuration options for text alignment\n */\nexport type AlignmentOptions = {\n /** Horizontal alignment preference */\n horizontal: HorizontalAlignment;\n /** Container width for alignment calculations (optional) */\n containerWidth?: number;\n /** Baseline Y position offset (optional) */\n baselineY?: number;\n};\n\n/**\n * Position data for a single glyph\n */\nexport type GlyphPosition = {\n /** The character this position represents */\n character: string;\n /** Horizontal position in pixels */\n x: number;\n /** Vertical position in pixels */\n y: number;\n /** Advance width for this glyph in pixels */\n advance: number;\n /** Glyph index in the font */\n glyphIndex: number;\n};\n\n/**\n * Result of text alignment operations\n */\nexport type AlignedLayout = {\n /** Array of positioned glyphs with alignment applied */\n positions: Array<GlyphPosition>;\n /** Bounding box of the aligned text */\n bounds: {\n x: number;\n y: number;\n width: number;\n height: number;\n };\n /** Final baseline Y position */\n baseline: number;\n};\n\n/**\n * Calculate positions for a sequence of glyphs\n *\n * Processes text string to determine exact pixel positions for each glyph,\n * including kerning, letter spacing, and advance width calculations.\n *\n * @param font - OpenType.js font object\n * @param text - Text string to position\n * @param metrics - Font metrics for calculations\n * @param fontSize - Font size in pixels\n * @param options - Spacing configuration options\n * @returns Array of positioned glyphs\n *\n * @example\n * ```typescript\n * const positions = calculateGlyphPositions(\n * font,\n * \"Hello\",\n * metrics,\n * 16,\n * { letterSpacing: 2 }\n * );\n * ```\n */\nexport function calculateGlyphPositions(\n font: OpenTypeFont,\n text: string,\n metrics: FontMetrics,\n fontSize: number,\n options: SpacingOptions = {}\n): Array<GlyphPosition> {\n if (!(font && text) || fontSize <= 0) {\n return [];\n }\n\n const { letterSpacing = 0, wordSpacing = 0 } = options;\n const positions: Array<GlyphPosition> = [];\n let currentX = 0;\n\n // Per-character mapping. The rest of the pipeline (`processGlyphs`, blob\n // lookup at `converter.ts:374`) is keyed by character, so shaped output\n // (e.g. an \"fi\" ligature glyph) would be silently corrupted anyway.\n const chars = [...text];\n const glyphs = chars.map((char) =>\n font.glyphForCodePoint(char.codePointAt(0) ?? 0)\n );\n\n // One layout call resolves all pair kernings; index i holds the kern\n // between glyphs[i] and glyphs[i + 1].\n const kernings = computeKernings(font, text, glyphs, metrics, fontSize);\n\n for (let i = 0; i < glyphs.length; i += 1) {\n const glyph = glyphs[i];\n const char = chars[i];\n if (!(glyph && char)) {\n continue;\n }\n\n if (i > 0) {\n currentX += kernings[i - 1] ?? 0;\n }\n\n const advance = scaleAdvanceWidth(glyph, metrics, fontSize);\n\n positions.push({\n character: char,\n x: currentX,\n y: 0, // Y position will be set by alignment or multi-line layout\n advance,\n glyphIndex: glyph.id,\n });\n\n let totalAdvance = advance + letterSpacing;\n if (char === \" \") {\n totalAdvance += wordSpacing;\n }\n currentX += totalAdvance;\n }\n\n return positions;\n}\n\n/**\n * Apply horizontal alignment to positioned glyphs\n *\n * Adjusts the X positions of all glyphs to achieve the desired horizontal\n * alignment within the available container width.\n *\n * @param positions - Array of positioned glyphs\n * @param metrics - Font metrics for baseline calculations\n * @param fontSize - Font size in pixels\n * @param textWidth - Total width of the text\n * @param options - Alignment configuration\n * @returns Layout with alignment applied\n *\n * @example\n * ```typescript\n * const alignedLayout = createSimpleLayout(positions, metrics, 16, 100, {\n * horizontal: 'center',\n * containerWidth: 200\n * });\n * ```\n */\nexport function createSimpleLayout(\n positions: Array<GlyphPosition>,\n metrics: FontMetrics,\n fontSize: number,\n textWidth: number,\n options: AlignmentOptions,\n preserveY = false // New parameter to preserve existing y coordinates\n): AlignedLayout {\n const { horizontal, containerWidth, baselineY } = options;\n\n // Calculate baseline position\n const baseline = baselineY ?? calculateBaselinePosition(metrics, fontSize);\n\n // Calculate horizontal offset for alignment\n const xOffset = getHorizontalOffset(horizontal, textWidth, containerWidth);\n\n // Apply alignment offset to all positions\n const alignedPositions = positions.map((pos) => ({\n ...pos,\n x: pos.x + xOffset,\n y: preserveY ? pos.y : baseline, // Preserve existing y for multi-line, or use baseline for single-line\n }));\n\n // Calculate bounds\n const bounds = calculateLayoutBounds(alignedPositions, metrics, fontSize);\n\n return {\n positions: alignedPositions,\n bounds,\n baseline,\n };\n}\n\n/**\n * Calculate total text width from positioned glyphs\n *\n * @param font - OpenType.js font object\n * @param text - Text string to measure\n * @param metrics - Font metrics\n * @param fontSize - Font size in pixels\n * @param options - Spacing options\n * @returns Total text width in pixels\n */\nexport function calculateTextWidth(\n font: OpenTypeFont,\n text: string,\n metrics: FontMetrics,\n fontSize: number,\n options: SpacingOptions = {}\n): number {\n const positions = calculateGlyphPositions(\n font,\n text,\n metrics,\n fontSize,\n options\n );\n\n if (positions.length === 0) {\n return 0;\n }\n\n const lastPosition = positions.at(-1);\n if (!lastPosition) {\n return 0;\n }\n\n return lastPosition.x + lastPosition.advance;\n}\n\n/**\n * Calculate X offset for horizontal text alignment\n *\n * @param alignment - Desired horizontal alignment\n * @param textWidth - Total width of the text in pixels\n * @param containerWidth - Width of the container (optional)\n * @returns X offset in pixels to achieve the desired alignment\n */\nexport function getHorizontalOffset(\n alignment: HorizontalAlignment,\n textWidth: number,\n containerWidth?: number\n): number {\n if (!containerWidth || textWidth <= 0) {\n return 0;\n }\n\n switch (alignment) {\n case \"left\":\n return 0;\n case \"center\":\n return Math.max(0, (containerWidth - textWidth) / 2);\n case \"right\":\n return Math.max(0, containerWidth - textWidth);\n default:\n return 0;\n }\n}\n\n/**\n * Calculate baseline position for text\n *\n * @param metrics - Font metrics\n * @param fontSize - Font size in pixels\n * @returns Baseline Y position in pixels\n */\nfunction calculateBaselinePosition(\n metrics: FontMetrics,\n fontSize: number\n): number {\n return (metrics.ascender / metrics.unitsPerEm) * fontSize;\n}\n\n/**\n * Calculate bounding box for positioned glyphs\n *\n * @param positions - Array of positioned glyphs\n * @param metrics - Font metrics\n * @param fontSize - Font size in pixels\n * @returns Bounding box coordinates\n */\nfunction calculateLayoutBounds(\n positions: Array<GlyphPosition>,\n metrics: FontMetrics,\n fontSize: number\n): { x: number; y: number; width: number; height: number } {\n if (positions.length === 0) {\n return { x: 0, y: 0, width: 0, height: 0 };\n }\n\n // biome-ignore lint/style/noNonNullAssertion: we just checked that positions is not empty above\n const firstPos = positions[0]!;\n // biome-ignore lint/style/noNonNullAssertion: we just checked that positions is not empty above\n const lastPos = positions.at(-1)!;\n\n const x = firstPos.x;\n const width = lastPos.x + lastPos.advance - firstPos.x;\n\n const ascender = (metrics.ascender / metrics.unitsPerEm) * fontSize;\n const descender = (metrics.descender / metrics.unitsPerEm) * fontSize;\n\n const y = firstPos.y - ascender;\n const height = ascender - descender;\n\n return { x, y, width, height };\n}\n","/**\n * Text Wrapping and Line Breaking Primitives\n *\n * Handles word wrapping, line breaks, and overflow management.\n * This module provides low-level text wrapping operations for multi-line text layouts.\n *\n * @module WrappingPrimitives\n */\n\nimport type { FontMetrics, OpenTypeFont } from \"../../types\";\nimport type {\n GlyphPosition,\n HorizontalAlignment,\n SpacingOptions,\n} from \"./positioning\";\nimport {\n calculateGlyphPositions,\n calculateTextWidth,\n getHorizontalOffset,\n} from \"./positioning\";\n\n/** Extended spacing options with wrapping constraints */\nexport type WrappingOptions = {\n /** Maximum width before wrapping (pixels) */\n maxWidth?: number;\n /** Whether to wrap at word boundaries (default: true) */\n wordWrap?: boolean;\n /** Whether to break long words that don't fit (default: false) */\n breakWords?: boolean;\n /** Horizontal alignment to apply to each line individually */\n alignment?: HorizontalAlignment;\n /** Container width for alignment calculations */\n containerWidth?: number;\n /** Line height in pixels (overrides font metrics calculation if specified) */\n lineHeight?: number;\n /** Tolerance margin for width calculations (default: 1px) - helps prevent unnecessary wrapping */\n wrapTolerance?: number;\n} & SpacingOptions;\n\n/** Data for a single line of text */\ntype LinePosition = {\n /** Text content of this line */\n characters: string;\n /** Positioned glyphs within this line */\n positions: Array<GlyphPosition>;\n /** Total width of this line */\n width: number;\n /** Height of this line */\n height: number;\n /** Baseline position within this line */\n baseline: number;\n};\n\n/**\n * Calculate baseline position using the CSS half-leading model.\n *\n * Distributes extra leading (lineHeight − textHeight) equally above and below\n * the text, then places the baseline at halfLeading + ascender.\n * This matches how browsers and Figma position text within a line box.\n */\nfunction calculateBaselineFromLineHeight(\n lineHeight: number,\n metrics: FontMetrics,\n fontSize: number\n): number {\n const ascenderPx = (metrics.ascender / metrics.unitsPerEm) * fontSize;\n const descenderPx = (metrics.descender / metrics.unitsPerEm) * fontSize;\n const textHeight = ascenderPx - descenderPx;\n const halfLeading = (lineHeight - textHeight) / 2;\n return halfLeading + ascenderPx;\n}\n\n/** Complete multi-line text layout with overflow information */\nexport type MultiLineLayout = {\n /** Array of line data */\n lines: Array<LinePosition>;\n /** Maximum width across all lines */\n totalWidth: number;\n /** Total height of all lines */\n totalHeight: number;\n /** Overflow status and clipped content */\n overflow: {\n horizontal: boolean;\n vertical: boolean;\n clippedText?: string;\n };\n};\n\n/**\n * Break text into tokens (words, spaces, newlines) for wrapping analysis\n *\n * @param text - Input text to tokenize\n * @returns Array of tokens with type and content\n *\n * @internal\n */\nfunction tokenizeText(\n text: string\n): Array<{ type: \"word\" | \"space\" | \"newline\"; content: string }> {\n const tokens: Array<{ type: \"word\" | \"space\" | \"newline\"; content: string }> =\n [];\n let currentWord = \"\";\n\n for (const char of text) {\n if (char === \" \") {\n // biome-ignore lint/nursery/noUnnecessaryConditions: false positive\n if (currentWord) {\n tokens.push({ type: \"word\", content: currentWord });\n currentWord = \"\";\n }\n tokens.push({ type: \"space\", content: char });\n } else if (char === \"\\n\") {\n // biome-ignore lint/nursery/noUnnecessaryConditions: false positive\n if (currentWord) {\n tokens.push({ type: \"word\", content: currentWord });\n currentWord = \"\";\n }\n tokens.push({ type: \"newline\", content: char });\n } else {\n currentWord += char;\n }\n }\n\n // biome-ignore lint/nursery/noUnnecessaryConditions: false positive\n if (currentWord) {\n tokens.push({ type: \"word\", content: currentWord });\n }\n\n return tokens;\n}\n\n/**\n * Calculate width of a text segment\n *\n * @param font - OpenType.js font object\n * @param text - Text segment to measure\n * @param metrics - Font metrics\n * @param fontSize - Font size in pixels\n * @param options - Spacing options\n * @returns Width in pixels\n *\n * @internal\n */\nfunction calculateSegmentWidth(\n font: OpenTypeFont,\n text: string,\n metrics: FontMetrics,\n fontSize: number,\n options: SpacingOptions\n): number {\n return calculateTextWidth(font, text, metrics, fontSize, options);\n}\n\n/**\n * Break a word that's too long to fit on a line\n *\n * @param font - OpenType.js font object\n * @param word - Word to break\n * @param maxWidth - Maximum allowed width\n * @param metrics - Font metrics\n * @param fontSize - Font size in pixels\n * @param options - Spacing options\n * @returns Array of word parts that fit within maxWidth\n *\n * @internal\n */\nfunction breakLongWord(\n font: OpenTypeFont,\n word: string,\n maxWidth: number,\n metrics: FontMetrics,\n fontSize: number,\n options: SpacingOptions\n): Array<string> {\n const parts: Array<string> = [];\n let currentPart = \"\";\n\n for (const char of word) {\n if (!char) {\n continue;\n }\n const testPart = currentPart + char;\n const width = calculateSegmentWidth(\n font,\n testPart,\n metrics,\n fontSize,\n options\n );\n\n if (width <= maxWidth) {\n currentPart = testPart;\n // biome-ignore lint/nursery/noUnnecessaryConditions: false positive\n } else if (currentPart) {\n parts.push(currentPart);\n currentPart = char;\n } else {\n // Even single character doesn't fit - force it anyway\n parts.push(char);\n }\n }\n\n // biome-ignore lint/nursery/noUnnecessaryConditions: false positive\n if (currentPart) {\n parts.push(currentPart);\n }\n\n return parts;\n}\n\n/**\n * Calculate multi-line text layout with wrapping\n *\n * Processes text string and applies word wrapping, line breaking, and overflow handling\n * to create a complete multi-line layout structure.\n *\n * @param font - OpenType.js font object\n * @param text - Text string to layout\n * @param metrics - Font metrics for calculations\n * @param fontSize - Font size in pixels\n * @param options - Wrapping configuration options\n * @returns Complete multi-line layout with line data and overflow information\n *\n * @example\n * ```typescript\n * const layout = calculateWrappedLayout(font, \"Long text...\", metrics, 16, {\n * maxWidth: 200,\n * wordWrap: true,\n * breakWords: false\n * });\n * ```\n */\nexport function calculateWrappedLayout(\n font: OpenTypeFont,\n text: string,\n metrics: FontMetrics,\n fontSize: number,\n options: WrappingOptions = {}\n): MultiLineLayout {\n const {\n maxWidth,\n wordWrap = true,\n breakWords = false,\n letterSpacing = 0,\n lineHeight: customLineHeight,\n wrapTolerance = 2, // Default 2px tolerance\n } = options;\n\n const lines: Array<LinePosition> = [];\n const lineHeight =\n customLineHeight ?? (metrics.lineHeight / metrics.unitsPerEm) * fontSize;\n\n // If no width constraint, treat as single line\n if (maxWidth && wordWrap) {\n // Tokenize text for word-based wrapping\n const tokens = tokenizeText(text);\n let currentLine = \"\";\n let currentLineWidth = 0;\n\n // Process tokens for line breaking\n for (const token of tokens) {\n // Handle explicit newlines\n if (token.type === \"newline\") {\n // Finalize current line\n // biome-ignore lint/nursery/noUnnecessaryConditions: <explanation>\n if (currentLine) {\n const positions = calculateGlyphPositions(\n font,\n currentLine,\n metrics,\n fontSize,\n {\n letterSpacing,\n }\n );\n lines.push({\n characters: currentLine,\n positions,\n width: currentLineWidth,\n height: lineHeight,\n baseline: calculateBaselineFromLineHeight(\n lineHeight,\n metrics,\n fontSize\n ),\n });\n }\n\n currentLine = \"\";\n currentLineWidth = 0;\n continue;\n }\n\n const tokenContent = token.content;\n\n // For words, check if we need to break them\n if (token.type === \"word\" && breakWords) {\n const wordWidth = calculateSegmentWidth(\n font,\n tokenContent,\n metrics,\n fontSize,\n {\n letterSpacing,\n }\n );\n // Apply tolerance when checking if word needs breaking\n if (wordWidth > maxWidth + wrapTolerance) {\n const wordParts = breakLongWord(\n font,\n tokenContent,\n maxWidth + wrapTolerance,\n metrics,\n fontSize,\n {\n letterSpacing,\n }\n );\n\n // Process each part as a separate token\n for (const part of wordParts) {\n const partWidth = calculateSegmentWidth(\n font,\n part,\n metrics,\n fontSize,\n {\n letterSpacing,\n }\n );\n const testLine = currentLine + part;\n const testWidth = calculateSegmentWidth(\n font,\n testLine,\n metrics,\n fontSize,\n {\n letterSpacing,\n }\n );\n\n // Apply tolerance when checking if line would overflow\n // biome-ignore lint/nursery/noUnnecessaryConditions: false positive\n if (currentLine && testWidth > maxWidth + wrapTolerance) {\n // Start new line with this part\n const positions = calculateGlyphPositions(\n font,\n currentLine,\n metrics,\n fontSize,\n {\n letterSpacing,\n }\n );\n lines.push({\n characters: currentLine,\n positions,\n width: currentLineWidth,\n height: lineHeight,\n baseline: calculateBaselineFromLineHeight(\n lineHeight,\n metrics,\n fontSize\n ),\n });\n\n currentLine = part;\n currentLineWidth = partWidth;\n } else {\n currentLine = testLine;\n currentLineWidth = testWidth;\n }\n }\n continue;\n }\n }\n\n // Try adding token to current line\n const testLine = currentLine + tokenContent;\n const testWidth = calculateSegmentWidth(\n font,\n testLine,\n metrics,\n fontSize,\n { letterSpacing }\n );\n\n // Apply tolerance when checking if line would overflow\n // biome-ignore lint/nursery/noUnnecessaryConditions: false positive\n if (currentLine && testWidth > maxWidth + wrapTolerance) {\n // Current line would overflow - finalize it and start new line\n // biome-ignore lint/nursery/noUnnecessaryConditions: false positive\n const positions = calculateGlyphPositions(\n font,\n currentLine,\n metrics,\n fontSize,\n {\n letterSpacing,\n }\n );\n lines.push({\n characters: currentLine,\n positions,\n width: currentLineWidth,\n height: lineHeight,\n baseline: calculateBaselineFromLineHeight(\n lineHeight,\n metrics,\n fontSize\n ),\n });\n\n // Start new line with current token (skip leading spaces)\n if (token.type === \"space\") {\n currentLine = \"\";\n currentLineWidth = 0;\n } else {\n currentLine = tokenContent;\n currentLineWidth = calculateSegmentWidth(\n font,\n tokenContent,\n metrics,\n fontSize,\n {\n letterSpacing,\n }\n );\n }\n } else {\n // Token fits on current line\n currentLine = testLine;\n currentLineWidth = testWidth;\n }\n }\n\n // Finalize last line\n // biome-ignore lint/nursery/noUnnecessaryConditions: false positive\n if (currentLine) {\n const positions = calculateGlyphPositions(\n font,\n currentLine,\n metrics,\n fontSize,\n {\n letterSpacing,\n }\n );\n lines.push({\n characters: currentLine,\n positions,\n width: currentLineWidth,\n height: lineHeight,\n baseline: calculateBaselineFromLineHeight(\n lineHeight,\n metrics,\n fontSize\n ),\n });\n }\n } else {\n const positions = calculateGlyphPositions(font, text, metrics, fontSize, {\n letterSpacing,\n });\n const width =\n positions.length > 0\n ? (positions.at(-1)?.x ?? 0) + (positions.at(-1)?.advance ?? 0)\n : 0;\n\n lines.push({\n characters: text,\n positions,\n width,\n height: lineHeight,\n baseline: calculateBaselineFromLineHeight(lineHeight, metrics, fontSize),\n });\n }\n\n // Calculate total dimensions\n const totalWidth = Math.max(...lines.map((line) => line.width));\n const totalHeight = lines.length * lineHeight;\n\n // Check for overflow (apply tolerance for horizontal overflow check)\n const overflow = {\n horizontal: maxWidth ? totalWidth > maxWidth + wrapTolerance : false,\n vertical: false, // DISABLED: No height clipping - let text wrap naturally and overflow if needed\n clippedText: undefined as string | undefined,\n };\n\n // DISABLED: No height clipping - let text wrap naturally and overflow if needed\n\n return {\n lines,\n totalWidth,\n totalHeight,\n overflow,\n };\n}\n\n/**\n * Apply horizontal alignment to each line individually in a multi-line layout\n *\n * @param layout - Multi-line layout to align\n * @param alignment - Horizontal alignment to apply\n * @param containerWidth - Container width for alignment calculations\n * @returns Multi-line layout with alignment applied to each line\n *\n * @example\n * ```typescript\n * const aligned = applyPerLineAlignment(multiLineLayout, 'center', 300);\n * ```\n */\nexport function applyPerLineAlignment(\n layout: MultiLineLayout,\n alignment: HorizontalAlignment,\n containerWidth?: number\n): MultiLineLayout {\n if (alignment === \"left\" || !containerWidth) {\n return layout; // No alignment needed\n }\n\n const alignedLines = layout.lines.map((line) => {\n const xOffset = getHorizontalOffset(alignment, line.width, containerWidth);\n\n if (xOffset === 0) {\n return line; // No offset needed\n }\n\n const alignedPositions = line.positions.map((pos) => ({\n ...pos,\n x: pos.x + xOffset,\n }));\n\n return {\n ...line,\n positions: alignedPositions,\n };\n });\n\n const result = {\n ...layout,\n lines: alignedLines,\n };\n\n return result;\n}\n\n/**\n * Extract all glyph positions from multi-line layout\n *\n * @param layout - Multi-line layout to extract positions from\n * @returns Array of positioned glyphs with absolute coordinates\n *\n * @example\n * ```typescript\n * const positions = extractAllGlyphPositions(multiLineLayout);\n * console.log(`Total glyphs: ${positions.length}`);\n * ```\n */\nexport function extractAllGlyphPositions(\n layout: MultiLineLayout\n): Array<GlyphPosition> {\n const flatPositions: Array<GlyphPosition> = [];\n\n for (let lineIndex = 0; lineIndex < layout.lines.length; lineIndex += 1) {\n const line = layout.lines[lineIndex];\n if (!line) {\n continue;\n }\n\n // Each line should be positioned below the previous one\n const lineY = lineIndex * line.height;\n\n for (const pos of line.positions) {\n const finalY = lineY + line.baseline;\n\n flatPositions.push({\n ...pos,\n y: finalY, // Position relative to document, not line\n });\n }\n }\n\n return flatPositions;\n}\n","/**\n * Layout Processing Primitives\n *\n * Main entry point for comprehensive text layout processing that coordinates\n * glyph positioning, wrapping, alignment, and scaling operations.\n *\n * @module LayoutProcessor\n */\n\nimport { extractFontMetrics } from \"../../primitives/font\";\nimport type {\n OpenTypeFont,\n ProcessedTextLayout,\n TextLayoutOptions,\n} from \"../../types\";\nimport {\n calculateGlyphPositions,\n calculateTextWidth,\n createSimpleLayout,\n} from \"./positioning\";\nimport type { WrappingOptions } from \"./wrapping\";\nimport {\n applyPerLineAlignment,\n calculateWrappedLayout,\n extractAllGlyphPositions,\n} from \"./wrapping\";\n\n/**\n * Process complete text layout using font metrics and properties\n *\n * Main entry point for comprehensive text layout processing that integrates\n * glyph positioning, wrapping, alignment, and creates the final layout structure.\n *\n * @param font - OpenType.js font object\n * @param text - Text string to layout\n * @param options - Complete layout configuration\n * @returns Fully processed text layout ready for rendering\n *\n * @example\n * ```typescript\n * const layout = processTextLayout(font, \"Hello World\", {\n * fontSize: 16,\n * alignment: 'center',\n * containerWidth: 200,\n * wrapping: { enabled: true, wordWrap: true }\n * });\n * ```\n */\nexport function processTextLayout(\n font: OpenTypeFont,\n text: string,\n options: TextLayoutOptions\n): ProcessedTextLayout {\n // Extract font metrics\n const metrics = extractFontMetrics(font);\n\n // Text wrapping should always be enabled when we have width constraints\n const shouldWrap =\n options.containerWidth && options.wrapping?.enabled !== false;\n\n if (shouldWrap) {\n // Use multi-line wrapping layout\n const wrappingOptions: WrappingOptions = {\n ...options.spacing,\n maxWidth: options.containerWidth,\n wordWrap: options.wrapping?.wordWrap ?? true,\n breakWords: options.wrapping?.breakWords ?? false,\n alignment: options.alignment,\n containerWidth: options.containerWidth,\n lineHeight: options.lineHeight,\n };\n\n let multiLineLayout = calculateWrappedLayout(\n font,\n text,\n metrics,\n options.fontSize,\n wrappingOptions\n );\n\n // Apply per-line alignment if specified\n if (options.alignment && options.alignment !== \"left\") {\n multiLineLayout = applyPerLineAlignment(\n multiLineLayout,\n options.alignment,\n options.containerWidth\n );\n }\n\n // Extract all glyph positions for compatibility\n const positions = extractAllGlyphPositions(multiLineLayout);\n\n // Create layout without additional alignment (already applied per-line)\n // IMPORTANT: preserveY=true to keep multi-line y positions from extractAllGlyphPositions\n const layout = createSimpleLayout(\n positions,\n metrics,\n options.fontSize,\n multiLineLayout.totalWidth,\n {\n horizontal: \"left\", // No additional alignment needed\n containerWidth: options.containerWidth,\n baselineY: options.baselineY,\n },\n true // preserveY: true for multi-line text\n );\n\n return {\n ...layout,\n metrics,\n textWidth: multiLineLayout.totalWidth,\n glyphCount: positions.length,\n options,\n multiLineLayout,\n isMultiLine: true,\n };\n }\n // Use single-line layout\n const positions = calculateGlyphPositions(\n font,\n text,\n metrics,\n options.fontSize,\n options.spacing\n );\n\n // Calculate total text width\n const textWidth = calculateTextWidth(\n font,\n text,\n metrics,\n options.fontSize,\n options.spacing\n );\n\n // Apply horizontal alignment\n const layout = createSimpleLayout(\n positions,\n metrics,\n options.fontSize,\n textWidth,\n {\n horizontal: options.alignment ?? \"left\",\n containerWidth: options.containerWidth,\n baselineY: options.baselineY,\n }\n );\n\n return {\n ...layout,\n metrics,\n textWidth,\n glyphCount: positions.length,\n options,\n isMultiLine: false,\n };\n}\n","import type { Position, Size } from \"../../dom\";\nimport { getElementSize, getTextSize, isTextNode } from \"../../dom\";\nimport type { FontCache } from \"../../font-cache\";\nimport { createSolidPaint, cssColorToFigmaColor } from \"../../styles/color\";\nimport { cssBackgroundToFigmaPaints } from \"../../styles/gradient\";\nimport { parseOpacity } from \"../../styles/opacity\";\nimport { cssTextShadowToFigmaEffects } from \"../../styles/shadow\";\nimport type {\n FigmaBlob,\n FigmaGuid,\n FigmaPaint,\n FigmaTextAlignHorizontal,\n FigmaTextCase,\n FigmaTextDecoration,\n FigmaTextNodeChange,\n} from \"../../types\";\nimport { buildBaselines } from \"./builders/baselines\";\nimport { buildCharacterOffsets } from \"./builders/character-offsets\";\nimport { processTextDecorations } from \"./builders/decorations\";\nimport { parseTextProperties } from \"./primitives/css/parser\";\nimport { processGlyphs } from \"./primitives/glyph/processor\";\nimport { processTextLayout } from \"./primitives/layout/processor\";\n\nconst cssToFigmaTextAlignHorizontalMap: Record<\n string,\n FigmaTextAlignHorizontal\n> = {\n left: \"LEFT\",\n center: \"CENTER\",\n right: \"RIGHT\",\n justify: \"JUSTIFIED\",\n};\n\nconst fontWeightToWidthBufferMap: Record<number, number> = {\n 100: 0,\n 200: 0,\n 300: 0,\n 400: 2,\n 500: 2,\n 600: 2,\n 700: 3,\n 800: 4,\n 900: 5,\n};\n\nfunction getWidthBuffer(fontWeight: number, fontSize: number) {\n const fontWeightBuffer = fontWeightToWidthBufferMap[fontWeight] ?? 0;\n return fontSize > 60 ? fontWeightBuffer + 1 : fontWeightBuffer;\n}\n\nconst cssToFigmaTextDecorationMap: Record<string, FigmaTextDecoration> = {\n none: \"NONE\",\n underline: \"UNDERLINE\",\n};\n\nconst cssToFigmaTextCaseMap: Record<string, FigmaTextCase> = {\n none: \"ORIGINAL\",\n uppercase: \"UPPER\",\n lowercase: \"LOWER\",\n capitalize: \"TITLE\",\n};\n\nfunction applyCssTextTransform(text: string, transform: string): string {\n switch (transform) {\n case \"uppercase\":\n return text.toUpperCase();\n case \"lowercase\":\n return text.toLowerCase();\n case \"capitalize\":\n return text.replace(/(^|\\s)(\\S)/g, (_, sp, c) => sp + c.toUpperCase());\n default:\n return text;\n }\n}\n\n// Detects whether the browser laid out the text on a single visual line.\n// Each visual line produces at least one client rect; we group by `top`\n// (tolerating sub-pixel jitter) to count distinct lines.\nfunction isTextOnSingleLine(node: Node) {\n const range = document.createRange();\n range.selectNodeContents(node);\n\n const rects = Array.from(range.getClientRects()).filter(\n (r) => r.width && r.height\n );\n\n const topJitterTolerance = 1;\n const lineTops: Array<number> = [];\n for (const rect of rects) {\n if (\n !lineTops.some((top) => Math.abs(top - rect.top) < topJitterTolerance)\n ) {\n lineTops.push(rect.top);\n }\n }\n return lineTops.length <= 1;\n}\n\ntype Params = {\n guid: FigmaGuid;\n parentGuid: FigmaGuid;\n childIndex: number;\n position: Position;\n size?: Size;\n textContent?: string;\n registerBlob: (blob: FigmaBlob) => number;\n inheritedProperties?: {\n textGradient?: Array<FigmaPaint>;\n };\n fontCache: FontCache;\n};\n\nexport async function nodeToTextNodeChange(\n node: Element | Text,\n options: Params\n): Promise<FigmaTextNodeChange> {\n const {\n guid,\n parentGuid,\n childIndex,\n position,\n registerBlob,\n size,\n inheritedProperties,\n textContent,\n fontCache,\n } = options;\n const isTextNodeValue = isTextNode(node);\n\n // If the node is a text node, use the parent element for getting the computed style, otherwise use the node itself\n const element = isTextNodeValue ? node.parentElement : node;\n\n if (!element) {\n throw new Error(\"Element not found\");\n }\n\n const computedStyle = window.getComputedStyle(element);\n\n const defaultTextContent = node.textContent?.trim() ?? \"\";\n const rawText = textContent ?? defaultTextContent;\n\n const defaultSize = isTextNodeValue\n ? getTextSize(node)\n : getElementSize(element);\n const nodeSize = size ?? defaultSize;\n\n const baseWidth = nodeSize.width;\n const baseHeight = nodeSize.height;\n\n const fontSize = Number.parseFloat(computedStyle.fontSize || \"16\");\n const fontFamily =\n computedStyle.fontFamily.replace(/[\"']/g, \"\").split(\",\")[0]?.trim() ?? \"\";\n const fontWeight = Number.parseInt(computedStyle.fontWeight, 10);\n const textAlign =\n cssToFigmaTextAlignHorizontalMap[computedStyle.textAlign] ?? \"LEFT\";\n const color = cssColorToFigmaColor(computedStyle.color);\n const textShadowEffects = cssTextShadowToFigmaEffects(\n computedStyle.textShadow\n );\n\n // Check for gradient backgrounds in text (for gradient text effects)\n const backgroundImage =\n computedStyle.backgroundImage || computedStyle.background;\n\n const fillPaints: Array<FigmaPaint> = [];\n\n // Prioritize inherited text gradient from parent with background-clip: text\n if (\n inheritedProperties?.textGradient &&\n inheritedProperties.textGradient.length > 0\n ) {\n fillPaints.push(...inheritedProperties.textGradient);\n }\n // Check for gradients first. Only apply gradient if the color is transparent (that's how text gradient works in CSS)\n else if (backgroundImage && backgroundImage !== \"none\" && color === null) {\n const gradientPaints = cssBackgroundToFigmaPaints(backgroundImage, {\n width: baseWidth,\n height: baseHeight,\n });\n fillPaints.push(...gradientPaints);\n }\n // Add solid color if present and no gradients found\n else if (color) {\n fillPaints.push(createSolidPaint(color.color, color.opacity));\n }\n\n // Check for text decoration on current element and parent elements\n let textDecoration = computedStyle.textDecorationLine || \"none\";\n\n // If no decoration found on current element, recursively check parent elements\n if (textDecoration === \"none\") {\n let parentElement = element.parentElement;\n while (parentElement) {\n const parentStyle = window.getComputedStyle(parentElement);\n const parentDecoration = parentStyle.textDecorationLine || \"none\";\n\n if (parentDecoration !== \"none\") {\n textDecoration = parentDecoration;\n break;\n }\n parentElement = parentElement.parentElement;\n }\n }\n\n const figmaTextDecoration =\n cssToFigmaTextDecorationMap[textDecoration] ?? \"NONE\";\n\n // Parse text transform (text case)\n const textTransform = computedStyle.textTransform || \"none\";\n const figmaTextCase = cssToFigmaTextCaseMap[textTransform] ?? \"ORIGINAL\";\n\n const text = applyCssTextTransform(rawText, textTransform);\n\n // It's generally more accurate to use the actual box height as the line height, but this doesn't work for text on multiple lines,\n // so in that case we use the computed line height.\n const computedLineHeight =\n computedStyle.lineHeight !== \"normal\"\n ? Number.parseFloat(computedStyle.lineHeight)\n : fontSize * 1.2;\n\n const lineHeight = computedLineHeight;\n\n // Whether the browser actually wrapped this text. If it didn't, we must not\n // wrap it in `derivedTextData` either — OpenType.js's metrics differ slightly\n // from the browser's and can spuriously break a line that fits in the DOM.\n const isSingleLine = isTextOnSingleLine(node);\n\n const letterSpacing =\n computedStyle.letterSpacing !== \"normal\"\n ? Number.parseFloat(computedStyle.letterSpacing)\n : 0;\n\n const widthBuffer = getWidthBuffer(fontWeight, fontSize);\n\n // The node ships the browser's measured box. The width buffer is horizontal\n // slack against Figma/OpenType remeasuring the same run a little wider than\n // the browser did, and it applies only to line breaking — see\n // `wrappingContainerWidth` below. Adding it to the emitted size instead put\n // every text node up to a buffer wider than the DOM (a standing\n // `geometry.width` discrepancy) and pushed runs past the content box of a\n // bordered inline parent; the box is transparent, so the slack bought\n // nothing here.\n const adjustedSize = {\n width: baseWidth,\n height: baseHeight,\n };\n\n const adjustedPosition = {\n x: position.x,\n // Need this max because of browser text alignment issues\n y: Math.max(0, position.y),\n };\n\n const { font, ...styles } = parseTextProperties(element);\n\n const loadedFont = await fontCache.get(font);\n\n // Wrap and align within the box this node ships with, NOT the parent\n // element's width: glyph/baseline offsets bake the alignment in, and the\n // box is already positioned at the measured rect — centering against the\n // parent again would double the offset (it did: centered raw text nodes\n // rendered shifted by (parentWidth - textWidth) / 2). Line breaking is\n // unaffected: every browser break also overflows the (narrower) box, and\n // no line exceeds it. The buffer absorbs font-metric differences between\n // the browser and OpenType.js.\n const wrappingContainerWidth = nodeSize.width + widthBuffer;\n\n const layout = processTextLayout(loadedFont.font, text, {\n fontSize: styles.fontSize,\n spacing: { letterSpacing: styles.letterSpacing },\n alignment: styles.textAlign,\n containerWidth: wrappingContainerWidth,\n lineHeight: styles.lineHeight,\n wrapping: {\n enabled: true,\n wordWrap: !isSingleLine,\n breakWords: false,\n },\n });\n\n const glyphs = processGlyphs(\n loadedFont,\n text,\n {\n fontSize: styles.fontSize,\n includeWhitespace: true,\n },\n registerBlob\n );\n\n const baselines = buildBaselines(\n layout,\n styles.fontSize,\n styles.textAlign,\n nodeSize.width,\n loadedFont.metrics\n );\n\n const characterOffsets = buildCharacterOffsets(layout);\n\n // Process text decorations (underlines, etc.) - implement strikethrough later\n const decorationType =\n figmaTextDecoration === \"UNDERLINE\" ? \"underline\" : \"none\";\n\n const decorations = processTextDecorations(layout, loadedFont.font, text, {\n decorationType,\n fontSize: styles.fontSize,\n respectGlyphDescent: true,\n });\n\n // Build textData and derivedTextData\n const result = {\n textData: {\n characters: text,\n lines: [\n {\n lineType: \"PLAIN\" as const,\n styleId: 0,\n indentationLevel: 0,\n sourceDirectionality: \"AUTO\" as const,\n listStartOffset: 0,\n isFirstLineOfList: false,\n },\n ],\n },\n derivedTextData: {\n layoutSize: {\n x: layout.bounds.width,\n y: layout.bounds.height,\n },\n baselines: baselines.map((baseline, index) => {\n let lineWidth = 0;\n let baselineX = 0;\n const lineHeight = styles.lineHeight;\n let lineY = 0;\n const firstCharacter = baseline.characterStart;\n const endCharacter = baseline.characterEnd;\n\n if (layout.isMultiLine && layout.multiLineLayout) {\n // For multi-line text, get width from the specific line\n const line = layout.multiLineLayout.lines[index];\n lineWidth = line ? line.width : 0;\n lineY = index * lineHeight;\n\n // Calculate X position based on alignment\n // Use wrappingContainerWidth to match the container width used for glyph positioning\n if (styles.textAlign !== \"left\" && wrappingContainerWidth) {\n switch (styles.textAlign) {\n case \"center\":\n if (lineWidth < wrappingContainerWidth) {\n baselineX = (wrappingContainerWidth - lineWidth) / 2;\n }\n break;\n case \"right\":\n baselineX = wrappingContainerWidth - lineWidth;\n break;\n default: {\n throw new Error(\"Invalid text align\");\n }\n }\n }\n } else {\n // For single-line text, use the overall bounds width\n lineWidth = layout.bounds.width;\n lineY = 0;\n\n // Calculate X position based on alignment\n // Use wrappingContainerWidth to match the container width used for glyph positioning\n if (\n styles.textAlign !== \"left\" &&\n !layout.isMultiLine &&\n wrappingContainerWidth\n ) {\n switch (styles.textAlign) {\n case \"center\":\n if (lineWidth < wrappingContainerWidth) {\n baselineX = (wrappingContainerWidth - lineWidth) / 2;\n }\n break;\n case \"right\":\n baselineX = wrappingContainerWidth - lineWidth;\n break;\n default: {\n throw new Error(\"Invalid text align\");\n }\n }\n }\n }\n\n return {\n position: {\n x: baselineX,\n y: baseline.baseline,\n },\n width: lineWidth,\n lineY,\n lineHeight,\n lineAscent:\n (loadedFont.metrics.ascender / loadedFont.metrics.unitsPerEm) *\n styles.fontSize,\n firstCharacter,\n endCharacter,\n };\n }),\n glyphs: layout.positions.map((pos, i) => {\n const glyphData = glyphs.glyphDataMap.get(pos.character);\n const glyphPosition = {\n commandsBlob: glyphData?.registeredBlobIndex ?? 0,\n position: {\n x: pos.x,\n y: pos.y,\n },\n fontSize: styles.fontSize,\n firstCharacter: i,\n advance: pos.advance / styles.fontSize,\n rotation: 0.0,\n };\n\n return glyphPosition;\n }),\n fontMetaData: [\n {\n key: {\n family: loadedFont.properties.family,\n style: loadedFont.fontStyleName,\n // Figma writes its FontMetaData entries with an empty postscript\n // even when the top-level fontName carries one. Match that so a\n // round-trip looks identical and Figma's font matching (which is\n // by family + style + weight) takes the same path on both sides.\n postscript: \"\",\n },\n // Intrinsic line-height ratio of the font, NOT the user's chosen\n // line-height in pixels. The user's line-height already lives on\n // `nc.lineHeight` above. See `FontMetrics.lineHeightRatio`.\n fontLineHeight: loadedFont.metrics.lineHeightRatio,\n fontStyle: loadedFont.actualItalic\n ? (\"ITALIC\" as const)\n : (\"NORMAL\" as const),\n fontWeight: loadedFont.actualWeight ?? loadedFont.properties.weight,\n },\n ],\n truncationStartIndex: -1,\n truncatedHeight: -1.0,\n logicalIndexToCharacterOffsetMap: characterOffsets,\n derivedLines: [\n {\n directionality: \"LTR\" as const,\n },\n ],\n ...(decorations.length > 0 && { decorations }),\n },\n };\n\n const nodeChange: FigmaTextNodeChange = {\n /* General Info */\n guid,\n phase: \"CREATED\",\n parentIndex: {\n guid: parentGuid,\n position: childIndex.toString(),\n },\n type: \"TEXT\",\n name: text,\n visible: true,\n opacity: parseOpacity(computedStyle.opacity),\n\n /* Effects */\n ...(textShadowEffects.length > 0 && { effects: textShadowEffects }),\n\n /* Size and Position */\n size: {\n x: adjustedSize.width,\n y: adjustedSize.height,\n },\n transform: {\n m00: 1.0,\n m01: 0.0,\n m02: adjustedPosition.x,\n m10: 0.0,\n m11: 1.0,\n m12: adjustedPosition.y,\n },\n\n /* Text */\n characters: text,\n\n /* Alignment */\n textAlignHorizontal: textAlign,\n textAlignVertical: \"TOP\",\n\n /* Font */\n fontSize,\n lineHeight: {\n value: lineHeight,\n units: \"PIXELS\",\n },\n fontName: {\n family: fontFamily,\n style: loadedFont.fontStyleName,\n postscript: loadedFont.postScriptName,\n },\n letterSpacing: {\n value: letterSpacing,\n units: \"PIXELS\",\n },\n\n /* Text Data */\n textData: result.textData,\n derivedTextData: result.derivedTextData,\n\n /* Stroke */\n strokeWeight: 1.0,\n strokeAlign: \"OUTSIDE\",\n strokeJoin: \"MITER\",\n\n /* Fill */\n fillPaints,\n\n /* Text Decoration */\n ...(figmaTextDecoration !== \"NONE\" && {\n textDecoration: figmaTextDecoration,\n }),\n\n /* Text Case */\n ...(figmaTextCase !== \"ORIGINAL\" && {\n textCase: figmaTextCase,\n }),\n\n /* Other */\n // CSS `font-variant-ligatures: normal` (the default) enables common and\n // contextual ligatures only. Discretionary and historical ligatures are\n // off unless the author opts in — match that here.\n fontVariantCommonLigatures: true,\n fontVariantContextualLigatures: true,\n fontVariantDiscretionaryLigatures: false,\n fontVersion: \"2\",\n textUserLayoutVersion: 4,\n textExplicitLayoutVersion: 1,\n textBidiVersion: 1,\n // Note: we deliberately do not emit `textAutoResize`. The right value\n // depends on whether the source DOM wrapped (`HEIGHT` — lock width,\n // grow height) or rendered on a single line (`WIDTH_AND_HEIGHT` is\n // safe). Emitting `WIDTH_AND_HEIGHT` unconditionally causes Figma to\n // un-wrap multi-line text on re-derivation and clip it against the\n // parent frame. Leaving the field unset gives Figma its default (NONE),\n // which preserves whatever we measured.\n autoRename: true,\n };\n\n return nodeChange;\n}\n","import type { Position } from \"../../dom\";\nimport { getElementSize } from \"../../dom\";\nimport type { FontCache } from \"../../font-cache\";\nimport type {\n FigmaBlob,\n FigmaGuid,\n FigmaNodeChange,\n FigmaPaint,\n} from \"../../types\";\nimport { elementToFrameNodeChange } from \"../frame\";\nimport { nodeToTextNodeChange } from \"../text\";\n\ntype FormElementParams = {\n element: Element;\n guid: FigmaGuid;\n parentGuid: FigmaGuid;\n childIndex: number;\n position: Position;\n registerBlob: (blob: FigmaBlob) => number;\n inheritedProperties?: {\n textGradient?: Array<FigmaPaint>;\n };\n fontCache: FontCache;\n createGuid: () => FigmaGuid;\n};\n\n/**\n * Creates a synthetic div element with all computed styles from the form element\n */\nfunction createStyledDiv(\n element: Element,\n placeholderText: string\n): HTMLDivElement {\n const syntheticDiv = element.ownerDocument.createElement(\"div\");\n const computedStyle = window.getComputedStyle(element);\n\n // Copy all CSS properties from the computed style\n let cssText = \"\";\n for (const property of computedStyle) {\n if (!property) {\n continue;\n }\n const value = computedStyle.getPropertyValue(property);\n\n if (property && value && value !== \"\") {\n cssText += `${property}: ${value}; `;\n }\n }\n\n syntheticDiv.style.cssText = cssText;\n\n // Override properties for proper text rendering\n syntheticDiv.style.setProperty(\"display\", \"block\");\n syntheticDiv.style.setProperty(\"user-select\", \"text\");\n syntheticDiv.style.setProperty(\"pointer-events\", \"none\");\n // Opacity is applied on the parent frame; resetting here avoids it being multiplied.\n syntheticDiv.style.setProperty(\"opacity\", \"1\");\n\n syntheticDiv.textContent = placeholderText;\n\n // Position for measurement\n const inputRect = element.getBoundingClientRect();\n syntheticDiv.style.position = \"absolute\";\n syntheticDiv.style.left = `${inputRect.left}px`;\n syntheticDiv.style.top = `${inputRect.top}px`;\n\n return syntheticDiv;\n}\n\n/**\n * Calculates proper text positioning within a form element\n */\nfunction calculateTextPosition(element: Element): {\n position: Position;\n size: { width: number; height: number };\n} {\n const computedStyle = window.getComputedStyle(element);\n const isTextarea = element.tagName.toLowerCase() === \"textarea\";\n\n // Extract padding and border values\n const paddingLeft = Number.parseInt(computedStyle.paddingLeft || \"0\", 10);\n const paddingTop = Number.parseInt(computedStyle.paddingTop || \"0\", 10);\n const paddingRight = Number.parseInt(computedStyle.paddingRight || \"0\", 10);\n const paddingBottom = Number.parseInt(computedStyle.paddingBottom || \"0\", 10);\n const borderLeftWidth = Number.parseInt(\n computedStyle.borderLeftWidth || \"0\",\n 10\n );\n const borderTopWidth = Number.parseInt(\n computedStyle.borderTopWidth || \"0\",\n 10\n );\n const borderRightWidth = Number.parseInt(\n computedStyle.borderRightWidth || \"0\",\n 10\n );\n const borderBottomWidth = Number.parseInt(\n computedStyle.borderBottomWidth || \"0\",\n 10\n );\n\n // Calculate font metrics\n const fontSize = Number.parseInt(computedStyle.fontSize || \"16\", 10);\n const lineHeight = computedStyle.lineHeight;\n\n let lineHeightPx = fontSize;\n if (lineHeight && lineHeight !== \"normal\") {\n if (lineHeight.endsWith(\"px\")) {\n lineHeightPx = Number.parseInt(lineHeight, 10);\n } else if (lineHeight.includes(\".\") || !lineHeight.endsWith(\"px\")) {\n lineHeightPx = fontSize * Number.parseFloat(lineHeight);\n }\n } else {\n lineHeightPx = fontSize * 1.2;\n }\n\n // Calculate dimensions\n const inputSize = getElementSize(element);\n const contentHeight =\n inputSize.height -\n paddingTop -\n paddingBottom -\n borderTopWidth -\n borderBottomWidth;\n\n // Calculate vertical positioning based on element type\n let textYOffset = 0;\n if (isTextarea) {\n // Textareas start text from the top\n textYOffset = 0;\n } else {\n // Inputs center their text vertically\n textYOffset = (contentHeight - lineHeightPx) / 2;\n }\n\n const position = {\n x: paddingLeft + borderLeftWidth,\n y: paddingTop + borderTopWidth + textYOffset,\n };\n\n const size = {\n width:\n inputSize.width -\n paddingLeft -\n paddingRight -\n borderLeftWidth -\n borderRightWidth,\n height:\n inputSize.height -\n paddingTop -\n paddingBottom -\n borderTopWidth -\n borderBottomWidth,\n };\n\n return { position, size };\n}\n\n/**\n * Converts a form element with placeholder text to Figma nodes\n */\nexport async function elementToFormNodeChange(\n params: FormElementParams\n): Promise<Array<FigmaNodeChange>> {\n const {\n element,\n guid,\n parentGuid,\n childIndex,\n position,\n registerBlob,\n inheritedProperties,\n fontCache,\n createGuid,\n } = params;\n\n const placeholderText = element.getAttribute(\"placeholder\")?.trim();\n if (!placeholderText) {\n throw new Error(\"Form element has no placeholder text\");\n }\n\n const nodeChanges: Array<FigmaNodeChange> = [];\n\n // Create the frame for the form element (border, background, etc.)\n const frameResult = elementToFrameNodeChange(element, {\n guid,\n parentGuid,\n childIndex,\n position,\n });\n\n nodeChanges.push(frameResult.nodeChange);\n\n // Create synthetic div for text styling\n const syntheticDiv = createStyledDiv(element, placeholderText);\n document.body.appendChild(syntheticDiv);\n\n try {\n // Calculate text positioning and sizing\n const { position: textPosition, size: textSize } =\n calculateTextPosition(element);\n\n // Create text node for placeholder\n const textGuid = createGuid();\n const textNodeChange = await nodeToTextNodeChange(syntheticDiv, {\n guid: textGuid,\n parentGuid: guid,\n childIndex: 0,\n position: textPosition,\n size: textSize,\n textContent: placeholderText,\n registerBlob,\n inheritedProperties,\n fontCache,\n });\n\n nodeChanges.push(textNodeChange);\n } finally {\n // Always clean up the synthetic element\n document.body.removeChild(syntheticDiv);\n }\n\n return nodeChanges;\n}\n","import type { Position } from \"../../dom\";\nimport { getElementSize } from \"../../dom\";\nimport { parseOpacity } from \"../../styles/opacity\";\nimport type { FigmaGroupNodeChange, FigmaGuid } from \"../../types\";\n\ntype Params = {\n guid: FigmaGuid;\n parentGuid: FigmaGuid;\n childIndex: number;\n position: Position;\n};\n\nexport function elementToGroupNodeChange(\n element: Element,\n options: Params\n): FigmaGroupNodeChange {\n const { guid, parentGuid, childIndex, position } = options;\n\n const size = getElementSize(element);\n const computedStyle = window.getComputedStyle(element);\n\n const nodeChange: FigmaGroupNodeChange = {\n /* General Info */\n guid,\n phase: \"CREATED\",\n parentIndex: {\n guid: parentGuid,\n position: childIndex.toString(),\n },\n type: \"GROUP\",\n name: element.tagName.toLowerCase(),\n visible: true,\n opacity: parseOpacity(computedStyle.opacity),\n\n /* Size and Position */\n size: {\n x: size.width,\n y: size.height,\n },\n transform: {\n m00: 1.0,\n m01: 0.0,\n m02: position.x,\n m10: 0.0,\n m11: 1.0,\n m12: position.y,\n },\n\n /* Other */\n autoRename: true,\n };\n\n return nodeChange;\n}\n","import type { Position } from \"../../dom\";\nimport type { ImageCache } from \"../../image-cache\";\nimport { parseBorderFromComputedStyle } from \"../../styles/border\";\nimport { parseOpacity } from \"../../styles/opacity\";\nimport { cssBoxShadowToFigmaEffects } from \"../../styles/shadow\";\nimport type {\n FigmaBlob,\n FigmaGuid,\n FigmaNodeChange,\n FigmaRoundedRectangleNodeChange,\n} from \"../../types\";\n\ntype Params = {\n guid: FigmaGuid;\n parentGuid: FigmaGuid;\n childIndex: number;\n position: Position;\n registerBlob: (blob: FigmaBlob) => number;\n imageCache: ImageCache;\n};\n\n/**\n * Maps CSS `object-fit` onto Figma's image scale modes. CSS defaults to `fill`\n * (stretch to the box, ignoring aspect ratio), which is Figma's `STRETCH` — not\n * `FILL`, which is Figma's name for the cover behaviour.\n *\n * `none` and `scale-down` have no Figma equivalent; `FIT` is the closest, since\n * it at least preserves the image's aspect ratio.\n */\nfunction objectFitToScaleMode(objectFit: string): \"FILL\" | \"FIT\" | \"STRETCH\" {\n switch (objectFit.trim()) {\n case \"cover\":\n return \"FILL\";\n case \"contain\":\n case \"none\":\n case \"scale-down\":\n return \"FIT\";\n default:\n return \"STRETCH\";\n }\n}\n\nexport async function elementToImageNodeChange(\n element: HTMLImageElement,\n options: Params\n): Promise<FigmaRoundedRectangleNodeChange> {\n const { guid, parentGuid, childIndex, position, registerBlob, imageCache } =\n options;\n\n const rect = element.getBoundingClientRect();\n const computedStyle = window.getComputedStyle(element);\n\n const width = Math.ceil(rect.width);\n const height = Math.ceil(rect.height);\n\n const boxShadow = computedStyle.boxShadow;\n const effects = cssBoxShadowToFigmaEffects(boxShadow);\n const opacity = parseOpacity(computedStyle.opacity);\n\n // Parse border information (includes border radius)\n const borderProperties = parseBorderFromComputedStyle(computedStyle, {\n width,\n height,\n });\n\n const { hash, bytes } = await imageCache.get(element);\n const blobIndex = registerBlob({ bytes });\n\n const nodeChange: FigmaNodeChange = {\n /* General Info */\n guid,\n phase: \"CREATED\",\n parentIndex: {\n guid: parentGuid,\n position: childIndex.toString(),\n },\n type: \"ROUNDED_RECTANGLE\",\n name: \"Image\",\n visible: true,\n opacity,\n\n /* Size and Position */\n size: {\n x: width,\n y: height,\n },\n transform: {\n m00: 1.0,\n m01: 0.0,\n m02: position.x,\n m10: 0.0,\n m11: 1.0,\n m12: position.y,\n },\n\n /* Stroke and Corner Radius */\n strokeAlign: \"INSIDE\",\n strokeJoin: \"MITER\",\n ...borderProperties,\n\n /* Fill */\n fillPaints: [\n {\n type: \"IMAGE\",\n opacity: 1.0,\n visible: true,\n blendMode: \"NORMAL\",\n transform: {\n m00: 1.0,\n m01: 0.0,\n m02: 0.0,\n m10: 0.0,\n m11: 1.0,\n m12: 0.0,\n },\n image: {\n hash,\n dataBlob: blobIndex,\n },\n // imageThumbnail: {\n // hash: [],\n // name: \"image\",\n // },\n imageScaleMode: objectFitToScaleMode(computedStyle.objectFit),\n // animationFrame: 0,\n // imageShouldColorManage: true,\n // rotation: 0.0,\n // scale: 0.5,\n // originalImageWidth: 3000,\n // originalImageHeight: 2003,\n // thumbHash: [],\n // altText: \"\",\n },\n ],\n\n /* Effects */\n effects,\n\n /* Aspect Ratio */\n targetAspectRatio: {\n value: {\n x: width,\n y: height,\n },\n },\n };\n\n return nodeChange;\n}\n","/**\n * Converts a circle element to an SVG path string.\n *\n * The circle is represented using two semicircular arc commands (A) that together\n * form a complete circle. This approach ensures proper handling of stroke caps\n * and other path-specific properties.\n *\n * @param cx - The x-coordinate of the circle's center\n * @param cy - The y-coordinate of the circle's center\n * @param r - The radius of the circle\n * @returns SVG path string representing the circle\n *\n * @example\n * ```typescript\n * const path = circleToPath(50, 50, 25);\n * // Returns: \"M25 50 A25 25 0 0 1 75 50 A25 25 0 0 1 25 50\"\n * ```\n */\nexport function circleToPath(cx: number, cy: number, r: number): string {\n const left = cx - r;\n const right = cx + r;\n return `M${left} ${cy} A${r} ${r} 0 0 1 ${right} ${cy} A${r} ${r} 0 0 1 ${left} ${cy}`;\n}\n","/**\n * Converts an ellipse element to an SVG path string.\n *\n * The ellipse is represented using two elliptical arc commands (A) with different\n * x and y radii. When rx equals ry, this produces a circle.\n *\n * @param cx - The x-coordinate of the ellipse's center\n * @param cy - The y-coordinate of the ellipse's center\n * @param rx - The horizontal radius (semi-major/minor axis)\n * @param ry - The vertical radius (semi-major/minor axis)\n * @returns SVG path string representing the ellipse\n *\n * @example\n * ```typescript\n * const path = ellipseToPath(50, 50, 30, 20);\n * // Returns: \"M20 50 A30 20 0 0 1 80 50 A30 20 0 0 1 20 50\"\n * ```\n */\nexport function ellipseToPath(\n cx: number,\n cy: number,\n rx: number,\n ry: number\n): string {\n const left = cx - rx;\n const right = cx + rx;\n return `M${left} ${cy} A${rx} ${ry} 0 0 1 ${right} ${cy} A${rx} ${ry} 0 0 1 ${left} ${cy}`;\n}\n","/**\n * Converts a line element to an SVG path string.\n *\n * Creates a simple path from one point to another using move (M) and line (L) commands.\n * This is the most basic path conversion, producing a straight line segment.\n *\n * @param x1 - The x-coordinate of the starting point\n * @param y1 - The y-coordinate of the starting point\n * @param x2 - The x-coordinate of the ending point\n * @param y2 - The y-coordinate of the ending point\n * @returns SVG path string representing the line\n *\n * @example\n * ```typescript\n * const path = lineToPath(10, 20, 50, 80);\n * // Returns: \"M10 20 L50 80\"\n * ```\n */\nexport function lineToPath(\n x1: number,\n y1: number,\n x2: number,\n y2: number\n): string {\n return `M${x1} ${y1} L${x2} ${y2}`;\n}\n","/**\n * Converts a polyline element to an SVG path string.\n *\n * Parses a points string (space or comma-separated coordinates) and creates\n * a path with line segments connecting all points. The polyline remains open\n * (does not automatically close back to the starting point).\n *\n * @param points - String containing coordinate pairs, e.g., \"10,20 30,40 50,60\"\n * @returns SVG path string representing the polyline, or empty string if insufficient points\n *\n * @example\n * ```typescript\n * const path = polylineToPath(\"10,20 30,40 50,60\");\n * // Returns: \"M10 20 L30 40 L50 60\"\n *\n * const emptyPath = polylineToPath(\"10,20\"); // Only one point\n * // Returns: \"\"\n * ```\n */\nexport function polylineToPath(points: string): string {\n const coords = points\n .trim()\n .split(/[\\s,]+/)\n .map(Number);\n\n // Need at least 2 points (4 coordinates) for a valid polyline\n if (coords.length < 4) {\n return \"\";\n }\n\n let path = `M${coords[0]} ${coords[1]}`;\n for (let i = 2; i < coords.length; i += 2) {\n path += ` L${coords[i]} ${coords[i + 1]}`;\n }\n return path;\n}\n\n/**\n * Converts a polygon element to an SVG path string.\n *\n * Similar to polylineToPath but automatically closes the shape by adding\n * a closepath (Z) command, creating a closed polygon.\n *\n * @param points - String containing coordinate pairs, e.g., \"10,20 30,40 50,60\"\n * @returns SVG path string representing the closed polygon\n *\n * @example\n * ```typescript\n * const path = polygonToPath(\"10,20 30,40 50,60\");\n * // Returns: \"M10 20 L30 40 L50 60 Z\"\n * ```\n */\nexport function polygonToPath(points: string): string {\n return `${polylineToPath(points)} Z`;\n}\n","/**\n * Converts a rectangle element to an SVG path string.\n *\n * Supports both sharp-cornered and rounded rectangles. For rounded corners,\n * the function automatically clamps corner radii to prevent invalid geometry\n * (radii cannot exceed half the width or height).\n *\n * @param x - The x-coordinate of the top-left corner\n * @param y - The y-coordinate of the top-left corner\n * @param width - The width of the rectangle\n * @param height - The height of the rectangle\n * @param rx - The horizontal radius for rounded corners (default: 0)\n * @param ry - The vertical radius for rounded corners (default: 0)\n * @returns SVG path string representing the rectangle\n *\n * @example\n * ```typescript\n * // Sharp rectangle\n * const sharpRect = rectToPath(10, 10, 100, 50);\n * // Returns: \"M10 10 H110 V60 H10 Z\"\n *\n * // Rounded rectangle\n * const roundedRect = rectToPath(10, 10, 100, 50, 5, 5);\n * // Returns complex path with arc commands for rounded corners\n * ```\n */\nexport function rectToPath(\n x: number,\n y: number,\n width: number,\n height: number,\n rx = 0,\n ry = 0\n): string {\n if (rx === 0 && ry === 0) {\n return `M${x} ${y} H${x + width} V${y + height} H${x} Z`;\n }\n\n // Clamp radii to prevent invalid geometry\n const clampedRx = Math.min(rx, width / 2);\n const clampedRy = Math.min(ry, height / 2);\n\n return `M${x + clampedRx} ${y} H${x + width - clampedRx} A${clampedRx} ${clampedRy} 0 0 1 ${x + width} ${\n y + clampedRy\n } V${y + height - clampedRy} A${clampedRx} ${clampedRy} 0 0 1 ${x + width - clampedRx} ${y + height} H${\n x + clampedRx\n } A${clampedRx} ${clampedRy} 0 0 1 ${x} ${y + height - clampedRy} V${y + clampedRy} A${clampedRx} ${clampedRy} 0 0 1 ${x + clampedRx} ${y} Z`;\n}\n","/**\n * @fileoverview SVG shape conversion utilities.\n *\n * This module provides a unified interface for converting various SVG shape elements\n * (circle, ellipse, rect, line, polyline, polygon, path) into standardized SVG path strings.\n * It serves as the main entry point for shape-to-path conversion in the vector graphics\n * processing pipeline.\n */\n\n/** biome-ignore-all lint/style/noExportedImports: kept when migrating to biome */\n\nimport {\n circleToPath,\n ellipseToPath,\n lineToPath,\n polygonToPath,\n polylineToPath,\n rectToPath,\n} from \"./basic\";\n// Re-export all basic shape conversion functions\n\n/**\n * Converts any supported SVG shape element to an SVG path string.\n *\n * This is the main dispatcher function that examines the SVG element's tag name\n * and delegates to the appropriate shape-specific conversion function. It extracts\n * the necessary geometric properties from the DOM element and converts them to\n * a standardized path representation.\n *\n * @param element - The SVG shape element to convert\n * @returns SVG path string representing the shape\n * @throws {Error} If the element type is not supported\n *\n * @example\n * ```typescript\n * // Convert a circle element\n * const circleElement = document.querySelector('circle');\n * const path = shapeToPath(circleElement);\n * // Returns: \"M25 50 A25 25 0 0 1 75 50 A25 25 0 0 1 25 50\"\n *\n * // Convert a rectangle element\n * const rectElement = document.querySelector('rect');\n * const path = shapeToPath(rectElement);\n * // Returns appropriate path with lines and optional arcs for rounded corners\n * ```\n *\n * @remarks\n * Supported SVG elements:\n * - `<circle>` - Converted using two semicircular arcs\n * - `<ellipse>` - Converted using two elliptical arcs\n * - `<rect>` - Converted to lines/arcs (supports rounded corners)\n * - `<line>` - Converted to a simple move + line command\n * - `<polyline>` - Converted to move + multiple line commands\n * - `<polygon>` - Same as polyline but with automatic closure (Z command)\n * - `<path>` - Returns the existing path data unchanged\n */\nexport function shapeToPath(element: SVGElement): string {\n const tagName = element.tagName.toLowerCase();\n\n switch (tagName) {\n case \"circle\": {\n const circle = element as SVGCircleElement;\n const cx = circle.cx.baseVal.value;\n const cy = circle.cy.baseVal.value;\n const r = circle.r.baseVal.value;\n return circleToPath(cx, cy, r);\n }\n\n case \"ellipse\": {\n const ellipse = element as SVGEllipseElement;\n const cx = ellipse.cx.baseVal.value;\n const cy = ellipse.cy.baseVal.value;\n const rx = ellipse.rx.baseVal.value;\n const ry = ellipse.ry.baseVal.value;\n return ellipseToPath(cx, cy, rx, ry);\n }\n\n case \"rect\": {\n const rect = element as SVGRectElement;\n const x = rect.x.baseVal.value;\n const y = rect.y.baseVal.value;\n const width = rect.width.baseVal.value;\n const height = rect.height.baseVal.value;\n const rx = rect.rx.baseVal.value;\n const ry = rect.ry.baseVal.value;\n return rectToPath(x, y, width, height, rx, ry);\n }\n\n case \"line\": {\n const line = element as SVGLineElement;\n const x1 = line.x1.baseVal.value;\n const y1 = line.y1.baseVal.value;\n const x2 = line.x2.baseVal.value;\n const y2 = line.y2.baseVal.value;\n return lineToPath(x1, y1, x2, y2);\n }\n\n case \"polyline\": {\n const polyline = element as SVGPolylineElement;\n return polylineToPath(polyline.getAttribute(\"points\") ?? \"\");\n }\n\n case \"polygon\": {\n const polygon = element as SVGPolygonElement;\n return polygonToPath(polygon.getAttribute(\"points\") ?? \"\");\n }\n\n case \"path\": {\n const path = element as SVGPathElement;\n return path.getAttribute(\"d\") ?? \"\";\n }\n\n default:\n throw new Error(`Unsupported SVG shape: ${tagName}`);\n }\n}\n","import type { Position } from \"../../dom\";\nimport { createSolidPaint, cssColorToFigmaColor } from \"../../styles/color\";\nimport { parseOpacity } from \"../../styles/opacity\";\nimport type { FigmaBlob, FigmaGuid, FigmaVectorNodeChange } from \"../../types\";\nimport { shapeToPath } from \"./shapes\";\nimport { svgPathToVectorNetworkWithScaling } from \"./vector-networks\";\nimport { vectorNetworkToBytes } from \"./vector-networks/encoder\";\nimport type { WindingRule } from \"./vector-networks/types\";\n\n/**\n * Parses SVG stroke-dasharray string into array of floats.\n * @param dashArray - SVG stroke-dasharray value (e.g., \"5,5\", \"10 5 2 5\", \"10px 2px\", \"none\")\n * @returns Array of floats or undefined if no dash pattern\n */\nfunction parseDashArray(\n dashArray: string | undefined | null\n): Array<number> | undefined {\n if (!dashArray || dashArray === \"none\" || dashArray === \"0\") {\n return;\n }\n\n // Split by comma or whitespace and convert to numbers\n const splitValues = dashArray.trim().split(/[\\s,]+/);\n\n const values = splitValues\n .map((v) => {\n const cleanValue = v.trim().replace(/px$/i, \"\");\n return Number.parseFloat(cleanValue);\n })\n .filter((v) => !Number.isNaN(v) && v >= 0);\n\n if (values.length === 0) {\n return;\n }\n\n // If we have an even number of values and every second value (gaps) is 0,\n // this creates a solid line, so return undefined\n if (values.length >= 2) {\n const gapValues = values.filter((_, index) => index % 2 === 1);\n\n const hasOnlyZeroGaps = gapValues.every((gap) => gap === 0);\n\n if (hasOnlyZeroGaps) {\n return;\n }\n }\n\n return values;\n}\n\n/**\n * Parses SVG stroke-linecap value to Figma strokeCap\n * @param lineCap - SVG stroke-linecap value (\"butt\", \"round\", \"square\")\n * @returns Figma strokeCap value or undefined\n */\nfunction parseStrokeCap(\n lineCap: string | undefined | null\n): \"NONE\" | \"ROUND\" | \"SQUARE\" | undefined {\n if (!lineCap) {\n return;\n }\n\n const normalizedCap = lineCap.toLowerCase().trim();\n\n switch (normalizedCap) {\n case \"butt\":\n return \"NONE\";\n case \"round\":\n return \"ROUND\";\n case \"square\":\n return \"SQUARE\";\n default:\n return;\n }\n}\n\n/**\n * Parses SVG stroke-linejoin value to Figma strokeJoin\n * @param lineJoin - SVG stroke-linejoin value (\"miter\", \"bevel\", \"round\")\n * @returns Figma strokeJoin value or undefined\n */\nfunction parseStrokeJoin(\n lineJoin: string | undefined | null\n): \"MITER\" | \"BEVEL\" | \"ROUND\" | undefined {\n if (!lineJoin) {\n return;\n }\n\n const normalizedJoin = lineJoin.toLowerCase().trim();\n\n switch (normalizedJoin) {\n case \"miter\":\n return \"MITER\";\n case \"bevel\":\n return \"BEVEL\";\n case \"round\":\n return \"ROUND\";\n default:\n return;\n }\n}\n\nfunction parseWindingRule(\n ruleValue: string | undefined | null\n): WindingRule | undefined {\n if (!ruleValue) {\n return;\n }\n\n const normalizedRule = ruleValue.toLowerCase().trim();\n\n switch (normalizedRule) {\n case \"evenodd\":\n return \"ODD\";\n case \"nonzero\":\n return \"NONZERO\";\n default:\n return;\n }\n}\n\nfunction getFillRule(\n element: SVGElement,\n computedStyle: CSSStyleDeclaration\n): WindingRule {\n const fillRuleAttr = element.getAttribute(\"fill-rule\");\n const fromFillRuleAttr = parseWindingRule(fillRuleAttr);\n if (fromFillRuleAttr) {\n return fromFillRuleAttr;\n }\n\n const clipRuleAttr = element.getAttribute(\"clip-rule\");\n const fromClipRuleAttr = parseWindingRule(clipRuleAttr);\n if (fromClipRuleAttr) {\n return fromClipRuleAttr;\n }\n\n // Check parent elements for inherited attributes\n let parent = element.parentElement;\n while (parent && parent instanceof SVGElement) {\n const parentFillRule = parent.getAttribute(\"fill-rule\");\n const fromParentFillRule = parseWindingRule(parentFillRule);\n if (fromParentFillRule) {\n return fromParentFillRule;\n }\n\n const parentClipRule = parent.getAttribute(\"clip-rule\");\n const fromParentClipRule = parseWindingRule(parentClipRule);\n if (fromParentClipRule) {\n return fromParentClipRule;\n }\n\n parent = parent.parentElement;\n }\n\n const fromComputedFillRule = parseWindingRule(computedStyle.fillRule);\n if (fromComputedFillRule) {\n return fromComputedFillRule;\n }\n\n const fromComputedClipRule = parseWindingRule(computedStyle.clipRule);\n if (fromComputedClipRule) {\n return fromComputedClipRule;\n }\n\n return \"NONZERO\";\n}\n\ntype Params = {\n guid: FigmaGuid;\n parentGuid: FigmaGuid;\n childIndex: number;\n position: Position;\n registerBlob: (blob: FigmaBlob) => number;\n inheritedProperties?: {\n svgViewbox?: {\n width: number;\n height: number;\n };\n };\n};\n\nexport type SVGChildElement =\n | SVGPathElement\n | SVGCircleElement\n | SVGRectElement\n | SVGEllipseElement\n | SVGLineElement\n | SVGPolylineElement\n | SVGPolygonElement;\n\nexport function elementToVectorNodeChange(\n element: SVGChildElement,\n options: Params\n): FigmaVectorNodeChange {\n const {\n guid,\n parentGuid,\n childIndex,\n position,\n registerBlob,\n inheritedProperties,\n } = options;\n\n const rect = element.getBoundingClientRect();\n const width = rect.width;\n const height = rect.height;\n\n const computedStyle = window.getComputedStyle(element);\n\n const fillColor = cssColorToFigmaColor(computedStyle.fill);\n const fillOpacity = Number.parseFloat(computedStyle.fillOpacity);\n\n const strokeWidth = Number.parseFloat(computedStyle.strokeWidth) || 0;\n const strokeColor = cssColorToFigmaColor(computedStyle.stroke);\n const strokeOpacity = Number.parseFloat(computedStyle.strokeOpacity);\n\n const strokeLinecap = parseStrokeCap(computedStyle.strokeLinecap);\n const strokeLinejoin = parseStrokeJoin(computedStyle.strokeLinejoin);\n const strokeDasharray = parseDashArray(computedStyle.strokeDasharray);\n const fillRule = getFillRule(element, computedStyle);\n const opacity = parseOpacity(computedStyle.opacity);\n\n const path = shapeToPath(element);\n\n const scalingResult = svgPathToVectorNetworkWithScaling(path, {\n normalize: true,\n viewBoxWidth: inheritedProperties?.svgViewbox?.width,\n viewBoxHeight: inheritedProperties?.svgViewbox?.height,\n targetWidth: width,\n targetHeight: height,\n preserveAspectRatio: true,\n originalStrokeWeight: strokeWidth,\n originalStrokeDashArray: strokeDasharray?.join(\",\"),\n fillRule,\n });\n\n const vectorNetwork = scalingResult.vectorNetwork;\n const vectorNetworkBytes = vectorNetworkToBytes(vectorNetwork);\n\n const blobIndex = registerBlob({ bytes: Array.from(vectorNetworkBytes) });\n\n const nodeChange: FigmaVectorNodeChange = {\n /* General Info */\n guid,\n phase: \"CREATED\",\n parentIndex: {\n guid: parentGuid,\n position: childIndex.toString(),\n },\n type: \"VECTOR\",\n name: \"Vector\",\n visible: true,\n opacity,\n\n /* Size and Position */\n size: {\n x: scalingResult.figmaSize.x,\n y: scalingResult.figmaSize.y,\n },\n transform: {\n m00: 1.0,\n m01: 0.0,\n m02: position.x,\n m10: 0.0,\n m11: 1.0,\n m12: position.y,\n },\n\n // /* Stroke */\n strokeWeight: scalingResult.scaledStrokeWeight,\n strokeAlign: \"CENTER\",\n strokeCap: strokeLinecap,\n strokeJoin: strokeLinejoin,\n strokePaints: strokeColor\n ? [createSolidPaint(strokeColor.color, strokeOpacity)]\n : [],\n dashPattern: strokeDasharray ?? [],\n\n /* Vector Data */\n vectorData: {\n vectorNetworkBlob: blobIndex,\n normalizedSize: {\n x: scalingResult.figmaSize.x,\n y: scalingResult.figmaSize.y,\n },\n },\n\n // /* Fill */\n fillPaints: fillColor\n ? [createSolidPaint(fillColor.color, fillOpacity)]\n : [],\n\n /* Other */\n horizontalConstraint: \"SCALE\",\n verticalConstraint: \"SCALE\",\n };\n\n return nodeChange;\n}\n","import type { ElementKind } from \"./classify\";\nimport type { Position } from \"./dom\";\nimport type { FontCache } from \"./font-cache\";\nimport type { ImageCache } from \"./image-cache\";\nimport type { InferredChildStack } from \"./layout/infer\";\nimport { elementToFormNodeChange } from \"./nodes/form\";\nimport { elementToFrameNodeChange } from \"./nodes/frame\";\nimport { elementToGroupNodeChange } from \"./nodes/group\";\nimport { elementToImageNodeChange } from \"./nodes/image\";\nimport { nodeToTextNodeChange } from \"./nodes/text\";\nimport type { SVGChildElement } from \"./nodes/vector/converter\";\nimport { elementToVectorNodeChange } from \"./nodes/vector/converter\";\nimport type {\n FigmaBlob,\n FigmaGuid,\n FigmaNodeChange,\n FigmaPaint,\n} from \"./types\";\nimport type { ConverterLayout } from \"./walk\";\n\nexport type InheritedProperties = {\n textGradient?: Array<FigmaPaint>;\n svgViewbox?: { width: number; height: number };\n};\n\nexport type ConvertContext = {\n guid: FigmaGuid;\n parentGuid: FigmaGuid;\n childIndex: number;\n position: Position;\n inheritedProperties: InheritedProperties;\n layout?: ConverterLayout;\n /** True when the direct parent frame became an inferred auto-layout stack. */\n parentIsAutoLayout?: boolean;\n /** This node's fill/stretch overrides from the parent stack's inference. */\n childStackSpec?: InferredChildStack;\n /** Set for the converted root element only: the paste-template frame size. */\n rootFill?: { width: number; height: number };\n registerBlob: (blob: FigmaBlob) => number;\n fontCache: FontCache;\n imageCache: ImageCache;\n createGuid: () => FigmaGuid;\n};\n\nexport type ConversionResult = {\n changes: ReadonlyArray<FigmaNodeChange>;\n hasChildren: boolean;\n /** Text gradient produced by a frame's `background-clip: text`, propagated to descendants. */\n frameTextGradient?: Array<FigmaPaint>;\n /** True when this element became an inferred auto-layout stack. */\n isAutoLayout?: boolean;\n /** Fill/stretch overrides for this element's children, keyed by element. */\n childStackSpecs?: ReadonlyMap<Element, InferredChildStack>;\n /** Reversed flex direction: children must be emitted in visual order. */\n reverseChildren?: boolean;\n /** Child slots (e.g. decomposed border sides) already emitted under this\n * node's guid; the walker starts its real children after them so their\n * `parentIndex.position` does not collide and they paint on top. */\n reservedChildCount?: number;\n};\n\n/**\n * Stamp the parent stack's child overrides (e.g. `stackPositioning:\n * \"ABSOLUTE\"`) onto a converted element's own node change. The frame\n * converter merges these itself; every other kind gets them here.\n */\nfunction withChildStackSpec(\n changes: ReadonlyArray<FigmaNodeChange>,\n childStackSpec: InferredChildStack | undefined\n): ReadonlyArray<FigmaNodeChange> {\n if (!childStackSpec || changes.length === 0) {\n return changes;\n }\n const [own, ...rest] = changes;\n return [{ ...own, ...childStackSpec } as FigmaNodeChange, ...rest];\n}\n\nexport async function convertElement(\n element: Element,\n kind: ElementKind,\n ctx: ConvertContext\n): Promise<ConversionResult> {\n const {\n guid,\n parentGuid,\n childIndex,\n position,\n inheritedProperties,\n layout,\n parentIsAutoLayout,\n childStackSpec,\n rootFill,\n registerBlob,\n fontCache,\n imageCache,\n createGuid,\n } = ctx;\n\n switch (kind) {\n case \"skip\":\n return { changes: [], hasChildren: false };\n\n case \"group\":\n return {\n changes: [\n elementToGroupNodeChange(element, {\n guid,\n parentGuid,\n childIndex,\n position,\n }),\n ],\n hasChildren: true,\n };\n\n case \"frame\": {\n const frameResult = elementToFrameNodeChange(element, {\n guid,\n parentGuid,\n childIndex,\n position,\n layout,\n parentIsAutoLayout,\n childStackSpec,\n rootFill,\n createGuid,\n registerBlob,\n });\n const borderChildren = frameResult.borderChildren ?? [];\n return {\n changes: [\n frameResult.nodeChange,\n ...borderChildren,\n ...(frameResult.extraChildren ?? []),\n ],\n hasChildren: true,\n reservedChildCount: borderChildren.length,\n frameTextGradient: frameResult.textGradient,\n isAutoLayout: frameResult.isAutoLayout,\n childStackSpecs: frameResult.childStackSpecs,\n reverseChildren: frameResult.reverseChildren,\n };\n }\n\n case \"vector\":\n return {\n changes: withChildStackSpec(\n [\n elementToVectorNodeChange(element as SVGChildElement, {\n guid,\n parentGuid,\n childIndex,\n position,\n registerBlob,\n }),\n ],\n childStackSpec\n ),\n hasChildren: false,\n };\n\n case \"image\":\n return {\n changes: withChildStackSpec(\n [\n await elementToImageNodeChange(element as HTMLImageElement, {\n guid,\n parentGuid,\n childIndex,\n position,\n registerBlob,\n imageCache,\n }),\n ],\n childStackSpec\n ),\n hasChildren: false,\n };\n\n case \"text\": {\n // Inside stacks the box edges drive sibling positions, so use the\n // exact measured size instead of the converter's ceiled default.\n const exactSize = parentIsAutoLayout\n ? (() => {\n const rect = element.getBoundingClientRect();\n return { width: rect.width, height: rect.height };\n })()\n : undefined;\n return {\n changes: withChildStackSpec(\n [\n await nodeToTextNodeChange(element, {\n guid,\n parentGuid,\n childIndex,\n position,\n size: exactSize,\n registerBlob,\n inheritedProperties,\n fontCache,\n }),\n ],\n childStackSpec\n ),\n hasChildren: false,\n };\n }\n\n case \"form-with-placeholder\":\n return {\n changes: withChildStackSpec(\n await elementToFormNodeChange({\n element,\n guid,\n parentGuid,\n childIndex,\n position,\n registerBlob,\n inheritedProperties,\n fontCache,\n createGuid,\n }),\n childStackSpec\n ),\n hasChildren: false,\n };\n\n default:\n throw new Error(`Unknown ElementKind: ${kind satisfies never}`);\n }\n}\n","import type { ElementKind } from \"./classify\";\nimport type { FigmaGuid } from \"./types\";\n\n/**\n * A single entry in a {@link ConvertTrace}: the link from one emitted Figma\n * node to the DOM element (or text node) it came from. Produced only when the\n * converter is created with `{ trace: true }`. Consumed by the visual-parity\n * harness to attribute discrepancies back to source DOM.\n */\nexport type TraceEntry = {\n /** GUID of the emitted node change this entry describes. */\n guid: FigmaGuid;\n /** Classified kind for the source element; always `\"text\"` for text nodes. */\n kind: ElementKind;\n /** Lowercase tag name of the source element; `\"#text\"` for text nodes. */\n tag: string;\n /**\n * Selector that resolves the source element from the scene root, built\n * incrementally from real DOM child positions, e.g.\n * `:scope > div:nth-child(2) > p:nth-child(1)`. The root element itself is\n * `:scope`. Text nodes carry their owner element's path plus a\n * `::text[i]` suffix (not a valid CSS selector — strip it to resolve the\n * owner element); every line segment of one text node shares that path.\n */\n domPath: string;\n /** Source rect in viewport coordinates at convert time (`getBoundingClientRect`). */\n rect: { x: number; y: number; width: number; height: number };\n /** First 120 characters of the rendered text; text nodes only. */\n text?: string;\n};\n\n/** The full trace for one `convert()` call. Exposed as `ConvertResult.trace`. */\nexport type ConvertTrace = {\n /** Root frame GUID the traced nodes descend from. */\n rootGuid: FigmaGuid;\n entries: Array<TraceEntry>;\n};\n\n/** Records trace entries during a walk; `undefined` disables tracing entirely. */\nexport type TraceRecorder = (entry: TraceEntry) => void;\n\n/**\n * Selector segment for an element, appended to its parent's path. The root\n * element (empty `parentDomPath`) is `:scope`; every other element is\n * positioned by its real 1-based index among its parent's element children,\n * so the result resolves via `root.querySelector(...)` regardless of the\n * converter's emission order.\n */\nexport function elementDomPath(\n element: Element,\n parentDomPath: string\n): string {\n if (parentDomPath === \"\") {\n return \":scope\";\n }\n const parent = element.parentElement;\n const index = parent ? Array.from(parent.children).indexOf(element) + 1 : 1;\n return `${parentDomPath} > ${element.tagName.toLowerCase()}:nth-child(${index})`;\n}\n\n/** Owner path for a text node: its parent element's path plus a `::text[i]`\n * ordinal identifying the text node among its parent's child nodes. */\nexport function textDomPath(textNode: Text, parentDomPath: string): string {\n const parent = textNode.parentNode;\n const index = parent ? Array.from(parent.childNodes).indexOf(textNode) : 0;\n return `${parentDomPath}::text[${index}]`;\n}\n","import type { ElementKind } from \"./classify\";\nimport { defaultClassify } from \"./classify\";\nimport type { ConversionResult, InheritedProperties } from \"./convert\";\nimport { convertElement } from \"./convert\";\nimport type { TextLineSegment } from \"./dom\";\nimport {\n getElementPositionRelativeToParent,\n getTextPositionRelativeToParent,\n getTextSize,\n isElementNode,\n isSvgElement,\n isTextEmpty,\n isTextNode,\n sortNodesByStackingOrder,\n splitMidLineWrappedText,\n} from \"./dom\";\nimport type { FontCache } from \"./font-cache\";\nimport type { ImageCache } from \"./image-cache\";\nimport type { InferredChildStack } from \"./layout/infer\";\nimport { nodeToTextNodeChange } from \"./nodes/text\";\nimport type { TraceEntry, TraceRecorder } from \"./trace\";\nimport { elementDomPath, textDomPath } from \"./trace\";\nimport type { FigmaBlob, FigmaGuid, FigmaNodeChange } from \"./types\";\n\nexport type Classify = (\n element: Element,\n defaultKind: ElementKind\n) => ElementKind;\n\n/** `\"auto\"` infers Figma auto-layout for flex containers; `\"absolute\"` keeps\n * every frame absolutely positioned (the historical behavior). */\nexport type ConverterLayout = \"absolute\" | \"auto\";\n\nexport type WalkContext = {\n classify?: Classify;\n layout?: ConverterLayout;\n createGuid: () => FigmaGuid;\n registerBlob: (blob: FigmaBlob) => number;\n fontCache: FontCache;\n imageCache: ImageCache;\n appendChanges: (changes: ReadonlyArray<FigmaNodeChange>) => void;\n /** Set only when the converter runs with `{ trace: true }`. */\n recordTrace?: TraceRecorder;\n};\n\nconst EMPTY_INHERITED: InheritedProperties = {};\nconst VIEWBOX_SEPARATOR = /[\\s,]+/;\n\nfunction rectFrom(rect: DOMRect): TraceEntry[\"rect\"] {\n return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };\n}\n\n/** Absolute rect of a whole text node, via a Range over its contents. */\nfunction textNodeRect(textNode: Text): TraceEntry[\"rect\"] {\n const range = document.createRange();\n range.selectNodeContents(textNode);\n return rectFrom(range.getBoundingClientRect());\n}\n\n/** Approximate absolute rect of one wrapped line segment. `origin` must be\n * the element the segment positions were measured against — the `relativeTo`\n * passed to `splitMidLineWrappedText` (for the inline-element shortcut that is\n * the span's parent, not the text node's parent). */\nfunction segmentRect(\n origin: Element | null,\n segment: TextLineSegment\n): TraceEntry[\"rect\"] {\n const originRect = origin?.getBoundingClientRect();\n return {\n x: (originRect?.x ?? 0) + segment.position.x,\n y: (originRect?.y ?? 0) + segment.position.y,\n width: segment.size.width,\n height: segment.size.height,\n };\n}\n\nexport async function walkRoot(\n root: Element,\n parentGuid: FigmaGuid,\n ctx: WalkContext,\n rootSize?: { width: number; height: number }\n) {\n await walkNode(root, parentGuid, 0, EMPTY_INHERITED, ctx, {\n isAutoLayout: false,\n rootFill: rootSize,\n });\n}\n\ntype ParentStackInfo = {\n isAutoLayout: boolean;\n childSpecs?: ReadonlyMap<Element, InferredChildStack>;\n /** Reversed flex parent: emit children in visual (reversed DOM) order. */\n reverse?: boolean;\n /** Only on the root walk: the paste-template frame size. */\n rootFill?: { width: number; height: number };\n};\n\nconst NO_PARENT_STACK: ParentStackInfo = { isAutoLayout: false };\n\n/** Record a trace entry for an emitted element node and return its dom path\n * (empty when tracing is disabled, so children can skip path work too). */\nfunction traceElement(\n ctx: WalkContext,\n node: Element,\n kind: ElementKind,\n guid: FigmaGuid,\n parentDomPath: string\n): string {\n if (!ctx.recordTrace) {\n return \"\";\n }\n const domPath = elementDomPath(node, parentDomPath);\n ctx.recordTrace({\n guid,\n kind,\n tag: node.tagName.toLowerCase(),\n domPath,\n rect: rectFrom(node.getBoundingClientRect()),\n });\n return domPath;\n}\n\n/** Returns the number of node changes emitted for this DOM node (text nodes\n * can split into several). */\nasync function walkNode(\n node: Node,\n parentGuid: FigmaGuid,\n childIndex: number,\n inheritedProperties: InheritedProperties,\n ctx: WalkContext,\n parentStack: ParentStackInfo = NO_PARENT_STACK,\n parentDomPath = \"\"\n): Promise<number> {\n try {\n if (isTextNode(node)) {\n return await renderTextNode(\n node,\n parentGuid,\n childIndex,\n inheritedProperties,\n ctx,\n parentDomPath\n );\n }\n if (!isElementNode(node)) {\n return 0;\n }\n\n const defaultKind = defaultClassify(node);\n const kind = ctx.classify ? ctx.classify(node, defaultKind) : defaultKind;\n\n if (kind === \"skip\") {\n return 0;\n }\n\n // An inline text element (span/a/…) whose inner text continues a line\n // and wraps needs the same per-line split as a raw text node; segments\n // are positioned against the element's parent, which is also their\n // parent in the emitted tree.\n if (kind === \"text\" && node.parentElement) {\n const only =\n node.childNodes.length === 1 && node.firstChild\n ? node.firstChild\n : null;\n if (only && isTextNode(only)) {\n const segments = splitMidLineWrappedText(only, {\n siblingContext: node,\n relativeTo: node.parentElement,\n });\n if (segments) {\n const ownerPath = ctx.recordTrace\n ? `${elementDomPath(node, parentDomPath)}::text[0]`\n : \"\";\n return await emitTextSegments(\n only,\n segments,\n parentGuid,\n childIndex,\n inheritedProperties,\n ctx,\n node.parentElement,\n ownerPath\n );\n }\n }\n }\n\n const guid = ctx.createGuid();\n const position = getElementPositionRelativeToParent(node);\n\n const result = await convertElement(node, kind, {\n guid,\n parentGuid,\n childIndex,\n position,\n inheritedProperties,\n layout: ctx.layout,\n parentIsAutoLayout: parentStack.isAutoLayout,\n childStackSpec: parentStack.childSpecs?.get(node),\n rootFill: parentStack.rootFill,\n registerBlob: ctx.registerBlob,\n fontCache: ctx.fontCache,\n imageCache: ctx.imageCache,\n createGuid: ctx.createGuid,\n });\n\n ctx.appendChanges(result.changes);\n const domPath = traceElement(ctx, node, kind, guid, parentDomPath);\n\n if (result.hasChildren) {\n await walkChildren(\n node,\n guid,\n nextInheritedProperties(node, inheritedProperties, result),\n ctx,\n {\n isAutoLayout: result.isAutoLayout ?? false,\n childSpecs: result.childStackSpecs,\n reverse: result.reverseChildren,\n },\n domPath,\n result.reservedChildCount ?? 0\n );\n }\n\n return 1;\n } catch (error) {\n console.warn(\"Failed to process node:\", error);\n return 0;\n }\n}\n\nasync function walkChildren(\n element: Element,\n parentGuid: FigmaGuid,\n inheritedProperties: InheritedProperties,\n ctx: WalkContext,\n parentStack: ParentStackInfo = NO_PARENT_STACK,\n parentDomPath = \"\",\n startChildIndex = 0\n) {\n const sortedNodes = sortNodesByStackingOrder(Array.from(element.childNodes));\n if (parentStack.reverse) {\n // Reversed flex parent: Figma stacks lay children out in emission order,\n // so emit the visual order; stackReverseZIndex restores paint order.\n sortedNodes.reverse();\n }\n\n // Real children start after any slots the converter already reserved (e.g.\n // decomposed border sides), so their positions don't collide and they paint\n // above the border.\n let childNodeIndex = startChildIndex;\n for (const node of sortedNodes) {\n childNodeIndex += await walkNode(\n node,\n parentGuid,\n childNodeIndex,\n inheritedProperties,\n ctx,\n parentStack,\n parentDomPath\n );\n }\n}\n\nasync function renderTextNode(\n textNode: Text,\n parentGuid: FigmaGuid,\n childIndex: number,\n inheritedProperties: InheritedProperties,\n ctx: WalkContext,\n parentDomPath = \"\"\n): Promise<number> {\n if (isTextEmpty(textNode)) {\n return 0;\n }\n if (!textNode.parentElement) {\n return 0;\n }\n\n // A text node that continues a sibling's line and wraps cannot be one\n // Figma text box (its indented first line isn't representable): emit one\n // box per rendered line instead.\n const segments = splitMidLineWrappedText(textNode);\n if (segments) {\n const ownerPath = ctx.recordTrace\n ? textDomPath(textNode, parentDomPath)\n : \"\";\n return await emitTextSegments(\n textNode,\n segments,\n parentGuid,\n childIndex,\n inheritedProperties,\n ctx,\n textNode.parentElement,\n ownerPath\n );\n }\n\n const guid = ctx.createGuid();\n const change = await nodeToTextNodeChange(textNode, {\n guid,\n parentGuid,\n childIndex,\n position: getTextPositionRelativeToParent(textNode),\n size: getTextSize(textNode),\n textContent: (textNode.textContent || \"\").trim(),\n registerBlob: ctx.registerBlob,\n inheritedProperties,\n fontCache: ctx.fontCache,\n });\n\n ctx.appendChanges([change]);\n ctx.recordTrace?.({\n guid,\n kind: \"text\",\n tag: \"#text\",\n domPath: textDomPath(textNode, parentDomPath),\n rect: textNodeRect(textNode),\n text: (textNode.textContent || \"\").trim().slice(0, 120),\n });\n return 1;\n}\n\nasync function emitTextSegments(\n textNode: Text,\n segments: ReadonlyArray<TextLineSegment>,\n parentGuid: FigmaGuid,\n childIndex: number,\n inheritedProperties: InheritedProperties,\n ctx: WalkContext,\n segmentOrigin: Element | null,\n ownerDomPath = \"\"\n): Promise<number> {\n let emitted = 0;\n for (const segment of segments) {\n const guid = ctx.createGuid();\n const change = await nodeToTextNodeChange(textNode, {\n guid,\n parentGuid,\n childIndex: childIndex + emitted,\n position: segment.position,\n size: segment.size,\n textContent: segment.text,\n registerBlob: ctx.registerBlob,\n inheritedProperties,\n fontCache: ctx.fontCache,\n });\n ctx.appendChanges([change]);\n // All line segments of one text node share its owner path; distinct guids.\n ctx.recordTrace?.({\n guid,\n kind: \"text\",\n tag: \"#text\",\n domPath: ownerDomPath,\n rect: segmentRect(segmentOrigin, segment),\n text: segment.text.slice(0, 120),\n });\n emitted += 1;\n }\n return emitted;\n}\n\nfunction nextInheritedProperties(\n element: Element,\n prev: InheritedProperties,\n result: ConversionResult\n): InheritedProperties {\n let svgViewbox = prev.svgViewbox;\n if (isSvgElement(element)) {\n const parsed = element\n .getAttribute(\"viewBox\")\n ?.split(VIEWBOX_SEPARATOR)\n .map(Number);\n if (parsed) {\n svgViewbox = {\n width: parsed[2] ?? 0,\n height: parsed[3] ?? 0,\n };\n }\n }\n\n return {\n textGradient: result.frameTextGradient ?? prev.textGradient,\n svgViewbox,\n };\n}\n","import {\n composeClipboardHtml,\n encodeFigmaData,\n toClipboardItem,\n} from \"@charz/fig-kiwi\";\nimport { BlobManager } from \"./converter/blob-manager\";\nimport { createFontCache } from \"./converter/font-cache\";\nimport { createImageCache } from \"./converter/image-cache\";\nimport type { ImageLoader } from \"./converter/nodes/image/loader\";\nimport { createDirectImageLoader } from \"./converter/nodes/image/loader\";\nimport {\n getMultiFrameRootTemplate,\n getRootTemplate,\n ROOT_FRAME_GUID,\n} from \"./converter/nodes/root\";\nimport type { FontLoader } from \"./converter/nodes/text/primitives/font/loader\";\nimport { createFontsourceLoader } from \"./converter/nodes/text/primitives/font/loader\";\nimport type { ConvertTrace, TraceEntry } from \"./converter/trace\";\nimport type {\n FigmaClipboard,\n FigmaGuid,\n FigmaNodeChange,\n} from \"./converter/types\";\nimport type { Classify, ConverterLayout, WalkContext } from \"./converter/walk\";\nimport { walkRoot } from \"./converter/walk\";\n\nexport type { ElementKind } from \"./converter/classify\";\nexport { defaultClassify } from \"./converter/classify\";\nexport type {\n ImageFile,\n ImageLoader,\n ImageRequest,\n} from \"./converter/nodes/image/loader\";\nexport { createDirectImageLoader } from \"./converter/nodes/image/loader\";\nexport type {\n FontFile,\n FontLoader,\n FontProperties,\n FontsourceLoaderOptions,\n} from \"./converter/nodes/text/primitives/font/loader\";\nexport { createFontsourceLoader } from \"./converter/nodes/text/primitives/font/loader\";\nexport type { ConvertTrace, TraceEntry } from \"./converter/trace\";\nexport type { FigmaClipboard } from \"./converter/types\";\nexport type { Classify, ConverterLayout } from \"./converter/walk\";\n\nexport type FrameInput = {\n element: Element;\n width: number;\n height: number;\n x: number;\n y: number;\n name: string;\n};\n\nexport type FigmaConverterConfig = {\n /** Defaults to `createFontsourceLoader()` (Google Fonts via fontsource jsDelivr CDN). */\n fontLoader?: FontLoader;\n /** Defaults to `createDirectImageLoader()` (single direct `fetch(src)`). */\n imageLoader?: ImageLoader;\n /** Override the default DOM-element classification. */\n classify?: Classify;\n /**\n * `\"auto\"` (default) converts containers into Figma auto-layout frames\n * whenever the layout can be reproduced exactly, falling back to absolute\n * positioning per node when it can't. `\"absolute\"` positions every frame\n * absolutely, disabling auto-layout inference entirely.\n */\n layout?: ConverterLayout;\n /**\n * When `true`, `convert()` also returns a {@link ConvertTrace} mapping every\n * emitted node back to its source DOM element. Off by default and adds no\n * cost when disabled; does not change the payload bytes. Intended for the\n * visual-parity harness, not production copies.\n */\n trace?: boolean;\n};\n\nexport type SingleFrameInput = {\n element: Element;\n width: number;\n height: number;\n name?: string;\n};\n\nexport type CanvasInput = {\n frames: ReadonlyArray<FrameInput>;\n canvasName?: string;\n};\n\nexport type ConvertInput = SingleFrameInput | CanvasInput;\n\nexport type ConvertResult = {\n /** Raw Figma node-change document. */\n document: FigmaClipboard;\n /** Encoded `.fig`-style binary. */\n bytes: Uint8Array;\n /** Base64-encoded representation of `bytes`. */\n base64: string;\n /** Construct a browser `ClipboardItem` for `navigator.clipboard.write([...])`. */\n toClipboardItem(): ClipboardItem;\n /** Get the raw HTML envelope Figma reads on paste. */\n toClipboardHtml(): string;\n /** DOM-to-node trace, present only when created with `{ trace: true }`. */\n trace?: ConvertTrace;\n};\n\nexport type FigmaConverter = {\n convert(input: ConvertInput): Promise<ConvertResult>;\n /** Drop cached fonts and images. Useful in long-running processes. */\n clearCache(): void;\n};\n\nconst ROOT_RESERVED_GUIDS = 3;\n\nexport function createFigmaConverter(\n config: FigmaConverterConfig = {}\n): FigmaConverter {\n const fontLoader = config.fontLoader ?? createFontsourceLoader();\n const imageLoader = config.imageLoader ?? createDirectImageLoader();\n const { classify } = config;\n const layout = config.layout ?? \"auto\";\n const traceEnabled = config.trace ?? false;\n\n const fontCache = createFontCache(fontLoader);\n const imageCache = createImageCache(imageLoader);\n\n const convert = async (input: ConvertInput): Promise<ConvertResult> => {\n const nodeChanges: Array<FigmaNodeChange> = [];\n const blobManager = new BlobManager();\n let idCounter = ROOT_RESERVED_GUIDS;\n\n const createGuid = (): FigmaGuid => {\n const localID = idCounter;\n idCounter += 1;\n return { sessionID: 0, localID };\n };\n\n const trace: ConvertTrace | undefined = traceEnabled\n ? { rootGuid: ROOT_FRAME_GUID, entries: [] as Array<TraceEntry> }\n : undefined;\n\n const walkContext: WalkContext = {\n classify,\n layout,\n createGuid,\n registerBlob: (blob) => blobManager.registerBlob(blob),\n fontCache,\n imageCache,\n appendChanges: (changes) => {\n for (const change of changes) {\n nodeChanges.push(change);\n }\n },\n recordTrace: trace\n ? (entry) => {\n trace.entries.push(entry);\n }\n : undefined,\n };\n\n const document =\n \"frames\" in input\n ? await buildCanvas(input, walkContext, blobManager, createGuid)\n : await buildSingle(input, walkContext, blobManager);\n\n document.nodeChanges.push(...nodeChanges);\n\n const encoded = encodeFigmaData(document);\n\n return {\n document,\n bytes: encoded.figBytes,\n base64: encoded.base64,\n toClipboardItem: () =>\n toClipboardItem(composeClipboardHtml(encoded.base64)),\n toClipboardHtml: () => composeClipboardHtml(encoded.base64),\n trace,\n };\n };\n\n return {\n convert,\n clearCache() {\n fontCache.clear();\n imageCache.clear();\n },\n };\n}\n\nasync function buildSingle(\n input: SingleFrameInput,\n walkContext: WalkContext,\n blobManager: BlobManager\n): Promise<FigmaClipboard> {\n await walkRoot(input.element, ROOT_FRAME_GUID, walkContext, {\n width: input.width,\n height: input.height,\n });\n return getRootTemplate({\n width: input.width,\n height: input.height,\n blobs: blobManager.getBlobs(),\n name: input.name,\n });\n}\n\nasync function buildCanvas(\n input: CanvasInput,\n walkContext: WalkContext,\n blobManager: BlobManager,\n createGuid: () => FigmaGuid\n): Promise<FigmaClipboard> {\n const frameConfigs: Array<{\n width: number;\n height: number;\n x: number;\n y: number;\n name: string;\n localId: number;\n }> = [];\n\n for (const frame of input.frames) {\n const frameGuid = createGuid();\n frameConfigs.push({\n width: frame.width,\n height: frame.height,\n x: frame.x,\n y: frame.y,\n name: frame.name,\n localId: frameGuid.localID,\n });\n await walkRoot(frame.element, frameGuid, walkContext, {\n width: frame.width,\n height: frame.height,\n });\n }\n\n return getMultiFrameRootTemplate({\n frames: frameConfigs,\n blobs: blobManager.getBlobs(),\n canvasName: input.canvasName,\n });\n}\n"],"mappings":"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B,OAAI,MAAM,SAAS,WAAW;AAG5B,QAAI,aAAa;KACf,MAAM,YAAY,wBAChB,MACA,aACA,SACA,UACA,EACE,eACD,CACF;AACD,WAAM,KAAK;MACT,YAAY;MACZ;MACA,OAAO;MACP,QAAQ;MACR,UAAU,gCACR,YACA,SACA,SACD;MACF,CAAC;;AAGJ,kBAAc;AACd,uBAAmB;AACnB;;GAGF,MAAM,eAAe,MAAM;AAG3B,OAAI,MAAM,SAAS,UAAU;QACT,sBAChB,MACA,cACA,SACA,UACA,EACE,eACD,CAGU,GAAG,WAAW,eAAe;KACxC,MAAM,YAAY,cAChB,MACA,cACA,WAAW,eACX,SACA,UACA,EACE,eACD,CACF;AAGD,UAAK,MAAM,QAAQ,WAAW;MAC5B,MAAM,YAAY,sBAChB,MACA,MACA,SACA,UACA,EACE,eACD,CACF;MACD,MAAM,WAAW,cAAc;MAC/B,MAAM,YAAY,sBAChB,MACA,UACA,SACA,UACA,EACE,eACD,CACF;AAID,UAAI,eAAe,YAAY,WAAW,eAAe;OAEvD,MAAM,YAAY,wBAChB,MACA,aACA,SACA,UACA,EACE,eACD,CACF;AACD,aAAM,KAAK;QACT,YAAY;QACZ;QACA,OAAO;QACP,QAAQ;QACR,UAAU,gCACR,YACA,SACA,SACD;QACF,CAAC;AAEF,qBAAc;AACd,0BAAmB;aACd;AACL,qBAAc;AACd,0BAAmB;;;AAGvB;;;GAKJ,MAAM,WAAW,cAAc;GAC/B,MAAM,YAAY,sBAChB,MACA,UACA,SACA,UACA,EAAE,eAAe,CAClB;AAID,OAAI,eAAe,YAAY,WAAW,eAAe;IAGvD,MAAM,YAAY,wBAChB,MACA,aACA,SACA,UACA,EACE,eACD,CACF;AACD,UAAM,KAAK;KACT,YAAY;KACZ;KACA,OAAO;KACP,QAAQ;KACR,UAAU,gCACR,YACA,SACA,SACD;KACF,CAAC;AAGF,QAAI,MAAM,SAAS,SAAS;AAC1B,mBAAc;AACd,wBAAmB;WACd;AACL,mBAAc;AACd,wBAAmB,sBACjB,MACA,cACA,SACA,UACA,EACE,eACD,CACF;;UAEE;AAEL,kBAAc;AACd,uBAAmB;;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"}
|