| 270 | } |
| 271 | |
| 272 | std::vector<uint32_t> utf8_codepoints(std::string_view text, std::string_view label) { |
| 273 | std::vector<uint32_t> out; |
| 274 | out.reserve(engine::text::utf8_codepoint_count(text, label)); |
| 275 | for (size_t pos = 0; pos < text.size();) { |
| 276 | const auto ch = static_cast<unsigned char>(text[pos]); |
| 277 | size_t width = 0; |
| 278 | uint32_t codepoint = 0; |
| 279 | if (ch <= 0x7FU) { |
| 280 | width = 1; |
| 281 | codepoint = ch; |
| 282 | } else if ((ch & 0xE0U) == 0xC0U) { |
| 283 | width = 2; |
| 284 | codepoint = ch & 0x1FU; |
| 285 | } else if ((ch & 0xF0U) == 0xE0U) { |
| 286 | width = 3; |
| 287 | codepoint = ch & 0x0FU; |
| 288 | } else if ((ch & 0xF8U) == 0xF0U) { |
| 289 | width = 4; |
| 290 | codepoint = ch & 0x07U; |
| 291 | } else { |
| 292 | throw std::runtime_error(std::string(label) + " contains invalid UTF-8"); |
| 293 | } |
| 294 | if (pos + width > text.size()) { |
| 295 | throw std::runtime_error(std::string(label) + " contains truncated UTF-8"); |
| 296 | } |
| 297 | for (size_t i = 1; i < width; ++i) { |
| 298 | const auto cont = static_cast<unsigned char>(text[pos + i]); |
| 299 | if (!engine::text::is_utf8_continuation(cont)) { |
| 300 | throw std::runtime_error(std::string(label) + " contains invalid UTF-8 continuation byte"); |
| 301 | } |
| 302 | codepoint = (codepoint << 6U) | (cont & 0x3FU); |
| 303 | } |
| 304 | out.push_back(codepoint); |
| 305 | pos += width; |
| 306 | } |
| 307 | return out; |
| 308 | } |
| 309 | |
| 310 | bool is_japanese_codepoint(uint32_t codepoint) { |
| 311 | return (codepoint >= 0x3040U && codepoint <= 0x309FU) || |
no test coverage detected