(text: string, maxWidth: number)
| 493 | * - Hard-breaks overlong tokens at grapheme boundaries |
| 494 | */ |
| 495 | export function wrapTextToLines(text: string, maxWidth: number): readonly string[] { |
| 496 | if (text.length === 0 || maxWidth <= 0) return Object.freeze([]); |
| 497 | |
| 498 | const lines: string[] = []; |
| 499 | const paragraphs = text.split("\n"); |
| 500 | for (let p = 0; p < paragraphs.length; p++) { |
| 501 | const paragraph = paragraphs[p] ?? ""; |
| 502 | if (paragraph.length === 0) { |
| 503 | lines.push(""); |
| 504 | continue; |
| 505 | } |
| 506 | |
| 507 | const tokens = paragraph.match(/[^\s]+|\s+/g); |
| 508 | if (!tokens || tokens.length === 0) { |
| 509 | lines.push(""); |
| 510 | continue; |
| 511 | } |
| 512 | |
| 513 | let line = ""; |
| 514 | let lineWidth = 0; |
| 515 | |
| 516 | for (let i = 0; i < tokens.length; i++) { |
| 517 | const token = tokens[i] ?? ""; |
| 518 | const tokenWidth = measureTextCells(token); |
| 519 | |
| 520 | if (lineWidth + tokenWidth <= maxWidth) { |
| 521 | line += token; |
| 522 | lineWidth += tokenWidth; |
| 523 | continue; |
| 524 | } |
| 525 | |
| 526 | if (tokenWidth <= maxWidth) { |
| 527 | if (lineWidth > 0 || line.length > 0) lines.push(line); |
| 528 | line = token; |
| 529 | lineWidth = tokenWidth; |
| 530 | continue; |
| 531 | } |
| 532 | |
| 533 | if (lineWidth > 0 || line.length > 0) { |
| 534 | lines.push(line); |
| 535 | line = ""; |
| 536 | lineWidth = 0; |
| 537 | } |
| 538 | |
| 539 | const chunks = splitWordByWidth(token, maxWidth); |
| 540 | for (let j = 0; j < chunks.length; j++) { |
| 541 | const chunk = chunks[j] ?? ""; |
| 542 | const chunkWidth = measureTextCells(chunk); |
| 543 | if (lineWidth + chunkWidth <= maxWidth) { |
| 544 | line += chunk; |
| 545 | lineWidth += chunkWidth; |
| 546 | continue; |
| 547 | } |
| 548 | if (lineWidth > 0 || line.length > 0) lines.push(line); |
| 549 | line = chunk; |
| 550 | lineWidth = chunkWidth; |
| 551 | } |
| 552 | } |
no test coverage detected