Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
text_utf8.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <cstddef>
5#include <cstdint>
6#include <string>
7
8namespace pulp::canvas {
9
12std::size_t cluster_step(const std::string& text, std::size_t byte_offset,
13 bool forward);
14
19inline std::size_t safe_utf8_prefix_size(const std::string& text,
20 std::size_t byte_index) noexcept {
21 const std::size_t limit = std::min(byte_index, text.size());
22 std::size_t i = 0;
23 std::size_t last_good = 0;
24
25 auto cont = [](unsigned char b) noexcept { return (b & 0xC0) == 0x80; };
26 while (i < limit) {
27 const auto b0 = static_cast<unsigned char>(text[i]);
28 std::size_t len = 0;
29 if (b0 < 0x80) {
30 len = 1;
31 } else if (b0 >= 0xC2 && b0 <= 0xDF) {
32 len = 2;
33 } else if (b0 >= 0xE0 && b0 <= 0xEF) {
34 len = 3;
35 } else if (b0 >= 0xF0 && b0 <= 0xF4) {
36 len = 4;
37 } else {
38 break;
39 }
40
41 if (i + len > limit) break;
42 if (len >= 2 && !cont(static_cast<unsigned char>(text[i + 1]))) break;
43 if (len >= 3 && !cont(static_cast<unsigned char>(text[i + 2]))) break;
44 if (len >= 4 && !cont(static_cast<unsigned char>(text[i + 3]))) break;
45 if (len == 3) {
46 const auto b1 = static_cast<unsigned char>(text[i + 1]);
47 if ((b0 == 0xE0 && b1 < 0xA0) || (b0 == 0xED && b1 >= 0xA0)) break;
48 } else if (len == 4) {
49 const auto b1 = static_cast<unsigned char>(text[i + 1]);
50 if ((b0 == 0xF0 && b1 < 0x90) || (b0 == 0xF4 && b1 >= 0x90)) break;
51 }
52
53 i += len;
54 last_good = i;
55 }
56 return last_good;
57}
58
59namespace detail {
60
62 std::uint32_t codepoint = 0xFFFDu;
63 std::size_t bytes = 1;
64};
65
66inline bool utf8_continuation(unsigned char b) noexcept { return (b & 0xC0u) == 0x80u; }
67
69 std::size_t i) noexcept {
70 if (i >= text.size()) return {};
71 const auto b0 = static_cast<unsigned char>(text[i]);
72 if (b0 < 0x80u) return {b0, 1};
73
74 if (b0 >= 0xC2u && b0 <= 0xDFu && i + 1 < text.size()) {
75 const auto b1 = static_cast<unsigned char>(text[i + 1]);
76 if (utf8_continuation(b1))
77 return {static_cast<std::uint32_t>(((b0 & 0x1Fu) << 6) | (b1 & 0x3Fu)), 2};
78 }
79
80 if (b0 >= 0xE0u && b0 <= 0xEFu && i + 2 < text.size()) {
81 const auto b1 = static_cast<unsigned char>(text[i + 1]);
82 const auto b2 = static_cast<unsigned char>(text[i + 2]);
83 const bool valid_prefix = (b0 != 0xE0u || b1 >= 0xA0u) && (b0 != 0xEDu || b1 < 0xA0u);
84 if (valid_prefix && utf8_continuation(b1) && utf8_continuation(b2)) {
85 return {static_cast<std::uint32_t>(((b0 & 0x0Fu) << 12)
86 | ((b1 & 0x3Fu) << 6) | (b2 & 0x3Fu)), 3};
87 }
88 }
89
90 if (b0 >= 0xF0u && b0 <= 0xF4u && i + 3 < text.size()) {
91 const auto b1 = static_cast<unsigned char>(text[i + 1]);
92 const auto b2 = static_cast<unsigned char>(text[i + 2]);
93 const auto b3 = static_cast<unsigned char>(text[i + 3]);
94 const bool valid_prefix = (b0 != 0xF0u || b1 >= 0x90u) && (b0 != 0xF4u || b1 < 0x90u);
95 if (valid_prefix && utf8_continuation(b1) && utf8_continuation(b2) && utf8_continuation(b3)) {
96 return {static_cast<std::uint32_t>(((b0 & 0x07u) << 18)
97 | ((b1 & 0x3Fu) << 12) | ((b2 & 0x3Fu) << 6) | (b3 & 0x3Fu)), 4};
98 }
99 }
100
101 return {};
102}
103
104inline std::size_t utf16_units_for_codepoint(std::uint32_t cp) noexcept {
105 return cp >= 0x10000u ? 2u : 1u;
106}
107
108} // namespace detail
109
113inline std::size_t utf8_offset_for_utf16_offset(const std::string& text,
114 std::size_t utf16_offset) noexcept {
115 std::size_t utf16_cursor = 0;
116 std::size_t byte_cursor = 0;
117 while (byte_cursor < text.size()) {
118 if (utf16_cursor >= utf16_offset) return byte_cursor;
119 const auto scalar = detail::decode_utf8_scalar_for_offsets(text, byte_cursor);
120 const auto next_utf16 = utf16_cursor + detail::utf16_units_for_codepoint(scalar.codepoint);
121 if (utf16_offset < next_utf16) return byte_cursor;
122 byte_cursor += std::max<std::size_t>(1, scalar.bytes);
123 utf16_cursor = next_utf16;
124 if (utf16_cursor == utf16_offset) return byte_cursor;
125 }
126 return text.size();
127}
128
131inline std::size_t utf16_offset_for_utf8_offset(const std::string& text,
132 std::size_t byte_offset) noexcept {
133 byte_offset = safe_utf8_prefix_size(text, byte_offset);
134 std::size_t utf16_cursor = 0;
135 std::size_t byte_cursor = 0;
136 while (byte_cursor < byte_offset && byte_cursor < text.size()) {
137 const auto scalar = detail::decode_utf8_scalar_for_offsets(text, byte_cursor);
138 byte_cursor += std::max<std::size_t>(1, scalar.bytes);
139 utf16_cursor += detail::utf16_units_for_codepoint(scalar.codepoint);
140 }
141 return utf16_cursor;
142}
143
146inline bool is_utf16_scalar_boundary(const std::string& text,
147 std::size_t utf16_offset) noexcept {
148 const auto byte_offset = utf8_offset_for_utf16_offset(text, utf16_offset);
149 return utf16_offset_for_utf8_offset(text, byte_offset) == utf16_offset;
150}
151
156inline bool is_valid_utf16_scalar_range(const std::string& text,
157 std::int64_t start,
158 std::int64_t length,
159 std::int64_t minimum_start = 0) noexcept {
160 if (start < minimum_start || start < 0 || length <= 0) return false;
161 const auto units = utf16_offset_for_utf8_offset(text, text.size());
162 const auto unsigned_start = static_cast<std::uint64_t>(start);
163 const auto unsigned_length = static_cast<std::uint64_t>(length);
164 if (unsigned_length > units || unsigned_start > units - unsigned_length)
165 return false;
166 return is_utf16_scalar_boundary(text, static_cast<std::size_t>(start)) &&
168 text, static_cast<std::size_t>(start + length));
169}
170
171} // namespace pulp::canvas
std::size_t utf16_units_for_codepoint(std::uint32_t cp) noexcept
Definition text_utf8.hpp:104
Utf8ScalarForOffsets decode_utf8_scalar_for_offsets(const std::string &text, std::size_t i) noexcept
Definition text_utf8.hpp:68
bool utf8_continuation(unsigned char b) noexcept
Definition text_utf8.hpp:66
Definition affine_transform.hpp:24
std::size_t utf8_offset_for_utf16_offset(const std::string &text, std::size_t utf16_offset) noexcept
Convert a UTF-16 code-unit offset into the nearest safe UTF-8 byte offset.
Definition text_utf8.hpp:113
std::size_t safe_utf8_prefix_size(const std::string &text, std::size_t byte_index) noexcept
Return the largest prefix length <= byte_index that ends on a valid UTF-8 scalar boundary.
Definition text_utf8.hpp:19
bool is_valid_utf16_scalar_range(const std::string &text, std::int64_t start, std::int64_t length, std::int64_t minimum_start=0) noexcept
Validate one positive, non-overlapping UTF-16 range against UTF-8 text.
Definition text_utf8.hpp:156
bool is_utf16_scalar_boundary(const std::string &text, std::size_t utf16_offset) noexcept
True when a UTF-16 code-unit offset falls between Unicode scalars rather than between the two code un...
Definition text_utf8.hpp:146
std::size_t utf16_offset_for_utf8_offset(const std::string &text, std::size_t byte_offset) noexcept
Convert a UTF-8 byte offset into a UTF-16 code-unit offset.
Definition text_utf8.hpp:131
std::size_t cluster_step(const std::string &text, std::size_t byte_offset, bool forward=true)
Grapheme-aware cursor step for TextEditor.
@ text
Definition scene_node.hpp:96
std::uint32_t codepoint
Definition text_utf8.hpp:62
std::size_t bytes
Definition text_utf8.hpp:63