HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
text.hpp
1
2
3#pragma once
4
5#include "character.hpp"
6#include "character_attributes.hpp"
7#include "../unicode/module.hpp"
8#include "../i18n/module.hpp"
9#include "../utility/module.hpp"
10#include <string>
11#include <string_view>
12#include <algorithm>
13#include <iterator>
14#include <ranges>
15#include <concepts>
16
24template<>
25class std::char_traits<hi::character> {
26public:
28 using int_type = int64_t;
29 using off_type = size_t;
30 using pos_type = size_t;
31
32 static constexpr void assign(char_type& r, char_type const& a) noexcept
33 {
34 r = a;
35 }
36
37 static constexpr char_type *assign(char_type *p, size_t count, char_type a) noexcept
38 {
39 std::fill_n(p, count, a);
40 return p;
41 }
42
43 static constexpr bool eq(char_type a, char_type b) noexcept
44 {
45 return a == b;
46 }
47
48 static constexpr bool lt(char_type a, char_type b) noexcept
49 {
50 return a < b;
51 }
52
53 static constexpr char_type *move(char_type *dest, const char_type *src, std::size_t count) noexcept
54 {
55 auto first = src;
56 auto last = src + count;
57
58 if (dest >= first and dest < last) {
59 std::copy_backward(first, last, dest);
60 } else {
61 std::copy(first, last, dest);
62 }
63 return dest;
64 }
65
66 static constexpr char_type *copy(char_type *dest, const char_type *src, std::size_t count) noexcept
67 {
68 std::copy_n(src, count, dest);
69 return dest;
70 }
71
72 static constexpr int compare(const char_type *s1, const char_type *s2, std::size_t count) noexcept
73 {
74 auto r = std::lexicographical_compare_three_way(s1, s1 + count, s2, s2 + count);
75 return r == std::strong_ordering::equal ? 0 : r == std::strong_ordering::less ? -1 : 1;
76 }
77
78 static constexpr std::size_t length(const char_type *s) noexcept
79 {
80 auto ptr = s;
81
82 while (*ptr++ != '\0') {}
83 return std::distance(s, ptr);
84 }
85
86 static constexpr const char_type *find(const char_type *p, std::size_t count, const char_type& ch) noexcept
87 {
88 auto first = p;
89 auto last = p + count;
90
91 auto it = std::find(first, last, ch);
92 if (it == last) {
93 it = nullptr;
94 }
95 return it;
96 }
97
98 static constexpr char_type to_char_type(int_type c) noexcept
99 {
100 auto tmp = hi::char_cast<char_type::value_type>(c);
101 if (c < 0) {
102 tmp = 0;
103 }
104 return char_type{hi::intrinsic_t{}, tmp};
105 }
106
107 static constexpr int_type to_int_type(char_type c) noexcept
108 {
109 return hi::char_cast<int_type>(c.intrinsic());
110 }
111
112 static constexpr bool eq_int_type(int_type c1, int_type c2) noexcept
113 {
114 return c2 == c2;
115 }
116
117 static constexpr int_type eof() noexcept
118 {
119 return -1;
120 }
121
122 static constexpr int_type not_eof(int_type e) noexcept
123 {
124 if (e < 0) {
125 e = 0;
126 }
127 return e;
128 }
129};
130
131namespace hi { inline namespace v1 {
132
134using text_view = std::basic_string_view<character>;
135
144template<typename It, std::sentinel_for<It> ItEnd>
145inline void fixup_script(It first, ItEnd last, iso_15924 default_script) noexcept
146 requires(std::is_same_v<std::iter_value_t<It>, character>)
147{
148 // Overwrite the script of a character if it is specific in the Unicode
149 // script table.
150 auto last_language = iso_639{};
151 auto last_script = iso_15924{};
152 auto missing_script_count = 0_uz;
153 for (auto it = first; it != last; ++it) {
154 hilet code_point = (*it)[0];
155 auto attributes = it->attributes();
156 hilet language = attributes.language();
157 hilet script = attributes.script();
158
159 // Reset the last script when the language changes.
160 if (language != last_language) {
161 last_language = language;
162 last_script = {};
163 }
164
165 hilet new_script = [&] {
166 hilet udb_script = ucd_get_script(code_point);
167
168 if (udb_script != unicode_script::Zzzz and udb_script != unicode_script::Common) {
169 // This character is defined in the Unicode database to have a
170 // specific script.
171 return udb_script;
172
173 } else if (not script and last_script) {
174 // This character did not have a script, but a previous character
175 // from the same language did have a script.
176 return last_script;
177
178 } else {
179 return script;
180 }
181 }();
182
183 // If the new script is different update the character.
184 if (script != new_script) {
185 attributes.set_script(new_script);
186 it->set_attributes(attributes);
187 }
188
189 // We found a script for the character, remember it.
190 if (new_script) {
191 last_script = new_script;
192 } else {
193 ++missing_script_count;
194 }
195 }
196
197 if (missing_script_count == 0) {
198 // Every character has a script assigned.
199 return;
200 }
201
202 // In the second iteration we search backwards for scripts and assign them.
203 // Since in this iteration we have to assign a script we don't care about
204 // the language. And we fallback to the operating system's default script.
205 last_script = default_script;
206 for (auto rev_it = last; rev_it != first; --rev_it) {
207 hilet it = rev_it - 1;
208 auto attributes = it->attributes();
209 hilet script = attributes.script();
210
211 hilet new_script = script ? script : last_script;
212
213 // If the new script is different update the character.
214 if (script != new_script) {
215 attributes.set_script(script);
216 it->set_attributes(attributes);
217 }
218
219 // We found a script for the character, remember it.
220 if (new_script) {
221 last_script = new_script;
222 }
223 }
224}
225
229template<std::ranges::range R>
230inline void fixup_script(R& str, iso_15924 default_script) noexcept
231 requires(std::is_same_v<std::ranges::range_value_t<R>, character>)
232{
233 return fixup_script(std::ranges::begin(str), std::ranges::end(str), default_script);
234}
235
239[[nodiscard]] inline text to_text(gstring_view str, character_attributes default_attributes) noexcept
240{
241 auto r = text{};
242 r.reserve(str.size());
243 for (hilet c : str) {
244 r += character{c, default_attributes};
245 }
246 return r;
247}
248
252template<character_attribute... Args>
253[[nodiscard]] inline text to_text(gstring_view str, Args const&...args) noexcept
254{
255 return to_text(str, character_attributes{args...});
256}
257
261[[nodiscard]] inline text to_text(
262 std::string_view str,
263 unicode_normalize_config config,
264 character_attributes default_attributes) noexcept
265{
266 return to_text(to_gstring(str, config), default_attributes);
267}
268
272[[nodiscard]] inline text
273to_text(std::string_view str, character_attributes default_attributes) noexcept
274{
275 return to_text(to_gstring(str, unicode_normalize_config::NFC()), default_attributes);
276}
277
278
282template<character_attribute... Args>
283[[nodiscard]] inline text to_text(std::string_view str, unicode_normalize_config config, Args const&...args) noexcept
284{
285 return to_text(str, config, character_attributes{args...});
286}
287
291template<character_attribute... Args>
292[[nodiscard]] inline text to_text(std::string_view str, Args const&...args) noexcept
293{
294 return to_text(str, unicode_normalize_config::NFC(), character_attributes{args...});
295}
296
300[[nodiscard]] inline text to_text_with_markup(gstring_view str, character_attributes default_attributes) noexcept
301{
302 auto r = text{};
303 auto attributes = default_attributes;
304
305 auto in_command = false;
306 auto capture = std::string{};
307
308 auto output_incomplete_command = [&] {
309 r += character{'[', attributes};
310 for (hilet cap_c : capture) {
311 r += character{cap_c, attributes};
312 }
313 };
314
315 for (hilet c : str) {
316 if (in_command) {
317 if (c == ']') {
318 in_command = false;
319
320 auto output_bad_command = [&] {
321 output_incomplete_command();
322 r += character{']', attributes};
323 };
324
325 if (capture.empty()) {
326 output_bad_command();
327
328 } else if (capture.size() == 1) {
329 if (capture.front() == '.') {
330 attributes = default_attributes;
331 } else if (auto phrasing = to_text_phrasing(capture.front())) {
332 attributes.set_phrasing(*phrasing);
333 } else {
334 output_bad_command();
335 }
336
337 } else if (is_alpha(capture.front())) {
338 try {
339 attributes.set_language(hi::language_tag{capture}.expand());
340 } catch (...) {
341 output_bad_command();
342 }
343
344 } else {
345 output_bad_command();
346 }
347 capture.clear();
348
349 } else if (c == '[' and capture.empty()) {
350 in_command = false;
351 r += character{'[', attributes};
352
353 } else if (c.size() == 1 and c[0] <= 0x7f) {
354 capture += char_cast<char>(c[0]);
355
356 } else {
357 in_command = false;
358 output_incomplete_command();
359 capture.clear();
360 }
361
362 } else if (c == '[') {
363 in_command = true;
364
365 } else {
366 r += character{c, attributes};
367 }
368 }
369
370 if (not capture.empty()) {
371 output_incomplete_command();
372 }
373
374 fixup_script(r, default_attributes.script());
375 return r;
376}
377
381[[nodiscard]] inline text to_text_with_markup(std::string_view str, character_attributes default_attributes) noexcept
382{
383 return to_text_with_markup(to_gstring(str), default_attributes);
384}
385
389template<character_attribute... Attributes>
390[[nodiscard]] inline text to_text_with_markup(gstring_view str, Attributes const&...attributes) noexcept
391{
392 return to_text_with_markup(str, character_attributes{attributes...});
393}
394
398template<character_attribute... Attributes>
399[[nodiscard]] inline text to_text_with_markup(std::string_view str, Attributes const&...attributes) noexcept
400{
401 return to_text_with_markup(str, character_attributes{attributes...});
402}
403
407[[nodiscard]] constexpr gstring to_gstring(text_view str) noexcept
408{
409 auto r = gstring{};
410 r.reserve(str.size());
411 for (hilet c : str) {
412 r += c.grapheme();
413 }
414 return r;
415}
416
420[[nodiscard]] constexpr std::string to_string(text_view str) noexcept
421{
422 return to_string(to_gstring(str));
423}
424
428[[nodiscard]] constexpr std::wstring to_wstring(text_view str) noexcept
429{
430 return to_wstring(to_gstring(str));
431}
432
436[[nodiscard]] constexpr std::u32string to_u32string(text_view str) noexcept
437{
438 return to_u32string(to_gstring(str));
439}
440
444template<typename It, std::sentinel_for<It> ItEnd>
445inline void set_attributes(It first, ItEnd last, character_attributes attributes) noexcept
446 requires(std::is_same_v<std::iter_value_t<It>, character>)
447{
448 for (auto it = first; it != last; ++it) {
449 it->set_attributes(attributes);
450 }
451 fixup_script(first, last);
452}
453
457template<typename It, std::sentinel_for<It> ItEnd, character_attribute... Args>
458inline void set_attributes(It first, ItEnd last, Args const&...args) noexcept
459 requires(std::is_same_v<std::iter_value_t<It>, character>)
460{
461 return set_attributes(first, last, character_attributes{args...});
462}
463
467template<std::ranges::range R>
468inline void set_attributes(R& str, character_attributes attributes) noexcept
469 requires(std::is_same_v<std::ranges::range_value_t<R>, character>)
470{
471 return set_attributes(std::ranges::begin(str), std::ranges::end(str), attributes);
472}
473
477template<std::ranges::range R, character_attribute... Args>
478inline void set_attributes(R& str, Args const&...args) noexcept
479 requires(std::is_same_v<std::ranges::range_value_t<R>, character>)
480{
481 return set_attributes(str, character_attributes{args...});
482}
483
491[[nodiscard]] text to_text(std::integral auto const& value) noexcept
492{
493 return to_text(to_string(value));
494}
495
503[[nodiscard]] text to_text(std::floating_point auto const& value) noexcept
504{
505 return to_text(to_string(value));
506}
507
517template<std::integral T>
518[[nodiscard]] T from_text(text_view str, int base = 10)
519{
520 return from_string<T>(to_string(str), base);
521}
522
531template<std::floating_point T>
532[[nodiscard]] T from_text(text_view str)
533{
534 return from_string<T>(to_string(str));
535}
536
537}} // namespace hi::v1
Defines the standard HikoGUI character type.
#define hilet
Invariant should be the default for variables.
Definition utility.hpp:23
constexpr std::string to_string(std::u32string_view rhs) noexcept
Conversion from UTF-32 to UTF-8.
Definition to_string.hpp:215
constexpr std::u32string to_u32string(std::u32string_view rhs) noexcept
Identity conversion from UTF-32 to UTF-32.
Definition to_string.hpp:23
constexpr std::wstring to_wstring(std::u32string_view rhs) noexcept
Conversion from UTF-32 to wide-string (UTF-16/32).
Definition to_string.hpp:155
T from_text(text_view str, int base=10)
Convert a string to an integer.
Definition text.hpp:518
void fixup_script(It first, ItEnd last, iso_15924 default_script) noexcept
Fixup the iso_15924 script in text.
Definition text.hpp:145
void set_attributes(It first, ItEnd last, character_attributes attributes) noexcept
Change the attributes on a piece of text.
Definition text.hpp:445
DOXYGEN BUG.
Definition algorithm.hpp:13
geometry/margins.hpp
Definition cache.hpp:11
The standard HikoGUI character type.
Definition character.hpp:34
Definition character_attributes.hpp:13
Tag used in constructors to set the intrinsic value of that object.
Definition utility.hpp:242
T assign(T... args)
T eq(T... args)
T compare(T... args)
T copy_backward(T... args)
T copy(T... args)
T copy_n(T... args)
T count(T... args)
T distance(T... args)
T eof(T... args)
T eq_int_type(T... args)
T fill_n(T... args)
T find(T... args)
T length(T... args)
T move(T... args)
T not_eof(T... args)
T reserve(T... args)
T to_char_type(T... args)
T to_int_type(T... args)