* Wrap text to fit within a given width using canvas measurement. * Used as fallback when DOM-based extraction fails (e.g., large zoomed-out graphs).
(text: string, maxWidth: number, fontSize: number, fontWeight: string = '400')
| 153 | * Used as fallback when DOM-based extraction fails (e.g., large zoomed-out graphs). |
| 154 | */ |
| 155 | function wrapTextToWidth(text: string, maxWidth: number, fontSize: number, fontWeight: string = '400'): string[] { |
| 156 | const canvas = document.createElement('canvas'); |
| 157 | const ctx = canvas.getContext('2d'); |
| 158 | if (!ctx) return [text]; |
| 159 | |
| 160 | ctx.font = `${fontWeight} ${fontSize}px "DM Sans", "Inter", "Segoe UI", -apple-system, sans-serif`; |
| 161 | |
| 162 | const words = text.split(/\s+/); |
| 163 | const lines: string[] = []; |
| 164 | let currentLine = ''; |
| 165 | |
| 166 | for (const word of words) { |
| 167 | const testLine = currentLine ? `${currentLine} ${word}` : word; |
| 168 | const metrics = ctx.measureText(testLine); |
| 169 | |
| 170 | if (metrics.width > maxWidth && currentLine) { |
| 171 | // Current line is full, start new line |
| 172 | lines.push(currentLine); |
| 173 | currentLine = word; |
| 174 | } else { |
| 175 | currentLine = testLine; |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | // Add the last line |
| 180 | if (currentLine) { |
| 181 | lines.push(currentLine); |
| 182 | } |
| 183 | |
| 184 | // If a single word is too long, we need to break it with hyphens |
| 185 | const finalLines: string[] = []; |
| 186 | for (const line of lines) { |
| 187 | if (ctx.measureText(line).width > maxWidth && !line.includes(' ')) { |
| 188 | // Single word that's too long - break it |
| 189 | let remaining = line; |
| 190 | while (remaining.length > 0) { |
| 191 | let breakPoint = remaining.length; |
| 192 | for (let i = 1; i <= remaining.length; i++) { |
| 193 | const segment = remaining.substring(0, i) + (i < remaining.length ? '-' : ''); |
| 194 | if (ctx.measureText(segment).width > maxWidth && i > 1) { |
| 195 | breakPoint = i - 1; |
| 196 | break; |
| 197 | } |
| 198 | } |
| 199 | if (breakPoint < remaining.length) { |
| 200 | finalLines.push(remaining.substring(0, breakPoint) + '-'); |
| 201 | remaining = remaining.substring(breakPoint); |
| 202 | } else { |
| 203 | finalLines.push(remaining); |
| 204 | remaining = ''; |
| 205 | } |
| 206 | } |
| 207 | } else { |
| 208 | finalLines.push(line); |
| 209 | } |
| 210 | } |
| 211 | |
| 212 | return finalLines.length > 0 ? finalLines : [text]; |
no outgoing calls
no test coverage detected