Eradicate remaining uses of std::wstring on non-Windows.
[crawl.git] / crawl-ref / source / unicode.cc
1 /**
2  * @file
3  * @brief Conversions between Unicode and local charsets, string
4  *        manipulation functions that act on character types.
5 **/
6
7 #include "AppHdr.h"
8
9 #include <locale.h>
10 #include <stdio.h>
11 #include <string>
12 #include <string.h>
13 #include <limits.h>
14
15 #include "syscalls.h"
16 #include "unicode.h"
17
18 // there must be at least 4 bytes free, NOT CHECKED!
19 int wctoutf8(char *d, ucs_t s)
20 {
21     if (s < 0x80)
22     {
23         d[0] = s;
24         return 1;
25     }
26     if (s < 0x800)
27     {
28         d[0] = ( s >>  6)         | 0xc0;
29         d[1] = ( s        & 0x3f) | 0x80;
30         return 2;
31     }
32     if (s < 0x10000)
33     {
34         d[0] = ( s >> 12)         | 0xe0;
35         d[1] = ((s >>  6) & 0x3f) | 0x80;
36         d[2] = ( s        & 0x3f) | 0x80;
37         return 3;
38     }
39     if (s < 0x110000)
40     {
41         d[0] = ( s >> 18)         | 0xf0;
42         d[1] = ((s >> 12) & 0x3f) | 0x80;
43         d[2] = ((s >>  6) & 0x3f) | 0x80;
44         d[3] = ( s        & 0x3f) | 0x80;
45         return 4;
46     }
47     // Invalid char marker (U+FFFD).
48     d[0] = 0xef;
49     d[1] = 0xbf;
50     d[2] = 0xbd;
51     return 3;
52 }
53
54 int utf8towc(ucs_t *d, const char *s)
55 {
56     if (*s == 0)
57     {
58         *d = 0;
59         return 0;
60     }
61     if (!(*s & 0x80))
62     {
63         *d = *s;
64         return 1;
65     }
66     if ((*s & 0xc0) == 0x80)
67     {   // bare tail, invalid
68         *d = 0xFFFD;
69         int bad = 0;
70         do bad++; while ((s[bad] & 0xc0) == 0x80);
71         return bad;
72     }
73
74     int cnt;
75     ucs_t c;
76     if ((*s & 0xe0) == 0xc0)
77         cnt=2, c = *s & 0x1f;
78     else if ((*s & 0xf0) == 0xe0)
79         cnt=3, c = *s & 0x0f;
80     else if ((*s & 0xf8) == 0xf0)
81         cnt=4, c =*s & 0x07;
82     /* valid UTF-8, invalid Unicode
83     else if ((*s & 0xfc) == 0xf8)
84         cnt=5, c = *s & 0x03;
85     else if ((*s & 0xfe) == 0xfc)
86         cnt=6, c = *s & 0x01;
87     */
88     else
89     {   // 0xfe or 0xff, invalid
90         *d = 0xFFFD;
91         return 1;
92     }
93
94     for (int i = 1;  i < cnt; i++)
95     {
96         if ((s[i] & 0xc0) != 0x80)
97         {   // only tail characters are allowed here, invalid
98             *d = 0xFFFD;
99             return i;
100         }
101         c = (c << 6) | (s[i] & 0x3f);
102     }
103
104     if (c < 0xA0                        // illegal characters
105         || (c >= 0xD800 && c <= 0xDFFF) // UTF-16 surrogates
106         || (cnt == 3 && c < 0x800)      // overlong characters
107         || (cnt == 4 && c < 0x10000)    // overlong characters
108         || c > 0x10FFFF)                // outside Unicode
109     {
110         c = 0xFFFD;
111     }
112     *d = c;
113     return cnt;
114 }
115
116 #ifdef TARGET_OS_WINDOWS
117 // don't pull in wstring templates on other systems
118 std::wstring utf8_to_16(const char *s)
119 {
120     std::wstring d;
121     ucs_t c;
122
123     while (int l = utf8towc(&c, s))
124     {
125         s += l;
126         if (c >= 0x10000)
127         {
128             c -= 0x10000;
129             d.push_back(0xD800 + (c >> 10));
130             d.push_back(0xDC00 + (c & 0x3FF));
131         }
132         else
133             d.push_back(c);
134     }
135     return d;
136 }
137 #endif
138
139 std::string utf16_to_8(const utf16_t *s)
140 {
141     std::string d;
142     ucs_t c;
143
144     while (*s)
145     {
146         if (*s >= 0xD800 && *s <= 0xDBFF)
147             if (s[1] >= 0xDC00 && s[1] <= 0xDFFF)
148             {
149                 c = (((ucs_t)s[0]) << 10) + s[1] - 0x35fdc00;
150                 s++;
151             }
152             else
153                 c = 0xFFFD; // leading surrogate without its tail
154         else if (*s >= 0xDC00 && *s <= 0xDFFF)
155             c = 0xFFFD;     // unpaired trailing surrogate
156         else
157             c = *s;
158         s++;
159
160         char buf[4];
161         int l = wctoutf8(buf, c);
162         for (int i = 0; i < l; i++)
163             d.push_back(buf[i]);
164     }
165
166     return d;
167 }
168
169 std::string utf8_to_mb(const char *s)
170 {
171     std::string d;
172     ucs_t c;
173     int l;
174     mbstate_t ps;
175
176     memset(&ps, 0, sizeof(ps));
177     while ((l = utf8towc(&c, s)))
178     {
179         s += l;
180
181         char buf[MB_LEN_MAX];
182         int r = wcrtomb(buf, c, &ps);
183         if (r != -1)
184         {
185             for (int i = 0; i < r; i++)
186                 d.push_back(buf[i]);
187         }
188         else
189             d.push_back('?'); // TODO: try to transliterate
190     }
191     return d;
192 }
193
194 std::string mb_to_utf8(const char *s)
195 {
196     std::string d;
197     wchar_t c;
198     int l;
199     mbstate_t ps;
200
201     memset(&ps, 0, sizeof(ps));
202     // the input is zero-terminated, so third argument doesn't matter
203     while ((l = mbrtowc(&c, s, MB_LEN_MAX, &ps)))
204     {
205         if (l > 0)
206             s += l;
207         else
208         {   // invalid input, mark it and try to recover
209             s++;
210             c = 0xFFFD;
211         }
212
213         char buf[4];
214         int r = wctoutf8(buf, c);
215         for (int i = 0; i < r; i++)
216             d.push_back(buf[i]);
217     }
218     return d;
219 }
220
221 static std::string utf8_validate(const char *s)
222 {
223     std::string d;
224     ucs_t c;
225     int l;
226
227     while ((l = utf8towc(&c, s)))
228     {
229         s += l;
230
231         char buf[4];
232         int r = wctoutf8(buf, c);
233         for (int i = 0; i < r; i++)
234             d.push_back(buf[i]);
235     }
236     return d;
237 }
238
239 static bool _check_trail(FILE *f, const char* bytes, int len)
240 {
241     while (len--)
242     {
243         if (fgetc(f) != (unsigned char)*bytes++)
244         {
245             rewind(f);
246             return false;
247         }
248     }
249     return true;
250 }
251
252 FileLineInput::FileLineInput(const char *name)
253 {
254     f = fopen_u(name, "r");
255     if (!f)
256     {
257         seen_eof = true;
258         return;
259     }
260     seen_eof = false;
261
262     bom = BOM_NORMAL;
263     int ch = fgetc(f);
264     switch (ch)
265     {
266     case 0xEF:
267         if (_check_trail(f, "\xBB\xBF", 2))
268             bom = BOM_UTF8;
269         break;
270     case 0xFE:
271         if (_check_trail(f, "\xFF", 1))
272             bom = BOM_UTF16BE;
273         break;
274     case 0xFF:
275         if (_check_trail(f, "\xFE\x00\x00", 3))
276             bom = BOM_UTF32LE;
277         else if (_check_trail(f, "\xFF\xFE", 2)) // rewound
278             bom = BOM_UTF16LE;
279         break;
280     case 0x00:
281         if (_check_trail(f, "\x00\xFE\xFF", 3))
282             bom = BOM_UTF32BE;
283         break;
284     default:
285         ungetc(ch, f);
286     }
287 }
288
289 FileLineInput::~FileLineInput()
290 {
291     if (f)
292         fclose(f);
293 }
294
295 std::string FileLineInput::get_line()
296 {
297     ASSERT(f);
298     std::vector<utf16_t> win;
299     std::string out;
300     char buf[512];
301     ucs_t c;
302     int len;
303
304     switch (bom)
305     {
306     case BOM_NORMAL:
307         do
308         {
309             if (!fgets(buf, sizeof buf, f))
310             {
311                 seen_eof = true;
312                 break;
313             }
314             out += buf;
315             if (out[out.length() - 1] == '\n')
316             {
317                 out.erase(out.length() - 1);
318                 break;
319             }
320         } while (!seen_eof);
321         return mb_to_utf8(out.c_str());
322
323     case BOM_UTF8:
324         do
325         {
326             if (!fgets(buf, sizeof buf, f))
327             {
328                 seen_eof = true;
329                 break;
330             }
331             out += buf;
332             if (out[out.length() - 1] == '\n')
333             {
334                 out.erase(out.length() - 1);
335                 break;
336             }
337         } while (!seen_eof);
338         return utf8_validate(out.c_str());
339
340     case BOM_UTF16LE:
341         do
342         {
343             if (fread(buf, 2, 1, f) != 1)
344             {
345                 seen_eof = true;
346                 break;
347             }
348             c = ((uint32_t)((unsigned char)buf[0]))
349               | ((uint32_t)((unsigned char)buf[1])) << 8;
350             if (c == '\n')
351                 break;
352             win.push_back(c);
353         }
354         while (!seen_eof);
355         win.push_back(0);
356         return utf16_to_8(&win[0]);
357
358     case BOM_UTF16BE:
359         do
360         {
361             if (fread(buf, 2, 1, f) != 1)
362             {
363                 seen_eof = true;
364                 break;
365             }
366             c = ((uint32_t)((unsigned char)buf[1]))
367               | ((uint32_t)((unsigned char)buf[0])) << 8;
368             if (c == '\n')
369                 break;
370             win.push_back(c);
371         }
372         while (!seen_eof);
373         win.push_back(0);
374         return utf16_to_8(&win[0]);
375
376     case BOM_UTF32LE:
377         do
378         {
379             if (fread(buf, 4, 1, f) != 1)
380             {
381                 seen_eof = true;
382                 break;
383             }
384             c = ((uint32_t)((unsigned char)buf[0]))
385               | ((uint32_t)((unsigned char)buf[1])) << 8
386               | ((uint32_t)((unsigned char)buf[2])) << 16
387               | ((uint32_t)((unsigned char)buf[3])) << 24;
388             if (c == '\n')
389                 break;
390             len = wctoutf8(buf, c);
391             for (int i = 0; i < len; i++)
392                 out.push_back(buf[i]);
393         }
394         while (!seen_eof);
395         return out;
396
397     case BOM_UTF32BE:
398         do
399         {
400             if (fread(buf, 4, 1, f) != 1)
401             {
402                 seen_eof = true;
403                 break;
404             }
405             c = ((uint32_t)((unsigned char)buf[0])) << 24
406               | ((uint32_t)((unsigned char)buf[1])) << 16
407               | ((uint32_t)((unsigned char)buf[2])) << 8
408               | ((uint32_t)((unsigned char)buf[3]));
409             if (c == '\n')
410                 break;
411             len = wctoutf8(buf, c);
412             for (int i = 0; i < len; i++)
413                 out.push_back(buf[i]);
414         }
415         while (!seen_eof);
416         return out;
417     }
418
419     die("memory got trampled");
420 }
421
422 UTF8FileLineInput::UTF8FileLineInput(const char *name)
423 {
424     f = fopen_u(name, "r");
425     if (!f)
426     {
427         seen_eof = true;
428         return;
429     }
430     seen_eof = false;
431 }
432
433 UTF8FileLineInput::~UTF8FileLineInput()
434 {
435     if (f)
436         fclose(f);
437 }
438
439 std::string UTF8FileLineInput::get_line()
440 {
441     ASSERT(f);
442     std::string out;
443     char buf[512];
444
445     do
446     {
447         if (!fgets(buf, sizeof buf, f))
448         {
449             seen_eof = true;
450             break;
451         }
452         out += buf;
453         if (out[out.length() - 1] == '\n')
454         {
455             out.erase(out.length() - 1);
456             break;
457         }
458     } while (!seen_eof);
459     return utf8_validate(out.c_str());
460 }
461
462 int strwidth(const char *s)
463 {
464     ucs_t c;
465     int w = 0;
466
467     while (int l = utf8towc(&c, s))
468     {
469         s += l;
470         int cw = wcwidth(c);
471         if (cw != -1) // shouldn't ever happen
472             w += cw;
473     }
474
475     return w;
476 }
477
478 int strwidth(const std::string &s)
479 {
480     return strwidth(s.c_str());
481 }
482
483 int wclen(ucs_t c)
484 {
485     char dummy[4];
486     return wctoutf8(dummy, c);
487 }
488
489 char *prev_glyph(char *s, char *start)
490 {
491     ucs_t c;
492     do
493     {
494         // Find the start of the previous code point.
495         do
496             if (--s < start)
497                 return 0;
498         while ((*s & 0xc0) == 0x80);
499         // If a combining one, continue.
500         utf8towc(&c, s);
501     } while (!wcwidth(c));
502     return s;
503 }
504
505 char *next_glyph(char *s)
506 {
507     char *s_cur;
508     ucs_t c;
509     // Skip at least one character.
510     s += utf8towc(&c, s);
511     if (!c)
512         return 0;
513     do
514         s += utf8towc(&c, s_cur = s);
515         // And any combining ones after it.
516     while (c && !wcwidth(c));
517     return s_cur;
518 }
519
520 std::string chop_string(const char *s, int width, bool spaces)
521 {
522     const char *s0 = s;
523     ucs_t c;
524
525     while (int clen = utf8towc(&c, s))
526     {
527         int cw = wcwidth(c);
528         // Due to combining chars, we can't stop at merely reaching the
529         // target width, the next character needs to exceed it.
530         if (cw > width) // note: a CJK character might leave one space left
531             break;
532         if (cw >= 0) // should we assert on control chars instead?
533             width -= cw;
534         s += clen;
535     }
536
537    if (spaces && width)
538        return std::string(s0, s - s0) + std::string(width, ' ');
539    return std::string(s0, s - s0);;
540 }
541
542 std::string chop_string(const std::string &s, int width, bool spaces)
543 {
544     return chop_string(s.c_str(), width, spaces);
545 }
546
547 unsigned short charset_vt100[128] =
548 {
549     0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
550     0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
551     0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
552     0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
553     0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
554     0x0028, 0x0029, 0x002a, 0x2192, 0x2190, 0x2191, 0x2193, 0x002f,
555     0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
556     0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
557     0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
558     0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
559     0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
560     0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x00a0,
561 #if 0
562     0x25c6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1,
563     0x2591, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0xf800,
564     0xf801, 0x2500, 0xf803, 0xf804, 0x251c, 0x2524, 0x2534, 0x252c,
565     0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, 0x007f,
566 #endif
567     0x2666, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1,
568     0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba,
569     0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c,
570     0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, 0x0020,
571 };
572 unsigned short charset_cp437[256] =
573 {
574     0x0000, 0x263a, 0x263b, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
575     0x25d8, 0x25cb, 0x25d9, 0x2642, 0x2640, 0x266a, 0x266b, 0x263c,
576     0x25b6, 0x25c0, 0x2195, 0x203c, 0x00b6, 0x00a7, 0x25ac, 0x21a8,
577     0x2191, 0x2193, 0x2192, 0x2190, 0x221f, 0x2194, 0x25b2, 0x25bc,
578     0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
579     0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
580     0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
581     0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
582     0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
583     0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
584     0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
585     0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
586     0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
587     0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
588     0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
589     0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2302,
590     0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7,
591     0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5,
592     0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9,
593     0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192,
594     0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba,
595     0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb,
596     0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
597     0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510,
598     0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f,
599     0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567,
600     0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b,
601     0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
602     0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4,
603     0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
604     0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248,
605     0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0,
606 };