render-tag 0.1.15 → 0.1.16

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', 'font-variant', 'font-variant-caps',\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 // Browsers default unstyled text to the UA serif font (Times). Match it so\n // HTML without an explicit font-family wraps/positions like the browser.\n fontFamily: 'serif',\n fontSize: 16,\n fontWeight: 400,\n fontStyle: 'normal',\n fontVariantCaps: '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 unicodeBidi: '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 lineClamp: 0,\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 bdo: { display: 'inline', unicodeBidi: 'bidi-override' },\n bdi: { display: 'inline', unicodeBidi: 'isolate' },\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 // Canvas only renders the `small-caps` variant; map anything containing it\n // (incl. the font-variant shorthand) to small-caps, else normal.\n case 'font-variant':\n case 'font-variant-caps':\n style.fontVariantCaps = /\\bsmall-caps\\b/.test(value) ? 'small-caps' : 'normal';\n 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 '-webkit-line-clamp':\n case 'line-clamp': {\n // Spec accepts `none` / `auto` / positive integer. We map both\n // `none` and `auto` to 0 (no clamp); a non-positive integer also\n // means no clamp. Otherwise store the integer.\n const v = value.trim().toLowerCase();\n if (v === 'none' || v === 'auto') {\n style.lineClamp = 0;\n } else {\n const n = parseInt(v, 10);\n style.lineClamp = Number.isFinite(n) && n > 0 ? n : 0;\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 case 'unicode-bidi': style.unicodeBidi = 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, LayoutLine } 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// Lines emitted during layout. Reset at the start of buildLayoutTree();\n// layoutInlineContent appends one entry per committed line.\nlet _lines: LayoutLine[] = [];\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 and ctx.letterSpacing must already be set by caller.\n // letterSpacing is part of the key because it changes measured width.\n const key = ctx.font + '\\0' + (ctx.letterSpacing || '') + '\\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 */\nexport function applyFont(ctx: CanvasRenderingContext2D, style: ResolvedStyle): void {\n ctx.font = buildCanvasFont(style);\n ctx.fontKerning = style.fontKerning === 'none' ? 'none' : 'normal';\n}\n\n/** Format a letter-spacing value (px) as a canvas `ctx.letterSpacing` string. */\nfunction formatLetterSpacing(value: number): string {\n // Negative letter-spacing is valid and narrows text — Chrome applies it per\n // character (trailing included). Clamping it to 0 measured text wider than\n // the browser renders it, causing earlier/extra line wraps. Guard against\n // non-finite values (undefined/NaN), which would produce an invalid\n // \"undefinedpx\"/\"NaNpx\" string that canvas silently ignores.\n return Number.isFinite(value) && value !== 0 ? `${value}px` : '0px';\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.fontVariantCaps}|${style.fontWeight}|${style.fontSize}|${style.fontFamily}`;\n const cached = _fontStringCache.get(key);\n if (cached) return cached;\n const parts: string[] = [];\n // CSS font shorthand order: style, variant, weight, size, family.\n if (style.fontStyle !== 'normal') parts.push(style.fontStyle);\n if (style.fontVariantCaps === 'small-caps') parts.push('small-caps');\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 // Capitalize the first letter of each word. A mid-word apostrophe is NOT a\n // word boundary (UAX#29), so \"o'clock\" → \"O'clock\", not \"O'Clock\".\n case 'capitalize': return text.replace(/(^|[\\s\\p{P}])(\\p{L})/gu, (m, p, c) =>\n p === \"'\" || p === '’' ? m : 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 * Baseline shift (canvas pixels, positive = downward) for a vertical-align\n * value, applied on top of the line baseline. Returns 0 for 'baseline' and for\n * the line-box-relative keywords 'top'/'bottom' — those need a second layout\n * pass (the box position depends on the final line box it helps size), so they\n * fall back to baseline rather than being approximated wrongly.\n *\n * - super/sub legacy fixed fractions of the parent font size\n * - text-top/-bottom align the box's ascent/descent edge with the line's\n * - middle box midpoint at parent baseline + half the x-height\n * - <length>/<%> raise (positive value) by the length / % of line-height\n */\nfunction verticalAlignShift(\n va: string,\n wAscent: number, wDescent: number,\n parentFontSize: number, maxAscent: number, maxDescent: number,\n lineHeight: number,\n): number {\n switch (va) {\n case 'super': return -parentFontSize * 0.4;\n case 'sub': return parentFontSize * 0.26;\n case 'text-top': return -(maxAscent - wAscent);\n case 'text-bottom': return maxDescent - wDescent;\n case 'middle': return -(parentFontSize * 0.25) - (wDescent - wAscent) / 2;\n default: {\n // baseline / top / bottom / '' all parseFloat to NaN → 0 (callers gate\n // on isShiftedVAlign, so those never actually reach here).\n const n = parseFloat(va);\n if (!Number.isFinite(n)) return 0;\n return va.endsWith('%') ? -(n / 100) * lineHeight : -n;\n }\n }\n}\n\n/** True when a vertical-align value moves content off the baseline. */\nfunction isShiftedVAlign(va: string): boolean {\n return va !== 'baseline' && va !== 'top' && va !== 'bottom' && va !== '';\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 /**\n * No soft-wrap opportunity before this word: it abuts the previous word with\n * no whitespace (e.g. adjacent inline spans `<span>a</span><span>b</span>`),\n * so the browser treats them as one unbreakable unit at that boundary.\n */\n noBreakBefore?: 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/** True for atomic inline-block words (boxOpen && boxClose && text together). */\nfunction isAtomicInlineBlock(w: Word): boolean {\n return !!(w.boxOpen && w.boxClose && w.text);\n}\n\n/**\n * Truncate a PositionedLine's trailing words and append \"…\" so the line\n * fits within maxWidth. Used by `-webkit-line-clamp` to mark the visible\n * cut-off on the Nth line.\n *\n * Trim strategy:\n * 1. Pick the style of the last NON-empty, NON-atomic-inline-block word\n * — so the ellipsis font matches the surrounding text, not the button\n * or pill it was sitting next to.\n * 2. Drop trailing isSpace words (genuine spaces only — box markers carry\n * padding/border that we must keep).\n * 3. Back-trim: pop trailing non-space words until ellipsis fits. If we\n * end up with a single text word that STILL doesn't fit, pop it too —\n * the ellipsis stands alone rather than overflowing the container.\n * Box-open markers earlier on the line stay; they preserve inline-box\n * padding/border that the emit loop needs.\n * 4. Inherit boxStyle from the trailing context so inline `<span>`\n * backgrounds/borders extend across the ellipsis.\n */\nfunction applyEllipsisToLine(\n ctx: CanvasRenderingContext2D,\n line: PositionedLine,\n maxWidth: number,\n): void {\n // 1. Find the last word whose style should drive the ellipsis.\n // Skip empty-text markers AND atomic inline-blocks (their style is\n // the inline-block element's, not the surrounding text).\n let styleIdx = line.words.length - 1;\n while (\n styleIdx >= 0 &&\n (line.words[styleIdx].text === '' || isAtomicInlineBlock(line.words[styleIdx]))\n ) styleIdx--;\n if (styleIdx < 0) return;\n const lastStyle = line.words[styleIdx].style;\n const boxStyle = line.words[styleIdx].boxStyle;\n applyFont(ctx, lastStyle);\n // ALWAYS assign (don't gate on truthy) — otherwise a previous segment's\n // non-zero letter-spacing leaks into the ellipsis measurement.\n ctx.letterSpacing = `${lastStyle.letterSpacing || 0}px` as any;\n const ellipsisWidth = cachedMeasureWidth(ctx, '…');\n\n // Helper: pop trailing isSpace words. Box markers (text === '' with\n // boxOpen/boxClose) are NOT popped — they carry inline-box padding the\n // emit loop relies on.\n const popTrailingSpaces = () => {\n while (\n line.words.length > 0 &&\n line.words[line.words.length - 1].isSpace\n ) {\n const r = line.words.pop()!;\n line.totalWidth -= r.width;\n }\n };\n\n // 2. Strip purely trailing whitespace.\n popTrailingSpaces();\n\n // 3. Back-trim non-space text words until the ellipsis fits.\n // Atomic inline-blocks are non-space too; they pop along with words.\n const isTrimmableText = (w: Word) =>\n !w.isSpace && w.text !== '' && !w.boxOpen && !w.boxClose;\n while (\n line.totalWidth + ellipsisWidth > maxWidth &&\n line.words.length > 0\n ) {\n const last = line.words[line.words.length - 1];\n if (!isTrimmableText(last) && !isAtomicInlineBlock(last)) break;\n line.totalWidth -= last.width;\n line.words.pop();\n popTrailingSpaces();\n }\n\n // 4. Append the ellipsis. Inherit boxStyle so inline-span backgrounds /\n // borders extend over the ellipsis.\n const ellipsisWord: Word = {\n text: '…',\n width: ellipsisWidth,\n style: lastStyle,\n isSpace: false,\n boxStyle,\n };\n line.words.push(ellipsisWord);\n line.totalWidth += ellipsisWidth;\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 // unicode-bidi: bidi-override (e.g. <bdo dir=\"rtl\">) forces visual order.\n // For an RTL override, reverse both the characters of each descendant run\n // and the order of the runs, so the subtree renders right-to-left.\n const ub = n.style.unicodeBidi;\n const overrideRtl = (ub === 'bidi-override' || ub === 'isolate-override') &&\n n.style.direction === 'rtl';\n const overrideStart = runs.length;\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 if (overrideRtl && runs.length > overrideStart) {\n const seg = runs.splice(overrideStart);\n for (const r of seg) {\n if (r.text) {\n r.text = [...r.text].reverse().join('');\n // The glyphs are now in visual (reversed) order, so render them\n // left-to-right; otherwise renderText would right-anchor x and the\n // LTR emission (which set x as the left edge) would misposition them.\n r.style = { ...r.style, direction: 'ltr' };\n }\n }\n seg.reverse();\n runs.push(...seg);\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 // Then add a break opportunity AFTER \"?\" inside an otherwise-unbreakable\n // token (the URL query delimiter): Chrome wraps \"\\u2026/q3?\" | \"lang=ar&\\u2026\"\n // even with overflow-wrap:normal. It does NOT break at \"/\", \"&\", \"=\", \".\"\n // or \":\" (verified against the browser), so only \"?\" is split here. The\n // \"?\" stays with the preceding fragment; a trailing \"?\" (no follower) is\n // left intact. Fragments measure cumulatively so kerning stays accurate.\n const words = text\n .split(/([ \\t\\n\\r\\f\\v]+)/)\n .flatMap((w) =>\n /^[ \\t\\n\\r\\f\\v]+$/.test(w) ? [w] : w.split(/(?<=\\?)(?=.)/),\n );\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 = formatLetterSpacing(run.style.letterSpacing);\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 = formatLetterSpacing(run.style.letterSpacing);\n const text = applyTextTransform(run.text, run.style.textTransform);\n\n // Mark the first word produced from `startLen` as having no soft-wrap\n // opportunity before it when it directly abuts real text from a previous\n // run (adjacent inline elements with no whitespace between them). The\n // preceding word must be actual text — not a space, newline, empty\n // box-padding marker, or box edge — so a whitespace/padding boundary still\n // allows a break.\n const markGlue = (startLen: number) => {\n const first = allWords[startLen];\n if (!first || first.isSpace || !first.text || first.text === '\\n') return;\n const prev = allWords[startLen - 1];\n if (\n !prev || prev.isSpace || !prev.text.trim() ||\n prev.boxOpen || prev.boxClose\n ) return;\n // CJK and segmenter-driven scripts (Thai/Khmer/…) have break\n // opportunities between characters regardless of element boundaries, so\n // an element edge between them is NOT a no-break point. Only glue when\n // both sides are ordinary (Latin-like) text with no intrinsic break.\n const firstChar = [...first.text][0];\n const prevChar = [...prev.text][prev.text.length - 1];\n if (\n isCJK(firstChar) || isCJK(prevChar) ||\n needsSegmenter(first.text) || needsSegmenter(prev.text)\n ) return;\n first.noBreakBefore = true;\n };\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 const startLen = allWords.length;\n tokenizeString(ctx, parts[i], run, allWords);\n markGlue(startLen);\n }\n }\n } else {\n const startLen = allWords.length;\n tokenizeString(ctx, text, run, allWords);\n markGlue(startLen);\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\nlet _graphemeSegmenter: Intl.Segmenter | undefined;\nfunction getGraphemeSegmenter(): Intl.Segmenter | null {\n if (_graphemeSegmenter) return _graphemeSegmenter;\n if (typeof Intl !== 'undefined' && Intl.Segmenter) {\n _graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });\n return _graphemeSegmenter;\n }\n return null;\n}\n\nconst EMOJI_PICTOGRAPHIC = /\\p{Extended_Pictographic}/u;\n/**\n * Is this grapheme cluster an emoji that creates a line-break opportunity?\n * Restricted to emoji-presentation clusters (emoji planes, regional-indicator\n * flags, and ZWJ/VS16 sequences) so plain text symbols like ©/®/™ — which are\n * Extended_Pictographic but render as text and do NOT break — are excluded.\n */\nfunction isEmojiCluster(s: string): boolean {\n for (const ch of s) {\n const cp = ch.codePointAt(0)!;\n if (cp >= 0x1f000) return true; // emoji planes (incl. regional indicators)\n }\n if (s.includes('\\u200D') || s.includes('\\uFE0F')) {\n return EMOJI_PICTOGRAPHIC.test(s); // ZWJ sequence or VS16 emoji presentation\n }\n return false;\n}\n\n/**\n * Break a word into character-level pieces if it contains CJK/emoji 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 // Emoji form their own break opportunities (a run of emoji wraps between\n // clusters). Only meaningful when a grapheme segmenter is available so ZWJ\n // sequences / skin-tone / flag pairs stay intact.\n const hasEmoji = EMOJI_PICTOGRAPHIC.test(word.text) && !!getGraphemeSegmenter();\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 && !hasEmoji && !needsBreak) return [word];\n\n // overflow-wrap:break-word is a LAST RESORT — the browser first uses any\n // normal break opportunity inside the word (a hyphen) before breaking\n // mid-character. So split a hyphenated word at its hyphens first and only\n // char-break the segments that are themselves still too wide. (word-break:\n // break-all genuinely allows breaking between any two characters, so it\n // skips this and falls through to the char loop below.)\n if (needsBreak && word.style.wordBreak !== 'break-all' &&\n word.style.overflowWrap === 'break-word') {\n const segTexts = word.text.split(/(?<=-)(?!\\d)|(?<=[^\\d]-)/).filter((s) => s.length);\n if (segTexts.length > 1) {\n ctx.font = buildCanvasFont(word.style);\n ctx.letterSpacing = formatLetterSpacing(word.style.letterSpacing);\n const out: Word[] = [];\n for (const segText of segTexts) {\n const segWidth = cachedMeasureWidth(ctx, segText);\n if (segWidth <= contentWidth) {\n out.push({ ...word, text: segText, width: segWidth });\n } else {\n // Segment still overflows — char-break just this segment.\n out.push(...breakWordIfNeeded(ctx, { ...word, text: segText, width: segWidth }, contentWidth, 0));\n }\n }\n return out;\n }\n }\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 // Re-assert letter-spacing: tokenizeRuns may have left ctx at a later run's\n // value, but break points must use THIS word's letter-spacing.\n ctx.letterSpacing = formatLetterSpacing(word.style.letterSpacing);\n // When the word contains emoji, iterate by GRAPHEME cluster so multi-codepoint\n // emoji (ZWJ families, skin tones, flags) are never split mid-cluster.\n const seg = hasEmoji ? getGraphemeSegmenter() : null;\n const chars = seg\n ? [...seg.segment(word.text)].map((s) => s.segment)\n : [...word.text];\n const pieces: Word[] = [];\n\n let current = '';\n let currentWidth = 0;\n\n for (const char of chars) {\n // Emoji clusters each get their own word — a break opportunity between\n // adjacent emoji, matching the browser line breaker.\n if (hasEmoji && isEmojiCluster(char)) {\n if (current) {\n pieces.push({ ...word, text: current, width: currentWidth });\n current = '';\n currentWidth = 0;\n }\n pieces.push({ ...word, text: char, width: cachedMeasureWidth(ctx, char) });\n continue;\n }\n\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/** Punctuation that cannot start a line — stays with the preceding word. */\nconst TRAILING_PUNCT = /^[,.\\;:!?\\)\\]\\}'\"»›]+$/;\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 (let wordIndex = 0; wordIndex < words.length; wordIndex++) {\n const word = words[wordIndex];\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 // Breaking a word that is split across a run boundary. A single word split\n // across adjacent inline runs (e.g. <span>E</span>xperience, a font-size\n // change mid-word, or <span>wel</span>l-being) is several Words glued by\n // `noBreakBefore`. Per-word break logic can't see the whole word, so its\n // internal break opportunities — hyphens, and break-word char points — are\n // lost and the unit overflows the edge. Detect the maximal glued chain\n // starting here and break it across the run boundaries like the browser.\n if (!word.isSpace && word.text && !word.noBreakBefore && !word.boxOpen && !word.boxClose) {\n let end = wordIndex;\n while (end + 1 < words.length) {\n const nx = words[end + 1];\n if (!nx.text || nx.isSpace || nx.boxOpen || nx.boxClose || !nx.noBreakBefore) break;\n end++;\n }\n if (end > wordIndex) {\n const breakWord = word.style.overflowWrap === 'break-word' || word.style.wordBreak === 'break-all';\n let combined = 0;\n for (let j = wordIndex; j <= end; j++) combined += words[j].width;\n // Flatten the chain into styled characters (per-run style retained).\n const cells: { ch: string; style: ResolvedStyle }[] = [];\n for (let j = wordIndex; j <= end; j++)\n for (const ch of [...words[j].text]) cells.push({ ch, style: words[j].style });\n const combinedText = cells.map((c) => c.ch).join('');\n // Hyphen break opportunities (same rule as the single-word hyphen path).\n const segTexts = combinedText.split(/(?<=-)(?!\\d)|(?<=[^\\d]-)/).filter((s) => s.length);\n const hyphenMode = segTexts.length > 1;\n const fitsLine = currentLine.totalWidth + combined <= effWidth();\n // A hyphen is an ordinary break opportunity — intervene whenever the\n // unit doesn't fit the remaining space. break-word is last-resort —\n // only when the unit can't fit a full line at all (otherwise the normal\n // flow + glued-tail fit check correctly wraps it whole to a fresh line).\n const enter = !fitsLine && (hyphenMode || (breakWord && combined > effWidth()));\n if (enter) {\n // Atomic units for breaking: hyphen segments, else the whole chain.\n const segs: { ch: string; style: ResolvedStyle }[][] = [];\n let ci = 0;\n for (const st of segTexts) {\n const len = [...st].length;\n segs.push(cells.slice(ci, ci + len));\n ci += len;\n }\n // Place a segment's cells onto the current line, splitting same-style\n // runs into pieces. When `chars` is set, wrap at the line edge between\n // characters (break-word); otherwise place atomically (it may overflow\n // its own line, e.g. a hyphen prefix wider than the container).\n const placeCells = (cs: { ch: string; style: ResolvedStyle }[], chars: boolean) => {\n let i = 0;\n while (i < cs.length) {\n const st = cs[i].style;\n applyFont(ctx, st);\n ctx.letterSpacing = formatLetterSpacing(st.letterSpacing);\n const lh = getLineHeight(ctx, st, useBulletProbe);\n const run: { ch: string; style: ResolvedStyle }[] = [];\n let cur = '';\n let curW = 0;\n while (i < cs.length && cs[i].style === st) {\n const ch = cs[i].ch;\n const candW = cachedMeasureWidth(ctx, cur + ch);\n if (chars && currentLine.totalWidth + candW > effWidth() &&\n (currentLine.words.length > 0 || cur)) {\n if (cur) {\n currentLine.words.push({ text: cur, width: curW, style: st, isSpace: false });\n currentLine.totalWidth += curW;\n currentLine.lineHeight = Math.max(currentLine.lineHeight, lh);\n }\n pushLine(true);\n afterHardBreak = false;\n cur = ch;\n curW = cachedMeasureWidth(ctx, ch);\n } else {\n cur += ch;\n curW = candW;\n }\n i++;\n }\n if (cur) {\n currentLine.words.push({ text: cur, width: curW, style: st, isSpace: false });\n currentLine.totalWidth += curW;\n currentLine.lineHeight = Math.max(currentLine.lineHeight, lh);\n afterHardBreak = false;\n }\n }\n };\n const measureSeg = (cs: { ch: string; style: ResolvedStyle }[]) => {\n let w = 0;\n let i = 0;\n while (i < cs.length) {\n const st = cs[i].style;\n let txt = '';\n while (i < cs.length && cs[i].style === st) { txt += cs[i].ch; i++; }\n applyFont(ctx, st);\n ctx.letterSpacing = formatLetterSpacing(st.letterSpacing);\n w += cachedMeasureWidth(ctx, txt);\n }\n return w;\n };\n // Pure break-word (no hyphen) is last-resort: move the whole word to a\n // fresh line first (using the preceding space), then break it there.\n if (!hyphenMode && currentLine.words.length > 0) {\n pushLine(true);\n afterHardBreak = false;\n }\n for (const seg of segs) {\n const segW = measureSeg(seg);\n if (currentLine.words.length > 0 && currentLine.totalWidth + segW > effWidth()) {\n pushLine(true);\n afterHardBreak = false;\n }\n // Char-break a segment only when break-word and it can't fit a line.\n placeCells(seg, breakWord && segW > effWidth());\n }\n wordIndex = end;\n continue;\n }\n }\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 // Glued tail: content immediately after this word that cannot start a new\n // line — trailing punctuation (\",.)]}…\"), an inline span's right\n // padding/border (empty boxClose markers), and a word continuation that\n // abuts this word across a run boundary with no soft-wrap opportunity\n // (noBreakBefore — e.g. one word split across two inline spans with\n // different font sizes). The browser includes all of it when deciding\n // whether this word fits, so the unit wraps together: if \"Music Experie\"\n // doesn't leave room for the glued \"nce\", the whole word wraps as one.\n // Stops at whitespace or the next breakable word.\n let gluedTailWidth = 0;\n for (let j = wordIndex + 1; j < words.length; j++) {\n const nw = words[j];\n if (nw.isSpace || nw.text === '\\n') break;\n const isPunct = !!nw.text && TRAILING_PUNCT.test(nw.text);\n const isCloseMarker = !nw.text && !!nw.boxClose;\n const isGluedCont = !!nw.text && !!nw.noBreakBefore;\n if (isPunct || isCloseMarker || isGluedCont) { gluedTailWidth += nw.width; continue; }\n break;\n }\n\n for (const piece of pieces) {\n const isLastPiece = piece === pieces[pieces.length - 1];\n // Only the last piece of the word carries the glued tail.\n const tail = isLastPiece ? gluedTailWidth : 0;\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 TRAILING_PUNCT.test(piece.text) &&\n currentLine.words.length > 0 &&\n !currentLine.words[currentLine.words.length - 1].isSpace;\n\n // A word that abuts the previous run with no whitespace has no soft-wrap\n // opportunity before it — keep it with the preceding word like trailing\n // punctuation. Only the FIRST piece carries the flag; a break-word split\n // inside the word may still wrap mid-word.\n const isGlued = piece === pieces[0] && piece.noBreakBefore &&\n currentLine.words.length > 0 &&\n !currentLine.words[currentLine.words.length - 1].isSpace;\n\n // Leading inline padding/border (an empty boxOpen marker) must not be\n // stranded at the end of a line — it belongs with the span's following\n // content (CSS applies padding-left at the box's start). Include the next\n // content word's width in this marker's fit test so the two wrap together\n // and the left padding lands on the new line with the content.\n let headExtra = 0;\n if (!piece.text && piece.boxOpen) {\n const next = words[wordIndex + 1];\n if (next && !next.isSpace && next.text) {\n // Only the next word's first BREAKABLE unit must stay with the leading\n // padding — the whole word for unbreakable Latin, but just the first\n // character for CJK / break-word (which wrap per character). Using the\n // whole word here would over-wrap a long CJK run that follows padding.\n const np = next.text.length > 1\n ? breakWordIfNeeded(ctx, next, effWidth(), 0)\n : [next];\n headExtra = np[0].width;\n }\n }\n\n // A soft-hyphen break point draws a visible '-' when the line breaks\n // right after this piece. Chrome only allows a break there if the prefix\n // PLUS the hyphen fits, so reserve the hyphen advance in the overflow\n // test — otherwise we pack one extra segment and the appended hyphen\n // overflows the line (breaking one segment later than the browser).\n let shReserve = 0;\n if (piece.isSoftHyphenBreak) {\n applyFont(ctx, piece.style);\n ctx.letterSpacing = formatLetterSpacing(piece.style.letterSpacing);\n shReserve = cachedMeasureWidth(ctx, '-');\n }\n\n // Would this piece overflow?\n if (!piece.isSpace && !isTrailingPunct && !isGlued && currentLine.words.length > 0 &&\n currentLine.totalWidth + piece.width + shReserve + tail + headExtra > effWidth()) {\n const overflow = currentLine.totalWidth + piece.width + shReserve + tail + headExtra - 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 (piece.isSoftHyphenBreak ? '-' : '');\n // Empty-text words carry non-glyph advance (inline padding/border\n // markers, inline-block margins) that measureText(fullText) misses —\n // add them back so padded inline spans aren't under-measured.\n let markerWidth = 0;\n for (const w of currentLine.words) if (!w.text) markerWidth += w.width;\n if (!piece.text) markerWidth += piece.width;\n const fullWidth = cachedMeasureWidth(ctx, fullText) + markerWidth + tail + headExtra;\n // Allow only a hair of sub-pixel overflow. measureText matches the\n // browser's rendered width to ~0.01px, so a larger slack would keep\n // lines the browser actually wraps (packing one extra word per\n // borderline line and drifting the whole document's breaks).\n if (fullWidth <= effWidth() + 0.02) {\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(/(?<=-)(?!\\d)|(?<=[^\\d]-)/);\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(/(?<=-)(?!\\d)|(?<=[^\\d]-)/);\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 * Shared line budget for `-webkit-line-clamp` on a block container whose\n * text lives in block descendants (Chrome legacy `-webkit-box` semantics:\n * line boxes are counted across ALL descendants; the Nth line gets an\n * ellipsis and everything after it is dropped). Created in layoutBlock at\n * the clamped element and threaded through descendant layout calls.\n *\n * Known limitation: when the budget runs out exactly at a paragraph\n * boundary (Nth line is a paragraph's last line), following content is\n * dropped but the already-emitted Nth line gets no ellipsis — its layout\n * nodes were positioned before we learned more content follows.\n */\ninterface LineClampState {\n /** Line boxes still allowed before the cut. */\n remaining: number;\n /** Truncation point reached — all subsequent content is dropped. */\n exhausted: boolean;\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 clamp?: LineClampState,\n): { nodes: LayoutNode[]; height: number } {\n const results: LayoutNode[] = [];\n if (clamp && (clamp.exhausted || clamp.remaining <= 0)) {\n // An ancestor's clamp already used its line budget — drop this content.\n clamp.exhausted = true;\n return { nodes: results, height: 0 };\n }\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\n // `-webkit-line-clamp` / `line-clamp`: truncate to N lines and append a\n // CSS-style ellipsis (\"…\") to the Nth line, back-trimming trailing words\n // until the ellipsis fits within contentWidth. The budget comes from an\n // ancestor's shared clamp state when one is active (clamp on a block\n // container with block children), else from this element's own style.\n const clampN = clamp ? clamp.remaining : node.style.lineClamp;\n if (clampN > 0 && lines.length > clampN) {\n lines.length = clampN;\n const lastLine = lines[clampN - 1];\n // First line has reduced width because of text-indent; a cut on this\n // element's first line (effective budget of 1) hits it.\n const lineMaxForEllipsis = contentWidth - (clampN === 1 ? textIndent : 0);\n applyEllipsisToLine(ctx, lastLine, lineMaxForEllipsis);\n // Tag the truncated line so per-line alignment (text-align vs\n // text-align-last) still picks the right branch.\n lastLine.endedByHardBreak = true;\n if (clamp) {\n clamp.remaining = 0;\n clamp.exhausted = true;\n }\n } else if (clamp) {\n clamp.remaining -= lines.length;\n }\n\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 // When the line overflows its container, browsers fall back to start\n // alignment (per CSS Text 3 §7.1) instead of pushing the line outside\n // the box. Common trigger: wide letter-spacing on text that doesn't\n // wrap at letter boundaries (no break-word/break-all), where centering\n // would put glyphs at negative x. Sub-pixel tolerance avoids switching\n // to start for rounding noise on lines that visually fit.\n // Start edge differs by direction. LTR lines start at the left (x+indent).\n // RTL lines are anchored at the right, inset from the content's right edge\n // by text-indent — and lineMaxWidth already subtracts indent, so the RTL\n // right edge is x+lineMaxWidth. `align` here is physically resolved\n // (start/end → left/right via resolveDir), so RTL with align==='left'\n // (explicit left, or end) correctly falls through to left alignment.\n const overflows = line.totalWidth > lineMaxWidth + 0.5;\n let curX = x + indent;\n if (overflows) {\n // Overflow fallback: pin to the start edge (CSS Text 3 §7.1).\n curX = isRTL ? x + lineMaxWidth - line.totalWidth : x + indent;\n } else if (align === 'center') {\n curX = x + indent + (lineMaxWidth - line.totalWidth) / 2;\n } else if (align === 'right') {\n curX = (isRTL ? x + lineMaxWidth : x + indent + lineMaxWidth) - line.totalWidth;\n } else if (align === 'justify' && isRTL) {\n // RTL justify: anchor the right edge at the inset start; spaces expand left.\n curX = x + lineMaxWidth - line.totalWidth;\n }\n // Snapshot the line's left edge before LTR emission advances curX.\n const lineLeftX = curX;\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 // Off-baseline content (sub/sup/middle/lengths/...) does not establish\n // the line's baseline position — only baseline-aligned content does.\n if (isShiftedVAlign(word.style.verticalAlign)) continue;\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 off-baseline 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 // Parent font size for vertical-align positioning (used in expansion + text\n // emit) — the largest baseline-aligned font on the line.\n const lineNormalWords = line.words.filter(w =>\n w.text !== '' && !isShiftedVAlign(w.style.verticalAlign));\n const parentFontSize = lineNormalWords.length > 0\n ? Math.max(...lineNormalWords.map(w => w.style.fontSize)) : 0;\n\n // Expand line height if vertically-shifted content extends beyond the line\n // box. Browsers grow the line box to fit all content, but keep the normal\n // text baseline position unchanged.\n let lineTop = curY;\n let lineBottom = curY + lineHeight;\n for (const word of line.words) {\n if (word.text === '') continue;\n const va = word.style.verticalAlign;\n if (!isShiftedVAlign(va)) continue;\n if (parentFontSize === 0) break;\n\n const { ascent: wAscent, descent: wDescent } = getFontMetrics(ctx, word.style);\n const shiftedBaseline = lineBaselineY +\n verticalAlignShift(va, wAscent, wDescent, parentFontSize, maxAscent, maxDescent, lineHeight);\n\n const wordTop = shiftedBaseline - wAscent;\n const wordBottom = shiftedBaseline + wDescent;\n if (wordTop < lineTop) lineTop = wordTop;\n if (wordBottom > lineBottom) lineBottom = wordBottom;\n }\n const effectiveLineHeight = lineBottom - lineTop;\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 (word.isSpace && justifyExtraPerSpace > 0) {\n // Justify only: break the shaping group at the space and fold the\n // expansion into the inter-group advance so the line fills the width.\n // (Arabic does not join across spaces, so this is shaping-safe.)\n // When not justifying, spaces stay merged into the group text below\n // so the canvas BiDi engine can reorder embedded LTR runs/numbers.\n if (currentGroup) { groups.push(currentGroup); currentGroup = null; }\n pendingPad += word.width + justifyExtraPerSpace;\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 // This line belongs to an LTR block (we're in the !isRTL branch), so it\n // must be painted with an LTR base direction even when its first word is\n // RTL (an RTL span that wrapped onto this line). Without forcing LTR the\n // node inherits the first word's direction:'rtl' and the paint path\n // right-aligns the whole line at the left edge (x=curX), drawing it\n // off-screen. The canvas BiDi engine still reorders the embedded\n // Arabic/Hebrew runs within the LTR line.\n results.push({\n type: 'text',\n text: lineText,\n x: curX,\n y: lineBaselineY,\n width: measuredWidth,\n style: { ...textWords[0].style, direction: 'ltr' },\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 (isShiftedVAlign(va)) {\n const pfs = parentFontSize || word.style.fontSize;\n const { ascent: wA, descent: wD } = getFontMetrics(ctx, word.style);\n baselineY += verticalAlignShift(va, wA, wD, pfs, maxAscent, maxDescent, lineHeight);\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 // Emit a public LayoutLine record for this committed line.\n // bounds.width: justified lines fill lineMaxWidth (spaces expanded);\n // others use the measured words width.\n const lineWidth =\n align === 'justify' && justifyExtraPerSpace > 0\n ? lineMaxWidth\n : line.totalWidth;\n _lines.push({\n y: Math.round(lineBaselineY),\n text: line.words.map(w => w.text).join(''),\n bounds: {\n x: lineLeftX,\n // Use the actual visual top (may be < curY when a super pushes the\n // line box upward) so the rect covers ascenders/super content.\n y: lineTop,\n width: lineWidth,\n height: effectiveLineHeight,\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 clamp?: LineClampState,\n): { box: LayoutBox; height: number; marginBottomOut: number } {\n const style = node.style;\n\n // `-webkit-line-clamp` on a block container: start a shared line budget\n // here and thread it through descendant layout so the count spans block\n // children (Chrome legacy -webkit-box semantics). An ancestor's active\n // clamp wins over a nested one.\n if (!clamp && style.lineClamp > 0) {\n clamp = { remaining: style.lineClamp, exhausted: false };\n }\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, clamp);\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 // Line-clamp budget exhausted — everything below the cut is dropped,\n // including the margin trailing the cut line.\n if (clamp && (clamp.exhausted || clamp.remaining <= 0)) {\n clamp.exhausted = true;\n prevMarginBottom = 0;\n break;\n }\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, clamp);\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, clamp,\n );\n box.children.push(childBox);\n curY += childTotalHeight;\n // A child truncated by line-clamp clips its trailing margin too.\n prevMarginBottom = clamp?.exhausted ? 0 : 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 const isBullet = BULLET_MARKERS.has(style.listStyleType);\n // Gap between marker and content, matching Chrome (measured empirically):\n // - bullets: Chrome paints a symbol (diameter ascent/3) whose ink ends\n // 7px + ascent/3 before the content edge, centered ascent/3 above the\n // baseline. We keep the glyph but position its ink to land there.\n // - text markers (\"1.\"): Chrome's marker text carries a \". \" suffix, so\n // the gap is one space advance and the baseline is the line baseline.\n // `::marker { padding-inline-end: <length> }` overrides the gap — we honor\n // the 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\n let markerX: number;\n let markerY = baselineY;\n let markerDirection = 'ltr';\n const contentStartX = box.x + style.borderLeftWidth + style.paddingLeft;\n const boxRightEdge = box.x + box.width;\n if (isBullet) {\n const { ascent } = getFontMetrics(ctx, markerStyleObj);\n const m = ctx.measureText(node.listMarker);\n const gap = explicitGap !== undefined ? explicitGap : 7 + ascent / 3;\n if (isRTL) {\n // actualBoundingBoxLeft is positive when ink extends left of origin\n markerX = boxRightEdge + gap + (m.actualBoundingBoxLeft ?? 0);\n } else {\n markerX = contentStartX - gap - (m.actualBoundingBoxRight ?? markerWidth);\n }\n const glyphInkCenter =\n ((m.actualBoundingBoxAscent ?? 0) - (m.actualBoundingBoxDescent ?? 0)) / 2;\n markerY = baselineY - ascent / 3 + glyphInkCenter;\n } else {\n const gap = explicitGap !== undefined\n ? explicitGap\n : cachedMeasureWidth(ctx, ' ');\n if (isRTL) {\n // RTL: marker in the parent's right padding area (outside the li box).\n // Numbered markers (\"1.\") need RTL direction to display as \".1\".\n // With textAlign='right', x is the right edge — so add markerWidth.\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 markerX = contentStartX - markerWidth - gap;\n }\n }\n\n box.children.unshift({\n type: 'text',\n text: node.listMarker,\n x: markerX,\n y: markerY,\n width: markerWidth,\n style: { ...markerStyleObj, textDecorationLine: 'none', fontWeight: ms?.fontWeight ?? 400, fontStyle: ms?.fontStyle ?? 'normal', direction: markerDirection },\n });\n\n // Also publish the marker through the LayoutLine stream so result.lines\n // sees the bullet/number alongside the item text. Markers are added AFTER\n // inline content is laid out, so they don't go through layoutInlineContent.\n // The buildLayoutTree sort+merge step picks up the marker by its baseline.\n // RTL numbered markers store their right edge in markerX (textAlign trick).\n const markerLeftX = markerDirection === 'rtl' ? markerX - markerWidth : markerX;\n _lines.push({\n y: Math.round(baselineY),\n text: node.listMarker,\n bounds: {\n x: markerLeftX,\n y: box.y + style.borderTopWidth + style.paddingTop,\n width: markerWidth,\n height: lineHeight,\n },\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; lines: LayoutLine[] } {\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 _lines = [];\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 // Sort by baseline y, then by left edge so cross-cell content merges in\n // reading order (LTR). List markers sit at smaller x than their content\n // and so come first, producing \"• Item\" rather than \"Item •\".\n const sorted = _lines.slice().sort((a, b) =>\n (a.y - b.y) || (a.bounds.x - b.bounds.x)\n );\n const lines: LayoutLine[] = [];\n for (const candidate of sorted) {\n const last = lines[lines.length - 1];\n // Tolerance keys off the candidate's line height (matches the legacy\n // extractLines behavior). Using max(last, candidate) is symmetric but\n // grows after each merge as last.bounds.height becomes the union — that\n // leaks across rows in tight multi-column layouts.\n const tolerance = candidate.bounds.height * 0.5;\n if (last && Math.abs(candidate.y - last.y) < tolerance) {\n // Cross-cell merge: insert a space separator so the text stays\n // readable when N cells of a table row collapse into one LayoutLine.\n // Skip if either side already has a boundary space.\n const needsSep = last.text.length > 0 && candidate.text.length > 0 &&\n !/\\s$/.test(last.text) && !/^\\s/.test(candidate.text);\n last.text += (needsSep ? ' ' : '') + candidate.text;\n // Carry baseline forward so the next comparison uses the running\n // edge of the group, not the stale first element's baseline.\n last.y = Math.max(last.y, candidate.y);\n const x1 = Math.min(last.bounds.x, candidate.bounds.x);\n const y1 = Math.min(last.bounds.y, candidate.bounds.y);\n const x2 = Math.max(last.bounds.x + last.bounds.width, candidate.bounds.x + candidate.bounds.width);\n const y2 = Math.max(last.bounds.y + last.bounds.height, candidate.bounds.y + candidate.bounds.height);\n last.bounds = { x: x1, y: y1, width: x2 - x1, height: y2 - y1 };\n } else {\n lines.push({ y: candidate.y, text: candidate.text, bounds: { ...candidate.bounds } });\n }\n }\n return { root: box, height, lines };\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 */\nexport function 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 */\nexport function 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 */\nexport function 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/** The solid fill color for text: -webkit-text-fill-color if set, else color. */\nfunction textFillColor(style: ResolvedStyle): string {\n return style.webkitTextFillColor && style.webkitTextFillColor !== 'transparent'\n ? style.webkitTextFillColor : style.color;\n}\n\n/** Apply the canvas stroke settings for -webkit-text-stroke. */\nfunction applyTextStroke(ctx: CanvasRenderingContext2D, style: ResolvedStyle): void {\n ctx.strokeStyle = style.webkitTextStrokeColor || style.color;\n ctx.lineWidth = style.webkitTextStrokeWidth;\n ctx.lineJoin = 'round';\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 (Number.isFinite(style.letterSpacing) && 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 (drawn behind the text). Cast the shadow from the shape that\n // is actually painted: the fill when it's visible, and/or the stroke. This\n // matters for stroked text with a transparent fill (color:transparent +\n // -webkit-text-stroke), where CSS casts the shadow from the stroke outline\n // rather than the invisible fill.\n const shadows = parseTextShadows(style.textShadow);\n if (shadows.length > 0) {\n const hasVisibleFill = isGradientText || !isFillTransparent;\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 if (hasVisibleFill) {\n ctx.fillStyle = isGradientText && gradientFill ? gradientFill : textFillColor(style);\n ctx.fillText(node.text, node.x, node.y);\n }\n if (isStrokedText) {\n applyTextStroke(ctx, style);\n ctx.strokeText(node.text, node.x, node.y);\n }\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 = textFillColor(style);\n ctx.fillText(node.text, node.x, node.y);\n }\n };\n\n const drawStroke = () => {\n if (!isStrokedText) return;\n ctx.save();\n applyTextStroke(ctx, style);\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, 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// ─── 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, lines } = buildLayoutTree(measureCtx, tree, width, useDomMeasurements, debug);\n const finalHeight = height || contentHeight;\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":"iRAIA,SAAgB,EAAU,EAA2D,CAEnF,IAAM,EADS,IAAI,WAAW,CACX,gBAAgB,EAAM,YAAY,CAG/C,EAAY,EAAI,iBAAiB,QAAQ,CAC3C,EAAM,GACV,IAAK,IAAM,KAAO,EAChB,GAAO,EAAI,YAAc;EACzB,EAAI,QAAQ,CAId,IAAM,EAAW,SAAS,wBAAwB,CAClD,KAAO,EAAI,KAAK,YACd,EAAS,YAAY,SAAS,UAAU,EAAI,KAAK,WAAW,CAAC,CAG/D,MAAO,CAAE,WAAU,MAAK,CCH1B,SAAS,EAAS,EAA4D,CAC5E,IAAM,EAAmB,EAAE,CACrB,EAA0B,EAAE,CAElC,EAAM,EAAI,QAAQ,oBAAqB,GAAG,CAE1C,IAAI,EAAI,EACR,KAAO,EAAI,EAAI,QAAQ,CAErB,KAAO,EAAI,EAAI,QAAU,KAAK,KAAK,EAAI,GAAG,EAAE,IAC5C,GAAI,GAAK,EAAI,OAAQ,MAGrB,GAAI,EAAI,KAAO,IAAK,CAClB,IAAM,EAAU,EACZ,EAAa,EACjB,KAAO,EAAI,EAAI,QAAQ,CAErB,GADI,EAAI,KAAO,KAAK,IAChB,EAAI,KAAO,MACb,IACI,GAAc,GAAG,CAAE,IAAK,MAE9B,IAGF,IAAM,EAAS,EAAI,MAAM,EAAS,EAAE,CAChC,EAAO,WAAW,aAAa,EACjC,EAAc,KAAK,EAAO,CAE5B,SAIF,IAAM,EAAgB,EACtB,KAAO,EAAI,EAAI,QAAU,EAAI,KAAO,KAAK,IACzC,GAAI,GAAK,EAAI,OAAQ,MACrB,IAAM,EAAc,EAAI,MAAM,EAAe,EAAE,CAAC,MAAM,CACtD,IAGA,IAAM,EAAY,EAClB,KAAO,EAAI,EAAI,QAAU,EAAI,KAAO,KAAK,IACzC,IAAM,EAAU,EAAI,MAAM,EAAW,EAAE,CAAC,MAAM,CAG9C,GAFA,IAEI,CAAC,EAAa,SAGlB,IAAM,EAAY,EAAY,MAAM,IAAI,CAAC,IAAI,GAAK,EAAE,MAAM,CAAC,CAAC,OAAO,QAAQ,CAGrE,EAAiC,EAAE,CACzC,IAAK,IAAM,KAAQ,EAAQ,MAAM,IAAI,CAAE,CACrC,IAAM,EAAW,EAAK,QAAQ,IAAI,CAClC,GAAI,IAAa,GAAI,SACrB,IAAM,EAAW,EAAK,MAAM,EAAG,EAAS,CAAC,MAAM,CAAC,aAAa,CACvD,EAAQ,EAAK,MAAM,EAAW,EAAE,CAAC,MAAM,CACzC,GAAY,GACd,EAAa,KAAK,CAAE,WAAU,QAAO,CAAC,CAItC,EAAU,OAAS,GAAK,EAAa,OAAS,GAChD,EAAM,KAAK,CAAE,YAAW,eAAc,CAAC,CAI3C,MAAO,CAAE,QAAO,gBAAe,CAgBjC,SAAS,EAAoB,EAA4C,CAGvE,IAAM,EADM,EAAS,QAAQ,YAAa,GAAG,CAC3B,MAAM,cAAc,CAClC,EAAM,EAAG,EAAU,EAAG,EAAO,EACjC,IAAK,IAAM,KAAQ,EAAO,CAExB,IAAM,EAAY,EAAK,MAAM,WAAW,CACpC,IAAW,GAAO,EAAU,QAEhC,IAAM,EAAe,EAAK,MAAM,YAAY,CACxC,IAAc,GAAW,EAAa,QAE1C,IAAM,EAAU,EAAK,QAAQ,cAAe,GAAG,CAAC,QAAQ,WAAY,GAAG,CAAC,MAAM,CAC1E,GAAW,IAAY,KAAK,IAElC,MAAO,CAAC,EAAK,EAAS,EAAK,CAS7B,SAAS,EAAU,EAA0B,CAC3C,IAAM,EAAe,EAAK,MAAM,YAAY,EAAI,EAAE,CAC5C,EAAM,EAAK,QAAQ,YAAa,GAAG,CAAC,QAAQ,WAAY,GAAG,CAAC,MAAM,CACxE,MAAO,CACL,IAAM,GAAO,IAAQ,IAAO,EAAM,GAClC,QAAS,EAAa,IAAI,GAAK,EAAE,MAAM,EAAE,CAAC,CAC3C,CAiBH,SAAS,EAAkB,EAAkB,EAA8B,CACzE,GAAI,EAAK,KAAO,EAAK,MAAQ,EAAI,QAAS,MAAO,GACjD,IAAK,IAAM,KAAO,EAAK,QACrB,GAAI,CAAC,EAAI,QAAQ,IAAI,EAAI,CAAE,MAAO,GAEpC,MAAO,GA4BT,SAAS,EAAc,EAAyC,CAG9D,IAAI,EACJ,GAAI,EAAS,SAAS,KAAK,CAAE,CAI3B,GADoB,EAAS,QAAQ,cAAe,GAAG,CACvC,SAAS,KAAK,CAAE,OAAO,KACvC,GAAI,aAAa,KAAK,EAAS,CAC7B,EAAgB,SAGhB,EAAW,EAAS,QAAQ,uBAAwB,MAAM,CAE1D,EAAW,EAAS,QAAQ,cAAe,GAAG,MAE9C,OAAO,KAGX,GAAI,8DAA8D,KAAK,EAAS,CAAE,OAAO,KAEzF,IAAM,EAAmB,EAAE,CACrB,EAAwB,EAAE,CAE1B,EAAM,EAAS,MAAM,CAAC,MAAM,MAAM,CACxC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAI,OAAQ,IAC1B,EAAI,KAAO,IACb,EAAY,KAAK,IAAI,EAEjB,EAAO,OAAS,EAAY,OAAS,GACvC,EAAY,KAAK,IAAI,CAEvB,EAAO,KAAK,EAAI,GAAG,EAGvB,KAAO,EAAY,OAAS,EAAO,OAAS,GAC1C,EAAY,KAAK,IAAI,CAGvB,GAAI,EAAO,SAAW,EAAG,OAAO,KAEhC,IAAM,EAAQ,EAAO,IAAI,EAAU,CAC7B,EAAY,EAAM,EAAM,OAAS,GACjC,EAAe,EAAU,IAE/B,MAAO,CACL,QACA,cACA,YACA,gBAAiB,IAAiB,QAAU,IAAiB,OAC7D,KAAM,EAAoB,EAAS,CACnC,gBACD,CAMH,SAAS,EAAsB,EAAqB,EAA8B,CAEhF,GAAI,EAAI,oBACF,EAAI,SAAW,KAAM,MAAO,WAE5B,CAAC,EAAkB,EAAI,UAAW,EAAI,CAAE,MAAO,GAIrD,GAAI,EAAI,MAAM,SAAW,EAAG,MAAO,GAGnC,IAAI,EAAiC,EAAI,OACzC,IAAK,IAAI,EAAK,EAAI,MAAM,OAAS,EAAG,GAAM,EAAG,IAAM,CACjD,GAAI,CAAC,EAAS,MAAO,GACrB,IAAM,EAAO,EAAI,MAAM,GAGvB,GAFmB,EAAI,YAAY,KAEhB,IAGjB,GADe,EAAQ,SAAW,OACnB,EAAK,MAAQ,QAAU,EAAK,MAAQ,QACjD,EAAU,EAAQ,eACT,EAAkB,EAAM,EAAQ,CACzC,EAAU,EAAQ,YAElB,MAAO,OAEJ,CAEL,IAAI,EAAQ,GACZ,KAAO,GAAS,CAEd,GADe,EAAQ,SAAW,OACnB,EAAK,MAAQ,QAAU,EAAK,MAAQ,QAAS,CAC1D,EAAU,EAAQ,OAClB,EAAQ,GACR,MAEF,GAAI,EAAkB,EAAM,EAAQ,CAAE,CACpC,EAAU,EAAQ,OAClB,EAAQ,GACR,MAEF,EAAU,EAAQ,OAEpB,GAAI,CAAC,EAAO,MAAO,IAIvB,MAAO,GAiBT,SAAS,GAA8B,CACrC,MAAO,CAGL,WAAY,QACZ,SAAU,GACV,WAAY,IACZ,UAAW,SACX,gBAAiB,SACjB,MAAO,eACP,UAAW,QACX,cAAe,OACf,WAAY,EACZ,cAAe,OACf,mBAAoB,OACpB,oBAAqB,QACrB,oBAAqB,eACrB,WAAY,OACZ,sBAAuB,EACvB,sBAAuB,GACvB,oBAAqB,GACrB,WAAY,SACZ,qBAAsB,GACtB,gBAAiB,OACjB,cAAe,EACf,YAAa,EACb,YAAa,OACb,WAAY,EACZ,cAAe,WACf,WAAY,SACZ,UAAW,SACX,aAAc,SACd,YAAa,SACb,UAAW,MACX,QAAS,QACT,MAAO,EACP,UAAW,EACX,WAAY,EACZ,aAAc,EACd,cAAe,EACf,YAAa,EACb,UAAW,EACX,YAAa,EACb,aAAc,EACd,WAAY,EACZ,gBAAiB,mBACjB,eAAgB,EAChB,eAAgB,eAChB,eAAgB,OAChB,iBAAkB,EAClB,iBAAkB,eAClB,iBAAkB,OAClB,kBAAmB,EACnB,kBAAmB,eACnB,kBAAmB,OACnB,gBAAiB,EACjB,gBAAiB,eACjB,gBAAiB,OACjB,cAAe,MACf,IAAK,EACL,SAAU,EACV,cAAe,OACf,UAAW,EACZ,CAIH,IAAM,EAAuD,CAC3D,KAAM,CAAE,QAAS,SAAU,CAC3B,EAAG,CAAE,QAAS,SAAU,CACxB,OAAQ,CAAE,QAAS,SAAU,WAAY,IAAK,CAC9C,EAAG,CAAE,QAAS,SAAU,WAAY,IAAK,CACzC,GAAI,CAAE,QAAS,SAAU,UAAW,SAAU,CAC9C,EAAG,CAAE,QAAS,SAAU,UAAW,SAAU,CAC7C,EAAG,CAAE,QAAS,SAAU,mBAAoB,YAAa,CACzD,EAAG,CAAE,QAAS,SAAU,mBAAoB,eAAgB,CAC5D,OAAQ,CAAE,QAAS,SAAU,mBAAoB,eAAgB,CACjE,IAAK,CAAE,QAAS,SAAU,mBAAoB,eAAgB,CAC9D,IAAK,CAAE,QAAS,SAAU,cAAe,MAAO,SAAU,IAAM,CAChE,IAAK,CAAE,QAAS,SAAU,cAAe,QAAS,SAAU,IAAM,CAClE,KAAM,CAAE,QAAS,SAAU,WAAY,YAAa,CACpD,KAAM,CAAE,QAAS,SAAU,UAAW,SAAU,CAChD,IAAK,CAAE,QAAS,SAAU,YAAa,gBAAiB,CACxD,IAAK,CAAE,QAAS,SAAU,YAAa,UAAW,CAClD,EAAG,CAAE,QAAS,QAAS,UAAW,GAAI,aAAc,GAAI,CACxD,IAAK,CAAE,QAAS,QAAS,CACzB,GAAI,CAAE,QAAS,QAAS,SAAU,EAAG,WAAY,IAAK,UAAW,KAAO,aAAc,KAAO,CAC7F,GAAI,CAAE,QAAS,QAAS,SAAU,IAAK,WAAY,IAAK,UAAW,KAAO,aAAc,KAAO,CAC/F,GAAI,CAAE,QAAS,QAAS,SAAU,KAAM,WAAY,IAAK,UAAW,GAAI,aAAc,GAAI,CAC1F,GAAI,CAAE,QAAS,QAAS,SAAU,EAAG,WAAY,IAAK,UAAW,MAAO,aAAc,MAAO,CAC7F,GAAI,CAAE,QAAS,QAAS,SAAU,IAAM,WAAY,IAAK,UAAW,MAAO,aAAc,MAAO,CAChG,GAAI,CAAE,QAAS,QAAS,SAAU,IAAM,WAAY,IAAK,UAAW,MAAO,aAAc,MAAO,CAChG,GAAI,CAAE,QAAS,QAAS,cAAe,OAAQ,UAAW,GAAI,aAAc,GAAI,CAChF,GAAI,CAAE,QAAS,QAAS,cAAe,UAAW,UAAW,GAAI,aAAc,GAAI,CACnF,GAAI,CAAE,QAAS,YAAa,CAC5B,WAAY,CAAE,QAAS,QAAS,UAAW,GAAI,aAAc,GAAI,WAAY,GAAI,YAAa,GAAI,CAClG,IAAK,CAAE,QAAS,QAAS,WAAY,MAAO,WAAY,YAAa,UAAW,GAAI,aAAc,GAAI,CACtG,MAAO,CAAE,QAAS,QAAS,CAC3B,GAAI,CAAE,QAAS,YAAa,CAC5B,GAAI,CAAE,QAAS,aAAc,CAC7B,GAAI,CAAE,QAAS,aAAc,WAAY,IAAK,CAC9C,GAAI,CAAE,QAAS,SAAU,CACzB,GAAI,CACF,QAAS,QACT,eAAgB,EAChB,eAAgB,QAChB,eAAgB,OAChB,UAAW,IACX,aAAc,IACf,CACF,CAKD,SAAS,EAAW,EAAe,EAAwB,EAAgC,CACzF,GAAI,CAAC,GAAS,IAAU,UAAY,IAAU,QAAU,IAAU,OAAQ,MAAO,GACjF,IAAM,EAAU,EAAM,MAAM,CAE5B,GAAI,EAAQ,SAAS,KAAK,CAAE,CAC1B,IAAM,EAAM,WAAW,EAAQ,CAC/B,OAAO,MAAM,EAAI,CAAG,EAAI,EAAM,EAEhC,GAAI,EAAQ,SAAS,IAAI,CAAE,CACzB,IAAM,EAAM,WAAW,EAAQ,CAC/B,OAAO,MAAM,EAAI,CAAG,EAAK,EAAM,IAAO,EAExC,GAAI,EAAQ,SAAS,KAAK,CAAE,CAC1B,IAAM,EAAM,WAAW,EAAQ,CAC/B,OAAO,MAAM,EAAI,CAAG,EAAI,EAG1B,IAAM,EAAM,WAAW,EAAQ,CAC/B,OAAO,MAAM,EAAI,CAAG,EAAI,EAG1B,SAAS,EAAgB,EAAuB,CAC9C,GAAI,IAAU,OAAQ,MAAO,KAC7B,GAAI,IAAU,SAAU,MAAO,KAC/B,IAAM,EAAM,SAAS,EAAO,GAAG,CAC/B,OAAO,MAAM,EAAI,CAAG,IAAM,EAQ5B,SAAgB,EAAyB,EAA6B,CACpE,IAAM,EAAI,EAAW,MAAM,CAAC,aAAa,CACzC,GAAI,CAAC,GAAK,IAAM,SAAU,MAAO,GACjC,IAAM,EAAS,EAAE,MAAM,MAAM,CAAC,OAAO,GAAK,IAAM,QAAU,IAAM,SAAS,CACnE,EAAY,EAAO,QAAQ,SAAS,CACpC,EAAU,EAAO,QAAQ,OAAO,CAGtC,OAFI,IAAc,GAAW,GACzB,IAAY,GAAW,GACpB,EAAY,EAIrB,SAAS,EAAwB,EAAyB,CACxD,IAAM,EAAkB,EAAE,CACtB,EAAQ,EACR,EAAM,GACV,IAAK,IAAI,EAAI,EAAG,EAAI,EAAM,OAAQ,IAAK,CACrC,IAAM,EAAK,EAAM,GACb,IAAO,KAAO,IAAS,GAAO,GACzB,IAAO,KAAO,EAAQ,KAAK,IAAI,EAAG,EAAQ,EAAE,CAAE,GAAO,GACrD,IAAU,GAAK,KAAK,KAAK,EAAG,CACP,KAAjB,EAAM,KAAK,EAAI,CAAQ,IAC7B,GAAO,EAGhB,OADI,GAAK,EAAM,KAAK,EAAI,CACjB,EAOT,SAAgB,EAAgB,EAAkB,EAAiC,CACjF,GAAI,IAAa,UAAY,IAAa,UAAW,CACnD,IAAM,EAAQ,EAAM,MAAM,CAAC,MAAM,MAAM,CACnC,EAAa,EAAe,EAAgB,EAWhD,OAVI,EAAM,SAAW,EACnB,EAAM,EAAQ,EAAS,EAAO,EAAM,GAC3B,EAAM,SAAW,GAC1B,EAAM,EAAS,EAAM,GACrB,EAAQ,EAAO,EAAM,IACZ,EAAM,SAAW,GAC1B,EAAM,EAAM,GAAI,EAAQ,EAAO,EAAM,GAAI,EAAS,EAAM,KAExD,EAAM,EAAM,GAAI,EAAQ,EAAM,GAAI,EAAS,EAAM,GAAI,EAAO,EAAM,IAE7D,CACL,CAAE,SAAU,GAAG,EAAS,MAAO,MAAO,EAAK,CAC3C,CAAE,SAAU,GAAG,EAAS,QAAS,MAAO,EAAO,CAC/C,CAAE,SAAU,GAAG,EAAS,SAAU,MAAO,EAAQ,CACjD,CAAE,SAAU,GAAG,EAAS,OAAQ,MAAO,EAAM,CAC9C,CAGH,GAAI,IAAa,UAAY,IAAa,cAAgB,IAAa,gBACnE,IAAa,iBAAmB,IAAa,cAAe,CAC9D,IAAM,EAAQ,EAAM,MAAM,CAAC,MAAM,MAAM,CACjC,EAAe,CAAC,QAAS,SAAU,SAAU,SAAU,OAAQ,SAAS,CACxE,EAAQ,EAAM,KAAK,GAAK,EAAE,SAAS,KAAK,EAAI,MAAM,KAAK,EAAE,CAAC,EAAI,IAC9D,EAAQ,EAAM,KAAK,GAAK,EAAa,SAAS,EAAE,CAAC,EAAI,OACrD,EAAQ,EAAM,KAAK,GAAK,CAAC,EAAE,SAAS,KAAK,EAAI,CAAC,MAAM,KAAK,EAAE,EAAI,CAAC,EAAa,SAAS,EAAE,CAAC,EAAI,eAC7F,EAA2B,EAAE,CAC7B,EAAQ,IAAa,SACvB,CAAC,MAAO,QAAS,SAAU,OAAO,CAClC,CAAC,EAAS,QAAQ,UAAW,GAAG,CAAC,CACrC,IAAK,IAAM,KAAQ,EACjB,EAAO,KAAK,CAAE,SAAU,UAAU,EAAK,QAAS,MAAO,EAAO,CAAC,CAC/D,EAAO,KAAK,CAAE,SAAU,UAAU,EAAK,QAAS,MAAO,EAAO,CAAC,CAC/D,EAAO,KAAK,CAAE,SAAU,UAAU,EAAK,QAAS,MAAO,EAAO,CAAC,CAEjE,OAAO,EAGT,GAAI,IAAa,aAKf,OAHI,IAAU,OACL,CAAC,CAAE,SAAU,kBAAmB,MAAO,OAAQ,CAAC,CAElD,CAAC,CAAE,SAAU,kBAAmB,QAAO,CAAC,CAGjD,GAAI,IAAa,kBAAmB,CAClC,IAAM,EAAI,EAAM,MAAM,CACtB,GAAI,IAAM,WAAa,IAAM,OAC3B,MAAO,CAAC,CAAE,SAAU,uBAAwB,MAAO,OAAQ,CAAC,CAI9D,IAAI,EAAa,GAKX,EAJiB,EAAE,QAAQ,qCAAuC,IACtE,EAAa,EACN,IACP,CAC2B,MAAM,MAAM,CAAC,OAAO,QAAQ,CACnD,EAAa,CAAC,YAAa,WAAY,eAAe,CACtD,EAAc,CAAC,QAAS,SAAU,SAAU,SAAU,OAAO,CAC7D,EAA2B,EAAE,CAC7B,EAAkB,EAAE,CAC1B,IAAK,IAAM,KAAK,EACV,EAAW,SAAS,EAAE,CAAE,EAAM,KAAK,EAAE,CAChC,EAAY,SAAS,EAAE,CAAE,EAAO,KAAK,CAAE,SAAU,wBAAyB,MAAO,EAAG,CAAC,CACrF,EAAE,WAAW,IAAI,EACxB,EAAO,KAAK,CAAE,SAAU,wBAAyB,MAAO,EAAG,CAAC,CAIhE,OAFI,GAAY,EAAO,KAAK,CAAE,SAAU,wBAAyB,MAAO,EAAY,CAAC,CACjF,EAAM,OAAS,GAAG,EAAO,QAAQ,CAAE,SAAU,uBAAwB,MAAO,EAAM,KAAK,IAAI,CAAE,CAAC,CAC3F,EAGT,GAAI,IAAa,sBAAuB,CAItC,IAAM,EAAQ,EAAwB,EAAM,MAAM,CAAC,CAC7C,EAAQ,EAAM,KAAK,GAAK,EAAE,SAAS,KAAK,EAAI,MAAM,KAAK,EAAE,CAAC,EAAI,IAC9D,EAAQ,EAAM,KAAK,GAAK,CAAC,EAAE,SAAS,KAAK,EAAI,CAAC,MAAM,KAAK,EAAE,CAAC,EAAI,eACtE,MAAO,CACL,CAAE,SAAU,4BAA6B,MAAO,EAAO,CACvD,CAAE,SAAU,4BAA6B,MAAO,EAAO,CACxD,CAGH,GAAI,IAAa,OAAQ,CAEvB,IAAM,EAAQ,EAAM,MAAM,CAAC,MAAM,MAAM,CACjC,EAAO,WAAW,EAAM,GAAG,CAIjC,OAHK,MAAM,EAAK,CAGT,EAAE,CAFA,CAAC,CAAE,SAAU,YAAa,MAAO,OAAO,EAAK,CAAE,CAAC,CAU3D,OALI,IAAa,mBAAqB,IAAa,iBAE1C,EAAE,CAGJ,CAAC,CAAE,WAAU,QAAO,CAAC,CAM9B,SAAS,EACP,EACA,EACA,EACA,EACA,EACA,EACM,CAGN,IAAM,EAAW,EAAM,UAAY,EAEnC,OAAQ,EAAR,CAEE,IAAK,cAAe,EAAM,WAAa,EAAM,MAAM,CAAE,MACrD,IAAK,YAAa,CAChB,IAAM,EAAI,EAAM,MAAM,CAClB,EAAE,SAAS,KAAK,CAClB,EAAM,SAAW,WAAW,EAAE,CAAG,EACxB,EAAE,SAAS,IAAI,CACxB,EAAM,SAAY,WAAW,EAAE,CAAG,IAAO,EAEzC,EAAM,SAAW,WAAW,EAAE,EAAI,EAEpC,MAEF,IAAK,cAAe,EAAM,WAAa,EAAgB,EAAM,CAAE,MAC/D,IAAK,aAAc,EAAM,UAAY,EAAM,MAAM,CAAE,MAGnD,IAAK,eACL,IAAK,oBACH,EAAM,gBAAkB,iBAAiB,KAAK,EAAM,CAAG,aAAe,SACtE,MACF,IAAK,QAAS,EAAM,MAAQ,EAAM,MAAM,CAAE,MAC1C,IAAK,aAAc,EAAM,UAAY,EAAM,MAAM,CAAE,MACnD,IAAK,kBAAmB,EAAM,cAAgB,EAAM,MAAM,CAAE,MAC5D,IAAK,cACH,EAAM,WAAa,EAAW,EAAO,EAAU,EAAe,CAAE,MAClE,IAAK,iBAAkB,EAAM,cAAgB,EAAM,MAAM,CAAE,MAC3D,IAAK,uBAAwB,EAAM,mBAAqB,EAAM,MAAM,CAAE,MAGtE,IAAK,kBAAmB,MACxB,IAAK,wBAAyB,EAAM,oBAAsB,EAAM,MAAM,CAAE,MACxE,IAAK,wBAAyB,EAAM,oBAAsB,EAAM,MAAM,CAAE,MACxE,IAAK,cAAe,EAAM,WAAa,EAAM,MAAM,CAAE,MACrD,IAAK,4BAA6B,EAAM,sBAAwB,EAAW,EAAO,EAAU,EAAe,CAAE,MAC7G,IAAK,4BAA6B,EAAM,sBAAwB,EAAM,MAAM,CAAE,MAC9E,IAAK,0BAA2B,EAAM,oBAAsB,EAAM,MAAM,CAAE,MAC1E,IAAK,cAAe,EAAM,WAAa,EAAM,MAAM,CAAE,MACrD,IAAK,0BACL,IAAK,kBAAmB,EAAM,qBAAuB,EAAM,MAAM,CAAE,MACnE,IAAK,mBAAoB,EAAM,gBAAkB,EAAM,MAAM,CAAE,MAC/D,IAAK,iBACH,EAAM,cAAgB,EAAM,MAAM,GAAK,SAAW,EAAI,EAAW,EAAO,EAAU,EAAe,CAAE,MACrG,IAAK,eACH,EAAM,YAAc,EAAM,MAAM,GAAK,SAAW,EAAI,EAAW,EAAO,EAAU,EAAe,CAAE,MACnG,IAAK,eAAgB,EAAM,YAAc,EAAM,MAAM,CAAE,MACvD,IAAK,cAAe,CAClB,IAAM,EAAI,EAAM,MAAM,CACtB,GAAI,IAAM,SACR,EAAM,WAAa,UACV,EAAE,SAAS,KAAK,CACzB,EAAM,WAAa,WAAW,EAAE,EAAI,UAC3B,EAAE,SAAS,KAAK,CACzB,EAAM,WAAa,WAAW,EAAE,CAAG,MAC9B,CAGL,IAAM,EAAM,WAAW,EAAE,CACpB,MAAM,EAAI,GACb,EAAM,WAAa,EAAM,EACxB,EAAc,sBAAwB,GAG3C,MAEF,IAAK,qBACL,IAAK,aAAc,CAIjB,IAAM,EAAI,EAAM,MAAM,CAAC,aAAa,CACpC,GAAI,IAAM,QAAU,IAAM,OACxB,EAAM,UAAY,MACb,CACL,IAAM,EAAI,SAAS,EAAG,GAAG,CACzB,EAAM,UAAY,OAAO,SAAS,EAAE,EAAI,EAAI,EAAI,EAAI,EAEtD,MAEF,IAAK,iBAAkB,EAAM,cAAgB,EAAM,MAAM,CAAE,MAC3D,IAAK,cAAe,EAAM,WAAa,EAAM,MAAM,CAAE,MACrD,IAAK,aAAc,EAAM,UAAY,EAAM,MAAM,CAAE,MACnD,IAAK,gBACL,IAAK,YAAa,EAAM,aAAe,EAAM,MAAM,CAAE,MACrD,IAAK,YAAa,EAAM,UAAY,EAAM,MAAM,CAAE,MAClD,IAAK,eAAgB,EAAM,YAAc,EAAM,MAAM,CAAE,MAGvD,IAAK,UAAW,EAAM,QAAU,EAAM,MAAM,CAAE,MAC9C,IAAK,QAAS,CACZ,IAAM,EAAI,EAAM,MAAM,CAClB,IAAM,OAAQ,EAAM,MAAQ,EACvB,IAAM,SAAQ,EAAM,MAAQ,EAAW,EAAG,EAAU,EAAe,EAC5E,MAEF,IAAK,aAAc,EAAM,UAAY,EAAW,EAAO,EAAU,EAAe,CAAE,MAClF,IAAK,cAAe,EAAM,WAAa,EAAW,EAAO,EAAU,EAAe,CAAE,MACpF,IAAK,gBAAiB,EAAM,aAAe,EAAW,EAAO,EAAU,EAAe,CAAE,MACxF,IAAK,iBAAkB,EAAM,cAAgB,EAAW,EAAO,EAAU,EAAe,CAAE,MAC1F,IAAK,eAAgB,EAAM,YAAc,EAAW,EAAO,EAAU,EAAe,CAAE,MACtF,IAAK,aAAc,EAAM,UAAY,EAAW,EAAO,EAAU,EAAe,CAAE,MAClF,IAAK,eAAgB,EAAM,YAAc,EAAW,EAAO,EAAU,EAAe,CAAE,MACtF,IAAK,gBAAiB,EAAM,aAAe,EAAW,EAAO,EAAU,EAAe,CAAE,MACxF,IAAK,cAAe,EAAM,WAAa,EAAW,EAAO,EAAU,EAAe,CAAE,MACpF,IAAK,mBAAoB,EAAM,gBAAkB,EAAM,MAAM,CAAE,MAC/D,IAAK,aAAc,CACjB,IAAM,EAAI,EAAM,MAAM,CAClB,EAAE,SAAS,YAAY,CAEzB,EAAM,gBAAkB,GACf,EAAE,WAAW,IAAI,EAAI,EAAE,WAAW,MAAM,EAAI,EAAE,WAAW,MAAM,EACtE,CAAC,cAAe,OAAQ,UAAU,CAAC,SAAS,EAAE,EAC9C,WAAW,KAAK,EAAE,IACpB,EAAM,gBAAkB,GAE1B,MAIF,IAAK,uBACC,IAAc,MAAO,EAAM,aAAe,EAAW,EAAO,EAAU,EAAe,CACpF,EAAM,YAAc,EAAW,EAAO,EAAU,EAAe,CACpE,MACF,IAAK,qBACC,IAAc,MAAO,EAAM,YAAc,EAAW,EAAO,EAAU,EAAe,CACnF,EAAM,aAAe,EAAW,EAAO,EAAU,EAAe,CACrE,MACF,IAAK,sBACC,IAAc,MAAO,EAAM,YAAc,EAAW,EAAO,EAAU,EAAe,CACnF,EAAM,WAAa,EAAW,EAAO,EAAU,EAAe,CACnE,MACF,IAAK,oBACC,IAAc,MAAO,EAAM,WAAa,EAAW,EAAO,EAAU,EAAe,CAClF,EAAM,YAAc,EAAW,EAAO,EAAU,EAAe,CACpE,MAGF,IAAK,mBAAoB,EAAM,eAAiB,EAAW,EAAO,EAAU,EAAe,CAAE,MAC7F,IAAK,mBAAoB,EAAM,eAAiB,EAAM,MAAM,CAAE,MAC9D,IAAK,mBAAoB,EAAM,eAAiB,EAAM,MAAM,CAAE,MAC9D,IAAK,qBAAsB,EAAM,iBAAmB,EAAW,EAAO,EAAU,EAAe,CAAE,MACjG,IAAK,qBAAsB,EAAM,iBAAmB,EAAM,MAAM,CAAE,MAClE,IAAK,qBAAsB,EAAM,iBAAmB,EAAM,MAAM,CAAE,MAClE,IAAK,sBAAuB,EAAM,kBAAoB,EAAW,EAAO,EAAU,EAAe,CAAE,MACnG,IAAK,sBAAuB,EAAM,kBAAoB,EAAM,MAAM,CAAE,MACpE,IAAK,sBAAuB,EAAM,kBAAoB,EAAM,MAAM,CAAE,MACpE,IAAK,oBAAqB,EAAM,gBAAkB,EAAW,EAAO,EAAU,EAAe,CAAE,MAC/F,IAAK,oBAAqB,EAAM,gBAAkB,EAAM,MAAM,CAAE,MAChE,IAAK,oBAAqB,EAAM,gBAAkB,EAAM,MAAM,CAAE,MAGhE,IAAK,iBAAkB,EAAM,cAAgB,EAAM,MAAM,CAAE,MAC3D,IAAK,MAAO,EAAM,IAAM,EAAW,EAAO,EAAU,EAAe,CAAE,MACrE,IAAK,YAAa,EAAM,SAAW,WAAW,EAAM,EAAI,EAAG,MAG3D,IAAK,kBAAmB,EAAM,cAAgB,EAAM,MAAM,CAAE,MAG5D,IAAK,WACL,IAAK,MACL,IAAK,OACL,IAAK,QACL,IAAK,SACL,IAAK,qBACL,IAAK,mBACL,IAAK,UACL,IAAK,gBACL,IAAK,oBACL,IAAK,gBACL,IAAK,yBACL,IAAK,0BACL,IAAK,4BACL,IAAK,6BACL,IAAK,SACL,IAAK,UACL,IAAK,WACL,IAAK,aACL,IAAK,UACL,IAAK,aACL,IAAK,YACL,IAAK,eACL,IAAK,eACL,IAAK,MACL,IAAK,gBACH,OAKN,IAAM,EAAoD,CACxD,CAAC,cAAe,aAAa,CAC7B,CAAC,YAAa,WAAW,CACzB,CAAC,cAAe,aAAa,CAC7B,CAAC,aAAc,YAAY,CAC3B,CAAC,QAAS,QAAQ,CAClB,CAAC,aAAc,YAAY,CAC3B,CAAC,kBAAmB,gBAAgB,CACpC,CAAC,cAAe,aAAa,CAC7B,CAAC,iBAAkB,gBAAgB,CACnC,CAAC,cAAe,aAAa,CAC7B,CAAC,aAAc,YAAY,CAC3B,CAAC,gBAAiB,eAAe,CACjC,CAAC,YAAa,YAAY,CAC1B,CAAC,iBAAkB,gBAAgB,CACnC,CAAC,eAAgB,cAAc,CAC/B,CAAC,cAAe,aAAa,CAC7B,CAAC,cAAe,aAAa,CAC7B,CAAC,eAAgB,cAAc,CAC/B,CAAC,kBAAmB,gBAAgB,CACpC,CAAC,iBAAkB,gBAAgB,CACnC,CAAC,cAAe,aAAa,CAC9B,CAMD,SAAS,EAAY,EAAsB,EAAuB,EAA6B,CAC7F,IAAK,GAAM,CAAC,EAAS,KAAQ,EAC3B,GAAI,CAAC,EAAS,IAAI,EAAQ,CACxB,GAAI,IAAQ,aAAc,CAExB,IAAM,EAAc,EAAe,sBAC/B,IAAe,IAAA,GAIjB,EAAM,WAAa,EAAO,YAH1B,EAAM,WAAa,EAAa,EAAM,SACrC,EAAc,sBAAwB,QAKxC,EAAc,GAAQ,EAAe,GA4B9C,SAAS,EAAe,EAItB,CACA,IAAM,EAAQ,IAAI,IACZ,EAAU,IAAI,IACd,EAA6B,EAAE,CACjC,EAAY,EAEhB,IAAK,IAAM,KAAQ,EAAO,CAExB,IAAM,EAA2E,EAAE,CACnF,IAAK,IAAM,KAAQ,EAAK,aAAc,CACpC,IAAM,EAAc,EAAK,MAAM,SAAS,aAAa,CAC/C,EAAa,EACf,EAAK,MAAM,QAAQ,oBAAqB,GAAG,CAAC,MAAM,CAClD,EAAK,MACH,EAAW,EAAgB,EAAK,SAAU,EAAW,CAC3D,IAAK,IAAM,KAAO,EAChB,EAAc,KAAK,CAAE,SAAU,EAAI,SAAU,MAAO,EAAI,MAAO,UAAW,EAAa,CAAC,CAI5F,IAAK,IAAM,KAAO,EAAK,UAAW,CAChC,IAAM,EAAS,EAAc,EAAI,CACjC,GAAI,CAAC,EAAQ,SAEb,IAAM,EAAuB,CAC3B,SAAU,EACV,aAAc,EACd,UAAW,IACZ,CAEK,EAAK,EAAO,UAClB,GAAI,EAAG,KAAO,CAAC,EAAO,gBAAiB,CAErC,IAAM,EAAO,EAAM,IAAI,EAAG,IAAI,CAC1B,EAAM,EAAK,KAAK,EAAM,CACrB,EAAM,IAAI,EAAG,IAAK,CAAC,EAAM,CAAC,CAEjC,GAAI,EAAG,QAAQ,OAAS,EAAG,CAEzB,IAAM,EAAM,EAAG,QAAQ,GACjB,EAAO,EAAQ,IAAI,EAAI,CACzB,EAAM,EAAK,KAAK,EAAM,CACrB,EAAQ,IAAI,EAAK,CAAC,EAAM,CAAC,CAE5B,CAAC,EAAG,KAAO,EAAG,QAAQ,SAAW,GAEnC,EAAU,KAAK,EAAM,CAGnB,EAAO,iBACT,EAAU,KAAK,EAAM,EAK3B,MAAO,CAAE,QAAO,UAAS,YAAW,CAItC,SAAS,EAAiB,EAAW,EAAsB,CACzD,OAAQ,EAAR,CACE,IAAK,OAAQ,MAAO,IACpB,IAAK,SAAU,MAAO,IACtB,IAAK,SAAU,MAAO,IACtB,IAAK,OAAQ,MAAO,GACpB,IAAK,uBACH,MAAO,GAAG,EAAI,IAAM,GAAK,EAAI,IAAM,EAAI,EAAE,GAC3C,IAAK,cAAe,MAAO,GAAG,EAAQ,EAAE,CAAC,aAAa,CAAC,GACvD,IAAK,cAAe,MAAO,GAAG,EAAQ,EAAE,CAAC,GACzC,IAAK,cACL,IAAK,cAAe,MAAO,GAAG,EAAQ,EAAE,CAAC,aAAa,CAAC,GACvD,IAAK,cACL,IAAK,cAAe,MAAO,GAAG,EAAQ,EAAE,CAAC,GAEzC,QACE,MAAO,GAAG,EAAE,IAIlB,SAAS,EAAQ,EAAmB,CAClC,GAAI,EAAI,GAAK,EAAI,KAAM,MAAO,GAAG,IACjC,IAAM,EAA0B,CAC9B,CAAC,IAAM,IAAI,CAAE,CAAC,IAAK,KAAK,CAAE,CAAC,IAAK,IAAI,CAAE,CAAC,IAAK,KAAK,CACjD,CAAC,IAAK,IAAI,CAAE,CAAC,GAAI,KAAK,CAAE,CAAC,GAAI,IAAI,CAAE,CAAC,GAAI,KAAK,CAC7C,CAAC,GAAI,IAAI,CAAE,CAAC,EAAG,KAAK,CAAE,CAAC,EAAG,IAAI,CAAE,CAAC,EAAG,KAAK,CAAE,CAAC,EAAG,IAAI,CACpD,CACG,EAAM,GACV,IAAK,GAAM,CAAC,EAAG,KAAM,EACnB,KAAO,GAAK,GAAK,GAAO,EAAG,GAAK,EAElC,OAAO,EAGT,SAAS,EAAQ,EAAmB,CAClC,GAAI,EAAI,EAAG,MAAO,GAAG,IACrB,IAAI,EAAM,GACV,KAAO,EAAI,GAAG,CACZ,IAAM,GAAK,EAAI,GAAK,GACpB,EAAM,OAAO,aAAa,GAAK,EAAE,CAAG,EACpC,EAAI,KAAK,OAAO,EAAI,GAAK,GAAG,CAE9B,OAAO,EAOT,SAAS,EAAc,EAAa,EAA2C,CAE7E,GADY,EAAG,QAAQ,aAAa,GACxB,KAAM,OAClB,GAAI,IAAkB,OAAQ,MAAO,GAErC,IAAM,EAAS,EAAG,cACZ,EAAY,GAAQ,QAAQ,aAAa,CAG/C,GAAI,IAAkB,QAAU,IAAkB,UAAY,IAAkB,SAC9E,OAAO,EAAiB,EAAG,EAAc,CAI3C,GAAI,IAAc,MAAQ,IAAc,MAAQ,CAAC,EAAQ,CACvD,IAAM,EAAU,EACZ,MAAM,KAAK,EAAO,SAAS,CAAC,OAAO,GAAK,EAAE,QAAQ,aAAa,GAAK,KAAK,CACzE,CAAC,EAAG,CACF,EAAY,GAAQ,aAAa,QAAQ,CACzC,EAAW,GAAQ,aAAa,WAAW,EAAI,GAC/C,EAAQ,EAAY,SAAS,EAAW,GAAG,CAAI,EAAW,EAAQ,OAAS,EAC3E,EAAO,EAAW,GAAK,EACzB,EAAI,EACR,IAAK,IAAM,KAAQ,EAAS,CAC1B,IAAM,EAAY,EAAK,aAAa,QAAQ,CAC5C,GAAI,EAAW,CACb,IAAM,EAAI,SAAS,EAAW,GAAG,CAC5B,OAAO,MAAM,EAAE,GAAE,EAAI,GAE5B,GAAI,IAAS,EAAI,OAAO,EAAiB,EAAG,GAAiB,UAAU,CACvE,GAAK,EAEP,OAAO,EAAiB,EAAG,GAAiB,UAAU,EAS1D,SAAS,EAAiB,EAAqC,CAC7D,IAAM,EAAiC,EAAE,CACzC,IAAK,IAAM,KAAQ,EAAU,MAAM,IAAI,CAAE,CACvC,IAAM,EAAW,EAAK,QAAQ,IAAI,CAClC,GAAI,IAAa,GAAI,SACrB,IAAM,EAAW,EAAK,MAAM,EAAG,EAAS,CAAC,MAAM,CAAC,aAAa,CACvD,EAAQ,EAAK,MAAM,EAAW,EAAE,CAAC,MAAM,CACzC,GAAY,GACd,EAAa,KAAK,CAAE,WAAU,QAAO,CAAC,CAG1C,OAAO,EAOT,SAAgB,EACd,EACA,EACA,EAC2C,CAC3C,GAAM,CAAE,QAAO,iBAAkB,EAAS,EAAI,CAG1C,EAAuC,KACvC,EAAc,OAAS,IACzB,EAAc,SAAS,cAAc,QAAQ,CAC7C,EAAY,YAAc,EAAc,KAAK;EAAK,CAClD,SAAS,KAAK,YAAY,EAAY,EAIxC,IAAM,EAAY,EAAe,EAAM,CAGjC,EAAY,SAAS,cAAc,MAAM,CAC/C,EAAU,YAAY,EAAS,CAE/B,SAAS,EAAa,EAAa,EAA+C,CAChF,IAAM,EAAU,IAAI,IACd,EAAY,EAAG,aAAa,QAAQ,CAC1C,GAAI,MACG,IAAM,KAAK,EAAU,MAAM,MAAM,CAChC,GAAG,EAAQ,IAAI,EAAE,CAGzB,MAAO,CACL,QAAS,EAAG,QAAQ,aAAa,CACjC,UACA,SACA,KACD,CAGH,SAAS,EACP,EACA,EACA,EACY,CACZ,IAAM,EAAM,EAAG,QAAQ,aAAa,CAC9B,EAAM,EAAa,EAAI,EAAU,CAGjC,EAAQ,GAAc,CAGtB,EAAW,IAAI,IAKf,EAA8B,EAAE,CAChC,EAAO,IAAI,IAEX,EAAW,EAAU,MAAM,IAAI,EAAI,CACzC,GAAI,EAAU,IAAK,IAAM,KAAK,EAAY,EAAK,IAAI,EAAE,CAAE,EAAW,KAAK,EAAE,CAEzE,IAAK,IAAM,KAAO,EAAI,QAAS,CAC7B,IAAM,EAAW,EAAU,QAAQ,IAAI,EAAI,CAC3C,GAAI,MAAe,IAAM,KAAK,EACvB,EAAK,IAAI,EAAE,GAAI,EAAK,IAAI,EAAE,CAAE,EAAW,KAAK,EAAE,EAIvD,IAAK,IAAM,KAAK,EAAU,UACnB,EAAK,IAAI,EAAE,GAAI,EAAK,IAAI,EAAE,CAAE,EAAW,KAAK,EAAE,EAMrD,IAAM,EAAgC,EAAE,CAClC,EAAsC,EAAE,CAC9C,IAAK,IAAM,KAAa,EACtB,GAAI,EAAsB,EAAU,SAAU,EAAI,CAAE,CAClD,IAAM,EAAS,EAAU,SAAS,gBAAkB,SAAW,EAAgB,EAC/E,IAAK,IAAM,KAAQ,EAAU,aAC3B,EAAO,KAAK,CACV,SAAU,EAAK,SACf,MAAO,EAAK,MACZ,YAAa,EAAU,SAAS,KAChC,MAAO,EAAU,UACjB,UAAW,EAAK,UACjB,CAAC,CAMR,IAAM,EAAS,EAAa,GACxB,EAAc,GAClB,GAAI,GAAQ,WAAa,IAAA,GAAW,CAClC,IAAM,EAAM,EAAO,SACf,EAAM,GACR,EAAM,SAAW,EAAM,EAAY,SAEnC,EAAM,SAAW,EAEnB,EAAc,GACd,EAAS,IAAI,YAAY,CAIvB,EAAQ,OAAS,GACnB,EAAQ,MAAM,EAAG,IAAM,CACrB,GAAI,EAAE,YAAc,EAAE,UAAW,OAAO,EAAE,UAAY,EAAI,GAC1D,IAAM,EAAK,EAAE,YAAa,EAAK,EAAE,YAIjC,OAHI,EAAG,KAAO,EAAG,GACb,EAAG,KAAO,EAAG,GACb,EAAG,KAAO,EAAG,GACV,EAAE,MAAQ,EAAE,MADS,EAAG,GAAK,EAAG,GADX,EAAG,GAAK,EAAG,GADX,EAAG,GAAK,EAAG,IAIvC,CAIJ,IAAK,IAAM,KAAK,EACV,EAAE,WAAa,cACjB,EAAiB,EAAO,EAAE,SAAU,EAAE,MAAO,EAAY,SAAU,EAAgB,EAAY,UAAU,CACzG,EAAc,IAKlB,GAAI,aAAc,aAAe,EAAG,MAAM,QAAS,CACjD,IAAM,EAAc,EAAiB,EAAG,MAAM,QAAQ,CACtD,IAAK,IAAM,KAAQ,EACb,EAAK,WAAa,cACpB,EAAiB,EAAO,EAAK,SAAU,EAAK,MAAO,EAAY,SAAU,EAAgB,EAAY,UAAU,CAC/G,EAAc,IAMf,IACH,EAAM,SAAW,EAAY,UAI/B,IAAM,EAAe,EAAM,SAK3B,GAAI,EAAQ,CACV,IAAK,GAAM,CAAC,EAAK,KAAQ,OAAO,QAAQ,EAAO,CAAE,CAC/C,GAAI,IAAQ,WAAY,SACvB,EAAc,GAAO,EACtB,IAAM,EAAS,EAAI,QAAQ,SAAU,GAAK,IAAM,EAAE,aAAa,CAAC,CAChE,EAAS,IAAI,EAAO,CAIlB,EAAM,UAAY,IAAG,EAAM,UAAY,KAAK,IAAI,EAAM,UAAU,CAAG,GACnE,EAAM,aAAe,IAAG,EAAM,aAAe,KAAK,IAAI,EAAM,aAAa,CAAG,IAG5E,IAAQ,MAAQ,IAAQ,QACd,EAAY,YACZ,OACV,EAAM,aAAe,GACrB,EAAS,IAAI,gBAAgB,GAE7B,EAAM,YAAc,GACpB,EAAS,IAAI,eAAe,GAMlC,IAAM,EAAY,EAAY,UAGxB,EAAuC,CAC3C,YAAa,gBACd,CAGD,IAAK,IAAM,KAAK,EACV,EAAE,WAAa,cACnB,EAAiB,EAAO,EAAE,SAAU,EAAE,MAAO,EAAc,EAAgB,EAAU,CACrF,EAAS,IAAI,EAAa,EAAE,WAAa,EAAE,SAAS,EAItD,IAAM,EAAiB,aAAc,aAAe,CAAC,CAAC,EAAG,MAAM,MAC/D,GAAI,aAAc,aAAe,EAAG,MAAM,QAAS,CACjD,IAAM,EAAc,EAAiB,EAAG,MAAM,QAAQ,CACtD,IAAK,IAAM,KAAQ,EAAa,CAC9B,GAAI,EAAK,WAAa,YAAa,CACjC,EAAS,IAAI,YAAY,CACzB,SAEF,IAAM,EAAW,EAAgB,EAAK,SAAU,EAAK,MAAM,CAC3D,IAAK,IAAM,KAAO,EAChB,EAAiB,EAAO,EAAI,SAAU,EAAI,MAAO,EAAc,EAAgB,EAAU,CACzF,EAAS,IAAI,EAAa,EAAI,WAAa,EAAI,SAAS,EAMzD,IACH,EAAM,MAAQ,GAIhB,IAAM,EAAU,EAAG,aAAa,MAAM,CAClC,IACF,EAAM,UAAY,EAClB,EAAS,IAAI,YAAY,EAI3B,EAAS,IAAI,YAAY,CACzB,EAAY,EAAO,EAAa,EAAS,CAGpC,EAAS,IAAI,wBAAwB,GACxC,EAAM,oBAAsB,EAAM,OAE/B,EAAS,IAAI,4BAA4B,CAInC,EAAM,wBAA0B,iBACzC,EAAM,sBAAwB,EAAM,QAJhC,EAAM,wBAA0B,IAAM,EAAM,wBAA0B,kBACxE,EAAM,sBAAwB,EAAM,OAKxC,IAAK,IAAM,IAAQ,CAAC,MAAO,QAAS,SAAU,OAAO,CAAW,CAC9D,IAAM,EAAW,SAAS,EAAK,OACzB,EAAW,UAAU,EAAK,aAAa,CAAC,QACzC,EAAS,IAAI,EAAS,CAEf,EAAc,KAAc,iBACrC,EAAc,GAAY,EAAM,OAFhC,EAAc,GAAY,EAAM,MAOrC,IAAM,EAAU,IAAI,IAAI,EAAM,mBAAmB,MAAM,MAAM,CAAC,OAAO,GAAK,GAAK,IAAM,OAAO,CAAC,CAC7F,GAAI,EAAY,oBAAsB,EAAY,qBAAuB,WAClE,IAAM,KAAK,EAAY,mBAAmB,MAAM,MAAM,CACrD,GAAK,IAAM,QAAQ,EAAQ,IAAI,EAAE,CAGrC,EAAQ,KAAO,IACjB,EAAM,mBAAqB,CAAC,GAAG,EAAQ,CAAC,KAAK,IAAI,EAInD,IAAM,EAAS,EAAc,EAAI,EAAM,cAAc,CAQjD,EACA,EAAe,GACnB,GAAI,IAAQ,MAAQ,EAAc,OAAS,EAAG,CAExC,EAAc,OAAS,GACzB,EAAc,MAAM,EAAG,IAAM,CAC3B,GAAI,EAAE,YAAc,EAAE,UAAW,OAAO,EAAE,UAAY,EAAI,GAC1D,IAAM,EAAK,EAAE,YAAa,EAAK,EAAE,YAIjC,OAHI,EAAG,KAAO,EAAG,GACb,EAAG,KAAO,EAAG,GACb,EAAG,KAAO,EAAG,GACV,EAAE,MAAQ,EAAE,MADS,EAAG,GAAK,EAAG,GADX,EAAG,GAAK,EAAG,GADX,EAAG,GAAK,EAAG,IAIvC,CAOJ,IAAM,EAAmC,CACvC,cAAe,eACf,WAAY,aAAc,aAAc,YACxC,QAAS,gBACV,CACK,EAAU,CAAE,GAAG,EAAO,CACtB,EAAU,IAAI,IACpB,IAAK,IAAM,KAAK,EAAe,CAI7B,GAAI,EAAE,WAAa,UAAW,CAC5B,IAAM,EAAI,EAAE,MAAM,MAAM,CAAC,aAAa,EAClC,IAAM,QAAU,IAAM,MAAQ,IAAM,MAAQ,IAAM,YAEpD,EAAgB,IAAM,QAAU,IAAM,MAAQ,IAAM,MAEtD,SAEF,IAAM,EAAS,EAAQ,IAAI,GAAK,EAAQ,GAAG,CAC3C,EAAiB,EAAS,EAAE,SAAU,EAAE,MAAO,EAAc,EAAgB,EAAU,CACvF,EAAQ,SAAS,EAAG,IAAM,CACpB,EAAQ,KAAO,EAAO,IAAI,EAAQ,IAAI,EAAE,EAC5C,CAEJ,GAAI,EAAQ,KAAO,EAAG,CACpB,EAAc,EAAE,CAChB,IAAK,IAAM,KAAK,EAAU,EAAoB,GAAK,EAAQ,IAK/D,IAAM,EAAyB,EAAE,CACjC,IAAK,IAAM,KAAS,EAAG,WAAY,CACjC,IAAM,EAAY,EAAS,EAAO,EAAO,EAAI,CACzC,GAAW,EAAS,KAAK,EAAU,CAGzC,MAAO,CACL,QAAS,EACT,QAAS,EACT,QACA,WACA,YAAa,KACb,WAAY,EACZ,cACA,aAAc,GAAgB,IAAA,GAC/B,CAGH,SAAS,EACP,EACA,EACA,EACmB,CACnB,GAAI,EAAK,WAAa,KAAK,UAAW,CACpC,IAAM,EAAO,EAAK,YAClB,GAAI,CAAC,EAAM,OAAO,KAElB,GAAI,EAAK,MAAM,GAAK,IAAM,CAAC,EAAK,SAAS,OAAS,CAAE,CAClD,IAAM,EAAK,EAAY,WACjB,EAAO,EAAK,gBACZ,EAAO,EAAK,YACZ,EAAmB,GAAmB,CAC1C,GAAI,CAAC,GAAK,EAAE,WAAa,KAAK,aAAc,OAAO,GAAG,WAAa,KAAK,UAGxE,IAAM,EADM,EADC,EAAc,QAAQ,aAAa,GAEjC,SAAW,QAC1B,OAAO,IAAM,UAAY,IAAM,gBAWjC,GARI,GAAQ,GAAQ,CAAC,EAAgB,EAAK,EAAI,CAAC,EAAgB,EAAK,EAC9D,MAAO,OAAS,IAAO,YAAc,IAAO,aAO9C,IAAO,OAAS,IAAO,YAAc,IAAO,YAC1C,EAAK,SAAS;EAAK,CAAE,OAAO,KAKpC,IAAM,EAAQ,CAAE,GAAG,EAAa,CAO1B,EAAK,EAAY,WACnB,EAAiB,EAKrB,OAJI,IAAO,OAAS,IAAO,YAAc,IAAO,YAAc,IAAO,iBACnE,EAAiB,EAAK,QAAQ,UAAW,IAAI,EAGxC,CACL,QAAS,KACT,QAAS,QACT,QACA,SAAU,EAAE,CACZ,YAAa,EACd,CAGH,GAAI,EAAK,WAAa,KAAK,aAAc,OAAO,KAEhD,IAAM,EAAK,EACL,EAAM,EAAG,QAAQ,aAAa,CAcpC,OAbI,IAAQ,SAAW,IAAQ,SAAiB,KAG5C,IAAQ,KACH,CACL,QAAS,KACT,QAAS,QACT,MAAO,CAAE,GAAG,EAAa,CACzB,SAAU,EAAE,CACZ,YAAa;EACd,CAGI,EAAe,EAAI,EAAa,EAAU,CAUnD,MAAO,CAAE,KANI,EAAe,EADV,GAAc,CACkB,KAAK,CAMxC,YAJO,CAChB,GAAa,EAAY,QAAQ,EAGf,CC76C1B,IAAI,EAAsB,GACtB,EAIA,EAAuB,EAAE,CAKvB,EAAgB,IAAI,IAE1B,SAAS,EAAmB,EAA+B,EAAsB,CAG/E,IAAM,EAAM,EAAI,KAAO,MAAQ,EAAI,eAAiB,IAAM,KAAO,EAC3D,EAAS,EAAc,IAAI,EAAI,CACrC,GAAI,IAAW,IAAA,GAAW,OAAO,EACjC,IAAM,EAAI,EAAI,YAAY,EAAK,CAAC,MAEhC,OADA,EAAc,IAAI,EAAK,EAAE,CAClB,EAOT,SAAS,EAAc,EAAwB,CAC7C,IAAI,EAAO,GACX,IAAK,IAAM,KAAK,EAAO,CACrB,GAAI,CAAC,EAAE,MAAQ,EAAE,QAAS,SAC1B,IAAM,EAAI,EAAgB,EAAE,MAAM,CAClC,GAAI,GAAQ,IAAM,EAAM,MAAO,GAC/B,EAAO,EAET,MAAO,GAQT,SAAgB,EAAU,EAA+B,EAA4B,CACnF,EAAI,KAAO,EAAgB,EAAM,CACjC,EAAI,YAAc,EAAM,cAAgB,OAAS,OAAS,SAI5D,SAAS,EAAoB,EAAuB,CAMlD,OAAO,OAAO,SAAS,EAAM,EAAI,IAAU,EAAI,GAAG,EAAM,IAAM,MAMhE,IAAM,EAAmB,IAAI,IAC7B,SAAgB,EAAgB,EAA8B,CAC5D,IAAM,EAAM,GAAG,EAAM,UAAU,GAAG,EAAM,gBAAgB,GAAG,EAAM,WAAW,GAAG,EAAM,SAAS,GAAG,EAAM,aACjG,EAAS,EAAiB,IAAI,EAAI,CACxC,GAAI,EAAQ,OAAO,EACnB,IAAM,EAAkB,EAAE,CAEtB,EAAM,YAAc,UAAU,EAAM,KAAK,EAAM,UAAU,CACzD,EAAM,kBAAoB,cAAc,EAAM,KAAK,aAAa,CAChE,EAAM,aAAe,KAAK,EAAM,KAAK,OAAO,EAAM,WAAW,CAAC,CAClE,EAAM,KAAK,GAAG,EAAM,SAAS,IAAI,CACjC,EAAM,KAAK,EAAM,WAAW,CAC5B,IAAM,EAAS,EAAM,KAAK,IAAI,CAE9B,OADA,EAAiB,IAAI,EAAK,EAAO,CAC1B,EAOT,IAAM,EAAmB,IAAI,IAMzB,EAAqC,KACrC,EAA6C,KAC7C,EAAmC,KAEjC,EAAiB,IAAI,IAAI,CAAC,OAAQ,SAAU,SAAS,CAAC,CAQ5D,SAAS,GAAqB,EAAc,EAAoB,EAAiB,GAAe,CAC9F,IAAM,EAAM,GAAG,EAAK,GAAG,EAAW,GAAG,EAAiB,QAAU,UAC1D,EAAS,EAAiB,IAAI,EAAI,CACxC,GAAI,IAAW,IAAA,GAAW,OAAO,EAEjC,IAAI,EACA,GACG,IACH,EAAoB,SAAS,cAAc,KAAK,CAChD,EAAkB,MAAM,QACtB,4GACF,EAAa,SAAS,cAAc,KAAK,CACzC,EAAW,MAAM,QAAU,kDAC3B,EAAW,YAAc,KACzB,EAAkB,YAAY,EAAW,CACzC,SAAS,KAAK,YAAY,EAAkB,EAE9C,EAAQ,IAEH,IACH,EAAc,SAAS,cAAc,MAAM,CAC3C,EAAY,MAAM,QAChB,+GACF,EAAY,YAAc,KAC1B,SAAS,KAAK,YAAY,EAAY,EAExC,EAAQ,GAGV,EAAM,MAAM,KAAO,EACnB,EAAM,MAAM,WAAa,EACzB,IAAM,EAAS,EAAM,uBAAuB,CAAC,OAG7C,OADA,EAAiB,IAAI,EAAK,EAAO,CAC1B,EAQT,SAAS,EAAc,EAA+B,EAAsB,EAAiB,GAAe,CAC1G,GAAI,EAAM,WAAa,EAMrB,OALI,EAEK,GADM,EAAgB,EAAM,CACD,GAAG,EAAM,WAAW,IAAK,EAAe,CAGrE,EAAM,WAGf,GAAI,EAEF,OAAO,GADM,EAAgB,EAAM,CACD,SAAU,EAAe,CAM7D,GAAM,CAAE,SAAQ,WAAY,EAAe,EAAK,EAAM,CACtD,OAAO,EAAS,EAOlB,SAAS,GAAiB,EAA+B,EAAsB,EAA4B,CACzG,GAAM,CAAE,SAAQ,WAAY,EAAe,EAAK,EAAM,CACtD,OAAQ,EAAS,GAAW,EAAI,EAAa,EAG/C,SAAS,GAAmB,EAAc,EAA2B,CAEnE,OAAQ,EAAR,CACE,IAAK,YAAa,OAAO,EAAK,aAAa,CAC3C,IAAK,YAAa,OAAO,EAAK,aAAa,CAG3C,IAAK,aAAc,OAAO,EAAK,QAAQ,0BAA2B,EAAG,EAAG,IACtE,IAAM,KAAO,IAAM,IAAM,EAAI,EAAI,EAAE,aAAa,CAAC,CACnD,QAAS,OAAO,GAIpB,SAAS,EAAS,EAA2B,CAC3C,GAAI,EAAK,UAAY,QAAS,MAAO,GACrC,IAAM,EAAI,EAAK,MAAM,QACrB,OAAO,IAAM,UAAY,IAAM,eAGjC,SAAS,GAAsB,EAA2B,CACxD,OAAO,EAAK,SAAS,OAAS,GAAK,EAAK,SAAS,MAAM,EAAS,CAGlE,SAAgB,GAAc,EAAwB,CACpD,MAAO,CAAC,GAAS,IAAU,eAAiB,IAAU,mBAMxD,IAAM,EAAoB,IAAI,IAC9B,SAAgB,EAAe,EAA+B,EAA2D,CACvH,IAAM,EAAO,EAAgB,EAAM,CAC7B,EAAS,EAAkB,IAAI,EAAK,CAC1C,GAAI,EAAQ,OAAO,EACnB,EAAI,KAAO,EACX,IAAM,EAAI,EAAI,YAAY,IAAI,CAGxB,EAAS,CAAE,OAFF,EAAE,uBAAyB,EAAE,wBAEnB,QADT,EAAE,wBAA0B,EAAE,yBACZ,CAElC,OADA,EAAkB,IAAI,EAAM,EAAO,CAC5B,EAeT,SAAS,GACP,EACA,EAAiB,EACjB,EAAwB,EAAmB,EAC3C,EACQ,CACR,OAAQ,EAAR,CACE,IAAK,QAAS,MAAO,CAAC,EAAiB,GACvC,IAAK,MAAO,OAAO,EAAiB,IACpC,IAAK,WAAY,MAAO,EAAE,EAAY,GACtC,IAAK,cAAe,OAAO,EAAa,EACxC,IAAK,SAAU,MAAO,EAAE,EAAiB,MAAS,EAAW,GAAW,EACxE,QAAS,CAGP,IAAM,EAAI,WAAW,EAAG,CAExB,OADK,OAAO,SAAS,EAAE,CAChB,EAAG,SAAS,IAAI,CAAG,EAAE,EAAI,KAAO,EAAa,CAAC,EADrB,IAOtC,SAAS,EAAgB,EAAqB,CAC5C,OAAO,IAAO,YAAc,IAAO,OAAS,IAAO,UAAY,IAAO,GAMxE,SAAS,GAAc,EAAkB,EAA2B,CAClE,OAAO,EAAE,aAAe,EAAE,YACxB,EAAE,WAAa,EAAE,UACjB,EAAE,aAAe,EAAE,YACnB,EAAE,YAAc,EAAE,WAClB,EAAE,QAAU,EAAE,OACd,EAAE,qBAAuB,EAAE,oBAC3B,EAAE,kBAAoB,EAAE,gBAG5B,SAAS,GAAoB,EAA+B,CAM1D,MALI,CAAC,GAAc,EAAM,gBAAgB,EACrC,EAAM,eAAiB,GAAK,EAAM,iBAAmB,QACrD,EAAM,iBAAmB,GAAK,EAAM,mBAAqB,QACzD,EAAM,kBAAoB,GAAK,EAAM,oBAAsB,QAC3D,EAAM,gBAAkB,GAAK,EAAM,kBAAoB,OAiD7D,SAAS,GAAoB,EAAkB,CAC7C,MAAO,CAAC,EAAE,EAAE,SAAW,EAAE,UAAY,EAAE,MAsBzC,SAAS,GACP,EACA,EACA,EACM,CAIN,IAAI,EAAW,EAAK,MAAM,OAAS,EACnC,KACE,GAAY,IACX,EAAK,MAAM,GAAU,OAAS,IAAM,GAAoB,EAAK,MAAM,GAAU,GAC9E,IACF,GAAI,EAAW,EAAG,OAClB,IAAM,EAAY,EAAK,MAAM,GAAU,MACjC,EAAW,EAAK,MAAM,GAAU,SACtC,EAAU,EAAK,EAAU,CAGzB,EAAI,cAAgB,GAAG,EAAU,eAAiB,EAAE,IACpD,IAAM,EAAgB,EAAmB,EAAK,IAAI,CAK5C,MAA0B,CAC9B,KACE,EAAK,MAAM,OAAS,GACpB,EAAK,MAAM,EAAK,MAAM,OAAS,GAAG,SAClC,CACA,IAAM,EAAI,EAAK,MAAM,KAAK,CAC1B,EAAK,YAAc,EAAE,QAKzB,GAAmB,CAInB,IAAM,EAAmB,GACvB,CAAC,EAAE,SAAW,EAAE,OAAS,IAAM,CAAC,EAAE,SAAW,CAAC,EAAE,SAClD,KACE,EAAK,WAAa,EAAgB,GAClC,EAAK,MAAM,OAAS,GACpB,CACA,IAAM,EAAO,EAAK,MAAM,EAAK,MAAM,OAAS,GAC5C,GAAI,CAAC,EAAgB,EAAK,EAAI,CAAC,GAAoB,EAAK,CAAE,MAC1D,EAAK,YAAc,EAAK,MACxB,EAAK,MAAM,KAAK,CAChB,GAAmB,CAKrB,IAAM,EAAqB,CACzB,KAAM,IACN,MAAO,EACP,MAAO,EACP,QAAS,GACT,WACD,CACD,EAAK,MAAM,KAAK,EAAa,CAC7B,EAAK,YAAc,EAUrB,SAAS,GAAgB,EAA6B,CACpD,IAAM,EAAkB,EAAE,CAE1B,SAAS,EAAK,EAAe,EAA0B,CACrD,GAAI,EAAE,UAAY,SAAW,EAAE,YAAa,CAC1C,EAAK,KAAK,CAAE,KAAM,EAAE,YAAa,MAAO,EAAE,MAAO,WAAU,CAAC,CAC5D,OAEF,IAAM,EAAgB,EAAE,MAAM,UAAY,eAEpC,EAAQ,GAAkB,EAAS,EAAE,EAAI,GAAoB,EAAE,MAAM,CACrE,EAAc,EAAQ,EAAE,MAAQ,EAChC,EAAkB,IAAU,EAAE,MAAM,YAAc,GAAK,EAAE,MAAM,aAAe,GAClF,EAAE,MAAM,gBAAkB,GAAK,EAAE,MAAM,iBAAmB,GAE5D,GAAI,EAAe,CAIjB,IAAM,EAAU,EAAE,SAAS,aAAe,GAC1C,EAAK,KAAK,CACR,KAAM,EACN,MAAO,EAAE,MACT,SAAU,EAEV,QAAS,EAAE,MACX,SAAU,EAAE,MACb,CAAC,CACF,OAMF,IAAM,EAAK,EAAE,MAAM,YACb,GAAe,IAAO,iBAAmB,IAAO,qBACpD,EAAE,MAAM,YAAc,MAClB,EAAgB,EAAK,OAEvB,GACF,EAAK,KAAK,CAAE,KAAM,GAAI,MAAO,EAAE,MAAO,SAAU,EAAa,QAAS,EAAE,MAAO,CAAC,CAGlF,IAAK,IAAM,KAAS,EAAE,SACpB,EAAK,EAAO,EAAQ,EAAc,EAAS,CAO7C,GAJI,GACF,EAAK,KAAK,CAAE,KAAM,GAAI,MAAO,EAAE,MAAO,SAAU,EAAa,SAAU,EAAE,MAAO,CAAC,CAG/E,GAAe,EAAK,OAAS,EAAe,CAC9C,IAAM,EAAM,EAAK,OAAO,EAAc,CACtC,IAAK,IAAM,KAAK,EACV,EAAE,OACJ,EAAE,KAAO,CAAC,GAAG,EAAE,KAAK,CAAC,SAAS,CAAC,KAAK,GAAG,CAIvC,EAAE,MAAQ,CAAE,GAAG,EAAE,MAAO,UAAW,MAAO,EAG9C,EAAI,SAAS,CACb,EAAK,KAAK,GAAG,EAAI,EAIrB,IAAK,IAAM,KAAS,EAAK,SACvB,EAAK,EAAM,CAEb,OAAO,EAOT,SAAS,EAAe,EAAuB,CAC7C,IAAK,IAAI,EAAI,EAAG,EAAI,EAAK,OAAQ,IAAK,CACpC,IAAM,EAAO,EAAK,YAAY,EAAE,CAChC,GACG,GAAQ,MAAU,GAAQ,MAC1B,GAAQ,MAAU,GAAQ,MAC1B,GAAQ,MAAU,GAAQ,MAC1B,GAAQ,MAAU,GAAQ,KAC3B,MAAO,GACL,EAAO,OAAQ,IAErB,MAAO,GAGT,IAAI,EACJ,SAAS,IAAsC,CAM7C,OALI,IACA,OAAO,KAAS,KAAe,KAAK,WACtC,EAAa,IAAI,KAAK,UAAU,IAAA,GAAW,CAAE,YAAa,OAAQ,CAAC,CAC5D,GAEF,MAMT,SAAS,EAAe,EAA+B,EAAc,EAAc,EAAkB,EAAwD,CAI3J,GAAI,EAAK,SAAS,IAAS,EAAI,EAAK,SAAS,IAAS,CAAE,CACtD,IAAM,EAAQ,EAAK,MAAM,kBAAkB,CAErC,EAAc,GAAY,CAAE,QAAS,GAAI,SAAU,EAAG,CACxD,EAAmB,GACvB,IAAK,IAAM,KAAQ,EAAO,CACxB,GAAI,IAAS,IAAU,CACrB,EAAmB,GACnB,SAEF,GAAI,IAAS,KAAY,IAAS,GAAI,CACpC,EAAmB,GACnB,SAEF,IAAM,EAAU,EAAS,OACzB,EAAe,EAAK,EAAM,EAAK,EAAU,EAAY,CACjD,GAAoB,EAAU,IAChC,EAAS,EAAU,GAAG,kBAAoB,IAE5C,EAAmB,GAEjB,GAAoB,EAAS,OAAS,IACxC,EAAS,EAAS,OAAS,GAAG,kBAAoB,IAEpD,OAUF,GAJmB,EAAI,MAAM,aAAe,OAC1C,EAAI,MAAM,aAAe,YACzB,EAAI,MAAM,aAAe,eAEX,CAEd,IAAM,EAAQ,EAAK,MAAM,UAAU,CAC7B,EAAkB,EAAmB,EAAK,IAAI,CAAG,EACvD,IAAK,IAAM,KAAK,EAAO,CACrB,GAAI,IAAM,GAAI,SACd,GAAI,IAAM,IAAM,CAEd,EAAS,KAAK,CACZ,KAAM,IACN,MAAO,EACP,MAAO,EAAI,MACX,QAAS,GACT,MAAO,GACP,SAAU,EAAI,SACf,CAAC,CACF,SAEF,IAAM,EAAU,OAAO,KAAK,EAAE,CAC9B,EAAS,KAAK,CACZ,KAAM,EACN,MAAO,EAAmB,EAAK,EAAE,CACjC,MAAO,EAAI,MACX,UACA,SAAU,EAAI,SACf,CAAC,MAEC,CAQL,IAAM,EAAQ,EACX,MAAM,mBAAmB,CACzB,QAAS,GACR,mBAAmB,KAAK,EAAE,CAAG,CAAC,EAAE,CAAG,EAAE,MAAM,eAAe,CAC3D,CAMC,EAAU,GAAU,SAAW,GAC/B,EAAW,GAAU,UAAY,EAErC,IAAK,IAAM,KAAK,EAAO,CACrB,GAAI,IAAM,GAAI,SAGd,GAFgB,mBAAmB,KAAK,EAAE,CAE7B,CACX,IAAM,EAAU,EAChB,GAAW,IACX,EAAW,EAAI,YAAY,EAAQ,CAAC,MACpC,IAAM,EAAa,EAAW,GAAW,EAAI,MAAM,aAAe,GAClE,EAAS,KAAK,CACZ,KAAM,IACN,MAAO,EACP,MAAO,EAAI,MACX,QAAS,GACT,SAAU,EAAI,SACf,CAAC,CACF,SAIF,GAAI,EAAe,EAAE,CAAE,CACrB,IAAM,EAAY,IAAc,CAChC,GAAI,EAAW,CACb,IAAK,IAAM,KAAO,EAAU,QAAQ,EAAE,CAAE,CACtC,IAAM,EAAI,EAAI,QACR,EAAU,EAChB,GAAW,EACX,EAAW,EAAI,YAAY,EAAQ,CAAC,MACpC,EAAS,KAAK,CACZ,KAAM,EACN,MAAO,EAAW,EAClB,MAAO,EAAI,MACX,QAAS,GACT,SAAU,EAAI,SACf,CAAC,CAEJ,UAIJ,IAAM,EAAU,EAChB,GAAW,EACX,EAAW,EAAI,YAAY,EAAQ,CAAC,MACpC,IAAI,EAAQ,EAAW,EACjB,EAAc,EAAmB,EAAK,EAAE,CAC1C,GACF,EAAO,CACL,KAAM,eACN,QAAS,IAAI,EAAE,UAAU,EAAM,QAAQ,EAAE,CAAC,UAAU,EAAY,QAAQ,EAAE,CAAC,SAAS,EAAQ,GAAa,QAAQ,EAAE,CAAC,YAAY,EAAQ,GACxI,KAAM,CAAE,KAAM,EAAG,WAAY,EAAO,cAAa,WAAU,UAAS,KAAM,EAAI,MAAM,WAAY,SAAU,EAAI,MAAM,SAAU,CAC/H,CAAC,CAEJ,EAAS,KAAK,CACZ,KAAM,EACN,QACA,MAAO,EAAI,MACX,QAAS,GACT,SAAU,EAAI,SACf,CAAC,CAIA,IACF,EAAS,QAAU,EACnB,EAAS,SAAW,IAQ1B,SAAS,GAAa,EAA+B,EAAyB,CAC5E,IAAM,EAAmB,EAAE,CAE3B,IAAK,IAAM,KAAO,EAAM,CAEtB,GAAI,EAAI,OAAS,IAAM,CAAC,EAAI,SAAW,CAAC,EAAI,SAAU,CACpD,IAAM,EAAS,EAAI,MAAM,UAAY,iBAChC,EAAI,MAAM,YAAc,EAAI,MAAM,cACnC,EACA,EAAS,GACX,EAAS,KAAK,CAAE,KAAM,GAAI,MAAO,EAAQ,MAAO,EAAI,MAAO,QAAS,GAAO,SAAU,EAAI,SAAU,CAAC,CAEtG,SAKF,GAAI,EAAI,SAAW,EAAI,UAAY,EAAI,KAAM,CAC3C,EAAU,EAAK,EAAI,MAAM,CACzB,EAAI,cAAgB,EAAoB,EAAI,MAAM,cAAc,CAChE,IAAM,EAAO,GAAmB,EAAI,KAAM,EAAI,MAAM,cAAc,CAC5D,EAAI,EAAI,MACR,EAAY,EAAmB,EAAK,EAAK,CACzC,EAAa,EAAE,WAAa,EAAE,gBAAkB,EAAE,YACtD,EAAY,EAAE,aAAe,EAAE,iBAAmB,EAAE,YACtD,EAAS,KAAK,CACZ,OACA,MAAO,EACP,MAAO,EAAI,MACX,QAAS,GACT,SAAU,EAAI,SACd,QAAS,EAAI,QACb,SAAU,EAAI,SACf,CAAC,CACF,SAIF,GAAI,EAAI,QAAS,CACf,IAAM,EAAM,EAAI,QAAQ,YAAc,EAAI,QAAQ,gBAC9C,EAAM,GACR,EAAS,KAAK,CAAE,KAAM,GAAI,MAAO,EAAK,MAAO,EAAI,MAAO,QAAS,GAAO,SAAU,EAAI,SAAU,QAAS,EAAI,QAAS,CAAC,CAEzH,SAEF,GAAI,EAAI,SAAU,CAChB,IAAM,EAAM,EAAI,SAAS,aAAe,EAAI,SAAS,iBACjD,EAAM,GACR,EAAS,KAAK,CAAE,KAAM,GAAI,MAAO,EAAK,MAAO,EAAI,MAAO,QAAS,GAAO,SAAU,EAAI,SAAU,SAAU,EAAI,SAAU,CAAC,CAE3H,SAGF,EAAU,EAAK,EAAI,MAAM,CACzB,EAAI,cAAgB,EAAoB,EAAI,MAAM,cAAc,CAChE,IAAM,EAAO,GAAmB,EAAI,KAAM,EAAI,MAAM,cAAc,CAQ5D,EAAY,GAAqB,CACrC,IAAM,EAAQ,EAAS,GACvB,GAAI,CAAC,GAAS,EAAM,SAAW,CAAC,EAAM,MAAQ,EAAM,OAAS;EAAM,OACnE,IAAM,EAAO,EAAS,EAAW,GACjC,GACE,CAAC,GAAQ,EAAK,SAAW,CAAC,EAAK,KAAK,MAAM,EAC1C,EAAK,SAAW,EAAK,SACrB,OAKF,IAAM,EAAY,CAAC,GAAG,EAAM,KAAK,CAAC,GAC5B,EAAW,CAAC,GAAG,EAAK,KAAK,CAAC,EAAK,KAAK,OAAS,GAEjD,EAAM,EAAU,EAAI,EAAM,EAAS,EACnC,EAAe,EAAM,KAAK,EAAI,EAAe,EAAK,KAAK,GAEzD,EAAM,cAAgB,KAIxB,GAAI,EAAK,SAAS;EAAK,CAAE,CACvB,IAAM,EAAQ,EAAK,MAAM;EAAK,CAC9B,IAAK,IAAI,EAAI,EAAG,EAAI,EAAM,OAAQ,IAIhC,GAHI,EAAI,GACN,EAAS,KAAK,CAAE,KAAM;EAAM,MAAO,EAAG,MAAO,EAAI,MAAO,QAAS,GAAO,SAAU,EAAI,SAAU,CAAC,CAE/F,EAAM,GAAI,CACZ,IAAM,EAAW,EAAS,OAC1B,EAAe,EAAK,EAAM,GAAI,EAAK,EAAS,CAC5C,EAAS,EAAS,MAGjB,CACL,IAAM,EAAW,EAAS,OAC1B,EAAe,EAAK,EAAM,EAAK,EAAS,CACxC,EAAS,EAAS,EAItB,OAAO,EAMT,SAAS,EAAM,EAAuB,CACpC,IAAM,EAAO,EAAK,YAAY,EAAE,EAAI,EACpC,OACG,GAAQ,OAAU,GAAQ,OAC1B,GAAQ,OAAU,GAAQ,OAC1B,GAAQ,OAAU,GAAQ,OAC1B,GAAQ,OAAU,GAAQ,OAC1B,GAAQ,OAAU,GAAQ,OAC1B,GAAQ,OAAU,GAAQ,OAC1B,GAAQ,OAAU,GAAQ,OAC1B,GAAQ,QAAW,GAAQ,OAIhC,IAAI,EACJ,SAAS,IAA8C,CAMrD,OALI,IACA,OAAO,KAAS,KAAe,KAAK,WACtC,EAAqB,IAAI,KAAK,UAAU,IAAA,GAAW,CAAE,YAAa,WAAY,CAAC,CACxE,GAEF,MAGT,IAAM,EAAqB,6BAO3B,SAAS,GAAe,EAAoB,CAC1C,IAAK,IAAM,KAAM,EAEf,GADW,EAAG,YAAY,EAAE,EAClB,OAAS,MAAO,GAK5B,OAHI,EAAE,SAAS,IAAS,EAAI,EAAE,SAAS,IAAS,CACvC,EAAmB,KAAK,EAAE,CAE5B,GAOT,SAAS,EACP,EACA,EACA,EACA,EACQ,CAER,IAAM,EAAS,CAAC,GAAG,EAAK,KAAK,CAAC,KAAK,EAAM,CAKnC,EAAW,EAAmB,KAAK,EAAK,KAAK,EAAI,CAAC,CAAC,IAAsB,CAGzE,EAAa,EAAK,MAAQ,IAC7B,EAAK,MAAM,eAAiB,cAAgB,EAAK,MAAM,YAAc,aAExE,GAAI,CAAC,GAAU,CAAC,GAAY,CAAC,EAAY,MAAO,CAAC,EAAK,CAQtD,GAAI,GAAc,EAAK,MAAM,YAAc,aACvC,EAAK,MAAM,eAAiB,aAAc,CAC5C,IAAM,EAAW,EAAK,KAAK,MAAM,2BAA2B,CAAC,OAAQ,GAAM,EAAE,OAAO,CACpF,GAAI,EAAS,OAAS,EAAG,CACvB,EAAI,KAAO,EAAgB,EAAK,MAAM,CACtC,EAAI,cAAgB,EAAoB,EAAK,MAAM,cAAc,CACjE,IAAM,EAAc,EAAE,CACtB,IAAK,IAAM,KAAW,EAAU,CAC9B,IAAM,EAAW,EAAmB,EAAK,EAAQ,CAC7C,GAAY,EACd,EAAI,KAAK,CAAE,GAAG,EAAM,KAAM,EAAS,MAAO,EAAU,CAAC,CAGrD,EAAI,KAAK,GAAG,EAAkB,EAAK,CAAE,GAAG,EAAM,KAAM,EAAS,MAAO,EAAU,CAAE,EAAc,EAAE,CAAC,CAGrG,OAAO,GAOX,EAAI,KAAO,EAAgB,EAAK,MAAM,CAGtC,EAAI,cAAgB,EAAoB,EAAK,MAAM,cAAc,CAGjE,IAAM,EAAM,EAAW,IAAsB,CAAG,KAC1C,EAAQ,EACV,CAAC,GAAG,EAAI,QAAQ,EAAK,KAAK,CAAC,CAAC,IAAK,GAAM,EAAE,QAAQ,CACjD,CAAC,GAAG,EAAK,KAAK,CACZ,EAAiB,EAAE,CAErB,EAAU,GACV,EAAe,EAEnB,IAAK,IAAM,KAAQ,EAAO,CAGxB,GAAI,GAAY,GAAe,EAAK,CAAE,CAChC,IACF,EAAO,KAAK,CAAE,GAAG,EAAM,KAAM,EAAS,MAAO,EAAc,CAAC,CAC5D,EAAU,GACV,EAAe,GAEjB,EAAO,KAAK,CAAE,GAAG,EAAM,KAAM,EAAM,MAAO,EAAmB,EAAK,EAAK,CAAE,CAAC,CAC1E,SAIF,GAAI,EAAM,EAAK,CAAE,CACX,IACF,EAAO,KAAK,CAAE,GAAG,EAAM,KAAM,EAAS,MAAO,EAAc,CAAC,CAC5D,EAAU,GACV,EAAe,GAEjB,IAAM,EAAY,EAAmB,EAAK,EAAK,CAC/C,EAAO,KAAK,CAAE,GAAG,EAAM,KAAM,EAAM,MAAO,EAAW,CAAC,CACtD,SAIF,IAAM,EAAgB,EAAU,EAC1B,EAAiB,EAAmB,EAAK,EAAc,CAG7D,GAAI,GAAc,EAAiB,GAAgB,EAAS,CAC1D,EAAO,KAAK,CAAE,GAAG,EAAM,KAAM,EAAS,MAAO,EAAc,CAAC,CAC5D,EAAU,EACV,EAAe,EAAmB,EAAK,EAAK,CAC5C,SAGF,EAAU,EACV,EAAe,EAOjB,OAJI,GACF,EAAO,KAAK,CAAE,GAAG,EAAM,KAAM,EAAS,MAAO,EAAc,CAAC,CAGvD,EAIT,IAAM,GAAiB,yBAMvB,SAAS,GACP,EACA,EACA,EACA,EACA,EAAiB,GACjB,EAAa,EACK,CAClB,IAAM,EAA0B,EAAE,CAC9B,EAA8B,CAAE,MAAO,EAAE,CAAE,WAAY,EAAG,WAAY,EAAG,CACvE,EAAS,IAAe,UAAY,IAAe,MAEnD,MAAiB,GAAgB,EAAM,SAAW,EAAI,EAAa,GAEnE,EAAY,IAAe,YAAc,IAAe,OAAS,IAAe,WAGhF,EACJ,IAAe,OAAS,IAAe,YAAc,IAAe,eAEtE,SAAS,EAAS,EAAa,GAAO,CACpC,IAAM,EAAW,EAAY,MAAM,OAAS,EAM5C,GAAI,EAFqB,IAAe,gBAClC,GAAuB,CAAC,GAE5B,KAAO,EAAY,MAAM,OAAS,GAAK,EAAY,MAAM,EAAY,MAAM,OAAS,GAAG,SACrF,EAAY,YAAc,EAAY,MAAM,EAAY,MAAM,OAAS,GAAG,MAC1E,EAAY,MAAM,KAAK,CAK3B,GAAI,GAAc,EAAY,MAAM,OAAS,EAAG,CAC9C,IAAM,EAAW,EAAY,MAAM,EAAY,MAAM,OAAS,GAC9D,GAAI,EAAS,kBAAmB,CAC9B,EAAU,EAAK,EAAS,MAAM,CAC9B,IAAM,EAAc,EAAmB,EAAK,IAAI,CAChD,EAAY,MAAM,KAAK,CACrB,KAAM,IACN,MAAO,EACP,MAAO,EAAS,MAChB,QAAS,GACV,CAAC,CACF,EAAY,YAAc,GAI9B,GAAI,EAAY,MAAM,OAAS,GAAM,GAAY,EAAY,CAC3D,GAAI,EAAQ,CACV,IAAM,EAAO,EAAY,MAAM,IAAI,GAAK,EAAE,KAAK,CAAC,KAAK,GAAG,CACxD,EAAO,CACL,KAAM,cACN,QAAS,QAAQ,EAAM,OAAO,KAAK,EAAK,UAAU,EAAY,WAAW,QAAQ,EAAE,CAAC,KAAK,IACzF,KAAM,CAAE,UAAW,EAAM,OAAQ,OAAM,WAAY,EAAY,WAAY,eAAc,CAC1F,CAAC,CAEJ,EAAM,KAAK,EAAY,CAEzB,EAAc,CAAE,MAAO,EAAE,CAAE,WAAY,EAAG,WAAY,EAAG,CAG3D,IAAI,EAAiB,GAErB,IAAK,IAAI,EAAY,EAAG,EAAY,EAAM,OAAQ,IAAa,CAC7D,IAAM,EAAO,EAAM,GACf,EAAiB,EAAc,EAAK,EAAK,MAAO,EAAe,CAEnE,GAAI,EAAK,UAAY,EAAK,SAAS,UAAY,eAAgB,CAC7D,IAAM,EAAK,EAAK,SAChB,EAAiB,KAAK,IAAI,EACxB,EAAiB,EAAG,WAAa,EAAG,cAAgB,EAAG,UAAY,EAAG,aACpE,EAAG,eAAiB,EAAG,kBAAkB,CAG/C,GAAI,EAAK,OAAS;EAAM,CAClB,EAAY,MAAM,SAAW,GAC/B,EAAY,WAAa,EACzB,EAAY,iBAAmB,GAC/B,EAAM,KAAK,EAAY,CACvB,EAAc,CAAE,MAAO,EAAE,CAAE,WAAY,EAAG,WAAY,EAAG,GAEzD,EAAY,iBAAmB,GAC/B,GAAU,EAEZ,EAAiB,GACjB,SAIF,GAAI,EAAQ,CACV,EAAY,MAAM,KAAK,EAAK,CAC5B,EAAY,YAAc,EAAK,MAC/B,EAAY,WAAa,KAAK,IAAI,EAAY,WAAY,EAAe,CACzE,SAUF,GAAI,CAAC,EAAK,SAAW,EAAK,MAAQ,CAAC,EAAK,eAAiB,CAAC,EAAK,SAAW,CAAC,EAAK,SAAU,CACxF,IAAI,EAAM,EACV,KAAO,EAAM,EAAI,EAAM,QAAQ,CAC7B,IAAM,EAAK,EAAM,EAAM,GACvB,GAAI,CAAC,EAAG,MAAQ,EAAG,SAAW,EAAG,SAAW,EAAG,UAAY,CAAC,EAAG,cAAe,MAC9E,IAEF,GAAI,EAAM,EAAW,CACnB,IAAM,EAAY,EAAK,MAAM,eAAiB,cAAgB,EAAK,MAAM,YAAc,YACnF,EAAW,EACf,IAAK,IAAI,EAAI,EAAW,GAAK,EAAK,IAAK,GAAY,EAAM,GAAG,MAE5D,IAAM,EAAgD,EAAE,CACxD,IAAK,IAAI,EAAI,EAAW,GAAK,EAAK,IAChC,IAAK,IAAM,IAAM,CAAC,GAAG,EAAM,GAAG,KAAK,CAAE,EAAM,KAAK,CAAE,KAAI,MAAO,EAAM,GAAG,MAAO,CAAC,CAGhF,IAAM,EAFe,EAAM,IAAK,GAAM,EAAE,GAAG,CAAC,KAAK,GAAG,CAEtB,MAAM,2BAA2B,CAAC,OAAQ,GAAM,EAAE,OAAO,CACjF,EAAa,EAAS,OAAS,EAOrC,GADc,EALG,EAAY,WAAa,GAAY,GAAU,IAKpC,GAAe,GAAa,EAAW,GAAU,EAClE,CAET,IAAM,EAAiD,EAAE,CACrD,EAAK,EACT,IAAK,IAAM,KAAM,EAAU,CACzB,IAAM,EAAM,CAAC,GAAG,EAAG,CAAC,OACpB,EAAK,KAAK,EAAM,MAAM,EAAI,EAAK,EAAI,CAAC,CACpC,GAAM,EAMR,IAAM,GAAc,EAA4C,IAAmB,CACjF,IAAI,EAAI,EACR,KAAO,EAAI,EAAG,QAAQ,CACpB,IAAM,EAAK,EAAG,GAAG,MACjB,EAAU,EAAK,EAAG,CAClB,EAAI,cAAgB,EAAoB,EAAG,cAAc,CACzD,IAAM,EAAK,EAAc,EAAK,EAAI,EAAe,CAE7C,EAAM,GACN,EAAO,EACX,KAAO,EAAI,EAAG,QAAU,EAAG,GAAG,QAAU,GAAI,CAC1C,IAAM,EAAK,EAAG,GAAG,GACX,EAAQ,EAAmB,EAAK,EAAM,EAAG,CAC3C,GAAS,EAAY,WAAa,EAAQ,GAAU,GACnD,EAAY,MAAM,OAAS,GAAK,IAC/B,IACF,EAAY,MAAM,KAAK,CAAE,KAAM,EAAK,MAAO,EAAM,MAAO,EAAI,QAAS,GAAO,CAAC,CAC7E,EAAY,YAAc,EAC1B,EAAY,WAAa,KAAK,IAAI,EAAY,WAAY,EAAG,EAE/D,EAAS,GAAK,CACd,EAAiB,GACjB,EAAM,EACN,EAAO,EAAmB,EAAK,EAAG,GAElC,GAAO,EACP,EAAO,GAET,IAEE,IACF,EAAY,MAAM,KAAK,CAAE,KAAM,EAAK,MAAO,EAAM,MAAO,EAAI,QAAS,GAAO,CAAC,CAC7E,EAAY,YAAc,EAC1B,EAAY,WAAa,KAAK,IAAI,EAAY,WAAY,EAAG,CAC7D,EAAiB,MAIjB,EAAc,GAA+C,CACjE,IAAI,EAAI,EACJ,EAAI,EACR,KAAO,EAAI,EAAG,QAAQ,CACpB,IAAM,EAAK,EAAG,GAAG,MACb,EAAM,GACV,KAAO,EAAI,EAAG,QAAU,EAAG,GAAG,QAAU,GAAM,GAAO,EAAG,GAAG,GAAI,IAC/D,EAAU,EAAK,EAAG,CAClB,EAAI,cAAgB,EAAoB,EAAG,cAAc,CACzD,GAAK,EAAmB,EAAK,EAAI,CAEnC,OAAO,GAIL,CAAC,GAAc,EAAY,MAAM,OAAS,IAC5C,EAAS,GAAK,CACd,EAAiB,IAEnB,IAAK,IAAM,KAAO,EAAM,CACtB,IAAM,EAAO,EAAW,EAAI,CACxB,EAAY,MAAM,OAAS,GAAK,EAAY,WAAa,EAAO,GAAU,GAC5E,EAAS,GAAK,CACd,EAAiB,IAGnB,EAAW,EAAK,GAAa,EAAO,GAAU,CAAC,CAEjD,EAAY,EACZ,WAMN,IAAM,EAAU,CAAC,EAAK,SAAW,EAAK,KAAK,OAAS,EAChD,EAAkB,EAAK,EAAM,GAAU,CAAE,EAAY,WAAW,CAChE,CAAC,EAAK,CAWN,EAAiB,EACrB,IAAK,IAAI,EAAI,EAAY,EAAG,EAAI,EAAM,OAAQ,IAAK,CACjD,IAAM,EAAK,EAAM,GACjB,GAAI,EAAG,SAAW,EAAG,OAAS;EAAM,MACpC,IAAM,EAAU,CAAC,CAAC,EAAG,MAAQ,GAAe,KAAK,EAAG,KAAK,CACnD,EAAgB,CAAC,EAAG,MAAQ,CAAC,CAAC,EAAG,SACjC,EAAc,CAAC,CAAC,EAAG,MAAQ,CAAC,CAAC,EAAG,cACtC,GAAI,GAAW,GAAiB,EAAa,CAAE,GAAkB,EAAG,MAAO,SAC3E,MAGF,IAAK,IAAM,KAAS,EAAQ,CAG1B,IAAM,EAFc,IAAU,EAAO,EAAO,OAAS,GAE1B,EAAiB,EAGtC,EAAkB,CAAC,EAAM,SAAW,EAAM,KAAK,OAAS,GAC5D,GAAe,KAAK,EAAM,KAAK,EAC/B,EAAY,MAAM,OAAS,GAC3B,CAAC,EAAY,MAAM,EAAY,MAAM,OAAS,GAAG,QAM7C,EAAU,IAAU,EAAO,IAAM,EAAM,eAC3C,EAAY,MAAM,OAAS,GAC3B,CAAC,EAAY,MAAM,EAAY,MAAM,OAAS,GAAG,QAO/C,EAAY,EAChB,GAAI,CAAC,EAAM,MAAQ,EAAM,QAAS,CAChC,IAAM,EAAO,EAAM,EAAY,GAC3B,GAAQ,CAAC,EAAK,SAAW,EAAK,OAQhC,GAHW,EAAK,KAAK,OAAS,EAC1B,EAAkB,EAAK,EAAM,GAAU,CAAE,EAAE,CAC3C,CAAC,EAAK,EACK,GAAG,OAStB,IAAI,EAAY,EAQhB,GAPI,EAAM,oBACR,EAAU,EAAK,EAAM,MAAM,CAC3B,EAAI,cAAgB,EAAoB,EAAM,MAAM,cAAc,CAClE,EAAY,EAAmB,EAAK,IAAI,EAItC,CAAC,EAAM,SAAW,CAAC,GAAmB,CAAC,GAAW,EAAY,MAAM,OAAS,GAC/E,EAAY,WAAa,EAAM,MAAQ,EAAY,EAAO,EAAY,GAAU,CAAE,CAClF,IAAM,EAAW,EAAY,WAAa,EAAM,MAAQ,EAAY,EAAO,EAAY,GAAU,CAO7F,EAAkB,GACtB,GAAI,EAAW,GAAK,CAAC,EAAc,CAAC,GAAG,EAAY,MAAO,EAAM,CAAC,CAAE,CACjE,EAAU,EAAK,EAAM,MAAM,CAC3B,IAAM,EAAW,EAAY,MAAM,IAAI,GAAK,EAAE,KAAK,CAAC,KAAK,GAAG,CAAG,EAAM,MAClE,EAAM,kBAAoB,IAAM,IAI/B,EAAc,EAClB,IAAK,IAAM,KAAK,EAAY,MAAY,EAAE,OAAM,GAAe,EAAE,OAC5D,EAAM,OAAM,GAAe,EAAM,OACpB,EAAmB,EAAK,EAAS,CAAG,EAAc,EAAO,GAK1D,GAAU,CAAG,MAC5B,EAAkB,IAOtB,GAAI,GAAmB,EAAM,KAAK,SAAS,IAAI,CAAE,CAC/C,IAAM,EAAQ,EAAM,KAAK,MAAM,2BAA2B,CAC1D,GAAI,EAAM,OAAS,EAAG,CACpB,EAAU,EAAK,EAAM,MAAM,CAC3B,IAAI,EAAS,GACT,EAAc,EACd,EAAU,EACR,EAAY,GAAU,CAAG,EAAY,WAC3C,KAAO,EAAU,EAAM,OAAQ,IAAW,CACxC,IAAM,EAAY,EAAS,EAAM,GAC3B,EAAiB,EAAmB,EAAK,EAAU,CACzD,GAAI,EAAiB,EAAW,MAChC,EAAS,EACT,EAAc,EAEhB,GAAI,EAAU,GAAK,EAAU,EAAM,OAAQ,CACzC,EAAY,MAAM,KAAK,CAAE,GAAG,EAAO,KAAM,EAAQ,MAAO,EAAa,CAAC,CACtE,EAAY,YAAc,EAC1B,EAAY,WAAa,KAAK,IAAI,EAAY,WAAY,EAAe,CACzE,EAAS,GAAK,CACd,EAAiB,GACjB,IAAM,EAAY,EAAM,MAAM,EAAQ,CAAC,KAAK,GAAG,CACzC,EAAiB,EAAmB,EAAK,EAAU,CACzD,EAAY,MAAM,KAAK,CAAE,GAAG,EAAO,KAAM,EAAW,MAAO,EAAgB,CAAC,CAC5E,EAAY,YAAc,EAC1B,EAAY,WAAa,KAAK,IAAI,EAAY,WAAY,EAAe,CACzE,WAKN,GAAI,EAAiB,CACnB,GAAI,EAAQ,CACV,IAAM,EAAW,EAAY,MAAM,IAAI,GAAK,EAAE,KAAK,CAAC,KAAK,GAAG,CAC5D,EAAO,CACL,KAAM,YACN,QAAS,IAAI,EAAM,KAAK,aAAa,EAAS,QAAQ,EAAE,CAAC,uBAAuB,EAAY,WAAW,QAAQ,EAAE,CAAC,cAAc,EAAM,MAAM,QAAQ,EAAE,CAAC,gBAAgB,EAAa,UAAU,EAAS,GACvM,KAAM,CAAE,KAAM,EAAM,KAAM,WAAU,UAAW,EAAY,WAAY,WAAY,EAAM,MAAO,eAAc,WAAU,CACzH,CAAC,CAEJ,EAAS,GAAK,CACd,EAAiB,IAOrB,GAAI,EAAM,SAAW,EAAY,MAAM,SAAW,IAC1C,CAAC,GAAkB,CAAC,GAAsB,SAGlD,IAAI,EAAa,EAAM,MACvB,GAAI,EAAM,MAAO,CACf,IAAM,EAAU,EAAM,MAChB,EAAa,EAAY,WAE/B,EADiB,KAAK,MAAM,EAAa,IAAO,EAAQ,CAAG,EACnC,EACxB,EAAM,MAAQ,EAIhB,GAAI,EAAY,MAAM,SAAW,GAAK,EAAa,GAAU,EACzD,CAAC,EAAM,SAAW,EAAM,KAAK,SAAS,IAAI,CAAE,CAC9C,IAAM,EAAW,EAAM,KAAK,MAAM,2BAA2B,CAC7D,GAAI,EAAS,OAAS,EAAG,CACvB,EAAU,EAAK,EAAM,MAAM,CAG3B,IAAM,EAAoB,EAAS,OAAO,GAAK,EAAE,CAAC,IAAI,IAAM,CAC1D,GAAG,EACH,KAAM,EACN,MAAO,EAAmB,EAAK,EAAE,CAClC,EAAE,CAIC,EAAQ,GACZ,IAAK,IAAM,KAAM,EACX,GACF,EAAQ,GAER,EAAY,MAAM,KAAK,EAAG,CAC1B,EAAY,YAAc,EAAG,MAC7B,EAAY,WAAa,KAAK,IAAI,EAAY,WAAY,EAAe,EAChE,EAAY,WAAa,EAAG,MAAQ,GAAU,EAEvD,EAAS,GAAK,CACd,EAAiB,GACjB,EAAY,MAAM,KAAK,EAAG,CAC1B,EAAY,YAAc,EAAG,MAC7B,EAAY,WAAa,KAAK,IAAI,EAAY,WAAY,EAAe,GAEzE,EAAY,MAAM,KAAK,EAAG,CAC1B,EAAY,YAAc,EAAG,MAC7B,EAAY,WAAa,KAAK,IAAI,EAAY,WAAY,EAAe,EAG7E,UAIJ,EAAY,MAAM,KAAK,EAAM,CAC7B,EAAY,YAAc,EAC1B,EAAY,WAAa,KAAK,IAAI,EAAY,WAAY,EAAe,CACpE,EAAM,UAAS,EAAiB,KAIzC,OADA,GAAU,CACH,EA0BT,SAAS,GACP,EACA,EACA,EACA,EACA,EACA,EAAiB,GACjB,EACyC,CACzC,IAAM,EAAwB,EAAE,CAChC,GAAI,IAAU,EAAM,WAAa,EAAM,WAAa,GAGlD,MADA,GAAM,UAAY,GACX,CAAE,MAAO,EAAS,OAAQ,EAAG,CAEtC,IAAM,EAAO,GAAgB,EAAK,CAClC,GAAI,EAAK,SAAW,EAAG,MAAO,CAAE,MAAO,EAAS,OAAQ,EAAG,CAE3D,IAAM,EAAQ,GAAa,EAAK,EAAK,CAC/B,EAAa,EAAK,MAAM,YAAc,EACtC,EAAQ,GAAmB,EAAK,EAAO,EAAc,EAAK,MAAM,WAAY,EAAgB,EAAW,CAOvG,EAAS,EAAQ,EAAM,UAAY,EAAK,MAAM,UACpD,GAAI,EAAS,GAAK,EAAM,OAAS,EAAQ,CACvC,EAAM,OAAS,EACf,IAAM,EAAW,EAAM,EAAS,GAIhC,GAAoB,EAAK,EADE,GAAgB,IAAW,EAAI,EAAa,GACjB,CAGtD,EAAS,iBAAmB,GACxB,IACF,EAAM,UAAY,EAClB,EAAM,UAAY,SAEX,IACT,EAAM,WAAa,EAAM,QAG3B,IAAM,EAAQ,EAAK,MAAM,YAAc,MACjC,EAAc,GACd,IAAM,QAAgB,EAAQ,QAAU,OACxC,IAAM,MAAc,EAAQ,OAAS,QAClC,EAEL,EAAY,EAAW,EAAK,MAAM,UAAU,CAG5C,EAAgB,EAAK,MAAM,eAAiB,OAChD,AAGE,EAHE,IAAkB,OACJ,EAAK,MAAM,YAAc,UAAa,EAAQ,QAAU,OAAU,EAElE,EAAW,EAAc,CAG3C,IAAI,EAAO,EAEX,IAAK,IAAI,EAAU,EAAG,EAAU,EAAM,OAAQ,IAAW,CACvD,IAAM,EAAO,EAAM,GACnB,GAAI,EAAK,MAAM,SAAW,EAAG,CAC3B,GAAQ,EAAK,WACb,SAGF,IAAM,EAAa,EAAK,WAClB,EAAa,IAAY,EAAM,OAAS,EACxC,EAAc,IAAY,EAK1B,EADU,GAAc,EAAK,iBACX,EAAgB,EAGlC,EAAS,EAAc,EAAa,EACpC,EAAe,EAAe,EAGhC,EAAuB,EAC3B,GAAI,IAAU,WAAa,EAAK,WAAa,EAAc,CACzD,IAAM,EAAa,EAAK,MAAM,OAAO,GAAK,EAAE,QAAQ,CAAC,OACjD,EAAa,IACf,GAAwB,EAAe,EAAK,YAAc,GAiB9D,IAAM,EAAY,EAAK,WAAa,EAAe,GAC/C,EAAO,EAAI,EACX,EAEF,EAAO,EAAQ,EAAI,EAAe,EAAK,WAAa,EAAI,EAC/C,IAAU,SACnB,EAAO,EAAI,GAAU,EAAe,EAAK,YAAc,EAC9C,IAAU,QACnB,GAAQ,EAAQ,EAAI,EAAe,EAAI,EAAS,GAAgB,EAAK,WAC5D,IAAU,WAAa,IAEhC,EAAO,EAAI,EAAe,EAAK,YAGjC,IAAM,EAAY,EAQd,EAAY,EACZ,EAAa,EACjB,IAAK,IAAM,KAAQ,EAAK,MAAO,CAI7B,GAHI,EAAK,OAAS,IAGd,EAAgB,EAAK,MAAM,cAAc,CAAE,SAC/C,GAAM,CAAE,OAAQ,EAAG,QAAS,GAAM,EAAe,EAAK,EAAK,MAAM,CAC7D,EAAI,IAAW,EAAY,GAC3B,EAAI,IAAY,EAAa,GAGnC,GAAI,IAAc,EAChB,IAAK,IAAM,KAAQ,EAAK,MAAO,CAC7B,GAAI,EAAK,OAAS,GAAI,SACtB,GAAM,CAAE,SAAQ,WAAY,EAAe,EAAK,EAAK,MAAM,CAC3D,EAAY,EACZ,EAAa,EACb,MAIJ,IAAM,EAAkB,EAAY,EAChC,EAAgB,GAAQ,EAAa,GAAmB,EAAI,EAI1D,EAAkB,EAAK,MAAM,OAAO,GACxC,EAAE,OAAS,IAAM,CAAC,EAAgB,EAAE,MAAM,cAAc,CAAC,CACrD,EAAiB,EAAgB,OAAS,EAC5C,KAAK,IAAI,GAAG,EAAgB,IAAI,GAAK,EAAE,MAAM,SAAS,CAAC,CAAG,EAK1D,EAAU,EACV,EAAa,EAAO,EACxB,IAAK,IAAM,KAAQ,EAAK,MAAO,CAC7B,GAAI,EAAK,OAAS,GAAI,SACtB,IAAM,EAAK,EAAK,MAAM,cACtB,GAAI,CAAC,EAAgB,EAAG,CAAE,SAC1B,GAAI,IAAmB,EAAG,MAE1B,GAAM,CAAE,OAAQ,EAAS,QAAS,GAAa,EAAe,EAAK,EAAK,MAAM,CACxE,EAAkB,EACtB,GAAmB,EAAI,EAAS,EAAU,EAAgB,EAAW,EAAY,EAAW,CAExF,EAAU,EAAkB,EAC5B,EAAa,EAAkB,EACjC,EAAU,IAAS,EAAU,GAC7B,EAAa,IAAY,EAAa,GAE5C,IAAM,EAAsB,EAAa,EAInC,GAAiB,EAAsB,EAAY,IAAe,CAGtE,GAAM,CAAE,OAAQ,EAAW,QAAS,GAAe,EAAe,EAAK,EAAM,CACvE,EAAS,EAAM,WAAa,EAAM,eAClC,EAAY,EAAM,cAAgB,EAAM,kBACxC,EAAY,EAAY,EAAa,EAAS,EAChD,EACJ,AAGE,EAHE,EAAM,UAAY,eACb,EAAO,EAAM,UAEb,EAAgB,EAAY,EAErC,EAAQ,KAAK,CACX,KAAM,MAAO,QAAO,EAAG,EAAI,EAAG,EAAM,MAAO,EAAI,OAAQ,EACvD,QAAS,OAAQ,SAAU,EAAE,CAC9B,CAAC,EAIJ,GAAI,CAAC,EAAO,CACV,IAAI,EAAQ,EACR,EAAY,EACZ,EACA,EAAa,GAEjB,IAAK,IAAM,KAAQ,EAAK,MAAO,CAC7B,GAAI,EAAK,SAAW,EAAK,UAAY,EAAK,KAAM,CAC1C,IACE,GAAY,EAAc,EAAiB,EAAW,EAAQ,EAAU,CAC5E,EAAkB,IAAA,GAClB,EAAa,IAEf,IAAM,EAAI,EAAK,MACT,EAAY,EAAK,MAAQ,EAAE,WAAa,EAAE,gBAAkB,EAAE,YAChE,EAAE,aAAe,EAAE,iBAAmB,EAAE,YAG5C,EAAc,EAFD,EAAQ,EAAE,WACV,EAAE,gBAAkB,EAAE,YAAc,EAAY,EAAE,aAAe,EAAE,iBACpD,CAC5B,EAAa,GACb,GAAS,EAAK,MACd,SAGE,EAAK,WAAa,IAChB,GAAmB,GACrB,EAAc,EAAiB,EAAW,EAAQ,EAAU,CAE9D,EAAkB,EAAK,SACvB,EAAY,EACZ,EAAa,IAEX,EAAK,MAAQ,CAAC,EAAK,UAAS,EAAa,IAC7C,GAAS,EAAK,OAAS,EAAK,QAAU,EAAuB,GAE3D,GAAmB,GACrB,EAAc,EAAiB,EAAW,EAAQ,EAAU,CAKhE,IAAM,EAAY,EAAK,MAAM,OAAO,GAAK,EAAE,OAAS,GAAG,CACjD,EAAe,EAAU,OAAS,GAAK,EAAU,MAAM,GAC3D,GAAc,EAAE,MAAO,EAAU,GAAG,MAAM,CAC3C,CAED,GAAI,EAAO,CAST,IAAM,EAAwB,EAAE,CAC5B,EAAmC,KACnC,EAAa,EAEjB,IAAK,IAAM,KAAQ,EAAK,MAAO,CAC7B,GAAI,EAAK,OAAS,GAAI,CAEpB,AAA+C,KAA3B,EAAO,KAAK,EAAa,CAAiB,MAC9D,GAAc,EAAK,MACnB,SAEF,GAAI,EAAK,SAAW,EAAuB,EAAG,CAM5C,AAA+C,KAA3B,EAAO,KAAK,EAAa,CAAiB,MAC9D,GAAc,EAAK,MAAQ,EAC3B,SAEE,GAAgB,GAAc,EAAa,MAAO,EAAK,MAAM,EAC/D,EAAa,MAAQ,EAAK,KAC1B,EAAa,OAAS,EAAK,QAEvB,GAAc,EAAO,KAAK,EAAa,CAC3C,EAAe,CAAE,KAAM,EAAK,KAAM,MAAO,EAAK,MAAO,MAAO,EAAK,MAAO,SAAU,EAAK,SAAU,EAAG,EAAG,UAAW,EAAY,CAC9H,EAAa,GAGb,GAAc,EAAO,KAAK,EAAa,CAI3C,IAAI,EAAO,EAAO,EAAK,WACvB,IAAK,IAAM,KAAS,EAAQ,CAC1B,GAAQ,EAAM,UACd,EAAU,EAAK,EAAM,MAAM,CAC3B,IAAM,EAAgB,EAAmB,EAAK,EAAM,KAAK,CACzD,GAAQ,EACR,EAAM,EAAI,EACV,EAAM,MAAQ,EAKhB,IAAK,IAAM,KAAS,EAClB,GAAI,EAAM,UAAY,GAAoB,EAAM,SAAS,CAAE,CACzD,IAAM,EAAK,EAAM,SACX,EAAU,EAAG,YAAc,EAAG,gBAC9B,EAAW,EAAG,aAAe,EAAG,iBACtC,EAAc,EAAI,EAAM,EAAI,EAAS,EAAM,MAAQ,EAAU,EAAS,CAK1E,IAAK,IAAM,KAAS,EAClB,EAAQ,KAAK,CACX,KAAM,OACN,KAAM,EAAM,KACZ,EAAG,EAAM,EAAI,EAAM,MACnB,EAAG,EACH,MAAO,EAAM,MACb,MAAO,CAAE,GAAG,EAAM,MAAO,UAAW,MAAO,CAC5C,CAAC,KAEC,CAKL,IAAM,EAAW,EAAK,MAAM,IAAI,GAAK,EAAE,KAAK,CAAC,KAAK,GAAG,CAIrD,GAHmB,GAAgB,4CAA4C,KAAK,EAAS,EAC3F,CAAC,EAAK,MAAM,KAAK,GAAK,EAAE,SAAW,EAAE,UACnC,EAAE,MAAM,gBAAkB,SAAW,EAAE,MAAM,gBAAkB,MAAM,CACzD,CACd,EAAU,EAAK,EAAU,GAAG,MAAM,CAClC,IAAM,EAAgB,EAAmB,EAAK,EAAS,CAQvD,EAAQ,KAAK,CACX,KAAM,OACN,KAAM,EACN,EAAG,EACH,EAAG,EACH,MAAO,EACP,MAAO,CAAE,GAAG,EAAU,GAAG,MAAO,UAAW,MAAO,CACnD,CAAC,MAGF,IAAK,IAAM,KAAQ,EAAK,MAAO,CAC7B,GAAI,EAAK,OAAS,GAAI,CACpB,GAAQ,EAAK,MACb,SAIF,GAAI,EAAK,SAAW,EAAK,SAAU,CACjC,IAAM,EAAI,EAAK,MACT,EAAQ,EAAO,EAAE,WAAa,EAAE,gBAAkB,EAAE,YAC1D,EAAQ,KAAK,CACX,KAAM,OACN,KAAM,EAAK,KACX,EAAG,EACH,EAAG,EACH,MAAO,EAAmB,EAAK,EAAK,KAAK,CACzC,MAAO,EAAK,MACb,CAAC,CACF,GAAQ,EAAK,MACb,SAIF,IAAI,EAAY,EACV,EAAK,EAAK,MAAM,cACtB,GAAI,EAAgB,EAAG,CAAE,CACvB,IAAM,EAAM,GAAkB,EAAK,MAAM,SACnC,CAAE,OAAQ,EAAI,QAAS,GAAO,EAAe,EAAK,EAAK,MAAM,CACnE,GAAa,GAAmB,EAAI,EAAI,EAAI,EAAK,EAAW,EAAY,EAAW,CAErF,IAAM,EAAiB,EAAK,OAAS,EAAK,QAAU,EAAuB,GAE3E,EAAQ,KAAK,CACX,KAAM,OACN,KAAM,EAAK,KACX,EAAG,EACH,EAAG,EACH,MAAO,EACP,MAAO,EAAK,MACb,CAAC,CAEF,GAAQ,GAQd,IAAM,EACJ,IAAU,WAAa,EAAuB,EAC1C,EACA,EAAK,WACX,EAAO,KAAK,CACV,EAAG,KAAK,MAAM,EAAc,CAC5B,KAAM,EAAK,MAAM,IAAI,GAAK,EAAE,KAAK,CAAC,KAAK,GAAG,CAC1C,OAAQ,CACH,EAGH,EAAG,EACH,MAAO,EACP,OAAQ,EACT,CACF,CAAC,CAEF,GAAQ,EAGV,MAAO,CAAE,MAAO,EAAS,OAAQ,EAAO,EAAG,CAS7C,SAAS,GAAgB,EAA0B,EAA+B,CAUhF,OARI,GAAoB,GAAK,GAAiB,EACrC,KAAK,IAAI,EAAkB,EAAc,CAG9C,EAAmB,GAAK,EAAgB,EACnC,KAAK,IAAI,EAAkB,EAAc,CAG3C,EAAmB,EAiB5B,SAAS,EACP,EACA,EACA,EACA,EACA,EACA,EAC6D,CAC7D,IAAM,EAAQ,EAAK,MAMf,CAAC,GAAS,EAAM,UAAY,IAC9B,EAAQ,CAAE,UAAW,EAAM,UAAW,UAAW,GAAO,EAI1D,IAAM,EAAa,EAAM,WACnB,EAAc,EAAM,YACpB,EAAa,EAAM,gBACnB,EAAc,EAAM,iBACpB,EAAY,EAAM,eAClB,EAAe,EAAM,kBACrB,EAAU,EAAM,YAChB,EAAW,EAAM,aACjB,EAAS,EAAM,WACf,EAAY,EAAM,cAElB,EAAO,EAAI,EAEX,EAAY,EAAM,MAAQ,EAC5B,EAAM,MACN,EAAiB,EAAa,EAC5B,EAAW,EAAO,EAAa,EAC/B,EAAe,KAAK,IAAI,EAAG,EAAW,EAAa,EAAc,EAAU,EAAS,CAEpF,EAAO,EACP,EAAgB,EAAO,EAAY,EAEnC,EAAiB,CACrB,KAAM,MACN,QACA,EAAG,EACH,EAAG,EACH,MAAO,EACP,OAAQ,EACR,QAAS,EAAK,QACd,SAAU,EAAE,CACZ,WAAY,EAAK,WAClB,CAGD,GAAI,EAAM,UAAY,OAAQ,CAC5B,IAAM,EAAS,GAAW,EAAK,EAAM,EAAU,EAAe,EAAa,CAG3E,MAFA,GAAI,SAAW,EAAO,SACtB,EAAI,OAAS,EAAY,EAAS,EAAO,OAAS,EAAY,EACvD,CAAE,MAAK,OAAQ,EAAI,OAAQ,gBAAiB,EAAM,aAAc,CAIzE,GAAI,EAAM,UAAY,QAAS,CAC7B,IAAM,EAAS,GAAY,EAAK,EAAM,EAAU,EAAe,EAAa,CAG5E,MAFA,GAAI,SAAW,EAAO,SACtB,EAAI,OAAS,EAAY,EAAS,EAAO,OAAS,EAAY,EACvD,CAAE,MAAK,OAAQ,EAAI,OAAQ,gBAAiB,EAAM,aAAc,CAKzE,GAAI,EAAK,SAAS,SAAW,EAG3B,MAFA,GAAI,OAAS,EAAY,EAAS,EAAY,EAC1C,EAAM,UAAY,IAAG,EAAI,OAAS,KAAK,IAAI,EAAI,OAAQ,EAAM,UAAU,EACpE,CAAE,MAAK,OAAQ,EAAI,OAAQ,gBAAiB,EAAM,aAAc,CAIzE,GAAI,GAAsB,EAAK,CAAE,CAG/B,GAAM,CAAE,QAAO,UAAW,GAAoB,EAAK,EAAM,EAAU,EAAe,EAD9D,EAAK,UAAY,MAAQ,EAAe,IAAI,EAAM,cAAc,CACyB,EAAM,CACnH,EAAI,SAAW,EACf,EAAI,OAAS,EAAY,EAAS,EAAS,EAAY,MAClD,CAEL,IAAI,EAAO,EACP,EAAmB,EACnB,EAAa,GAEX,EACJ,EAAK,UAAY,MAAQ,EAAK,UAAY,MAAQ,EAAK,UAAY,MACnE,EAAK,UAAY,MAAQ,EAAK,UAAY,KAE5C,IAAK,IAAI,EAAK,EAAG,EAAK,EAAK,SAAS,OAAQ,IAAM,CAChD,IAAM,EAAQ,EAAK,SAAS,GAI5B,GAAI,IAAU,EAAM,WAAa,EAAM,WAAa,GAAI,CACtD,EAAM,UAAY,GAClB,EAAmB,EACnB,MAGF,GAAI,EAAM,UAAY,SAAW,EAAS,EAAM,CAAE,CAEhD,IAAM,EAA+B,CAAC,EAAM,CAC5C,KAAO,EAAK,EAAI,EAAK,SAAS,QAAQ,CACpC,IAAM,EAAO,EAAK,SAAS,EAAK,GAChC,GAAI,EAAK,UAAY,SAAW,EAAS,EAAK,CAC5C,EAAe,KAAK,EAAK,CACzB,SAEA,MAKA,EAAmB,IACrB,GAAQ,EACR,EAAmB,GAGrB,IAAM,EAA0B,CAC9B,QAAS,KACT,QAAS,MACT,MAAO,CAAE,GAAG,EAAK,MAAO,QAAS,QAAS,UAAW,EAAG,aAAc,EAAG,WAAY,EAAG,cAAe,EAAG,eAAgB,EAAG,kBAAmB,EAAG,CACnJ,SAAU,EACV,YAAa,KACd,CACK,EAAe,EAAK,UAAY,MAAQ,EAAe,IAAI,EAAM,cAAc,CAC/E,CAAE,QAAO,UAAW,GAAoB,EAAK,EAAa,EAAU,EAAM,EAAc,EAAc,EAAM,CAClH,EAAI,SAAS,KAAK,GAAG,EAAM,CAC3B,GAAQ,EACR,EAAmB,EACnB,EAAa,GACb,SAIF,IAAM,EAAiB,EAAM,MAAM,UAMnC,GAAI,GAAC,GAAc,IAAW,GAAK,IAAc,GAAK,GAE/C,CACL,IAAM,EAAY,GAAgB,EAAkB,EAAe,CACnE,GAAQ,EAGV,GAAM,CAAE,IAAK,EAAU,OAAQ,EAAkB,mBAAoB,EACnE,EAAK,EAAO,EAAU,EAAM,EAAc,EAC3C,CACD,EAAI,SAAS,KAAK,EAAS,CAC3B,GAAQ,EAER,EAAmB,GAAO,UAAY,EAAI,EAC1C,EAAa,GAKf,IAAI,EAAkB,EAAM,aACtB,EAAqB,IAAc,GAAK,IAAiB,GAAK,EAChE,GAAsB,EAAmB,IAE3C,EAAkB,KAAK,IAAI,EAAM,aAAc,EAAiB,EAIlE,IAAI,EAAa,EAAO,EAMxB,MALI,CAAC,GAAsB,EAAmB,IAC5C,GAAc,GAEhB,EAAI,OAAS,EAAY,EAAS,EAAa,EAAY,EACvD,EAAM,UAAY,IAAG,EAAI,OAAS,KAAK,IAAI,EAAI,OAAQ,EAAM,UAAU,EACpE,CAAE,MAAK,OAAQ,EAAI,OAAQ,kBAAiB,CAIrD,OADI,EAAM,UAAY,IAAG,EAAI,OAAS,KAAK,IAAI,EAAI,OAAQ,EAAM,UAAU,EACpE,CAAE,MAAK,OAAQ,EAAI,OAAQ,gBAAiB,EAAM,aAAc,CAKzE,SAAS,GACP,EACA,EACA,EACA,EACA,EAC4C,CAC5C,IAAM,EAAyB,EAAE,CAG3B,EAAqB,EAAE,CAC7B,IAAK,IAAM,KAAS,EAAK,SACvB,GAAI,EAAM,UAAY,KACpB,EAAK,KAAK,EAAM,SACP,CAAC,QAAS,QAAS,QAAQ,CAAC,SAAS,EAAM,QAAQ,KACvD,IAAM,KAAc,EAAM,SACzB,EAAW,UAAY,MAAM,EAAK,KAAK,EAAW,CAK5D,GAAI,EAAK,SAAW,EAAG,MAAO,CAAE,WAAU,OAAQ,EAAG,CAGrD,IAAM,EAAW,KAAK,IAAI,GAAG,EAAK,IAAI,GAAK,EAAE,SAAS,OAAO,GAAK,EAAE,UAAY,MAAQ,EAAE,UAAY,KAAK,CAAC,OAAO,CAAC,CACpH,GAAI,IAAa,EAAG,MAAO,CAAE,WAAU,OAAQ,EAAG,CAGlD,IAAM,EAAW,EAAe,EAE5B,EAAO,EAEX,IAAK,IAAM,KAAO,EAAM,CACtB,IAAM,EAAQ,EAAI,SAAS,OAAO,GAAK,EAAE,UAAY,MAAQ,EAAE,UAAY,KAAK,CAC5E,EAAgB,EACd,EAAyB,EAAE,CAEjC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAM,OAAQ,IAAK,CACrC,IAAM,EAAO,EAAM,GAGb,CAAE,IAAK,EAAS,OAAQ,GAAe,EAAY,EAAK,EAFhD,EAAW,EAAI,EAE8C,EAAM,EAAS,CAC1F,EAAU,KAAK,EAAQ,CACvB,EAAgB,KAAK,IAAI,EAAe,EAAW,CAIrD,IAAK,IAAM,KAAW,EACpB,EAAQ,OAAS,EACjB,EAAS,KAAK,EAAQ,CAGxB,GAAQ,EAGV,MAAO,CAAE,WAAU,OAAQ,EAAO,EAAU,CAK9C,SAAS,GACP,EACA,EACA,EACA,EACA,EAC4C,CAC5C,IAAM,EAAQ,EAAK,MACb,EAAM,EAAM,IACZ,EAAyB,EAAE,CAE3B,EAAe,EAAK,SAAS,OAAO,GAAK,EAAE,UAAY,SAAW,EAAE,aAAa,MAAM,CAAC,CAC9F,GAAI,EAAa,SAAW,EAAG,MAAO,CAAE,WAAU,OAAQ,EAAG,CAE7D,GAAI,EAAM,gBAAkB,OAAS,EAAM,gBAAkB,GAAI,CAE/D,IAAM,EAAY,GAAO,EAAa,OAAS,GACzC,EAAY,EAAa,QAAQ,EAAG,IAAM,GAAK,EAAE,MAAM,UAAY,GAAI,EAAE,CACzE,GAAa,EAAe,IAAc,GAAa,EAAa,QAEtE,EAAO,EACP,EAAY,EAEhB,IAAK,IAAM,KAAS,EAAc,CAChC,GAAI,EAAM,UAAY,QAAS,SAE/B,IAAM,EAAa,GADN,EAAM,MAAM,WAAa,IAAc,EAAI,EAAI,IAGtD,CAAE,MAAK,UAAW,EAAY,EAAK,EAAO,EAAM,EAAU,EAAW,CAC3E,EAAS,KAAK,EAAI,CAClB,EAAY,KAAK,IAAI,EAAW,EAAO,CACvC,GAAQ,EAAa,EAGvB,MAAO,CAAE,WAAU,OAAQ,EAAW,CAIxC,IAAI,EAAO,EACX,IAAK,IAAM,KAAS,EAAc,CAChC,GAAI,EAAM,UAAY,QAAS,SAC/B,GAAM,CAAE,MAAK,UAAW,EAAY,EAAK,EAAO,EAAU,EAAM,EAAa,CAC7E,EAAS,KAAK,EAAI,CAClB,GAAQ,EAAS,EAEnB,MAAO,CAAE,WAAU,OAAQ,EAAO,EAAU,CAQ9C,SAAS,GACP,EACA,EACA,EACM,CAIN,GAHI,CAAC,EAAK,YAGN,EAAK,aAAc,OAEvB,IAAM,EAAQ,EAAK,MAIb,EAAK,EAAK,YACV,EAAgC,EAAK,CAAE,GAAG,EAAO,GAAG,EAAI,CAAG,EAEjE,EAAI,KAAO,EAAgB,EAAe,CAC1C,IAAM,EAAa,EAAc,EAAK,EAAM,CACtC,EAAY,EAAI,EAAI,EAAM,eAAiB,EAAM,WACrD,GAAiB,EAAK,EAAO,EAAW,CAEpC,EAAc,EAAmB,EAAK,EAAK,WAAW,CACtD,EAAQ,EAAM,YAAc,MAC5B,EAAW,EAAe,IAAI,EAAM,cAAc,CAUlD,EAAc,EAAQ,GAAI,YAAc,GAAI,aAE9C,EACA,EAAU,EACV,EAAkB,MAChB,EAAgB,EAAI,EAAI,EAAM,gBAAkB,EAAM,YACtD,EAAe,EAAI,EAAI,EAAI,MACjC,GAAI,EAAU,CACZ,GAAM,CAAE,UAAW,EAAe,EAAK,EAAe,CAChD,EAAI,EAAI,YAAY,EAAK,WAAW,CACpC,EAAM,IAAgB,IAAA,GAA0B,EAAI,EAAS,EAA3B,EACxC,AAIE,EAJE,EAEQ,EAAe,GAAO,EAAE,uBAAyB,GAEjD,EAAgB,GAAO,EAAE,wBAA0B,GAE/D,IAAM,IACF,EAAE,yBAA2B,IAAM,EAAE,0BAA4B,IAAM,EAC3E,EAAU,EAAY,EAAS,EAAI,MAC9B,CACL,IAAM,EAAM,IAAgB,IAAA,GAExB,EAAmB,EAAK,IAAI,CAD5B,EAEA,EAIiB,KAAK,KAAK,EAAK,WAAW,EAE3C,EAAkB,MAClB,EAAU,EAAe,EAAM,GAE/B,EAAU,EAAe,EAI3B,EAAU,EAAgB,EAAc,EAI5C,EAAI,SAAS,QAAQ,CACnB,KAAM,OACN,KAAM,EAAK,WACX,EAAG,EACH,EAAG,EACH,MAAO,EACP,MAAO,CAAE,GAAG,EAAgB,mBAAoB,OAAQ,WAAY,GAAI,YAAc,IAAK,UAAW,GAAI,WAAa,SAAU,UAAW,EAAiB,CAC9J,CAAC,CAOF,IAAM,EAAc,IAAoB,MAAQ,EAAU,EAAc,EACxE,EAAO,KAAK,CACV,EAAG,KAAK,MAAM,EAAU,CACxB,KAAM,EAAK,WACX,OAAQ,CACN,EAAG,EACH,EAAG,EAAI,EAAI,EAAM,eAAiB,EAAM,WACxC,MAAO,EACP,OAAQ,EACT,CACF,CAAC,CASJ,SAAgB,GACd,EACA,EACA,EACA,EAAqB,GACrB,EAC0D,CAC1D,EAAsB,EACtB,EAAS,EAGT,EAAiB,OAAO,CACxB,EAAkB,OAAO,CACzB,EAAiB,OAAO,CACxB,EAAc,OAAO,CACrB,EAAS,EAAE,CAGX,GAAM,CAAE,MAAK,UAAW,EAAY,EAAK,EAAY,EAAG,EAAG,EAAe,CAG1E,GAAwB,EAAK,EAAK,EAAW,CAK7C,IAAM,EAAS,EAAO,OAAO,CAAC,MAAM,EAAG,IACpC,EAAE,EAAI,EAAE,GAAO,EAAE,OAAO,EAAI,EAAE,OAAO,EACvC,CACK,EAAsB,EAAE,CAC9B,IAAK,IAAM,KAAa,EAAQ,CAC9B,IAAM,EAAO,EAAM,EAAM,OAAS,GAK5B,EAAY,EAAU,OAAO,OAAS,GAC5C,GAAI,GAAQ,KAAK,IAAI,EAAU,EAAI,EAAK,EAAE,CAAG,EAAW,CAItD,IAAM,EAAW,EAAK,KAAK,OAAS,GAAK,EAAU,KAAK,OAAS,GAC/D,CAAC,MAAM,KAAK,EAAK,KAAK,EAAI,CAAC,MAAM,KAAK,EAAU,KAAK,CACvD,EAAK,OAAS,EAAW,IAAM,IAAM,EAAU,KAG/C,EAAK,EAAI,KAAK,IAAI,EAAK,EAAG,EAAU,EAAE,CACtC,IAAM,EAAK,KAAK,IAAI,EAAK,OAAO,EAAG,EAAU,OAAO,EAAE,CAChD,EAAK,KAAK,IAAI,EAAK,OAAO,EAAG,EAAU,OAAO,EAAE,CAChD,EAAK,KAAK,IAAI,EAAK,OAAO,EAAI,EAAK,OAAO,MAAO,EAAU,OAAO,EAAI,EAAU,OAAO,MAAM,CAC7F,EAAK,KAAK,IAAI,EAAK,OAAO,EAAI,EAAK,OAAO,OAAQ,EAAU,OAAO,EAAI,EAAU,OAAO,OAAO,CACrG,EAAK,OAAS,CAAE,EAAG,EAAI,EAAG,EAAI,MAAO,EAAK,EAAI,OAAQ,EAAK,EAAI,MAE/D,EAAM,KAAK,CAAE,EAAG,EAAU,EAAG,KAAM,EAAU,KAAM,OAAQ,CAAE,GAAG,EAAU,OAAQ,CAAE,CAAC,CAGzF,MAAO,CAAE,KAAM,EAAK,SAAQ,QAAO,CAGrC,SAAS,GACP,EACA,EACA,EACM,CACN,GAAc,EAAK,EAAK,EAAK,CAI7B,IAAI,EAAc,EAClB,IAAK,IAAM,KAAe,EAAK,SACzB,OAAY,UAAY,SAAW,EAAS,EAAY,EAI5D,KAAO,EAAc,EAAI,SAAS,QAAQ,CACxC,IAAM,EAAc,EAAI,SAAS,GACjC,GAAI,EAAY,OAAS,OAAS,EAAY,UAAY,EAAY,QAAS,CAC7E,GAAwB,EAAK,EAAa,EAAY,CACtD,IACA,MAEF,KC3zEN,SAAgB,GAAiB,EAK9B,CACD,GAAI,CAAC,GAAU,IAAW,OAAQ,MAAO,EAAE,CAE3C,IAAM,EAAoF,EAAE,CAGtF,EAAQ,EAAO,MAAM,eAAe,CAE1C,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAU,EAAK,MAAM,CAErB,EAAa,EAAQ,MAAM,uDAAuD,CAClF,EAAa,EAAQ,MAAM,cAAc,CAE/C,GAAI,GAAc,EAAW,QAAU,EAAG,CACxC,IAAM,EAAO,EAAW,IAAI,GAAK,WAAW,EAAE,CAAC,CAC/C,EAAQ,KAAK,CACX,QAAS,EAAK,GACd,QAAS,EAAK,GACd,KAAM,EAAK,IAAM,EACjB,MAAO,EAAa,EAAW,GAAK,gBACrC,CAAC,EAIN,OAAO,EAMT,SAAS,GAAU,EAAsB,EAAoD,CAG3F,OAFc,EAAM,SAAS,EAAK,QAEnB,GADK,EAAM,SAAS,EAAK,UACJ,OAMtC,SAAgB,EACd,EACA,EACA,EACA,EACA,EACA,EACA,EACM,CAKN,GAJA,EAAI,MAAM,CACV,EAAI,YAAc,EAClB,EAAI,UAAY,EAEZ,IAAc,SAAU,CAC1B,IAAM,EAAM,KAAK,IAAI,EAAW,EAAE,CAClC,EAAI,UAAY,KAAK,IAAI,GAAK,EAAY,GAAI,CAC9C,EAAI,WAAW,CACf,EAAI,OAAO,EAAG,EAAI,EAAM,EAAE,CAC1B,EAAI,OAAO,EAAI,EAAO,EAAI,EAAM,EAAE,CAClC,EAAI,OAAO,EAAG,EAAI,EAAM,EAAE,CAC1B,EAAI,OAAO,EAAI,EAAO,EAAI,EAAM,EAAE,CAClC,EAAI,QAAQ,SACH,IAAc,OAAQ,CAC/B,IAAM,EAAY,KAAK,IAAI,IAAK,EAAU,CACpC,EAAa,EAAY,EAC/B,EAAI,WAAW,CACf,EAAI,OAAO,EAAG,EAAE,CAChB,IAAK,IAAI,EAAK,EAAG,EAAK,EAAI,EAAO,GAAM,EACrC,EAAI,iBAAiB,EAAK,EAAa,EAAG,EAAI,EAAW,EAAK,EAAa,EAAG,EAAE,CAChF,EAAI,iBAAiB,EAAK,EAAa,EAAI,EAAG,EAAI,EAAW,EAAK,EAAY,EAAE,CAElF,EAAI,QAAQ,MAGR,IAAc,SAAU,EAAI,YAAY,CAAC,EAAW,EAAY,EAAE,CAAC,CAC9D,IAAc,UAAU,EAAI,YAAY,CAAC,EAAY,EAAG,EAAY,EAAE,CAAC,CAChF,EAAI,WAAW,CACf,EAAI,OAAO,EAAG,EAAE,CAChB,EAAI,OAAO,EAAI,EAAO,EAAE,CACxB,EAAI,QAAQ,CAGd,EAAI,YAAY,EAAE,CAAC,CACnB,EAAI,SAAS,CAMf,SAAgB,GACd,EACA,EACA,EACA,EACA,EACA,EACuB,CAEvB,IAAM,EAAW,EAAQ,QAAQ,mBAAmB,CACpD,GAAI,IAAa,GAAI,OAAO,KAC5B,IAAI,EAAQ,EACR,EAAS,GACb,IAAK,IAAI,EAAI,EAAW,GAAI,EAAI,EAAQ,OAAQ,IAC9C,GAAI,EAAQ,KAAO,IAAK,YACf,EAAQ,KAAO,IAAK,CAC3B,GAAI,IAAU,EAAG,CAAE,EAAS,EAAG,MAC/B,IAGJ,GAAI,IAAW,GAAI,OAAO,KAC1B,IAAM,EAAe,EAAQ,MAAM,EAAW,GAAI,EAAO,CAGnD,EAAkB,EAAE,CAC1B,EAAQ,EACR,IAAI,EAAQ,EACN,EAAQ,EACd,IAAK,IAAI,EAAI,EAAG,EAAI,EAAM,OAAQ,IAC5B,EAAM,KAAO,IAAK,IACb,EAAM,KAAO,IAAK,IAClB,EAAM,KAAO,KAAO,IAAU,IACrC,EAAM,KAAK,EAAM,MAAM,EAAO,EAAE,CAAC,MAAM,CAAC,CACxC,EAAQ,EAAI,GAGhB,EAAM,KAAK,EAAM,MAAM,EAAM,CAAC,MAAM,CAAC,CAErC,IAAI,EAAQ,IACR,EAAgB,EACd,EAAY,EAAM,GACpB,EAAU,SAAS,MAAM,EAC3B,EAAQ,WAAW,EAAU,CAC7B,EAAgB,GACP,IAAc,YACvB,EAAQ,GAAI,EAAgB,GACnB,IAAc,WACvB,EAAQ,IAAK,EAAgB,GACpB,IAAc,aACvB,EAAQ,IAAK,EAAgB,GACpB,IAAc,WACvB,EAAQ,EAAG,EAAgB,GAG7B,IAAM,GAAO,EAAQ,IAAM,KAAK,GAAK,IAC/B,EAAK,EAAI,EAAQ,EACjB,EAAK,EAAI,EAAS,EAClB,EAAM,KAAK,IAAI,EAAQ,KAAK,IAAI,EAAI,CAAC,CAAG,KAAK,IAAI,EAAS,KAAK,IAAI,EAAI,CAAC,CACxE,EAAK,KAAK,IAAI,EAAI,CAAG,EAAM,EAC3B,EAAK,KAAK,IAAI,EAAI,CAAG,EAAM,EAE3B,EAAW,EAAI,qBAAqB,EAAK,EAAI,EAAK,EAAI,EAAK,EAAI,EAAK,EAAG,CAEvE,EAAS,EAAM,MAAM,EAAc,CACzC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAO,OAAQ,IAAK,CACtC,IAAM,EAAQ,EAAO,GAAG,MAAM,CAG1B,EAAQ,EACR,EAAO,EAAI,KAAK,IAAI,EAAG,EAAO,OAAS,EAAE,CACvC,EAAe,EAAM,MAAM,mBAAmB,CAChD,IACF,EAAO,WAAW,EAAa,GAAG,CAAG,IACrC,EAAQ,EAAM,MAAM,EAAG,EAAM,OAAS,EAAa,GAAG,OAAO,CAAC,MAAM,EAEtE,GAAI,CACF,EAAS,aAAa,EAAM,EAAM,MAC5B,GAKV,OAAO,EAIT,SAAS,GAAc,EAA8B,CACnD,OAAO,EAAM,qBAAuB,EAAM,sBAAwB,cAC9D,EAAM,oBAAsB,EAAM,MAIxC,SAAS,GAAgB,EAA+B,EAA4B,CAClF,EAAI,YAAc,EAAM,uBAAyB,EAAM,MACvD,EAAI,UAAY,EAAM,sBACtB,EAAI,SAAW,QAOjB,SAAS,GAAW,EAA+B,EAAkB,EAA4C,CAC/G,GAAM,CAAE,SAAU,EAElB,EAAI,MAAM,CACV,EAAI,KAAO,EAAgB,EAAM,CACjC,EAAI,aAAe,aACnB,EAAI,YAAc,EAAM,cAAgB,OAAS,OAAS,SACtD,OAAO,SAAS,EAAM,cAAc,EAAI,EAAM,gBAAkB,IAClE,EAAI,cAAgB,GAAG,EAAM,cAAc,KAEzC,EAAM,cACP,EAAY,YAAc,GAAG,EAAM,YAAY,KAE9C,EAAM,YAAc,QACtB,EAAI,UAAY,MAChB,EAAI,UAAY,SAGlB,IAAM,EAAiB,EAAM,uBAAyB,QACpD,EAAM,iBAAmB,EAAM,kBAAoB,OAC/C,EAAgB,EAAM,sBAAwB,EAC9C,EAAoB,EAAM,sBAAwB,eACtD,EAAM,QAAU,cAOZ,EAAU,GAAiB,EAAM,WAAW,CAClD,GAAI,EAAQ,OAAS,EAAG,CACtB,IAAM,EAAiB,GAAkB,CAAC,EAC1C,IAAK,IAAM,KAAU,EACnB,EAAI,MAAM,CACV,EAAI,cAAgB,EAAO,QAC3B,EAAI,cAAgB,EAAO,QAC3B,EAAI,WAAa,EAAO,KACxB,EAAI,YAAc,EAAO,MACrB,IACF,EAAI,UAAY,GAAkB,EAAe,EAAe,GAAc,EAAM,CACpF,EAAI,SAAS,EAAK,KAAM,EAAK,EAAG,EAAK,EAAE,EAErC,IACF,GAAgB,EAAK,EAAM,CAC3B,EAAI,WAAW,EAAK,KAAM,EAAK,EAAG,EAAK,EAAE,EAE3C,EAAI,SAAS,CAIjB,IAAM,MAAiB,CACrB,GAAI,EAAgB,CAElB,GADA,EAAI,MAAM,CACN,EACF,EAAI,UAAY,MACX,CAEL,GAAM,CAAE,SAAQ,WAAY,EAAe,EAAK,EAAM,CAMtD,EAAI,UALa,GACf,EAAK,EAAM,gBACX,EAAK,EAAG,EAAK,MACb,EAAK,EAAI,EAAQ,EAAS,EAC3B,EAC2B,EAAM,MAEpC,EAAI,SAAS,EAAK,KAAM,EAAK,EAAG,EAAK,EAAE,CACvC,EAAI,SAAS,OACJ,CAAC,GAAqB,CAAC,KAEhC,EAAI,UAAY,GAAc,EAAM,CACpC,EAAI,SAAS,EAAK,KAAM,EAAK,EAAG,EAAK,EAAE,GAIrC,MAAmB,CAClB,IACL,EAAI,MAAM,CACV,GAAgB,EAAK,EAAM,CAC3B,EAAI,WAAW,EAAK,KAAM,EAAK,EAAG,EAAK,EAAE,CACzC,EAAI,SAAS,GAGX,EAAyB,EAAM,WAAW,EAC5C,GAAY,CACZ,GAAU,GAEV,GAAU,CACV,GAAY,EAId,IAAM,EAAY,EAAK,MACjB,EAAW,EAAM,SACjB,EAAY,EAAM,qBAAuB,EAAM,MAC/C,EAAY,EAAM,qBAAuB,QACzC,EAAY,KAAK,IAAI,EAAG,EAAW,GAAG,CAGtC,EAAQ,EAAM,YAAc,MAAQ,EAAK,EAAI,EAAY,EAAK,EAEpE,GAAI,EAAM,qBAAuB,OAAQ,CACvC,GAAM,CAAE,OAAQ,GAAe,EAAe,EAAK,EAAM,CACzD,EAAI,KAAO,EAAgB,EAAM,CACjC,IAAM,EAAU,EAAI,YAAY,IAAI,CAAC,wBAErC,GAAI,EAAM,mBAAmB,SAAS,YAAY,CAAE,CAClD,IAAM,EAAU,EAAW,GAC3B,EAAmB,EAAK,EAAO,EAAK,EAAI,EAAS,EAAW,EAAW,EAAW,EAAU,CAG9F,GAAI,EAAM,mBAAmB,SAAS,eAAe,CAAE,CACrD,IAAM,EAAU,EAAE,EAAU,IAC5B,EAAmB,EAAK,EAAO,EAAK,EAAI,EAAS,EAAW,EAAW,EAAW,EAAU,CAG1F,EAAM,mBAAmB,SAAS,WAAW,EAC/C,EAAmB,EAAK,EAAO,EAAK,EAAI,EAAY,EAAW,EAAW,EAAW,EAAU,CAInG,EAAI,SAAS,CAMf,SAAS,GAAU,EAA+B,EAAsB,CACtE,GAAM,CAAE,SAAU,EAGb,GAAc,EAAM,gBAAgB,GACvC,EAAI,UAAY,EAAM,gBACtB,EAAI,SAAS,EAAI,EAAG,EAAI,EAAG,EAAI,MAAO,EAAI,OAAO,EAInD,IAAM,EAAyG,CAC7G,CAAC,MAAO,EAAI,EAAG,EAAI,EAAI,EAAM,eAAiB,EAAG,EAAI,EAAI,EAAI,MAAO,EAAI,EAAI,EAAM,eAAiB,EAAE,CACrG,CAAC,QAAS,EAAI,EAAI,EAAI,MAAQ,EAAM,iBAAmB,EAAG,EAAI,EAAG,EAAI,EAAI,EAAI,MAAQ,EAAM,iBAAmB,EAAG,EAAI,EAAI,EAAI,OAAO,CACpI,CAAC,SAAU,EAAI,EAAG,EAAI,EAAI,EAAI,OAAS,EAAM,kBAAoB,EAAG,EAAI,EAAI,EAAI,MAAO,EAAI,EAAI,EAAI,OAAS,EAAM,kBAAoB,EAAE,CACxI,CAAC,OAAQ,EAAI,EAAI,EAAM,gBAAkB,EAAG,EAAI,EAAG,EAAI,EAAI,EAAM,gBAAkB,EAAG,EAAI,EAAI,EAAI,OAAO,CAC1G,CACD,IAAK,GAAM,CAAC,EAAM,EAAI,EAAI,EAAI,KAAO,EAC9B,GAAU,EAAO,EAAK,GAC3B,EAAI,YAAc,EAAM,SAAS,EAAK,QACtC,EAAI,UAAY,EAAM,SAAS,EAAK,QACpC,EAAI,WAAW,CACf,EAAI,OAAO,EAAI,EAAG,CAClB,EAAI,OAAO,EAAI,EAAG,CAClB,EAAI,QAAQ,EAId,IAAI,EAAsC,KACtC,EAAM,uBAAyB,QAAU,EAAM,iBAAmB,EAAM,kBAAoB,SAC9F,EAAe,GAAoB,EAAK,EAAM,gBAAiB,EAAI,EAAG,EAAI,MAAO,EAAI,EAAG,EAAI,OAAO,EAIrG,IAAK,IAAM,KAAS,EAAI,SACtB,GAAW,EAAK,EAAO,EAAa,CAOxC,SAAgB,GAAW,EAA+B,EAAkB,EAA4C,CAClH,EAAK,OAAS,OAChB,GAAW,EAAK,EAAM,EAAa,CAEnC,GAAU,EAAK,EAAK,CCrWxB,SAAgB,GAAO,EAAoC,CACzD,GAAM,CACJ,OACA,QACA,SACA,WAAW,cACX,SACE,EAEJ,GAAI,CAAC,GAAS,GAAS,GAAK,OAAO,MAAM,EAAM,CAC7C,MAAU,UAAU,gDAAgD,IAAQ,CAG9E,IAAM,EAAqB,IAAa,WAElC,CAAE,WAAU,OAAQ,EAAU,EAAK,CACnC,CAAE,OAAM,WAAY,EAAqB,EAAU,EAAK,EAAM,CAG9D,EADY,SAAS,cAAc,SAAS,CACrB,WAAW,KAAK,CAC7C,EAAW,YAAc,SAEzB,GAAM,CAAE,OAAM,OAAQ,EAAe,SAAU,GAAgB,EAAY,EAAM,EAAO,EAAoB,EAAM,CAC5G,EAAc,GAAU,EAI9B,OAFA,GAAS,CAEF,CAAE,WAAY,EAAM,OAAQ,EAAa,QAAO,CASzD,SAAgB,GAAW,EAA2C,CACpE,GAAM,CACJ,OAAQ,EACR,QACA,aAAa,WAAW,kBAAoB,GAC1C,EAEJ,GAAI,EAAO,KAAO,EAAO,OACvB,MAAU,UAAU,6EAA6E,CAGnG,IAAM,EAAc,EAAa,OAC7B,EACA,EAmBJ,OAjBI,EAAO,KACT,EAAY,EAAO,IACnB,EAAS,EAAO,IAAI,SAEpB,EAAS,EAAO,QAAU,SAAS,cAAc,SAAS,CAC1D,EAAO,MAAQ,KAAK,KAAK,EAAQ,EAAW,CAC5C,EAAO,OAAS,KAAK,KAAK,EAAc,EAAW,CAC/C,UAAW,IACZ,EAA6B,MAAM,MAAQ,GAAG,EAAM,IACpD,EAA6B,MAAM,OAAS,GAAG,EAAY,KAE9D,EAAY,EAAO,WAAW,KAAK,CACnC,EAAU,MAAM,EAAY,EAAW,EAGzC,GAAW,EAAuC,EAAa,WAAW,CAEnE,CAAE,SAAQ,CAUnB,SAAgB,GAAO,EAAoC,CACzD,GAAI,EAAO,KAAO,EAAO,OACvB,MAAU,UAAU,yEAAyE,CAG/F,IAAM,EAAe,GAAO,CAC1B,KAAM,EAAO,KACb,MAAO,EAAO,MACd,OAAQ,EAAO,OACf,SAAU,EAAO,SACjB,MAAO,EAAO,MACf,CAAC,CAEI,CAAE,UAAW,GAAW,CAC5B,OAAQ,EACR,MAAO,EAAO,MACd,IAAK,EAAO,IACZ,OAAQ,EAAO,OACf,WAAY,EAAO,WACpB,CAAC,CAEF,MAAO,CACL,SACA,OAAQ,EAAa,OACrB,WAAY,EAAa,WACzB,MAAO,EAAa,MACrB"}
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/** Default values for all ResolvedStyle properties */\nfunction defaultStyle(): ResolvedStyle {\n return {\n // Browsers default unstyled text to the UA serif font (Times). Match it so\n // HTML without an explicit font-family wraps/positions like the browser.\n fontFamily: 'serif',\n fontSize: 16,\n fontWeight: 400,\n fontStyle: 'normal',\n fontVariantCaps: '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 unicodeBidi: '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 lineClamp: 0,\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 bdo: { display: 'inline', unicodeBidi: 'bidi-override' },\n bdi: { display: 'inline', unicodeBidi: 'isolate' },\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 // Remaining tokens are the color (#hex or named) — except thickness\n // values (2px, .5em, 10%) and thickness keywords, which are skipped.\n else if (!/^[\\d.+-]/.test(p) && p !== 'auto' && p !== 'from-font')\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/** Normalize the (case-insensitive) currentColor keyword to '', the canonical unset value. */\nfunction normalizeCurrentColor(value: string): string {\n const v = value.trim();\n return v.toLowerCase() === 'currentcolor' ? '' : v;\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 // Canvas only renders the `small-caps` variant; map anything containing it\n // (incl. the font-variant shorthand) to small-caps, else normal.\n case 'font-variant':\n case 'font-variant-caps':\n style.fontVariantCaps = /\\bsmall-caps\\b/.test(value) ? 'small-caps' : 'normal';\n 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 // '' is the canonical currentColor for these two: it must survive\n // inheritance as a keyword and resolve against each element's own\n // color at render time, so it is never eagerly resolved here.\n case '-webkit-text-stroke-color': style.webkitTextStrokeColor = normalizeCurrentColor(value); break;\n case '-webkit-text-fill-color': style.webkitTextFillColor = normalizeCurrentColor(value); 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 '-webkit-line-clamp':\n case 'line-clamp': {\n // Spec accepts `none` / `auto` / positive integer. We map both\n // `none` and `auto` to 0 (no clamp); a non-positive integer also\n // means no clamp. Otherwise store the integer.\n const v = value.trim().toLowerCase();\n if (v === 'none' || v === 'auto') {\n style.lineClamp = 0;\n } else {\n const n = parseInt(v, 10);\n style.lineClamp = Number.isFinite(n) && n > 0 ? n : 0;\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 case 'unicode-bidi': style.unicodeBidi = 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 ['-webkit-text-stroke-width', 'webkitTextStrokeWidth'],\n ['-webkit-text-stroke-color', 'webkitTextStrokeColor'],\n ['-webkit-text-fill-color', 'webkitTextFillColor'],\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 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, LayoutLine } 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// Lines emitted during layout. Reset at the start of buildLayoutTree();\n// layoutInlineContent appends one entry per committed line.\nlet _lines: LayoutLine[] = [];\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 and ctx.letterSpacing must already be set by caller.\n // letterSpacing is part of the key because it changes measured width.\n const key = ctx.font + '\\0' + (ctx.letterSpacing || '') + '\\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 */\nexport function applyFont(ctx: CanvasRenderingContext2D, style: ResolvedStyle): void {\n ctx.font = buildCanvasFont(style);\n ctx.fontKerning = style.fontKerning === 'none' ? 'none' : 'normal';\n}\n\n/** Format a letter-spacing value (px) as a canvas `ctx.letterSpacing` string. */\nfunction formatLetterSpacing(value: number): string {\n // Negative letter-spacing is valid and narrows text — Chrome applies it per\n // character (trailing included). Clamping it to 0 measured text wider than\n // the browser renders it, causing earlier/extra line wraps. Guard against\n // non-finite values (undefined/NaN), which would produce an invalid\n // \"undefinedpx\"/\"NaNpx\" string that canvas silently ignores.\n return Number.isFinite(value) && value !== 0 ? `${value}px` : '0px';\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.fontVariantCaps}|${style.fontWeight}|${style.fontSize}|${style.fontFamily}`;\n const cached = _fontStringCache.get(key);\n if (cached) return cached;\n const parts: string[] = [];\n // CSS font shorthand order: style, variant, weight, size, family.\n if (style.fontStyle !== 'normal') parts.push(style.fontStyle);\n if (style.fontVariantCaps === 'small-caps') parts.push('small-caps');\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 // Capitalize the first letter of each word. A mid-word apostrophe is NOT a\n // word boundary (UAX#29), so \"o'clock\" → \"O'clock\", not \"O'Clock\".\n case 'capitalize': return text.replace(/(^|[\\s\\p{P}])(\\p{L})/gu, (m, p, c) =>\n p === \"'\" || p === '’' ? m : 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 * Baseline shift (canvas pixels, positive = downward) for a vertical-align\n * value, applied on top of the line baseline. Returns 0 for 'baseline' and for\n * the line-box-relative keywords 'top'/'bottom' — those need a second layout\n * pass (the box position depends on the final line box it helps size), so they\n * fall back to baseline rather than being approximated wrongly.\n *\n * - super/sub legacy fixed fractions of the parent font size\n * - text-top/-bottom align the box's ascent/descent edge with the line's\n * - middle box midpoint at parent baseline + half the x-height\n * - <length>/<%> raise (positive value) by the length / % of line-height\n */\nfunction verticalAlignShift(\n va: string,\n wAscent: number, wDescent: number,\n parentFontSize: number, maxAscent: number, maxDescent: number,\n lineHeight: number,\n): number {\n switch (va) {\n case 'super': return -parentFontSize * 0.4;\n case 'sub': return parentFontSize * 0.26;\n case 'text-top': return -(maxAscent - wAscent);\n case 'text-bottom': return maxDescent - wDescent;\n case 'middle': return -(parentFontSize * 0.25) - (wDescent - wAscent) / 2;\n default: {\n // baseline / top / bottom / '' all parseFloat to NaN → 0 (callers gate\n // on isShiftedVAlign, so those never actually reach here).\n const n = parseFloat(va);\n if (!Number.isFinite(n)) return 0;\n return va.endsWith('%') ? -(n / 100) * lineHeight : -n;\n }\n }\n}\n\n/** True when a vertical-align value moves content off the baseline. */\nfunction isShiftedVAlign(va: string): boolean {\n return va !== 'baseline' && va !== 'top' && va !== 'bottom' && va !== '';\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 /**\n * No soft-wrap opportunity before this word: it abuts the previous word with\n * no whitespace (e.g. adjacent inline spans `<span>a</span><span>b</span>`),\n * so the browser treats them as one unbreakable unit at that boundary.\n */\n noBreakBefore?: 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/** True for atomic inline-block words (boxOpen && boxClose && text together). */\nfunction isAtomicInlineBlock(w: Word): boolean {\n return !!(w.boxOpen && w.boxClose && w.text);\n}\n\n/**\n * Truncate a PositionedLine's trailing words and append \"…\" so the line\n * fits within maxWidth. Used by `-webkit-line-clamp` to mark the visible\n * cut-off on the Nth line.\n *\n * Trim strategy:\n * 1. Pick the style of the last NON-empty, NON-atomic-inline-block word\n * — so the ellipsis font matches the surrounding text, not the button\n * or pill it was sitting next to.\n * 2. Drop trailing isSpace words (genuine spaces only — box markers carry\n * padding/border that we must keep).\n * 3. Back-trim: pop trailing non-space words until ellipsis fits. If we\n * end up with a single text word that STILL doesn't fit, pop it too —\n * the ellipsis stands alone rather than overflowing the container.\n * Box-open markers earlier on the line stay; they preserve inline-box\n * padding/border that the emit loop needs.\n * 4. Inherit boxStyle from the trailing context so inline `<span>`\n * backgrounds/borders extend across the ellipsis.\n */\nfunction applyEllipsisToLine(\n ctx: CanvasRenderingContext2D,\n line: PositionedLine,\n maxWidth: number,\n): void {\n // 1. Find the last word whose style should drive the ellipsis.\n // Skip empty-text markers AND atomic inline-blocks (their style is\n // the inline-block element's, not the surrounding text).\n let styleIdx = line.words.length - 1;\n while (\n styleIdx >= 0 &&\n (line.words[styleIdx].text === '' || isAtomicInlineBlock(line.words[styleIdx]))\n ) styleIdx--;\n if (styleIdx < 0) return;\n const lastStyle = line.words[styleIdx].style;\n const boxStyle = line.words[styleIdx].boxStyle;\n applyFont(ctx, lastStyle);\n // ALWAYS assign (don't gate on truthy) — otherwise a previous segment's\n // non-zero letter-spacing leaks into the ellipsis measurement.\n ctx.letterSpacing = `${lastStyle.letterSpacing || 0}px` as any;\n const ellipsisWidth = cachedMeasureWidth(ctx, '…');\n\n // Helper: pop trailing isSpace words. Box markers (text === '' with\n // boxOpen/boxClose) are NOT popped — they carry inline-box padding the\n // emit loop relies on.\n const popTrailingSpaces = () => {\n while (\n line.words.length > 0 &&\n line.words[line.words.length - 1].isSpace\n ) {\n const r = line.words.pop()!;\n line.totalWidth -= r.width;\n }\n };\n\n // 2. Strip purely trailing whitespace.\n popTrailingSpaces();\n\n // 3. Back-trim non-space text words until the ellipsis fits.\n // Atomic inline-blocks are non-space too; they pop along with words.\n const isTrimmableText = (w: Word) =>\n !w.isSpace && w.text !== '' && !w.boxOpen && !w.boxClose;\n while (\n line.totalWidth + ellipsisWidth > maxWidth &&\n line.words.length > 0\n ) {\n const last = line.words[line.words.length - 1];\n if (!isTrimmableText(last) && !isAtomicInlineBlock(last)) break;\n line.totalWidth -= last.width;\n line.words.pop();\n popTrailingSpaces();\n }\n\n // 4. Append the ellipsis. Inherit boxStyle so inline-span backgrounds /\n // borders extend over the ellipsis.\n const ellipsisWord: Word = {\n text: '…',\n width: ellipsisWidth,\n style: lastStyle,\n isSpace: false,\n boxStyle,\n };\n line.words.push(ellipsisWord);\n line.totalWidth += ellipsisWidth;\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 // unicode-bidi: bidi-override (e.g. <bdo dir=\"rtl\">) forces visual order.\n // For an RTL override, reverse both the characters of each descendant run\n // and the order of the runs, so the subtree renders right-to-left.\n const ub = n.style.unicodeBidi;\n const overrideRtl = (ub === 'bidi-override' || ub === 'isolate-override') &&\n n.style.direction === 'rtl';\n const overrideStart = runs.length;\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 if (overrideRtl && runs.length > overrideStart) {\n const seg = runs.splice(overrideStart);\n for (const r of seg) {\n if (r.text) {\n r.text = [...r.text].reverse().join('');\n // The glyphs are now in visual (reversed) order, so render them\n // left-to-right; otherwise renderText would right-anchor x and the\n // LTR emission (which set x as the left edge) would misposition them.\n r.style = { ...r.style, direction: 'ltr' };\n }\n }\n seg.reverse();\n runs.push(...seg);\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 // Then add a break opportunity AFTER \"?\" inside an otherwise-unbreakable\n // token (the URL query delimiter): Chrome wraps \"\\u2026/q3?\" | \"lang=ar&\\u2026\"\n // even with overflow-wrap:normal. It does NOT break at \"/\", \"&\", \"=\", \".\"\n // or \":\" (verified against the browser), so only \"?\" is split here. The\n // \"?\" stays with the preceding fragment; a trailing \"?\" (no follower) is\n // left intact. Fragments measure cumulatively so kerning stays accurate.\n const words = text\n .split(/([ \\t\\n\\r\\f\\v]+)/)\n .flatMap((w) =>\n /^[ \\t\\n\\r\\f\\v]+$/.test(w) ? [w] : w.split(/(?<=\\?)(?=.)/),\n );\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 = formatLetterSpacing(run.style.letterSpacing);\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 = formatLetterSpacing(run.style.letterSpacing);\n const text = applyTextTransform(run.text, run.style.textTransform);\n\n // Mark the first word produced from `startLen` as having no soft-wrap\n // opportunity before it when it directly abuts real text from a previous\n // run (adjacent inline elements with no whitespace between them). The\n // preceding word must be actual text — not a space, newline, empty\n // box-padding marker, or box edge — so a whitespace/padding boundary still\n // allows a break.\n const markGlue = (startLen: number) => {\n const first = allWords[startLen];\n if (!first || first.isSpace || !first.text || first.text === '\\n') return;\n const prev = allWords[startLen - 1];\n if (\n !prev || prev.isSpace || !prev.text.trim() ||\n prev.boxOpen || prev.boxClose\n ) return;\n // CJK and segmenter-driven scripts (Thai/Khmer/…) have break\n // opportunities between characters regardless of element boundaries, so\n // an element edge between them is NOT a no-break point. Only glue when\n // both sides are ordinary (Latin-like) text with no intrinsic break.\n const firstChar = [...first.text][0];\n const prevChar = [...prev.text][prev.text.length - 1];\n if (\n isCJK(firstChar) || isCJK(prevChar) ||\n needsSegmenter(first.text) || needsSegmenter(prev.text)\n ) return;\n first.noBreakBefore = true;\n };\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 const startLen = allWords.length;\n tokenizeString(ctx, parts[i], run, allWords);\n markGlue(startLen);\n }\n }\n } else {\n const startLen = allWords.length;\n tokenizeString(ctx, text, run, allWords);\n markGlue(startLen);\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\nlet _graphemeSegmenter: Intl.Segmenter | undefined;\nfunction getGraphemeSegmenter(): Intl.Segmenter | null {\n if (_graphemeSegmenter) return _graphemeSegmenter;\n if (typeof Intl !== 'undefined' && Intl.Segmenter) {\n _graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });\n return _graphemeSegmenter;\n }\n return null;\n}\n\nconst EMOJI_PICTOGRAPHIC = /\\p{Extended_Pictographic}/u;\n/**\n * Is this grapheme cluster an emoji that creates a line-break opportunity?\n * Restricted to emoji-presentation clusters (emoji planes, regional-indicator\n * flags, and ZWJ/VS16 sequences) so plain text symbols like ©/®/™ — which are\n * Extended_Pictographic but render as text and do NOT break — are excluded.\n */\nfunction isEmojiCluster(s: string): boolean {\n for (const ch of s) {\n const cp = ch.codePointAt(0)!;\n if (cp >= 0x1f000) return true; // emoji planes (incl. regional indicators)\n }\n if (s.includes('\\u200D') || s.includes('\\uFE0F')) {\n return EMOJI_PICTOGRAPHIC.test(s); // ZWJ sequence or VS16 emoji presentation\n }\n return false;\n}\n\n/**\n * Break a word into character-level pieces if it contains CJK/emoji 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 // Emoji form their own break opportunities (a run of emoji wraps between\n // clusters). Only meaningful when a grapheme segmenter is available so ZWJ\n // sequences / skin-tone / flag pairs stay intact.\n const hasEmoji = EMOJI_PICTOGRAPHIC.test(word.text) && !!getGraphemeSegmenter();\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 && !hasEmoji && !needsBreak) return [word];\n\n // overflow-wrap:break-word is a LAST RESORT — the browser first uses any\n // normal break opportunity inside the word (a hyphen) before breaking\n // mid-character. So split a hyphenated word at its hyphens first and only\n // char-break the segments that are themselves still too wide. (word-break:\n // break-all genuinely allows breaking between any two characters, so it\n // skips this and falls through to the char loop below.)\n if (needsBreak && word.style.wordBreak !== 'break-all' &&\n word.style.overflowWrap === 'break-word') {\n const segTexts = word.text.split(/(?<=-)(?!\\d)|(?<=[^\\d]-)/).filter((s) => s.length);\n if (segTexts.length > 1) {\n ctx.font = buildCanvasFont(word.style);\n ctx.letterSpacing = formatLetterSpacing(word.style.letterSpacing);\n const out: Word[] = [];\n for (const segText of segTexts) {\n const segWidth = cachedMeasureWidth(ctx, segText);\n if (segWidth <= contentWidth) {\n out.push({ ...word, text: segText, width: segWidth });\n } else {\n // Segment still overflows — char-break just this segment.\n out.push(...breakWordIfNeeded(ctx, { ...word, text: segText, width: segWidth }, contentWidth, 0));\n }\n }\n return out;\n }\n }\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 // Re-assert letter-spacing: tokenizeRuns may have left ctx at a later run's\n // value, but break points must use THIS word's letter-spacing.\n ctx.letterSpacing = formatLetterSpacing(word.style.letterSpacing);\n // When the word contains emoji, iterate by GRAPHEME cluster so multi-codepoint\n // emoji (ZWJ families, skin tones, flags) are never split mid-cluster.\n const seg = hasEmoji ? getGraphemeSegmenter() : null;\n const chars = seg\n ? [...seg.segment(word.text)].map((s) => s.segment)\n : [...word.text];\n const pieces: Word[] = [];\n\n let current = '';\n let currentWidth = 0;\n\n for (const char of chars) {\n // Emoji clusters each get their own word — a break opportunity between\n // adjacent emoji, matching the browser line breaker.\n if (hasEmoji && isEmojiCluster(char)) {\n if (current) {\n pieces.push({ ...word, text: current, width: currentWidth });\n current = '';\n currentWidth = 0;\n }\n pieces.push({ ...word, text: char, width: cachedMeasureWidth(ctx, char) });\n continue;\n }\n\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/** Punctuation that cannot start a line — stays with the preceding word. */\nconst TRAILING_PUNCT = /^[,.\\;:!?\\)\\]\\}'\"»›]+$/;\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 tabMetrics?: { interval: number; halfSpace: number },\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 (let wordIndex = 0; wordIndex < words.length; wordIndex++) {\n const word = words[wordIndex];\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 // Breaking a word that is split across a run boundary. A single word split\n // across adjacent inline runs (e.g. <span>E</span>xperience, a font-size\n // change mid-word, or <span>wel</span>l-being) is several Words glued by\n // `noBreakBefore`. Per-word break logic can't see the whole word, so its\n // internal break opportunities — hyphens, and break-word char points — are\n // lost and the unit overflows the edge. Detect the maximal glued chain\n // starting here and break it across the run boundaries like the browser.\n if (!word.isSpace && word.text && !word.noBreakBefore && !word.boxOpen && !word.boxClose) {\n let end = wordIndex;\n while (end + 1 < words.length) {\n const nx = words[end + 1];\n if (!nx.text || nx.isSpace || nx.boxOpen || nx.boxClose || !nx.noBreakBefore) break;\n end++;\n }\n if (end > wordIndex) {\n const breakWord = word.style.overflowWrap === 'break-word' || word.style.wordBreak === 'break-all';\n let combined = 0;\n for (let j = wordIndex; j <= end; j++) combined += words[j].width;\n // Flatten the chain into styled characters (per-run style retained).\n const cells: { ch: string; style: ResolvedStyle }[] = [];\n for (let j = wordIndex; j <= end; j++)\n for (const ch of [...words[j].text]) cells.push({ ch, style: words[j].style });\n const combinedText = cells.map((c) => c.ch).join('');\n // Hyphen break opportunities (same rule as the single-word hyphen path).\n const segTexts = combinedText.split(/(?<=-)(?!\\d)|(?<=[^\\d]-)/).filter((s) => s.length);\n const hyphenMode = segTexts.length > 1;\n const fitsLine = currentLine.totalWidth + combined <= effWidth();\n // A hyphen is an ordinary break opportunity — intervene whenever the\n // unit doesn't fit the remaining space. break-word is last-resort —\n // only when the unit can't fit a full line at all (otherwise the normal\n // flow + glued-tail fit check correctly wraps it whole to a fresh line).\n const enter = !fitsLine && (hyphenMode || (breakWord && combined > effWidth()));\n if (enter) {\n // Atomic units for breaking: hyphen segments, else the whole chain.\n const segs: { ch: string; style: ResolvedStyle }[][] = [];\n let ci = 0;\n for (const st of segTexts) {\n const len = [...st].length;\n segs.push(cells.slice(ci, ci + len));\n ci += len;\n }\n // Place a segment's cells onto the current line, splitting same-style\n // runs into pieces. When `chars` is set, wrap at the line edge between\n // characters (break-word); otherwise place atomically (it may overflow\n // its own line, e.g. a hyphen prefix wider than the container).\n const placeCells = (cs: { ch: string; style: ResolvedStyle }[], chars: boolean) => {\n let i = 0;\n while (i < cs.length) {\n const st = cs[i].style;\n applyFont(ctx, st);\n ctx.letterSpacing = formatLetterSpacing(st.letterSpacing);\n const lh = getLineHeight(ctx, st, useBulletProbe);\n const run: { ch: string; style: ResolvedStyle }[] = [];\n let cur = '';\n let curW = 0;\n while (i < cs.length && cs[i].style === st) {\n const ch = cs[i].ch;\n const candW = cachedMeasureWidth(ctx, cur + ch);\n if (chars && currentLine.totalWidth + candW > effWidth() &&\n (currentLine.words.length > 0 || cur)) {\n if (cur) {\n currentLine.words.push({ text: cur, width: curW, style: st, isSpace: false });\n currentLine.totalWidth += curW;\n currentLine.lineHeight = Math.max(currentLine.lineHeight, lh);\n }\n pushLine(true);\n afterHardBreak = false;\n cur = ch;\n curW = cachedMeasureWidth(ctx, ch);\n } else {\n cur += ch;\n curW = candW;\n }\n i++;\n }\n if (cur) {\n currentLine.words.push({ text: cur, width: curW, style: st, isSpace: false });\n currentLine.totalWidth += curW;\n currentLine.lineHeight = Math.max(currentLine.lineHeight, lh);\n afterHardBreak = false;\n }\n }\n };\n const measureSeg = (cs: { ch: string; style: ResolvedStyle }[]) => {\n let w = 0;\n let i = 0;\n while (i < cs.length) {\n const st = cs[i].style;\n let txt = '';\n while (i < cs.length && cs[i].style === st) { txt += cs[i].ch; i++; }\n applyFont(ctx, st);\n ctx.letterSpacing = formatLetterSpacing(st.letterSpacing);\n w += cachedMeasureWidth(ctx, txt);\n }\n return w;\n };\n // Pure break-word (no hyphen) is last-resort: move the whole word to a\n // fresh line first (using the preceding space), then break it there.\n if (!hyphenMode && currentLine.words.length > 0) {\n pushLine(true);\n afterHardBreak = false;\n }\n for (const seg of segs) {\n const segW = measureSeg(seg);\n if (currentLine.words.length > 0 && currentLine.totalWidth + segW > effWidth()) {\n pushLine(true);\n afterHardBreak = false;\n }\n // Char-break a segment only when break-word and it can't fit a line.\n placeCells(seg, breakWord && segW > effWidth());\n }\n wordIndex = end;\n continue;\n }\n }\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 // Glued tail: content immediately after this word that cannot start a new\n // line — trailing punctuation (\",.)]}…\"), an inline span's right\n // padding/border (empty boxClose markers), and a word continuation that\n // abuts this word across a run boundary with no soft-wrap opportunity\n // (noBreakBefore — e.g. one word split across two inline spans with\n // different font sizes). The browser includes all of it when deciding\n // whether this word fits, so the unit wraps together: if \"Music Experie\"\n // doesn't leave room for the glued \"nce\", the whole word wraps as one.\n // Stops at whitespace or the next breakable word.\n let gluedTailWidth = 0;\n for (let j = wordIndex + 1; j < words.length; j++) {\n const nw = words[j];\n if (nw.isSpace || nw.text === '\\n') break;\n const isPunct = !!nw.text && TRAILING_PUNCT.test(nw.text);\n const isCloseMarker = !nw.text && !!nw.boxClose;\n const isGluedCont = !!nw.text && !!nw.noBreakBefore;\n if (isPunct || isCloseMarker || isGluedCont) { gluedTailWidth += nw.width; continue; }\n break;\n }\n\n for (const piece of pieces) {\n const isLastPiece = piece === pieces[pieces.length - 1];\n // Only the last piece of the word carries the glued tail.\n const tail = isLastPiece ? gluedTailWidth : 0;\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 TRAILING_PUNCT.test(piece.text) &&\n currentLine.words.length > 0 &&\n !currentLine.words[currentLine.words.length - 1].isSpace;\n\n // A word that abuts the previous run with no whitespace has no soft-wrap\n // opportunity before it — keep it with the preceding word like trailing\n // punctuation. Only the FIRST piece carries the flag; a break-word split\n // inside the word may still wrap mid-word.\n const isGlued = piece === pieces[0] && piece.noBreakBefore &&\n currentLine.words.length > 0 &&\n !currentLine.words[currentLine.words.length - 1].isSpace;\n\n // Leading inline padding/border (an empty boxOpen marker) must not be\n // stranded at the end of a line — it belongs with the span's following\n // content (CSS applies padding-left at the box's start). Include the next\n // content word's width in this marker's fit test so the two wrap together\n // and the left padding lands on the new line with the content.\n let headExtra = 0;\n if (!piece.text && piece.boxOpen) {\n const next = words[wordIndex + 1];\n if (next && !next.isSpace && next.text) {\n // Only the next word's first BREAKABLE unit must stay with the leading\n // padding — the whole word for unbreakable Latin, but just the first\n // character for CJK / break-word (which wrap per character). Using the\n // whole word here would over-wrap a long CJK run that follows padding.\n const np = next.text.length > 1\n ? breakWordIfNeeded(ctx, next, effWidth(), 0)\n : [next];\n headExtra = np[0].width;\n }\n }\n\n // A soft-hyphen break point draws a visible '-' when the line breaks\n // right after this piece. Chrome only allows a break there if the prefix\n // PLUS the hyphen fits, so reserve the hyphen advance in the overflow\n // test — otherwise we pack one extra segment and the appended hyphen\n // overflows the line (breaking one segment later than the browser).\n let shReserve = 0;\n if (piece.isSoftHyphenBreak) {\n applyFont(ctx, piece.style);\n ctx.letterSpacing = formatLetterSpacing(piece.style.letterSpacing);\n shReserve = cachedMeasureWidth(ctx, '-');\n }\n\n // Would this piece overflow?\n if (!piece.isSpace && !isTrailingPunct && !isGlued && currentLine.words.length > 0 &&\n currentLine.totalWidth + piece.width + shReserve + tail + headExtra > effWidth()) {\n const overflow = currentLine.totalWidth + piece.width + shReserve + tail + headExtra - 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 (piece.isSoftHyphenBreak ? '-' : '');\n // Empty-text words carry non-glyph advance (inline padding/border\n // markers, inline-block margins) that measureText(fullText) misses —\n // add them back so padded inline spans aren't under-measured.\n let markerWidth = 0;\n for (const w of currentLine.words) if (!w.text) markerWidth += w.width;\n if (!piece.text) markerWidth += piece.width;\n const fullWidth = cachedMeasureWidth(ctx, fullText) + markerWidth + tail + headExtra;\n // Allow only a hair of sub-pixel overflow. measureText matches the\n // browser's rendered width to ~0.01px, so a larger slack would keep\n // lines the browser actually wraps (packing one extra word per\n // borderline line and drifting the whole document's breaks).\n if (fullWidth <= effWidth() + 0.02) {\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(/(?<=-)(?!\\d)|(?<=[^\\d]-)/);\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: advance to the next tab stop (stops measured from the content\n // edge). Chrome rule: when the next stop is closer than half a space\n // width, skip to the following stop (Blink Font::TabWidth).\n let pieceWidth = piece.width;\n if (piece.isTab) {\n const interval = tabMetrics?.interval || piece.width;\n const halfSpace = tabMetrics?.halfSpace ?? 0;\n const currentPos = (lines.length === 0 ? textIndent : 0) + currentLine.totalWidth;\n let advance = interval - (currentPos % interval);\n if (advance < halfSpace) advance += interval;\n pieceWidth = advance;\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(/(?<=-)(?!\\d)|(?<=[^\\d]-)/);\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 * Shared line budget for `-webkit-line-clamp` on a block container whose\n * text lives in block descendants (Chrome legacy `-webkit-box` semantics:\n * line boxes are counted across ALL descendants; the Nth line gets an\n * ellipsis and everything after it is dropped). Created in layoutBlock at\n * the clamped element and threaded through descendant layout calls.\n *\n * Known limitation: when the budget runs out exactly at a paragraph\n * boundary (Nth line is a paragraph's last line), following content is\n * dropped but the already-emitted Nth line gets no ellipsis — its layout\n * nodes were positioned before we learned more content follows.\n */\ninterface LineClampState {\n /** Line boxes still allowed before the cut. */\n remaining: number;\n /** Truncation point reached — all subsequent content is dropped. */\n exhausted: boolean;\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 clamp?: LineClampState,\n): { nodes: LayoutNode[]; height: number } {\n const results: LayoutNode[] = [];\n if (clamp && (clamp.exhausted || clamp.remaining <= 0)) {\n // An ancestor's clamp already used its line budget — drop this content.\n clamp.exhausted = true;\n return { nodes: results, height: 0 };\n }\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 // Tab stops follow the BLOCK's style, not the inline run the tab sits in:\n // Chrome sizes the interval as tab-size(8) × the block font's space advance\n // plus letter- and word-spacing (css-text-3 §tab-size) — verified against\n // the DOM: a tab inside a bold span still uses the regular-weight space.\n applyFont(ctx, node.style);\n const prevLetterSpacing = ctx.letterSpacing;\n ctx.letterSpacing = '0px';\n const blockSpaceWidth = cachedMeasureWidth(ctx, ' ');\n ctx.letterSpacing = prevLetterSpacing;\n const tabMetrics = {\n interval: (blockSpaceWidth + (node.style.letterSpacing || 0) + (node.style.wordSpacing || 0)) * 8,\n halfSpace: blockSpaceWidth / 2,\n };\n const lines = flowWordsIntoLines(ctx, words, contentWidth, node.style.whiteSpace, useBulletProbe, textIndent, tabMetrics);\n\n // `-webkit-line-clamp` / `line-clamp`: truncate to N lines and append a\n // CSS-style ellipsis (\"…\") to the Nth line, back-trimming trailing words\n // until the ellipsis fits within contentWidth. The budget comes from an\n // ancestor's shared clamp state when one is active (clamp on a block\n // container with block children), else from this element's own style.\n const clampN = clamp ? clamp.remaining : node.style.lineClamp;\n if (clampN > 0 && lines.length > clampN) {\n lines.length = clampN;\n const lastLine = lines[clampN - 1];\n // First line has reduced width because of text-indent; a cut on this\n // element's first line (effective budget of 1) hits it.\n const lineMaxForEllipsis = contentWidth - (clampN === 1 ? textIndent : 0);\n applyEllipsisToLine(ctx, lastLine, lineMaxForEllipsis);\n // Tag the truncated line so per-line alignment (text-align vs\n // text-align-last) still picks the right branch.\n lastLine.endedByHardBreak = true;\n if (clamp) {\n clamp.remaining = 0;\n clamp.exhausted = true;\n }\n } else if (clamp) {\n clamp.remaining -= lines.length;\n }\n\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 // When the line overflows its container, browsers fall back to start\n // alignment (per CSS Text 3 §7.1) instead of pushing the line outside\n // the box. Common trigger: wide letter-spacing on text that doesn't\n // wrap at letter boundaries (no break-word/break-all), where centering\n // would put glyphs at negative x. Sub-pixel tolerance avoids switching\n // to start for rounding noise on lines that visually fit.\n // Start edge differs by direction. LTR lines start at the left (x+indent).\n // RTL lines are anchored at the right, inset from the content's right edge\n // by text-indent — and lineMaxWidth already subtracts indent, so the RTL\n // right edge is x+lineMaxWidth. `align` here is physically resolved\n // (start/end → left/right via resolveDir), so RTL with align==='left'\n // (explicit left, or end) correctly falls through to left alignment.\n const overflows = line.totalWidth > lineMaxWidth + 0.5;\n let curX = x + indent;\n if (overflows) {\n // Overflow fallback: pin to the start edge (CSS Text 3 §7.1).\n curX = isRTL ? x + lineMaxWidth - line.totalWidth : x + indent;\n } else if (align === 'center') {\n curX = x + indent + (lineMaxWidth - line.totalWidth) / 2;\n } else if (align === 'right') {\n curX = (isRTL ? x + lineMaxWidth : x + indent + lineMaxWidth) - line.totalWidth;\n } else if (align === 'justify' && isRTL) {\n // RTL justify: anchor the right edge at the inset start; spaces expand left.\n curX = x + lineMaxWidth - line.totalWidth;\n }\n // Snapshot the line's left edge before LTR emission advances curX.\n const lineLeftX = curX;\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 // Off-baseline content (sub/sup/middle/lengths/...) does not establish\n // the line's baseline position — only baseline-aligned content does.\n if (isShiftedVAlign(word.style.verticalAlign)) continue;\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 off-baseline 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 // Parent font size for vertical-align positioning (used in expansion + text\n // emit) — the largest baseline-aligned font on the line.\n const lineNormalWords = line.words.filter(w =>\n w.text !== '' && !isShiftedVAlign(w.style.verticalAlign));\n const parentFontSize = lineNormalWords.length > 0\n ? Math.max(...lineNormalWords.map(w => w.style.fontSize)) : 0;\n\n // Expand line height if vertically-shifted content extends beyond the line\n // box. Browsers grow the line box to fit all content, but keep the normal\n // text baseline position unchanged.\n let lineTop = curY;\n let lineBottom = curY + lineHeight;\n for (const word of line.words) {\n if (word.text === '') continue;\n const va = word.style.verticalAlign;\n if (!isShiftedVAlign(va)) continue;\n if (parentFontSize === 0) break;\n\n const { ascent: wAscent, descent: wDescent } = getFontMetrics(ctx, word.style);\n const shiftedBaseline = lineBaselineY +\n verticalAlignShift(va, wAscent, wDescent, parentFontSize, maxAscent, maxDescent, lineHeight);\n\n const wordTop = shiftedBaseline - wAscent;\n const wordBottom = shiftedBaseline + wDescent;\n if (wordTop < lineTop) lineTop = wordTop;\n if (wordBottom > lineBottom) lineBottom = wordBottom;\n }\n const effectiveLineHeight = lineBottom - lineTop;\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 (word.isSpace && justifyExtraPerSpace > 0) {\n // Justify only: break the shaping group at the space and fold the\n // expansion into the inter-group advance so the line fills the width.\n // (Arabic does not join across spaces, so this is shaping-safe.)\n // When not justifying, spaces stay merged into the group text below\n // so the canvas BiDi engine can reorder embedded LTR runs/numbers.\n if (currentGroup) { groups.push(currentGroup); currentGroup = null; }\n pendingPad += word.width + justifyExtraPerSpace;\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 // This line belongs to an LTR block (we're in the !isRTL branch), so it\n // must be painted with an LTR base direction even when its first word is\n // RTL (an RTL span that wrapped onto this line). Without forcing LTR the\n // node inherits the first word's direction:'rtl' and the paint path\n // right-aligns the whole line at the left edge (x=curX), drawing it\n // off-screen. The canvas BiDi engine still reorders the embedded\n // Arabic/Hebrew runs within the LTR line.\n results.push({\n type: 'text',\n text: lineText,\n x: curX,\n y: lineBaselineY,\n width: measuredWidth,\n style: { ...textWords[0].style, direction: 'ltr' },\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 (isShiftedVAlign(va)) {\n const pfs = parentFontSize || word.style.fontSize;\n const { ascent: wA, descent: wD } = getFontMetrics(ctx, word.style);\n baselineY += verticalAlignShift(va, wA, wD, pfs, maxAscent, maxDescent, lineHeight);\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 // Emit a public LayoutLine record for this committed line.\n // bounds.width: justified lines fill lineMaxWidth (spaces expanded);\n // others use the measured words width.\n const lineWidth =\n align === 'justify' && justifyExtraPerSpace > 0\n ? lineMaxWidth\n : line.totalWidth;\n _lines.push({\n y: Math.round(lineBaselineY),\n text: line.words.map(w => w.text).join(''),\n bounds: {\n x: lineLeftX,\n // Use the actual visual top (may be < curY when a super pushes the\n // line box upward) so the rect covers ascenders/super content.\n y: lineTop,\n width: lineWidth,\n height: effectiveLineHeight,\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 clamp?: LineClampState,\n): { box: LayoutBox; height: number; marginBottomOut: number } {\n const style = node.style;\n\n // `-webkit-line-clamp` on a block container: start a shared line budget\n // here and thread it through descendant layout so the count spans block\n // children (Chrome legacy -webkit-box semantics). An ancestor's active\n // clamp wins over a nested one.\n if (!clamp && style.lineClamp > 0) {\n clamp = { remaining: style.lineClamp, exhausted: false };\n }\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, clamp);\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 // Line-clamp budget exhausted — everything below the cut is dropped,\n // including the margin trailing the cut line.\n if (clamp && (clamp.exhausted || clamp.remaining <= 0)) {\n clamp.exhausted = true;\n prevMarginBottom = 0;\n break;\n }\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, clamp);\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, clamp,\n );\n box.children.push(childBox);\n curY += childTotalHeight;\n // A child truncated by line-clamp clips its trailing margin too.\n prevMarginBottom = clamp?.exhausted ? 0 : 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 const isBullet = BULLET_MARKERS.has(style.listStyleType);\n // Gap between marker and content, matching Chrome (measured empirically):\n // - bullets: Chrome paints a symbol (diameter ascent/3) whose ink ends\n // 7px + ascent/3 before the content edge, centered ascent/3 above the\n // baseline. We keep the glyph but position its ink to land there.\n // - text markers (\"1.\"): Chrome's marker text carries a \". \" suffix, so\n // the gap is one space advance and the baseline is the line baseline.\n // `::marker { padding-inline-end: <length> }` overrides the gap — we honor\n // the 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\n let markerX: number;\n let markerY = baselineY;\n let markerDirection = 'ltr';\n const contentStartX = box.x + style.borderLeftWidth + style.paddingLeft;\n const boxRightEdge = box.x + box.width;\n if (isBullet) {\n const { ascent } = getFontMetrics(ctx, markerStyleObj);\n const m = ctx.measureText(node.listMarker);\n const gap = explicitGap !== undefined ? explicitGap : 7 + ascent / 3;\n if (isRTL) {\n // actualBoundingBoxLeft is positive when ink extends left of origin\n markerX = boxRightEdge + gap + (m.actualBoundingBoxLeft ?? 0);\n } else {\n markerX = contentStartX - gap - (m.actualBoundingBoxRight ?? markerWidth);\n }\n const glyphInkCenter =\n ((m.actualBoundingBoxAscent ?? 0) - (m.actualBoundingBoxDescent ?? 0)) / 2;\n markerY = baselineY - ascent / 3 + glyphInkCenter;\n } else {\n const gap = explicitGap !== undefined\n ? explicitGap\n : cachedMeasureWidth(ctx, ' ');\n if (isRTL) {\n // RTL: marker in the parent's right padding area (outside the li box).\n // Numbered markers (\"1.\") need RTL direction to display as \".1\".\n // With textAlign='right', x is the right edge — so add markerWidth.\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 markerX = contentStartX - markerWidth - gap;\n }\n }\n\n box.children.unshift({\n type: 'text',\n text: node.listMarker,\n x: markerX,\n y: markerY,\n width: markerWidth,\n style: { ...markerStyleObj, textDecorationLine: 'none', fontWeight: ms?.fontWeight ?? 400, fontStyle: ms?.fontStyle ?? 'normal', direction: markerDirection },\n });\n\n // Also publish the marker through the LayoutLine stream so result.lines\n // sees the bullet/number alongside the item text. Markers are added AFTER\n // inline content is laid out, so they don't go through layoutInlineContent.\n // The buildLayoutTree sort+merge step picks up the marker by its baseline.\n // RTL numbered markers store their right edge in markerX (textAlign trick).\n const markerLeftX = markerDirection === 'rtl' ? markerX - markerWidth : markerX;\n _lines.push({\n y: Math.round(baselineY),\n text: node.listMarker,\n bounds: {\n x: markerLeftX,\n y: box.y + style.borderTopWidth + style.paddingTop,\n width: markerWidth,\n height: lineHeight,\n },\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; lines: LayoutLine[] } {\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 _lines = [];\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 // Sort by baseline y, then by left edge so cross-cell content merges in\n // reading order (LTR). List markers sit at smaller x than their content\n // and so come first, producing \"• Item\" rather than \"Item •\".\n const sorted = _lines.slice().sort((a, b) =>\n (a.y - b.y) || (a.bounds.x - b.bounds.x)\n );\n const lines: LayoutLine[] = [];\n for (const candidate of sorted) {\n const last = lines[lines.length - 1];\n // Tolerance keys off the candidate's line height (matches the legacy\n // extractLines behavior). Using max(last, candidate) is symmetric but\n // grows after each merge as last.bounds.height becomes the union — that\n // leaks across rows in tight multi-column layouts.\n const tolerance = candidate.bounds.height * 0.5;\n if (last && Math.abs(candidate.y - last.y) < tolerance) {\n // Cross-cell merge: insert a space separator so the text stays\n // readable when N cells of a table row collapse into one LayoutLine.\n // Skip if either side already has a boundary space.\n const needsSep = last.text.length > 0 && candidate.text.length > 0 &&\n !/\\s$/.test(last.text) && !/^\\s/.test(candidate.text);\n last.text += (needsSep ? ' ' : '') + candidate.text;\n // Carry baseline forward so the next comparison uses the running\n // edge of the group, not the stale first element's baseline.\n last.y = Math.max(last.y, candidate.y);\n const x1 = Math.min(last.bounds.x, candidate.bounds.x);\n const y1 = Math.min(last.bounds.y, candidate.bounds.y);\n const x2 = Math.max(last.bounds.x + last.bounds.width, candidate.bounds.x + candidate.bounds.width);\n const y2 = Math.max(last.bounds.y + last.bounds.height, candidate.bounds.y + candidate.bounds.height);\n last.bounds = { x: x1, y: y1, width: x2 - x1, height: y2 - y1 };\n } else {\n lines.push({ y: candidate.y, text: candidate.text, bounds: { ...candidate.bounds } });\n }\n }\n return { root: box, height, lines };\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 */\nexport function 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 */\nexport function 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 // Chrome paints decorations as crisp integer-pixel bands. Snap the stroke\n // center so the band edges land on the pixel grid.\n y = Math.round(y - lineWidth / 2) + lineWidth / 2;\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 */\nexport function 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/** The solid fill color for text: -webkit-text-fill-color if set, else color. */\nexport function textFillColor(style: ResolvedStyle): string {\n return style.webkitTextFillColor && style.webkitTextFillColor !== 'transparent'\n ? style.webkitTextFillColor : style.color;\n}\n\n/**\n * Text decoration thickness for `auto`. Chromium paints an integer-pixel\n * band of max(1, floor(fontSize / 10)) regardless of font (measured across\n * 6 fonts × 16-64px against the DOM raster).\n */\nexport function decorationThickness(fontSize: number): number {\n return Math.max(1, Math.floor(fontSize / 10));\n}\n\n/** Apply the canvas stroke settings for -webkit-text-stroke. */\nexport function applyTextStroke(ctx: CanvasRenderingContext2D, style: ResolvedStyle): void {\n ctx.strokeStyle = style.webkitTextStrokeColor || style.color;\n ctx.lineWidth = style.webkitTextStrokeWidth;\n ctx.lineJoin = 'round';\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 (Number.isFinite(style.letterSpacing) && 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 (drawn behind the text). Cast the shadow from the shape that\n // is actually painted: the fill when it's visible, and/or the stroke. This\n // matters for stroked text with a transparent fill (color:transparent +\n // -webkit-text-stroke), where CSS casts the shadow from the stroke outline\n // rather than the invisible fill.\n const shadows = parseTextShadows(style.textShadow);\n if (shadows.length > 0) {\n const hasVisibleFill = isGradientText || !isFillTransparent;\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 if (hasVisibleFill) {\n ctx.fillStyle = isGradientText && gradientFill ? gradientFill : textFillColor(style);\n ctx.fillText(node.text, node.x, node.y);\n }\n if (isStrokedText) {\n applyTextStroke(ctx, style);\n ctx.strokeText(node.text, node.x, node.y);\n }\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 = textFillColor(style);\n ctx.fillText(node.text, node.x, node.y);\n }\n };\n\n const drawStroke = () => {\n if (!isStrokedText) return;\n ctx.save();\n applyTextStroke(ctx, style);\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 = decorationThickness(fontSize);\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\n if (style.textDecorationLine.includes('underline')) {\n // Chrome centers the underline ~0.105em below the baseline for every\n // font tested (measured against the DOM raster sweep). The -0.2px is a\n // rounding tiebreak: at fractional baselines (line-height 1.6/1.8/2.0)\n // Chrome resolves the pixel row downward less often than plain\n // rounding; empirically this cuts row-off-by-one cases 39 → 12 across\n // the sweep without disturbing integer baselines.\n const yOffset = fontSize * 0.105 - 0.2;\n drawDecorationLine(ctx, decoX, node.y + yOffset, textWidth, decoWidth, decoStyle, decoColor);\n }\n\n if (style.textDecorationLine.includes('line-through')) {\n // Chrome positions the strike from the font's OS/2 strikeout metric,\n // which canvas can't read. 0.33em above the baseline is the closest\n // single-formula fit (tuned against the DOM raster sweep; ±1px for\n // most fonts, ±2px worst case).\n const yOffset = -(fontSize * 0.33);\n drawDecorationLine(ctx, decoX, node.y + yOffset, textWidth, decoWidth, decoStyle, decoColor);\n }\n\n if (style.textDecorationLine.includes('overline')) {\n // Chrome hangs the overline band above the ascent line: its bottom\n // edge sits on the floored ascent pixel row, growing upward.\n const overlineY = Math.floor(node.y - decoAscent) - decoWidth / 2;\n drawDecorationLine(ctx, decoX, overlineY, 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, 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// ─── 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, lines } = buildLayoutTree(measureCtx, tree, width, useDomMeasurements, debug);\n const finalHeight = height || contentHeight;\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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"}