461c41e4fcc9c6a0b477aa596422bca477a4110a
[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 wchar_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::wstring win; // actually, these are more of a lose
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         return utf16_to_8(win.c_str());
356
357     case BOM_UTF16BE:
358         do
359         {
360             if (fread(buf, 2, 1, f) != 1)
361             {
362                 seen_eof = true;
363                 break;
364             }
365             c = ((uint32_t)((unsigned char)buf[1]))
366               | ((uint32_t)((unsigned char)buf[0])) << 8;
367             if (c == '\n')
368                 break;
369             win.push_back(c);
370         }
371         while (!seen_eof);
372         return utf16_to_8(win.c_str());
373
374     case BOM_UTF32LE:
375         do
376         {
377             if (fread(buf, 4, 1, f) != 1)
378             {
379                 seen_eof = true;
380                 break;
381             }
382             c = ((uint32_t)((unsigned char)buf[0]))
383               | ((uint32_t)((unsigned char)buf[1])) << 8
384               | ((uint32_t)((unsigned char)buf[2])) << 16
385               | ((uint32_t)((unsigned char)buf[3])) << 24;
386             if (c == '\n')
387                 break;
388             len = wctoutf8(buf, c);
389             for (int i = 0; i < len; i++)
390                 out.push_back(buf[i]);
391         }
392         while (!seen_eof);
393         return out;
394
395     case BOM_UTF32BE:
396         do
397         {
398             if (fread(buf, 4, 1, f) != 1)
399             {
400                 seen_eof = true;
401                 break;
402             }
403             c = ((uint32_t)((unsigned char)buf[0])) << 24
404               | ((uint32_t)((unsigned char)buf[1])) << 16
405               | ((uint32_t)((unsigned char)buf[2])) << 8
406               | ((uint32_t)((unsigned char)buf[3]));
407             if (c == '\n')
408                 break;
409             len = wctoutf8(buf, c);
410             for (int i = 0; i < len; i++)
411                 out.push_back(buf[i]);
412         }
413         while (!seen_eof);
414         return out;
415     }
416
417     die("memory got trampled");
418 }
419
420 UTF8FileLineInput::UTF8FileLineInput(const char *name)
421 {
422     f = fopen_u(name, "r");
423     if (!f)
424     {
425         seen_eof = true;
426         return;
427     }
428     seen_eof = false;
429 }
430
431 UTF8FileLineInput::~UTF8FileLineInput()
432 {
433     if (f)
434         fclose(f);
435 }
436
437 std::string UTF8FileLineInput::get_line()
438 {
439     ASSERT(f);
440     std::string out;
441     char buf[512];
442
443     do
444     {
445         if (!fgets(buf, sizeof buf, f))
446         {
447             seen_eof = true;
448             break;
449         }
450         out += buf;
451         if (out[out.length() - 1] == '\n')
452         {
453             out.erase(out.length() - 1);
454             break;
455         }
456     } while (!seen_eof);
457     return utf8_validate(out.c_str());
458 }
459
460 int strwidth(const char *s)
461 {
462     ucs_t c;
463     int w = 0;
464
465     while (int l = utf8towc(&c, s))
466     {
467         s += l;
468         int cw = wcwidth(c);
469         if (cw != -1) // shouldn't ever happen
470             w += cw;
471     }
472
473     return w;
474 }
475
476 int strwidth(const std::string &s)
477 {
478     return strwidth(s.c_str());
479 }
480
481 int wclen(ucs_t c)
482 {
483     char dummy[4];
484     return wctoutf8(dummy, c);
485 }
486
487 char *prev_glyph(char *s, char *start)
488 {
489     ucs_t c;
490     do
491     {
492         // Find the start of the previous code point.
493         do
494             if (--s < start)
495                 return 0;
496         while ((*s & 0xc0) == 0x80);
497         // If a combining one, continue.
498         utf8towc(&c, s);
499     } while (!wcwidth(c));
500     return s;
501 }
502
503 char *next_glyph(char *s)
504 {
505     char *s_cur;
506     ucs_t c;
507     // Skip at least one character.
508     s += utf8towc(&c, s);
509     if (!c)
510         return 0;
511     do
512         s += utf8towc(&c, s_cur = s);
513         // And any combining ones after it.
514     while (c && !wcwidth(c));
515     return s_cur;
516 }
517
518 std::string chop_string(const char *s, int width, bool spaces)
519 {
520     const char *s0 = s;
521     ucs_t c;
522
523     while (int clen = utf8towc(&c, s))
524     {
525         int cw = wcwidth(c);
526         // Due to combining chars, we can't stop at merely reaching the
527         // target width, the next character needs to exceed it.
528         if (cw > width) // note: a CJK character might leave one space left
529             break;
530         if (cw >= 0) // should we assert on control chars instead?
531             width -= cw;
532         s += clen;
533     }
534
535    if (spaces && width)
536        return std::string(s0, s - s0) + std::string(width, ' ');
537    return std::string(s0, s - s0);;
538 }
539
540 std::string chop_string(const std::string &s, int width, bool spaces)
541 {
542     return chop_string(s.c_str(), width, spaces);
543 }
544
545 unsigned short charset_vt100[128] =
546 {
547     0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
548     0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
549     0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
550     0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
551     0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
552     0x0028, 0x0029, 0x002a, 0x2192, 0x2190, 0x2191, 0x2193, 0x002f,
553     0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
554     0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
555     0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
556     0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
557     0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
558     0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x00a0,
559 #if 0
560     0x25c6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1,
561     0x2591, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0xf800,
562     0xf801, 0x2500, 0xf803, 0xf804, 0x251c, 0x2524, 0x2534, 0x252c,
563     0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, 0x007f,
564 #endif
565     0x2666, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1,
566     0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba,
567     0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c,
568     0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, 0x0020,
569 };
570 unsigned short charset_cp437[256] =
571 {
572     0x0000, 0x263a, 0x263b, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
573     0x25d8, 0x25cb, 0x25d9, 0x2642, 0x2640, 0x266a, 0x266b, 0x263c,
574     0x25b6, 0x25c0, 0x2195, 0x203c, 0x00b6, 0x00a7, 0x25ac, 0x21a8,
575     0x2191, 0x2193, 0x2192, 0x2190, 0x221f, 0x2194, 0x25b2, 0x25bc,
576     0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
577     0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
578     0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
579     0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
580     0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
581     0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
582     0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
583     0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
584     0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
585     0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
586     0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
587     0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2302,
588     0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7,
589     0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5,
590     0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9,
591     0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192,
592     0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba,
593     0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb,
594     0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
595     0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510,
596     0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f,
597     0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567,
598     0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b,
599     0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
600     0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4,
601     0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
602     0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248,
603     0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0,
604 };