#pragma once #include #include namespace roro::utf8 { inline bool isContinuation(unsigned char c) { return (c & 0xC0) == 0x80; } inline std::string encode(uint32_t cp) { std::string out; if (cp < 0x80) { out += static_cast(cp); } else if (cp < 0x800) { out += static_cast(0xC0 | (cp >> 6)); out += static_cast(0x80 | (cp & 0x3F)); } else if (cp < 0x10000) { out += static_cast(0xE0 | (cp >> 12)); out += static_cast(0x80 | ((cp >> 6) & 0x3F)); out += static_cast(0x80 | (cp & 0x3F)); } else { out += static_cast(0xF0 | (cp >> 18)); out += static_cast(0x80 | ((cp >> 12) & 0x3F)); out += static_cast(0x80 | ((cp >> 6) & 0x3F)); out += static_cast(0x80 | (cp & 0x3F)); } return out; } // Byte index of the character start before / after `pos`. inline size_t previous(const std::string& s, size_t pos) { if (pos == 0) return 0; do pos--; while (pos > 0 && isContinuation(static_cast(s[pos]))); return pos; } inline size_t next(const std::string& s, size_t pos) { if (pos >= s.size()) return s.size(); do pos++; while (pos < s.size() && isContinuation(static_cast(s[pos]))); return pos; } // Longest prefix of at most `maxBytes` that doesn't cut a character. inline size_t clampToBoundary(const std::string& s, size_t maxBytes) { if (s.size() <= maxBytes) return s.size(); size_t pos = maxBytes; while (pos > 0 && isContinuation(static_cast(s[pos]))) pos--; return pos; } } // namespace roro::utf8