* Returns an array of indices where the string should be broken to fit in lines * of up to `width` characters. * * This handles double-width CJK characters * (https://en.wikipedia.org/wiki/Duospaced_font), and will break words if they * cannot fit in a line.
(
string: string,
charWidths: number[],
width: number
)
| 11 | * cannot fit in a line. |
| 12 | */ |
| 13 | function getLineBreaksForString( |
| 14 | string: string, |
| 15 | charWidths: number[], |
| 16 | width: number |
| 17 | ): number[] { |
| 18 | const lineBreaks: number[] = []; |
| 19 | let budget = width; |
| 20 | let curLineEnd = 0; |
| 21 | |
| 22 | function flushLine() { |
| 23 | lineBreaks.push(curLineEnd); |
| 24 | budget = width; |
| 25 | } |
| 26 | |
| 27 | function pushWord(startIndex: number, endIndex: number) { |
| 28 | let wordWidth = 0; |
| 29 | for (let i = startIndex; i < endIndex; i++) { |
| 30 | wordWidth += charWidths[i]; |
| 31 | } |
| 32 | |
| 33 | // word can fit on current line |
| 34 | if (wordWidth <= budget) { |
| 35 | curLineEnd = endIndex; |
| 36 | budget -= wordWidth; |
| 37 | return; |
| 38 | } |
| 39 | |
| 40 | // word can fit in the new line, so start a new one |
| 41 | if (wordWidth <= width) { |
| 42 | flushLine(); |
| 43 | curLineEnd = endIndex; |
| 44 | budget -= wordWidth; |
| 45 | return; |
| 46 | } |
| 47 | |
| 48 | // word is too long to fit in any line, so lets break it and push each |
| 49 | // part |
| 50 | for (let i = startIndex; i < endIndex; i++) { |
| 51 | const charLength = charWidths[i]; |
| 52 | if (budget < charLength) { |
| 53 | flushLine(); |
| 54 | } |
| 55 | budget -= charLength; |
| 56 | curLineEnd++; |
| 57 | } |
| 58 | } |
| 59 | |
| 60 | let prevIndex = 0; |
| 61 | let curIndex = 1; |
| 62 | let prevIsSpace = SPACE_REGEX.test(string[prevIndex]); |
| 63 | |
| 64 | // Add one word at a time |
| 65 | while (curIndex < string.length) { |
| 66 | const isSpace = SPACE_REGEX.test(string[curIndex]); |
| 67 | if (isSpace) { |
| 68 | pushWord(prevIndex, curIndex); |
| 69 | prevIndex = curIndex; |
| 70 | } else if (prevIsSpace) { |
no test coverage detected