render-tag 0.1.6 → 0.1.7

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.
@@ -1 +1 @@
1
- {"version":3,"file":"render-tag.umd.js","names":[],"sources":["../src/parse.ts","../src/css-resolver.ts","../src/layout.ts","../src/render.ts","../src/index.ts"],"sourcesContent":["/**\n * Parse HTML string and extract inline <style> blocks.\n * Returns the content element and combined CSS text.\n */\nexport function parseHTML(html: string): { fragment: DocumentFragment; css: string } {\n const parser = new DOMParser();\n const doc = parser.parseFromString(html, 'text/html');\n\n // Extract all <style> tag contents\n const styleTags = doc.querySelectorAll('style');\n let css = '';\n for (const tag of styleTags) {\n css += tag.textContent + '\\n';\n tag.remove();\n }\n\n // Move body children into a fragment\n const fragment = document.createDocumentFragment();\n while (doc.body.firstChild) {\n fragment.appendChild(document.adoptNode(doc.body.firstChild));\n }\n\n return { fragment, css };\n}\n","import type { ResolvedStyle, StyledNode } from './types.js';\n\n// ─── CSS Parser ──────────────────────────────────────────────────────\n\ninterface CSSDeclaration {\n property: string;\n value: string;\n}\n\ninterface CSSRule {\n selectors: string[];\n declarations: CSSDeclaration[];\n}\n\n/**\n * Parse a simple CSS string into rules.\n * Supports: tag, .class, parent > child, comma-separated selectors.\n * Extracts @font-face rules separately for injection into the document.\n */\nfunction parseCSS(css: string): { rules: CSSRule[]; fontFaceRules: string[] } {\n const rules: CSSRule[] = [];\n const fontFaceRules: string[] = [];\n // Remove comments\n css = css.replace(/\\/\\*[\\s\\S]*?\\*\\//g, '');\n\n let i = 0;\n while (i < css.length) {\n // Skip whitespace\n while (i < css.length && /\\s/.test(css[i])) i++;\n if (i >= css.length) break;\n\n // Handle at-rules (@font-face, @media, etc.)\n if (css[i] === '@') {\n const atStart = i;\n let braceDepth = 0;\n while (i < css.length) {\n if (css[i] === '{') braceDepth++;\n if (css[i] === '}') {\n braceDepth--;\n if (braceDepth <= 0) { i++; break; }\n }\n i++;\n }\n // Capture @font-face rules for injection\n const atRule = css.slice(atStart, i);\n if (atRule.startsWith('@font-face')) {\n fontFaceRules.push(atRule);\n }\n continue;\n }\n\n // Read selector(s) up to '{'\n const selectorStart = i;\n while (i < css.length && css[i] !== '{') i++;\n if (i >= css.length) break;\n const selectorStr = css.slice(selectorStart, i).trim();\n i++; // skip '{'\n\n // Read declarations up to '}'\n const declStart = i;\n while (i < css.length && css[i] !== '}') i++;\n const declStr = css.slice(declStart, i).trim();\n i++; // skip '}'\n\n if (!selectorStr) continue;\n\n // Parse selectors (comma-separated)\n const selectors = selectorStr.split(',').map(s => s.trim()).filter(Boolean);\n\n // Parse declarations\n const declarations: CSSDeclaration[] = [];\n for (const decl of declStr.split(';')) {\n const colonIdx = decl.indexOf(':');\n if (colonIdx === -1) continue;\n const property = decl.slice(0, colonIdx).trim().toLowerCase();\n const value = decl.slice(colonIdx + 1).trim();\n if (property && value) {\n declarations.push({ property, value });\n }\n }\n\n if (selectors.length > 0 && declarations.length > 0) {\n rules.push({ selectors, declarations });\n }\n }\n\n return { rules, fontFaceRules };\n}\n\n// ─── Selector Matching ───────────────────────────────────────────────\n\ninterface ElementContext {\n tagName: string;\n classes: Set<string>;\n parent: ElementContext | null;\n el: Element;\n}\n\n/**\n * Compute specificity for a simple selector.\n * Returns [ids, classes, tags] tuple.\n */\nfunction selectorSpecificity(selector: string): [number, number, number] {\n // Remove pseudo-elements for specificity calculation\n const sel = selector.replace(/::[\\w-]+/g, '');\n const parts = sel.split(/\\s*>\\s*|\\s+/);\n let ids = 0, classes = 0, tags = 0;\n for (const part of parts) {\n // Count #id\n const idMatches = part.match(/#[\\w-]+/g);\n if (idMatches) ids += idMatches.length;\n // Count .class\n const classMatches = part.match(/\\.[\\w-]+/g);\n if (classMatches) classes += classMatches.length;\n // Count tag (strip classes/ids/pseudo)\n const tagPart = part.replace(/[#.][\\w-]+/g, '').replace(/:[\\w-]+/g, '').trim();\n if (tagPart && tagPart !== '*') tags++;\n }\n return [ids, classes, tags];\n}\n\n/** Parsed representation of a simple selector part (e.g. \"ul.foo\") */\ninterface ParsedPart {\n tag: string; // '' if no tag, or the tag name\n classes: string[]; // class names without the dot\n}\n\nfunction parsePart(part: string): ParsedPart {\n const classMatches = part.match(/\\.[\\w-]+/g) || [];\n const tag = part.replace(/\\.[\\w-]+/g, '').replace(/:[\\w-]+/g, '').trim();\n return {\n tag: (tag && tag !== '*') ? tag : '',\n classes: classMatches.map(c => c.slice(1)),\n };\n}\n\n/**\n * Check if a parsed selector part matches an element context.\n */\nfunction matchesPart(part: string, ctx: ElementContext): boolean {\n const classMatches = part.match(/\\.[\\w-]+/g) || [];\n const tag = part.replace(/\\.[\\w-]+/g, '').replace(/:[\\w-]+/g, '').trim();\n\n if (tag && tag !== '*' && tag !== ctx.tagName) return false;\n for (const cls of classMatches) {\n if (!ctx.classes.has(cls.slice(1))) return false;\n }\n return true;\n}\n\nfunction matchesParsedPart(part: ParsedPart, ctx: ElementContext): boolean {\n if (part.tag && part.tag !== ctx.tagName) return false;\n for (const cls of part.classes) {\n if (!ctx.classes.has(cls)) return false;\n }\n return true;\n}\n\n/** Pre-parsed selector ready for fast matching */\ninterface ParsedSelector {\n /** Parsed parts, rightmost first (match order) */\n parts: ParsedPart[];\n /** Combinators between parts[i] and parts[i+1]: '>' or ' ' */\n combinators: string[];\n /** Rightmost part — used for index lookup */\n rightmost: ParsedPart;\n /** Whether the rightmost part is 'html' or 'body' (matches root) */\n rightmostIsRoot: boolean;\n /** Original specificity */\n spec: [number, number, number];\n /**\n * Pseudo-element on the rightmost compound. Currently we only honor\n * `::marker` — its declarations apply to the marker box of `<li>`,\n * not to the element body.\n */\n pseudoElement?: 'marker';\n}\n\n/**\n * Pre-parse and tokenize a selector string into a ParsedSelector.\n * Returns null for selectors we can't handle (pseudo-classes; pseudo-elements\n * other than `::marker`).\n */\nfunction parseSelector(selector: string): ParsedSelector | null {\n // Detect `::marker` on the rightmost compound. Other pseudo-elements\n // (::before, ::after, ::first-line, …) are still rejected.\n let pseudoElement: 'marker' | undefined;\n if (selector.includes('::')) {\n // Allow only a single trailing ::marker on the rightmost compound.\n // Anything else is unsupported.\n const otherPseudo = selector.replace(/::marker\\b/g, '');\n if (otherPseudo.includes('::')) return null;\n if (/::marker\\b/.test(selector)) {\n pseudoElement = 'marker';\n // Bare `::marker` (start of input or after whitespace/`>`) → `*`\n // so descendant/child combinators are preserved.\n selector = selector.replace(/(^|[\\s>])::marker\\b/g, '$1*');\n // `::marker` glued to a tag/class compound → strip the pseudo only.\n selector = selector.replace(/::marker\\b/g, '');\n } else {\n return null;\n }\n }\n if (/:(?:nth-|hover|focus|active|visited|first-child|last-child)/.test(selector)) return null;\n\n const tokens: string[] = [];\n const combinators: string[] = [];\n\n const raw = selector.trim().split(/\\s+/);\n for (let i = 0; i < raw.length; i++) {\n if (raw[i] === '>') {\n combinators.push('>');\n } else {\n if (tokens.length > combinators.length + 1) {\n combinators.push(' ');\n }\n tokens.push(raw[i]);\n }\n }\n while (combinators.length < tokens.length - 1) {\n combinators.push(' ');\n }\n\n if (tokens.length === 0) return null;\n\n const parts = tokens.map(parsePart);\n const rightmost = parts[parts.length - 1];\n const rightmostTag = rightmost.tag;\n\n return {\n parts,\n combinators,\n rightmost,\n rightmostIsRoot: rightmostTag === 'html' || rightmostTag === 'body',\n spec: selectorSpecificity(selector),\n pseudoElement,\n };\n}\n\n/**\n * Match a pre-parsed selector against an element context.\n */\nfunction matchesParsedSelector(sel: ParsedSelector, ctx: ElementContext): boolean {\n // Quick check: rightmost part must match current element\n if (sel.rightmostIsRoot) {\n if (ctx.parent !== null) return false; // html/body only match root\n } else {\n if (!matchesParsedPart(sel.rightmost, ctx)) return false;\n }\n\n // Single-part selector — already matched\n if (sel.parts.length === 1) return true;\n\n // Walk ancestors for remaining parts (right-to-left)\n let current: ElementContext | null = ctx.parent;\n for (let ti = sel.parts.length - 2; ti >= 0; ti--) {\n if (!current) return false;\n const part = sel.parts[ti];\n const combinator = sel.combinators[ti];\n\n if (combinator === '>') {\n // Direct child: current ancestor must match\n const isRoot = current.parent === null;\n if (isRoot && (part.tag === 'html' || part.tag === 'body')) {\n current = current.parent;\n } else if (matchesParsedPart(part, current)) {\n current = current.parent;\n } else {\n return false;\n }\n } else {\n // Descendant: any ancestor must match\n let found = false;\n while (current) {\n const isRoot = current.parent === null;\n if (isRoot && (part.tag === 'html' || part.tag === 'body')) {\n current = current.parent;\n found = true;\n break;\n }\n if (matchesParsedPart(part, current)) {\n current = current.parent;\n found = true;\n break;\n }\n current = current.parent;\n }\n if (!found) return false;\n }\n }\n\n return true;\n}\n\n// ─── Style Resolution ────────────────────────────────────────────────\n\n/** Properties that inherit from parent to child */\nconst INHERITED_PROPERTIES = new Set([\n 'font-family', 'font-size', 'font-weight', 'font-style',\n 'color', 'text-align', 'text-align-last', 'text-indent', 'text-transform',\n 'text-decoration-line', 'text-decoration-style', 'text-decoration-color',\n 'letter-spacing', 'word-spacing', 'font-kerning',\n 'line-height', 'white-space', 'word-break', 'overflow-wrap',\n 'direction', 'text-shadow', 'list-style-type',\n 'vertical-align',\n]);\n\n/** Default values for all ResolvedStyle properties */\nfunction defaultStyle(): ResolvedStyle {\n return {\n fontFamily: 'sans-serif',\n fontSize: 16,\n fontWeight: 400,\n fontStyle: 'normal',\n color: 'rgb(0, 0, 0)',\n textAlign: 'start',\n textAlignLast: 'auto',\n textIndent: 0,\n textTransform: 'none',\n textDecorationLine: 'none',\n textDecorationStyle: 'solid',\n textDecorationColor: 'rgb(0, 0, 0)',\n textShadow: 'none',\n webkitTextStrokeWidth: 0,\n webkitTextStrokeColor: '',\n webkitTextFillColor: '',\n webkitBackgroundClip: '',\n backgroundImage: 'none',\n letterSpacing: 0,\n wordSpacing: 0,\n fontKerning: 'auto',\n lineHeight: 0,\n verticalAlign: 'baseline',\n whiteSpace: 'normal',\n wordBreak: 'normal',\n overflowWrap: 'normal',\n direction: 'ltr',\n display: 'block',\n width: 0,\n minHeight: 0,\n paddingTop: 0,\n paddingRight: 0,\n paddingBottom: 0,\n paddingLeft: 0,\n marginTop: 0,\n marginRight: 0,\n marginBottom: 0,\n marginLeft: 0,\n backgroundColor: 'rgba(0, 0, 0, 0)',\n borderTopWidth: 0,\n borderTopColor: 'rgb(0, 0, 0)',\n borderTopStyle: 'none',\n borderRightWidth: 0,\n borderRightColor: 'rgb(0, 0, 0)',\n borderRightStyle: 'none',\n borderBottomWidth: 0,\n borderBottomColor: 'rgb(0, 0, 0)',\n borderBottomStyle: 'none',\n borderLeftWidth: 0,\n borderLeftColor: 'rgb(0, 0, 0)',\n borderLeftStyle: 'none',\n flexDirection: 'row',\n gap: 0,\n flexGrow: 0,\n listStyleType: 'disc',\n };\n}\n\n/** Tag-level default display values and browser default margins */\nconst TAG_DEFAULTS: Record<string, Partial<ResolvedStyle>> = {\n span: { display: 'inline' },\n a: { display: 'inline' },\n strong: { display: 'inline', fontWeight: 700 },\n b: { display: 'inline', fontWeight: 700 },\n em: { display: 'inline', fontStyle: 'italic' },\n i: { display: 'inline', fontStyle: 'italic' },\n u: { display: 'inline', textDecorationLine: 'underline' },\n s: { display: 'inline', textDecorationLine: 'line-through' },\n strike: { display: 'inline', textDecorationLine: 'line-through' },\n del: { display: 'inline', textDecorationLine: 'line-through' },\n sub: { display: 'inline', verticalAlign: 'sub', fontSize: 0.83 },\n sup: { display: 'inline', verticalAlign: 'super', fontSize: 0.83 },\n code: { display: 'inline', fontFamily: 'monospace' },\n cite: { display: 'inline', fontStyle: 'italic' },\n p: { display: 'block', marginTop: -1, marginBottom: -1 }, // -1 = 1em, resolved later\n div: { display: 'block' },\n h1: { display: 'block', fontSize: 2, fontWeight: 700, marginTop: -0.67, marginBottom: -0.67 },\n h2: { display: 'block', fontSize: 1.5, fontWeight: 700, marginTop: -0.83, marginBottom: -0.83 },\n h3: { display: 'block', fontSize: 1.17, fontWeight: 700, marginTop: -1, marginBottom: -1 },\n h4: { display: 'block', fontSize: 1, fontWeight: 700, marginTop: -1.33, marginBottom: -1.33 },\n h5: { display: 'block', fontSize: 0.83, fontWeight: 700, marginTop: -1.67, marginBottom: -1.67 },\n h6: { display: 'block', fontSize: 0.67, fontWeight: 700, marginTop: -2.33, marginBottom: -2.33 },\n ul: { display: 'block', listStyleType: 'disc', marginTop: -1, marginBottom: -1 },\n ol: { display: 'block', listStyleType: 'decimal', marginTop: -1, marginBottom: -1 },\n li: { display: 'list-item' },\n blockquote: { display: 'block', marginTop: -1, marginBottom: -1, marginLeft: 40, marginRight: 40 },\n pre: { display: 'block', whiteSpace: 'pre', fontFamily: 'monospace', marginTop: -1, marginBottom: -1 },\n table: { display: 'table' },\n tr: { display: 'table-row' },\n td: { display: 'table-cell' },\n th: { display: 'table-cell', fontWeight: 700 },\n br: { display: 'inline' },\n hr: {\n display: 'block',\n borderTopWidth: 1,\n borderTopStyle: 'solid',\n borderTopColor: 'gray',\n marginTop: -0.5,\n marginBottom: -0.5,\n },\n};\n\n/**\n * Parse a CSS value to pixels given a parent font size for em/% resolution.\n */\nfunction parseValue(value: string, parentFontSize: number, containerWidth: number): number {\n if (!value || value === 'normal' || value === 'auto' || value === 'none') return 0;\n const trimmed = value.trim();\n\n if (trimmed.endsWith('em')) {\n const num = parseFloat(trimmed);\n return isNaN(num) ? 0 : num * parentFontSize;\n }\n if (trimmed.endsWith('%')) {\n const num = parseFloat(trimmed);\n return isNaN(num) ? 0 : (num / 100) * containerWidth;\n }\n if (trimmed.endsWith('px')) {\n const num = parseFloat(trimmed);\n return isNaN(num) ? 0 : num;\n }\n // Bare number (for line-height, etc.)\n const num = parseFloat(trimmed);\n return isNaN(num) ? 0 : num;\n}\n\nfunction parseFontWeight(value: string): number {\n if (value === 'bold') return 700;\n if (value === 'normal') return 400;\n const num = parseInt(value, 10);\n return isNaN(num) ? 400 : num;\n}\n\n/**\n * Expand shorthand properties into individual ones.\n * E.g., margin: 10px 20px → marginTop/Right/Bottom/Left\n */\nfunction expandShorthand(property: string, value: string): CSSDeclaration[] {\n if (property === 'margin' || property === 'padding') {\n const parts = value.trim().split(/\\s+/);\n let top: string, right: string, bottom: string, left: string;\n if (parts.length === 1) {\n top = right = bottom = left = parts[0];\n } else if (parts.length === 2) {\n top = bottom = parts[0];\n right = left = parts[1];\n } else if (parts.length === 3) {\n top = parts[0]; right = left = parts[1]; bottom = parts[2];\n } else {\n top = parts[0]; right = parts[1]; bottom = parts[2]; left = parts[3];\n }\n return [\n { property: `${property}-top`, value: top },\n { property: `${property}-right`, value: right },\n { property: `${property}-bottom`, value: bottom },\n { property: `${property}-left`, value: left },\n ];\n }\n\n if (property === 'border' || property === 'border-top' || property === 'border-right' ||\n property === 'border-bottom' || property === 'border-left') {\n const parts = value.trim().split(/\\s+/);\n const borderStyles = ['solid', 'dashed', 'dotted', 'double', 'none', 'hidden'];\n const width = parts.find(p => p.endsWith('px') || /^\\d/.test(p)) || '0';\n const style = parts.find(p => borderStyles.includes(p)) || 'none';\n const color = parts.find(p => !p.endsWith('px') && !/^\\d/.test(p) && !borderStyles.includes(p)) || 'currentColor';\n const result: CSSDeclaration[] = [];\n const sides = property === 'border'\n ? ['top', 'right', 'bottom', 'left']\n : [property.replace('border-', '')];\n for (const side of sides) {\n result.push({ property: `border-${side}-width`, value: width });\n result.push({ property: `border-${side}-style`, value: style });\n result.push({ property: `border-${side}-color`, value: color });\n }\n return result;\n }\n\n if (property === 'list-style') {\n // list-style: none → list-style-type: none\n if (value === 'none') {\n return [{ property: 'list-style-type', value: 'none' }];\n }\n return [{ property: 'list-style-type', value }];\n }\n\n if (property === 'text-decoration') {\n const v = value.trim();\n if (v === 'inherit' || v === 'none') {\n return [{ property: 'text-decoration-line', value: 'none' }];\n }\n // Extract color functions (rgb(...), hsl(...)) before splitting on whitespace,\n // because they contain spaces internally (e.g. \"rgb(231, 76, 60)\").\n let colorValue = '';\n const withoutColorFn = v.replace(/\\b(rgba?\\([^)]*\\)|hsla?\\([^)]*\\))/i, (match) => {\n colorValue = match;\n return '';\n });\n const parts = withoutColorFn.split(/\\s+/).filter(Boolean);\n const lineValues = ['underline', 'overline', 'line-through'];\n const styleValues = ['solid', 'double', 'dotted', 'dashed', 'wavy'];\n const result: CSSDeclaration[] = [];\n const lines: string[] = [];\n for (const p of parts) {\n if (lineValues.includes(p)) lines.push(p);\n else if (styleValues.includes(p)) result.push({ property: 'text-decoration-style', value: p });\n else if (p.startsWith('#'))\n result.push({ property: 'text-decoration-color', value: p });\n }\n if (colorValue) result.push({ property: 'text-decoration-color', value: colorValue });\n if (lines.length > 0) result.unshift({ property: 'text-decoration-line', value: lines.join(' ') });\n return result;\n }\n\n if (property === '-webkit-text-stroke') {\n // -webkit-text-stroke: 1px #1e40af → width + color\n const parts = value.trim().split(/\\s+/);\n const width = parts.find(p => p.endsWith('px') || /^\\d/.test(p)) || '0';\n const color = parts.find(p => !p.endsWith('px') && !/^\\d/.test(p)) || 'currentColor';\n return [\n { property: '-webkit-text-stroke-width', value: width },\n { property: '-webkit-text-stroke-color', value: color },\n ];\n }\n\n if (property === 'flex') {\n // flex: 1 → flex-grow: 1\n const parts = value.trim().split(/\\s+/);\n const grow = parseFloat(parts[0]);\n if (!isNaN(grow)) {\n return [{ property: 'flex-grow', value: String(grow) }];\n }\n return [];\n }\n\n if (property === 'border-collapse' || property === 'border-spacing') {\n // Ignored — table-specific properties we don't handle\n return [];\n }\n\n return [{ property, value }];\n}\n\n/**\n * Apply a CSS declaration to a ResolvedStyle, resolving units.\n */\nfunction applyDeclaration(\n style: ResolvedStyle,\n property: string,\n value: string,\n parentFontSize: number,\n containerWidth: number,\n direction: string,\n): void {\n // Resolve the font-size first if that's what we're setting, since\n // em values for other properties depend on the element's own font-size\n const fontSize = style.fontSize || parentFontSize;\n\n switch (property) {\n // Font & text\n case 'font-family': style.fontFamily = value.trim(); break;\n case 'font-size': {\n const v = value.trim();\n if (v.endsWith('em')) {\n style.fontSize = parseFloat(v) * parentFontSize;\n } else if (v.endsWith('%')) {\n style.fontSize = (parseFloat(v) / 100) * parentFontSize;\n } else {\n style.fontSize = parseFloat(v) || parentFontSize;\n }\n break;\n }\n case 'font-weight': style.fontWeight = parseFontWeight(value); break;\n case 'font-style': style.fontStyle = value.trim(); break;\n case 'color': style.color = value.trim(); break;\n case 'text-align': style.textAlign = value.trim(); break;\n case 'text-align-last': style.textAlignLast = value.trim(); break;\n case 'text-indent':\n style.textIndent = parseValue(value, fontSize, containerWidth); break;\n case 'text-transform': style.textTransform = value.trim(); break;\n case 'text-decoration-line': style.textDecorationLine = value.trim(); break;\n // text-decoration is expanded in expandShorthand, should not reach here\n // but handle just in case\n case 'text-decoration': break;\n case 'text-decoration-style': style.textDecorationStyle = value.trim(); break;\n case 'text-decoration-color': style.textDecorationColor = value.trim(); break;\n case 'text-shadow': style.textShadow = value.trim(); break;\n case '-webkit-text-stroke-width': style.webkitTextStrokeWidth = parseValue(value, fontSize, containerWidth); break;\n case '-webkit-text-stroke-color': style.webkitTextStrokeColor = value.trim(); break;\n case '-webkit-text-fill-color': style.webkitTextFillColor = value.trim(); break;\n case '-webkit-background-clip':\n case 'background-clip': style.webkitBackgroundClip = value.trim(); break;\n case 'background-image': style.backgroundImage = value.trim(); break;\n case 'letter-spacing':\n style.letterSpacing = value.trim() === 'normal' ? 0 : parseValue(value, fontSize, containerWidth); break;\n case 'word-spacing':\n style.wordSpacing = value.trim() === 'normal' ? 0 : parseValue(value, fontSize, containerWidth); break;\n case 'font-kerning': style.fontKerning = value.trim(); break;\n case 'line-height': {\n const v = value.trim();\n if (v === 'normal') {\n style.lineHeight = 0; // 0 signals \"normal\"\n } else if (v.endsWith('px')) {\n style.lineHeight = parseFloat(v) || 0;\n } else if (v.endsWith('em')) {\n style.lineHeight = parseFloat(v) * fontSize;\n } else {\n // Unitless multiplier — compute for this element's font size\n // and mark as unitless so children re-compute\n const num = parseFloat(v);\n if (!isNaN(num)) {\n style.lineHeight = num * fontSize;\n (style as any)._lineHeightMultiplier = num;\n }\n }\n break;\n }\n case 'vertical-align': style.verticalAlign = value.trim(); break;\n case 'white-space': style.whiteSpace = value.trim(); break;\n case 'word-break': style.wordBreak = value.trim(); break;\n case 'overflow-wrap':\n case 'word-wrap': style.overflowWrap = value.trim(); break;\n case 'direction': style.direction = value.trim(); break;\n\n // Box model\n case 'display': style.display = value.trim(); break;\n case 'width': {\n const v = value.trim();\n if (v === '100%') style.width = containerWidth;\n else if (v !== 'auto') style.width = parseValue(v, fontSize, containerWidth);\n break;\n }\n case 'min-height': style.minHeight = parseValue(value, fontSize, containerWidth); break;\n case 'padding-top': style.paddingTop = parseValue(value, fontSize, containerWidth); break;\n case 'padding-right': style.paddingRight = parseValue(value, fontSize, containerWidth); break;\n case 'padding-bottom': style.paddingBottom = parseValue(value, fontSize, containerWidth); break;\n case 'padding-left': style.paddingLeft = parseValue(value, fontSize, containerWidth); break;\n case 'margin-top': style.marginTop = parseValue(value, fontSize, containerWidth); break;\n case 'margin-right': style.marginRight = parseValue(value, fontSize, containerWidth); break;\n case 'margin-bottom': style.marginBottom = parseValue(value, fontSize, containerWidth); break;\n case 'margin-left': style.marginLeft = parseValue(value, fontSize, containerWidth); break;\n case 'background-color': style.backgroundColor = value.trim(); break;\n case 'background': {\n const v = value.trim();\n if (v.includes('gradient(')) {\n // background: linear-gradient(...) → backgroundImage\n style.backgroundImage = v;\n } else if (v.startsWith('#') || v.startsWith('rgb') || v.startsWith('hsl') ||\n ['transparent', 'none', 'inherit'].includes(v) ||\n /^[a-z]+$/.test(v)) {\n style.backgroundColor = v;\n }\n break;\n }\n\n // Logical properties → physical (based on direction)\n case 'padding-inline-start':\n if (direction === 'rtl') style.paddingRight = parseValue(value, fontSize, containerWidth);\n else style.paddingLeft = parseValue(value, fontSize, containerWidth);\n break;\n case 'padding-inline-end':\n if (direction === 'rtl') style.paddingLeft = parseValue(value, fontSize, containerWidth);\n else style.paddingRight = parseValue(value, fontSize, containerWidth);\n break;\n case 'margin-inline-start':\n if (direction === 'rtl') style.marginRight = parseValue(value, fontSize, containerWidth);\n else style.marginLeft = parseValue(value, fontSize, containerWidth);\n break;\n case 'margin-inline-end':\n if (direction === 'rtl') style.marginLeft = parseValue(value, fontSize, containerWidth);\n else style.marginRight = parseValue(value, fontSize, containerWidth);\n break;\n\n // Border\n case 'border-top-width': style.borderTopWidth = parseValue(value, fontSize, containerWidth); break;\n case 'border-top-color': style.borderTopColor = value.trim(); break;\n case 'border-top-style': style.borderTopStyle = value.trim(); break;\n case 'border-right-width': style.borderRightWidth = parseValue(value, fontSize, containerWidth); break;\n case 'border-right-color': style.borderRightColor = value.trim(); break;\n case 'border-right-style': style.borderRightStyle = value.trim(); break;\n case 'border-bottom-width': style.borderBottomWidth = parseValue(value, fontSize, containerWidth); break;\n case 'border-bottom-color': style.borderBottomColor = value.trim(); break;\n case 'border-bottom-style': style.borderBottomStyle = value.trim(); break;\n case 'border-left-width': style.borderLeftWidth = parseValue(value, fontSize, containerWidth); break;\n case 'border-left-color': style.borderLeftColor = value.trim(); break;\n case 'border-left-style': style.borderLeftStyle = value.trim(); break;\n\n // Flex\n case 'flex-direction': style.flexDirection = value.trim(); break;\n case 'gap': style.gap = parseValue(value, fontSize, containerWidth); break;\n case 'flex-grow': style.flexGrow = parseFloat(value) || 0; break;\n\n // List\n case 'list-style-type': style.listStyleType = value.trim(); break;\n\n // Ignored properties (not relevant for our layout)\n case 'position':\n case 'top':\n case 'left':\n case 'right':\n case 'bottom':\n case 'inset-inline-start':\n case 'inset-inline-end':\n case 'content':\n case 'counter-reset':\n case 'counter-increment':\n case 'border-radius':\n case 'border-top-left-radius':\n case 'border-top-right-radius':\n case 'border-bottom-left-radius':\n case 'border-bottom-right-radius':\n case 'cursor':\n case 'opacity':\n case 'overflow':\n case 'box-sizing':\n case 'outline':\n case 'transition':\n case 'transform':\n case 'font-stretch':\n case 'font-display':\n case 'src':\n case 'unicode-range':\n break;\n }\n}\n\n/** Inheritable property names (CSS kebab-case) mapped to ResolvedStyle keys */\nconst INHERITABLE_KEYS: [string, keyof ResolvedStyle][] = [\n ['font-family', 'fontFamily'],\n ['font-size', 'fontSize'],\n ['font-weight', 'fontWeight'],\n ['font-style', 'fontStyle'],\n ['color', 'color'],\n ['text-align', 'textAlign'],\n ['text-align-last', 'textAlignLast'],\n ['text-indent', 'textIndent'],\n ['text-transform', 'textTransform'],\n ['white-space', 'whiteSpace'],\n ['word-break', 'wordBreak'],\n ['overflow-wrap', 'overflowWrap'],\n ['direction', 'direction'],\n ['letter-spacing', 'letterSpacing'],\n ['word-spacing', 'wordSpacing'],\n ['line-height', 'lineHeight'],\n ['text-shadow', 'textShadow'],\n ['font-kerning', 'fontKerning'],\n ['list-style-type', 'listStyleType'],\n ['vertical-align', 'verticalAlign'],\n];\n\n/**\n * Inherit properties from parent style to child style for properties\n * not explicitly set (tracked via setProps).\n */\nfunction inheritFrom(child: ResolvedStyle, parent: ResolvedStyle, setProps: Set<string>): void {\n for (const [cssProp, key] of INHERITABLE_KEYS) {\n if (!setProps.has(cssProp)) {\n if (key === 'lineHeight') {\n // Unitless line-height: re-compute relative to child's font-size\n const multiplier = (parent as any)._lineHeightMultiplier;\n if (multiplier !== undefined) {\n child.lineHeight = multiplier * child.fontSize;\n (child as any)._lineHeightMultiplier = multiplier;\n } else {\n child.lineHeight = parent.lineHeight;\n }\n } else {\n (child as any)[key] = (parent as any)[key];\n }\n }\n }\n}\n\n// ─── Main resolver ───────────────────────────────────────────────────\n\ninterface MatchedDeclaration {\n property: string;\n value: string;\n specificity: [number, number, number];\n order: number;\n important: boolean;\n}\n\n/** A pre-processed rule entry with parsed selector and pre-expanded declarations */\ninterface ProcessedRule {\n selector: ParsedSelector;\n declarations: { property: string; value: string; important: boolean }[];\n /** Global order for cascade sorting */\n orderBase: number;\n}\n\n/**\n * Build an index of processed rules keyed by rightmost tag name and class names.\n * The '*' key holds rules that match any element (no tag or class constraint).\n */\nfunction buildRuleIndex(rules: CSSRule[]): {\n byTag: Map<string, ProcessedRule[]>;\n byClass: Map<string, ProcessedRule[]>;\n universal: ProcessedRule[];\n} {\n const byTag = new Map<string, ProcessedRule[]>();\n const byClass = new Map<string, ProcessedRule[]>();\n const universal: ProcessedRule[] = [];\n let orderBase = 0;\n\n for (const rule of rules) {\n // Pre-expand declarations once\n const expandedDecls: { property: string; value: string; important: boolean }[] = [];\n for (const decl of rule.declarations) {\n const isImportant = decl.value.includes('!important');\n const cleanValue = isImportant\n ? decl.value.replace(/\\s*!important\\s*/g, '').trim()\n : decl.value;\n const expanded = expandShorthand(decl.property, cleanValue);\n for (const exp of expanded) {\n expandedDecls.push({ property: exp.property, value: exp.value, important: isImportant });\n }\n }\n\n for (const sel of rule.selectors) {\n const parsed = parseSelector(sel);\n if (!parsed) continue;\n\n const entry: ProcessedRule = {\n selector: parsed,\n declarations: expandedDecls,\n orderBase: orderBase++,\n };\n\n const rm = parsed.rightmost;\n if (rm.tag && !parsed.rightmostIsRoot) {\n // Index by tag\n const list = byTag.get(rm.tag);\n if (list) list.push(entry);\n else byTag.set(rm.tag, [entry]);\n }\n if (rm.classes.length > 0) {\n // Index by first class (most selective)\n const cls = rm.classes[0];\n const list = byClass.get(cls);\n if (list) list.push(entry);\n else byClass.set(cls, [entry]);\n }\n if (!rm.tag && rm.classes.length === 0) {\n // Universal selector or html/body root\n universal.push(entry);\n }\n // Also add root-matching selectors to universal\n if (parsed.rightmostIsRoot) {\n universal.push(entry);\n }\n }\n }\n\n return { byTag, byClass, universal };\n}\n\n/** Format an integer using a CSS list-style-type. */\nfunction formatListMarker(n: number, type: string): string {\n switch (type) {\n case 'disc': return '•';\n case 'circle': return '○';\n case 'square': return '■';\n case 'none': return '';\n case 'decimal-leading-zero':\n return `${n < 10 && n >= 0 ? '0' + n : n}.`;\n case 'lower-roman': return `${toRoman(n).toLowerCase()}.`;\n case 'upper-roman': return `${toRoman(n)}.`;\n case 'lower-alpha':\n case 'lower-latin': return `${toAlpha(n).toLowerCase()}.`;\n case 'upper-alpha':\n case 'upper-latin': return `${toAlpha(n)}.`;\n case 'decimal':\n default:\n return `${n}.`;\n }\n}\n\nfunction toRoman(n: number): string {\n if (n < 1 || n > 3999) return `${n}`;\n const map: [number, string][] = [\n [1000, 'M'], [900, 'CM'], [500, 'D'], [400, 'CD'],\n [100, 'C'], [90, 'XC'], [50, 'L'], [40, 'XL'],\n [10, 'X'], [9, 'IX'], [5, 'V'], [4, 'IV'], [1, 'I'],\n ];\n let out = '';\n for (const [v, s] of map) {\n while (n >= v) { out += s; n -= v; }\n }\n return out;\n}\n\nfunction toAlpha(n: number): string {\n if (n < 1) return `${n}`;\n let out = '';\n while (n > 0) {\n const r = (n - 1) % 26;\n out = String.fromCharCode(65 + r) + out;\n n = Math.floor((n - 1) / 26);\n }\n return out;\n}\n\n/**\n * Detect list marker text for a <li> element based on tree position,\n * honoring list-style-type, <ol start>, <ol reversed>, and <li value>.\n */\nfunction getListMarker(el: Element, listStyleType: string): string | undefined {\n const tag = el.tagName.toLowerCase();\n if (tag !== 'li') return undefined;\n if (listStyleType === 'none') return '';\n\n const parent = el.parentElement;\n const parentTag = parent?.tagName.toLowerCase();\n\n // Bullet markers: independent of position.\n if (listStyleType === 'disc' || listStyleType === 'circle' || listStyleType === 'square') {\n return formatListMarker(0, listStyleType);\n }\n\n // Numbered markers: compute index from siblings + ol attributes + li value.\n if (parentTag === 'ol' || parentTag === 'ul' || !parent) {\n const liItems = parent\n ? Array.from(parent.children).filter(c => c.tagName.toLowerCase() === 'li')\n : [el];\n const startAttr = parent?.getAttribute('start');\n const reversed = parent?.hasAttribute('reversed') ?? false;\n const start = startAttr ? parseInt(startAttr, 10) : (reversed ? liItems.length : 1);\n const step = reversed ? -1 : 1;\n let n = start;\n for (const item of liItems) {\n const valueAttr = item.getAttribute('value');\n if (valueAttr) {\n const v = parseInt(valueAttr, 10);\n if (!Number.isNaN(v)) n = v;\n }\n if (item === el) return formatListMarker(n, listStyleType || 'decimal');\n n += step;\n }\n return formatListMarker(n, listStyleType || 'decimal');\n }\n\n return undefined;\n}\n\n/**\n * Parse inline style attribute into declarations.\n */\nfunction parseInlineStyle(styleAttr: string): CSSDeclaration[] {\n const declarations: CSSDeclaration[] = [];\n for (const decl of styleAttr.split(';')) {\n const colonIdx = decl.indexOf(':');\n if (colonIdx === -1) continue;\n const property = decl.slice(0, colonIdx).trim().toLowerCase();\n const value = decl.slice(colonIdx + 1).trim();\n if (property && value) {\n declarations.push({ property, value });\n }\n }\n return declarations;\n}\n\n/**\n * Resolve styles for a DOM tree without inserting into the document.\n * Parses CSS rules, matches selectors, resolves cascade + inheritance.\n */\nexport function resolveStylesFromCSS(\n fragment: DocumentFragment,\n css: string,\n containerWidth: number,\n): { tree: StyledNode; cleanup: () => void } {\n const { rules, fontFaceRules } = parseCSS(css);\n\n // Inject @font-face rules into the document so fonts can load\n let fontStyleEl: HTMLStyleElement | null = null;\n if (fontFaceRules.length > 0) {\n fontStyleEl = document.createElement('style');\n fontStyleEl.textContent = fontFaceRules.join('\\n');\n document.head.appendChild(fontStyleEl);\n }\n\n // Build indexed rule lookup\n const ruleIndex = buildRuleIndex(rules);\n\n // Wrap fragment in a container div so resolveElement has a single root Element\n const container = document.createElement('div');\n container.appendChild(fragment);\n\n function buildContext(el: Element, parent: ElementContext | null): ElementContext {\n const classes = new Set<string>();\n const className = el.getAttribute('class');\n if (className) {\n for (const c of className.split(/\\s+/)) {\n if (c) classes.add(c);\n }\n }\n return {\n tagName: el.tagName.toLowerCase(),\n classes,\n parent,\n el,\n };\n }\n\n function resolveElement(\n el: Element,\n parentStyle: ResolvedStyle,\n parentCtx: ElementContext | null,\n ): StyledNode {\n const tag = el.tagName.toLowerCase();\n const ctx = buildContext(el, parentCtx);\n\n // Start with defaults\n const style = defaultStyle();\n\n // Track which properties are explicitly set (tag defaults, CSS rules, inline styles)\n const setProps = new Set<string>();\n\n // --- Step 1: Determine font-size first (needed for em/multiplier resolution) ---\n\n // Collect candidate rules from index (only rules that could match this element)\n const candidates: ProcessedRule[] = [];\n const seen = new Set<ProcessedRule>();\n\n const tagRules = ruleIndex.byTag.get(tag);\n if (tagRules) for (const r of tagRules) { seen.add(r); candidates.push(r); }\n\n for (const cls of ctx.classes) {\n const clsRules = ruleIndex.byClass.get(cls);\n if (clsRules) for (const r of clsRules) {\n if (!seen.has(r)) { seen.add(r); candidates.push(r); }\n }\n }\n\n for (const r of ruleIndex.universal) {\n if (!seen.has(r)) { seen.add(r); candidates.push(r); }\n }\n\n // Match candidates and collect pre-expanded declarations.\n // Rules with `::marker` are routed to a separate list and applied to the\n // <li>'s markerStyle later — they do not affect the element body.\n const matched: MatchedDeclaration[] = [];\n const matchedMarker: MatchedDeclaration[] = [];\n for (const candidate of candidates) {\n if (matchesParsedSelector(candidate.selector, ctx)) {\n const target = candidate.selector.pseudoElement === 'marker' ? matchedMarker : matched;\n for (const decl of candidate.declarations) {\n target.push({\n property: decl.property,\n value: decl.value,\n specificity: candidate.selector.spec,\n order: candidate.orderBase,\n important: decl.important,\n });\n }\n }\n }\n\n // Tag default font-size\n const tagDef = TAG_DEFAULTS[tag];\n let fontSizeSet = false;\n if (tagDef?.fontSize !== undefined) {\n const val = tagDef.fontSize as number;\n if (val < 10) {\n style.fontSize = val * parentStyle.fontSize;\n } else {\n style.fontSize = val;\n }\n fontSizeSet = true;\n setProps.add('font-size');\n }\n\n // Sort by: !important first, then specificity, then source order\n if (matched.length > 1) {\n matched.sort((a, b) => {\n if (a.important !== b.important) return a.important ? 1 : -1;\n const sa = a.specificity, sb = b.specificity;\n if (sa[0] !== sb[0]) return sa[0] - sb[0];\n if (sa[1] !== sb[1]) return sa[1] - sb[1];\n if (sa[2] !== sb[2]) return sa[2] - sb[2];\n return a.order - b.order;\n });\n }\n\n // Apply font-size from CSS rules\n for (const m of matched) {\n if (m.property === 'font-size') {\n applyDeclaration(style, m.property, m.value, parentStyle.fontSize, containerWidth, parentStyle.direction);\n fontSizeSet = true;\n }\n }\n\n // Apply font-size from inline styles\n if (el instanceof HTMLElement && el.style.cssText) {\n const inlineDecls = parseInlineStyle(el.style.cssText);\n for (const decl of inlineDecls) {\n if (decl.property === 'font-size') {\n applyDeclaration(style, decl.property, decl.value, parentStyle.fontSize, containerWidth, parentStyle.direction);\n fontSizeSet = true;\n }\n }\n }\n\n // Inherit font-size from parent if not set\n if (!fontSizeSet) {\n style.fontSize = parentStyle.fontSize;\n }\n\n // Now style.fontSize is the element's computed font-size\n const elemFontSize = style.fontSize;\n\n // --- Step 2: Apply all other properties using resolved font-size ---\n\n // Apply non-fontSize tag defaults\n if (tagDef) {\n for (const [key, val] of Object.entries(tagDef)) {\n if (key === 'fontSize') continue; // already handled\n (style as any)[key] = val;\n const cssKey = key.replace(/[A-Z]/g, m => '-' + m.toLowerCase());\n setProps.add(cssKey);\n }\n\n // Resolve negative margin values (em multipliers from tag defaults)\n if (style.marginTop < 0) style.marginTop = Math.abs(style.marginTop) * elemFontSize;\n if (style.marginBottom < 0) style.marginBottom = Math.abs(style.marginBottom) * elemFontSize;\n\n // Default padding-inline-start for lists (direction-aware)\n if (tag === 'ul' || tag === 'ol') {\n const dir = parentStyle.direction;\n if (dir === 'rtl') {\n style.paddingRight = 40;\n setProps.add('padding-right');\n } else {\n style.paddingLeft = 40;\n setProps.add('padding-left');\n }\n }\n }\n\n // Determine direction from parent for logical property resolution\n const direction = parentStyle.direction;\n\n // Property aliases: CSS name → canonical name for setProps tracking\n const PROP_ALIASES: Record<string, string> = {\n 'word-wrap': 'overflow-wrap',\n };\n\n // Apply matched CSS declarations (skip font-size, already applied)\n for (const m of matched) {\n if (m.property === 'font-size') continue;\n applyDeclaration(style, m.property, m.value, elemFontSize, containerWidth, direction);\n setProps.add(PROP_ALIASES[m.property] || m.property);\n }\n\n // Apply inline styles (highest specificity, skip font-size)\n const hasInlineWidth = el instanceof HTMLElement && !!el.style.width;\n if (el instanceof HTMLElement && el.style.cssText) {\n const inlineDecls = parseInlineStyle(el.style.cssText);\n for (const decl of inlineDecls) {\n if (decl.property === 'font-size') {\n setProps.add('font-size');\n continue;\n }\n const expanded = expandShorthand(decl.property, decl.value);\n for (const exp of expanded) {\n applyDeclaration(style, exp.property, exp.value, elemFontSize, containerWidth, direction);\n setProps.add(PROP_ALIASES[exp.property] || exp.property);\n }\n }\n }\n\n // Only keep explicit width from inline styles (match DOM resolver behavior)\n if (!hasInlineWidth) {\n style.width = 0;\n }\n\n // Handle `dir` attribute\n const dirAttr = el.getAttribute('dir');\n if (dirAttr) {\n style.direction = dirAttr;\n setProps.add('direction');\n }\n\n // Inherit from parent for properties not explicitly set\n setProps.add('font-size'); // already resolved\n inheritFrom(style, parentStyle, setProps);\n\n // Auto-set currentColor defaults (browser default behavior)\n if (!setProps.has('text-decoration-color')) {\n style.textDecorationColor = style.color;\n }\n if (!setProps.has('-webkit-text-stroke-color')) {\n if (style.webkitTextStrokeColor === '' || style.webkitTextStrokeColor === 'currentColor') {\n style.webkitTextStrokeColor = style.color;\n }\n } else if (style.webkitTextStrokeColor === 'currentColor') {\n style.webkitTextStrokeColor = style.color;\n }\n for (const side of ['Top', 'Right', 'Bottom', 'Left'] as const) {\n const colorKey = `border${side}Color` as keyof ResolvedStyle;\n const propName = `border-${side.toLowerCase()}-color`;\n if (!setProps.has(propName)) {\n (style as any)[colorKey] = style.color;\n } else if ((style as any)[colorKey] === 'currentColor') {\n (style as any)[colorKey] = style.color;\n }\n }\n\n // Handle text-decoration inheritance (propagates visually, not via normal inheritance)\n const decoSet = new Set(style.textDecorationLine.split(/\\s+/).filter(d => d && d !== 'none'));\n if (parentStyle.textDecorationLine && parentStyle.textDecorationLine !== 'none') {\n for (const d of parentStyle.textDecorationLine.split(/\\s+/)) {\n if (d && d !== 'none') decoSet.add(d);\n }\n }\n if (decoSet.size > 0) {\n style.textDecorationLine = [...decoSet].join(' ');\n }\n\n // List marker\n const marker = getListMarker(el, style.listStyleType);\n\n // Resolve `::marker` rules into a Partial<ResolvedStyle> override and a\n // hidden flag. We only do this for `<li>` because `::marker` only applies\n // to elements with `display: list-item` (in our model, just `<li>`).\n // The override records ONLY the keys actually written by marker\n // declarations, so the layout consumer can distinguish \"user set padding\n // to 0\" from \"no rule\".\n let markerStyle: Partial<ResolvedStyle> | undefined;\n let markerHidden = false;\n if (tag === 'li' && matchedMarker.length > 0) {\n // Sort by cascade order — same rules as element style.\n if (matchedMarker.length > 1) {\n matchedMarker.sort((a, b) => {\n if (a.important !== b.important) return a.important ? 1 : -1;\n const sa = a.specificity, sb = b.specificity;\n if (sa[0] !== sb[0]) return sa[0] - sb[0];\n if (sa[1] !== sb[1]) return sa[1] - sb[1];\n if (sa[2] !== sb[2]) return sa[2] - sb[2];\n return a.order - b.order;\n });\n }\n\n // Apply to a scratch style cloned from the resolved <li> style, then\n // copy out the keys that changed. Whitelist the physical fields we\n // actually consume in addListMarker — adding more later is a one-line\n // change once the layout side reads them.\n const TRACKED: (keyof ResolvedStyle)[] = [\n 'paddingLeft', 'paddingRight',\n 'fontSize', 'fontFamily', 'fontWeight', 'fontStyle',\n 'color', 'letterSpacing',\n ];\n const scratch = { ...style } as ResolvedStyle;\n const touched = new Set<keyof ResolvedStyle>();\n for (const m of matchedMarker) {\n // `content: none` (and `content: ''`) suppresses the marker entirely,\n // matching DOM `::marker` behavior. `content` isn't part of\n // ResolvedStyle, so we handle it inline.\n if (m.property === 'content') {\n const v = m.value.trim().toLowerCase();\n if (v === 'none' || v === '\"\"' || v === \"''\" || v === 'normal') {\n // 'normal' is the initial value — no override\n markerHidden = (v === 'none' || v === '\"\"' || v === \"''\");\n }\n continue;\n }\n const before = TRACKED.map(k => scratch[k]);\n applyDeclaration(scratch, m.property, m.value, elemFontSize, containerWidth, direction);\n TRACKED.forEach((k, i) => {\n if (scratch[k] !== before[i]) touched.add(k);\n });\n }\n if (touched.size > 0) {\n markerStyle = {};\n for (const k of touched) (markerStyle as any)[k] = scratch[k];\n }\n }\n\n // Walk children\n const children: StyledNode[] = [];\n for (const child of el.childNodes) {\n const childNode = walkNode(child, style, ctx);\n if (childNode) children.push(childNode);\n }\n\n return {\n element: el,\n tagName: tag,\n style,\n children,\n textContent: null,\n listMarker: marker,\n markerStyle,\n markerHidden: markerHidden || undefined,\n };\n }\n\n function walkNode(\n node: Node,\n parentStyle: ResolvedStyle,\n parentCtx: ElementContext | null,\n ): StyledNode | null {\n if (node.nodeType === Node.TEXT_NODE) {\n const text = node.textContent;\n if (!text) return null;\n\n if (text.trim() === '' && !text.includes('\\u00A0')) {\n const ws = parentStyle.whiteSpace;\n const prev = node.previousSibling;\n const next = node.nextSibling;\n const isInlineSibling = (n: Node | null) => {\n if (!n || n.nodeType !== Node.ELEMENT_NODE) return n?.nodeType === Node.TEXT_NODE;\n const tag = (n as Element).tagName.toLowerCase();\n const def = TAG_DEFAULTS[tag];\n const d = def?.display || 'block';\n return d === 'inline' || d === 'inline-block';\n };\n\n if (prev && next && !isInlineSibling(prev) && !isInlineSibling(next)) {\n if (ws === 'pre' || ws === 'pre-wrap' || ws === 'pre-line') {\n // Keep\n } else {\n return null;\n }\n }\n\n if (ws !== 'pre' && ws !== 'pre-wrap' && ws !== 'pre-line') {\n if (text.includes('\\n')) return null;\n }\n }\n\n // Clone parent style for text node (text nodes don't match CSS rules)\n const style = { ...parentStyle };\n\n // CSS Text 3 §4.1.1: in `normal` and `nowrap`, a source newline is\n // collapsed to a single space (no forced break). Only `pre`,\n // `pre-wrap`, `pre-line`, and `break-spaces` preserve newlines.\n // <br>-derived text nodes are created separately below with `\\n`\n // and are not touched here, so they keep forcing breaks.\n const ws = parentStyle.whiteSpace;\n let normalizedText = text;\n if (ws !== 'pre' && ws !== 'pre-wrap' && ws !== 'pre-line' && ws !== 'break-spaces') {\n normalizedText = text.replace(/[\\n\\r]/g, ' ');\n }\n\n return {\n element: null,\n tagName: '#text',\n style,\n children: [],\n textContent: normalizedText,\n };\n }\n\n if (node.nodeType !== Node.ELEMENT_NODE) return null;\n\n const el = node as Element;\n const tag = el.tagName.toLowerCase();\n if (tag === 'style' || tag === 'script') return null;\n\n // <br> → text node with newline\n if (tag === 'br') {\n return {\n element: null,\n tagName: '#text',\n style: { ...parentStyle },\n children: [],\n textContent: '\\n',\n };\n }\n\n return resolveElement(el, parentStyle, parentCtx);\n }\n\n const rootStyle = defaultStyle();\n const tree = resolveElement(container, rootStyle, null);\n\n const cleanup = () => {\n if (fontStyleEl) fontStyleEl.remove();\n };\n\n return { tree, cleanup };\n}\n","import type { StyledNode, LayoutNode, LayoutBox, LayoutText, ResolvedStyle } from './types.js';\n\n// Module-level flag controlling DOM measurement usage.\n// Set by buildLayoutTree() based on the useDomMeasurements option.\nlet _useDomMeasurements = true;\nlet _debug: ((entry: import('./types.ts').DebugEntry) => void) | undefined;\n\n// ─── measureText width cache ──────────────────────────────────────────\n// Caches ctx.measureText(text).width keyed by \"font\\0text\".\n// Cleared at the start of each buildLayoutTree() call.\nconst _measureCache = new Map<string, number>();\n\nfunction cachedMeasureWidth(ctx: CanvasRenderingContext2D, text: string): number {\n // ctx.font must already be set by caller\n const key = ctx.font + '\\0' + text;\n const cached = _measureCache.get(key);\n if (cached !== undefined) return cached;\n const w = ctx.measureText(text).width;\n _measureCache.set(key, w);\n return w;\n}\n\n\n/**\n * Check if a line has mixed fonts (different fontFamily/fontSize/fontWeight/fontStyle).\n */\nfunction hasMixedFonts(words: Word[]): boolean {\n let font = '';\n for (const w of words) {\n if (!w.text || w.isSpace) continue;\n const f = buildCanvasFont(w.style);\n if (font && f !== font) return true;\n font = f;\n }\n return false;\n}\n\n// ─── Canvas font helpers ───────────────────────────────────────────────\n\n/**\n * Set canvas font and kerning from resolved style.\n */\nfunction applyFont(ctx: CanvasRenderingContext2D, style: ResolvedStyle): void {\n ctx.font = buildCanvasFont(style);\n ctx.fontKerning = style.fontKerning === 'none' ? 'none' : 'normal';\n}\n\n/**\n * Build a canvas font string from resolved style. Results are cached.\n */\nconst _fontStringCache = new Map<string, string>();\nexport function buildCanvasFont(style: ResolvedStyle): string {\n const key = `${style.fontStyle}|${style.fontWeight}|${style.fontSize}|${style.fontFamily}`;\n const cached = _fontStringCache.get(key);\n if (cached) return cached;\n const parts: string[] = [];\n if (style.fontStyle !== 'normal') parts.push(style.fontStyle);\n if (style.fontWeight !== 400) parts.push(String(style.fontWeight));\n parts.push(`${style.fontSize}px`);\n parts.push(style.fontFamily);\n const result = parts.join(' ');\n _fontStringCache.set(key, result);\n return result;\n}\n\n/**\n * Cache for DOM-measured line heights.\n * Key: \"font|lineHeight|probeType\" → actual pixel height from the browser.\n */\nconst _lineHeightCache = new Map<string, number>();\n\n// Probe elements: a <div> for general use, and a <ul><li> for unordered list items.\n// Firefox renders <ul><li> with bullet markers (disc/circle/square) 1.5px taller\n// than other elements for the same line-height, due to the ::marker pseudo-element.\n// <ol><li> items do NOT have this extra height.\nlet _blockProbe: HTMLDivElement | null = null;\nlet _ulProbeContainer: HTMLUListElement | null = null;\nlet _ulProbeLi: HTMLLIElement | null = null;\n\nconst BULLET_MARKERS = new Set(['disc', 'circle', 'square']);\n\n/**\n * Measure the actual line height using a hidden DOM element.\n * Uses an actual <li> inside a <ul> when listStyleType is a bullet marker\n * (disc/circle/square) to capture Firefox's ::marker line box contribution.\n * Results are cached per font+lineHeight+probeType combination.\n */\nfunction measureDomLineHeight(font: string, lineHeight: string, useBulletProbe = false): number {\n const key = `${font}|${lineHeight}|${useBulletProbe ? 'ul-li' : 'block'}`;\n const cached = _lineHeightCache.get(key);\n if (cached !== undefined) return cached;\n\n let probe: HTMLElement;\n if (useBulletProbe) {\n if (!_ulProbeContainer) {\n _ulProbeContainer = document.createElement('ul');\n _ulProbeContainer.style.cssText =\n 'position:absolute;top:-9999px;left:-9999px;visibility:hidden;padding:0;margin:0;border:0;list-style:disc;';\n _ulProbeLi = document.createElement('li');\n _ulProbeLi.style.cssText = 'white-space:nowrap;padding:0;margin:0;border:0;';\n _ulProbeLi.textContent = 'Mg';\n _ulProbeContainer.appendChild(_ulProbeLi);\n document.body.appendChild(_ulProbeContainer);\n }\n probe = _ulProbeLi!;\n } else {\n if (!_blockProbe) {\n _blockProbe = document.createElement('div');\n _blockProbe.style.cssText =\n 'position:absolute;top:-9999px;left:-9999px;visibility:hidden;white-space:nowrap;padding:0;margin:0;border:0;';\n _blockProbe.textContent = 'Mg';\n document.body.appendChild(_blockProbe);\n }\n probe = _blockProbe;\n }\n\n probe.style.font = font;\n probe.style.lineHeight = lineHeight;\n const height = probe.getBoundingClientRect().height;\n\n _lineHeightCache.set(key, height);\n return height;\n}\n\n/**\n * Get the effective line height for a style.\n * Uses DOM measurement for accuracy across browsers (Firefox vs Chrome).\n * Falls back to canvas metrics for \"normal\" line-height.\n */\nfunction getLineHeight(ctx: CanvasRenderingContext2D, style: ResolvedStyle, useBulletProbe = false): number {\n if (style.lineHeight > 0) {\n if (_useDomMeasurements) {\n const font = buildCanvasFont(style);\n return measureDomLineHeight(font, `${style.lineHeight}px`, useBulletProbe);\n }\n // Canvas-only: use the CSS line-height value directly\n return style.lineHeight;\n }\n\n if (_useDomMeasurements) {\n const font = buildCanvasFont(style);\n return measureDomLineHeight(font, 'normal', useBulletProbe);\n }\n\n // Canvas-only fallback for \"normal\" line-height: use font bounding box\n // fontBoundingBoxAscent + fontBoundingBoxDescent already represents the\n // full line box height, no multiplier needed.\n const { ascent, descent } = getFontMetrics(ctx, style);\n return ascent + descent;\n}\n\n/**\n * Compute the baseline Y offset within a line.\n * Uses the Konva approach: center (ascent - descent) within lineHeight.\n */\nfunction computeBaselineY(ctx: CanvasRenderingContext2D, style: ResolvedStyle, lineHeight: number): number {\n const { ascent, descent } = getFontMetrics(ctx, style);\n return (ascent - descent) / 2 + lineHeight / 2;\n}\n\nfunction applyTextTransform(text: string, transform: string): string {\n\n switch (transform) {\n case 'uppercase': return text.toUpperCase();\n case 'lowercase': return text.toLowerCase();\n case 'capitalize': return text.replace(/(^|[\\s\\p{P}])(\\p{L})/gu, (_, p, c) => p + c.toUpperCase());\n default: return text;\n }\n}\n\nfunction isInline(node: StyledNode): boolean {\n if (node.tagName === '#text') return true;\n const d = node.style.display;\n return d === 'inline' || d === 'inline-block';\n}\n\nfunction hasOnlyInlineChildren(node: StyledNode): boolean {\n return node.children.length > 0 && node.children.every(isInline);\n}\n\nexport function isTransparent(color: string): boolean {\n return !color || color === 'transparent' || color === 'rgba(0, 0, 0, 0)';\n}\n\n/**\n * Get font ascent and descent metrics. Results are cached per font string.\n */\nconst _fontMetricsCache = new Map<string, { ascent: number; descent: number }>();\nexport function getFontMetrics(ctx: CanvasRenderingContext2D, style: ResolvedStyle): { ascent: number; descent: number } {\n const font = buildCanvasFont(style);\n const cached = _fontMetricsCache.get(font);\n if (cached) return cached;\n ctx.font = font;\n const m = ctx.measureText('M');\n const ascent = m.fontBoundingBoxAscent ?? m.actualBoundingBoxAscent;\n const descent = m.fontBoundingBoxDescent ?? m.actualBoundingBoxDescent;\n const result = { ascent, descent };\n _fontMetricsCache.set(font, result);\n return result;\n}\n\n/**\n * Check if two styles have the same text rendering properties.\n */\nfunction sameTextStyle(a: ResolvedStyle, b: ResolvedStyle): boolean {\n return a.fontFamily === b.fontFamily &&\n a.fontSize === b.fontSize &&\n a.fontWeight === b.fontWeight &&\n a.fontStyle === b.fontStyle &&\n a.color === b.color &&\n a.textDecorationLine === b.textDecorationLine &&\n a.backgroundColor === b.backgroundColor;\n}\n\nfunction hasVisibleBoxStyles(style: ResolvedStyle): boolean {\n if (!isTransparent(style.backgroundColor)) return true;\n if (style.borderTopWidth > 0 && style.borderTopStyle !== 'none') return true;\n if (style.borderRightWidth > 0 && style.borderRightStyle !== 'none') return true;\n if (style.borderBottomWidth > 0 && style.borderBottomStyle !== 'none') return true;\n if (style.borderLeftWidth > 0 && style.borderLeftStyle !== 'none') return true;\n return false;\n}\n\n// ─── Inline text run types ─────────────────────────────────────────────\n\ninterface TextRun {\n text: string;\n style: ResolvedStyle;\n /** If this run came from an inline element with visible box styles */\n boxStyle?: ResolvedStyle;\n /** Marks the start of an inline box */\n boxOpen?: ResolvedStyle;\n /** Marks the end of an inline box */\n boxClose?: ResolvedStyle;\n}\n\ninterface Word {\n text: string;\n width: number;\n style: ResolvedStyle;\n isSpace: boolean;\n /** Tab character — width computed dynamically based on position */\n isTab?: boolean;\n /** Word came from soft-hyphen split — show '-' if this word ends a line */\n isSoftHyphenBreak?: boolean;\n boxStyle?: ResolvedStyle;\n /** Marks the start of an inline box (adds left padding/border) */\n boxOpen?: ResolvedStyle;\n /** Marks the end of an inline box (adds right padding/border) */\n boxClose?: ResolvedStyle;\n}\n\ninterface PositionedLine {\n words: Word[];\n totalWidth: number;\n lineHeight: number;\n /** True if this line ends at a forced break (\\n or <br>). Such a line is\n * treated as a \"last line\" for text-align — never justified. */\n endedByHardBreak?: boolean;\n}\n\n// ─── Inline layout ─────────────────────────────────────────────────────\n\n/**\n * Collect text runs from inline children, preserving style and tracking\n * inline elements with visible backgrounds. Emits open/close markers\n * for inline boxes so padding/border can be applied.\n */\nfunction collectTextRuns(node: StyledNode): TextRun[] {\n const runs: TextRun[] = [];\n\n function walk(n: StyledNode, boxStyle?: ResolvedStyle) {\n if (n.tagName === '#text' && n.textContent) {\n runs.push({ text: n.textContent, style: n.style, boxStyle });\n return;\n }\n const isInlineBlock = n.style.display === 'inline-block';\n // Inline-block always needs box treatment (padding/margin affect layout)\n const isBox = isInlineBlock || (isInline(n) && hasVisibleBoxStyles(n.style));\n const newBoxStyle = isBox ? n.style : boxStyle;\n const hasHorizSpacing = isBox && (n.style.paddingLeft > 0 || n.style.paddingRight > 0 ||\n n.style.borderLeftWidth > 0 || n.style.borderRightWidth > 0);\n\n if (isInlineBlock) {\n // Inline-block is fully atomic — the entire element (margins + padding + text)\n // wraps as one unit. We emit a single \"atomic\" TextRun with a special marker\n // so the tokenizer creates one non-splittable word with the full box width.\n const allText = n.element?.textContent || '';\n runs.push({\n text: allText,\n style: n.style,\n boxStyle: newBoxStyle,\n // Store the full box info for atomic inline-block handling\n boxOpen: n.style, // signals this is a boxed element\n boxClose: n.style,\n });\n return;\n }\n\n if (hasHorizSpacing) {\n runs.push({ text: '', style: n.style, boxStyle: newBoxStyle, boxOpen: n.style });\n }\n\n for (const child of n.children) {\n walk(child, isBox ? newBoxStyle : boxStyle);\n }\n\n if (hasHorizSpacing) {\n runs.push({ text: '', style: n.style, boxStyle: newBoxStyle, boxClose: n.style });\n }\n }\n\n for (const child of node.children) {\n walk(child);\n }\n return runs;\n}\n\n/**\n * Check if text needs Intl.Segmenter for word breaking (Thai, Khmer, Lao, Myanmar).\n * These scripts don't use spaces between words.\n */\nfunction needsSegmenter(text: string): boolean {\n for (let i = 0; i < text.length; i++) {\n const code = text.codePointAt(i)!;\n if (\n (code >= 0x0E00 && code <= 0x0E7F) || // Thai\n (code >= 0x0E80 && code <= 0x0EFF) || // Lao\n (code >= 0x1000 && code <= 0x109F) || // Myanmar\n (code >= 0x1780 && code <= 0x17FF) // Khmer\n ) return true;\n if (code > 0xFFFF) i++; // skip surrogate pair\n }\n return false;\n}\n\nlet _segmenter: Intl.Segmenter | undefined;\nfunction getSegmenter(): Intl.Segmenter | null {\n if (_segmenter) return _segmenter;\n if (typeof Intl !== 'undefined' && Intl.Segmenter) {\n _segmenter = new Intl.Segmenter(undefined, { granularity: 'word' });\n return _segmenter;\n }\n return null;\n}\n\n/**\n * Tokenize a single string into words based on whitespace mode.\n */\nfunction tokenizeString(ctx: CanvasRenderingContext2D, text: string, run: TextRun, allWords: Word[], cumState?: { cumText: string; cumWidth: number }): void {\n // Split on zero-width spaces and soft hyphens (break opportunities).\n // Pass cumulative state through so pieces are measured as one text run\n // (preserving kerning accuracy across break points).\n if (text.includes('\\u200B') || text.includes('\\u00AD')) {\n const parts = text.split(/(\\u200B|\\u00AD)/);\n // Share cumulative state across all sub-parts for accurate measurement\n const sharedState = cumState ?? { cumText: '', cumWidth: 0 };\n let nextIsSoftHyphen = false;\n for (const part of parts) {\n if (part === '\\u00AD') {\n nextIsSoftHyphen = true;\n continue;\n }\n if (part === '\\u200B' || part === '') {\n nextIsSoftHyphen = false;\n continue;\n }\n const prevLen = allWords.length;\n tokenizeString(ctx, part, run, allWords, sharedState);\n if (nextIsSoftHyphen && prevLen > 0) {\n allWords[prevLen - 1].isSoftHyphenBreak = true;\n }\n nextIsSoftHyphen = false;\n }\n if (nextIsSoftHyphen && allWords.length > 0) {\n allWords[allWords.length - 1].isSoftHyphenBreak = true;\n }\n return;\n }\n\n // `pre-line` preserves newlines (handled by the \\n pre-split in\n // tokenizeRuns) but collapses spaces and tabs — so it goes through the\n // non-preserving branch below, same as `normal`.\n const isPreserve = run.style.whiteSpace === 'pre' ||\n run.style.whiteSpace === 'pre-wrap' ||\n run.style.whiteSpace === 'break-spaces';\n\n if (isPreserve) {\n // Split on spaces and tabs, keeping delimiters\n const words = text.split(/( +|\\t)/);\n const tabStopInterval = cachedMeasureWidth(ctx, ' ') * 8; // CSS default: 8 spaces\n for (const w of words) {\n if (w === '') continue;\n if (w === '\\t') {\n // Tab width depends on current position — mark it for dynamic calculation\n allWords.push({\n text: '\\t',\n width: tabStopInterval, // placeholder — recalculated in flowWordsIntoLines\n style: run.style,\n isSpace: true,\n isTab: true,\n boxStyle: run.boxStyle,\n });\n continue;\n }\n const isSpace = /^ +$/.test(w);\n allWords.push({\n text: w,\n width: cachedMeasureWidth(ctx, w),\n style: run.style,\n isSpace,\n boxStyle: run.boxStyle,\n });\n }\n } else {\n // Split on whitespace but NOT on non-breaking spaces (\\u00A0)\n const words = text.split(/([ \\t\\n\\r\\f\\v]+)/);\n\n // Use cumulative measurement to avoid rounding error accumulation\n // within a single text run. When cumState is provided (from \\u200B/\\u00AD\n // split), continue from the previous cumulative position to preserve\n // kerning accuracy across break points.\n let cumText = cumState?.cumText ?? '';\n let cumWidth = cumState?.cumWidth ?? 0;\n\n for (const w of words) {\n if (w === '') continue;\n const isSpace = /^[ \\t\\n\\r\\f\\v]+$/.test(w);\n\n if (isSpace) {\n const prevCum = cumWidth;\n cumText += ' ';\n cumWidth = ctx.measureText(cumText).width;\n const spaceWidth = cumWidth - prevCum + (run.style.wordSpacing || 0);\n allWords.push({\n text: ' ',\n width: spaceWidth,\n style: run.style,\n isSpace: true,\n boxStyle: run.boxStyle,\n });\n continue;\n }\n\n // Use Intl.Segmenter for scripts without spaces (Thai, Khmer, etc.)\n if (needsSegmenter(w)) {\n const segmenter = getSegmenter();\n if (segmenter) {\n for (const seg of segmenter.segment(w)) {\n const s = seg.segment;\n const prevCum = cumWidth;\n cumText += s;\n cumWidth = ctx.measureText(cumText).width;\n allWords.push({\n text: s,\n width: cumWidth - prevCum,\n style: run.style,\n isSpace: false,\n boxStyle: run.boxStyle,\n });\n }\n continue;\n }\n }\n\n const prevCum = cumWidth;\n cumText += w;\n cumWidth = ctx.measureText(cumText).width;\n let width = cumWidth - prevCum;\n const directWidth = cachedMeasureWidth(ctx, w);\n if (_debug) {\n _debug({\n type: 'measure-word',\n message: `\"${w}\" delta=${width.toFixed(2)} direct=${directWidth.toFixed(2)} diff=${(width - directWidth).toFixed(2)} cumText=\"${cumText}\"`,\n data: { text: w, deltaWidth: width, directWidth, cumWidth, prevCum, font: run.style.fontFamily, fontSize: run.style.fontSize },\n });\n }\n allWords.push({\n text: w,\n width,\n style: run.style,\n isSpace: false,\n boxStyle: run.boxStyle,\n });\n }\n\n // Propagate cumulative state back to caller (for \\u200B/\\u00AD splits)\n if (cumState) {\n cumState.cumText = cumText;\n cumState.cumWidth = cumWidth;\n }\n }\n}\n\n/**\n * Tokenize text runs into words for line wrapping.\n */\nfunction tokenizeRuns(ctx: CanvasRenderingContext2D, runs: TextRun[]): Word[] {\n const allWords: Word[] = [];\n\n for (const run of runs) {\n // Handle inline-block margins (empty text, no boxOpen/boxClose)\n if (run.text === '' && !run.boxOpen && !run.boxClose) {\n const margin = run.style.display === 'inline-block'\n ? (run.style.marginLeft || run.style.marginRight || 0)\n : 0;\n if (margin > 0) {\n allWords.push({ text: '', width: margin, style: run.style, isSpace: false, boxStyle: run.boxStyle });\n }\n continue;\n }\n\n // Atomic inline-block: entire element (margin + padding + text) is one word\n // Must check before boxOpen/boxClose handlers since atomic has both set.\n if (run.boxOpen && run.boxClose && run.text) {\n applyFont(ctx, run.style);\n ctx.letterSpacing = run.style.letterSpacing > 0 ? `${run.style.letterSpacing}px` : '0px';\n const text = applyTextTransform(run.text, run.style.textTransform);\n const s = run.style;\n const textWidth = cachedMeasureWidth(ctx, text);\n const totalWidth = s.marginLeft + s.borderLeftWidth + s.paddingLeft +\n textWidth + s.paddingRight + s.borderRightWidth + s.marginRight;\n allWords.push({\n text,\n width: totalWidth,\n style: run.style,\n isSpace: false,\n boxStyle: run.boxStyle,\n boxOpen: run.boxOpen,\n boxClose: run.boxClose,\n });\n continue;\n }\n\n // Handle inline box open/close markers (padding)\n if (run.boxOpen) {\n const pad = run.boxOpen.paddingLeft + run.boxOpen.borderLeftWidth;\n if (pad > 0) {\n allWords.push({ text: '', width: pad, style: run.style, isSpace: false, boxStyle: run.boxStyle, boxOpen: run.boxOpen });\n }\n continue;\n }\n if (run.boxClose) {\n const pad = run.boxClose.paddingRight + run.boxClose.borderRightWidth;\n if (pad > 0) {\n allWords.push({ text: '', width: pad, style: run.style, isSpace: false, boxStyle: run.boxStyle, boxClose: run.boxClose });\n }\n continue;\n }\n\n applyFont(ctx, run.style);\n ctx.letterSpacing = run.style.letterSpacing > 0 ? `${run.style.letterSpacing}px` : '0px';\n const text = applyTextTransform(run.text, run.style.textTransform);\n\n // Handle explicit newlines (from <br> or pre-wrap) — always force line break\n if (text.includes('\\n')) {\n const parts = text.split('\\n');\n for (let i = 0; i < parts.length; i++) {\n if (i > 0) {\n allWords.push({ text: '\\n', width: 0, style: run.style, isSpace: false, boxStyle: run.boxStyle });\n }\n if (parts[i]) {\n tokenizeString(ctx, parts[i], run, allWords);\n }\n }\n } else {\n tokenizeString(ctx, text, run, allWords);\n }\n }\n\n return allWords;\n}\n\n/**\n * Check if a character is CJK (Chinese/Japanese/Korean) — these wrap at character level.\n */\nfunction isCJK(char: string): boolean {\n const code = char.codePointAt(0) || 0;\n return (\n (code >= 0x4E00 && code <= 0x9FFF) || // CJK Unified\n (code >= 0x3400 && code <= 0x4DBF) || // CJK Extension A\n (code >= 0x3000 && code <= 0x303F) || // CJK Symbols\n (code >= 0x3040 && code <= 0x309F) || // Hiragana\n (code >= 0x30A0 && code <= 0x30FF) || // Katakana\n (code >= 0xAC00 && code <= 0xD7AF) || // Hangul\n (code >= 0xFF00 && code <= 0xFFEF) || // Fullwidth\n (code >= 0x20000 && code <= 0x2A6DF) // CJK Extension B\n );\n}\n\n/**\n * Break a word into character-level pieces if it contains CJK or if\n * overflow-wrap: break-word is set and the word is too wide.\n */\nfunction breakWordIfNeeded(\n ctx: CanvasRenderingContext2D,\n word: Word,\n contentWidth: number,\n currentLineWidth: number,\n): Word[] {\n // Check if word has CJK characters — always break at character level\n const hasCJK = [...word.text].some(isCJK);\n\n // Check if word needs break-word splitting — when it won't fit on a fresh line\n const needsBreak = word.width > contentWidth &&\n (word.style.overflowWrap === 'break-word' || word.style.wordBreak === 'break-all');\n\n if (!hasCJK && !needsBreak) return [word];\n\n // Split into characters using cumulative measurement for accuracy.\n // Measuring each char individually ignores kerning — the sum of individual\n // widths diverges from the true string width over many characters.\n ctx.font = buildCanvasFont(word.style);\n const chars = [...word.text];\n const pieces: Word[] = [];\n\n let current = '';\n let currentWidth = 0;\n\n for (const char of chars) {\n // CJK chars always get their own word for wrapping\n if (isCJK(char)) {\n if (current) {\n pieces.push({ ...word, text: current, width: currentWidth });\n current = '';\n currentWidth = 0;\n }\n const charWidth = cachedMeasureWidth(ctx, char);\n pieces.push({ ...word, text: char, width: charWidth });\n continue;\n }\n\n // Use cumulative measurement: measure the growing string, not individual chars\n const candidateText = current + char;\n const candidateWidth = cachedMeasureWidth(ctx, candidateText);\n\n // For break-word: break when adding this char would exceed container\n if (needsBreak && candidateWidth > contentWidth && current) {\n pieces.push({ ...word, text: current, width: currentWidth });\n current = char;\n currentWidth = cachedMeasureWidth(ctx, char);\n continue;\n }\n\n current = candidateText;\n currentWidth = candidateWidth;\n }\n\n if (current) {\n pieces.push({ ...word, text: current, width: currentWidth });\n }\n\n return pieces;\n}\n\n/**\n * Flow words into lines that fit within contentWidth.\n * Handles: word wrapping, nowrap, break-word, CJK character wrapping.\n */\nfunction flowWordsIntoLines(\n ctx: CanvasRenderingContext2D,\n words: Word[],\n contentWidth: number,\n whiteSpace: string,\n useBulletProbe = false,\n textIndent = 0,\n): PositionedLine[] {\n const lines: PositionedLine[] = [];\n let currentLine: PositionedLine = { words: [], totalWidth: 0, lineHeight: 0 };\n const noWrap = whiteSpace === 'nowrap' || whiteSpace === 'pre';\n // text-indent reduces the first line's width budget; subsequent lines use full width.\n const effWidth = () => contentWidth - (lines.length === 0 ? textIndent : 0);\n\n const isPreWrap = whiteSpace === 'pre-wrap' || whiteSpace === 'pre' || whiteSpace === 'pre-line';\n // `pre`, `pre-wrap`, and `break-spaces` preserve author whitespace\n // (leading and trailing); the others collapse it.\n const preservesWhitespace =\n whiteSpace === 'pre' || whiteSpace === 'pre-wrap' || whiteSpace === 'break-spaces';\n\n function pushLine(isSoftWrap = false) {\n const hadWords = currentLine.words.length > 0;\n // Trim trailing spaces. `break-spaces` preserves them even at soft wraps;\n // `pre`/`pre-wrap` preserve them at hard breaks and end-of-content but not\n // at soft wraps (per CSS Text 3 §4.1.1).\n const preserveTrailing = whiteSpace === 'break-spaces'\n || (preservesWhitespace && !isSoftWrap);\n if (!preserveTrailing) {\n while (currentLine.words.length > 0 && currentLine.words[currentLine.words.length - 1].isSpace) {\n currentLine.totalWidth -= currentLine.words[currentLine.words.length - 1].width;\n currentLine.words.pop();\n }\n }\n // Soft hyphen: if this is a soft wrap and the last word has a soft-hyphen\n // break, append a visible '-' since the word is being broken here.\n if (isSoftWrap && currentLine.words.length > 0) {\n const lastWord = currentLine.words[currentLine.words.length - 1];\n if (lastWord.isSoftHyphenBreak) {\n applyFont(ctx, lastWord.style);\n const hyphenWidth = cachedMeasureWidth(ctx, '-');\n currentLine.words.push({\n text: '-',\n width: hyphenWidth,\n style: lastWord.style,\n isSpace: false,\n });\n currentLine.totalWidth += hyphenWidth;\n }\n }\n // In pre-wrap mode, space-only lines still need height (they are content)\n if (currentLine.words.length > 0 || (hadWords && isPreWrap)) {\n if (_debug) {\n const text = currentLine.words.map(w => w.text).join('');\n _debug({\n type: 'line-commit',\n message: `Line ${lines.length}: \"${text}\" width=${currentLine.totalWidth.toFixed(2)} / ${contentWidth}`,\n data: { lineIndex: lines.length, text, totalWidth: currentLine.totalWidth, contentWidth },\n });\n }\n lines.push(currentLine);\n }\n currentLine = { words: [], totalWidth: 0, lineHeight: 0 };\n }\n\n let afterHardBreak = true; // start of content is like after a hard break\n\n for (const word of words) {\n let wordLineHeight = getLineHeight(ctx, word.style, useBulletProbe);\n // Inline-block elements expand line height with their vertical padding+margin\n if (word.boxStyle && word.boxStyle.display === 'inline-block') {\n const bs = word.boxStyle;\n wordLineHeight = Math.max(wordLineHeight,\n wordLineHeight + bs.paddingTop + bs.paddingBottom + bs.marginTop + bs.marginBottom\n + bs.borderTopWidth + bs.borderBottomWidth);\n }\n\n if (word.text === '\\n') {\n if (currentLine.words.length === 0) {\n currentLine.lineHeight = wordLineHeight;\n currentLine.endedByHardBreak = true;\n lines.push(currentLine);\n currentLine = { words: [], totalWidth: 0, lineHeight: 0 };\n } else {\n currentLine.endedByHardBreak = true;\n pushLine();\n }\n afterHardBreak = true;\n continue;\n }\n\n // No wrapping mode — everything on one line\n if (noWrap) {\n currentLine.words.push(word);\n currentLine.totalWidth += word.width;\n currentLine.lineHeight = Math.max(currentLine.lineHeight, wordLineHeight);\n continue;\n }\n\n // Break long words / CJK characters if needed\n const pieces = (!word.isSpace && word.text.length > 1)\n ? breakWordIfNeeded(ctx, word, effWidth(), currentLine.totalWidth)\n : [word];\n\n for (const piece of pieces) {\n // Trailing punctuation (e.g. comma after </span>) should not wrap\n // independently — browsers keep it with the preceding word.\n const isTrailingPunct = !piece.isSpace && piece.text.length > 0 &&\n /^[,.\\;:!?\\)\\]\\}'\"»›]+$/.test(piece.text) &&\n currentLine.words.length > 0 &&\n !currentLine.words[currentLine.words.length - 1].isSpace;\n\n // Would this piece overflow?\n if (!piece.isSpace && !isTrailingPunct && currentLine.words.length > 0 &&\n currentLine.totalWidth + piece.width > effWidth()) {\n const overflow = currentLine.totalWidth + piece.width - effWidth();\n\n // For borderline cases (overflow < 1px), word-by-word delta\n // accumulation may introduce rounding errors. Re-measure the\n // full candidate line as a single string for accuracy.\n // Only works for single-font lines — mixed fonts can't be\n // measured as one string.\n let reallyOverflows = true;\n if (overflow < 1 && !hasMixedFonts([...currentLine.words, piece])) {\n applyFont(ctx, piece.style);\n const fullText = currentLine.words.map(w => w.text).join('') + piece.text;\n const fullWidth = cachedMeasureWidth(ctx, fullText);\n // Allow tiny sub-pixel overflow — canvas measureText and DOM\n // text layout can differ by fractions of a pixel.\n if (fullWidth <= effWidth() + 0.1) {\n reallyOverflows = false;\n }\n }\n\n // Hyphen break on current line: before wrapping the whole word,\n // try fitting a hyphen prefix on the current line. Browsers prefer\n // keeping content on the current line by splitting at hyphens.\n if (reallyOverflows && piece.text.includes('-')) {\n const parts = piece.text.split(/(?<=-)/);\n if (parts.length > 1) {\n applyFont(ctx, piece.style);\n let fitted = '';\n let fittedWidth = 0;\n let partIdx = 0;\n const available = effWidth() - currentLine.totalWidth;\n for (; partIdx < parts.length; partIdx++) {\n const candidate = fitted + parts[partIdx];\n const candidateWidth = cachedMeasureWidth(ctx, candidate);\n if (candidateWidth > available) break;\n fitted = candidate;\n fittedWidth = candidateWidth;\n }\n if (partIdx > 0 && partIdx < parts.length) {\n currentLine.words.push({ ...piece, text: fitted, width: fittedWidth });\n currentLine.totalWidth += fittedWidth;\n currentLine.lineHeight = Math.max(currentLine.lineHeight, wordLineHeight);\n pushLine(true);\n afterHardBreak = false;\n const remainder = parts.slice(partIdx).join('');\n const remainderWidth = cachedMeasureWidth(ctx, remainder);\n currentLine.words.push({ ...piece, text: remainder, width: remainderWidth });\n currentLine.totalWidth += remainderWidth;\n currentLine.lineHeight = Math.max(currentLine.lineHeight, wordLineHeight);\n continue;\n }\n }\n }\n\n if (reallyOverflows) {\n if (_debug) {\n const lineText = currentLine.words.map(w => w.text).join('');\n _debug({\n type: 'line-wrap',\n message: `\"${piece.text}\" overflow=${overflow.toFixed(2)} wrap=true lineWidth=${currentLine.totalWidth.toFixed(2)} pieceWidth=${piece.width.toFixed(2)} contentWidth=${contentWidth} line=\"${lineText}\"`,\n data: { text: piece.text, overflow, lineWidth: currentLine.totalWidth, pieceWidth: piece.width, contentWidth, lineText },\n });\n }\n pushLine(true);\n afterHardBreak = false;\n }\n }\n\n // Skip leading spaces at the start of a line. Preserving modes\n // (pre/pre-wrap/break-spaces) keep them after hard breaks; collapsing\n // modes (normal/nowrap/pre-line) drop them in all cases.\n if (piece.isSpace && currentLine.words.length === 0\n && (!afterHardBreak || !preservesWhitespace)) continue;\n\n // Tab: snap to next tab stop based on current position\n let pieceWidth = piece.width;\n if (piece.isTab) {\n const tabStop = piece.width; // tabStopInterval stored as width\n const currentPos = currentLine.totalWidth;\n const nextStop = Math.ceil((currentPos + 0.1) / tabStop) * tabStop;\n pieceWidth = nextStop - currentPos;\n piece.width = pieceWidth;\n }\n\n // Hyphen break on a fresh line when word still too wide.\n if (currentLine.words.length === 0 && pieceWidth > effWidth() &&\n !piece.isSpace && piece.text.includes('-')) {\n const subParts = piece.text.split(/(?<=-)/);\n if (subParts.length > 1) {\n applyFont(ctx, piece.style);\n // Inject sub-parts as individual pieces — they'll flow through\n // the normal overflow/wrap logic on subsequent iterations.\n const newPieces: Word[] = subParts.filter(p => p).map(p => ({\n ...piece,\n text: p,\n width: cachedMeasureWidth(ctx, p),\n }));\n // Replace current piece with the sub-parts by splicing into the pieces array\n // Since we're iterating `pieces`, we push remaining sub-parts after the first\n // onto the current line normally, letting the overflow check handle wrapping.\n let first = true;\n for (const sp of newPieces) {\n if (first) {\n first = false;\n // First sub-part: add to current line (it fits since it's smaller)\n currentLine.words.push(sp);\n currentLine.totalWidth += sp.width;\n currentLine.lineHeight = Math.max(currentLine.lineHeight, wordLineHeight);\n } else if (currentLine.totalWidth + sp.width > effWidth()) {\n // Overflow: wrap to next line\n pushLine(true);\n afterHardBreak = false;\n currentLine.words.push(sp);\n currentLine.totalWidth += sp.width;\n currentLine.lineHeight = Math.max(currentLine.lineHeight, wordLineHeight);\n } else {\n currentLine.words.push(sp);\n currentLine.totalWidth += sp.width;\n currentLine.lineHeight = Math.max(currentLine.lineHeight, wordLineHeight);\n }\n }\n continue;\n }\n }\n\n currentLine.words.push(piece);\n currentLine.totalWidth += pieceWidth;\n currentLine.lineHeight = Math.max(currentLine.lineHeight, wordLineHeight);\n if (!piece.isSpace) afterHardBreak = false;\n }\n }\n pushLine();\n return lines;\n}\n\n/**\n * Layout inline content: text wrapping + positioning using pure canvas measurement.\n * Returns layout nodes and the total height consumed.\n */\nfunction layoutInlineContent(\n ctx: CanvasRenderingContext2D,\n node: StyledNode,\n x: number,\n y: number,\n contentWidth: number,\n useBulletProbe = false,\n): { nodes: LayoutNode[]; height: number } {\n const results: LayoutNode[] = [];\n const runs = collectTextRuns(node);\n if (runs.length === 0) return { nodes: results, height: 0 };\n\n const words = tokenizeRuns(ctx, runs);\n const textIndent = node.style.textIndent || 0;\n const lines = flowWordsIntoLines(ctx, words, contentWidth, node.style.whiteSpace, useBulletProbe, textIndent);\n const isRTL = node.style.direction === 'rtl';\n const resolveDir = (a: string) => {\n if (a === 'start') return isRTL ? 'right' : 'left';\n if (a === 'end') return isRTL ? 'left' : 'right';\n return a;\n };\n let textAlign = resolveDir(node.style.textAlign);\n // text-align-last: 'auto' inherits from text-align except when text-align is\n // 'justify', then defaults to 'start' (CSS Text 3 §7.2).\n let textAlignLast = node.style.textAlignLast || 'auto';\n if (textAlignLast === 'auto') {\n textAlignLast = node.style.textAlign === 'justify' ? (isRTL ? 'right' : 'left') : textAlign;\n } else {\n textAlignLast = resolveDir(textAlignLast);\n }\n\n let curY = y;\n\n for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {\n const line = lines[lineIdx];\n if (line.words.length === 0) {\n curY += line.lineHeight;\n continue;\n }\n\n const lineHeight = line.lineHeight;\n const isLastLine = lineIdx === lines.length - 1;\n const isFirstLine = lineIdx === 0;\n\n // Per-line alignment: lines ending at a forced break or the last line\n // use text-align-last; all others use text-align (CSS Text 3 §7.1, §7.2).\n const useLast = isLastLine || line.endedByHardBreak;\n const align = useLast ? textAlignLast : textAlign;\n\n // text-indent narrows the first line's available width.\n const indent = isFirstLine ? textIndent : 0;\n const lineMaxWidth = contentWidth - indent;\n\n // Justify: expand spaces to fill the line.\n let justifyExtraPerSpace = 0;\n if (align === 'justify' && line.totalWidth < lineMaxWidth) {\n const spaceCount = line.words.filter(w => w.isSpace).length;\n if (spaceCount > 0) {\n justifyExtraPerSpace = (lineMaxWidth - line.totalWidth) / spaceCount;\n }\n }\n\n // text-align (with first-line indent baked into curX)\n let curX = x + indent;\n if (align === 'center') {\n curX = x + indent + (lineMaxWidth - line.totalWidth) / 2;\n } else if (align === 'right' || (align !== 'justify' && isRTL)) {\n curX = x + indent + lineMaxWidth - line.totalWidth;\n } else if (isRTL) {\n curX = x + indent + lineMaxWidth - line.totalWidth;\n }\n\n // Inline background boxes and text are emitted after baseline computation\n // (below) so that emitInlineBox can use line-level metrics for alignment.\n\n // Compute a single shared baseline for the entire line.\n // Exclude sub/sup words — they sit above/below the baseline and\n // shouldn't influence where the baseline is positioned.\n let maxAscent = 0;\n let maxDescent = 0;\n for (const word of line.words) {\n if (word.text === '') continue;\n const va = word.style.verticalAlign;\n if (va === 'super' || va === 'sub') continue; // skip sub/sup for baseline calc\n const { ascent: a, descent: d } = getFontMetrics(ctx, word.style);\n if (a > maxAscent) maxAscent = a;\n if (d > maxDescent) maxDescent = d;\n }\n // If only sub/sup words on the line, use the first word's metrics\n if (maxAscent === 0) {\n for (const word of line.words) {\n if (word.text === '') continue;\n const { ascent, descent } = getFontMetrics(ctx, word.style);\n maxAscent = ascent;\n maxDescent = descent;\n break;\n }\n }\n // Center the text block (ascent + descent) within the lineHeight\n const textBlockHeight = maxAscent + maxDescent;\n let lineBaselineY = curY + (lineHeight - textBlockHeight) / 2 + maxAscent;\n\n // Compute parent font size for sub/sup positioning (used in expansion + text emit)\n const lineNormalWords = line.words.filter(w =>\n w.text !== '' && w.style.verticalAlign !== 'super' && w.style.verticalAlign !== 'sub');\n const parentFontSize = lineNormalWords.length > 0\n ? Math.max(...lineNormalWords.map(w => w.style.fontSize)) : 0;\n\n // Expand line height if sub/sup extends beyond the line box.\n // Browsers grow the line box to fit all content, but keep\n // the normal text baseline position unchanged.\n let effectiveLineHeight = lineHeight;\n {\n\n let minTop = curY;\n let maxBottom = curY + lineHeight;\n\n for (const word of line.words) {\n if (word.text === '') continue;\n const va = word.style.verticalAlign;\n if (va !== 'super' && va !== 'sub') continue;\n if (parentFontSize === 0) break;\n\n const { ascent: wAscent, descent: wDescent } = getFontMetrics(ctx, word.style);\n\n let shiftedBaseline = lineBaselineY;\n if (va === 'super') {\n shiftedBaseline -= parentFontSize * 0.4;\n } else {\n shiftedBaseline += parentFontSize * 0.26;\n }\n\n const wordTop = shiftedBaseline - wAscent;\n const wordBottom = shiftedBaseline + wDescent;\n if (wordTop < minTop) minTop = wordTop;\n if (wordBottom > maxBottom) maxBottom = wordBottom;\n }\n\n effectiveLineHeight = maxBottom - minTop;\n }\n\n // Emit inline background box using line-level baseline for vertical alignment.\n // Uses the line's ascent/descent (not the box's own font) so box aligns with text.\n const emitInlineBox = (style: ResolvedStyle, bx: number, bw: number) => {\n // Use the box's OWN font for height (not the line's largest font),\n // but align vertically to the line's baseline.\n const { ascent: boxAscent, descent: boxDescent } = getFontMetrics(ctx, style);\n const padTop = style.paddingTop + style.borderTopWidth;\n const padBottom = style.paddingBottom + style.borderBottomWidth;\n const boxHeight = boxAscent + boxDescent + padTop + padBottom;\n let boxY: number;\n if (style.display === 'inline-block') {\n boxY = curY + style.marginTop;\n } else {\n boxY = lineBaselineY - boxAscent - padTop;\n }\n results.push({\n type: 'box', style, x: bx, y: boxY, width: bw, height: boxHeight,\n tagName: 'span', children: [],\n });\n };\n\n // LTR: emit inline background boxes (Pass 1) before text.\n if (!isRTL) {\n let scanX = curX;\n let boxStartX = scanX;\n let currentBoxStyle: ResolvedStyle | undefined;\n let boxHasText = false;\n\n for (const word of line.words) {\n if (word.boxOpen && word.boxClose && word.text) {\n if (currentBoxStyle) {\n if (boxHasText) emitInlineBox(currentBoxStyle, boxStartX, scanX - boxStartX);\n currentBoxStyle = undefined;\n boxHasText = false;\n }\n const s = word.style;\n const textWidth = word.width - s.marginLeft - s.borderLeftWidth - s.paddingLeft\n - s.paddingRight - s.borderRightWidth - s.marginRight;\n const boxX = scanX + s.marginLeft;\n const boxW = s.borderLeftWidth + s.paddingLeft + textWidth + s.paddingRight + s.borderRightWidth;\n emitInlineBox(s, boxX, boxW);\n boxHasText = false;\n scanX += word.width;\n continue;\n }\n\n if (word.boxStyle !== currentBoxStyle) {\n if (currentBoxStyle && boxHasText) {\n emitInlineBox(currentBoxStyle, boxStartX, scanX - boxStartX);\n }\n currentBoxStyle = word.boxStyle;\n boxStartX = scanX;\n boxHasText = false;\n }\n if (word.text && !word.isSpace) boxHasText = true;\n scanX += word.width + (word.isSpace ? justifyExtraPerSpace : 0);\n }\n if (currentBoxStyle && boxHasText) {\n emitInlineBox(currentBoxStyle, boxStartX, scanX - boxStartX);\n }\n }\n\n // Emit text nodes.\n const textWords = line.words.filter(w => w.text !== '');\n const allSameStyle = textWords.length > 0 && textWords.every(w =>\n sameTextStyle(w.style, textWords[0].style)\n );\n\n if (isRTL) {\n // RTL: build groups, compute positions, emit boxes then text.\n // Groups join consecutive same-style words for proper glyph shaping.\n // Padding markers between groups create spacing.\n interface StyledGroup {\n text: string; style: ResolvedStyle; width: number;\n boxStyle?: ResolvedStyle; x: number;\n padBefore: number; // padding before this group (from boxOpen/boxClose markers)\n }\n const groups: StyledGroup[] = [];\n let currentGroup: StyledGroup | null = null;\n let pendingPad = 0;\n\n for (const word of line.words) {\n if (word.text === '') {\n // Padding marker — accumulate for the next group boundary\n if (currentGroup) { groups.push(currentGroup); currentGroup = null; }\n pendingPad += word.width;\n continue;\n }\n if (currentGroup && sameTextStyle(currentGroup.style, word.style)) {\n currentGroup.text += word.text;\n currentGroup.width += word.width;\n } else {\n if (currentGroup) groups.push(currentGroup);\n currentGroup = { text: word.text, style: word.style, width: word.width, boxStyle: word.boxStyle, x: 0, padBefore: pendingPad };\n pendingPad = 0;\n }\n }\n if (currentGroup) groups.push(currentGroup);\n\n // Compute positions right-to-left: group-level measureText for accuracy,\n // with padding markers creating spacing between groups.\n let rtlX = curX + line.totalWidth;\n for (const group of groups) {\n rtlX -= group.padBefore; // spacing from padding markers\n applyFont(ctx, group.style);\n const measuredWidth = cachedMeasureWidth(ctx, group.text);\n rtlX -= measuredWidth;\n group.x = rtlX;\n group.width = measuredWidth;\n }\n\n // Emit inline boxes first (behind text).\n // Include padding/border from boxStyle in box dimensions.\n for (const group of groups) {\n if (group.boxStyle && hasVisibleBoxStyles(group.boxStyle)) {\n const bs = group.boxStyle;\n const padLeft = bs.paddingLeft + bs.borderLeftWidth;\n const padRight = bs.paddingRight + bs.borderRightWidth;\n emitInlineBox(bs, group.x - padLeft, group.width + padLeft + padRight);\n }\n }\n\n // Emit text groups\n for (const group of groups) {\n results.push({\n type: 'text',\n text: group.text,\n x: group.x + group.width, // x = right edge for RTL textAlign\n y: lineBaselineY,\n width: group.width,\n style: { ...group.style, direction: 'rtl' },\n });\n }\n } else {\n // LTR with mixed BiDi scripts: emit the entire line as one fillText call\n // so the canvas engine handles BiDi reordering (Arabic/Hebrew in LTR).\n // Only do this when the line contains RTL characters — pure LTR lines\n // are more accurate with word-by-word positioning.\n const lineText = line.words.map(w => w.text).join('');\n const hasBidiMix = allSameStyle && /[\\u0590-\\u08FF\\uFB50-\\uFDFF\\uFE70-\\uFEFF]/.test(lineText) &&\n !line.words.some(w => w.boxOpen || w.boxClose ||\n w.style.verticalAlign === 'super' || w.style.verticalAlign === 'sub');\n if (hasBidiMix) {\n applyFont(ctx, textWords[0].style);\n const measuredWidth = cachedMeasureWidth(ctx, lineText);\n results.push({\n type: 'text',\n text: lineText,\n x: curX,\n y: lineBaselineY,\n width: measuredWidth,\n style: textWords[0].style,\n });\n } else {\n // Mixed styles: word by word\n for (const word of line.words) {\n if (word.text === '') {\n curX += word.width;\n continue;\n }\n\n // Atomic inline-block: position text inside the box (after margin + padding)\n if (word.boxOpen && word.boxClose) {\n const s = word.style;\n const textX = curX + s.marginLeft + s.borderLeftWidth + s.paddingLeft;\n results.push({\n type: 'text',\n text: word.text,\n x: textX,\n y: lineBaselineY,\n width: cachedMeasureWidth(ctx, word.text),\n style: word.style,\n });\n curX += word.width;\n continue;\n }\n\n // Adjust baseline for vertical-align\n let baselineY = lineBaselineY;\n const va = word.style.verticalAlign;\n if (va === 'super' || va === 'sub') {\n const pfs = parentFontSize || word.style.fontSize;\n if (va === 'super') {\n baselineY -= pfs * 0.4;\n } else {\n baselineY += pfs * 0.26;\n }\n }\n const effectiveWidth = word.width + (word.isSpace ? justifyExtraPerSpace : 0);\n\n results.push({\n type: 'text',\n text: word.text,\n x: curX,\n y: baselineY,\n width: effectiveWidth,\n style: word.style,\n });\n\n curX += effectiveWidth;\n }\n }\n }\n\n curY += effectiveLineHeight;\n }\n\n return { nodes: results, height: curY - y };\n}\n\n// ─── Block layout ──────────────────────────────────────────────────────\n\n/**\n * Collapse margins between two adjacent block elements.\n * Returns the effective spacing (max of the two margins, not sum).\n */\nfunction collapseMargins(prevMarginBottom: number, nextMarginTop: number): number {\n // Both positive: take the larger\n if (prevMarginBottom >= 0 && nextMarginTop >= 0) {\n return Math.max(prevMarginBottom, nextMarginTop);\n }\n // Both negative: take the more negative\n if (prevMarginBottom < 0 && nextMarginTop < 0) {\n return Math.min(prevMarginBottom, nextMarginTop);\n }\n // One positive, one negative: sum them\n return prevMarginBottom + nextMarginTop;\n}\n\n/**\n * Check if a node is a block-level display.\n */\nfunction isBlock(node: StyledNode): boolean {\n const d = node.style.display;\n return d === 'block' || d === 'list-item' || d === 'flex' || d === 'table' ||\n d === 'table-row' || d === 'table-cell' || d === 'table-row-group' ||\n d === 'table-header-group' || d === 'table-footer-group';\n}\n\n/**\n * Layout a block-level element and all its children.\n * Returns the LayoutBox and total height consumed (including margins).\n */\nfunction layoutBlock(\n ctx: CanvasRenderingContext2D,\n node: StyledNode,\n x: number,\n y: number,\n availableWidth: number,\n): { box: LayoutBox; height: number; marginBottomOut: number } {\n const style = node.style;\n\n // Box model\n const marginLeft = style.marginLeft;\n const marginRight = style.marginRight;\n const borderLeft = style.borderLeftWidth;\n const borderRight = style.borderRightWidth;\n const borderTop = style.borderTopWidth;\n const borderBottom = style.borderBottomWidth;\n const padLeft = style.paddingLeft;\n const padRight = style.paddingRight;\n const padTop = style.paddingTop;\n const padBottom = style.paddingBottom;\n\n const boxX = x + marginLeft;\n // If element has explicit width, use it; otherwise fill available width\n const boxWidth = (style.width > 0)\n ? style.width\n : availableWidth - marginLeft - marginRight;\n const contentX = boxX + borderLeft + padLeft;\n const contentWidth = Math.max(0, boxWidth - borderLeft - borderRight - padLeft - padRight);\n\n const boxY = y;\n const contentStartY = boxY + borderTop + padTop;\n\n const box: LayoutBox = {\n type: 'box',\n style,\n x: boxX,\n y: boxY,\n width: boxWidth,\n height: 0, // computed below\n tagName: node.tagName,\n children: [],\n listMarker: node.listMarker,\n };\n\n // Flex layout\n if (style.display === 'flex') {\n const result = layoutFlex(ctx, node, contentX, contentStartY, contentWidth);\n box.children = result.children;\n box.height = borderTop + padTop + result.height + padBottom + borderBottom;\n return { box, height: box.height, marginBottomOut: style.marginBottom };\n }\n\n // Table layout\n if (style.display === 'table') {\n const result = layoutTable(ctx, node, contentX, contentStartY, contentWidth);\n box.children = result.children;\n box.height = borderTop + padTop + result.height + padBottom + borderBottom;\n return { box, height: box.height, marginBottomOut: style.marginBottom };\n }\n\n // Empty block elements: zero content height (CSS spec — no line boxes created).\n // Only min-height or padding/border contribute to height.\n if (node.children.length === 0) {\n box.height = borderTop + padTop + padBottom + borderBottom;\n if (style.minHeight > 0) box.height = Math.max(box.height, style.minHeight);\n return { box, height: box.height, marginBottomOut: style.marginBottom };\n }\n\n // Layout children\n if (hasOnlyInlineChildren(node)) {\n // Inline formatting context\n const bulletProbe = node.tagName === 'li' && BULLET_MARKERS.has(style.listStyleType);\n const { nodes, height } = layoutInlineContent(ctx, node, contentX, contentStartY, contentWidth, bulletProbe);\n box.children = nodes;\n box.height = borderTop + padTop + height + padBottom + borderBottom;\n } else {\n // Block formatting context — stack children vertically\n let curY = contentStartY;\n let prevMarginBottom = 0;\n let hasContent = false; // tracks whether we've placed any content\n // Margin collapsing through parent: only for list elements.\n const allowCollapseThrough =\n node.tagName === 'li' || node.tagName === 'ul' || node.tagName === 'ol' ||\n node.tagName === 'dd' || node.tagName === 'dt';\n\n for (let ci = 0; ci < node.children.length; ci++) {\n const child = node.children[ci];\n\n if (child.tagName === '#text' || isInline(child)) {\n // Collect ALL consecutive inline/text children into one group\n const inlineChildren: StyledNode[] = [child];\n while (ci + 1 < node.children.length) {\n const next = node.children[ci + 1];\n if (next.tagName === '#text' || isInline(next)) {\n inlineChildren.push(next);\n ci++;\n } else {\n break;\n }\n }\n\n // Apply pending margin before inline content\n if (prevMarginBottom > 0) {\n curY += prevMarginBottom;\n prevMarginBottom = 0;\n }\n\n const inlineGroup: StyledNode = {\n element: null,\n tagName: 'div',\n style: { ...node.style, display: 'block', marginTop: 0, marginBottom: 0, paddingTop: 0, paddingBottom: 0, borderTopWidth: 0, borderBottomWidth: 0 },\n children: inlineChildren,\n textContent: null,\n };\n const bulletProbe2 = node.tagName === 'li' && BULLET_MARKERS.has(style.listStyleType);\n const { nodes, height } = layoutInlineContent(ctx, inlineGroup, contentX, curY, contentWidth, bulletProbe2);\n box.children.push(...nodes);\n curY += height;\n prevMarginBottom = 0;\n hasContent = true;\n continue;\n }\n\n // Block child — collapse margins\n const childMarginTop = child.style.marginTop;\n\n // First child margin-top collapses through parent if parent has no top border/padding\n // Only for elements that don't establish a new BFC (not root, not flex, not overflow)\n // First child margin-top collapses through parent if parent has no\n // top padding/border and doesn't establish a new BFC.\n if (!hasContent && padTop === 0 && borderTop === 0 && allowCollapseThrough) {\n // Skip — margin collapses with parent's margin\n } else {\n const collapsed = collapseMargins(prevMarginBottom, childMarginTop);\n curY += collapsed;\n }\n\n const { box: childBox, height: childTotalHeight, marginBottomOut } = layoutBlock(\n ctx, child, contentX, curY, contentWidth,\n );\n box.children.push(childBox);\n curY += childTotalHeight;\n prevMarginBottom = marginBottomOut;\n hasContent = true;\n }\n\n // Last child's margin-bottom collapses through parent if no bottom border/padding.\n // Root container does NOT collapse last-child margin (it defines the content height).\n let marginBottomOut = style.marginBottom;\n const canCollapseThrough = padBottom === 0 && borderBottom === 0 && allowCollapseThrough;\n if (canCollapseThrough && prevMarginBottom > 0) {\n // Last child's margin passes through to become parent's effective margin-bottom\n marginBottomOut = Math.max(style.marginBottom, prevMarginBottom);\n }\n\n // Include last child's margin-bottom in parent height when it can't collapse through\n let contentEnd = curY - contentStartY;\n if (!canCollapseThrough && prevMarginBottom > 0) {\n contentEnd += prevMarginBottom;\n }\n box.height = borderTop + padTop + contentEnd + padBottom + borderBottom;\n if (style.minHeight > 0) box.height = Math.max(box.height, style.minHeight);\n return { box, height: box.height, marginBottomOut };\n }\n\n if (style.minHeight > 0) box.height = Math.max(box.height, style.minHeight);\n return { box, height: box.height, marginBottomOut: style.marginBottom };\n}\n\n// ─── Table layout ──────────────────────────────────────────────────────\n\nfunction layoutTable(\n ctx: CanvasRenderingContext2D,\n node: StyledNode,\n contentX: number,\n contentY: number,\n contentWidth: number,\n): { children: LayoutNode[]; height: number } {\n const children: LayoutNode[] = [];\n\n // Collect rows from thead, tbody, tfoot, or direct tr children\n const rows: StyledNode[] = [];\n for (const child of node.children) {\n if (child.tagName === 'tr') {\n rows.push(child);\n } else if (['thead', 'tbody', 'tfoot'].includes(child.tagName)) {\n for (const grandchild of child.children) {\n if (grandchild.tagName === 'tr') rows.push(grandchild);\n }\n }\n }\n\n if (rows.length === 0) return { children, height: 0 };\n\n // Determine column count from first row\n const colCount = Math.max(...rows.map(r => r.children.filter(c => c.tagName === 'td' || c.tagName === 'th').length));\n if (colCount === 0) return { children, height: 0 };\n\n // Equal column widths (simple approach)\n const colWidth = contentWidth / colCount;\n\n let curY = contentY;\n\n for (const row of rows) {\n const cells = row.children.filter(c => c.tagName === 'td' || c.tagName === 'th');\n let maxCellHeight = 0;\n const cellBoxes: LayoutBox[] = [];\n\n for (let i = 0; i < cells.length; i++) {\n const cell = cells[i];\n const cellX = contentX + i * colWidth;\n\n const { box: cellBox, height: cellHeight } = layoutBlock(ctx, cell, cellX, curY, colWidth);\n cellBoxes.push(cellBox);\n maxCellHeight = Math.max(maxCellHeight, cellHeight);\n }\n\n // Normalize cell heights to the tallest cell in the row\n for (const cellBox of cellBoxes) {\n cellBox.height = maxCellHeight;\n children.push(cellBox);\n }\n\n curY += maxCellHeight;\n }\n\n return { children, height: curY - contentY };\n}\n\n// ─── Flex layout ───────────────────────────────────────────────────────\n\nfunction layoutFlex(\n ctx: CanvasRenderingContext2D,\n node: StyledNode,\n contentX: number,\n contentY: number,\n contentWidth: number,\n): { children: LayoutNode[]; height: number } {\n const style = node.style;\n const gap = style.gap;\n const children: LayoutNode[] = [];\n\n const flexChildren = node.children.filter(c => c.tagName !== '#text' || c.textContent?.trim());\n if (flexChildren.length === 0) return { children, height: 0 };\n\n if (style.flexDirection === 'row' || style.flexDirection === '') {\n // Row layout\n const totalGaps = gap * (flexChildren.length - 1);\n const totalGrow = flexChildren.reduce((s, c) => s + (c.style.flexGrow || 0), 0);\n const flexBasis = (contentWidth - totalGaps) / (totalGrow || flexChildren.length);\n\n let curX = contentX;\n let maxHeight = 0;\n\n for (const child of flexChildren) {\n if (child.tagName === '#text') continue;\n const grow = child.style.flexGrow || (totalGrow === 0 ? 1 : 0);\n const childWidth = flexBasis * grow;\n\n const { box, height } = layoutBlock(ctx, child, curX, contentY, childWidth);\n children.push(box);\n maxHeight = Math.max(maxHeight, height);\n curX += childWidth + gap;\n }\n\n return { children, height: maxHeight };\n }\n\n // Column layout (fallback)\n let curY = contentY;\n for (const child of flexChildren) {\n if (child.tagName === '#text') continue;\n const { box, height } = layoutBlock(ctx, child, contentX, curY, contentWidth);\n children.push(box);\n curY += height + gap;\n }\n return { children, height: curY - contentY };\n}\n\n// ─── List marker layout ────────────────────────────────────────────────\n\n/**\n * Add list marker to a layout box if applicable.\n */\nfunction addListMarker(\n ctx: CanvasRenderingContext2D,\n box: LayoutBox,\n node: StyledNode,\n): void {\n if (!node.listMarker) return;\n // `::marker { content: none }` suppresses the marker entirely —\n // canonical CSS behavior, matches the DOM reference.\n if (node.markerHidden) return;\n\n const style = node.style;\n // Marker style = li style with explicit `::marker` overrides applied on top.\n // `markerStyle` holds only keys explicitly set by `::marker` rules, so a\n // missing key falls back to the li style. A present key (incl. 0) wins.\n const ms = node.markerStyle;\n const markerStyleObj: ResolvedStyle = ms ? { ...style, ...ms } : style;\n\n ctx.font = buildCanvasFont(markerStyleObj);\n const lineHeight = getLineHeight(ctx, style);\n const baselineY = box.y + style.borderTopWidth + style.paddingTop +\n computeBaselineY(ctx, style, lineHeight);\n\n const markerWidth = cachedMeasureWidth(ctx, node.listMarker);\n const isRTL = style.direction === 'rtl';\n // Gap between marker box and content. Default = fontSize * 0.15 (intrinsic).\n // `::marker { padding-inline-end: <length> }` overrides — we honor the\n // direction-resolved physical padding (paddingRight in LTR, paddingLeft\n // in RTL) when explicitly set on the marker.\n const explicitGap = isRTL ? ms?.paddingLeft : ms?.paddingRight;\n const gap = explicitGap !== undefined\n ? explicitGap\n : markerStyleObj.fontSize * 0.15;\n\n let markerX: number;\n let markerDirection = 'ltr';\n if (isRTL) {\n // RTL: marker in the parent's right padding area (outside the li box).\n const boxRightEdge = box.x + box.width;\n // Numbered markers (\"1.\") need RTL direction to display as \".1\".\n // With textAlign='right', x is the right edge — so add markerWidth.\n // Bullet markers (•, ○, ■) stay LTR — they're symmetric.\n const isNumbered = /\\d/.test(node.listMarker);\n if (isNumbered) {\n markerDirection = 'rtl';\n markerX = boxRightEdge + gap + markerWidth;\n } else {\n markerX = boxRightEdge + gap;\n }\n } else {\n // LTR: marker in the parent's left padding area (outside the li box).\n // Right-aligned within the padding, with a gap before content.\n const contentStartX = box.x + style.borderLeftWidth + style.paddingLeft;\n markerX = contentStartX - markerWidth - gap;\n }\n\n box.children.unshift({\n type: 'text',\n text: node.listMarker,\n x: markerX,\n y: baselineY,\n width: markerWidth,\n style: { ...markerStyleObj, textDecorationLine: 'none', fontWeight: ms?.fontWeight ?? 400, fontStyle: ms?.fontStyle ?? 'normal', direction: markerDirection },\n });\n}\n\n// ─── Main entry ────────────────────────────────────────────────────────\n\n/**\n * Build the layout tree from the styled tree using pure canvas measurement.\n * No DOM measurements used — all positions computed from CSS values + canvas.measureText.\n */\nexport function buildLayoutTree(\n ctx: CanvasRenderingContext2D,\n styledTree: StyledNode,\n containerWidth: number,\n useDomMeasurements = true,\n debug?: (entry: import('./types.ts').DebugEntry) => void,\n): { root: LayoutBox; height: number } {\n _useDomMeasurements = useDomMeasurements;\n _debug = debug;\n\n // Clear caches — fonts may have loaded since last call\n _lineHeightCache.clear();\n _fontMetricsCache.clear();\n _fontStringCache.clear();\n _measureCache.clear();\n\n // The styledTree root is our container div — layout its children as a block flow\n const { box, height } = layoutBlock(ctx, styledTree, 0, 0, containerWidth);\n\n // Add list markers post-layout\n addListMarkersRecursive(ctx, box, styledTree);\n\n return { root: box, height };\n}\n\nfunction addListMarkersRecursive(\n ctx: CanvasRenderingContext2D,\n box: LayoutBox,\n node: StyledNode,\n): void {\n addListMarker(ctx, box, node);\n\n // Match children — box.children may have extra text/inline nodes,\n // so we correlate by walking both in parallel\n let boxChildIdx = 0;\n for (const styledChild of node.children) {\n if (styledChild.tagName === '#text' || isInline(styledChild)) {\n continue;\n }\n // Find the matching LayoutBox\n while (boxChildIdx < box.children.length) {\n const layoutChild = box.children[boxChildIdx];\n if (layoutChild.type === 'box' && layoutChild.tagName === styledChild.tagName) {\n addListMarkersRecursive(ctx, layoutChild, styledChild);\n boxChildIdx++;\n break;\n }\n boxChildIdx++;\n }\n }\n}\n","import type { LayoutNode, LayoutBox, LayoutText, ResolvedStyle } from './types.js';\nimport { buildCanvasFont, isTransparent, getFontMetrics } from './layout.js';\n\n/**\n * Parse a CSS text-shadow string into individual shadow values.\n * Format: \"2px 2px 4px rgba(0,0,0,0.3), ...\"\n */\nfunction parseTextShadows(shadow: string): Array<{\n offsetX: number;\n offsetY: number;\n blur: number;\n color: string;\n}> {\n if (!shadow || shadow === 'none') return [];\n\n const shadows: Array<{ offsetX: number; offsetY: number; blur: number; color: string }> = [];\n\n // Split by comma but not within parentheses\n const parts = shadow.split(/,(?![^(]*\\))/);\n\n for (const part of parts) {\n const trimmed = part.trim();\n // Extract color (rgb/rgba or named) and numbers\n const colorMatch = trimmed.match(/(rgb[a]?\\([^)]+\\)|#[0-9a-fA-F]+|\\b[a-z]+\\b)(?:\\s|$)/i);\n const numMatches = trimmed.match(/-?[\\d.]+px/g);\n\n if (numMatches && numMatches.length >= 2) {\n const nums = numMatches.map(n => parseFloat(n));\n shadows.push({\n offsetX: nums[0],\n offsetY: nums[1],\n blur: nums[2] || 0,\n color: colorMatch ? colorMatch[1] : 'rgba(0,0,0,1)',\n });\n }\n }\n\n return shadows;\n}\n\n/**\n * Check if a border is visible.\n */\nfunction hasBorder(style: ResolvedStyle, side: 'Top' | 'Right' | 'Bottom' | 'Left'): boolean {\n const width = style[`border${side}Width` as keyof ResolvedStyle] as number;\n const borderStyle = style[`border${side}Style` as keyof ResolvedStyle] as string;\n return width > 0 && borderStyle !== 'none';\n}\n\n/**\n * Draw a decoration line with the given style (solid, dotted, dashed, double, wavy).\n */\nfunction drawDecorationLine(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n width: number,\n lineWidth: number,\n decoStyle: string,\n color: string,\n): void {\n ctx.save();\n ctx.strokeStyle = color;\n ctx.lineWidth = lineWidth;\n\n if (decoStyle === 'double') {\n const gap = Math.max(lineWidth, 2);\n ctx.lineWidth = Math.max(0.5, lineWidth * 0.5);\n ctx.beginPath();\n ctx.moveTo(x, y - gap / 2);\n ctx.lineTo(x + width, y - gap / 2);\n ctx.moveTo(x, y + gap / 2);\n ctx.lineTo(x + width, y + gap / 2);\n ctx.stroke();\n } else if (decoStyle === 'wavy') {\n const amplitude = Math.max(1.5, lineWidth);\n const wavelength = amplitude * 4;\n ctx.beginPath();\n ctx.moveTo(x, y);\n for (let cx = x; cx < x + width; cx += wavelength) {\n ctx.quadraticCurveTo(cx + wavelength / 4, y - amplitude, cx + wavelength / 2, y);\n ctx.quadraticCurveTo(cx + wavelength * 3 / 4, y + amplitude, cx + wavelength, y);\n }\n ctx.stroke();\n } else {\n // solid, dotted, dashed\n if (decoStyle === 'dotted') ctx.setLineDash([lineWidth, lineWidth * 2]);\n else if (decoStyle === 'dashed') ctx.setLineDash([lineWidth * 3, lineWidth * 2]);\n ctx.beginPath();\n ctx.moveTo(x, y);\n ctx.lineTo(x + width, y);\n ctx.stroke();\n }\n\n ctx.setLineDash([]);\n ctx.restore();\n}\n\n/**\n * Parse a CSS linear-gradient into canvas CanvasGradient.\n */\nfunction parseLinearGradient(\n ctx: CanvasRenderingContext2D,\n bgImage: string,\n x: number,\n width: number,\n y: number,\n height: number,\n): CanvasGradient | null {\n // Extract content inside linear-gradient(...) handling nested parens\n const startIdx = bgImage.indexOf('linear-gradient(');\n if (startIdx === -1) return null;\n let depth = 0;\n let endIdx = -1;\n for (let i = startIdx + 16; i < bgImage.length; i++) {\n if (bgImage[i] === '(') depth++;\n else if (bgImage[i] === ')') {\n if (depth === 0) { endIdx = i; break; }\n depth--;\n }\n }\n if (endIdx === -1) return null;\n const innerContent = bgImage.slice(startIdx + 16, endIdx);\n\n // Split by commas not inside parentheses\n const parts: string[] = [];\n depth = 0;\n let start = 0;\n const inner = innerContent;\n for (let i = 0; i < inner.length; i++) {\n if (inner[i] === '(') depth++;\n else if (inner[i] === ')') depth--;\n else if (inner[i] === ',' && depth === 0) {\n parts.push(inner.slice(start, i).trim());\n start = i + 1;\n }\n }\n parts.push(inner.slice(start).trim());\n // Parse angle/direction\n let angle = 180; // default top to bottom\n let colorStartIdx = 0;\n const firstPart = parts[0];\n if (firstPart.endsWith('deg')) {\n angle = parseFloat(firstPart);\n colorStartIdx = 1;\n } else if (firstPart === 'to right') {\n angle = 90; colorStartIdx = 1;\n } else if (firstPart === 'to left') {\n angle = 270; colorStartIdx = 1;\n } else if (firstPart === 'to bottom') {\n angle = 180; colorStartIdx = 1;\n } else if (firstPart === 'to top') {\n angle = 0; colorStartIdx = 1;\n }\n\n const rad = (angle - 90) * Math.PI / 180;\n const cx = x + width / 2;\n const cy = y + height / 2;\n const len = Math.abs(width * Math.cos(rad)) + Math.abs(height * Math.sin(rad));\n const dx = Math.cos(rad) * len / 2;\n const dy = Math.sin(rad) * len / 2;\n\n const gradient = ctx.createLinearGradient(cx - dx, cy - dy, cx + dx, cy + dy);\n\n const colors = parts.slice(colorStartIdx);\n for (let i = 0; i < colors.length; i++) {\n const entry = colors[i].trim();\n // Match color followed by optional percentage: \"rgb(220, 38, 38) 0%\"\n // The percentage is always at the very end after the last space outside parens\n let color = entry;\n let stop = i / Math.max(1, colors.length - 1);\n const percentMatch = entry.match(/\\s+([\\d.]+%)\\s*$/);\n if (percentMatch) {\n stop = parseFloat(percentMatch[1]) / 100;\n color = entry.slice(0, entry.length - percentMatch[0].length).trim();\n }\n try {\n gradient.addColorStop(stop, color);\n } catch {\n // Invalid color, skip\n }\n }\n\n return gradient;\n}\n\n/**\n * Render a single text node to canvas.\n * @param gradientFill — pre-computed gradient for background-clip:text spanning full element\n */\nfunction renderText(ctx: CanvasRenderingContext2D, node: LayoutText, gradientFill?: CanvasGradient | null): void {\n const { style } = node;\n\n ctx.save();\n ctx.font = buildCanvasFont(style);\n ctx.textBaseline = 'alphabetic';\n ctx.fontKerning = style.fontKerning === 'none' ? 'none' : 'normal';\n if (style.letterSpacing > 0) {\n ctx.letterSpacing = `${style.letterSpacing}px`;\n }\n if (style.wordSpacing) {\n (ctx as any).wordSpacing = `${style.wordSpacing}px`;\n }\n if (style.direction === 'rtl') {\n ctx.direction = 'rtl';\n ctx.textAlign = 'right';\n }\n\n const isGradientText = style.webkitBackgroundClip === 'text' &&\n style.backgroundImage && style.backgroundImage !== 'none';\n const isStrokedText = style.webkitTextStrokeWidth > 0;\n const isFillTransparent = style.webkitTextFillColor === 'transparent' ||\n style.color === 'transparent';\n\n // Text shadow (draw before main text)\n const shadows = parseTextShadows(style.textShadow);\n if (shadows.length > 0) {\n for (const shadow of shadows) {\n ctx.save();\n ctx.shadowOffsetX = shadow.offsetX;\n ctx.shadowOffsetY = shadow.offsetY;\n ctx.shadowBlur = shadow.blur;\n ctx.shadowColor = shadow.color;\n ctx.fillStyle = style.color;\n ctx.fillText(node.text, node.x, node.y);\n ctx.restore();\n }\n }\n\n // Main text fill\n if (isGradientText) {\n ctx.save();\n if (gradientFill) {\n ctx.fillStyle = gradientFill;\n } else {\n // Fallback: per-word gradient (shouldn't normally reach here)\n const { ascent, descent } = getFontMetrics(ctx, style);\n const gradient = parseLinearGradient(\n ctx, style.backgroundImage,\n node.x, node.width,\n node.y - ascent, ascent + descent,\n );\n ctx.fillStyle = gradient || style.color;\n }\n ctx.fillText(node.text, node.x, node.y);\n ctx.restore();\n } else if (!isFillTransparent || !isStrokedText) {\n // Normal text fill (skip if transparent + stroked, stroke handles it)\n ctx.fillStyle = style.webkitTextFillColor && style.webkitTextFillColor !== 'transparent'\n ? style.webkitTextFillColor : style.color;\n\n // letterSpacing is set on ctx above — fillText handles it natively\n ctx.fillText(node.text, node.x, node.y);\n }\n\n // Text stroke (outline text)\n if (isStrokedText) {\n ctx.save();\n ctx.strokeStyle = style.webkitTextStrokeColor || style.color;\n ctx.lineWidth = style.webkitTextStrokeWidth;\n ctx.lineJoin = 'round';\n ctx.strokeText(node.text, node.x, node.y);\n ctx.restore();\n }\n\n // Text decorations — use font metrics for accurate positioning\n const textWidth = node.width;\n const fontSize = style.fontSize;\n const decoColor = style.textDecorationColor || style.color;\n const decoStyle = style.textDecorationStyle || 'solid';\n const decoWidth = Math.max(1, fontSize / 15);\n // For RTL text, node.x is the right edge (textAlign='right').\n // Decoration lines need the left edge as start position.\n const decoX = style.direction === 'rtl' ? node.x - textWidth : node.x;\n\n if (style.textDecorationLine !== 'none') {\n const { ascent: decoAscent } = getFontMetrics(ctx, style);\n ctx.font = buildCanvasFont(style);\n const xHeight = ctx.measureText('x').actualBoundingBoxAscent;\n\n if (style.textDecorationLine.includes('underline')) {\n const yOffset = fontSize * 0.1;\n drawDecorationLine(ctx, decoX, node.y + yOffset, textWidth, decoWidth, decoStyle, decoColor);\n }\n\n if (style.textDecorationLine.includes('line-through')) {\n const yOffset = -(xHeight * 0.5);\n drawDecorationLine(ctx, decoX, node.y + yOffset, textWidth, decoWidth, decoStyle, decoColor);\n }\n\n if (style.textDecorationLine.includes('overline')) {\n drawDecorationLine(ctx, decoX, node.y - decoAscent, textWidth, decoWidth, decoStyle, decoColor);\n }\n }\n\n ctx.restore();\n}\n\n/**\n * Render a layout box and its children to canvas.\n */\nfunction renderBox(ctx: CanvasRenderingContext2D, box: LayoutBox): void {\n const { style } = box;\n\n // Background\n if (!isTransparent(style.backgroundColor)) {\n ctx.fillStyle = style.backgroundColor;\n ctx.fillRect(box.x, box.y, box.width, box.height);\n }\n\n // Borders\n const borders: [side: 'Top' | 'Right' | 'Bottom' | 'Left', x1: number, y1: number, x2: number, y2: number][] = [\n ['Top', box.x, box.y + style.borderTopWidth / 2, box.x + box.width, box.y + style.borderTopWidth / 2],\n ['Right', box.x + box.width - style.borderRightWidth / 2, box.y, box.x + box.width - style.borderRightWidth / 2, box.y + box.height],\n ['Bottom', box.x, box.y + box.height - style.borderBottomWidth / 2, box.x + box.width, box.y + box.height - style.borderBottomWidth / 2],\n ['Left', box.x + style.borderLeftWidth / 2, box.y, box.x + style.borderLeftWidth / 2, box.y + box.height],\n ];\n for (const [side, x1, y1, x2, y2] of borders) {\n if (!hasBorder(style, side)) continue;\n ctx.strokeStyle = style[`border${side}Color` as keyof ResolvedStyle] as string;\n ctx.lineWidth = style[`border${side}Width` as keyof ResolvedStyle] as number;\n ctx.beginPath();\n ctx.moveTo(x1, y1);\n ctx.lineTo(x2, y2);\n ctx.stroke();\n }\n\n // Pre-compute gradient for background-clip: text elements\n let gradientFill: CanvasGradient | null = null;\n if (style.webkitBackgroundClip === 'text' && style.backgroundImage && style.backgroundImage !== 'none') {\n gradientFill = parseLinearGradient(ctx, style.backgroundImage, box.x, box.width, box.y, box.height);\n }\n\n // Children\n for (const child of box.children) {\n renderNode(ctx, child, gradientFill);\n }\n}\n\n/**\n * Render any layout node.\n */\nexport function renderNode(ctx: CanvasRenderingContext2D, node: LayoutNode, gradientFill?: CanvasGradient | null): void {\n if (node.type === 'text') {\n renderText(ctx, node, gradientFill);\n } else {\n renderBox(ctx, node);\n }\n}\n","import type {\n RenderConfig, RenderResult,\n LayoutConfig, LayoutResult, DrawConfig,\n LayoutLine, LayoutNode, AnyCanvas, AnyContext,\n} from './types.js';\nimport { parseHTML } from './parse.js';\nimport { resolveStylesFromCSS } from './css-resolver.js';\nimport { buildLayoutTree } from './layout.js';\nimport { renderNode } from './render.js';\n\nexport type { RenderConfig, RenderResult, LayoutConfig, LayoutResult, DrawConfig, LayoutLine };\n\n// ─── Line extraction ─────────────────────────────────────────────────\n\nfunction extractLines(root: LayoutNode): LayoutLine[] {\n const wordPositions: { y: number; fontSize: number; text: string }[] = [];\n function walk(node: LayoutNode) {\n if (node.type === 'text' && node.text.trim()) {\n wordPositions.push({ y: node.y, fontSize: node.style.fontSize, text: node.text });\n }\n if (node.type === 'box') {\n for (const child of node.children) walk(child);\n }\n }\n walk(root);\n wordPositions.sort((a, b) => a.y - b.y);\n\n const lines: LayoutLine[] = [];\n let lineMaxFontSize = 0;\n for (const wp of wordPositions) {\n const lastLine = lines[lines.length - 1];\n const tolerance = Math.max(lineMaxFontSize, wp.fontSize) * 0.5;\n if (lastLine && Math.abs(wp.y - lastLine.y) < tolerance) {\n lastLine.text += wp.text;\n lineMaxFontSize = Math.max(lineMaxFontSize, wp.fontSize);\n } else {\n lines.push({ y: Math.round(wp.y), text: wp.text });\n lineMaxFontSize = wp.fontSize;\n }\n }\n return lines;\n}\n\n// ─── layout() ────────────────────────────────────────────────────────\n\n/**\n * Compute layout for an HTML string without rendering.\n * Returns a reusable LayoutResult that can be drawn onto multiple targets via drawLayout().\n */\nexport function layout(config: LayoutConfig): LayoutResult {\n const {\n html,\n width,\n height,\n accuracy = 'performance',\n debug,\n } = config;\n\n if (!width || width <= 0 || Number.isNaN(width)) {\n throw new TypeError(`layout: width must be a positive number, got ${width}`);\n }\n\n const useDomMeasurements = accuracy === 'balanced';\n\n const { fragment, css } = parseHTML(html);\n const { tree, cleanup } = resolveStylesFromCSS(fragment, css, width);\n\n const tmpCanvas = document.createElement('canvas');\n const measureCtx = tmpCanvas.getContext('2d')!;\n measureCtx.fontKerning = 'normal';\n\n const { root, height: contentHeight } = buildLayoutTree(measureCtx, tree, width, useDomMeasurements, debug);\n const finalHeight = height || contentHeight;\n const lines = extractLines(root);\n\n cleanup();\n\n return { layoutRoot: root, height: finalHeight, lines };\n}\n\n// ─── drawLayout() ────────────────────────────────────────────────────\n\n/**\n * Draw a pre-computed layout onto a canvas or context.\n * Use with layout() to render the same content onto multiple targets.\n */\nexport function drawLayout(config: DrawConfig): { canvas: AnyCanvas } {\n const {\n layout: layoutResult,\n width,\n pixelRatio = globalThis.devicePixelRatio ?? 1,\n } = config;\n\n if (config.ctx && config.canvas) {\n throw new TypeError('drawLayout: ctx and canvas are mutually exclusive — provide one or neither');\n }\n\n const finalHeight = layoutResult.height;\n let canvas: AnyCanvas;\n let renderCtx: AnyContext;\n\n if (config.ctx) {\n renderCtx = config.ctx;\n canvas = config.ctx.canvas;\n } else {\n canvas = config.canvas ?? document.createElement('canvas');\n canvas.width = Math.ceil(width * pixelRatio);\n canvas.height = Math.ceil(finalHeight * pixelRatio);\n if ('style' in canvas) {\n (canvas as HTMLCanvasElement).style.width = `${width}px`;\n (canvas as HTMLCanvasElement).style.height = `${finalHeight}px`;\n }\n renderCtx = canvas.getContext('2d')! as AnyContext;\n renderCtx.scale(pixelRatio, pixelRatio);\n }\n\n renderNode(renderCtx as CanvasRenderingContext2D, layoutResult.layoutRoot);\n\n return { canvas };\n}\n\n// ─── render() ────────────────────────────────────────────────────────\n\n/**\n * Render an HTML string onto a canvas using pure 2D canvas API.\n * Convenience function combining layout() + drawLayout().\n * Fonts must already be loaded before calling this function.\n */\nexport function render(config: RenderConfig): RenderResult {\n if (config.ctx && config.canvas) {\n throw new TypeError('render: ctx and canvas are mutually exclusive — provide one or neither');\n }\n\n const layoutResult = layout({\n html: config.html,\n width: config.width,\n height: config.height,\n accuracy: config.accuracy,\n debug: config.debug,\n });\n\n const { canvas } = drawLayout({\n layout: layoutResult,\n width: config.width,\n ctx: config.ctx,\n canvas: config.canvas,\n pixelRatio: config.pixelRatio,\n });\n\n return {\n canvas,\n height: layoutResult.height,\n layoutRoot: layoutResult.layoutRoot,\n lines: layoutResult.lines,\n };\n}\n\n"],"mappings":"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"}
1
+ {"version":3,"file":"render-tag.umd.js","names":[],"sources":["../src/parse.ts","../src/css-resolver.ts","../src/layout.ts","../src/render.ts","../src/index.ts"],"sourcesContent":["/**\n * Parse HTML string and extract inline <style> blocks.\n * Returns the content element and combined CSS text.\n */\nexport function parseHTML(html: string): { fragment: DocumentFragment; css: string } {\n const parser = new DOMParser();\n const doc = parser.parseFromString(html, 'text/html');\n\n // Extract all <style> tag contents\n const styleTags = doc.querySelectorAll('style');\n let css = '';\n for (const tag of styleTags) {\n css += tag.textContent + '\\n';\n tag.remove();\n }\n\n // Move body children into a fragment\n const fragment = document.createDocumentFragment();\n while (doc.body.firstChild) {\n fragment.appendChild(document.adoptNode(doc.body.firstChild));\n }\n\n return { fragment, css };\n}\n","import type { ResolvedStyle, StyledNode } from './types.js';\n\n// ─── CSS Parser ──────────────────────────────────────────────────────\n\ninterface CSSDeclaration {\n property: string;\n value: string;\n}\n\ninterface CSSRule {\n selectors: string[];\n declarations: CSSDeclaration[];\n}\n\n/**\n * Parse a simple CSS string into rules.\n * Supports: tag, .class, parent > child, comma-separated selectors.\n * Extracts @font-face rules separately for injection into the document.\n */\nfunction parseCSS(css: string): { rules: CSSRule[]; fontFaceRules: string[] } {\n const rules: CSSRule[] = [];\n const fontFaceRules: string[] = [];\n // Remove comments\n css = css.replace(/\\/\\*[\\s\\S]*?\\*\\//g, '');\n\n let i = 0;\n while (i < css.length) {\n // Skip whitespace\n while (i < css.length && /\\s/.test(css[i])) i++;\n if (i >= css.length) break;\n\n // Handle at-rules (@font-face, @media, etc.)\n if (css[i] === '@') {\n const atStart = i;\n let braceDepth = 0;\n while (i < css.length) {\n if (css[i] === '{') braceDepth++;\n if (css[i] === '}') {\n braceDepth--;\n if (braceDepth <= 0) { i++; break; }\n }\n i++;\n }\n // Capture @font-face rules for injection\n const atRule = css.slice(atStart, i);\n if (atRule.startsWith('@font-face')) {\n fontFaceRules.push(atRule);\n }\n continue;\n }\n\n // Read selector(s) up to '{'\n const selectorStart = i;\n while (i < css.length && css[i] !== '{') i++;\n if (i >= css.length) break;\n const selectorStr = css.slice(selectorStart, i).trim();\n i++; // skip '{'\n\n // Read declarations up to '}'\n const declStart = i;\n while (i < css.length && css[i] !== '}') i++;\n const declStr = css.slice(declStart, i).trim();\n i++; // skip '}'\n\n if (!selectorStr) continue;\n\n // Parse selectors (comma-separated)\n const selectors = selectorStr.split(',').map(s => s.trim()).filter(Boolean);\n\n // Parse declarations\n const declarations: CSSDeclaration[] = [];\n for (const decl of declStr.split(';')) {\n const colonIdx = decl.indexOf(':');\n if (colonIdx === -1) continue;\n const property = decl.slice(0, colonIdx).trim().toLowerCase();\n const value = decl.slice(colonIdx + 1).trim();\n if (property && value) {\n declarations.push({ property, value });\n }\n }\n\n if (selectors.length > 0 && declarations.length > 0) {\n rules.push({ selectors, declarations });\n }\n }\n\n return { rules, fontFaceRules };\n}\n\n// ─── Selector Matching ───────────────────────────────────────────────\n\ninterface ElementContext {\n tagName: string;\n classes: Set<string>;\n parent: ElementContext | null;\n el: Element;\n}\n\n/**\n * Compute specificity for a simple selector.\n * Returns [ids, classes, tags] tuple.\n */\nfunction selectorSpecificity(selector: string): [number, number, number] {\n // Remove pseudo-elements for specificity calculation\n const sel = selector.replace(/::[\\w-]+/g, '');\n const parts = sel.split(/\\s*>\\s*|\\s+/);\n let ids = 0, classes = 0, tags = 0;\n for (const part of parts) {\n // Count #id\n const idMatches = part.match(/#[\\w-]+/g);\n if (idMatches) ids += idMatches.length;\n // Count .class\n const classMatches = part.match(/\\.[\\w-]+/g);\n if (classMatches) classes += classMatches.length;\n // Count tag (strip classes/ids/pseudo)\n const tagPart = part.replace(/[#.][\\w-]+/g, '').replace(/:[\\w-]+/g, '').trim();\n if (tagPart && tagPart !== '*') tags++;\n }\n return [ids, classes, tags];\n}\n\n/** Parsed representation of a simple selector part (e.g. \"ul.foo\") */\ninterface ParsedPart {\n tag: string; // '' if no tag, or the tag name\n classes: string[]; // class names without the dot\n}\n\nfunction parsePart(part: string): ParsedPart {\n const classMatches = part.match(/\\.[\\w-]+/g) || [];\n const tag = part.replace(/\\.[\\w-]+/g, '').replace(/:[\\w-]+/g, '').trim();\n return {\n tag: (tag && tag !== '*') ? tag : '',\n classes: classMatches.map(c => c.slice(1)),\n };\n}\n\n/**\n * Check if a parsed selector part matches an element context.\n */\nfunction matchesPart(part: string, ctx: ElementContext): boolean {\n const classMatches = part.match(/\\.[\\w-]+/g) || [];\n const tag = part.replace(/\\.[\\w-]+/g, '').replace(/:[\\w-]+/g, '').trim();\n\n if (tag && tag !== '*' && tag !== ctx.tagName) return false;\n for (const cls of classMatches) {\n if (!ctx.classes.has(cls.slice(1))) return false;\n }\n return true;\n}\n\nfunction matchesParsedPart(part: ParsedPart, ctx: ElementContext): boolean {\n if (part.tag && part.tag !== ctx.tagName) return false;\n for (const cls of part.classes) {\n if (!ctx.classes.has(cls)) return false;\n }\n return true;\n}\n\n/** Pre-parsed selector ready for fast matching */\ninterface ParsedSelector {\n /** Parsed parts, rightmost first (match order) */\n parts: ParsedPart[];\n /** Combinators between parts[i] and parts[i+1]: '>' or ' ' */\n combinators: string[];\n /** Rightmost part — used for index lookup */\n rightmost: ParsedPart;\n /** Whether the rightmost part is 'html' or 'body' (matches root) */\n rightmostIsRoot: boolean;\n /** Original specificity */\n spec: [number, number, number];\n /**\n * Pseudo-element on the rightmost compound. Currently we only honor\n * `::marker` — its declarations apply to the marker box of `<li>`,\n * not to the element body.\n */\n pseudoElement?: 'marker';\n}\n\n/**\n * Pre-parse and tokenize a selector string into a ParsedSelector.\n * Returns null for selectors we can't handle (pseudo-classes; pseudo-elements\n * other than `::marker`).\n */\nfunction parseSelector(selector: string): ParsedSelector | null {\n // Detect `::marker` on the rightmost compound. Other pseudo-elements\n // (::before, ::after, ::first-line, …) are still rejected.\n let pseudoElement: 'marker' | undefined;\n if (selector.includes('::')) {\n // Allow only a single trailing ::marker on the rightmost compound.\n // Anything else is unsupported.\n const otherPseudo = selector.replace(/::marker\\b/g, '');\n if (otherPseudo.includes('::')) return null;\n if (/::marker\\b/.test(selector)) {\n pseudoElement = 'marker';\n // Bare `::marker` (start of input or after whitespace/`>`) → `*`\n // so descendant/child combinators are preserved.\n selector = selector.replace(/(^|[\\s>])::marker\\b/g, '$1*');\n // `::marker` glued to a tag/class compound → strip the pseudo only.\n selector = selector.replace(/::marker\\b/g, '');\n } else {\n return null;\n }\n }\n if (/:(?:nth-|hover|focus|active|visited|first-child|last-child)/.test(selector)) return null;\n\n const tokens: string[] = [];\n const combinators: string[] = [];\n\n const raw = selector.trim().split(/\\s+/);\n for (let i = 0; i < raw.length; i++) {\n if (raw[i] === '>') {\n combinators.push('>');\n } else {\n if (tokens.length > combinators.length + 1) {\n combinators.push(' ');\n }\n tokens.push(raw[i]);\n }\n }\n while (combinators.length < tokens.length - 1) {\n combinators.push(' ');\n }\n\n if (tokens.length === 0) return null;\n\n const parts = tokens.map(parsePart);\n const rightmost = parts[parts.length - 1];\n const rightmostTag = rightmost.tag;\n\n return {\n parts,\n combinators,\n rightmost,\n rightmostIsRoot: rightmostTag === 'html' || rightmostTag === 'body',\n spec: selectorSpecificity(selector),\n pseudoElement,\n };\n}\n\n/**\n * Match a pre-parsed selector against an element context.\n */\nfunction matchesParsedSelector(sel: ParsedSelector, ctx: ElementContext): boolean {\n // Quick check: rightmost part must match current element\n if (sel.rightmostIsRoot) {\n if (ctx.parent !== null) return false; // html/body only match root\n } else {\n if (!matchesParsedPart(sel.rightmost, ctx)) return false;\n }\n\n // Single-part selector — already matched\n if (sel.parts.length === 1) return true;\n\n // Walk ancestors for remaining parts (right-to-left)\n let current: ElementContext | null = ctx.parent;\n for (let ti = sel.parts.length - 2; ti >= 0; ti--) {\n if (!current) return false;\n const part = sel.parts[ti];\n const combinator = sel.combinators[ti];\n\n if (combinator === '>') {\n // Direct child: current ancestor must match\n const isRoot = current.parent === null;\n if (isRoot && (part.tag === 'html' || part.tag === 'body')) {\n current = current.parent;\n } else if (matchesParsedPart(part, current)) {\n current = current.parent;\n } else {\n return false;\n }\n } else {\n // Descendant: any ancestor must match\n let found = false;\n while (current) {\n const isRoot = current.parent === null;\n if (isRoot && (part.tag === 'html' || part.tag === 'body')) {\n current = current.parent;\n found = true;\n break;\n }\n if (matchesParsedPart(part, current)) {\n current = current.parent;\n found = true;\n break;\n }\n current = current.parent;\n }\n if (!found) return false;\n }\n }\n\n return true;\n}\n\n// ─── Style Resolution ────────────────────────────────────────────────\n\n/** Properties that inherit from parent to child */\nconst INHERITED_PROPERTIES = new Set([\n 'font-family', 'font-size', 'font-weight', 'font-style',\n 'color', 'text-align', 'text-align-last', 'text-indent', 'text-transform',\n 'text-decoration-line', 'text-decoration-style', 'text-decoration-color',\n 'letter-spacing', 'word-spacing', 'font-kerning',\n 'line-height', 'white-space', 'word-break', 'overflow-wrap',\n 'direction', 'text-shadow', 'list-style-type',\n 'vertical-align', 'paint-order',\n]);\n\n/** Default values for all ResolvedStyle properties */\nfunction defaultStyle(): ResolvedStyle {\n return {\n fontFamily: 'sans-serif',\n fontSize: 16,\n fontWeight: 400,\n fontStyle: 'normal',\n color: 'rgb(0, 0, 0)',\n textAlign: 'start',\n textAlignLast: 'auto',\n textIndent: 0,\n textTransform: 'none',\n textDecorationLine: 'none',\n textDecorationStyle: 'solid',\n textDecorationColor: 'rgb(0, 0, 0)',\n textShadow: 'none',\n webkitTextStrokeWidth: 0,\n webkitTextStrokeColor: '',\n webkitTextFillColor: '',\n paintOrder: 'normal',\n webkitBackgroundClip: '',\n backgroundImage: 'none',\n letterSpacing: 0,\n wordSpacing: 0,\n fontKerning: 'auto',\n lineHeight: 0,\n verticalAlign: 'baseline',\n whiteSpace: 'normal',\n wordBreak: 'normal',\n overflowWrap: 'normal',\n direction: 'ltr',\n display: 'block',\n width: 0,\n minHeight: 0,\n paddingTop: 0,\n paddingRight: 0,\n paddingBottom: 0,\n paddingLeft: 0,\n marginTop: 0,\n marginRight: 0,\n marginBottom: 0,\n marginLeft: 0,\n backgroundColor: 'rgba(0, 0, 0, 0)',\n borderTopWidth: 0,\n borderTopColor: 'rgb(0, 0, 0)',\n borderTopStyle: 'none',\n borderRightWidth: 0,\n borderRightColor: 'rgb(0, 0, 0)',\n borderRightStyle: 'none',\n borderBottomWidth: 0,\n borderBottomColor: 'rgb(0, 0, 0)',\n borderBottomStyle: 'none',\n borderLeftWidth: 0,\n borderLeftColor: 'rgb(0, 0, 0)',\n borderLeftStyle: 'none',\n flexDirection: 'row',\n gap: 0,\n flexGrow: 0,\n listStyleType: 'disc',\n };\n}\n\n/** Tag-level default display values and browser default margins */\nconst TAG_DEFAULTS: Record<string, Partial<ResolvedStyle>> = {\n span: { display: 'inline' },\n a: { display: 'inline' },\n strong: { display: 'inline', fontWeight: 700 },\n b: { display: 'inline', fontWeight: 700 },\n em: { display: 'inline', fontStyle: 'italic' },\n i: { display: 'inline', fontStyle: 'italic' },\n u: { display: 'inline', textDecorationLine: 'underline' },\n s: { display: 'inline', textDecorationLine: 'line-through' },\n strike: { display: 'inline', textDecorationLine: 'line-through' },\n del: { display: 'inline', textDecorationLine: 'line-through' },\n sub: { display: 'inline', verticalAlign: 'sub', fontSize: 0.83 },\n sup: { display: 'inline', verticalAlign: 'super', fontSize: 0.83 },\n code: { display: 'inline', fontFamily: 'monospace' },\n cite: { display: 'inline', fontStyle: 'italic' },\n p: { display: 'block', marginTop: -1, marginBottom: -1 }, // -1 = 1em, resolved later\n div: { display: 'block' },\n h1: { display: 'block', fontSize: 2, fontWeight: 700, marginTop: -0.67, marginBottom: -0.67 },\n h2: { display: 'block', fontSize: 1.5, fontWeight: 700, marginTop: -0.83, marginBottom: -0.83 },\n h3: { display: 'block', fontSize: 1.17, fontWeight: 700, marginTop: -1, marginBottom: -1 },\n h4: { display: 'block', fontSize: 1, fontWeight: 700, marginTop: -1.33, marginBottom: -1.33 },\n h5: { display: 'block', fontSize: 0.83, fontWeight: 700, marginTop: -1.67, marginBottom: -1.67 },\n h6: { display: 'block', fontSize: 0.67, fontWeight: 700, marginTop: -2.33, marginBottom: -2.33 },\n ul: { display: 'block', listStyleType: 'disc', marginTop: -1, marginBottom: -1 },\n ol: { display: 'block', listStyleType: 'decimal', marginTop: -1, marginBottom: -1 },\n li: { display: 'list-item' },\n blockquote: { display: 'block', marginTop: -1, marginBottom: -1, marginLeft: 40, marginRight: 40 },\n pre: { display: 'block', whiteSpace: 'pre', fontFamily: 'monospace', marginTop: -1, marginBottom: -1 },\n table: { display: 'table' },\n tr: { display: 'table-row' },\n td: { display: 'table-cell' },\n th: { display: 'table-cell', fontWeight: 700 },\n br: { display: 'inline' },\n hr: {\n display: 'block',\n borderTopWidth: 1,\n borderTopStyle: 'solid',\n borderTopColor: 'gray',\n marginTop: -0.5,\n marginBottom: -0.5,\n },\n};\n\n/**\n * Parse a CSS value to pixels given a parent font size for em/% resolution.\n */\nfunction parseValue(value: string, parentFontSize: number, containerWidth: number): number {\n if (!value || value === 'normal' || value === 'auto' || value === 'none') return 0;\n const trimmed = value.trim();\n\n if (trimmed.endsWith('em')) {\n const num = parseFloat(trimmed);\n return isNaN(num) ? 0 : num * parentFontSize;\n }\n if (trimmed.endsWith('%')) {\n const num = parseFloat(trimmed);\n return isNaN(num) ? 0 : (num / 100) * containerWidth;\n }\n if (trimmed.endsWith('px')) {\n const num = parseFloat(trimmed);\n return isNaN(num) ? 0 : num;\n }\n // Bare number (for line-height, etc.)\n const num = parseFloat(trimmed);\n return isNaN(num) ? 0 : num;\n}\n\nfunction parseFontWeight(value: string): number {\n if (value === 'bold') return 700;\n if (value === 'normal') return 400;\n const num = parseInt(value, 10);\n return isNaN(num) ? 400 : num;\n}\n\n/**\n * Resolve `paint-order` to whether stroke is painted before fill.\n * Per CSS spec, missing tokens append in order: fill, stroke, markers.\n * So `stroke` alone implies `stroke fill markers` (stroke first).\n */\nexport function paintOrderHasStrokeFirst(paintOrder: string): boolean {\n const v = paintOrder.trim().toLowerCase();\n if (!v || v === 'normal') return false;\n const tokens = v.split(/\\s+/).filter(t => t === 'fill' || t === 'stroke');\n const strokeIdx = tokens.indexOf('stroke');\n const fillIdx = tokens.indexOf('fill');\n if (strokeIdx === -1) return false;\n if (fillIdx === -1) return true;\n return strokeIdx < fillIdx;\n}\n\n/** Split on whitespace, but only at paren-depth 0 — keeps `rgb(1, 2, 3)` intact. */\nfunction splitTopLevelWhitespace(value: string): string[] {\n const parts: string[] = [];\n let depth = 0;\n let cur = '';\n for (let i = 0; i < value.length; i++) {\n const ch = value[i];\n if (ch === '(') { depth++; cur += ch; }\n else if (ch === ')') { depth = Math.max(0, depth - 1); cur += ch; }\n else if (depth === 0 && /\\s/.test(ch)) {\n if (cur) { parts.push(cur); cur = ''; }\n } else cur += ch;\n }\n if (cur) parts.push(cur);\n return parts;\n}\n\n/**\n * Expand shorthand properties into individual ones.\n * E.g., margin: 10px 20px → marginTop/Right/Bottom/Left\n */\nexport function expandShorthand(property: string, value: string): CSSDeclaration[] {\n if (property === 'margin' || property === 'padding') {\n const parts = value.trim().split(/\\s+/);\n let top: string, right: string, bottom: string, left: string;\n if (parts.length === 1) {\n top = right = bottom = left = parts[0];\n } else if (parts.length === 2) {\n top = bottom = parts[0];\n right = left = parts[1];\n } else if (parts.length === 3) {\n top = parts[0]; right = left = parts[1]; bottom = parts[2];\n } else {\n top = parts[0]; right = parts[1]; bottom = parts[2]; left = parts[3];\n }\n return [\n { property: `${property}-top`, value: top },\n { property: `${property}-right`, value: right },\n { property: `${property}-bottom`, value: bottom },\n { property: `${property}-left`, value: left },\n ];\n }\n\n if (property === 'border' || property === 'border-top' || property === 'border-right' ||\n property === 'border-bottom' || property === 'border-left') {\n const parts = value.trim().split(/\\s+/);\n const borderStyles = ['solid', 'dashed', 'dotted', 'double', 'none', 'hidden'];\n const width = parts.find(p => p.endsWith('px') || /^\\d/.test(p)) || '0';\n const style = parts.find(p => borderStyles.includes(p)) || 'none';\n const color = parts.find(p => !p.endsWith('px') && !/^\\d/.test(p) && !borderStyles.includes(p)) || 'currentColor';\n const result: CSSDeclaration[] = [];\n const sides = property === 'border'\n ? ['top', 'right', 'bottom', 'left']\n : [property.replace('border-', '')];\n for (const side of sides) {\n result.push({ property: `border-${side}-width`, value: width });\n result.push({ property: `border-${side}-style`, value: style });\n result.push({ property: `border-${side}-color`, value: color });\n }\n return result;\n }\n\n if (property === 'list-style') {\n // list-style: none → list-style-type: none\n if (value === 'none') {\n return [{ property: 'list-style-type', value: 'none' }];\n }\n return [{ property: 'list-style-type', value }];\n }\n\n if (property === 'text-decoration') {\n const v = value.trim();\n if (v === 'inherit' || v === 'none') {\n return [{ property: 'text-decoration-line', value: 'none' }];\n }\n // Extract color functions (rgb(...), hsl(...)) before splitting on whitespace,\n // because they contain spaces internally (e.g. \"rgb(231, 76, 60)\").\n let colorValue = '';\n const withoutColorFn = v.replace(/\\b(rgba?\\([^)]*\\)|hsla?\\([^)]*\\))/i, (match) => {\n colorValue = match;\n return '';\n });\n const parts = withoutColorFn.split(/\\s+/).filter(Boolean);\n const lineValues = ['underline', 'overline', 'line-through'];\n const styleValues = ['solid', 'double', 'dotted', 'dashed', 'wavy'];\n const result: CSSDeclaration[] = [];\n const lines: string[] = [];\n for (const p of parts) {\n if (lineValues.includes(p)) lines.push(p);\n else if (styleValues.includes(p)) result.push({ property: 'text-decoration-style', value: p });\n else if (p.startsWith('#'))\n result.push({ property: 'text-decoration-color', value: p });\n }\n if (colorValue) result.push({ property: 'text-decoration-color', value: colorValue });\n if (lines.length > 0) result.unshift({ property: 'text-decoration-line', value: lines.join(' ') });\n return result;\n }\n\n if (property === '-webkit-text-stroke') {\n // -webkit-text-stroke: 1px #1e40af → width + color\n // Split on whitespace at paren-depth 0 so colors with internal spaces\n // (rgb(255, 255, 255), var(--c, ...), color(srgb 1 0 0), …) survive intact.\n const parts = splitTopLevelWhitespace(value.trim());\n const width = parts.find(p => p.endsWith('px') || /^\\d/.test(p)) || '0';\n const color = parts.find(p => !p.endsWith('px') && !/^\\d/.test(p)) || 'currentColor';\n return [\n { property: '-webkit-text-stroke-width', value: width },\n { property: '-webkit-text-stroke-color', value: color },\n ];\n }\n\n if (property === 'flex') {\n // flex: 1 → flex-grow: 1\n const parts = value.trim().split(/\\s+/);\n const grow = parseFloat(parts[0]);\n if (!isNaN(grow)) {\n return [{ property: 'flex-grow', value: String(grow) }];\n }\n return [];\n }\n\n if (property === 'border-collapse' || property === 'border-spacing') {\n // Ignored — table-specific properties we don't handle\n return [];\n }\n\n return [{ property, value }];\n}\n\n/**\n * Apply a CSS declaration to a ResolvedStyle, resolving units.\n */\nfunction applyDeclaration(\n style: ResolvedStyle,\n property: string,\n value: string,\n parentFontSize: number,\n containerWidth: number,\n direction: string,\n): void {\n // Resolve the font-size first if that's what we're setting, since\n // em values for other properties depend on the element's own font-size\n const fontSize = style.fontSize || parentFontSize;\n\n switch (property) {\n // Font & text\n case 'font-family': style.fontFamily = value.trim(); break;\n case 'font-size': {\n const v = value.trim();\n if (v.endsWith('em')) {\n style.fontSize = parseFloat(v) * parentFontSize;\n } else if (v.endsWith('%')) {\n style.fontSize = (parseFloat(v) / 100) * parentFontSize;\n } else {\n style.fontSize = parseFloat(v) || parentFontSize;\n }\n break;\n }\n case 'font-weight': style.fontWeight = parseFontWeight(value); break;\n case 'font-style': style.fontStyle = value.trim(); break;\n case 'color': style.color = value.trim(); break;\n case 'text-align': style.textAlign = value.trim(); break;\n case 'text-align-last': style.textAlignLast = value.trim(); break;\n case 'text-indent':\n style.textIndent = parseValue(value, fontSize, containerWidth); break;\n case 'text-transform': style.textTransform = value.trim(); break;\n case 'text-decoration-line': style.textDecorationLine = value.trim(); break;\n // text-decoration is expanded in expandShorthand, should not reach here\n // but handle just in case\n case 'text-decoration': break;\n case 'text-decoration-style': style.textDecorationStyle = value.trim(); break;\n case 'text-decoration-color': style.textDecorationColor = value.trim(); break;\n case 'text-shadow': style.textShadow = value.trim(); break;\n case '-webkit-text-stroke-width': style.webkitTextStrokeWidth = parseValue(value, fontSize, containerWidth); break;\n case '-webkit-text-stroke-color': style.webkitTextStrokeColor = value.trim(); break;\n case '-webkit-text-fill-color': style.webkitTextFillColor = value.trim(); break;\n case 'paint-order': style.paintOrder = value.trim(); break;\n case '-webkit-background-clip':\n case 'background-clip': style.webkitBackgroundClip = value.trim(); break;\n case 'background-image': style.backgroundImage = value.trim(); break;\n case 'letter-spacing':\n style.letterSpacing = value.trim() === 'normal' ? 0 : parseValue(value, fontSize, containerWidth); break;\n case 'word-spacing':\n style.wordSpacing = value.trim() === 'normal' ? 0 : parseValue(value, fontSize, containerWidth); break;\n case 'font-kerning': style.fontKerning = value.trim(); break;\n case 'line-height': {\n const v = value.trim();\n if (v === 'normal') {\n style.lineHeight = 0; // 0 signals \"normal\"\n } else if (v.endsWith('px')) {\n style.lineHeight = parseFloat(v) || 0;\n } else if (v.endsWith('em')) {\n style.lineHeight = parseFloat(v) * fontSize;\n } else {\n // Unitless multiplier — compute for this element's font size\n // and mark as unitless so children re-compute\n const num = parseFloat(v);\n if (!isNaN(num)) {\n style.lineHeight = num * fontSize;\n (style as any)._lineHeightMultiplier = num;\n }\n }\n break;\n }\n case 'vertical-align': style.verticalAlign = value.trim(); break;\n case 'white-space': style.whiteSpace = value.trim(); break;\n case 'word-break': style.wordBreak = value.trim(); break;\n case 'overflow-wrap':\n case 'word-wrap': style.overflowWrap = value.trim(); break;\n case 'direction': style.direction = value.trim(); break;\n\n // Box model\n case 'display': style.display = value.trim(); break;\n case 'width': {\n const v = value.trim();\n if (v === '100%') style.width = containerWidth;\n else if (v !== 'auto') style.width = parseValue(v, fontSize, containerWidth);\n break;\n }\n case 'min-height': style.minHeight = parseValue(value, fontSize, containerWidth); break;\n case 'padding-top': style.paddingTop = parseValue(value, fontSize, containerWidth); break;\n case 'padding-right': style.paddingRight = parseValue(value, fontSize, containerWidth); break;\n case 'padding-bottom': style.paddingBottom = parseValue(value, fontSize, containerWidth); break;\n case 'padding-left': style.paddingLeft = parseValue(value, fontSize, containerWidth); break;\n case 'margin-top': style.marginTop = parseValue(value, fontSize, containerWidth); break;\n case 'margin-right': style.marginRight = parseValue(value, fontSize, containerWidth); break;\n case 'margin-bottom': style.marginBottom = parseValue(value, fontSize, containerWidth); break;\n case 'margin-left': style.marginLeft = parseValue(value, fontSize, containerWidth); break;\n case 'background-color': style.backgroundColor = value.trim(); break;\n case 'background': {\n const v = value.trim();\n if (v.includes('gradient(')) {\n // background: linear-gradient(...) → backgroundImage\n style.backgroundImage = v;\n } else if (v.startsWith('#') || v.startsWith('rgb') || v.startsWith('hsl') ||\n ['transparent', 'none', 'inherit'].includes(v) ||\n /^[a-z]+$/.test(v)) {\n style.backgroundColor = v;\n }\n break;\n }\n\n // Logical properties → physical (based on direction)\n case 'padding-inline-start':\n if (direction === 'rtl') style.paddingRight = parseValue(value, fontSize, containerWidth);\n else style.paddingLeft = parseValue(value, fontSize, containerWidth);\n break;\n case 'padding-inline-end':\n if (direction === 'rtl') style.paddingLeft = parseValue(value, fontSize, containerWidth);\n else style.paddingRight = parseValue(value, fontSize, containerWidth);\n break;\n case 'margin-inline-start':\n if (direction === 'rtl') style.marginRight = parseValue(value, fontSize, containerWidth);\n else style.marginLeft = parseValue(value, fontSize, containerWidth);\n break;\n case 'margin-inline-end':\n if (direction === 'rtl') style.marginLeft = parseValue(value, fontSize, containerWidth);\n else style.marginRight = parseValue(value, fontSize, containerWidth);\n break;\n\n // Border\n case 'border-top-width': style.borderTopWidth = parseValue(value, fontSize, containerWidth); break;\n case 'border-top-color': style.borderTopColor = value.trim(); break;\n case 'border-top-style': style.borderTopStyle = value.trim(); break;\n case 'border-right-width': style.borderRightWidth = parseValue(value, fontSize, containerWidth); break;\n case 'border-right-color': style.borderRightColor = value.trim(); break;\n case 'border-right-style': style.borderRightStyle = value.trim(); break;\n case 'border-bottom-width': style.borderBottomWidth = parseValue(value, fontSize, containerWidth); break;\n case 'border-bottom-color': style.borderBottomColor = value.trim(); break;\n case 'border-bottom-style': style.borderBottomStyle = value.trim(); break;\n case 'border-left-width': style.borderLeftWidth = parseValue(value, fontSize, containerWidth); break;\n case 'border-left-color': style.borderLeftColor = value.trim(); break;\n case 'border-left-style': style.borderLeftStyle = value.trim(); break;\n\n // Flex\n case 'flex-direction': style.flexDirection = value.trim(); break;\n case 'gap': style.gap = parseValue(value, fontSize, containerWidth); break;\n case 'flex-grow': style.flexGrow = parseFloat(value) || 0; break;\n\n // List\n case 'list-style-type': style.listStyleType = value.trim(); break;\n\n // Ignored properties (not relevant for our layout)\n case 'position':\n case 'top':\n case 'left':\n case 'right':\n case 'bottom':\n case 'inset-inline-start':\n case 'inset-inline-end':\n case 'content':\n case 'counter-reset':\n case 'counter-increment':\n case 'border-radius':\n case 'border-top-left-radius':\n case 'border-top-right-radius':\n case 'border-bottom-left-radius':\n case 'border-bottom-right-radius':\n case 'cursor':\n case 'opacity':\n case 'overflow':\n case 'box-sizing':\n case 'outline':\n case 'transition':\n case 'transform':\n case 'font-stretch':\n case 'font-display':\n case 'src':\n case 'unicode-range':\n break;\n }\n}\n\n/** Inheritable property names (CSS kebab-case) mapped to ResolvedStyle keys */\nconst INHERITABLE_KEYS: [string, keyof ResolvedStyle][] = [\n ['font-family', 'fontFamily'],\n ['font-size', 'fontSize'],\n ['font-weight', 'fontWeight'],\n ['font-style', 'fontStyle'],\n ['color', 'color'],\n ['text-align', 'textAlign'],\n ['text-align-last', 'textAlignLast'],\n ['text-indent', 'textIndent'],\n ['text-transform', 'textTransform'],\n ['white-space', 'whiteSpace'],\n ['word-break', 'wordBreak'],\n ['overflow-wrap', 'overflowWrap'],\n ['direction', 'direction'],\n ['letter-spacing', 'letterSpacing'],\n ['word-spacing', 'wordSpacing'],\n ['line-height', 'lineHeight'],\n ['text-shadow', 'textShadow'],\n ['font-kerning', 'fontKerning'],\n ['list-style-type', 'listStyleType'],\n ['vertical-align', 'verticalAlign'],\n ['paint-order', 'paintOrder'],\n];\n\n/**\n * Inherit properties from parent style to child style for properties\n * not explicitly set (tracked via setProps).\n */\nfunction inheritFrom(child: ResolvedStyle, parent: ResolvedStyle, setProps: Set<string>): void {\n for (const [cssProp, key] of INHERITABLE_KEYS) {\n if (!setProps.has(cssProp)) {\n if (key === 'lineHeight') {\n // Unitless line-height: re-compute relative to child's font-size\n const multiplier = (parent as any)._lineHeightMultiplier;\n if (multiplier !== undefined) {\n child.lineHeight = multiplier * child.fontSize;\n (child as any)._lineHeightMultiplier = multiplier;\n } else {\n child.lineHeight = parent.lineHeight;\n }\n } else {\n (child as any)[key] = (parent as any)[key];\n }\n }\n }\n}\n\n// ─── Main resolver ───────────────────────────────────────────────────\n\ninterface MatchedDeclaration {\n property: string;\n value: string;\n specificity: [number, number, number];\n order: number;\n important: boolean;\n}\n\n/** A pre-processed rule entry with parsed selector and pre-expanded declarations */\ninterface ProcessedRule {\n selector: ParsedSelector;\n declarations: { property: string; value: string; important: boolean }[];\n /** Global order for cascade sorting */\n orderBase: number;\n}\n\n/**\n * Build an index of processed rules keyed by rightmost tag name and class names.\n * The '*' key holds rules that match any element (no tag or class constraint).\n */\nfunction buildRuleIndex(rules: CSSRule[]): {\n byTag: Map<string, ProcessedRule[]>;\n byClass: Map<string, ProcessedRule[]>;\n universal: ProcessedRule[];\n} {\n const byTag = new Map<string, ProcessedRule[]>();\n const byClass = new Map<string, ProcessedRule[]>();\n const universal: ProcessedRule[] = [];\n let orderBase = 0;\n\n for (const rule of rules) {\n // Pre-expand declarations once\n const expandedDecls: { property: string; value: string; important: boolean }[] = [];\n for (const decl of rule.declarations) {\n const isImportant = decl.value.includes('!important');\n const cleanValue = isImportant\n ? decl.value.replace(/\\s*!important\\s*/g, '').trim()\n : decl.value;\n const expanded = expandShorthand(decl.property, cleanValue);\n for (const exp of expanded) {\n expandedDecls.push({ property: exp.property, value: exp.value, important: isImportant });\n }\n }\n\n for (const sel of rule.selectors) {\n const parsed = parseSelector(sel);\n if (!parsed) continue;\n\n const entry: ProcessedRule = {\n selector: parsed,\n declarations: expandedDecls,\n orderBase: orderBase++,\n };\n\n const rm = parsed.rightmost;\n if (rm.tag && !parsed.rightmostIsRoot) {\n // Index by tag\n const list = byTag.get(rm.tag);\n if (list) list.push(entry);\n else byTag.set(rm.tag, [entry]);\n }\n if (rm.classes.length > 0) {\n // Index by first class (most selective)\n const cls = rm.classes[0];\n const list = byClass.get(cls);\n if (list) list.push(entry);\n else byClass.set(cls, [entry]);\n }\n if (!rm.tag && rm.classes.length === 0) {\n // Universal selector or html/body root\n universal.push(entry);\n }\n // Also add root-matching selectors to universal\n if (parsed.rightmostIsRoot) {\n universal.push(entry);\n }\n }\n }\n\n return { byTag, byClass, universal };\n}\n\n/** Format an integer using a CSS list-style-type. */\nfunction formatListMarker(n: number, type: string): string {\n switch (type) {\n case 'disc': return '•';\n case 'circle': return '○';\n case 'square': return '■';\n case 'none': return '';\n case 'decimal-leading-zero':\n return `${n < 10 && n >= 0 ? '0' + n : n}.`;\n case 'lower-roman': return `${toRoman(n).toLowerCase()}.`;\n case 'upper-roman': return `${toRoman(n)}.`;\n case 'lower-alpha':\n case 'lower-latin': return `${toAlpha(n).toLowerCase()}.`;\n case 'upper-alpha':\n case 'upper-latin': return `${toAlpha(n)}.`;\n case 'decimal':\n default:\n return `${n}.`;\n }\n}\n\nfunction toRoman(n: number): string {\n if (n < 1 || n > 3999) return `${n}`;\n const map: [number, string][] = [\n [1000, 'M'], [900, 'CM'], [500, 'D'], [400, 'CD'],\n [100, 'C'], [90, 'XC'], [50, 'L'], [40, 'XL'],\n [10, 'X'], [9, 'IX'], [5, 'V'], [4, 'IV'], [1, 'I'],\n ];\n let out = '';\n for (const [v, s] of map) {\n while (n >= v) { out += s; n -= v; }\n }\n return out;\n}\n\nfunction toAlpha(n: number): string {\n if (n < 1) return `${n}`;\n let out = '';\n while (n > 0) {\n const r = (n - 1) % 26;\n out = String.fromCharCode(65 + r) + out;\n n = Math.floor((n - 1) / 26);\n }\n return out;\n}\n\n/**\n * Detect list marker text for a <li> element based on tree position,\n * honoring list-style-type, <ol start>, <ol reversed>, and <li value>.\n */\nfunction getListMarker(el: Element, listStyleType: string): string | undefined {\n const tag = el.tagName.toLowerCase();\n if (tag !== 'li') return undefined;\n if (listStyleType === 'none') return '';\n\n const parent = el.parentElement;\n const parentTag = parent?.tagName.toLowerCase();\n\n // Bullet markers: independent of position.\n if (listStyleType === 'disc' || listStyleType === 'circle' || listStyleType === 'square') {\n return formatListMarker(0, listStyleType);\n }\n\n // Numbered markers: compute index from siblings + ol attributes + li value.\n if (parentTag === 'ol' || parentTag === 'ul' || !parent) {\n const liItems = parent\n ? Array.from(parent.children).filter(c => c.tagName.toLowerCase() === 'li')\n : [el];\n const startAttr = parent?.getAttribute('start');\n const reversed = parent?.hasAttribute('reversed') ?? false;\n const start = startAttr ? parseInt(startAttr, 10) : (reversed ? liItems.length : 1);\n const step = reversed ? -1 : 1;\n let n = start;\n for (const item of liItems) {\n const valueAttr = item.getAttribute('value');\n if (valueAttr) {\n const v = parseInt(valueAttr, 10);\n if (!Number.isNaN(v)) n = v;\n }\n if (item === el) return formatListMarker(n, listStyleType || 'decimal');\n n += step;\n }\n return formatListMarker(n, listStyleType || 'decimal');\n }\n\n return undefined;\n}\n\n/**\n * Parse inline style attribute into declarations.\n */\nfunction parseInlineStyle(styleAttr: string): CSSDeclaration[] {\n const declarations: CSSDeclaration[] = [];\n for (const decl of styleAttr.split(';')) {\n const colonIdx = decl.indexOf(':');\n if (colonIdx === -1) continue;\n const property = decl.slice(0, colonIdx).trim().toLowerCase();\n const value = decl.slice(colonIdx + 1).trim();\n if (property && value) {\n declarations.push({ property, value });\n }\n }\n return declarations;\n}\n\n/**\n * Resolve styles for a DOM tree without inserting into the document.\n * Parses CSS rules, matches selectors, resolves cascade + inheritance.\n */\nexport function resolveStylesFromCSS(\n fragment: DocumentFragment,\n css: string,\n containerWidth: number,\n): { tree: StyledNode; cleanup: () => void } {\n const { rules, fontFaceRules } = parseCSS(css);\n\n // Inject @font-face rules into the document so fonts can load\n let fontStyleEl: HTMLStyleElement | null = null;\n if (fontFaceRules.length > 0) {\n fontStyleEl = document.createElement('style');\n fontStyleEl.textContent = fontFaceRules.join('\\n');\n document.head.appendChild(fontStyleEl);\n }\n\n // Build indexed rule lookup\n const ruleIndex = buildRuleIndex(rules);\n\n // Wrap fragment in a container div so resolveElement has a single root Element\n const container = document.createElement('div');\n container.appendChild(fragment);\n\n function buildContext(el: Element, parent: ElementContext | null): ElementContext {\n const classes = new Set<string>();\n const className = el.getAttribute('class');\n if (className) {\n for (const c of className.split(/\\s+/)) {\n if (c) classes.add(c);\n }\n }\n return {\n tagName: el.tagName.toLowerCase(),\n classes,\n parent,\n el,\n };\n }\n\n function resolveElement(\n el: Element,\n parentStyle: ResolvedStyle,\n parentCtx: ElementContext | null,\n ): StyledNode {\n const tag = el.tagName.toLowerCase();\n const ctx = buildContext(el, parentCtx);\n\n // Start with defaults\n const style = defaultStyle();\n\n // Track which properties are explicitly set (tag defaults, CSS rules, inline styles)\n const setProps = new Set<string>();\n\n // --- Step 1: Determine font-size first (needed for em/multiplier resolution) ---\n\n // Collect candidate rules from index (only rules that could match this element)\n const candidates: ProcessedRule[] = [];\n const seen = new Set<ProcessedRule>();\n\n const tagRules = ruleIndex.byTag.get(tag);\n if (tagRules) for (const r of tagRules) { seen.add(r); candidates.push(r); }\n\n for (const cls of ctx.classes) {\n const clsRules = ruleIndex.byClass.get(cls);\n if (clsRules) for (const r of clsRules) {\n if (!seen.has(r)) { seen.add(r); candidates.push(r); }\n }\n }\n\n for (const r of ruleIndex.universal) {\n if (!seen.has(r)) { seen.add(r); candidates.push(r); }\n }\n\n // Match candidates and collect pre-expanded declarations.\n // Rules with `::marker` are routed to a separate list and applied to the\n // <li>'s markerStyle later — they do not affect the element body.\n const matched: MatchedDeclaration[] = [];\n const matchedMarker: MatchedDeclaration[] = [];\n for (const candidate of candidates) {\n if (matchesParsedSelector(candidate.selector, ctx)) {\n const target = candidate.selector.pseudoElement === 'marker' ? matchedMarker : matched;\n for (const decl of candidate.declarations) {\n target.push({\n property: decl.property,\n value: decl.value,\n specificity: candidate.selector.spec,\n order: candidate.orderBase,\n important: decl.important,\n });\n }\n }\n }\n\n // Tag default font-size\n const tagDef = TAG_DEFAULTS[tag];\n let fontSizeSet = false;\n if (tagDef?.fontSize !== undefined) {\n const val = tagDef.fontSize as number;\n if (val < 10) {\n style.fontSize = val * parentStyle.fontSize;\n } else {\n style.fontSize = val;\n }\n fontSizeSet = true;\n setProps.add('font-size');\n }\n\n // Sort by: !important first, then specificity, then source order\n if (matched.length > 1) {\n matched.sort((a, b) => {\n if (a.important !== b.important) return a.important ? 1 : -1;\n const sa = a.specificity, sb = b.specificity;\n if (sa[0] !== sb[0]) return sa[0] - sb[0];\n if (sa[1] !== sb[1]) return sa[1] - sb[1];\n if (sa[2] !== sb[2]) return sa[2] - sb[2];\n return a.order - b.order;\n });\n }\n\n // Apply font-size from CSS rules\n for (const m of matched) {\n if (m.property === 'font-size') {\n applyDeclaration(style, m.property, m.value, parentStyle.fontSize, containerWidth, parentStyle.direction);\n fontSizeSet = true;\n }\n }\n\n // Apply font-size from inline styles\n if (el instanceof HTMLElement && el.style.cssText) {\n const inlineDecls = parseInlineStyle(el.style.cssText);\n for (const decl of inlineDecls) {\n if (decl.property === 'font-size') {\n applyDeclaration(style, decl.property, decl.value, parentStyle.fontSize, containerWidth, parentStyle.direction);\n fontSizeSet = true;\n }\n }\n }\n\n // Inherit font-size from parent if not set\n if (!fontSizeSet) {\n style.fontSize = parentStyle.fontSize;\n }\n\n // Now style.fontSize is the element's computed font-size\n const elemFontSize = style.fontSize;\n\n // --- Step 2: Apply all other properties using resolved font-size ---\n\n // Apply non-fontSize tag defaults\n if (tagDef) {\n for (const [key, val] of Object.entries(tagDef)) {\n if (key === 'fontSize') continue; // already handled\n (style as any)[key] = val;\n const cssKey = key.replace(/[A-Z]/g, m => '-' + m.toLowerCase());\n setProps.add(cssKey);\n }\n\n // Resolve negative margin values (em multipliers from tag defaults)\n if (style.marginTop < 0) style.marginTop = Math.abs(style.marginTop) * elemFontSize;\n if (style.marginBottom < 0) style.marginBottom = Math.abs(style.marginBottom) * elemFontSize;\n\n // Default padding-inline-start for lists (direction-aware)\n if (tag === 'ul' || tag === 'ol') {\n const dir = parentStyle.direction;\n if (dir === 'rtl') {\n style.paddingRight = 40;\n setProps.add('padding-right');\n } else {\n style.paddingLeft = 40;\n setProps.add('padding-left');\n }\n }\n }\n\n // Determine direction from parent for logical property resolution\n const direction = parentStyle.direction;\n\n // Property aliases: CSS name → canonical name for setProps tracking\n const PROP_ALIASES: Record<string, string> = {\n 'word-wrap': 'overflow-wrap',\n };\n\n // Apply matched CSS declarations (skip font-size, already applied)\n for (const m of matched) {\n if (m.property === 'font-size') continue;\n applyDeclaration(style, m.property, m.value, elemFontSize, containerWidth, direction);\n setProps.add(PROP_ALIASES[m.property] || m.property);\n }\n\n // Apply inline styles (highest specificity, skip font-size)\n const hasInlineWidth = el instanceof HTMLElement && !!el.style.width;\n if (el instanceof HTMLElement && el.style.cssText) {\n const inlineDecls = parseInlineStyle(el.style.cssText);\n for (const decl of inlineDecls) {\n if (decl.property === 'font-size') {\n setProps.add('font-size');\n continue;\n }\n const expanded = expandShorthand(decl.property, decl.value);\n for (const exp of expanded) {\n applyDeclaration(style, exp.property, exp.value, elemFontSize, containerWidth, direction);\n setProps.add(PROP_ALIASES[exp.property] || exp.property);\n }\n }\n }\n\n // Only keep explicit width from inline styles (match DOM resolver behavior)\n if (!hasInlineWidth) {\n style.width = 0;\n }\n\n // Handle `dir` attribute\n const dirAttr = el.getAttribute('dir');\n if (dirAttr) {\n style.direction = dirAttr;\n setProps.add('direction');\n }\n\n // Inherit from parent for properties not explicitly set\n setProps.add('font-size'); // already resolved\n inheritFrom(style, parentStyle, setProps);\n\n // Auto-set currentColor defaults (browser default behavior)\n if (!setProps.has('text-decoration-color')) {\n style.textDecorationColor = style.color;\n }\n if (!setProps.has('-webkit-text-stroke-color')) {\n if (style.webkitTextStrokeColor === '' || style.webkitTextStrokeColor === 'currentColor') {\n style.webkitTextStrokeColor = style.color;\n }\n } else if (style.webkitTextStrokeColor === 'currentColor') {\n style.webkitTextStrokeColor = style.color;\n }\n for (const side of ['Top', 'Right', 'Bottom', 'Left'] as const) {\n const colorKey = `border${side}Color` as keyof ResolvedStyle;\n const propName = `border-${side.toLowerCase()}-color`;\n if (!setProps.has(propName)) {\n (style as any)[colorKey] = style.color;\n } else if ((style as any)[colorKey] === 'currentColor') {\n (style as any)[colorKey] = style.color;\n }\n }\n\n // Handle text-decoration inheritance (propagates visually, not via normal inheritance)\n const decoSet = new Set(style.textDecorationLine.split(/\\s+/).filter(d => d && d !== 'none'));\n if (parentStyle.textDecorationLine && parentStyle.textDecorationLine !== 'none') {\n for (const d of parentStyle.textDecorationLine.split(/\\s+/)) {\n if (d && d !== 'none') decoSet.add(d);\n }\n }\n if (decoSet.size > 0) {\n style.textDecorationLine = [...decoSet].join(' ');\n }\n\n // List marker\n const marker = getListMarker(el, style.listStyleType);\n\n // Resolve `::marker` rules into a Partial<ResolvedStyle> override and a\n // hidden flag. We only do this for `<li>` because `::marker` only applies\n // to elements with `display: list-item` (in our model, just `<li>`).\n // The override records ONLY the keys actually written by marker\n // declarations, so the layout consumer can distinguish \"user set padding\n // to 0\" from \"no rule\".\n let markerStyle: Partial<ResolvedStyle> | undefined;\n let markerHidden = false;\n if (tag === 'li' && matchedMarker.length > 0) {\n // Sort by cascade order — same rules as element style.\n if (matchedMarker.length > 1) {\n matchedMarker.sort((a, b) => {\n if (a.important !== b.important) return a.important ? 1 : -1;\n const sa = a.specificity, sb = b.specificity;\n if (sa[0] !== sb[0]) return sa[0] - sb[0];\n if (sa[1] !== sb[1]) return sa[1] - sb[1];\n if (sa[2] !== sb[2]) return sa[2] - sb[2];\n return a.order - b.order;\n });\n }\n\n // Apply to a scratch style cloned from the resolved <li> style, then\n // copy out the keys that changed. Whitelist the physical fields we\n // actually consume in addListMarker — adding more later is a one-line\n // change once the layout side reads them.\n const TRACKED: (keyof ResolvedStyle)[] = [\n 'paddingLeft', 'paddingRight',\n 'fontSize', 'fontFamily', 'fontWeight', 'fontStyle',\n 'color', 'letterSpacing',\n ];\n const scratch = { ...style } as ResolvedStyle;\n const touched = new Set<keyof ResolvedStyle>();\n for (const m of matchedMarker) {\n // `content: none` (and `content: ''`) suppresses the marker entirely,\n // matching DOM `::marker` behavior. `content` isn't part of\n // ResolvedStyle, so we handle it inline.\n if (m.property === 'content') {\n const v = m.value.trim().toLowerCase();\n if (v === 'none' || v === '\"\"' || v === \"''\" || v === 'normal') {\n // 'normal' is the initial value — no override\n markerHidden = (v === 'none' || v === '\"\"' || v === \"''\");\n }\n continue;\n }\n const before = TRACKED.map(k => scratch[k]);\n applyDeclaration(scratch, m.property, m.value, elemFontSize, containerWidth, direction);\n TRACKED.forEach((k, i) => {\n if (scratch[k] !== before[i]) touched.add(k);\n });\n }\n if (touched.size > 0) {\n markerStyle = {};\n for (const k of touched) (markerStyle as any)[k] = scratch[k];\n }\n }\n\n // Walk children\n const children: StyledNode[] = [];\n for (const child of el.childNodes) {\n const childNode = walkNode(child, style, ctx);\n if (childNode) children.push(childNode);\n }\n\n return {\n element: el,\n tagName: tag,\n style,\n children,\n textContent: null,\n listMarker: marker,\n markerStyle,\n markerHidden: markerHidden || undefined,\n };\n }\n\n function walkNode(\n node: Node,\n parentStyle: ResolvedStyle,\n parentCtx: ElementContext | null,\n ): StyledNode | null {\n if (node.nodeType === Node.TEXT_NODE) {\n const text = node.textContent;\n if (!text) return null;\n\n if (text.trim() === '' && !text.includes('\\u00A0')) {\n const ws = parentStyle.whiteSpace;\n const prev = node.previousSibling;\n const next = node.nextSibling;\n const isInlineSibling = (n: Node | null) => {\n if (!n || n.nodeType !== Node.ELEMENT_NODE) return n?.nodeType === Node.TEXT_NODE;\n const tag = (n as Element).tagName.toLowerCase();\n const def = TAG_DEFAULTS[tag];\n const d = def?.display || 'block';\n return d === 'inline' || d === 'inline-block';\n };\n\n if (prev && next && !isInlineSibling(prev) && !isInlineSibling(next)) {\n if (ws === 'pre' || ws === 'pre-wrap' || ws === 'pre-line') {\n // Keep\n } else {\n return null;\n }\n }\n\n if (ws !== 'pre' && ws !== 'pre-wrap' && ws !== 'pre-line') {\n if (text.includes('\\n')) return null;\n }\n }\n\n // Clone parent style for text node (text nodes don't match CSS rules)\n const style = { ...parentStyle };\n\n // CSS Text 3 §4.1.1: in `normal` and `nowrap`, a source newline is\n // collapsed to a single space (no forced break). Only `pre`,\n // `pre-wrap`, `pre-line`, and `break-spaces` preserve newlines.\n // <br>-derived text nodes are created separately below with `\\n`\n // and are not touched here, so they keep forcing breaks.\n const ws = parentStyle.whiteSpace;\n let normalizedText = text;\n if (ws !== 'pre' && ws !== 'pre-wrap' && ws !== 'pre-line' && ws !== 'break-spaces') {\n normalizedText = text.replace(/[\\n\\r]/g, ' ');\n }\n\n return {\n element: null,\n tagName: '#text',\n style,\n children: [],\n textContent: normalizedText,\n };\n }\n\n if (node.nodeType !== Node.ELEMENT_NODE) return null;\n\n const el = node as Element;\n const tag = el.tagName.toLowerCase();\n if (tag === 'style' || tag === 'script') return null;\n\n // <br> → text node with newline\n if (tag === 'br') {\n return {\n element: null,\n tagName: '#text',\n style: { ...parentStyle },\n children: [],\n textContent: '\\n',\n };\n }\n\n return resolveElement(el, parentStyle, parentCtx);\n }\n\n const rootStyle = defaultStyle();\n const tree = resolveElement(container, rootStyle, null);\n\n const cleanup = () => {\n if (fontStyleEl) fontStyleEl.remove();\n };\n\n return { tree, cleanup };\n}\n","import type { StyledNode, LayoutNode, LayoutBox, LayoutText, ResolvedStyle } from './types.js';\n\n// Module-level flag controlling DOM measurement usage.\n// Set by buildLayoutTree() based on the useDomMeasurements option.\nlet _useDomMeasurements = true;\nlet _debug: ((entry: import('./types.ts').DebugEntry) => void) | undefined;\n\n// ─── measureText width cache ──────────────────────────────────────────\n// Caches ctx.measureText(text).width keyed by \"font\\0text\".\n// Cleared at the start of each buildLayoutTree() call.\nconst _measureCache = new Map<string, number>();\n\nfunction cachedMeasureWidth(ctx: CanvasRenderingContext2D, text: string): number {\n // ctx.font must already be set by caller\n const key = ctx.font + '\\0' + text;\n const cached = _measureCache.get(key);\n if (cached !== undefined) return cached;\n const w = ctx.measureText(text).width;\n _measureCache.set(key, w);\n return w;\n}\n\n\n/**\n * Check if a line has mixed fonts (different fontFamily/fontSize/fontWeight/fontStyle).\n */\nfunction hasMixedFonts(words: Word[]): boolean {\n let font = '';\n for (const w of words) {\n if (!w.text || w.isSpace) continue;\n const f = buildCanvasFont(w.style);\n if (font && f !== font) return true;\n font = f;\n }\n return false;\n}\n\n// ─── Canvas font helpers ───────────────────────────────────────────────\n\n/**\n * Set canvas font and kerning from resolved style.\n */\nfunction applyFont(ctx: CanvasRenderingContext2D, style: ResolvedStyle): void {\n ctx.font = buildCanvasFont(style);\n ctx.fontKerning = style.fontKerning === 'none' ? 'none' : 'normal';\n}\n\n/**\n * Build a canvas font string from resolved style. Results are cached.\n */\nconst _fontStringCache = new Map<string, string>();\nexport function buildCanvasFont(style: ResolvedStyle): string {\n const key = `${style.fontStyle}|${style.fontWeight}|${style.fontSize}|${style.fontFamily}`;\n const cached = _fontStringCache.get(key);\n if (cached) return cached;\n const parts: string[] = [];\n if (style.fontStyle !== 'normal') parts.push(style.fontStyle);\n if (style.fontWeight !== 400) parts.push(String(style.fontWeight));\n parts.push(`${style.fontSize}px`);\n parts.push(style.fontFamily);\n const result = parts.join(' ');\n _fontStringCache.set(key, result);\n return result;\n}\n\n/**\n * Cache for DOM-measured line heights.\n * Key: \"font|lineHeight|probeType\" → actual pixel height from the browser.\n */\nconst _lineHeightCache = new Map<string, number>();\n\n// Probe elements: a <div> for general use, and a <ul><li> for unordered list items.\n// Firefox renders <ul><li> with bullet markers (disc/circle/square) 1.5px taller\n// than other elements for the same line-height, due to the ::marker pseudo-element.\n// <ol><li> items do NOT have this extra height.\nlet _blockProbe: HTMLDivElement | null = null;\nlet _ulProbeContainer: HTMLUListElement | null = null;\nlet _ulProbeLi: HTMLLIElement | null = null;\n\nconst BULLET_MARKERS = new Set(['disc', 'circle', 'square']);\n\n/**\n * Measure the actual line height using a hidden DOM element.\n * Uses an actual <li> inside a <ul> when listStyleType is a bullet marker\n * (disc/circle/square) to capture Firefox's ::marker line box contribution.\n * Results are cached per font+lineHeight+probeType combination.\n */\nfunction measureDomLineHeight(font: string, lineHeight: string, useBulletProbe = false): number {\n const key = `${font}|${lineHeight}|${useBulletProbe ? 'ul-li' : 'block'}`;\n const cached = _lineHeightCache.get(key);\n if (cached !== undefined) return cached;\n\n let probe: HTMLElement;\n if (useBulletProbe) {\n if (!_ulProbeContainer) {\n _ulProbeContainer = document.createElement('ul');\n _ulProbeContainer.style.cssText =\n 'position:absolute;top:-9999px;left:-9999px;visibility:hidden;padding:0;margin:0;border:0;list-style:disc;';\n _ulProbeLi = document.createElement('li');\n _ulProbeLi.style.cssText = 'white-space:nowrap;padding:0;margin:0;border:0;';\n _ulProbeLi.textContent = 'Mg';\n _ulProbeContainer.appendChild(_ulProbeLi);\n document.body.appendChild(_ulProbeContainer);\n }\n probe = _ulProbeLi!;\n } else {\n if (!_blockProbe) {\n _blockProbe = document.createElement('div');\n _blockProbe.style.cssText =\n 'position:absolute;top:-9999px;left:-9999px;visibility:hidden;white-space:nowrap;padding:0;margin:0;border:0;';\n _blockProbe.textContent = 'Mg';\n document.body.appendChild(_blockProbe);\n }\n probe = _blockProbe;\n }\n\n probe.style.font = font;\n probe.style.lineHeight = lineHeight;\n const height = probe.getBoundingClientRect().height;\n\n _lineHeightCache.set(key, height);\n return height;\n}\n\n/**\n * Get the effective line height for a style.\n * Uses DOM measurement for accuracy across browsers (Firefox vs Chrome).\n * Falls back to canvas metrics for \"normal\" line-height.\n */\nfunction getLineHeight(ctx: CanvasRenderingContext2D, style: ResolvedStyle, useBulletProbe = false): number {\n if (style.lineHeight > 0) {\n if (_useDomMeasurements) {\n const font = buildCanvasFont(style);\n return measureDomLineHeight(font, `${style.lineHeight}px`, useBulletProbe);\n }\n // Canvas-only: use the CSS line-height value directly\n return style.lineHeight;\n }\n\n if (_useDomMeasurements) {\n const font = buildCanvasFont(style);\n return measureDomLineHeight(font, 'normal', useBulletProbe);\n }\n\n // Canvas-only fallback for \"normal\" line-height: use font bounding box\n // fontBoundingBoxAscent + fontBoundingBoxDescent already represents the\n // full line box height, no multiplier needed.\n const { ascent, descent } = getFontMetrics(ctx, style);\n return ascent + descent;\n}\n\n/**\n * Compute the baseline Y offset within a line.\n * Uses the Konva approach: center (ascent - descent) within lineHeight.\n */\nfunction computeBaselineY(ctx: CanvasRenderingContext2D, style: ResolvedStyle, lineHeight: number): number {\n const { ascent, descent } = getFontMetrics(ctx, style);\n return (ascent - descent) / 2 + lineHeight / 2;\n}\n\nfunction applyTextTransform(text: string, transform: string): string {\n\n switch (transform) {\n case 'uppercase': return text.toUpperCase();\n case 'lowercase': return text.toLowerCase();\n case 'capitalize': return text.replace(/(^|[\\s\\p{P}])(\\p{L})/gu, (_, p, c) => p + c.toUpperCase());\n default: return text;\n }\n}\n\nfunction isInline(node: StyledNode): boolean {\n if (node.tagName === '#text') return true;\n const d = node.style.display;\n return d === 'inline' || d === 'inline-block';\n}\n\nfunction hasOnlyInlineChildren(node: StyledNode): boolean {\n return node.children.length > 0 && node.children.every(isInline);\n}\n\nexport function isTransparent(color: string): boolean {\n return !color || color === 'transparent' || color === 'rgba(0, 0, 0, 0)';\n}\n\n/**\n * Get font ascent and descent metrics. Results are cached per font string.\n */\nconst _fontMetricsCache = new Map<string, { ascent: number; descent: number }>();\nexport function getFontMetrics(ctx: CanvasRenderingContext2D, style: ResolvedStyle): { ascent: number; descent: number } {\n const font = buildCanvasFont(style);\n const cached = _fontMetricsCache.get(font);\n if (cached) return cached;\n ctx.font = font;\n const m = ctx.measureText('M');\n const ascent = m.fontBoundingBoxAscent ?? m.actualBoundingBoxAscent;\n const descent = m.fontBoundingBoxDescent ?? m.actualBoundingBoxDescent;\n const result = { ascent, descent };\n _fontMetricsCache.set(font, result);\n return result;\n}\n\n/**\n * Check if two styles have the same text rendering properties.\n */\nfunction sameTextStyle(a: ResolvedStyle, b: ResolvedStyle): boolean {\n return a.fontFamily === b.fontFamily &&\n a.fontSize === b.fontSize &&\n a.fontWeight === b.fontWeight &&\n a.fontStyle === b.fontStyle &&\n a.color === b.color &&\n a.textDecorationLine === b.textDecorationLine &&\n a.backgroundColor === b.backgroundColor;\n}\n\nfunction hasVisibleBoxStyles(style: ResolvedStyle): boolean {\n if (!isTransparent(style.backgroundColor)) return true;\n if (style.borderTopWidth > 0 && style.borderTopStyle !== 'none') return true;\n if (style.borderRightWidth > 0 && style.borderRightStyle !== 'none') return true;\n if (style.borderBottomWidth > 0 && style.borderBottomStyle !== 'none') return true;\n if (style.borderLeftWidth > 0 && style.borderLeftStyle !== 'none') return true;\n return false;\n}\n\n// ─── Inline text run types ─────────────────────────────────────────────\n\ninterface TextRun {\n text: string;\n style: ResolvedStyle;\n /** If this run came from an inline element with visible box styles */\n boxStyle?: ResolvedStyle;\n /** Marks the start of an inline box */\n boxOpen?: ResolvedStyle;\n /** Marks the end of an inline box */\n boxClose?: ResolvedStyle;\n}\n\ninterface Word {\n text: string;\n width: number;\n style: ResolvedStyle;\n isSpace: boolean;\n /** Tab character — width computed dynamically based on position */\n isTab?: boolean;\n /** Word came from soft-hyphen split — show '-' if this word ends a line */\n isSoftHyphenBreak?: boolean;\n boxStyle?: ResolvedStyle;\n /** Marks the start of an inline box (adds left padding/border) */\n boxOpen?: ResolvedStyle;\n /** Marks the end of an inline box (adds right padding/border) */\n boxClose?: ResolvedStyle;\n}\n\ninterface PositionedLine {\n words: Word[];\n totalWidth: number;\n lineHeight: number;\n /** True if this line ends at a forced break (\\n or <br>). Such a line is\n * treated as a \"last line\" for text-align — never justified. */\n endedByHardBreak?: boolean;\n}\n\n// ─── Inline layout ─────────────────────────────────────────────────────\n\n/**\n * Collect text runs from inline children, preserving style and tracking\n * inline elements with visible backgrounds. Emits open/close markers\n * for inline boxes so padding/border can be applied.\n */\nfunction collectTextRuns(node: StyledNode): TextRun[] {\n const runs: TextRun[] = [];\n\n function walk(n: StyledNode, boxStyle?: ResolvedStyle) {\n if (n.tagName === '#text' && n.textContent) {\n runs.push({ text: n.textContent, style: n.style, boxStyle });\n return;\n }\n const isInlineBlock = n.style.display === 'inline-block';\n // Inline-block always needs box treatment (padding/margin affect layout)\n const isBox = isInlineBlock || (isInline(n) && hasVisibleBoxStyles(n.style));\n const newBoxStyle = isBox ? n.style : boxStyle;\n const hasHorizSpacing = isBox && (n.style.paddingLeft > 0 || n.style.paddingRight > 0 ||\n n.style.borderLeftWidth > 0 || n.style.borderRightWidth > 0);\n\n if (isInlineBlock) {\n // Inline-block is fully atomic — the entire element (margins + padding + text)\n // wraps as one unit. We emit a single \"atomic\" TextRun with a special marker\n // so the tokenizer creates one non-splittable word with the full box width.\n const allText = n.element?.textContent || '';\n runs.push({\n text: allText,\n style: n.style,\n boxStyle: newBoxStyle,\n // Store the full box info for atomic inline-block handling\n boxOpen: n.style, // signals this is a boxed element\n boxClose: n.style,\n });\n return;\n }\n\n if (hasHorizSpacing) {\n runs.push({ text: '', style: n.style, boxStyle: newBoxStyle, boxOpen: n.style });\n }\n\n for (const child of n.children) {\n walk(child, isBox ? newBoxStyle : boxStyle);\n }\n\n if (hasHorizSpacing) {\n runs.push({ text: '', style: n.style, boxStyle: newBoxStyle, boxClose: n.style });\n }\n }\n\n for (const child of node.children) {\n walk(child);\n }\n return runs;\n}\n\n/**\n * Check if text needs Intl.Segmenter for word breaking (Thai, Khmer, Lao, Myanmar).\n * These scripts don't use spaces between words.\n */\nfunction needsSegmenter(text: string): boolean {\n for (let i = 0; i < text.length; i++) {\n const code = text.codePointAt(i)!;\n if (\n (code >= 0x0E00 && code <= 0x0E7F) || // Thai\n (code >= 0x0E80 && code <= 0x0EFF) || // Lao\n (code >= 0x1000 && code <= 0x109F) || // Myanmar\n (code >= 0x1780 && code <= 0x17FF) // Khmer\n ) return true;\n if (code > 0xFFFF) i++; // skip surrogate pair\n }\n return false;\n}\n\nlet _segmenter: Intl.Segmenter | undefined;\nfunction getSegmenter(): Intl.Segmenter | null {\n if (_segmenter) return _segmenter;\n if (typeof Intl !== 'undefined' && Intl.Segmenter) {\n _segmenter = new Intl.Segmenter(undefined, { granularity: 'word' });\n return _segmenter;\n }\n return null;\n}\n\n/**\n * Tokenize a single string into words based on whitespace mode.\n */\nfunction tokenizeString(ctx: CanvasRenderingContext2D, text: string, run: TextRun, allWords: Word[], cumState?: { cumText: string; cumWidth: number }): void {\n // Split on zero-width spaces and soft hyphens (break opportunities).\n // Pass cumulative state through so pieces are measured as one text run\n // (preserving kerning accuracy across break points).\n if (text.includes('\\u200B') || text.includes('\\u00AD')) {\n const parts = text.split(/(\\u200B|\\u00AD)/);\n // Share cumulative state across all sub-parts for accurate measurement\n const sharedState = cumState ?? { cumText: '', cumWidth: 0 };\n let nextIsSoftHyphen = false;\n for (const part of parts) {\n if (part === '\\u00AD') {\n nextIsSoftHyphen = true;\n continue;\n }\n if (part === '\\u200B' || part === '') {\n nextIsSoftHyphen = false;\n continue;\n }\n const prevLen = allWords.length;\n tokenizeString(ctx, part, run, allWords, sharedState);\n if (nextIsSoftHyphen && prevLen > 0) {\n allWords[prevLen - 1].isSoftHyphenBreak = true;\n }\n nextIsSoftHyphen = false;\n }\n if (nextIsSoftHyphen && allWords.length > 0) {\n allWords[allWords.length - 1].isSoftHyphenBreak = true;\n }\n return;\n }\n\n // `pre-line` preserves newlines (handled by the \\n pre-split in\n // tokenizeRuns) but collapses spaces and tabs — so it goes through the\n // non-preserving branch below, same as `normal`.\n const isPreserve = run.style.whiteSpace === 'pre' ||\n run.style.whiteSpace === 'pre-wrap' ||\n run.style.whiteSpace === 'break-spaces';\n\n if (isPreserve) {\n // Split on spaces and tabs, keeping delimiters\n const words = text.split(/( +|\\t)/);\n const tabStopInterval = cachedMeasureWidth(ctx, ' ') * 8; // CSS default: 8 spaces\n for (const w of words) {\n if (w === '') continue;\n if (w === '\\t') {\n // Tab width depends on current position — mark it for dynamic calculation\n allWords.push({\n text: '\\t',\n width: tabStopInterval, // placeholder — recalculated in flowWordsIntoLines\n style: run.style,\n isSpace: true,\n isTab: true,\n boxStyle: run.boxStyle,\n });\n continue;\n }\n const isSpace = /^ +$/.test(w);\n allWords.push({\n text: w,\n width: cachedMeasureWidth(ctx, w),\n style: run.style,\n isSpace,\n boxStyle: run.boxStyle,\n });\n }\n } else {\n // Split on whitespace but NOT on non-breaking spaces (\\u00A0)\n const words = text.split(/([ \\t\\n\\r\\f\\v]+)/);\n\n // Use cumulative measurement to avoid rounding error accumulation\n // within a single text run. When cumState is provided (from \\u200B/\\u00AD\n // split), continue from the previous cumulative position to preserve\n // kerning accuracy across break points.\n let cumText = cumState?.cumText ?? '';\n let cumWidth = cumState?.cumWidth ?? 0;\n\n for (const w of words) {\n if (w === '') continue;\n const isSpace = /^[ \\t\\n\\r\\f\\v]+$/.test(w);\n\n if (isSpace) {\n const prevCum = cumWidth;\n cumText += ' ';\n cumWidth = ctx.measureText(cumText).width;\n const spaceWidth = cumWidth - prevCum + (run.style.wordSpacing || 0);\n allWords.push({\n text: ' ',\n width: spaceWidth,\n style: run.style,\n isSpace: true,\n boxStyle: run.boxStyle,\n });\n continue;\n }\n\n // Use Intl.Segmenter for scripts without spaces (Thai, Khmer, etc.)\n if (needsSegmenter(w)) {\n const segmenter = getSegmenter();\n if (segmenter) {\n for (const seg of segmenter.segment(w)) {\n const s = seg.segment;\n const prevCum = cumWidth;\n cumText += s;\n cumWidth = ctx.measureText(cumText).width;\n allWords.push({\n text: s,\n width: cumWidth - prevCum,\n style: run.style,\n isSpace: false,\n boxStyle: run.boxStyle,\n });\n }\n continue;\n }\n }\n\n const prevCum = cumWidth;\n cumText += w;\n cumWidth = ctx.measureText(cumText).width;\n let width = cumWidth - prevCum;\n const directWidth = cachedMeasureWidth(ctx, w);\n if (_debug) {\n _debug({\n type: 'measure-word',\n message: `\"${w}\" delta=${width.toFixed(2)} direct=${directWidth.toFixed(2)} diff=${(width - directWidth).toFixed(2)} cumText=\"${cumText}\"`,\n data: { text: w, deltaWidth: width, directWidth, cumWidth, prevCum, font: run.style.fontFamily, fontSize: run.style.fontSize },\n });\n }\n allWords.push({\n text: w,\n width,\n style: run.style,\n isSpace: false,\n boxStyle: run.boxStyle,\n });\n }\n\n // Propagate cumulative state back to caller (for \\u200B/\\u00AD splits)\n if (cumState) {\n cumState.cumText = cumText;\n cumState.cumWidth = cumWidth;\n }\n }\n}\n\n/**\n * Tokenize text runs into words for line wrapping.\n */\nfunction tokenizeRuns(ctx: CanvasRenderingContext2D, runs: TextRun[]): Word[] {\n const allWords: Word[] = [];\n\n for (const run of runs) {\n // Handle inline-block margins (empty text, no boxOpen/boxClose)\n if (run.text === '' && !run.boxOpen && !run.boxClose) {\n const margin = run.style.display === 'inline-block'\n ? (run.style.marginLeft || run.style.marginRight || 0)\n : 0;\n if (margin > 0) {\n allWords.push({ text: '', width: margin, style: run.style, isSpace: false, boxStyle: run.boxStyle });\n }\n continue;\n }\n\n // Atomic inline-block: entire element (margin + padding + text) is one word\n // Must check before boxOpen/boxClose handlers since atomic has both set.\n if (run.boxOpen && run.boxClose && run.text) {\n applyFont(ctx, run.style);\n ctx.letterSpacing = run.style.letterSpacing > 0 ? `${run.style.letterSpacing}px` : '0px';\n const text = applyTextTransform(run.text, run.style.textTransform);\n const s = run.style;\n const textWidth = cachedMeasureWidth(ctx, text);\n const totalWidth = s.marginLeft + s.borderLeftWidth + s.paddingLeft +\n textWidth + s.paddingRight + s.borderRightWidth + s.marginRight;\n allWords.push({\n text,\n width: totalWidth,\n style: run.style,\n isSpace: false,\n boxStyle: run.boxStyle,\n boxOpen: run.boxOpen,\n boxClose: run.boxClose,\n });\n continue;\n }\n\n // Handle inline box open/close markers (padding)\n if (run.boxOpen) {\n const pad = run.boxOpen.paddingLeft + run.boxOpen.borderLeftWidth;\n if (pad > 0) {\n allWords.push({ text: '', width: pad, style: run.style, isSpace: false, boxStyle: run.boxStyle, boxOpen: run.boxOpen });\n }\n continue;\n }\n if (run.boxClose) {\n const pad = run.boxClose.paddingRight + run.boxClose.borderRightWidth;\n if (pad > 0) {\n allWords.push({ text: '', width: pad, style: run.style, isSpace: false, boxStyle: run.boxStyle, boxClose: run.boxClose });\n }\n continue;\n }\n\n applyFont(ctx, run.style);\n ctx.letterSpacing = run.style.letterSpacing > 0 ? `${run.style.letterSpacing}px` : '0px';\n const text = applyTextTransform(run.text, run.style.textTransform);\n\n // Handle explicit newlines (from <br> or pre-wrap) — always force line break\n if (text.includes('\\n')) {\n const parts = text.split('\\n');\n for (let i = 0; i < parts.length; i++) {\n if (i > 0) {\n allWords.push({ text: '\\n', width: 0, style: run.style, isSpace: false, boxStyle: run.boxStyle });\n }\n if (parts[i]) {\n tokenizeString(ctx, parts[i], run, allWords);\n }\n }\n } else {\n tokenizeString(ctx, text, run, allWords);\n }\n }\n\n return allWords;\n}\n\n/**\n * Check if a character is CJK (Chinese/Japanese/Korean) — these wrap at character level.\n */\nfunction isCJK(char: string): boolean {\n const code = char.codePointAt(0) || 0;\n return (\n (code >= 0x4E00 && code <= 0x9FFF) || // CJK Unified\n (code >= 0x3400 && code <= 0x4DBF) || // CJK Extension A\n (code >= 0x3000 && code <= 0x303F) || // CJK Symbols\n (code >= 0x3040 && code <= 0x309F) || // Hiragana\n (code >= 0x30A0 && code <= 0x30FF) || // Katakana\n (code >= 0xAC00 && code <= 0xD7AF) || // Hangul\n (code >= 0xFF00 && code <= 0xFFEF) || // Fullwidth\n (code >= 0x20000 && code <= 0x2A6DF) // CJK Extension B\n );\n}\n\n/**\n * Break a word into character-level pieces if it contains CJK or if\n * overflow-wrap: break-word is set and the word is too wide.\n */\nfunction breakWordIfNeeded(\n ctx: CanvasRenderingContext2D,\n word: Word,\n contentWidth: number,\n currentLineWidth: number,\n): Word[] {\n // Check if word has CJK characters — always break at character level\n const hasCJK = [...word.text].some(isCJK);\n\n // Check if word needs break-word splitting — when it won't fit on a fresh line\n const needsBreak = word.width > contentWidth &&\n (word.style.overflowWrap === 'break-word' || word.style.wordBreak === 'break-all');\n\n if (!hasCJK && !needsBreak) return [word];\n\n // Split into characters using cumulative measurement for accuracy.\n // Measuring each char individually ignores kerning — the sum of individual\n // widths diverges from the true string width over many characters.\n ctx.font = buildCanvasFont(word.style);\n const chars = [...word.text];\n const pieces: Word[] = [];\n\n let current = '';\n let currentWidth = 0;\n\n for (const char of chars) {\n // CJK chars always get their own word for wrapping\n if (isCJK(char)) {\n if (current) {\n pieces.push({ ...word, text: current, width: currentWidth });\n current = '';\n currentWidth = 0;\n }\n const charWidth = cachedMeasureWidth(ctx, char);\n pieces.push({ ...word, text: char, width: charWidth });\n continue;\n }\n\n // Use cumulative measurement: measure the growing string, not individual chars\n const candidateText = current + char;\n const candidateWidth = cachedMeasureWidth(ctx, candidateText);\n\n // For break-word: break when adding this char would exceed container\n if (needsBreak && candidateWidth > contentWidth && current) {\n pieces.push({ ...word, text: current, width: currentWidth });\n current = char;\n currentWidth = cachedMeasureWidth(ctx, char);\n continue;\n }\n\n current = candidateText;\n currentWidth = candidateWidth;\n }\n\n if (current) {\n pieces.push({ ...word, text: current, width: currentWidth });\n }\n\n return pieces;\n}\n\n/**\n * Flow words into lines that fit within contentWidth.\n * Handles: word wrapping, nowrap, break-word, CJK character wrapping.\n */\nfunction flowWordsIntoLines(\n ctx: CanvasRenderingContext2D,\n words: Word[],\n contentWidth: number,\n whiteSpace: string,\n useBulletProbe = false,\n textIndent = 0,\n): PositionedLine[] {\n const lines: PositionedLine[] = [];\n let currentLine: PositionedLine = { words: [], totalWidth: 0, lineHeight: 0 };\n const noWrap = whiteSpace === 'nowrap' || whiteSpace === 'pre';\n // text-indent reduces the first line's width budget; subsequent lines use full width.\n const effWidth = () => contentWidth - (lines.length === 0 ? textIndent : 0);\n\n const isPreWrap = whiteSpace === 'pre-wrap' || whiteSpace === 'pre' || whiteSpace === 'pre-line';\n // `pre`, `pre-wrap`, and `break-spaces` preserve author whitespace\n // (leading and trailing); the others collapse it.\n const preservesWhitespace =\n whiteSpace === 'pre' || whiteSpace === 'pre-wrap' || whiteSpace === 'break-spaces';\n\n function pushLine(isSoftWrap = false) {\n const hadWords = currentLine.words.length > 0;\n // Trim trailing spaces. `break-spaces` preserves them even at soft wraps;\n // `pre`/`pre-wrap` preserve them at hard breaks and end-of-content but not\n // at soft wraps (per CSS Text 3 §4.1.1).\n const preserveTrailing = whiteSpace === 'break-spaces'\n || (preservesWhitespace && !isSoftWrap);\n if (!preserveTrailing) {\n while (currentLine.words.length > 0 && currentLine.words[currentLine.words.length - 1].isSpace) {\n currentLine.totalWidth -= currentLine.words[currentLine.words.length - 1].width;\n currentLine.words.pop();\n }\n }\n // Soft hyphen: if this is a soft wrap and the last word has a soft-hyphen\n // break, append a visible '-' since the word is being broken here.\n if (isSoftWrap && currentLine.words.length > 0) {\n const lastWord = currentLine.words[currentLine.words.length - 1];\n if (lastWord.isSoftHyphenBreak) {\n applyFont(ctx, lastWord.style);\n const hyphenWidth = cachedMeasureWidth(ctx, '-');\n currentLine.words.push({\n text: '-',\n width: hyphenWidth,\n style: lastWord.style,\n isSpace: false,\n });\n currentLine.totalWidth += hyphenWidth;\n }\n }\n // In pre-wrap mode, space-only lines still need height (they are content)\n if (currentLine.words.length > 0 || (hadWords && isPreWrap)) {\n if (_debug) {\n const text = currentLine.words.map(w => w.text).join('');\n _debug({\n type: 'line-commit',\n message: `Line ${lines.length}: \"${text}\" width=${currentLine.totalWidth.toFixed(2)} / ${contentWidth}`,\n data: { lineIndex: lines.length, text, totalWidth: currentLine.totalWidth, contentWidth },\n });\n }\n lines.push(currentLine);\n }\n currentLine = { words: [], totalWidth: 0, lineHeight: 0 };\n }\n\n let afterHardBreak = true; // start of content is like after a hard break\n\n for (const word of words) {\n let wordLineHeight = getLineHeight(ctx, word.style, useBulletProbe);\n // Inline-block elements expand line height with their vertical padding+margin\n if (word.boxStyle && word.boxStyle.display === 'inline-block') {\n const bs = word.boxStyle;\n wordLineHeight = Math.max(wordLineHeight,\n wordLineHeight + bs.paddingTop + bs.paddingBottom + bs.marginTop + bs.marginBottom\n + bs.borderTopWidth + bs.borderBottomWidth);\n }\n\n if (word.text === '\\n') {\n if (currentLine.words.length === 0) {\n currentLine.lineHeight = wordLineHeight;\n currentLine.endedByHardBreak = true;\n lines.push(currentLine);\n currentLine = { words: [], totalWidth: 0, lineHeight: 0 };\n } else {\n currentLine.endedByHardBreak = true;\n pushLine();\n }\n afterHardBreak = true;\n continue;\n }\n\n // No wrapping mode — everything on one line\n if (noWrap) {\n currentLine.words.push(word);\n currentLine.totalWidth += word.width;\n currentLine.lineHeight = Math.max(currentLine.lineHeight, wordLineHeight);\n continue;\n }\n\n // Break long words / CJK characters if needed\n const pieces = (!word.isSpace && word.text.length > 1)\n ? breakWordIfNeeded(ctx, word, effWidth(), currentLine.totalWidth)\n : [word];\n\n for (const piece of pieces) {\n // Trailing punctuation (e.g. comma after </span>) should not wrap\n // independently — browsers keep it with the preceding word.\n const isTrailingPunct = !piece.isSpace && piece.text.length > 0 &&\n /^[,.\\;:!?\\)\\]\\}'\"»›]+$/.test(piece.text) &&\n currentLine.words.length > 0 &&\n !currentLine.words[currentLine.words.length - 1].isSpace;\n\n // Would this piece overflow?\n if (!piece.isSpace && !isTrailingPunct && currentLine.words.length > 0 &&\n currentLine.totalWidth + piece.width > effWidth()) {\n const overflow = currentLine.totalWidth + piece.width - effWidth();\n\n // For borderline cases (overflow < 1px), word-by-word delta\n // accumulation may introduce rounding errors. Re-measure the\n // full candidate line as a single string for accuracy.\n // Only works for single-font lines — mixed fonts can't be\n // measured as one string.\n let reallyOverflows = true;\n if (overflow < 1 && !hasMixedFonts([...currentLine.words, piece])) {\n applyFont(ctx, piece.style);\n const fullText = currentLine.words.map(w => w.text).join('') + piece.text;\n const fullWidth = cachedMeasureWidth(ctx, fullText);\n // Allow tiny sub-pixel overflow — canvas measureText and DOM\n // text layout can differ by fractions of a pixel.\n if (fullWidth <= effWidth() + 0.1) {\n reallyOverflows = false;\n }\n }\n\n // Hyphen break on current line: before wrapping the whole word,\n // try fitting a hyphen prefix on the current line. Browsers prefer\n // keeping content on the current line by splitting at hyphens.\n if (reallyOverflows && piece.text.includes('-')) {\n const parts = piece.text.split(/(?<=-)/);\n if (parts.length > 1) {\n applyFont(ctx, piece.style);\n let fitted = '';\n let fittedWidth = 0;\n let partIdx = 0;\n const available = effWidth() - currentLine.totalWidth;\n for (; partIdx < parts.length; partIdx++) {\n const candidate = fitted + parts[partIdx];\n const candidateWidth = cachedMeasureWidth(ctx, candidate);\n if (candidateWidth > available) break;\n fitted = candidate;\n fittedWidth = candidateWidth;\n }\n if (partIdx > 0 && partIdx < parts.length) {\n currentLine.words.push({ ...piece, text: fitted, width: fittedWidth });\n currentLine.totalWidth += fittedWidth;\n currentLine.lineHeight = Math.max(currentLine.lineHeight, wordLineHeight);\n pushLine(true);\n afterHardBreak = false;\n const remainder = parts.slice(partIdx).join('');\n const remainderWidth = cachedMeasureWidth(ctx, remainder);\n currentLine.words.push({ ...piece, text: remainder, width: remainderWidth });\n currentLine.totalWidth += remainderWidth;\n currentLine.lineHeight = Math.max(currentLine.lineHeight, wordLineHeight);\n continue;\n }\n }\n }\n\n if (reallyOverflows) {\n if (_debug) {\n const lineText = currentLine.words.map(w => w.text).join('');\n _debug({\n type: 'line-wrap',\n message: `\"${piece.text}\" overflow=${overflow.toFixed(2)} wrap=true lineWidth=${currentLine.totalWidth.toFixed(2)} pieceWidth=${piece.width.toFixed(2)} contentWidth=${contentWidth} line=\"${lineText}\"`,\n data: { text: piece.text, overflow, lineWidth: currentLine.totalWidth, pieceWidth: piece.width, contentWidth, lineText },\n });\n }\n pushLine(true);\n afterHardBreak = false;\n }\n }\n\n // Skip leading spaces at the start of a line. Preserving modes\n // (pre/pre-wrap/break-spaces) keep them after hard breaks; collapsing\n // modes (normal/nowrap/pre-line) drop them in all cases.\n if (piece.isSpace && currentLine.words.length === 0\n && (!afterHardBreak || !preservesWhitespace)) continue;\n\n // Tab: snap to next tab stop based on current position\n let pieceWidth = piece.width;\n if (piece.isTab) {\n const tabStop = piece.width; // tabStopInterval stored as width\n const currentPos = currentLine.totalWidth;\n const nextStop = Math.ceil((currentPos + 0.1) / tabStop) * tabStop;\n pieceWidth = nextStop - currentPos;\n piece.width = pieceWidth;\n }\n\n // Hyphen break on a fresh line when word still too wide.\n if (currentLine.words.length === 0 && pieceWidth > effWidth() &&\n !piece.isSpace && piece.text.includes('-')) {\n const subParts = piece.text.split(/(?<=-)/);\n if (subParts.length > 1) {\n applyFont(ctx, piece.style);\n // Inject sub-parts as individual pieces — they'll flow through\n // the normal overflow/wrap logic on subsequent iterations.\n const newPieces: Word[] = subParts.filter(p => p).map(p => ({\n ...piece,\n text: p,\n width: cachedMeasureWidth(ctx, p),\n }));\n // Replace current piece with the sub-parts by splicing into the pieces array\n // Since we're iterating `pieces`, we push remaining sub-parts after the first\n // onto the current line normally, letting the overflow check handle wrapping.\n let first = true;\n for (const sp of newPieces) {\n if (first) {\n first = false;\n // First sub-part: add to current line (it fits since it's smaller)\n currentLine.words.push(sp);\n currentLine.totalWidth += sp.width;\n currentLine.lineHeight = Math.max(currentLine.lineHeight, wordLineHeight);\n } else if (currentLine.totalWidth + sp.width > effWidth()) {\n // Overflow: wrap to next line\n pushLine(true);\n afterHardBreak = false;\n currentLine.words.push(sp);\n currentLine.totalWidth += sp.width;\n currentLine.lineHeight = Math.max(currentLine.lineHeight, wordLineHeight);\n } else {\n currentLine.words.push(sp);\n currentLine.totalWidth += sp.width;\n currentLine.lineHeight = Math.max(currentLine.lineHeight, wordLineHeight);\n }\n }\n continue;\n }\n }\n\n currentLine.words.push(piece);\n currentLine.totalWidth += pieceWidth;\n currentLine.lineHeight = Math.max(currentLine.lineHeight, wordLineHeight);\n if (!piece.isSpace) afterHardBreak = false;\n }\n }\n pushLine();\n return lines;\n}\n\n/**\n * Layout inline content: text wrapping + positioning using pure canvas measurement.\n * Returns layout nodes and the total height consumed.\n */\nfunction layoutInlineContent(\n ctx: CanvasRenderingContext2D,\n node: StyledNode,\n x: number,\n y: number,\n contentWidth: number,\n useBulletProbe = false,\n): { nodes: LayoutNode[]; height: number } {\n const results: LayoutNode[] = [];\n const runs = collectTextRuns(node);\n if (runs.length === 0) return { nodes: results, height: 0 };\n\n const words = tokenizeRuns(ctx, runs);\n const textIndent = node.style.textIndent || 0;\n const lines = flowWordsIntoLines(ctx, words, contentWidth, node.style.whiteSpace, useBulletProbe, textIndent);\n const isRTL = node.style.direction === 'rtl';\n const resolveDir = (a: string) => {\n if (a === 'start') return isRTL ? 'right' : 'left';\n if (a === 'end') return isRTL ? 'left' : 'right';\n return a;\n };\n let textAlign = resolveDir(node.style.textAlign);\n // text-align-last: 'auto' inherits from text-align except when text-align is\n // 'justify', then defaults to 'start' (CSS Text 3 §7.2).\n let textAlignLast = node.style.textAlignLast || 'auto';\n if (textAlignLast === 'auto') {\n textAlignLast = node.style.textAlign === 'justify' ? (isRTL ? 'right' : 'left') : textAlign;\n } else {\n textAlignLast = resolveDir(textAlignLast);\n }\n\n let curY = y;\n\n for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {\n const line = lines[lineIdx];\n if (line.words.length === 0) {\n curY += line.lineHeight;\n continue;\n }\n\n const lineHeight = line.lineHeight;\n const isLastLine = lineIdx === lines.length - 1;\n const isFirstLine = lineIdx === 0;\n\n // Per-line alignment: lines ending at a forced break or the last line\n // use text-align-last; all others use text-align (CSS Text 3 §7.1, §7.2).\n const useLast = isLastLine || line.endedByHardBreak;\n const align = useLast ? textAlignLast : textAlign;\n\n // text-indent narrows the first line's available width.\n const indent = isFirstLine ? textIndent : 0;\n const lineMaxWidth = contentWidth - indent;\n\n // Justify: expand spaces to fill the line.\n let justifyExtraPerSpace = 0;\n if (align === 'justify' && line.totalWidth < lineMaxWidth) {\n const spaceCount = line.words.filter(w => w.isSpace).length;\n if (spaceCount > 0) {\n justifyExtraPerSpace = (lineMaxWidth - line.totalWidth) / spaceCount;\n }\n }\n\n // text-align (with first-line indent baked into curX)\n let curX = x + indent;\n if (align === 'center') {\n curX = x + indent + (lineMaxWidth - line.totalWidth) / 2;\n } else if (align === 'right' || (align !== 'justify' && isRTL)) {\n curX = x + indent + lineMaxWidth - line.totalWidth;\n } else if (isRTL) {\n curX = x + indent + lineMaxWidth - line.totalWidth;\n }\n\n // Inline background boxes and text are emitted after baseline computation\n // (below) so that emitInlineBox can use line-level metrics for alignment.\n\n // Compute a single shared baseline for the entire line.\n // Exclude sub/sup words — they sit above/below the baseline and\n // shouldn't influence where the baseline is positioned.\n let maxAscent = 0;\n let maxDescent = 0;\n for (const word of line.words) {\n if (word.text === '') continue;\n const va = word.style.verticalAlign;\n if (va === 'super' || va === 'sub') continue; // skip sub/sup for baseline calc\n const { ascent: a, descent: d } = getFontMetrics(ctx, word.style);\n if (a > maxAscent) maxAscent = a;\n if (d > maxDescent) maxDescent = d;\n }\n // If only sub/sup words on the line, use the first word's metrics\n if (maxAscent === 0) {\n for (const word of line.words) {\n if (word.text === '') continue;\n const { ascent, descent } = getFontMetrics(ctx, word.style);\n maxAscent = ascent;\n maxDescent = descent;\n break;\n }\n }\n // Center the text block (ascent + descent) within the lineHeight\n const textBlockHeight = maxAscent + maxDescent;\n let lineBaselineY = curY + (lineHeight - textBlockHeight) / 2 + maxAscent;\n\n // Compute parent font size for sub/sup positioning (used in expansion + text emit)\n const lineNormalWords = line.words.filter(w =>\n w.text !== '' && w.style.verticalAlign !== 'super' && w.style.verticalAlign !== 'sub');\n const parentFontSize = lineNormalWords.length > 0\n ? Math.max(...lineNormalWords.map(w => w.style.fontSize)) : 0;\n\n // Expand line height if sub/sup extends beyond the line box.\n // Browsers grow the line box to fit all content, but keep\n // the normal text baseline position unchanged.\n let effectiveLineHeight = lineHeight;\n {\n\n let minTop = curY;\n let maxBottom = curY + lineHeight;\n\n for (const word of line.words) {\n if (word.text === '') continue;\n const va = word.style.verticalAlign;\n if (va !== 'super' && va !== 'sub') continue;\n if (parentFontSize === 0) break;\n\n const { ascent: wAscent, descent: wDescent } = getFontMetrics(ctx, word.style);\n\n let shiftedBaseline = lineBaselineY;\n if (va === 'super') {\n shiftedBaseline -= parentFontSize * 0.4;\n } else {\n shiftedBaseline += parentFontSize * 0.26;\n }\n\n const wordTop = shiftedBaseline - wAscent;\n const wordBottom = shiftedBaseline + wDescent;\n if (wordTop < minTop) minTop = wordTop;\n if (wordBottom > maxBottom) maxBottom = wordBottom;\n }\n\n effectiveLineHeight = maxBottom - minTop;\n }\n\n // Emit inline background box using line-level baseline for vertical alignment.\n // Uses the line's ascent/descent (not the box's own font) so box aligns with text.\n const emitInlineBox = (style: ResolvedStyle, bx: number, bw: number) => {\n // Use the box's OWN font for height (not the line's largest font),\n // but align vertically to the line's baseline.\n const { ascent: boxAscent, descent: boxDescent } = getFontMetrics(ctx, style);\n const padTop = style.paddingTop + style.borderTopWidth;\n const padBottom = style.paddingBottom + style.borderBottomWidth;\n const boxHeight = boxAscent + boxDescent + padTop + padBottom;\n let boxY: number;\n if (style.display === 'inline-block') {\n boxY = curY + style.marginTop;\n } else {\n boxY = lineBaselineY - boxAscent - padTop;\n }\n results.push({\n type: 'box', style, x: bx, y: boxY, width: bw, height: boxHeight,\n tagName: 'span', children: [],\n });\n };\n\n // LTR: emit inline background boxes (Pass 1) before text.\n if (!isRTL) {\n let scanX = curX;\n let boxStartX = scanX;\n let currentBoxStyle: ResolvedStyle | undefined;\n let boxHasText = false;\n\n for (const word of line.words) {\n if (word.boxOpen && word.boxClose && word.text) {\n if (currentBoxStyle) {\n if (boxHasText) emitInlineBox(currentBoxStyle, boxStartX, scanX - boxStartX);\n currentBoxStyle = undefined;\n boxHasText = false;\n }\n const s = word.style;\n const textWidth = word.width - s.marginLeft - s.borderLeftWidth - s.paddingLeft\n - s.paddingRight - s.borderRightWidth - s.marginRight;\n const boxX = scanX + s.marginLeft;\n const boxW = s.borderLeftWidth + s.paddingLeft + textWidth + s.paddingRight + s.borderRightWidth;\n emitInlineBox(s, boxX, boxW);\n boxHasText = false;\n scanX += word.width;\n continue;\n }\n\n if (word.boxStyle !== currentBoxStyle) {\n if (currentBoxStyle && boxHasText) {\n emitInlineBox(currentBoxStyle, boxStartX, scanX - boxStartX);\n }\n currentBoxStyle = word.boxStyle;\n boxStartX = scanX;\n boxHasText = false;\n }\n if (word.text && !word.isSpace) boxHasText = true;\n scanX += word.width + (word.isSpace ? justifyExtraPerSpace : 0);\n }\n if (currentBoxStyle && boxHasText) {\n emitInlineBox(currentBoxStyle, boxStartX, scanX - boxStartX);\n }\n }\n\n // Emit text nodes.\n const textWords = line.words.filter(w => w.text !== '');\n const allSameStyle = textWords.length > 0 && textWords.every(w =>\n sameTextStyle(w.style, textWords[0].style)\n );\n\n if (isRTL) {\n // RTL: build groups, compute positions, emit boxes then text.\n // Groups join consecutive same-style words for proper glyph shaping.\n // Padding markers between groups create spacing.\n interface StyledGroup {\n text: string; style: ResolvedStyle; width: number;\n boxStyle?: ResolvedStyle; x: number;\n padBefore: number; // padding before this group (from boxOpen/boxClose markers)\n }\n const groups: StyledGroup[] = [];\n let currentGroup: StyledGroup | null = null;\n let pendingPad = 0;\n\n for (const word of line.words) {\n if (word.text === '') {\n // Padding marker — accumulate for the next group boundary\n if (currentGroup) { groups.push(currentGroup); currentGroup = null; }\n pendingPad += word.width;\n continue;\n }\n if (currentGroup && sameTextStyle(currentGroup.style, word.style)) {\n currentGroup.text += word.text;\n currentGroup.width += word.width;\n } else {\n if (currentGroup) groups.push(currentGroup);\n currentGroup = { text: word.text, style: word.style, width: word.width, boxStyle: word.boxStyle, x: 0, padBefore: pendingPad };\n pendingPad = 0;\n }\n }\n if (currentGroup) groups.push(currentGroup);\n\n // Compute positions right-to-left: group-level measureText for accuracy,\n // with padding markers creating spacing between groups.\n let rtlX = curX + line.totalWidth;\n for (const group of groups) {\n rtlX -= group.padBefore; // spacing from padding markers\n applyFont(ctx, group.style);\n const measuredWidth = cachedMeasureWidth(ctx, group.text);\n rtlX -= measuredWidth;\n group.x = rtlX;\n group.width = measuredWidth;\n }\n\n // Emit inline boxes first (behind text).\n // Include padding/border from boxStyle in box dimensions.\n for (const group of groups) {\n if (group.boxStyle && hasVisibleBoxStyles(group.boxStyle)) {\n const bs = group.boxStyle;\n const padLeft = bs.paddingLeft + bs.borderLeftWidth;\n const padRight = bs.paddingRight + bs.borderRightWidth;\n emitInlineBox(bs, group.x - padLeft, group.width + padLeft + padRight);\n }\n }\n\n // Emit text groups\n for (const group of groups) {\n results.push({\n type: 'text',\n text: group.text,\n x: group.x + group.width, // x = right edge for RTL textAlign\n y: lineBaselineY,\n width: group.width,\n style: { ...group.style, direction: 'rtl' },\n });\n }\n } else {\n // LTR with mixed BiDi scripts: emit the entire line as one fillText call\n // so the canvas engine handles BiDi reordering (Arabic/Hebrew in LTR).\n // Only do this when the line contains RTL characters — pure LTR lines\n // are more accurate with word-by-word positioning.\n const lineText = line.words.map(w => w.text).join('');\n const hasBidiMix = allSameStyle && /[\\u0590-\\u08FF\\uFB50-\\uFDFF\\uFE70-\\uFEFF]/.test(lineText) &&\n !line.words.some(w => w.boxOpen || w.boxClose ||\n w.style.verticalAlign === 'super' || w.style.verticalAlign === 'sub');\n if (hasBidiMix) {\n applyFont(ctx, textWords[0].style);\n const measuredWidth = cachedMeasureWidth(ctx, lineText);\n results.push({\n type: 'text',\n text: lineText,\n x: curX,\n y: lineBaselineY,\n width: measuredWidth,\n style: textWords[0].style,\n });\n } else {\n // Mixed styles: word by word\n for (const word of line.words) {\n if (word.text === '') {\n curX += word.width;\n continue;\n }\n\n // Atomic inline-block: position text inside the box (after margin + padding)\n if (word.boxOpen && word.boxClose) {\n const s = word.style;\n const textX = curX + s.marginLeft + s.borderLeftWidth + s.paddingLeft;\n results.push({\n type: 'text',\n text: word.text,\n x: textX,\n y: lineBaselineY,\n width: cachedMeasureWidth(ctx, word.text),\n style: word.style,\n });\n curX += word.width;\n continue;\n }\n\n // Adjust baseline for vertical-align\n let baselineY = lineBaselineY;\n const va = word.style.verticalAlign;\n if (va === 'super' || va === 'sub') {\n const pfs = parentFontSize || word.style.fontSize;\n if (va === 'super') {\n baselineY -= pfs * 0.4;\n } else {\n baselineY += pfs * 0.26;\n }\n }\n const effectiveWidth = word.width + (word.isSpace ? justifyExtraPerSpace : 0);\n\n results.push({\n type: 'text',\n text: word.text,\n x: curX,\n y: baselineY,\n width: effectiveWidth,\n style: word.style,\n });\n\n curX += effectiveWidth;\n }\n }\n }\n\n curY += effectiveLineHeight;\n }\n\n return { nodes: results, height: curY - y };\n}\n\n// ─── Block layout ──────────────────────────────────────────────────────\n\n/**\n * Collapse margins between two adjacent block elements.\n * Returns the effective spacing (max of the two margins, not sum).\n */\nfunction collapseMargins(prevMarginBottom: number, nextMarginTop: number): number {\n // Both positive: take the larger\n if (prevMarginBottom >= 0 && nextMarginTop >= 0) {\n return Math.max(prevMarginBottom, nextMarginTop);\n }\n // Both negative: take the more negative\n if (prevMarginBottom < 0 && nextMarginTop < 0) {\n return Math.min(prevMarginBottom, nextMarginTop);\n }\n // One positive, one negative: sum them\n return prevMarginBottom + nextMarginTop;\n}\n\n/**\n * Check if a node is a block-level display.\n */\nfunction isBlock(node: StyledNode): boolean {\n const d = node.style.display;\n return d === 'block' || d === 'list-item' || d === 'flex' || d === 'table' ||\n d === 'table-row' || d === 'table-cell' || d === 'table-row-group' ||\n d === 'table-header-group' || d === 'table-footer-group';\n}\n\n/**\n * Layout a block-level element and all its children.\n * Returns the LayoutBox and total height consumed (including margins).\n */\nfunction layoutBlock(\n ctx: CanvasRenderingContext2D,\n node: StyledNode,\n x: number,\n y: number,\n availableWidth: number,\n): { box: LayoutBox; height: number; marginBottomOut: number } {\n const style = node.style;\n\n // Box model\n const marginLeft = style.marginLeft;\n const marginRight = style.marginRight;\n const borderLeft = style.borderLeftWidth;\n const borderRight = style.borderRightWidth;\n const borderTop = style.borderTopWidth;\n const borderBottom = style.borderBottomWidth;\n const padLeft = style.paddingLeft;\n const padRight = style.paddingRight;\n const padTop = style.paddingTop;\n const padBottom = style.paddingBottom;\n\n const boxX = x + marginLeft;\n // If element has explicit width, use it; otherwise fill available width\n const boxWidth = (style.width > 0)\n ? style.width\n : availableWidth - marginLeft - marginRight;\n const contentX = boxX + borderLeft + padLeft;\n const contentWidth = Math.max(0, boxWidth - borderLeft - borderRight - padLeft - padRight);\n\n const boxY = y;\n const contentStartY = boxY + borderTop + padTop;\n\n const box: LayoutBox = {\n type: 'box',\n style,\n x: boxX,\n y: boxY,\n width: boxWidth,\n height: 0, // computed below\n tagName: node.tagName,\n children: [],\n listMarker: node.listMarker,\n };\n\n // Flex layout\n if (style.display === 'flex') {\n const result = layoutFlex(ctx, node, contentX, contentStartY, contentWidth);\n box.children = result.children;\n box.height = borderTop + padTop + result.height + padBottom + borderBottom;\n return { box, height: box.height, marginBottomOut: style.marginBottom };\n }\n\n // Table layout\n if (style.display === 'table') {\n const result = layoutTable(ctx, node, contentX, contentStartY, contentWidth);\n box.children = result.children;\n box.height = borderTop + padTop + result.height + padBottom + borderBottom;\n return { box, height: box.height, marginBottomOut: style.marginBottom };\n }\n\n // Empty block elements: zero content height (CSS spec — no line boxes created).\n // Only min-height or padding/border contribute to height.\n if (node.children.length === 0) {\n box.height = borderTop + padTop + padBottom + borderBottom;\n if (style.minHeight > 0) box.height = Math.max(box.height, style.minHeight);\n return { box, height: box.height, marginBottomOut: style.marginBottom };\n }\n\n // Layout children\n if (hasOnlyInlineChildren(node)) {\n // Inline formatting context\n const bulletProbe = node.tagName === 'li' && BULLET_MARKERS.has(style.listStyleType);\n const { nodes, height } = layoutInlineContent(ctx, node, contentX, contentStartY, contentWidth, bulletProbe);\n box.children = nodes;\n box.height = borderTop + padTop + height + padBottom + borderBottom;\n } else {\n // Block formatting context — stack children vertically\n let curY = contentStartY;\n let prevMarginBottom = 0;\n let hasContent = false; // tracks whether we've placed any content\n // Margin collapsing through parent: only for list elements.\n const allowCollapseThrough =\n node.tagName === 'li' || node.tagName === 'ul' || node.tagName === 'ol' ||\n node.tagName === 'dd' || node.tagName === 'dt';\n\n for (let ci = 0; ci < node.children.length; ci++) {\n const child = node.children[ci];\n\n if (child.tagName === '#text' || isInline(child)) {\n // Collect ALL consecutive inline/text children into one group\n const inlineChildren: StyledNode[] = [child];\n while (ci + 1 < node.children.length) {\n const next = node.children[ci + 1];\n if (next.tagName === '#text' || isInline(next)) {\n inlineChildren.push(next);\n ci++;\n } else {\n break;\n }\n }\n\n // Apply pending margin before inline content\n if (prevMarginBottom > 0) {\n curY += prevMarginBottom;\n prevMarginBottom = 0;\n }\n\n const inlineGroup: StyledNode = {\n element: null,\n tagName: 'div',\n style: { ...node.style, display: 'block', marginTop: 0, marginBottom: 0, paddingTop: 0, paddingBottom: 0, borderTopWidth: 0, borderBottomWidth: 0 },\n children: inlineChildren,\n textContent: null,\n };\n const bulletProbe2 = node.tagName === 'li' && BULLET_MARKERS.has(style.listStyleType);\n const { nodes, height } = layoutInlineContent(ctx, inlineGroup, contentX, curY, contentWidth, bulletProbe2);\n box.children.push(...nodes);\n curY += height;\n prevMarginBottom = 0;\n hasContent = true;\n continue;\n }\n\n // Block child — collapse margins\n const childMarginTop = child.style.marginTop;\n\n // First child margin-top collapses through parent if parent has no top border/padding\n // Only for elements that don't establish a new BFC (not root, not flex, not overflow)\n // First child margin-top collapses through parent if parent has no\n // top padding/border and doesn't establish a new BFC.\n if (!hasContent && padTop === 0 && borderTop === 0 && allowCollapseThrough) {\n // Skip — margin collapses with parent's margin\n } else {\n const collapsed = collapseMargins(prevMarginBottom, childMarginTop);\n curY += collapsed;\n }\n\n const { box: childBox, height: childTotalHeight, marginBottomOut } = layoutBlock(\n ctx, child, contentX, curY, contentWidth,\n );\n box.children.push(childBox);\n curY += childTotalHeight;\n prevMarginBottom = marginBottomOut;\n hasContent = true;\n }\n\n // Last child's margin-bottom collapses through parent if no bottom border/padding.\n // Root container does NOT collapse last-child margin (it defines the content height).\n let marginBottomOut = style.marginBottom;\n const canCollapseThrough = padBottom === 0 && borderBottom === 0 && allowCollapseThrough;\n if (canCollapseThrough && prevMarginBottom > 0) {\n // Last child's margin passes through to become parent's effective margin-bottom\n marginBottomOut = Math.max(style.marginBottom, prevMarginBottom);\n }\n\n // Include last child's margin-bottom in parent height when it can't collapse through\n let contentEnd = curY - contentStartY;\n if (!canCollapseThrough && prevMarginBottom > 0) {\n contentEnd += prevMarginBottom;\n }\n box.height = borderTop + padTop + contentEnd + padBottom + borderBottom;\n if (style.minHeight > 0) box.height = Math.max(box.height, style.minHeight);\n return { box, height: box.height, marginBottomOut };\n }\n\n if (style.minHeight > 0) box.height = Math.max(box.height, style.minHeight);\n return { box, height: box.height, marginBottomOut: style.marginBottom };\n}\n\n// ─── Table layout ──────────────────────────────────────────────────────\n\nfunction layoutTable(\n ctx: CanvasRenderingContext2D,\n node: StyledNode,\n contentX: number,\n contentY: number,\n contentWidth: number,\n): { children: LayoutNode[]; height: number } {\n const children: LayoutNode[] = [];\n\n // Collect rows from thead, tbody, tfoot, or direct tr children\n const rows: StyledNode[] = [];\n for (const child of node.children) {\n if (child.tagName === 'tr') {\n rows.push(child);\n } else if (['thead', 'tbody', 'tfoot'].includes(child.tagName)) {\n for (const grandchild of child.children) {\n if (grandchild.tagName === 'tr') rows.push(grandchild);\n }\n }\n }\n\n if (rows.length === 0) return { children, height: 0 };\n\n // Determine column count from first row\n const colCount = Math.max(...rows.map(r => r.children.filter(c => c.tagName === 'td' || c.tagName === 'th').length));\n if (colCount === 0) return { children, height: 0 };\n\n // Equal column widths (simple approach)\n const colWidth = contentWidth / colCount;\n\n let curY = contentY;\n\n for (const row of rows) {\n const cells = row.children.filter(c => c.tagName === 'td' || c.tagName === 'th');\n let maxCellHeight = 0;\n const cellBoxes: LayoutBox[] = [];\n\n for (let i = 0; i < cells.length; i++) {\n const cell = cells[i];\n const cellX = contentX + i * colWidth;\n\n const { box: cellBox, height: cellHeight } = layoutBlock(ctx, cell, cellX, curY, colWidth);\n cellBoxes.push(cellBox);\n maxCellHeight = Math.max(maxCellHeight, cellHeight);\n }\n\n // Normalize cell heights to the tallest cell in the row\n for (const cellBox of cellBoxes) {\n cellBox.height = maxCellHeight;\n children.push(cellBox);\n }\n\n curY += maxCellHeight;\n }\n\n return { children, height: curY - contentY };\n}\n\n// ─── Flex layout ───────────────────────────────────────────────────────\n\nfunction layoutFlex(\n ctx: CanvasRenderingContext2D,\n node: StyledNode,\n contentX: number,\n contentY: number,\n contentWidth: number,\n): { children: LayoutNode[]; height: number } {\n const style = node.style;\n const gap = style.gap;\n const children: LayoutNode[] = [];\n\n const flexChildren = node.children.filter(c => c.tagName !== '#text' || c.textContent?.trim());\n if (flexChildren.length === 0) return { children, height: 0 };\n\n if (style.flexDirection === 'row' || style.flexDirection === '') {\n // Row layout\n const totalGaps = gap * (flexChildren.length - 1);\n const totalGrow = flexChildren.reduce((s, c) => s + (c.style.flexGrow || 0), 0);\n const flexBasis = (contentWidth - totalGaps) / (totalGrow || flexChildren.length);\n\n let curX = contentX;\n let maxHeight = 0;\n\n for (const child of flexChildren) {\n if (child.tagName === '#text') continue;\n const grow = child.style.flexGrow || (totalGrow === 0 ? 1 : 0);\n const childWidth = flexBasis * grow;\n\n const { box, height } = layoutBlock(ctx, child, curX, contentY, childWidth);\n children.push(box);\n maxHeight = Math.max(maxHeight, height);\n curX += childWidth + gap;\n }\n\n return { children, height: maxHeight };\n }\n\n // Column layout (fallback)\n let curY = contentY;\n for (const child of flexChildren) {\n if (child.tagName === '#text') continue;\n const { box, height } = layoutBlock(ctx, child, contentX, curY, contentWidth);\n children.push(box);\n curY += height + gap;\n }\n return { children, height: curY - contentY };\n}\n\n// ─── List marker layout ────────────────────────────────────────────────\n\n/**\n * Add list marker to a layout box if applicable.\n */\nfunction addListMarker(\n ctx: CanvasRenderingContext2D,\n box: LayoutBox,\n node: StyledNode,\n): void {\n if (!node.listMarker) return;\n // `::marker { content: none }` suppresses the marker entirely —\n // canonical CSS behavior, matches the DOM reference.\n if (node.markerHidden) return;\n\n const style = node.style;\n // Marker style = li style with explicit `::marker` overrides applied on top.\n // `markerStyle` holds only keys explicitly set by `::marker` rules, so a\n // missing key falls back to the li style. A present key (incl. 0) wins.\n const ms = node.markerStyle;\n const markerStyleObj: ResolvedStyle = ms ? { ...style, ...ms } : style;\n\n ctx.font = buildCanvasFont(markerStyleObj);\n const lineHeight = getLineHeight(ctx, style);\n const baselineY = box.y + style.borderTopWidth + style.paddingTop +\n computeBaselineY(ctx, style, lineHeight);\n\n const markerWidth = cachedMeasureWidth(ctx, node.listMarker);\n const isRTL = style.direction === 'rtl';\n // Gap between marker box and content. Default = fontSize * 0.15 (intrinsic).\n // `::marker { padding-inline-end: <length> }` overrides — we honor the\n // direction-resolved physical padding (paddingRight in LTR, paddingLeft\n // in RTL) when explicitly set on the marker.\n const explicitGap = isRTL ? ms?.paddingLeft : ms?.paddingRight;\n const gap = explicitGap !== undefined\n ? explicitGap\n : markerStyleObj.fontSize * 0.15;\n\n let markerX: number;\n let markerDirection = 'ltr';\n if (isRTL) {\n // RTL: marker in the parent's right padding area (outside the li box).\n const boxRightEdge = box.x + box.width;\n // Numbered markers (\"1.\") need RTL direction to display as \".1\".\n // With textAlign='right', x is the right edge — so add markerWidth.\n // Bullet markers (•, ○, ■) stay LTR — they're symmetric.\n const isNumbered = /\\d/.test(node.listMarker);\n if (isNumbered) {\n markerDirection = 'rtl';\n markerX = boxRightEdge + gap + markerWidth;\n } else {\n markerX = boxRightEdge + gap;\n }\n } else {\n // LTR: marker in the parent's left padding area (outside the li box).\n // Right-aligned within the padding, with a gap before content.\n const contentStartX = box.x + style.borderLeftWidth + style.paddingLeft;\n markerX = contentStartX - markerWidth - gap;\n }\n\n box.children.unshift({\n type: 'text',\n text: node.listMarker,\n x: markerX,\n y: baselineY,\n width: markerWidth,\n style: { ...markerStyleObj, textDecorationLine: 'none', fontWeight: ms?.fontWeight ?? 400, fontStyle: ms?.fontStyle ?? 'normal', direction: markerDirection },\n });\n}\n\n// ─── Main entry ────────────────────────────────────────────────────────\n\n/**\n * Build the layout tree from the styled tree using pure canvas measurement.\n * No DOM measurements used — all positions computed from CSS values + canvas.measureText.\n */\nexport function buildLayoutTree(\n ctx: CanvasRenderingContext2D,\n styledTree: StyledNode,\n containerWidth: number,\n useDomMeasurements = true,\n debug?: (entry: import('./types.ts').DebugEntry) => void,\n): { root: LayoutBox; height: number } {\n _useDomMeasurements = useDomMeasurements;\n _debug = debug;\n\n // Clear caches — fonts may have loaded since last call\n _lineHeightCache.clear();\n _fontMetricsCache.clear();\n _fontStringCache.clear();\n _measureCache.clear();\n\n // The styledTree root is our container div — layout its children as a block flow\n const { box, height } = layoutBlock(ctx, styledTree, 0, 0, containerWidth);\n\n // Add list markers post-layout\n addListMarkersRecursive(ctx, box, styledTree);\n\n return { root: box, height };\n}\n\nfunction addListMarkersRecursive(\n ctx: CanvasRenderingContext2D,\n box: LayoutBox,\n node: StyledNode,\n): void {\n addListMarker(ctx, box, node);\n\n // Match children — box.children may have extra text/inline nodes,\n // so we correlate by walking both in parallel\n let boxChildIdx = 0;\n for (const styledChild of node.children) {\n if (styledChild.tagName === '#text' || isInline(styledChild)) {\n continue;\n }\n // Find the matching LayoutBox\n while (boxChildIdx < box.children.length) {\n const layoutChild = box.children[boxChildIdx];\n if (layoutChild.type === 'box' && layoutChild.tagName === styledChild.tagName) {\n addListMarkersRecursive(ctx, layoutChild, styledChild);\n boxChildIdx++;\n break;\n }\n boxChildIdx++;\n }\n }\n}\n","import type { LayoutNode, LayoutBox, LayoutText, ResolvedStyle } from './types.js';\nimport { buildCanvasFont, isTransparent, getFontMetrics } from './layout.js';\nimport { paintOrderHasStrokeFirst } from './css-resolver.js';\n\n/**\n * Parse a CSS text-shadow string into individual shadow values.\n * Format: \"2px 2px 4px rgba(0,0,0,0.3), ...\"\n */\nfunction parseTextShadows(shadow: string): Array<{\n offsetX: number;\n offsetY: number;\n blur: number;\n color: string;\n}> {\n if (!shadow || shadow === 'none') return [];\n\n const shadows: Array<{ offsetX: number; offsetY: number; blur: number; color: string }> = [];\n\n // Split by comma but not within parentheses\n const parts = shadow.split(/,(?![^(]*\\))/);\n\n for (const part of parts) {\n const trimmed = part.trim();\n // Extract color (rgb/rgba or named) and numbers\n const colorMatch = trimmed.match(/(rgb[a]?\\([^)]+\\)|#[0-9a-fA-F]+|\\b[a-z]+\\b)(?:\\s|$)/i);\n const numMatches = trimmed.match(/-?[\\d.]+px/g);\n\n if (numMatches && numMatches.length >= 2) {\n const nums = numMatches.map(n => parseFloat(n));\n shadows.push({\n offsetX: nums[0],\n offsetY: nums[1],\n blur: nums[2] || 0,\n color: colorMatch ? colorMatch[1] : 'rgba(0,0,0,1)',\n });\n }\n }\n\n return shadows;\n}\n\n/**\n * Check if a border is visible.\n */\nfunction hasBorder(style: ResolvedStyle, side: 'Top' | 'Right' | 'Bottom' | 'Left'): boolean {\n const width = style[`border${side}Width` as keyof ResolvedStyle] as number;\n const borderStyle = style[`border${side}Style` as keyof ResolvedStyle] as string;\n return width > 0 && borderStyle !== 'none';\n}\n\n/**\n * Draw a decoration line with the given style (solid, dotted, dashed, double, wavy).\n */\nfunction drawDecorationLine(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n width: number,\n lineWidth: number,\n decoStyle: string,\n color: string,\n): void {\n ctx.save();\n ctx.strokeStyle = color;\n ctx.lineWidth = lineWidth;\n\n if (decoStyle === 'double') {\n const gap = Math.max(lineWidth, 2);\n ctx.lineWidth = Math.max(0.5, lineWidth * 0.5);\n ctx.beginPath();\n ctx.moveTo(x, y - gap / 2);\n ctx.lineTo(x + width, y - gap / 2);\n ctx.moveTo(x, y + gap / 2);\n ctx.lineTo(x + width, y + gap / 2);\n ctx.stroke();\n } else if (decoStyle === 'wavy') {\n const amplitude = Math.max(1.5, lineWidth);\n const wavelength = amplitude * 4;\n ctx.beginPath();\n ctx.moveTo(x, y);\n for (let cx = x; cx < x + width; cx += wavelength) {\n ctx.quadraticCurveTo(cx + wavelength / 4, y - amplitude, cx + wavelength / 2, y);\n ctx.quadraticCurveTo(cx + wavelength * 3 / 4, y + amplitude, cx + wavelength, y);\n }\n ctx.stroke();\n } else {\n // solid, dotted, dashed\n if (decoStyle === 'dotted') ctx.setLineDash([lineWidth, lineWidth * 2]);\n else if (decoStyle === 'dashed') ctx.setLineDash([lineWidth * 3, lineWidth * 2]);\n ctx.beginPath();\n ctx.moveTo(x, y);\n ctx.lineTo(x + width, y);\n ctx.stroke();\n }\n\n ctx.setLineDash([]);\n ctx.restore();\n}\n\n/**\n * Parse a CSS linear-gradient into canvas CanvasGradient.\n */\nfunction parseLinearGradient(\n ctx: CanvasRenderingContext2D,\n bgImage: string,\n x: number,\n width: number,\n y: number,\n height: number,\n): CanvasGradient | null {\n // Extract content inside linear-gradient(...) handling nested parens\n const startIdx = bgImage.indexOf('linear-gradient(');\n if (startIdx === -1) return null;\n let depth = 0;\n let endIdx = -1;\n for (let i = startIdx + 16; i < bgImage.length; i++) {\n if (bgImage[i] === '(') depth++;\n else if (bgImage[i] === ')') {\n if (depth === 0) { endIdx = i; break; }\n depth--;\n }\n }\n if (endIdx === -1) return null;\n const innerContent = bgImage.slice(startIdx + 16, endIdx);\n\n // Split by commas not inside parentheses\n const parts: string[] = [];\n depth = 0;\n let start = 0;\n const inner = innerContent;\n for (let i = 0; i < inner.length; i++) {\n if (inner[i] === '(') depth++;\n else if (inner[i] === ')') depth--;\n else if (inner[i] === ',' && depth === 0) {\n parts.push(inner.slice(start, i).trim());\n start = i + 1;\n }\n }\n parts.push(inner.slice(start).trim());\n // Parse angle/direction\n let angle = 180; // default top to bottom\n let colorStartIdx = 0;\n const firstPart = parts[0];\n if (firstPart.endsWith('deg')) {\n angle = parseFloat(firstPart);\n colorStartIdx = 1;\n } else if (firstPart === 'to right') {\n angle = 90; colorStartIdx = 1;\n } else if (firstPart === 'to left') {\n angle = 270; colorStartIdx = 1;\n } else if (firstPart === 'to bottom') {\n angle = 180; colorStartIdx = 1;\n } else if (firstPart === 'to top') {\n angle = 0; colorStartIdx = 1;\n }\n\n const rad = (angle - 90) * Math.PI / 180;\n const cx = x + width / 2;\n const cy = y + height / 2;\n const len = Math.abs(width * Math.cos(rad)) + Math.abs(height * Math.sin(rad));\n const dx = Math.cos(rad) * len / 2;\n const dy = Math.sin(rad) * len / 2;\n\n const gradient = ctx.createLinearGradient(cx - dx, cy - dy, cx + dx, cy + dy);\n\n const colors = parts.slice(colorStartIdx);\n for (let i = 0; i < colors.length; i++) {\n const entry = colors[i].trim();\n // Match color followed by optional percentage: \"rgb(220, 38, 38) 0%\"\n // The percentage is always at the very end after the last space outside parens\n let color = entry;\n let stop = i / Math.max(1, colors.length - 1);\n const percentMatch = entry.match(/\\s+([\\d.]+%)\\s*$/);\n if (percentMatch) {\n stop = parseFloat(percentMatch[1]) / 100;\n color = entry.slice(0, entry.length - percentMatch[0].length).trim();\n }\n try {\n gradient.addColorStop(stop, color);\n } catch {\n // Invalid color, skip\n }\n }\n\n return gradient;\n}\n\n/**\n * Render a single text node to canvas.\n * @param gradientFill — pre-computed gradient for background-clip:text spanning full element\n */\nfunction renderText(ctx: CanvasRenderingContext2D, node: LayoutText, gradientFill?: CanvasGradient | null): void {\n const { style } = node;\n\n ctx.save();\n ctx.font = buildCanvasFont(style);\n ctx.textBaseline = 'alphabetic';\n ctx.fontKerning = style.fontKerning === 'none' ? 'none' : 'normal';\n if (style.letterSpacing > 0) {\n ctx.letterSpacing = `${style.letterSpacing}px`;\n }\n if (style.wordSpacing) {\n (ctx as any).wordSpacing = `${style.wordSpacing}px`;\n }\n if (style.direction === 'rtl') {\n ctx.direction = 'rtl';\n ctx.textAlign = 'right';\n }\n\n const isGradientText = style.webkitBackgroundClip === 'text' &&\n style.backgroundImage && style.backgroundImage !== 'none';\n const isStrokedText = style.webkitTextStrokeWidth > 0;\n const isFillTransparent = style.webkitTextFillColor === 'transparent' ||\n style.color === 'transparent';\n\n // Text shadow (draw before main text)\n const shadows = parseTextShadows(style.textShadow);\n if (shadows.length > 0) {\n for (const shadow of shadows) {\n ctx.save();\n ctx.shadowOffsetX = shadow.offsetX;\n ctx.shadowOffsetY = shadow.offsetY;\n ctx.shadowBlur = shadow.blur;\n ctx.shadowColor = shadow.color;\n ctx.fillStyle = style.color;\n ctx.fillText(node.text, node.x, node.y);\n ctx.restore();\n }\n }\n\n const drawFill = () => {\n if (isGradientText) {\n ctx.save();\n if (gradientFill) {\n ctx.fillStyle = gradientFill;\n } else {\n // Fallback: per-word gradient (shouldn't normally reach here)\n const { ascent, descent } = getFontMetrics(ctx, style);\n const gradient = parseLinearGradient(\n ctx, style.backgroundImage,\n node.x, node.width,\n node.y - ascent, ascent + descent,\n );\n ctx.fillStyle = gradient || style.color;\n }\n ctx.fillText(node.text, node.x, node.y);\n ctx.restore();\n } else if (!isFillTransparent || !isStrokedText) {\n // Normal text fill (skip if transparent + stroked, stroke handles it)\n ctx.fillStyle = style.webkitTextFillColor && style.webkitTextFillColor !== 'transparent'\n ? style.webkitTextFillColor : style.color;\n ctx.fillText(node.text, node.x, node.y);\n }\n };\n\n const drawStroke = () => {\n if (!isStrokedText) return;\n ctx.save();\n ctx.strokeStyle = style.webkitTextStrokeColor || style.color;\n ctx.lineWidth = style.webkitTextStrokeWidth;\n ctx.lineJoin = 'round';\n ctx.strokeText(node.text, node.x, node.y);\n ctx.restore();\n };\n\n if (paintOrderHasStrokeFirst(style.paintOrder)) {\n drawStroke();\n drawFill();\n } else {\n drawFill();\n drawStroke();\n }\n\n // Text decorations — use font metrics for accurate positioning\n const textWidth = node.width;\n const fontSize = style.fontSize;\n const decoColor = style.textDecorationColor || style.color;\n const decoStyle = style.textDecorationStyle || 'solid';\n const decoWidth = Math.max(1, fontSize / 15);\n // For RTL text, node.x is the right edge (textAlign='right').\n // Decoration lines need the left edge as start position.\n const decoX = style.direction === 'rtl' ? node.x - textWidth : node.x;\n\n if (style.textDecorationLine !== 'none') {\n const { ascent: decoAscent } = getFontMetrics(ctx, style);\n ctx.font = buildCanvasFont(style);\n const xHeight = ctx.measureText('x').actualBoundingBoxAscent;\n\n if (style.textDecorationLine.includes('underline')) {\n const yOffset = fontSize * 0.1;\n drawDecorationLine(ctx, decoX, node.y + yOffset, textWidth, decoWidth, decoStyle, decoColor);\n }\n\n if (style.textDecorationLine.includes('line-through')) {\n const yOffset = -(xHeight * 0.5);\n drawDecorationLine(ctx, decoX, node.y + yOffset, textWidth, decoWidth, decoStyle, decoColor);\n }\n\n if (style.textDecorationLine.includes('overline')) {\n drawDecorationLine(ctx, decoX, node.y - decoAscent, textWidth, decoWidth, decoStyle, decoColor);\n }\n }\n\n ctx.restore();\n}\n\n/**\n * Render a layout box and its children to canvas.\n */\nfunction renderBox(ctx: CanvasRenderingContext2D, box: LayoutBox): void {\n const { style } = box;\n\n // Background\n if (!isTransparent(style.backgroundColor)) {\n ctx.fillStyle = style.backgroundColor;\n ctx.fillRect(box.x, box.y, box.width, box.height);\n }\n\n // Borders\n const borders: [side: 'Top' | 'Right' | 'Bottom' | 'Left', x1: number, y1: number, x2: number, y2: number][] = [\n ['Top', box.x, box.y + style.borderTopWidth / 2, box.x + box.width, box.y + style.borderTopWidth / 2],\n ['Right', box.x + box.width - style.borderRightWidth / 2, box.y, box.x + box.width - style.borderRightWidth / 2, box.y + box.height],\n ['Bottom', box.x, box.y + box.height - style.borderBottomWidth / 2, box.x + box.width, box.y + box.height - style.borderBottomWidth / 2],\n ['Left', box.x + style.borderLeftWidth / 2, box.y, box.x + style.borderLeftWidth / 2, box.y + box.height],\n ];\n for (const [side, x1, y1, x2, y2] of borders) {\n if (!hasBorder(style, side)) continue;\n ctx.strokeStyle = style[`border${side}Color` as keyof ResolvedStyle] as string;\n ctx.lineWidth = style[`border${side}Width` as keyof ResolvedStyle] as number;\n ctx.beginPath();\n ctx.moveTo(x1, y1);\n ctx.lineTo(x2, y2);\n ctx.stroke();\n }\n\n // Pre-compute gradient for background-clip: text elements\n let gradientFill: CanvasGradient | null = null;\n if (style.webkitBackgroundClip === 'text' && style.backgroundImage && style.backgroundImage !== 'none') {\n gradientFill = parseLinearGradient(ctx, style.backgroundImage, box.x, box.width, box.y, box.height);\n }\n\n // Children\n for (const child of box.children) {\n renderNode(ctx, child, gradientFill);\n }\n}\n\n/**\n * Render any layout node.\n */\nexport function renderNode(ctx: CanvasRenderingContext2D, node: LayoutNode, gradientFill?: CanvasGradient | null): void {\n if (node.type === 'text') {\n renderText(ctx, node, gradientFill);\n } else {\n renderBox(ctx, node);\n }\n}\n","import type {\n RenderConfig, RenderResult,\n LayoutConfig, LayoutResult, DrawConfig,\n LayoutLine, LayoutNode, AnyCanvas, AnyContext,\n} from './types.js';\nimport { parseHTML } from './parse.js';\nimport { resolveStylesFromCSS } from './css-resolver.js';\nimport { buildLayoutTree } from './layout.js';\nimport { renderNode } from './render.js';\n\nexport type { RenderConfig, RenderResult, LayoutConfig, LayoutResult, DrawConfig, LayoutLine };\n\n// ─── Line extraction ─────────────────────────────────────────────────\n\nfunction extractLines(root: LayoutNode): LayoutLine[] {\n const wordPositions: { y: number; fontSize: number; text: string }[] = [];\n function walk(node: LayoutNode) {\n if (node.type === 'text' && node.text.trim()) {\n wordPositions.push({ y: node.y, fontSize: node.style.fontSize, text: node.text });\n }\n if (node.type === 'box') {\n for (const child of node.children) walk(child);\n }\n }\n walk(root);\n wordPositions.sort((a, b) => a.y - b.y);\n\n const lines: LayoutLine[] = [];\n let lineMaxFontSize = 0;\n for (const wp of wordPositions) {\n const lastLine = lines[lines.length - 1];\n const tolerance = Math.max(lineMaxFontSize, wp.fontSize) * 0.5;\n if (lastLine && Math.abs(wp.y - lastLine.y) < tolerance) {\n lastLine.text += wp.text;\n lineMaxFontSize = Math.max(lineMaxFontSize, wp.fontSize);\n } else {\n lines.push({ y: Math.round(wp.y), text: wp.text });\n lineMaxFontSize = wp.fontSize;\n }\n }\n return lines;\n}\n\n// ─── layout() ────────────────────────────────────────────────────────\n\n/**\n * Compute layout for an HTML string without rendering.\n * Returns a reusable LayoutResult that can be drawn onto multiple targets via drawLayout().\n */\nexport function layout(config: LayoutConfig): LayoutResult {\n const {\n html,\n width,\n height,\n accuracy = 'performance',\n debug,\n } = config;\n\n if (!width || width <= 0 || Number.isNaN(width)) {\n throw new TypeError(`layout: width must be a positive number, got ${width}`);\n }\n\n const useDomMeasurements = accuracy === 'balanced';\n\n const { fragment, css } = parseHTML(html);\n const { tree, cleanup } = resolveStylesFromCSS(fragment, css, width);\n\n const tmpCanvas = document.createElement('canvas');\n const measureCtx = tmpCanvas.getContext('2d')!;\n measureCtx.fontKerning = 'normal';\n\n const { root, height: contentHeight } = buildLayoutTree(measureCtx, tree, width, useDomMeasurements, debug);\n const finalHeight = height || contentHeight;\n const lines = extractLines(root);\n\n cleanup();\n\n return { layoutRoot: root, height: finalHeight, lines };\n}\n\n// ─── drawLayout() ────────────────────────────────────────────────────\n\n/**\n * Draw a pre-computed layout onto a canvas or context.\n * Use with layout() to render the same content onto multiple targets.\n */\nexport function drawLayout(config: DrawConfig): { canvas: AnyCanvas } {\n const {\n layout: layoutResult,\n width,\n pixelRatio = globalThis.devicePixelRatio ?? 1,\n } = config;\n\n if (config.ctx && config.canvas) {\n throw new TypeError('drawLayout: ctx and canvas are mutually exclusive — provide one or neither');\n }\n\n const finalHeight = layoutResult.height;\n let canvas: AnyCanvas;\n let renderCtx: AnyContext;\n\n if (config.ctx) {\n renderCtx = config.ctx;\n canvas = config.ctx.canvas;\n } else {\n canvas = config.canvas ?? document.createElement('canvas');\n canvas.width = Math.ceil(width * pixelRatio);\n canvas.height = Math.ceil(finalHeight * pixelRatio);\n if ('style' in canvas) {\n (canvas as HTMLCanvasElement).style.width = `${width}px`;\n (canvas as HTMLCanvasElement).style.height = `${finalHeight}px`;\n }\n renderCtx = canvas.getContext('2d')! as AnyContext;\n renderCtx.scale(pixelRatio, pixelRatio);\n }\n\n renderNode(renderCtx as CanvasRenderingContext2D, layoutResult.layoutRoot);\n\n return { canvas };\n}\n\n// ─── render() ────────────────────────────────────────────────────────\n\n/**\n * Render an HTML string onto a canvas using pure 2D canvas API.\n * Convenience function combining layout() + drawLayout().\n * Fonts must already be loaded before calling this function.\n */\nexport function render(config: RenderConfig): RenderResult {\n if (config.ctx && config.canvas) {\n throw new TypeError('render: ctx and canvas are mutually exclusive — provide one or neither');\n }\n\n const layoutResult = layout({\n html: config.html,\n width: config.width,\n height: config.height,\n accuracy: config.accuracy,\n debug: config.debug,\n });\n\n const { canvas } = drawLayout({\n layout: layoutResult,\n width: config.width,\n ctx: config.ctx,\n canvas: config.canvas,\n pixelRatio: config.pixelRatio,\n });\n\n return {\n canvas,\n height: layoutResult.height,\n layoutRoot: layoutResult.layoutRoot,\n lines: layoutResult.lines,\n };\n}\n\n"],"mappings":"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"}