| 101 | } |
| 102 | |
| 103 | bool computeVisualWordOrder(const std::vector<std::string>& words, bool paragraphIsRtl, |
| 104 | std::vector<uint16_t>& visualOrder) { |
| 105 | visualOrder.clear(); |
| 106 | const size_t nWords = words.size(); |
| 107 | if (nWords <= 1 || nWords > BIDI_MAX_LINE) return false; |
| 108 | |
| 109 | static bidi_char line[BIDI_MAX_LINE]; |
| 110 | int count = 0; |
| 111 | bool truncated = false; |
| 112 | |
| 113 | for (size_t w = 0; w < nWords && !truncated; w++) { |
| 114 | auto* p = reinterpret_cast<const unsigned char*>(words[w].c_str()); |
| 115 | while (*p) { |
| 116 | if (count >= BIDI_MAX_LINE) { |
| 117 | truncated = true; |
| 118 | break; |
| 119 | } |
| 120 | const uint32_t cp = utf8NextCodepoint(&p); |
| 121 | if (!cp || cp == REPLACEMENT_GLYPH) break; |
| 122 | line[count].origwc = line[count].wc = cp; |
| 123 | line[count].index = static_cast<uint16_t>(w); |
| 124 | count++; |
| 125 | } |
| 126 | |
| 127 | if (!truncated && w + 1 < nWords) { |
| 128 | if (count >= BIDI_MAX_LINE) { |
| 129 | truncated = true; |
| 130 | break; |
| 131 | } |
| 132 | line[count].origwc = line[count].wc = ' '; |
| 133 | line[count].index = static_cast<uint16_t>(nWords); |
| 134 | count++; |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | if (truncated || count == 0) return false; |
| 139 | |
| 140 | // Fast-path for homogeneous lines: skip UAX#9 if there's no mixing. |
| 141 | bool hasL = false, hasR = false; |
| 142 | for (int i = 0; i < count; i++) { |
| 143 | uchar bc = bidi_class(line[i].wc); |
| 144 | if (bc == L || bc == EN || bc == AN) |
| 145 | hasL = true; |
| 146 | else if (bc == R || bc == AL) |
| 147 | hasR = true; |
| 148 | } |
| 149 | |
| 150 | // Purely LTR line in RTL paragraph: identity order, but we might still need to reorder |
| 151 | // if some characters are mirrored or neutral resolution differs. |
| 152 | // Actually, UAX#9 rule L1/L2 says purely LTR in RTL para stays as is (identity). |
| 153 | // Purely RTL line: just reverse the words. |
| 154 | if (!hasL && hasR && paragraphIsRtl) { |
| 155 | visualOrder.reserve(nWords); |
| 156 | for (int i = static_cast<int>(nWords) - 1; i >= 0; i--) { |
| 157 | visualOrder.push_back(static_cast<uint16_t>(i)); |
| 158 | } |
| 159 | return true; |
| 160 | } |
no test coverage detected