bux API Reference 1.12.5
Static library of whatever are seen required in general purpose but not directly supported from Modern C++. Or whatever reusable originated from my side projects.
Loading...
Searching...
No Matches
UnicodeCvt.cpp
Go to the documentation of this file.
1#include "UnicodeCvt.h"
2#include <bit> // std::endian::*, std::byteswap()
3#include <charconv> // std::to_chars()
4#include <cstring> // memcmp()
5#include <istream> // std::istream
6#include <memory> // std::make_unique<>()
7#include <stdexcept> // std::runtime_error
8
9#ifdef _WIN32
10#pragma comment(lib, "Advapi32.lib") // IsTextUnicode()
11#include <windows.h> // Win32 API
12#else
13#include <errno.h> // errno
14#endif
15
16namespace {
17
18//
19// In-Module Types
20//
21class FC_ReadMem
22{
23public:
24
25 // Nonvirtuals
26 FC_ReadMem(std::string_view sv) noexcept: m_Src(sv.data()), m_End(sv.data()+sv.size()) {}
27 std::optional<char> operator()() noexcept
28 {
29 if (m_Src < m_End)
30 return *m_Src++;
31
32 return {};
33 }
34
35private:
36
37 // Data
38 const char *m_Src, *const m_End;
39};
40
41//
42// In-Module Constants
43//
44#ifdef _WIN32
45enum
46{
47 CHSETS_UTF8 = CP_UTF8
48};
49#else
50// shell command `iconv --list` to show available locales "in this host"
51constinit const char *const CHSETS_SJIS[] = {"CP932", "EUC-JP", "SHIFT_JIS", "SHIFT-JIS", "SJIS", 0};
52constinit const char *const CHSETS_GB[] = {"CP936", "EUC-CN", "GB18030", "GBK", 0};
53constinit const char *const CHSETS_KSC[] = {"CP949", "EUC-KR", "JOHAB", 0};
54constinit const char *const CHSETS_BIG5[] = {"CP950", "EUC-TW", "BIG5-HKSCS", "BIG5HKSCS", "BIG-5", "BIG5", 0};
55constinit const char *const CHSETS_UTF8[] = {"UTF-8", "UTF8", 0};
56constinit const char *const CHSETS_UTF7[] = {"UTF-7", "UTF7", 0};
57constinit const char *const CHSETS_UTF16LE[] = {"UTF-16LE", "UTF16LE", "UCS-2LE", "USC2LE", 0};
58constinit const char *const CHSETS_UTF16BE[] = {"UTF-16BE", "UTF16BE", "UCS-2BE", "USC2BE", 0};
59/* Not used due to the introduced algorithm "Identify UTF-32 wt BOM in OS-agnostic way"
60constinit const char *const CHSETS_UTF32LE[] = {"UTF-32LE", "UTF32LE", "UCS-4LE", "USC4LE", 0};
61constinit const char *const CHSETS_UTF32BE[] = {"UTF-32BE", "UTF32BE", "UCS-4BE", "USC4BE", 0};
62*/
63#endif
64
65//
66// In-Module Functions
67//
68int u32toutf8(T_Utf32 c, T_Utf8 *dst) noexcept
69{
70 int ret;
71 if (c < 0x80)
72 // 1 byte
73 {
74 ret =1;
75 *dst = T_Utf8(c);
76 }
77 else if (c < 0x800)
78 // 2 bytes
79 {
80 ret =2;
81 goto Encode;
82 }
83 else if (c < 0x10000)
84 // 3 bytes
85 {
86 ret =3;
87 goto Encode;
88 }
89 else if (c < 0x200000)
90 // 4 bytes
91 {
92 ret =4;
93 goto Encode;
94 }
95 else if (c < 0x4000000)
96 // 5 bytes
97 {
98 ret =5;
99 goto Encode;
100 }
101 else if (c < 0x80000000)
102 // 6 bytesguessCodePage()
103 {
104 ret =6;
105 goto Encode;
106 }
107 else
108 ret =-1;
109
110 return ret;
111Encode:
112 for (int i =ret; --i > 0;)
113 {
114 dst[i] = T_Utf8((c &0x3F) | 0x80);
115 c >>=6;
116 }
117 dst[0] = T_Utf8(c |((const unsigned char*)"\xC0\xE0\xF0\xF8\xFC")[ret-2]);
118 return ret;
119}
120
121} // namespace
122
123namespace bux {
124
125//
126// Constants
127//
128const T_Encoding ENCODING_UTF8 = CHSETS_UTF8;
129
130//
131// Function Defitions
132//
133std::string_view to_utf8(T_Utf32 uc)
134{
135 static thread_local T_Utf8 buf[MAX_UTF8];
136 const auto bytes = u32toutf8(uc, buf);
137 if (bytes <= 0)
138 {
139 char uc_hex[10];
140 auto ret = std::to_chars(uc_hex, uc_hex+sizeof uc_hex, uc, 16);
141 throw std::runtime_error{"u32toutf8(u+" + std::string{uc_hex,ret.ptr} + ") returns " + std::to_string(bytes)};
142 }
143 return {reinterpret_cast<char*>(buf), size_t(bytes)};
144}
145
146std::string to_utf8(C_UnicodeIn &&uin)
147{
148 T_Utf8 u8[MAX_UTF8];
149 int n;
150 std::string ret;
151 while ((n = uin.get(u8)) > 0)
152 ret.append(reinterpret_cast<char*>(u8), size_t(n));
153
154 if (n < 0)
155 throw std::runtime_error{"UTF-8 conversion error " + std::to_string(n)};
156
157 return ret;
158}
159
160//
161// Class Implementations
162//
164 m_Src(std::move(readc)),
165 m_CodePage(codepage)
166{
167 init();
168}
169
170C_UnicodeIn::C_UnicodeIn(std::string_view sv, T_Encoding codepage):
171 C_UnicodeIn(FC_ReadMem(sv), codepage)
172{
173}
174
175C_UnicodeIn::C_UnicodeIn(std::istream &in, T_Encoding codepage):
176 m_Src([&]()->std::optional<char> {
177 char ch;
178 if (static_cast<bool>(in.get(ch)))
179 return ch;
180 return {};
181 }),
182 m_CodePage(codepage)
199{
200 init();
201}
202
204{
205#ifndef _WIN32
206 reset_iconv();
207#endif
208}
209
211{
212 if (m_GetQ.empty())
213 {
214 if (m_ErrCode < 0)
215 // Error code persistsiconv --list
216 return m_ErrCode;
217
218 if (m_ReadMethod)
219 (this->*m_ReadMethod)();
220
221 if (lastError() <= 0)
222 // Error happens
223 return m_ErrCode;
224 }
225 c = m_GetQ.front();
226 m_GetQ.pop();
227 return 1;
228}
229
231{
232 T_Utf32 c;
233 int ret = get(c);
234 if (ret > 0)
235 {
236 if (c < 0x10000)
237 // Encode into a single word
238 *dst =T_Utf16(c);
239 else
240 // Encode into two words
241 {
242 ret =2;
243 c -=0x10000;
244 dst[0] =T_Utf16((c >>10) |0xD800);
245 dst[1] =T_Utf16((c &0x3FF) |0xDC00);
246 }
247 }
248 return ret;
249}
250
252{
253 T_Utf32 c;
254 int ret = get(c);
255 if (ret > 0)
256 ret = u32toutf8(c, dst);
257
258 return ret;
259}
260
261void C_UnicodeIn::ingestMBCS()
264{
265 if (auto size = m_Src.size())
266 {
267#ifdef _WIN32
268 const auto utf16 = std::make_unique<wchar_t[]>(size);
269 if (int wn = MultiByteToWideChar(m_CodePage, MB_ERR_INVALID_CHARS, m_Src.buffer(), int(size), utf16.get(), int(size)))
270 {
271 FC_ReadMem read({reinterpret_cast<char*>(utf16.get()), size_t(wn*2)});
272 C_Source src(std::move(read));
273 while (readUTF16(src, false));
274 m_Src.pop(size);
275 }
276 else
277 m_ErrCode = UIE_NO_UNICODE_TRANSLATION;
278#else
279 static constexpr const char *const TO_UCS4 = std::endian::native == std::endian::little? "UTF-32LE": "UTF-32BE";
280 static_assert(std::endian::little != std::endian::big &&
281 (std::endian::native == std::endian::little || std::endian::native == std::endian::big));
282 for (T_Encoding i = m_CodePage; *i && m_iconv == (iconv_t)(-1); ++i)
283 m_iconv = iconv_open(TO_UCS4, *i);
284
285 if (m_iconv == (iconv_t)(-1))
286 // Fail to initialize the corresponding iconv_t descriptor
287 {
288 m_ErrCode = UIE_NO_UNICODE_TRANSLATION;
289 return;
290 }
291 const auto ucs4 = std::make_unique<T_Utf32[]>(size);
292 size_t size_ucs4 = size * 4;
293 auto src = const_cast<char*>(m_Src.buffer());
294 auto dst = reinterpret_cast<char*>(ucs4.get());
295 if (size_t(-1) != iconv(m_iconv, &src, &size, &dst, &size_ucs4))
296 // Fully converted
297 {
298 for (const T_Utf32 *i = ucs4.get(); i < reinterpret_cast<T_Utf32*>(dst); m_GetQ.push(*i++));
299 m_Src.pop(m_Src.size());
300 }
301 else switch (errno)
302 {
303 case EILSEQ: // invalid multibyte sequence
304 m_ErrCode = UIE_ICONV_FAIL;
305 break;
306 case EINVAL: // incomplete multibyte sequence
307 for (const T_Utf32 *i = ucs4.get(); i < reinterpret_cast<T_Utf32*>(dst); m_GetQ.push(*i++));
308 m_Src.pop(m_Src.size()-size);
309 break;
310 case E2BIG: // output buffer overflow, which is impossible.
311 default:
312 m_ErrCode = UIE_INTERNAL;
313 }
314#endif
315 }
316}
317
318void C_UnicodeIn::init()
319{
320 // BOM encodings from https://en.wikipedia.org/wiki/Byte_order_mark#Byte-order_marks_by_encoding
321 m_Src.read(4);
322 switch (m_Src.size())
323 {
324 case 4:
325 switch (m_Src.getUtf32(0, false))
326 {
327 case 0xFEFF: // UTF-32 with BOM
328 m_Src.pop(4);
329 m_ReadMethod = &C_UnicodeIn::readUTF32;
330 m_BOMed = true;
331 return;
332 case 0xFFFE0000: // Reverse UTF-32 with BOM
333 m_Src.pop(4);
334 m_ReadMethod = &C_UnicodeIn::readReverseUTF32;
335 m_BOMed = true;
336 return;
337 }
338 [[fallthrough]];
339 case 3:
340 case 2:
341 switch (m_Src.getUtf16(0, false))
342 {
343 case 0xFEFF: // UTF-16 with BOM
344 m_Src.pop(2);
345 m_ReadMethod = &C_UnicodeIn::readUTF16;
346 m_BOMed = true;
347 return;
348 case 0xFFFE: // Reverse UTF-16 with BOM
349 m_Src.pop(2);
350 m_ReadMethod = &C_UnicodeIn::readReverseUTF16;
351 m_BOMed = true;
352 return;
353 default:
354 if (m_Src.size() >= 3 && 0 == memcmp(m_Src.buffer(), u8"\uFEFF", 3))
355 // UTF-8 with BOM
356 {
357 m_Src.pop(3);
358 setCodePage(CHSETS_UTF8);
359 m_ReadMethod = &C_UnicodeIn::readCodePage;
360 m_BOMed = true;
361 return;
362 }
363
364 // Infer the encoding from first-1000-bytes chunk (UTF-8, ACP, or something else ?)
365 if (!m_CodePage)
366 {
367 m_Src.read(1000);
368 const auto size = m_Src.size();
369
370 // Identify UTF-32 wt BOM in OS-agnostic way
371 if (size && size % 4 == 0)
372 {
373 size_t n_u32_chars = 0, n_u32rev_chars = 0;
374 const auto p_dwords = reinterpret_cast<const T_Utf32*>(m_Src.buffer());
375 const size_t n = size / 4;
376 for (size_t i = 0; i < n; ++i)
377 {
378 if (auto u32 = p_dwords[i])
379 {
380 bool matched = false;
381 if (u32 == (u32 & 0xFFFFFF))
382 {
383 matched = true;
384 ++n_u32_chars;
385 }
386 //-----------------------------
387 u32 = std::byteswap(u32);
388 if (u32 == (u32 & 0xFFFFFF))
389 {
390 matched = true;
391 ++n_u32rev_chars;
392 }
393 if (!matched)
394 goto PostCheckUTF32;
395 }
396 }
397
398 const auto n_overflow = n_u32_chars + n_u32rev_chars - n;
399 if (n_u32_chars <= n_overflow)
400 {
401 m_ReadMethod = &C_UnicodeIn::readReverseUTF32;
402 return;
403 }
404 if (n_u32rev_chars <= n_overflow)
405 {
406 m_ReadMethod = &C_UnicodeIn::readUTF32;
407 return;
408 }
409 }
410
411 PostCheckUTF32:
412#ifdef _WIN32
413 int mask = IS_TEXT_UNICODE_UNICODE_MASK;
414 if (IsTextUnicode(m_Src.buffer(), int(size), &mask) || mask)
415 {
416 m_ReadMethod = &C_UnicodeIn::readUTF16;
417 return;
418 }
419 mask = IS_TEXT_UNICODE_REVERSE_MASK;
420 if (IsTextUnicode(m_Src.buffer(), int(size), &mask) || mask)
421 {
422 m_ReadMethod = &C_UnicodeIn::readReverseUTF16;
423 return;
424 }
425#endif
426 // Guess harder
427 if (guessCodePage())
428 return;
429 }
430
431 // Read heading asciis off then we can distinguish CP_UTF8 from CP_ACP.
432 m_ReadMethod = &C_UnicodeIn::readASCII;
433 }
434 break;
435 case 1: // has to be ascii
436 if (*m_Src.buffer() &0x80)
437 m_ErrCode = UIE_INCOMPLETE_UNICODE;
438 else
439 m_GetQ.push(T_Utf8(*m_Src.buffer()));
440 break;
441 case 0: // empty string
442 break;
443 default:
444 m_ErrCode = UIE_INTERNAL;
445 }
446}
447
448void C_UnicodeIn::readASCII()
449{
450 m_Src.read(1);
451 if (m_Src.size())
452 {
453 const auto c = T_Utf8(*m_Src.buffer());
454#ifdef _WIN32
455 if (!(c &0x80))
456#else
457 if (!(c &0x80) && c)
458#endif
459 {
460 // Still an ASCII
461 m_GetQ.push(c);
462 return m_Src.pop(1);
463 }
464 }
465
466 // Is the rest CP_UTF8 or CP_ACP ?
467 m_Src.readTillCtrl();
468 if (m_CodePage)
469 {
470 ingestMBCS();
471 if (m_ErrCode != UIE_NO_UNICODE_TRANSLATION)
472 // Should be m_CodePage
473 {
474 m_ReadMethod = &C_UnicodeIn::readCodePage;
475 return;
476 }
477 }
478 guessCodePage();
479}
480
481bool C_UnicodeIn::guessCodePage()
482{
483 if (m_Src.size() < 2) [[unlikely]]
484 return false;
485
486 static constinit const T_Encoding MBCS_CODEPAGES[] ={
487#ifdef _WIN32
488 CP_UTF8, CP_ACP,
489 932, 936, 949, 950, 951, // from https://en.wikipedia.org/wiki/Windows_code_page#East_Asian_multi-byte_code_pages
490 CP_UTF7
491#else
492 CHSETS_UTF8, CHSETS_SJIS, CHSETS_GB, CHSETS_KSC, CHSETS_BIG5, CHSETS_UTF7, CHSETS_UTF16LE, CHSETS_UTF16BE
493#endif
494 };
495 for (auto i: MBCS_CODEPAGES)
496 {
497 m_ErrCode = UIE_EOF; // reset error code
498 setCodePage(i);
499 ingestMBCS();
500 if (m_ErrCode != UIE_NO_UNICODE_TRANSLATION)
501 {
502 m_ReadMethod = &C_UnicodeIn::readCodePage;
503 return true;
504 }
505 }
506 return false;
507}
508
509bool C_UnicodeIn::readUTF16(C_Source &src, bool reverseWord)
510{
511 bool ret = false;
512 src.read(2);
513 const size_t read = src.size();
514 if (read >= 2)
515 {
516 const auto uc = src.getUtf16(0,reverseWord);
517 if (0xD800 <= uc && uc < 0xDC00)
518 // Hi word of 2-word encoding
519 {
520 src.read(4);
521 if (src.size() >= 4)
522 {
523 const T_Utf16 uc2 = src.getUtf16(1,reverseWord);
524 if (0xDC00 <= uc2 && uc2 < 0xE000)
525 // Low word of 2-word encoding
526 {
527 src.pop(4);
528 m_GetQ.push(T_Utf32((((uc&0x3FF)<<10)|(uc2&0x3FF))+0x10000));
529 ret =true;
530 }
531 else
532 // Anything lese is ill-formed
533 m_ErrCode = UIE_ILLFORMED_UNICODE;
534 }
535 else
536 m_ErrCode = UIE_INCOMPLETE_UNICODE;
537 }
538 else if (0xDC00 <= uc && uc < 0xE000)
539 // Low word of 2-word encoding - ill-fomed
540 m_ErrCode = UIE_ILLFORMED_UNICODE;
541 else
542 {
543 src.pop(2);
544 m_GetQ.push(uc);
545 ret = true;
546 }
547 }
548 else if (read > 0)
549 m_ErrCode = UIE_INCOMPLETE_UNICODE;
550
551 return ret;
552}
553
554void C_UnicodeIn::readUTF16()
555{
556 readUTF16(m_Src, false);
557}
558
559void C_UnicodeIn::readReverseUTF16()
560{
561 readUTF16(m_Src, true);
562}
563
564bool C_UnicodeIn::readUTF32(C_Source &src, bool reverseWord)
565{
566 bool ret = false;
567 src.read(4);
568 const size_t read = src.size();
569 if (read >= 4)
570 {
571 m_GetQ.push(src.getUtf32(0,reverseWord));
572 src.pop(4);
573 ret = true;
574 }
575 else if (read > 0)
576 m_ErrCode = UIE_INCOMPLETE_UNICODE;
577
578 return ret;
579}
580
581void C_UnicodeIn::readUTF32()
582{
583 readUTF32(m_Src, false);
584}
585
586void C_UnicodeIn::readReverseUTF32()
587{
588 readUTF32(m_Src, true);
589}
590
591void C_UnicodeIn::readCodePage()
592{
593 m_Src.readTillCtrl();
594 ingestMBCS();
595}
596
597void C_UnicodeIn::setCodePage(T_Encoding cp)
598{
599 m_CodePage = cp;
600#ifndef _WIN32
601 reset_iconv();
602#endif
603}
604
605#ifndef _WIN32
606void C_UnicodeIn::reset_iconv()
607{
608 if (m_iconv != (iconv_t)(-1))
609 {
610 iconv_close(m_iconv);
611 m_iconv = (iconv_t)(-1);
612 }
613}
614#endif
615
616C_UnicodeIn::C_Source::C_Source(FH_ReadChar &&readc) noexcept:
617 m_ReadCh(std::move(readc)),
618 m_AvailBeg(0)
619{
620}
621
622const char *C_UnicodeIn::C_Source::buffer() const noexcept
623{
624 return m_ReadBuf.data() + m_AvailBeg;
625}
626
627T_Utf16 C_UnicodeIn::C_Source::getUtf16(size_t pos, bool reverseWord) const
628{
629 const size_t off = m_AvailBeg + pos * 2;
630 if (off + 2 > m_ReadBuf.size())
631 throw std::runtime_error{"End of char " + std::to_string(off+2) + " passes end of buffer"};
632
633 auto ret = *reinterpret_cast<const T_Utf16*>(m_ReadBuf.data() + off);
634 return reverseWord? std::byteswap(ret): ret;
635}
636
637T_Utf32 C_UnicodeIn::C_Source::getUtf32(size_t pos, bool reverseWord) const
638{
639 const size_t off = m_AvailBeg + pos * 4;
640 if (off + 4 > m_ReadBuf.size())
641 throw std::runtime_error{"End of char " + std::to_string(off+4) + " passes end of buffer"};
642
643 auto ret = *reinterpret_cast<const T_Utf32*>(m_ReadBuf.data() + off);
644 return reverseWord? std::byteswap(ret): ret;
645}
646
647void C_UnicodeIn::C_Source::pop(size_t bytes)
648{
649 m_AvailBeg += bytes;
650 if (m_AvailBeg > m_ReadBuf.size())
651 {
652 m_AvailBeg -= bytes; // rollback
653 throw std::runtime_error{"m_AvailBeg overflow"};
654 }
655}
656
657void C_UnicodeIn::C_Source::read(size_t bytes)
658{
659 if (m_AvailBeg + bytes > m_ReadBuf.size())
660 {
661 if (m_AvailBeg)
662 {
663 m_ReadBuf.erase(0, m_AvailBeg);
664 m_AvailBeg = 0;
665 }
666 bytes -= m_ReadBuf.size();
667 for (size_t i = 0; i < bytes; ++i)
668 if (auto c = m_ReadCh())
669 m_ReadBuf += *c;
670 else
671 break;
672 }
673}
674
675void C_UnicodeIn::C_Source::readTillCtrl()
676{
677 if (m_AvailBeg == m_ReadBuf.size())
678 {
679 m_ReadBuf.clear();
680 m_AvailBeg = 0;
681 }
682
683 while (auto c = m_ReadCh())
684 {
685 m_ReadBuf += *c;
686 if (0 == (*c &0xE0))
687 // Control char
688 break;
689 }
690}
691
692size_t C_UnicodeIn::C_Source::size() const noexcept
693{
694 return m_ReadBuf.size() - m_AvailBeg;
695}
696
697} // namespace bux
int get(T_Utf32 &c)
~C_UnicodeIn() noexcept
C_UnicodeIn(FH_ReadChar &&readc, T_Encoding codepage=0)
int lastError() const noexcept
Definition UnicodeCvt.h:72
THE common namespace of bux library.
Definition AtomiX.cpp:3
std::string_view to_utf8(T_Utf32 uc)
const char *const * T_Encoding
Definition UnicodeCvt.h:53
std::function< std::optional< char >()> FH_ReadChar
Definition UnicodeCvt.h:48
void read(const std::string &src, size_t &off, T &data) noexcept
Definition Serialize.h:35
const T_Encoding ENCODING_UTF8
std::uint16_t T_Utf16
UTF-16: You need T_Utf16[2] to hold full range of unicode.
Definition UnicodeCvt.h:41
std::uint8_t T_Utf8
UTF-8: You need T_Utf8[4] to hold full range of unicode.
Definition UnicodeCvt.h:42
@ MAX_UTF8
Definition UnicodeCvt.h:24
@ UIE_ILLFORMED_UNICODE
Definition UnicodeCvt.h:30
@ UIE_EOF
Definition UnicodeCvt.h:29
@ UIE_INCOMPLETE_UNICODE
Definition UnicodeCvt.h:31
@ UIE_ICONV_FAIL
Definition UnicodeCvt.h:33
@ UIE_INTERNAL
Definition UnicodeCvt.h:34
@ UIE_NO_UNICODE_TRANSLATION
Definition UnicodeCvt.h:32
std::uint32_t T_Utf32
UTF-32 to cover the full range of codespace U+0000 ~ U+10FFFF.
Definition UnicodeCvt.h:40