(characters: ClusteredChar[])
| 51 | * Returns the same array on bidi-capable terminals (no-op). |
| 52 | */ |
| 53 | export function reorderBidi(characters: ClusteredChar[]): ClusteredChar[] { |
| 54 | if (!needsBidi() || characters.length === 0) { |
| 55 | return characters |
| 56 | } |
| 57 | |
| 58 | // Build a plain string from the clustered chars to run through bidi |
| 59 | const plainText = characters.map(c => c.value).join('') |
| 60 | |
| 61 | // Check if there are any RTL characters — skip bidi if pure LTR |
| 62 | if (!hasRTLCharacters(plainText)) { |
| 63 | return characters |
| 64 | } |
| 65 | |
| 66 | const bidi = getBidi() |
| 67 | const { levels } = bidi.getEmbeddingLevels(plainText, 'auto') |
| 68 | |
| 69 | // Map bidi levels back to ClusteredChar indices. |
| 70 | // Each ClusteredChar may be multiple code units in the joined string. |
| 71 | const charLevels: number[] = [] |
| 72 | let offset = 0 |
| 73 | for (let i = 0; i < characters.length; i++) { |
| 74 | charLevels.push(levels[offset]!) |
| 75 | offset += characters[i]!.value.length |
| 76 | } |
| 77 | |
| 78 | // Get reorder segments from bidi-js, but we need to work at the |
| 79 | // ClusteredChar level, not the string level. We'll implement the |
| 80 | // standard bidi reordering: find the max level, then for each level |
| 81 | // from max down to 1, reverse all contiguous runs >= that level. |
| 82 | const reordered = [...characters] |
| 83 | const maxLevel = Math.max(...charLevels) |
| 84 | |
| 85 | for (let level = maxLevel; level >= 1; level--) { |
| 86 | let i = 0 |
| 87 | while (i < reordered.length) { |
| 88 | if (charLevels[i]! >= level) { |
| 89 | // Find the end of this run |
| 90 | let j = i + 1 |
| 91 | while (j < reordered.length && charLevels[j]! >= level) { |
| 92 | j++ |
| 93 | } |
| 94 | // Reverse the run in both arrays |
| 95 | reverseRange(reordered, i, j - 1) |
| 96 | reverseRangeNumbers(charLevels, i, j - 1) |
| 97 | i = j |
| 98 | } else { |
| 99 | i++ |
| 100 | } |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | return reordered |
| 105 | } |
| 106 | |
| 107 | function reverseRange<T>(arr: T[], start: number, end: number): void { |
| 108 | while (start < end) { |
no test coverage detected