| 86 | } |
| 87 | |
| 88 | function validateTextBoxPosition(slideData, bodyDimensions) { |
| 89 | const errors = []; |
| 90 | const slideHeightInches = bodyDimensions.height / PX_PER_IN; |
| 91 | const minBottomMargin = 0.5; // 0.5 inches from bottom |
| 92 | |
| 93 | for (const el of slideData.elements) { |
| 94 | // Check text elements (p, h1-h6, list) |
| 95 | if (['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'list'].includes(el.type)) { |
| 96 | const fontSize = el.style?.fontSize || 0; |
| 97 | const bottomEdge = el.position.y + el.position.h; |
| 98 | const distanceFromBottom = slideHeightInches - bottomEdge; |
| 99 | |
| 100 | if (fontSize > 12 && distanceFromBottom < minBottomMargin) { |
| 101 | const getText = () => { |
| 102 | if (typeof el.text === 'string') return el.text; |
| 103 | if (Array.isArray(el.text)) return el.text.find(t => t.text)?.text || ''; |
| 104 | if (Array.isArray(el.items)) return el.items.find(item => item.text)?.text || ''; |
| 105 | return ''; |
| 106 | }; |
| 107 | const textPrefix = getText().substring(0, 50) + (getText().length > 50 ? '...' : ''); |
| 108 | |
| 109 | errors.push( |
| 110 | `Text box "${textPrefix}" ends too close to bottom edge ` + |
| 111 | `(${distanceFromBottom.toFixed(2)}" from bottom, minimum ${minBottomMargin}" required)` |
| 112 | ); |
| 113 | } |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | return errors; |
| 118 | } |
| 119 | |
| 120 | // Helper: Add background to slide |
| 121 | async function addBackground(slideData, targetSlide, tmpDir) { |