OSDN Git Service

1376457
[winmerge-jp/winmerge-jp.git] /
1 /**
2  *  @file   unicoder.cpp
3  *  @author Perry Rapp, Creator, 2003-2004
4  *  @date   Created: 2003-10
5  *  @date   Edited:  2004-05-26 (Perry Rapp)
6  *
7  *  @brief  Implementation of utility unicode conversion routines
8  */
9
10 /* The MIT License
11 Copyright (c) 2003 Perry Rapp
12 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
13 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15 */
16
17 #include "StdAfx.h"
18 #include "unicoder.h"
19
20 #ifdef _DEBUG
21 #define new DEBUG_NEW
22 #undef THIS_FILE
23 static char THIS_FILE[] = __FILE__;
24 #endif
25
26 // This is not in older platform sdk versions
27 #ifndef WC_NO_BEST_FIT_CHARS
28 #define WC_NO_BEST_FIT_CHARS        0x00000400
29 #endif
30
31 namespace ucr {
32
33 // current OS version
34 static bool f_osvi_fetched=false;
35 static OSVERSIONINFO f_osvi;
36
37 /**
38  * @brief fetch current OS version into file level variable & set flag
39  */
40 static void
41 fetch_verinfo()
42 {
43         memset(&f_osvi, 0, sizeof(f_osvi));
44         f_osvi.dwOSVersionInfoSize = sizeof(f_osvi);
45         GetVersionEx(&f_osvi);
46         f_osvi_fetched = true;
47 }
48
49 static LPCTSTR f_unicodesetNames[] = { _T("<NONE>"), _T("UCS-2LE"), _T("UCS-2BE"), _T("UTF-8") };
50 /**
51  * @brief return string for enum value
52  */
53 CString GetUnicodesetName(UNICODESET unicoding)
54 {
55         if (unicoding>=0 && unicoding<sizeof(f_unicodesetNames)/sizeof(f_unicodesetNames[0]))
56                 return f_unicodesetNames[unicoding];
57         else
58                 return _T("?");
59 }
60
61 /**
62  * @brief Convert unicode codepoint to UTF-8 byte string
63  *
64  * utf8 must be a 7+ byte buffer
65  * returns length of byte string written
66  * Does not zero-terminate!
67  */
68 int
69 Ucs4_to_Utf8(UINT unich, unsigned char * utf8)
70 {
71 #pragma warning(disable: 4244) // possible loss of data due to type conversion
72         if (unich <= 0x7f)
73         {
74                 utf8[0] = (unsigned char)unich;
75                 return 1;
76         }
77         else if (unich <= 0x7ff)
78         {
79                 utf8[0] = 0xc0 + (unich >> 6);
80                 utf8[1] = 0x80 + (unich & 0x3f);
81                 return 2;
82         }
83         else if (unich <= 0xffff)
84         {
85                 utf8[0] = 0xe0 + (unich >> 12);
86                 utf8[1] = 0x80 + ((unich >> 6) & 0x3f);
87                 utf8[2] = 0x80 + (unich & 0x3f);
88                 return 3;
89         }
90         else if (unich <= 0x1fffff)
91         {
92                 utf8[0] = 0xf0 + (unich >> 18);
93                 utf8[1] = 0x80 + ((unich >> 12) & 0x3f);
94                 utf8[2] = 0x80 + ((unich >> 6) & 0x3f);
95                 utf8[3] = 0x80 + (unich & 0x3f);
96                 return 4;
97         }
98         else if (unich <= 0x3ffffff)
99         {
100                 utf8[0] = 0xf8 + (unich >> 24);
101                 utf8[1] = 0x80 + ((unich >> 18) & 0x3f);
102                 utf8[2] = 0x80 + ((unich >> 12) & 0x3f);
103                 utf8[3] = 0x80 + ((unich >> 6) & 0x3f);
104                 utf8[4] = 0x80 + (unich & 0x3f);
105                 return 5;
106         }
107         else if (unich <= 0x7fffffff)
108         {
109                 utf8[0] = 0xfc + (unich >> 30);
110                 utf8[1] = 0x80 + ((unich >> 24) & 0x3f);
111                 utf8[2] = 0x80 + ((unich >> 18) & 0x3f);
112                 utf8[3] = 0x80 + ((unich >> 12) & 0x3f);
113                 utf8[4] = 0x80 + ((unich >> 6) & 0x3f);
114                 utf8[5] = 0x80 + (unich & 0x3f);
115                 return 6;
116         }
117         else
118         {
119                 // Invalid Unicode codepoint (high bit was set)
120                 // TODO: What do we do ?
121                 utf8[0] = '?';
122                 return 1;
123         }
124 #pragma warning(default: 4244) // possible loss of data due to type conversion
125 }
126
127 /**
128  * @brief return byte length of UTF-8 character from its initial character (-1 if invalid)
129  */
130 int
131 Utf8len_fromLeadByte(unsigned char ch)
132 {
133         if (ch < 0x80) return 1;
134         if (ch < 0xC0) return -1;
135         if (ch < 0xE0) return 2;
136         if (ch < 0xF0) return 3;
137         if (ch < 0xF8) return 4;
138         if (ch < 0xFC) return 5;
139         if (ch < 0xFE) return 6;
140         return -1;
141 }
142
143 /**
144  * @brief return #bytes required to represent Unicode codepoint as UTF-8 
145  */
146 int
147 Utf8len_fromCodepoint(UINT ch)
148 {
149         if (ch <= 0x7F) return 1;
150         if (ch <= 0x7FF) return 2;
151         if (ch <= 0xFFFF) return 3;
152         if (ch <= 0x1FFFFF) return 4;
153         if (ch <= 0x3FFFFFF) return 5;
154         if (ch <= 0x7FFFFFFF) return 6;
155         return -1;
156 }
157
158 /**
159  * @brief How many bytes will it take to write string as UTF-8 ? 
160  *
161  * @param size size argument as filemapping are not 0 terminated
162  *
163  * @bug Fails for files larger than 2gigs
164  */
165 UINT
166 Utf8len_of_string(LPCWSTR text, int size)
167 {
168         UINT len=0;
169         for (int i=0; i<size; ++i)
170         {
171                 int chlen = Utf8len_fromCodepoint(text[i]);
172                 if (chlen < 1) chlen = 1;
173                 len += chlen;
174         }
175         return len;
176 }
177 /**
178  * @brief How many chars in this UTF-8 string ? 
179  *
180  * @param size size argument as filemapping are not 0 terminated
181  *
182  * @bug Fails for files larger than 2gigs
183  */
184 UINT
185 stringlen_of_utf8(LPCSTR text, int size)
186 {
187         UINT len=0;
188         for (int i=0; i<size; )
189         {
190                 int chlen = Utf8len_fromLeadByte(text[i]);
191                 if (chlen < 1) chlen = 1;
192                 i += chlen;
193                 len ++;
194         }
195         return len;
196 }
197
198 /**
199  * @brief Read UTF-8 character and return as Unicode
200  */
201 UINT
202 GetUtf8Char(unsigned char * str)
203 {
204         /* test short cases first, as probably much more common */
205         if (!(*str & 0x80 && *str & 0x40)) {
206                 return str[0];
207         }
208         if (!(*str & 0x20)) {
209                 UINT ch = ((str[0] & 0x1F) << 6)
210                         + (str[1] & 0x3F);
211                 return ch;
212         }
213         if (!(*str & 0x10)) {
214                 UINT ch = ((str[0] & 0x0f) << 12)
215                         + ((str[1] & 0x3F) << 6)
216                         + (str[2] & 0x3F);
217                 return ch;
218         }
219         if (!(*str & 0x08)) {
220                 UINT ch = ((str[0] & 0x0F) << 18)
221                         + ((str[1] & 0x3F) << 12)
222                         + ((str[2] & 0x3F) << 6)
223                         + (str[3] & 0x3F);
224                 return ch;
225         }
226         if (!(*str & 0x04)) {
227                 UINT ch = ((str[0] & 0x0F) << 24)
228                         + ((str[1] & 0x3F) << 18)
229                         + ((str[2] & 0x3F) << 12)
230                         + ((str[3] & 0x3F) << 6)
231                         + (str[4] & 0x3F);
232                 return ch;
233         } else {
234                 UINT ch = ((str[0] & 0x0F) << 30)
235                         + ((str[1] & 0x3F) << 24)
236                         + ((str[2] & 0x3F) << 18)
237                         + ((str[3] & 0x3F) << 12)
238                         + ((str[4] & 0x3F) << 6)
239                         + (str[5] & 0x3F);
240                 return ch;
241         }
242 }
243
244 /**
245  * @brief Write unicode codepoint u out as UTF-8 to lpd, and advance lpd
246  *
247  * Returns number of bytes written (or -1 for error, in which case it writes '?')
248  */
249 int to_utf8_advance(UINT u, unsigned char * &lpd)
250 {
251 #pragma warning(disable: 4244) // possible loss of data due to type conversion
252         if (u < 0x80)
253         {
254                 *lpd++ = u;
255                 return 1;
256         }
257         else if (u < 0x800)
258         {
259                 *lpd++ = 0xC0 + (u >> 6);
260                 *lpd++ = 0x80 + (u & 0x3F);
261                 return 2;
262         }
263         else if (u < 0x10000)
264         {
265                 *lpd++ = 0xE0 + (u >> 12);
266                 *lpd++ = 0x80 + ((u >> 6) & 0x3F);
267                 *lpd++ = 0x80 + (u & 0x3F);
268                 return 3;
269         }
270         else if (u < 0x200000)
271         {
272                 *lpd++ = 0xF0 + (u >> 18);
273                 *lpd++ = 0x80 + ((u >> 12) & 0x3F);
274                 *lpd++ = 0x80 + ((u >> 6) & 0x3F);
275                 *lpd++ = 0x80 + (u & 0x3F);
276                 return 4;
277         }
278         else if (u < 0x4000000)
279         {
280                 *lpd++ = 0xF8 + (u >> 24);
281                 *lpd++ = 0x80 + ((u >> 18) & 0x3F);
282                 *lpd++ = 0x80 + ((u >> 12) & 0x3F);
283                 *lpd++ = 0x80 + ((u >> 6) & 0x3F);
284                 *lpd++ = 0x80 + (u & 0x3F);
285                 return 5;
286         }
287         else if (u < 0x80000000)
288         {
289                 *lpd++ = 0xF8 + (u >> 30);
290                 *lpd++ = 0x80 + ((u >> 24) & 0x3F);
291                 *lpd++ = 0x80 + ((u >> 18) & 0x3F);
292                 *lpd++ = 0x80 + ((u >> 12) & 0x3F);
293                 *lpd++ = 0x80 + ((u >> 6) & 0x3F);
294                 *lpd++ = 0x80 + (u & 0x3F);
295                 return 6;
296         }
297         else
298         {
299                 *lpd++ = '?';
300                 return 1;
301         }
302 #pragma warning(default: 4244) // possible loss of data due to type conversion
303 }
304
305 /**
306  * @brief convert character passed (Unicode codepoint) to a TCHAR (set lossy flag if imperfect conversion)
307  */
308 CString maketchar(UINT unich, bool & lossy)
309 {
310         static bool vercheck=false;
311         static UINT codepage = CP_ACP;
312         if (!vercheck)
313         {
314                 if (!f_osvi_fetched) fetch_verinfo();
315                 // Need 2000 or better for CP_THREAD_ACP
316                 if (f_osvi.dwMajorVersion>=5)
317                         codepage = CP_THREAD_ACP;
318                 vercheck = true;
319         }
320
321         return maketchar(unich, lossy, codepage);
322 }
323
324 /**
325  * @brief convert character passed (Unicode codepoint) to a TCHAR (set lossy flag if imperfect conversion)
326  */
327 CString maketchar(UINT unich, bool & lossy, UINT codepage)
328 {
329 #ifdef _UNICODE
330         if (unich < 0x10000)
331         {
332                 CString s;
333                 s = (TCHAR)unich;
334                 return s;
335         }
336         lossy = TRUE;
337         return '?';
338 #else
339         if (unich < 0x80)
340         {
341                 CString s = (TCHAR)unich;
342                 return s;
343         }
344         wchar_t wch = (wchar_t)unich;
345         if (!lossy)
346         {
347                 static bool vercheck=false;
348                 static bool has_no_best_fit=false;
349                 if (!vercheck)
350                 {
351                         if (!f_osvi_fetched) fetch_verinfo();
352                         // Need 2000 (5.x) or 98 (4.10)
353                         has_no_best_fit = f_osvi.dwMajorVersion>=5 || (f_osvi.dwMajorVersion==4 && f_osvi.dwMinorVersion>=10);
354                         vercheck = true;
355                 }
356                 // So far it isn't lossy, so try for lossless conversion
357                 TCHAR outch;
358                 BOOL defaulted=FALSE;
359                 DWORD flags = has_no_best_fit ? WC_NO_BEST_FIT_CHARS : 0;
360                 if (WideCharToMultiByte(codepage, flags, &wch, 1, &outch, 1, NULL, &defaulted)
361                         && !defaulted)
362                 {
363                         CString s = outch;
364                         return s;
365                 }
366                 lossy = TRUE;
367         }
368         // already lossy, so make our best shot
369         DWORD flags = WC_COMPOSITECHECK+WC_DISCARDNS+WC_SEPCHARS+WC_DEFAULTCHAR;
370         TCHAR outbuff[16];
371         int n = WideCharToMultiByte(codepage, flags, &wch, 1, outbuff, sizeof(outbuff)-1, NULL, NULL);
372         if (n>0)
373         {
374                 outbuff[n] =0;
375                 return outbuff;
376         }
377         return _T("?");
378 #endif
379 }
380
381 /**
382  * @brief convert 8-bit character input to Unicode codepoint and return it
383  */
384 UINT
385 byteToUnicode (unsigned char ch)
386 {
387         static bool vercheck=false;
388         static UINT codepage = CP_ACP;
389         if (!vercheck)
390         {
391                 if (!f_osvi_fetched) fetch_verinfo();
392                 // Need 2000 or better for CP_THREAD_ACP
393                 if (f_osvi.dwMajorVersion>=5)
394                         codepage = CP_THREAD_ACP;
395                 vercheck = true;
396         }
397         return byteToUnicode(ch, codepage);
398 }
399
400 /**
401  * @brief convert 8-bit character input to Unicode codepoint and return it
402  */
403 UINT
404 byteToUnicode (unsigned char ch, UINT codepage)
405 {
406
407         if (ch < 0x80)
408                 return ch;
409
410         DWORD flags = 0;
411         wchar_t wbuff;
412         int n = MultiByteToWideChar(codepage, flags, (LPCSTR)&ch, 1, &wbuff, 1);
413         if (n>0)
414                 return wbuff;
415         else
416                 return '?';
417 }
418
419 /**
420  * @brief Get appropriate default codepage for this operating system
421  */
422 int
423 getDefaultCodepage()
424 {
425         // default codepage is CP_THREAD_ACP if available, else CP_ACP
426         static bool vercheck=false;
427         static int defcodepage = CP_ACP;
428         if (!vercheck)
429         {
430                 if (!f_osvi_fetched) fetch_verinfo();
431                 // Need 2000 or better for CP_THREAD_ACP
432                 if (f_osvi.dwMajorVersion>=5)
433                         defcodepage = CP_THREAD_ACP;
434                 vercheck = true;
435         }
436         return defcodepage;
437 }
438
439 void getDefaultEncoding(UNICODESET * unicoding, int * codepage)
440 {
441 #ifdef _UNICODE
442         *unicoding = UCS2LE;
443         *codepage = 0;
444 #else
445         *unicoding = NONE;
446         *codepage = getDefaultCodepage();
447 #endif
448 }
449
450 /**
451  * @brief Write appropriate BOM (Unicode byte order marker)
452  * returns #bytes written
453  */
454 int
455 writeBom(LPVOID dest, UNICODESET unicoding)
456 {
457         unsigned char * lpd = reinterpret_cast<unsigned char *>(dest);
458         // write Unicode byte order marker (BOM)
459         if (unicoding == UCS2LE)
460         {
461                 *lpd++ = 0xFF;
462                 *lpd++ = 0xFE;
463                 return 2;
464         }
465         else if (unicoding == UCS2BE)
466         {
467                 *lpd++ = 0xFE;
468                 *lpd++ = 0xFF;
469                 return 2;
470         }
471         else if (unicoding == UTF8)
472         {
473                 *lpd++ = 0xEF;
474                 *lpd++ = 0xBB;
475                 *lpd++ = 0xBF;
476                 return 3;
477         }
478         return 0;
479 }
480
481 /**
482  * @brief Write src string (TCHAR) to destination buffer in codeset specified.
483  *
484  * In ANSI build, source string is assumed to be in defcodepage.
485  * This is designed for use writing to memory-mapped file.
486  * NB: Destination is not zero-terminated!
487  */
488 int
489 convertToBuffer(const CString & src, LPVOID dest, UNICODESET unicoding, int codepage)
490 {
491         static int defcodepage = getDefaultCodepage();
492
493         unsigned char * lpd = reinterpret_cast<unsigned char *>(dest);
494         unsigned char * start = lpd;
495
496 #ifdef _UNICODE
497         if (unicoding == UCS2LE)
498         {
499                 int nbytes = src.GetLength() * 2;
500                 CopyMemory(lpd, src, nbytes);
501                 return nbytes;
502         }
503         else if (unicoding == UCS2BE)
504         {
505                 for (int i=0; i<src.GetLength(); ++i)
506                 {
507                         UINT u = src[i];
508                         *lpd++ = (unsigned char)(u >> 8);
509                         *lpd++ = (unsigned char)(u & 0xFF);
510                 }
511                 return lpd - start;
512         }
513         else if (unicoding == UTF8)
514         {
515                 for (int i=0; i<src.GetLength(); ++i)
516                 {
517                         UINT u = src[i];
518                         ucr::to_utf8_advance(u, lpd);
519                 }
520                 return lpd - start;
521         }
522         else
523         {
524                 ASSERT(unicoding == NONE); // there aren't any other values in UNICODESET
525                 // Write wchars to chars
526                 DWORD flags = 0;
527                 // Take a swag at the maximum length :(
528                 int maxlen = src.GetLength() * 3;
529                 BOOL replaced=FALSE;
530                 int nbytes = WideCharToMultiByte(codepage, flags, src, src.GetLength(), (char *)lpd, maxlen, NULL, &replaced);
531                 // TODO: Ought to output replaced flag to caller
532                 return nbytes;
533         }
534
535 #else
536         // ANSI build, TCHAR=char
537
538         if (unicoding == UCS2LE)
539         {
540                 for (int i=0; i<src.GetLength(); ++i)
541                 {
542                         UINT u = src[i];
543                         *lpd++ = (unsigned char)u;
544                         *lpd++ = 0;
545                 }
546                 return lpd - start;
547         }
548         else if (unicoding == UCS2BE)
549         {
550                 for (int i=0; i<src.GetLength(); ++i)
551                 {
552                         UINT u = src[i];
553                         *lpd++ = 0;
554                         *lpd++ = (unsigned char)u;
555                 }
556                 return lpd - start;
557         }
558         else if (unicoding == UTF8)
559         {
560                 for (int i=0; i<src.GetLength(); ++i)
561                 {
562                         UINT u = src[i];
563                         to_utf8_advance(u, lpd);
564                 }
565                 return lpd - start;
566         }
567         else
568         {
569                 ASSERT(unicoding == NONE); // there aren't any other values in UNICODESET
570
571                 if (codepage == defcodepage)
572                 {
573                         // trivial case, string is already in the correct codepage
574                         CopyMemory(lpd, (LPCTSTR)src, src.GetLength());
575                         return src.GetLength();
576                 }
577                 bool lossy=false;
578                 // Take a swag at the maximum length :(
579                 int maxlen = src.GetLength() * 3;
580                 int nbytes = CrossConvert(src, src.GetLength(), (LPSTR)lpd, maxlen, defcodepage, codepage, &lossy);
581                 return nbytes;
582         }
583 #endif
584 }
585
586 /**
587  * @brief Extract character from pointer, handling UCS-2 codesets (doesn't handle UTF-8)
588  */
589 UINT
590 get_unicode_char(unsigned char * ptr, UNICODESET codeset, int codepage)
591 {
592         UINT ch;
593         switch (codeset)
594         {
595         case UCS2LE:
596                 ch = *((WORD *)ptr);
597                 break;
598         case UCS2BE:
599                 ch = (ptr[0] << 8) + ptr[1];
600                 break;
601         default:
602                 // TODO: How do we recognize valid codepage ?
603                 // if not, use byteToUnicode(*ptr)
604                 ch = byteToUnicode(*ptr, codepage);
605         }
606         return ch;
607 }
608
609 /**
610  * @brief Convert series of bytes (8-bit chars) to TCHARs, using specified codepage
611  *
612  * TODO: This doesn't inform the caller whether translation was lossy
613  *  In fact, this doesn't even know. Probably going to have to make
614  *  two passes, the first with MB_ERR_INVALID_CHARS. Ugh. :(
615  */
616 CString maketstring(LPCSTR lpd, UINT len, int codepage, bool * lossy)
617 {
618         static int defcodepage = getDefaultCodepage();
619
620         if (!len) return _T("");
621
622         // NO ! codepage = 0 is the value of CP_ACP !
623         // if (!codepage)
624         //      codepage = defcodepage;
625
626 #ifdef UNICODE
627         // Convert input to Unicode, using specified codepage
628         // TCHAR is wchar_t, so convert into CString (str)
629         CString str;
630         DWORD flags = 0;
631         int wlen = len*2+6;
632         LPWSTR wbuff = str.GetBuffer(wlen);
633         int n = MultiByteToWideChar(codepage, flags, (LPCSTR)lpd, len, wbuff, wlen-1);
634         if (n)
635         {
636                 str.ReleaseBuffer(n);
637         }
638         else
639         {
640                 str = _T("?");
641         }
642         return str;
643
644 #else
645         if (EqualCodepages(codepage, ucr::getDefaultCodepage()))
646         {
647                 // trivial case, they want the bytes in the file interpreted in our current codepage
648                 // Only caveat is that input (lpd) is not zero-terminated
649                 CString str;
650                 LPTSTR strbuff = str.GetBuffer(len+1);
651                 _tcsncpy(strbuff, lpd, len);
652                 strbuff[len] = 0; // Cannot call str.SetAt(...) until after ReleaseBuffer call
653                 str.ReleaseBuffer();
654                 return str;
655         }
656
657         CString str = CrossConvertToStringA(lpd, len, codepage, defcodepage, lossy);
658         return str;
659 #endif
660 }
661
662 /**
663  * @brief (ANSI build only) Convert from one 8 bit codepage to another
664  */
665 #ifndef UNICODE
666 CString
667 CrossConvertToStringA(LPCSTR src, UINT srclen, int cpin, int cpout, bool * lossy)
668 {
669
670         CString str;
671         int wlen = srclen*2+6;
672         int clen = wlen * 2 + 6;
673         LPSTR cbuff = str.GetBuffer(clen);
674         int nbytes = CrossConvert(src, srclen, cbuff, clen, cpin, cpout, lossy);
675         str.ReleaseBuffer(nbytes);
676         return str;
677 }
678 #endif
679
680 /**
681  * @brief Convert from one 8-bit codepage to another
682  *
683  * destsize must be at least 2
684  */
685 int
686 CrossConvert(LPCSTR src, UINT srclen, LPSTR dest, UINT destsize, int cpin, int cpout, bool * lossy)
687 {
688         ASSERT(destsize > 1);
689
690         // Convert input to Unicode, using specified codepage
691         DWORD flags = 0;
692         int wlen = srclen*2+6;
693         wchar_t * wbuff = new wchar_t[wlen];
694         int n = MultiByteToWideChar(cpin, flags, (LPCSTR)src, srclen, wbuff, wlen-1);
695         if (!n)
696         {
697                 delete [] wbuff;
698                 dest[0] = '?';
699                 return 1;
700         }
701         wbuff[n] = 0; // zero-terminate string
702
703         // Now convert to TCHAR (which means defcodepage)
704         flags = WC_NO_BEST_FIT_CHARS; // TODO: Think about this
705         wlen = n;
706         int clen = wlen * 2 + 6;
707         BOOL defaulted=FALSE;
708         n = WideCharToMultiByte(cpout, flags, wbuff, n, dest, destsize-1, NULL, &defaulted);
709         dest[n] = 0;
710         delete [] wbuff;
711         if (lossy)
712                 *lossy = !!defaulted;
713         return n;
714 }
715
716 buffer::buffer(unsigned int needed)
717 {
718         used = 0;
719         size = needed;
720         ptr = (unsigned char *)calloc(size, 1);
721 }
722 buffer::~buffer()
723 {
724         free(ptr);
725 }
726 void buffer::resize(unsigned int needed)
727 {
728         if (size < needed)
729         {
730                 size = needed;
731                 ptr = (unsigned char *)realloc(ptr, size);
732         }
733 }
734
735 /**
736  * @brief Convert from one text encoding to another; return false if any lossing conversions
737  */
738 bool convert(UNICODESET unicoding1, int codepage1, const unsigned char * src, int srcbytes, UNICODESET unicoding2, int codepage2, buffer * dest)
739 {
740         if (unicoding1 == unicoding2 && (unicoding1 || EqualCodepages(codepage1, codepage2)))
741         {
742                 // simple byte copy
743                 dest->resize(srcbytes);
744                 CopyMemory(dest->ptr, src, srcbytes);
745                 dest->used = srcbytes;
746                 return true;
747         }
748         if ((unicoding1 == UCS2LE && unicoding2 == UCS2BE)
749                 || (unicoding1 == UCS2BE && unicoding2 == UCS2LE))
750         {
751                 // simple byte swap
752                 dest->resize(srcbytes);
753                 for (int i=0; i<srcbytes; i += 2)
754                 {
755                         // Byte-swap into destination
756                         dest->ptr[i] = src[i+1];
757                         dest->ptr[i+1] = src[i];
758                 }
759                 dest->used = srcbytes;
760                 return true;
761         }
762         if (unicoding1 != UCS2LE && unicoding2 != UCS2LE)
763         {
764                 // Break problem into two simpler pieces by converting through UCS-2LE
765                 buffer intermed(dest->size);
766                 bool step1 = convert(unicoding1, codepage1, src, srcbytes, UCS2LE, 0, &intermed);
767                 bool step2 = convert(UCS2LE, 0, intermed.ptr, intermed.used, unicoding2, codepage2, dest);
768                 return step1 && step2;
769         }
770         if (unicoding1 == UCS2LE)
771         {
772                 // From UCS-2LE to 8-bit (or UTF-8)
773
774                 // WideCharToMultiByte: lpDefaultChar & lpUsedDefaultChar must be NULL when using UTF-8
775
776                 int destcp = (unicoding2 == UTF8 ? CP_UTF8 : codepage2);
777                 DWORD flags = 0;
778                 int bytes = WideCharToMultiByte(destcp, flags, (LPCWSTR)src, srcbytes/2, 0, 0, NULL, NULL);
779                 dest->resize(bytes);
780                 int losses = 0;
781                 bytes = WideCharToMultiByte(destcp, flags, (LPCWSTR)src, srcbytes/2, (char *)dest->ptr, dest->size, NULL, NULL);
782                 dest->used = bytes;
783                 return losses==0;
784         }
785         else
786         {
787                 // From 8-bit (or UTF-8) to UCS-2LE
788                 int srccp = (unicoding1 == UTF8 ? CP_UTF8 : codepage1);
789                 DWORD flags = 0;
790                 int wchars = MultiByteToWideChar(srccp, flags, (LPCSTR)src, srcbytes, 0, 0);
791                 dest->resize(wchars*2);
792                 wchars = MultiByteToWideChar(srccp, flags, (LPCSTR)src, srcbytes, (LPWSTR)dest->ptr, dest->size/2);
793                 dest->used = wchars * 2;
794                 return true;
795         }
796 }
797
798 } // namespace ucr
799
800 /**
801  * @brief Change any special codepage constants into real codepage numbers
802  */
803 static int NormalizeCodepage(int cp)
804 {
805         if (cp == CP_THREAD_ACP) // should only happen on Win2000+
806         {
807                 TCHAR buff[32];
808                 if (GetLocaleInfo(GetThreadLocale(), LOCALE_IDEFAULTANSICODEPAGE, buff, sizeof(buff)/sizeof(buff[0])))
809                         cp = _ttol(buff);
810                 else
811                         // a valid codepage is better than no codepage
812                         cp = GetACP();
813         }
814         if (cp == CP_ACP) cp = GetACP();
815         if (cp == CP_OEMCP) cp = GetOEMCP();
816         return cp;
817 }
818
819 /**
820  * @brief Compare two codepages for equality
821  */
822 bool EqualCodepages(int cp1, int cp2)
823 {
824         return (cp1 == cp2)
825                 || (NormalizeCodepage(cp1) == NormalizeCodepage(cp2));
826 }
827