Sourcemeta Core 0.0.0
Loading...
Searching...
No Matches
unicode.h
1#ifndef SOURCEMETA_CORE_UNICODE_H_
2#define SOURCEMETA_CORE_UNICODE_H_
3
4#ifndef SOURCEMETA_CORE_UNICODE_EXPORT
5#include <sourcemeta/core/unicode_export.h>
6#endif
7
8#include <sourcemeta/core/unicode_ucd.h>
9
10#include <cstddef> // std::size_t
11#include <cstdint> // std::uint8_t
12#include <istream> // std::istream
13#include <optional> // std::optional
14#include <ostream> // std::ostream
15#include <string> // std::string, std::u32string, std::wstring
16#include <string_view> // std::string_view, std::wstring_view
17#include <utility> // std::pair, std::make_pair
18
27
28namespace sourcemeta::core {
29
41SOURCEMETA_CORE_UNICODE_EXPORT
42auto codepoint_to_utf8(const char32_t codepoint) -> std::string;
43
58SOURCEMETA_CORE_UNICODE_EXPORT
59auto codepoint_to_utf8(const char32_t codepoint, std::ostream &output) -> void;
60
74SOURCEMETA_CORE_UNICODE_EXPORT
75auto codepoint_to_utf8(const char32_t codepoint, std::string &output) -> void;
76
91SOURCEMETA_CORE_UNICODE_EXPORT
92auto utf8_to_utf32(std::istream &input) -> std::optional<std::u32string>;
93
106SOURCEMETA_CORE_UNICODE_EXPORT
107auto utf8_to_utf32(const std::string_view input)
108 -> std::optional<std::u32string>;
109
122SOURCEMETA_CORE_UNICODE_EXPORT
123auto utf32_to_utf8(const std::u32string_view input) -> std::string;
124
144SOURCEMETA_CORE_UNICODE_EXPORT
145auto utf32_to_utf8_lenient(const std::u32string_view input) -> std::string;
146
162SOURCEMETA_CORE_UNICODE_EXPORT
163auto to_valid_utf8(const std::string_view input) -> std::string;
164
176SOURCEMETA_CORE_UNICODE_EXPORT
177auto utf8_to_wide(const std::string_view input) -> std::wstring;
178
189SOURCEMETA_CORE_UNICODE_EXPORT
190auto wide_to_utf8(const std::wstring_view input) -> std::string;
191
209inline constexpr auto utf8_lead_byte_size(const unsigned char byte)
210 -> std::uint8_t {
211 if (byte < 0x80) {
212 return 1;
213 }
214 if (byte >= 0xC2 && byte <= 0xDF) {
215 return 2;
216 }
217 if (byte >= 0xE0 && byte <= 0xEF) {
218 return 3;
219 }
220 if (byte >= 0xF0 && byte <= 0xF4) {
221 return 4;
222 }
223 return 0;
224}
225
248inline constexpr auto is_utf8_tail(const unsigned char lead,
249 const std::size_t position,
250 const unsigned char byte) -> bool {
251 const unsigned char lower{static_cast<unsigned char>(
252 position > 1 ? 0x80
253 : (lead == 0xE0 ? 0xA0 : (lead == 0xF0 ? 0x90 : 0x80)))};
254 const unsigned char upper{static_cast<unsigned char>(
255 position > 1 ? 0xBF
256 : (lead == 0xED ? 0x9F : (lead == 0xF4 ? 0x8F : 0xBF)))};
257 return byte >= lower && byte <= upper;
258}
259
273inline constexpr auto utf8_sequence_size(const std::string_view input)
274 -> std::size_t {
275 if (input.empty()) {
276 return 0;
277 }
278
279 const auto lead{static_cast<unsigned char>(input.front())};
280 const std::size_t size{utf8_lead_byte_size(lead)};
281 if (size == 0 || input.size() < size) {
282 return 0;
283 }
284
285 for (std::size_t index{1}; index < size; index += 1) {
286 if (!is_utf8_tail(lead, index, static_cast<unsigned char>(input[index]))) {
287 return 0;
288 }
289 }
290
291 return size;
292}
293
307inline constexpr auto is_utf8_continuation(const unsigned char byte) -> bool {
308 return byte >= 0x80 && byte <= 0xBF;
309}
310
323inline constexpr auto utf8_codepoint_count(const std::string_view input)
324 -> std::size_t {
325 std::size_t count{0};
326 for (const auto byte : input) {
327 if (!is_utf8_continuation(static_cast<unsigned char>(byte))) {
328 count += 1;
329 }
330 }
331
332 return count;
333}
334
350inline constexpr auto utf8_codepoint_within(const std::string_view input,
351 const std::size_t minimum,
352 const std::size_t maximum) -> bool {
353 const auto bytes{input.size()};
354
355 // A code point is at least one byte, so the count never exceeds the byte
356 // length: fewer bytes than the minimum cannot reach it
357 if (bytes < minimum) {
358 return false;
359 }
360
361 // A code point is at most four bytes, so the count is at least the byte
362 // length divided by four (rounded up): too many bytes cannot fit the maximum
363 const auto lower_bound{(bytes + 3) / 4};
364 if (lower_bound > maximum) {
365 return false;
366 }
367
368 // If the byte length already satisfies the maximum and the rounded-up lower
369 // bound already satisfies the minimum, the count must be in range
370 if (bytes <= maximum && lower_bound >= minimum) {
371 return true;
372 }
373
374 // Otherwise count, stopping as soon as the maximum is exceeded. Reaching here
375 // implies the byte length is at most four times the maximum, so this is
376 // bounded regardless of how long the input is
377 std::size_t count{0};
378 for (const auto byte : input) {
379 if (!is_utf8_continuation(static_cast<unsigned char>(byte))) {
380 count += 1;
381 if (count > maximum) {
382 return false;
383 }
384 }
385 }
386
387 return count >= minimum;
388}
389
404inline constexpr auto is_surrogate(const char32_t codepoint) -> bool {
405 return codepoint >= 0xD800 && codepoint <= 0xDFFF;
406}
407
422inline constexpr auto is_valid_codepoint(const char32_t codepoint) -> bool {
423 return codepoint <= 0x10FFFF && !is_surrogate(codepoint);
424}
425
441inline constexpr auto is_ucschar(const char32_t codepoint) -> bool {
442 if (codepoint >= 0xA0 && codepoint <= 0xD7FF) {
443 return true;
444 }
445 if (codepoint >= 0xF900 && codepoint <= 0xFDCF) {
446 return true;
447 }
448 if (codepoint >= 0xFDF0 && codepoint <= 0xFFEF) {
449 return true;
450 }
451 // Supplementary planes 1 through 14. Each plane allows 0..FFFD;
452 // FFFE and FFFF are noncharacters. Plane 14 starts at offset 0x1000
453 // rather than 0x0000.
454 if (codepoint < 0x10000 || codepoint > 0xEFFFD) {
455 return false;
456 }
457 if (codepoint >= 0xE0000 && codepoint < 0xE1000) {
458 return false;
459 }
460 return (codepoint & 0xFFFFU) <= 0xFFFDU;
461}
462
477inline constexpr auto is_iprivate(const char32_t codepoint) -> bool {
478 if (codepoint >= 0xE000 && codepoint <= 0xF8FF) {
479 return true;
480 }
481 if (codepoint >= 0xF0000 && codepoint <= 0xFFFFD) {
482 return true;
483 }
484 if (codepoint >= 0x100000 && codepoint <= 0x10FFFD) {
485 return true;
486 }
487 return false;
488}
489
505inline constexpr auto utf8_codepoint_byte_count(const char32_t codepoint)
506 -> std::uint8_t {
507 if (codepoint < 0x80) {
508 return 1;
509 }
510 if (codepoint < 0x800) {
511 return 2;
512 }
513 if (codepoint < 0x10000) {
514 return 3;
515 }
516 return 4;
517}
518
532SOURCEMETA_CORE_UNICODE_EXPORT
533auto combining_class(const char32_t codepoint) noexcept -> std::uint8_t;
534
551SOURCEMETA_CORE_UNICODE_EXPORT
552auto joining_type(const char32_t codepoint) noexcept -> JoiningType;
553
570SOURCEMETA_CORE_UNICODE_EXPORT
571auto bidi_class(const char32_t codepoint) noexcept -> BidiClass;
572
589SOURCEMETA_CORE_UNICODE_EXPORT
590auto script(const char32_t codepoint) noexcept -> UnicodeScript;
591
606SOURCEMETA_CORE_UNICODE_EXPORT
607auto is_combining_mark(const char32_t codepoint) noexcept -> bool;
608
625SOURCEMETA_CORE_UNICODE_EXPORT
626auto nfc_quick_check(const char32_t codepoint) noexcept -> NFCQuickCheck;
627
645SOURCEMETA_CORE_UNICODE_EXPORT
646auto canonical_decomposition(const char32_t codepoint) noexcept
647 -> std::u32string_view;
648
666SOURCEMETA_CORE_UNICODE_EXPORT
667auto canonical_composition(const char32_t starter,
668 const char32_t combining) noexcept
669 -> std::optional<char32_t>;
670
683SOURCEMETA_CORE_UNICODE_EXPORT
684auto nfc(const std::u32string_view input) -> std::u32string;
685
700SOURCEMETA_CORE_UNICODE_EXPORT
701auto is_nfc(const std::u32string_view input) -> bool;
702
720inline constexpr auto
721utf8_codepoint_length(const std::string_view input,
722 const std::string_view::size_type position)
723 -> std::size_t {
724 if (position >= input.size()) {
725 return 0;
726 }
727 const auto byte_0{static_cast<unsigned char>(input[position])};
728 const auto size{utf8_lead_byte_size(byte_0)};
729 if (size == 0 || position + size > input.size()) {
730 return 0;
731 }
732 if (size == 1) {
733 return 1;
734 }
735
736 // The second byte after the lead has tighter sub-ranges for specific leads
737 // (RFC 3629 ยง4) that exclude overlong encodings, surrogates, and code
738 // points above U+10FFFF
739 const auto byte_1{static_cast<unsigned char>(input[position + 1])};
740 bool byte_1_ok{false};
741 if (size == 2) {
742 byte_1_ok = is_utf8_continuation(byte_1);
743 } else if (size == 3) {
744 if (byte_0 == 0xE0) {
745 byte_1_ok = byte_1 >= 0xA0 && byte_1 <= 0xBF;
746 } else if (byte_0 == 0xED) {
747 byte_1_ok = byte_1 >= 0x80 && byte_1 <= 0x9F;
748 } else {
749 byte_1_ok = is_utf8_continuation(byte_1);
750 }
751 } else {
752 if (byte_0 == 0xF0) {
753 byte_1_ok = byte_1 >= 0x90 && byte_1 <= 0xBF;
754 } else if (byte_0 == 0xF4) {
755 byte_1_ok = byte_1 >= 0x80 && byte_1 <= 0x8F;
756 } else {
757 byte_1_ok = is_utf8_continuation(byte_1);
758 }
759 }
760
761 if (!byte_1_ok) {
762 return 0;
763 }
764
765 // Remaining continuation bytes (if any) are unconstrained beyond the
766 // continuation byte range
767 for (std::size_t index{2}; index < size; ++index) {
769 static_cast<unsigned char>(input[position + index]))) {
770 return 0;
771 }
772 }
773
774 return size;
775}
776
794inline constexpr auto utf8_decode(const std::string_view input,
795 const std::string_view::size_type position)
796 -> std::optional<std::pair<char32_t, std::size_t>> {
797 const auto size{utf8_codepoint_length(input, position)};
798 if (size == 0) {
799 return std::nullopt;
800 }
801
802 const auto lead{static_cast<unsigned char>(input[position])};
803 char32_t codepoint{0};
804 if (size == 1) {
805 codepoint = static_cast<char32_t>(lead);
806 } else if (size == 2) {
807 codepoint = static_cast<char32_t>(lead & 0x1FU);
808 } else if (size == 3) {
809 codepoint = static_cast<char32_t>(lead & 0x0FU);
810 } else {
811 codepoint = static_cast<char32_t>(lead & 0x07U);
812 }
813
814 for (std::size_t index{1}; index < size; ++index) {
815 const auto continuation{
816 static_cast<unsigned char>(input[position + index])};
817 codepoint = (codepoint << 6) | static_cast<char32_t>(continuation & 0x3FU);
818 }
819
820 return std::make_pair(codepoint, size);
821}
822
823} // namespace sourcemeta::core
824
825#endif
constexpr auto utf8_lead_byte_size(const unsigned char byte) -> std::uint8_t
Definition unicode.h:209
SOURCEMETA_CORE_UNICODE_EXPORT auto nfc(const std::u32string_view input) -> std::u32string
SOURCEMETA_CORE_UNICODE_EXPORT auto utf8_to_wide(const std::string_view input) -> std::wstring
constexpr auto is_ucschar(const char32_t codepoint) -> bool
Definition unicode.h:441
SOURCEMETA_CORE_UNICODE_EXPORT auto codepoint_to_utf8(const char32_t codepoint) -> std::string
constexpr auto utf8_codepoint_count(const std::string_view input) -> std::size_t
Definition unicode.h:323
constexpr auto utf8_sequence_size(const std::string_view input) -> std::size_t
Definition unicode.h:273
SOURCEMETA_CORE_UNICODE_EXPORT auto canonical_decomposition(const char32_t codepoint) noexcept -> std::u32string_view
SOURCEMETA_CORE_UNICODE_EXPORT auto utf32_to_utf8_lenient(const std::u32string_view input) -> std::string
JoiningType
Definition unicode_ucd.h:21
constexpr auto is_surrogate(const char32_t codepoint) -> bool
Definition unicode.h:404
SOURCEMETA_CORE_UNICODE_EXPORT auto is_nfc(const std::u32string_view input) -> bool
SOURCEMETA_CORE_UNICODE_EXPORT auto wide_to_utf8(const std::wstring_view input) -> std::string
constexpr auto utf8_codepoint_byte_count(const char32_t codepoint) -> std::uint8_t
Definition unicode.h:505
SOURCEMETA_CORE_UNICODE_EXPORT auto utf8_to_utf32(std::istream &input) -> std::optional< std::u32string >
SOURCEMETA_CORE_UNICODE_EXPORT auto bidi_class(const char32_t codepoint) noexcept -> BidiClass
constexpr auto is_utf8_tail(const unsigned char lead, const std::size_t position, const unsigned char byte) -> bool
Definition unicode.h:248
UnicodeScript
Definition unicode_ucd.h:252
SOURCEMETA_CORE_UNICODE_EXPORT auto joining_type(const char32_t codepoint) noexcept -> JoiningType
constexpr auto utf8_decode(const std::string_view input, const std::string_view::size_type position) -> std::optional< std::pair< char32_t, std::size_t > >
Definition unicode.h:794
BidiClass
Definition unicode_ucd.h:59
constexpr auto is_utf8_continuation(const unsigned char byte) -> bool
Definition unicode.h:307
SOURCEMETA_CORE_UNICODE_EXPORT auto script(const char32_t codepoint) noexcept -> UnicodeScript
constexpr auto utf8_codepoint_length(const std::string_view input, const std::string_view::size_type position) -> std::size_t
Definition unicode.h:721
SOURCEMETA_CORE_UNICODE_EXPORT auto is_combining_mark(const char32_t codepoint) noexcept -> bool
constexpr auto is_iprivate(const char32_t codepoint) -> bool
Definition unicode.h:477
constexpr auto is_valid_codepoint(const char32_t codepoint) -> bool
Definition unicode.h:422
NFCQuickCheck
Definition unicode_ucd.h:270
SOURCEMETA_CORE_UNICODE_EXPORT auto to_valid_utf8(const std::string_view input) -> std::string
constexpr auto utf8_codepoint_within(const std::string_view input, const std::size_t minimum, const std::size_t maximum) -> bool
Definition unicode.h:350
SOURCEMETA_CORE_UNICODE_EXPORT auto nfc_quick_check(const char32_t codepoint) noexcept -> NFCQuickCheck
SOURCEMETA_CORE_UNICODE_EXPORT auto combining_class(const char32_t codepoint) noexcept -> std::uint8_t
SOURCEMETA_CORE_UNICODE_EXPORT auto utf32_to_utf8(const std::u32string_view input) -> std::string
SOURCEMETA_CORE_UNICODE_EXPORT auto canonical_composition(const char32_t starter, const char32_t combining) noexcept -> std::optional< char32_t >