b70c5b476dc574cd34fa5b36e4a5ce29b45ef946
[gnulib.git] / lib / unicodeio.c
1 /* Unicode character output to streams with locale dependent encoding.
2
3    Copyright (C) 2000 Free Software Foundation, Inc.
4
5    This program is free software; you can redistribute it and/or modify it
6    under the terms of the GNU Library General Public License as published
7    by the Free Software Foundation; either version 2, or (at your option)
8    any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Library General Public License for more details.
14
15    You should have received a copy of the GNU Library General Public
16    License along with this program; if not, write to the Free Software
17    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
18    USA.  */
19
20 /* Written by Bruno Haible <haible@clisp.cons.org>.  */
21
22 #ifdef HAVE_CONFIG_H
23 # include <config.h>
24 #endif
25
26 #if HAVE_STDDEF_H
27 # include <stddef.h>
28 #endif
29
30 #include <stdio.h>
31 #if HAVE_STRING_H
32 # include <string.h>
33 #else
34 # include <strings.h>
35 #endif
36
37 #include <errno.h>
38 #ifndef errno
39 extern int errno;
40 #endif
41
42 #if HAVE_LIMITS_H
43 # include <limits.h>
44 #endif
45 #ifndef MB_LEN_MAX
46 # define MB_LEN_MAX 1
47 #endif
48
49 #if HAVE_ICONV
50 # include <iconv.h>
51 #endif
52
53 #include <error.h>
54
55 #if ENABLE_NLS
56 # include <libintl.h>
57 # define _(Text) gettext (Text)
58 #else
59 # define _(Text) Text
60 #endif
61
62 #include "unicodeio.h"
63
64 #if __STDC_ISO_10646__ && HAVE_WCTOMB
65
66 /* Values of type wchar_t are Unicode code points.  */
67
68 /* Place into BUF the locale-dependent representation of the character
69    CODE.  Return the size of the result.  If there is a conversion
70    error, return -1, setting errno appropriately.  Assumes that the
71    locale doesn't change between two calls.  */
72 static size_t
73 convert_unicode_char (char buf[MB_LEN_MAX], unsigned int code)
74 {
75   wchar_t wc = code;
76   errno = 0;
77   /* Test for truncation before invoking wctomb.  */
78   return wc == code ? wctomb (buf, wc) : -1;
79 }
80
81 #else
82
83 /* When we pass a Unicode character to iconv(), we must pass it in a
84    suitable encoding. The standardized Unicode encodings are
85    UTF-8, UCS-2, UCS-4, UTF-16, UTF-16BE, UTF-16LE, UTF-7.
86    UCS-2 supports only characters up to \U0000FFFF.
87    UTF-16 and variants support only characters up to \U0010FFFF.
88    UTF-7 is way too complex and not supported by glibc-2.1.
89    UCS-4 specification leaves doubts about endianness and byte order
90    mark. glibc currently interprets it as big endian without byte order
91    mark, but this is not backed by an RFC.
92    So we use UTF-8. It supports characters up to \U7FFFFFFF and is
93    unambiguously defined.  */
94
95 /* Stores the UTF-8 representation of the Unicode character wc in r[0..5].
96    Returns the number of bytes stored, or -1 if wc is out of range.  */
97 static int
98 utf8_wctomb (unsigned char *r, unsigned int wc)
99 {
100   int count;
101
102   if (wc < 0x80)
103     count = 1;
104   else if (wc < 0x800)
105     count = 2;
106   else if (wc < 0x10000)
107     count = 3;
108   else if (wc < 0x200000)
109     count = 4;
110   else if (wc < 0x4000000)
111     count = 5;
112   else if (wc <= 0x7fffffff)
113     count = 6;
114   else
115     return -1;
116
117   switch (count)
118     {
119       /* Note: code falls through cases! */
120       case 6: r[5] = 0x80 | (wc & 0x3f); wc = wc >> 6; wc |= 0x4000000;
121       case 5: r[4] = 0x80 | (wc & 0x3f); wc = wc >> 6; wc |= 0x200000;
122       case 4: r[3] = 0x80 | (wc & 0x3f); wc = wc >> 6; wc |= 0x10000;
123       case 3: r[2] = 0x80 | (wc & 0x3f); wc = wc >> 6; wc |= 0x800;
124       case 2: r[1] = 0x80 | (wc & 0x3f); wc = wc >> 6; wc |= 0xc0;
125       case 1: r[0] = wc;
126     }
127
128   return count;
129 }
130
131 /* Luckily, the encoding's name is platform independent.  */
132 # define UTF8_NAME "UTF-8"
133
134 /* Place into BUF the locale-dependent representation of the character
135    CODE.  Return the size of the result.  If there is a conversion
136    error, return -1, setting errno appropriately.  Assumes that the
137    locale doesn't change between two calls.  */
138 static size_t
139 convert_unicode_char (char buf[MB_LEN_MAX], unsigned int code)
140 {
141   static int initialized;
142   static int is_utf8;
143 # if HAVE_ICONV
144   static iconv_t utf8_to_local;
145 # endif
146
147   if (!initialized)
148     {
149       extern const char *locale_charset PARAMS ((void));
150       const char *charset = locale_charset ();
151
152       is_utf8 = (charset != NULL && !strcmp (charset, UTF8_NAME));
153 # if HAVE_ICONV
154       if (!is_utf8)
155         {
156           utf8_to_local = (charset != NULL
157                            ? iconv_open (charset, UTF8_NAME)
158                            : (iconv_t) -1);
159           if (utf8_to_local == (iconv_t) -1)
160             {
161               /* For an unknown encoding, assume ASCII.  */
162               utf8_to_local = iconv_open ("ASCII", UTF8_NAME);
163               if (utf8_to_local == (iconv_t) -1)
164                 {
165                   errno = ENOTSUP;
166                   return -1;
167                 }
168             }
169         }
170 # endif
171       initialized = 1;
172     }
173
174   /* Convert the character to UTF-8.  */
175   if (is_utf8)
176     return utf8_wctomb ((unsigned char *) buf, code);
177   else
178     {
179 # if HAVE_ICONV
180       char inbuf[6];
181       const char *inptr = inbuf;
182       size_t inbytesleft = utf8_wctomb ((unsigned char *) inbuf, code);
183       char *outptr = buf;
184       size_t outbytesleft = MB_LEN_MAX;
185       size_t res;
186
187       if (inbytesleft == (size_t) -1)
188         return -1;
189
190       /* Convert the character from UTF-8 to the locale's charset.  */
191       res = iconv (utf8_to_local, &inptr, &inbytesleft, &outptr, &outbytesleft);
192       if (inbytesleft > 0 || res == (size_t) -1
193           /* Irix iconv() inserts a NUL byte if it cannot convert. */
194 #  if !defined _LIBICONV_VERSION && (defined sgi || defined __sgi)
195           || (res > 0 && code != 0 && outptr - outbuf == 1 && *outbuf == '\0')
196 #  endif
197           )
198         return -1;
199
200       /* Avoid glibc-2.1 bug and Solaris 2.7 bug.  */
201 #  if defined _LIBICONV_VERSION \
202     || !((__GLIBC__ - 0 == 2 && __GLIBC_MINOR__ - 0 <= 1) || defined __sun)
203
204       /* Get back to the initial shift state.  */
205       return iconv (utf8_to_local, NULL, NULL, &outptr, &outbytesleft);
206 #  endif
207
208       return outptr - buf;
209 # else
210       errno = ENOTSUP;
211       return -1;
212 # endif
213     }
214 }
215
216 #endif
217
218 /* Output the Unicode character CODE to the output stream STREAM.  */
219 void
220 print_unicode_char (FILE *stream, unsigned int code)
221 {
222   char buf[MB_LEN_MAX];
223   size_t s = convert_unicode_char (buf, code);
224
225   if (s == (size_t) -1)
226     error (1, errno, _("cannot convert U+%04X to local character set"), code);
227   else
228     fwrite (buf, 1, s, stream);
229 }