]> git.lyx.org Git - lyx.git/blob - src/support/unicode.cpp
954aa79322b5e7d509a4ea6195480494d0fa152e
[lyx.git] / src / support / unicode.cpp
1 /**
2  * \file unicode.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  *
8  * Full author contact details are available in file CREDITS.
9  *
10  * A collection of unicode conversion functions, using iconv.
11  */
12
13 #include <config.h>
14
15 #include "support/unicode.h"
16 #include "support/debug.h"
17
18 #include <QThreadStorage>
19
20 #include <iconv.h>
21
22 #include <boost/cstdint.hpp>
23
24 #include <cerrno>
25 #include <map>
26 #include <ostream>
27 //Needed in MSVC
28 #include <string>
29
30
31 using namespace std;
32
33 namespace {
34
35 #ifdef WORDS_BIGENDIAN
36         char const * utf16_codeset = "UTF16-BE";
37 #else
38         char const * utf16_codeset = "UTF16-LE";
39 #endif
40
41 }
42
43
44 namespace lyx {
45
46 #ifdef WORDS_BIGENDIAN
47         char const * ucs4_codeset = "UCS-4BE";
48 #else
49         char const * ucs4_codeset = "UCS-4LE";
50 #endif
51
52 static const iconv_t invalid_cd = (iconv_t)(-1);
53
54
55 struct IconvProcessor::Impl
56 {
57         Impl(string const & to, string const & from)
58                 : cd(invalid_cd), tocode_(to), fromcode_(from)
59         {}
60
61         ~Impl()
62         {
63                 if (cd != invalid_cd && iconv_close(cd) == -1)
64                         LYXERR0("Error returned from iconv_close(" << errno << ')');
65         }
66
67         iconv_t cd;
68         string tocode_;
69         string fromcode_;
70 };
71
72
73 IconvProcessor::IconvProcessor(char const * tocode, char const * fromcode)
74         : pimpl_(new IconvProcessor::Impl(tocode, fromcode))
75 {
76 }
77
78
79 IconvProcessor::IconvProcessor(IconvProcessor const & other)
80         : pimpl_(new IconvProcessor::Impl(other.pimpl_->tocode_, other.pimpl_->fromcode_))
81 {
82 }
83
84
85 IconvProcessor::~IconvProcessor()
86 {
87         delete pimpl_;
88 }
89
90
91 IconvProcessor & IconvProcessor::operator=(IconvProcessor const & other)
92 {
93         if (&other != this) {
94                 delete pimpl_;
95                 pimpl_ = new Impl(other.pimpl_->tocode_, other.pimpl_->fromcode_);
96         }
97         return *this;
98 }
99
100
101 bool IconvProcessor::init()
102 {
103         if (pimpl_->cd != invalid_cd)
104                 return true;
105
106         pimpl_->cd = iconv_open(pimpl_->tocode_.c_str(), pimpl_->fromcode_.c_str());
107         if (pimpl_->cd != invalid_cd)
108                 return true;
109
110         lyxerr << "Error returned from iconv_open" << endl;
111         switch (errno) {
112                 case EINVAL:
113                         lyxerr << "EINVAL The conversion from " << pimpl_->fromcode_
114                                 << " to " << pimpl_->tocode_
115                                 << " is not supported by the implementation."
116                                 << endl;
117                         break;
118                 default:
119                         lyxerr << "\tSome other error: " << errno << endl;
120                         break;
121         }
122         return false;
123 }
124
125
126 int IconvProcessor::convert(char const * buf, size_t buflen,
127                 char * outbuf, size_t maxoutsize)
128 {
129         if (buflen == 0)
130                 return 0;
131
132         if (pimpl_->cd == invalid_cd) {
133                 if (!init())
134                         return -1;
135         }
136
137         char ICONV_CONST * inbuf = const_cast<char ICONV_CONST *>(buf);
138         size_t inbytesleft = buflen;
139         size_t outbytesleft = maxoutsize;
140
141         int res = iconv(pimpl_->cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
142
143         // flush out remaining data. This is needed because iconv sometimes
144         // holds back chars in the stream, waiting for a combination character
145         // (see e.g. http://sources.redhat.com/bugzilla/show_bug.cgi?id=1124)
146         iconv(pimpl_->cd, NULL, NULL, &outbuf, &outbytesleft);
147
148         //lyxerr << dec;
149         //lyxerr << "Inbytesleft: " << inbytesleft << endl;
150         //lyxerr << "Outbytesleft: " << outbytesleft << endl;
151
152         if (res != -1)
153                 // Everything went well.
154                 return maxoutsize - outbytesleft;
155
156         // There are some errors in the conversion
157         lyxerr << "Error returned from iconv" << endl;
158         switch (errno) {
159                 case E2BIG:
160                         lyxerr << "E2BIG  There is not sufficient room at *outbuf." << endl;
161                         break;
162                 case EILSEQ:
163                         lyxerr << "EILSEQ An invalid multibyte sequence"
164                                 << " has been encountered in the input.\n"
165                                 << "When converting from " << pimpl_->fromcode_
166                                 << " to " << pimpl_->tocode_ << ".\n";
167                         lyxerr << "Input:" << hex;
168                         for (size_t i = 0; i < buflen; ++i) {
169                                 // char may be signed, avoid output of
170                                 // something like 0xffffffc2
171                                 boost::uint32_t const b =
172                                         *reinterpret_cast<unsigned char const *>(buf + i);
173                                 lyxerr << " 0x" << (unsigned int)b;
174                         }
175                         lyxerr << dec << endl;
176                         break;
177                 case EINVAL:
178                         lyxerr << "EINVAL An incomplete multibyte sequence"
179                                 << " has been encountered in the input.\n"
180                                 << "When converting from " << pimpl_->fromcode_
181                                 << " to " << pimpl_->tocode_ << ".\n";
182                         lyxerr << "Input:" << hex;
183                         for (size_t i = 0; i < buflen; ++i) {
184                                 // char may be signed, avoid output of
185                                 // something like 0xffffffc2
186                                 boost::uint32_t const b =
187                                         *reinterpret_cast<unsigned char const *>(buf + i);
188                                 lyxerr << " 0x" << (unsigned int)b;
189                         }
190                         lyxerr << dec << endl;
191                         break;
192                 default:
193                         lyxerr << "\tSome other error: " << errno << endl;
194                         break;
195         }
196         // We got an error so we close down the conversion engine
197         if (iconv_close(pimpl_->cd) == -1) {
198                 lyxerr << "Error returned from iconv_close("
199                         << errno << ")" << endl;
200         }
201         pimpl_->cd = invalid_cd;
202         return -1;
203 }
204
205
206 std::string IconvProcessor::from() const
207 {
208         return pimpl_->fromcode_;
209 }
210
211
212 std::string IconvProcessor::to() const
213 {
214         return pimpl_->tocode_;
215 }
216
217
218 namespace {
219
220
221 template<typename RetType, typename InType>
222 vector<RetType>
223 iconv_convert(IconvProcessor & processor, InType const * buf, size_t buflen)
224 {
225         if (buflen == 0)
226                 return vector<RetType>();
227
228         char const * inbuf = reinterpret_cast<char const *>(buf);
229         size_t inbytesleft = buflen * sizeof(InType);
230
231         static QThreadStorage<std::vector<char> *> static_outbuf;
232         if (!static_outbuf.hasLocalData())
233                 static_outbuf.setLocalData(new std::vector<char>(32768));
234         std::vector<char> & outbuf = *static_outbuf.localData();
235         // The number of UCS4 code points in buf is at most inbytesleft.
236         // The output encoding will use at most
237         // max_encoded_bytes(pimpl_->tocode_) per UCS4 code point.
238         size_t maxoutbufsize = max_encoded_bytes(processor.to()) * inbytesleft;
239         if (outbuf.size() < maxoutbufsize)
240                 outbuf.resize(maxoutbufsize);
241
242         int bytes = processor.convert(inbuf, inbytesleft, &outbuf[0], outbuf.size());
243         if (bytes <= 0)
244                 // Conversion failed
245                 // FIXME Maybe throw an exception and handle that in the caller?
246                 return vector<RetType>();
247
248         RetType const * tmp = reinterpret_cast<RetType const *>(&outbuf[0]);
249         return vector<RetType>(tmp, tmp + bytes / sizeof(RetType));
250 }
251
252 } // anon namespace
253
254
255 IconvProcessor & utf8ToUcs4()
256 {
257         static QThreadStorage<IconvProcessor *> processor;
258         if (!processor.hasLocalData())
259                 processor.setLocalData(new IconvProcessor(ucs4_codeset, "UTF-8"));
260         return *processor.localData();
261 }
262
263
264 vector<char_type> utf8_to_ucs4(vector<char> const & utf8str)
265 {
266         if (utf8str.empty())
267                 return vector<char_type>();
268
269         return utf8_to_ucs4(&utf8str[0], utf8str.size());
270 }
271
272
273 vector<char_type>
274 utf8_to_ucs4(char const * utf8str, size_t ls)
275 {
276         return iconv_convert<char_type>(utf8ToUcs4(), utf8str, ls);
277 }
278
279
280 vector<char_type>
281 utf16_to_ucs4(unsigned short const * s, size_t ls)
282 {
283         static QThreadStorage<IconvProcessor *> processor;
284         if (!processor.hasLocalData())
285                 processor.setLocalData(new IconvProcessor(ucs4_codeset, utf16_codeset));
286         return iconv_convert<char_type>(*processor.localData(), s, ls);
287 }
288
289
290 vector<unsigned short>
291 ucs4_to_utf16(char_type const * s, size_t ls)
292 {
293         static QThreadStorage<IconvProcessor *> processor;
294         if (!processor.hasLocalData())
295                 processor.setLocalData(new IconvProcessor(utf16_codeset, ucs4_codeset));
296         return iconv_convert<unsigned short>(*processor.localData(), s, ls);
297 }
298
299
300 IconvProcessor & ucs4ToUtf8()
301 {
302         static QThreadStorage<IconvProcessor *> processor;
303         if (!processor.hasLocalData())
304                 processor.setLocalData(new IconvProcessor("UTF-8", ucs4_codeset));
305         return *processor.localData();
306 }
307
308
309 vector<char>
310 ucs4_to_utf8(char_type c)
311 {
312         return iconv_convert<char>(ucs4ToUtf8(), &c, 1);
313 }
314
315
316 vector<char>
317 ucs4_to_utf8(vector<char_type> const & ucs4str)
318 {
319         if (ucs4str.empty())
320                 return vector<char>();
321
322         return ucs4_to_utf8(&ucs4str[0], ucs4str.size());
323 }
324
325
326 vector<char>
327 ucs4_to_utf8(char_type const * ucs4str, size_t ls)
328 {
329         return iconv_convert<char>(ucs4ToUtf8(), ucs4str, ls);
330 }
331
332
333 vector<char_type>
334 eightbit_to_ucs4(char const * s, size_t ls, string const & encoding)
335 {
336         static QThreadStorage<map<string, IconvProcessor> *> static_processors;
337         if (!static_processors.hasLocalData())
338                 static_processors.setLocalData(new map<string, IconvProcessor>);
339         map<string, IconvProcessor> & processors = *static_processors.localData();
340         if (processors.find(encoding) == processors.end()) {
341                 IconvProcessor processor(ucs4_codeset, encoding.c_str());
342                 processors.insert(make_pair(encoding, processor));
343         }
344         return iconv_convert<char_type>(processors[encoding], s, ls);
345 }
346
347
348 namespace {
349
350 map<string, IconvProcessor> & ucs4To8bitProcessors()
351 {
352         static QThreadStorage<map<string, IconvProcessor> *> processors;
353         if (!processors.hasLocalData())
354                 processors.setLocalData(new map<string, IconvProcessor>);
355         return *processors.localData();
356 }
357
358 }
359
360
361 vector<char>
362 ucs4_to_eightbit(char_type const * ucs4str, size_t ls, string const & encoding)
363 {
364         map<string, IconvProcessor> & processors(ucs4To8bitProcessors());
365         if (processors.find(encoding) == processors.end()) {
366                 IconvProcessor processor(encoding.c_str(), ucs4_codeset);
367                 processors.insert(make_pair(encoding, processor));
368         }
369         return iconv_convert<char>(processors[encoding], ucs4str, ls);
370 }
371
372
373 char ucs4_to_eightbit(char_type ucs4, string const & encoding)
374 {
375         map<string, IconvProcessor> & processors(ucs4To8bitProcessors());
376         map<string, IconvProcessor>::iterator it = processors.find(encoding);
377         if (it == processors.end()) {
378                 IconvProcessor processor(encoding.c_str(), ucs4_codeset);
379                 it = processors.insert(make_pair(encoding, processor)).first;
380         }
381
382         char out;
383         int const bytes = it->second.convert((char *)(&ucs4), 4, &out, 1);
384         if (bytes > 0)
385                 return out;
386         return 0;
387 }
388
389
390 void ucs4_to_multibytes(char_type ucs4, vector<char> & out,
391         string const & encoding)
392 {
393         static QThreadStorage<map<string, IconvProcessor> *> static_processors;
394         if (!static_processors.hasLocalData())
395                 static_processors.setLocalData(new map<string, IconvProcessor>);
396         map<string, IconvProcessor> & processors = *static_processors.localData();
397         map<string, IconvProcessor>::iterator it = processors.find(encoding);
398         if (it == processors.end()) {
399                 IconvProcessor processor(encoding.c_str(), ucs4_codeset);
400                 it = processors.insert(make_pair(encoding, processor)).first;
401         }
402
403         out.resize(4);
404         int bytes = it->second.convert((char *)(&ucs4), 4, &out[0], 4);
405         if (bytes > 0)
406                 out.resize(bytes);
407         else
408                 out.clear();
409 }
410
411 int max_encoded_bytes(std::string const & encoding)
412 {
413         // FIXME: this information should be transferred to lib/encodings
414         // UTF8 uses at most 4 bytes to represent one UCS4 code point
415         // (see RFC 3629). RFC 2279 specifies 6 bytes, but that
416         // information is outdated, and RFC 2279 has been superseded by
417         // RFC 3629.
418         // The CJK encodings use (different) multibyte representation as well.
419         // All other encodings encode one UCS4 code point in one byte
420         // (and can therefore only encode a subset of UCS4)
421         // Furthermore, all encodings that use shifting (like SJIS) do not work with
422         // iconv_codecvt_facet.
423         if (encoding == "UTF-8" ||
424             encoding == "GB" ||
425             encoding == "EUC-TW")
426                 return 4;
427         else if (encoding == "EUC-JP")
428                 return 3;
429         else if (encoding == "ISO-2022-JP")
430                 return 8;
431         else if (encoding == "BIG5" ||
432                  encoding == "EUC-KR" ||
433                  encoding == "EUC-CN" ||
434                  encoding == "SJIS" ||
435                  encoding == "GBK")
436                 return 2;
437         else
438                 return 1;
439 }
440
441 } // namespace lyx