// Run in the preview frame after fonts/pagination settle, rather than using text-cell tops. export function minimumNameInkClearance(selector: string): number { const name = document.querySelector(selector); const flow = document.querySelector('[data-cv-preview-flow]'); const context = document.createElement('canvas').getContext('2d'); if (!name || !flow || !context) throw new Error('Missing name or settled print fragment'); const clipTop = flow.getBoundingClientRect().top; const clearance: number[] = []; const walker = document.createTreeWalker(name, NodeFilter.SHOW_TEXT); let node; while ((node = walker.nextNode())) { const style = getComputedStyle(node.parentElement!); context.font = `${style.fontStyle} ${style.fontWeight} ${style.fontSize} ${style.fontFamily}`; let offset = 0; for (const character of node.textContent || '') { const range = document.createRange(); range.setStart(node, offset); range.setEnd(node, offset + character.length); offset += character.length; if (!character.trim()) continue; const rect = range.getClientRects()[0], glyph = context.measureText(character); if (!rect) throw new Error('Name glyph has no rendered position'); clearance.push(rect.top + glyph.fontBoundingBoxAscent - glyph.actualBoundingBoxAscent - clipTop); } } if (!clearance.length) throw new Error('Empty name cannot qualify glyph clearance'); return Math.min(...clearance); }