/** * Shared Morphus conversion pipeline. * Reused by the CLI and the local HTTP bridge for the Figma plugin. */ import { extractFromFile, extractFromHtml } from '../core/extractor.js'; import { resolveFonts } from '../figma/font-resolver.js'; import { sortByZIndex } from '../figma/z-index-sorter.js'; import { buildFigmaTree } from '../figma/mapper.js'; const DEFAULT_VIEWPORT = { width: 1440, height: 900 }; /** * @param {string} inputPath * @param {ConvertOptions} options */ export async function convertHtmlFile(inputPath, options = {}) { const viewport = normalizeViewport(options.viewport); return convertWithExtractor({ extractor: () => extractFromFile(inputPath, viewport), source: inputPath, viewport, onProgress: options.onProgress, }); } /** * @param {string} html * @param {ConvertHtmlOptions} options */ export async function convertHtmlString(html, options = {}) { const viewport = normalizeViewport(options.viewport); return convertWithExtractor({ extractor: () => extractFromHtml(html, { ...viewport, baseUrl: options.baseUrl ?? null, }), source: options.sourceName ?? 'inline.html', viewport, baseUrl: options.baseUrl ?? null, onProgress: options.onProgress, }); } async function convertWithExtractor({ extractor, source, viewport, baseUrl = null, onProgress = null }) { progress(onProgress, 5, 'Extracting page...'); const { domTree, title } = await extractor(); progress(onProgress, 78, 'Resolving fonts...'); const fontMap = await resolveFonts(domTree); progress(onProgress, 86, 'Building Figma tree...'); const sorted = sortByZIndex(domTree); const figmaTree = buildFigmaTree(sorted, { fontMap }); const documentTitle = normalizeDocumentTitle(title); progress(onProgress, 90, 'Snapshot ready. Sending to Figma...'); return { version: '0.1.0', meta: { source, ...(documentTitle ? { title: documentTitle } : {}), viewport, ...(baseUrl ? { baseUrl } : {}), }, warnings: [], figmaTree, }; } function normalizeDocumentTitle(title) { return String(title || '').replace(/\s+/g, ' ').trim(); } function progress(onProgress, percent, message) { if (typeof onProgress === 'function') { onProgress(percent, message); } } function normalizeViewport(viewport = {}) { const width = Number.parseInt(viewport.width ?? DEFAULT_VIEWPORT.width, 10); const height = Number.parseInt(viewport.height ?? DEFAULT_VIEWPORT.height, 10); return { width: Number.isFinite(width) ? width : DEFAULT_VIEWPORT.width, height: Number.isFinite(height) ? height : DEFAULT_VIEWPORT.height, }; } /** * @typedef {{ viewport?: { width?: number, height?: number } }} ConvertOptions * @typedef {ConvertOptions & { sourceName?: string, baseUrl?: string | null }} ConvertHtmlOptions */