xref: /vim-8.2.3635/src/os_mac_conv.c (revision 00a927d6)
1 /* vi:set ts=8 sts=4 sw=4:
2  *
3  * VIM - Vi IMproved	by Bram Moolenaar
4  *
5  * Do ":help uganda"  in Vim to read copying and usage conditions.
6  * Do ":help credits" in Vim to see a list of people who contributed.
7  * See README.txt for an overview of the Vim source code.
8  */
9 /*
10  * os_mac_conv.c: Code specifically for Mac string conversions.
11  *
12  * This code has been put in a separate file to avoid the conflicts that are
13  * caused by including both the X11 and Carbon header files.
14  */
15 
16 #define NO_X11_INCLUDES
17 #include "vim.h"
18 
19 #if defined(MACOS_CONVERT) || defined(PROTO)
20 # ifdef PROTO
21 /* A few dummy types to be able to generate function prototypes. */
22 typedef int UniChar;
23 typedef int *TECObjectRef;
24 typedef int CFStringRef;
25 # endif
26 
27 static char_u	    *mac_utf16_to_utf8 __ARGS((UniChar *from, size_t fromLen, size_t *actualLen));
28 static UniChar	    *mac_utf8_to_utf16 __ARGS((char_u *from, size_t fromLen, size_t *actualLen));
29 
30 /* Converter for composing decomposed HFS+ file paths */
31 static TECObjectRef gPathConverter;
32 /* Converter used by mac_utf16_to_utf8 */
33 static TECObjectRef gUTF16ToUTF8Converter;
34 
35 /*
36  * A Mac version of string_convert_ext() for special cases.
37  */
38     char_u *
39 mac_string_convert(ptr, len, lenp, fail_on_error, from_enc, to_enc, unconvlenp)
40     char_u		*ptr;
41     int			len;
42     int			*lenp;
43     int			fail_on_error;
44     int			from_enc;
45     int			to_enc;
46     int			*unconvlenp;
47 {
48     char_u		*retval, *d;
49     CFStringRef		cfstr;
50     int			buflen, in, out, l, i;
51     CFStringEncoding	from;
52     CFStringEncoding	to;
53 
54     switch (from_enc)
55     {
56 	case 'l':   from = kCFStringEncodingISOLatin1; break;
57 	case 'm':   from = kCFStringEncodingMacRoman; break;
58 	case 'u':   from = kCFStringEncodingUTF8; break;
59 	default:    return NULL;
60     }
61     switch (to_enc)
62     {
63 	case 'l':   to = kCFStringEncodingISOLatin1; break;
64 	case 'm':   to = kCFStringEncodingMacRoman; break;
65 	case 'u':   to = kCFStringEncodingUTF8; break;
66 	default:    return NULL;
67     }
68 
69     if (unconvlenp != NULL)
70 	*unconvlenp = 0;
71     cfstr = CFStringCreateWithBytes(NULL, ptr, len, from, 0);
72 
73     if(cfstr == NULL)
74 	fprintf(stderr, "Encoding failed\n");
75     /* When conversion failed, try excluding bytes from the end, helps when
76      * there is an incomplete byte sequence.  Only do up to 6 bytes to avoid
77      * looping a long time when there really is something unconvertible. */
78     while (cfstr == NULL && unconvlenp != NULL && len > 1 && *unconvlenp < 6)
79     {
80 	--len;
81 	++*unconvlenp;
82 	cfstr = CFStringCreateWithBytes(NULL, ptr, len, from, 0);
83     }
84     if (cfstr == NULL)
85 	return NULL;
86 
87     if (to == kCFStringEncodingUTF8)
88 	buflen = len * 6 + 1;
89     else
90 	buflen = len + 1;
91     retval = alloc(buflen);
92     if (retval == NULL)
93     {
94 	CFRelease(cfstr);
95 	return NULL;
96     }
97 
98 #if 0
99     CFRange convertRange = CFRangeMake(0, CFStringGetLength(cfstr));
100     /*  Determine output buffer size */
101     CFStringGetBytes(cfstr, convertRange, to, NULL, FALSE, NULL, 0, (CFIndex *)&buflen);
102     retval = (buflen > 0) ? alloc(buflen) : NULL;
103     if (retval == NULL) {
104 	CFRelease(cfstr);
105 	return NULL;
106     }
107 
108     if (lenp)
109 	*lenp = buflen / sizeof(char_u);
110 
111     if (!CFStringGetBytes(cfstr, convertRange, to, NULL, FALSE, retval, buflen, NULL))
112 #endif
113     if (!CFStringGetCString(cfstr, (char *)retval, buflen, to))
114     {
115 	CFRelease(cfstr);
116 	if (fail_on_error)
117 	{
118 	    vim_free(retval);
119 	    return NULL;
120 	}
121 
122 	fprintf(stderr, "Trying char-by-char conversion...\n");
123 	/* conversion failed for the whole string, but maybe it will work
124 	 * for each character */
125 	for (d = retval, in = 0, out = 0; in < len && out < buflen - 1;)
126 	{
127 	    if (from == kCFStringEncodingUTF8)
128 		l = utf_ptr2len(ptr + in);
129 	    else
130 		l = 1;
131 	    cfstr = CFStringCreateWithBytes(NULL, ptr + in, l, from, 0);
132 	    if (cfstr == NULL)
133 	    {
134 		*d++ = '?';
135 		out++;
136 	    }
137 	    else
138 	    {
139 		if (!CFStringGetCString(cfstr, (char *)d, buflen - out, to))
140 		{
141 		    *d++ = '?';
142 		    out++;
143 		}
144 		else
145 		{
146 		    i = STRLEN(d);
147 		    d += i;
148 		    out += i;
149 		}
150 		CFRelease(cfstr);
151 	    }
152 	    in += l;
153 	}
154 	*d = NUL;
155 	if (lenp != NULL)
156 	    *lenp = out;
157 	return retval;
158     }
159     CFRelease(cfstr);
160     if (lenp != NULL)
161 	*lenp = STRLEN(retval);
162 
163     return retval;
164 }
165 
166 /*
167  * Conversion from Apple MacRoman char encoding to UTF-8 or latin1, using
168  * standard Carbon framework.
169  * Input: "ptr[*sizep]".
170  * "real_size" is the size of the buffer that "ptr" points to.
171  * output is in-place, "sizep" is adjusted.
172  * Returns OK or FAIL.
173  */
174     int
175 macroman2enc(ptr, sizep, real_size)
176     char_u	*ptr;
177     long	*sizep;
178     long	real_size;
179 {
180     CFStringRef		cfstr;
181     CFRange		r;
182     CFIndex		len = *sizep;
183 
184     /* MacRoman is an 8-bit encoding, no need to move bytes to
185      * conv_rest[]. */
186     cfstr = CFStringCreateWithBytes(NULL, ptr, len,
187 						kCFStringEncodingMacRoman, 0);
188     /*
189      * If there is a conversion error, try using another
190      * conversion.
191      */
192     if (cfstr == NULL)
193 	return FAIL;
194 
195     r.location = 0;
196     r.length = CFStringGetLength(cfstr);
197     if (r.length != CFStringGetBytes(cfstr, r,
198 	    (enc_utf8) ? kCFStringEncodingUTF8 : kCFStringEncodingISOLatin1,
199 	    0, /* no lossy conversion */
200 	    0, /* not external representation */
201 	    ptr + *sizep, real_size - *sizep, &len))
202     {
203 	CFRelease(cfstr);
204 	return FAIL;
205     }
206     CFRelease(cfstr);
207     mch_memmove(ptr, ptr + *sizep, len);
208     *sizep = len;
209 
210     return OK;
211 }
212 
213 /*
214  * Conversion from UTF-8 or latin1 to MacRoman.
215  * Input: "from[fromlen]"
216  * Output: "to[maxtolen]" length in "*tolenp"
217  * Unconverted rest in rest[*restlenp].
218  * Returns OK or FAIL.
219  */
220     int
221 enc2macroman(from, fromlen, to, tolenp, maxtolen, rest, restlenp)
222     char_u	*from;
223     size_t	fromlen;
224     char_u	*to;
225     int		*tolenp;
226     int		maxtolen;
227     char_u	*rest;
228     int		*restlenp;
229 {
230     CFStringRef	cfstr;
231     CFRange	r;
232     CFIndex	l;
233 
234     *restlenp = 0;
235     cfstr = CFStringCreateWithBytes(NULL, from, fromlen,
236 	    (enc_utf8) ? kCFStringEncodingUTF8 : kCFStringEncodingISOLatin1,
237 	    0);
238     while (cfstr == NULL && *restlenp < 3 && fromlen > 1)
239     {
240 	rest[*restlenp++] = from[--fromlen];
241 	cfstr = CFStringCreateWithBytes(NULL, from, fromlen,
242 		(enc_utf8) ? kCFStringEncodingUTF8 : kCFStringEncodingISOLatin1,
243 		0);
244     }
245     if (cfstr == NULL)
246 	return FAIL;
247 
248     r.location = 0;
249     r.length = CFStringGetLength(cfstr);
250     if (r.length != CFStringGetBytes(cfstr, r,
251 		kCFStringEncodingMacRoman,
252 		0, /* no lossy conversion */
253 		0, /* not external representation (since vim
254 		    * handles this internally */
255 		to, maxtolen, &l))
256     {
257 	CFRelease(cfstr);
258 	return FAIL;
259     }
260     CFRelease(cfstr);
261     *tolenp = l;
262     return OK;
263 }
264 
265 /*
266  * Initializes text converters
267  */
268     void
269 mac_conv_init()
270 {
271     TextEncoding    utf8_encoding;
272     TextEncoding    utf8_hfsplus_encoding;
273     TextEncoding    utf8_canon_encoding;
274     TextEncoding    utf16_encoding;
275 
276     utf8_encoding = CreateTextEncoding(kTextEncodingUnicodeDefault,
277 	    kTextEncodingDefaultVariant, kUnicodeUTF8Format);
278     utf8_hfsplus_encoding = CreateTextEncoding(kTextEncodingUnicodeDefault,
279 	    kUnicodeHFSPlusCompVariant, kUnicodeUTF8Format);
280     utf8_canon_encoding = CreateTextEncoding(kTextEncodingUnicodeDefault,
281 	    kUnicodeCanonicalCompVariant, kUnicodeUTF8Format);
282     utf16_encoding = CreateTextEncoding(kTextEncodingUnicodeDefault,
283 	    kTextEncodingDefaultVariant, kUnicode16BitFormat);
284 
285     if (TECCreateConverter(&gPathConverter, utf8_encoding,
286 		utf8_hfsplus_encoding) != noErr)
287 	gPathConverter = NULL;
288 
289     if (TECCreateConverter(&gUTF16ToUTF8Converter, utf16_encoding,
290 		utf8_canon_encoding) != noErr)
291     {
292 	/* On pre-10.3, Unicode normalization is not available so
293 	 * fall back to non-normalizing converter */
294 	if (TECCreateConverter(&gUTF16ToUTF8Converter, utf16_encoding,
295 		    utf8_encoding) != noErr)
296 	    gUTF16ToUTF8Converter = NULL;
297     }
298 }
299 
300 /*
301  * Destroys text converters
302  */
303     void
304 mac_conv_cleanup()
305 {
306     if (gUTF16ToUTF8Converter)
307     {
308 	TECDisposeConverter(gUTF16ToUTF8Converter);
309 	gUTF16ToUTF8Converter = NULL;
310     }
311 
312     if (gPathConverter)
313     {
314 	TECDisposeConverter(gPathConverter);
315 	gPathConverter = NULL;
316     }
317 }
318 
319 /*
320  * Conversion from UTF-16 UniChars to 'encoding'
321  * The function signature uses the real type of UniChar (as typedef'ed in
322  * CFBase.h) to avoid clashes with X11 header files in the .pro file
323  */
324     char_u *
325 mac_utf16_to_enc(from, fromLen, actualLen)
326     unsigned short *from;
327     size_t fromLen;
328     size_t *actualLen;
329 {
330     /* Following code borrows somewhat from os_mswin.c */
331     vimconv_T	conv;
332     size_t      utf8_len;
333     char_u      *utf8_str;
334     char_u      *result = NULL;
335 
336     /* Convert to utf-8 first, works better with iconv */
337     utf8_len = 0;
338     utf8_str = mac_utf16_to_utf8(from, fromLen, &utf8_len);
339 
340     if (utf8_str)
341     {
342 	/* We might be called before we have p_enc set up. */
343 	conv.vc_type = CONV_NONE;
344 
345 	/* If encoding (p_enc) is any unicode, it is actually in utf-8 (vim
346 	 * internal unicode is always utf-8) so don't convert in such cases */
347 
348 	if ((enc_canon_props(p_enc) & ENC_UNICODE) == 0)
349 	    convert_setup(&conv, (char_u *)"utf-8",
350 		    p_enc? p_enc: (char_u *)"macroman");
351 	if (conv.vc_type == CONV_NONE)
352 	{
353 	    /* p_enc is utf-8, so we're done. */
354 	    result = utf8_str;
355 	}
356 	else
357 	{
358 	    result = string_convert(&conv, utf8_str, (int *)&utf8_len);
359 	    vim_free(utf8_str);
360 	}
361 
362 	convert_setup(&conv, NULL, NULL);
363 
364 	if (actualLen)
365 	    *actualLen = utf8_len;
366     }
367     else if (actualLen)
368 	*actualLen = 0;
369 
370     return result;
371 }
372 
373 /*
374  * Conversion from 'encoding' to UTF-16 UniChars
375  * The function return uses the real type of UniChar (as typedef'ed in
376  * CFBase.h) to avoid clashes with X11 header files in the .pro file
377  */
378     unsigned short *
379 mac_enc_to_utf16(from, fromLen, actualLen)
380     char_u *from;
381     size_t fromLen;
382     size_t *actualLen;
383 {
384     /* Following code borrows somewhat from os_mswin.c */
385     vimconv_T	conv;
386     size_t      utf8_len;
387     char_u      *utf8_str;
388     UniChar     *result = NULL;
389     Boolean     should_free_utf8 = FALSE;
390 
391     do
392     {
393 	/* Use MacRoman by default, we might be called before we have p_enc
394 	 * set up.  Convert to utf-8 first, works better with iconv().  Does
395 	 * nothing if 'encoding' is "utf-8". */
396 	conv.vc_type = CONV_NONE;
397 	if ((enc_canon_props(p_enc) & ENC_UNICODE) == 0 &&
398 		convert_setup(&conv, p_enc ? p_enc : (char_u *)"macroman",
399 		    (char_u *)"utf-8") == FAIL)
400 	    break;
401 
402 	if (conv.vc_type != CONV_NONE)
403 	{
404 	    utf8_len = fromLen;
405 	    utf8_str = string_convert(&conv, from, (int *)&utf8_len);
406 	    should_free_utf8 = TRUE;
407 	}
408 	else
409 	{
410 	    utf8_str = from;
411 	    utf8_len = fromLen;
412 	}
413 
414 	if (utf8_str == NULL)
415 	    break;
416 
417 	convert_setup(&conv, NULL, NULL);
418 
419 	result = mac_utf8_to_utf16(utf8_str, utf8_len, actualLen);
420 
421 	if (should_free_utf8)
422 	    vim_free(utf8_str);
423 	return result;
424     }
425     while (0);
426 
427     if (actualLen)
428 	*actualLen = 0;
429 
430     return result;
431 }
432 
433 /*
434  * Converts from UTF-16 UniChars to CFString
435  * The void * return type is actually a CFStringRef
436  */
437     void *
438 mac_enc_to_cfstring(from, fromLen)
439     char_u  *from;
440     size_t  fromLen;
441 {
442     UniChar	*utf16_str;
443     size_t	utf16_len;
444     CFStringRef	result = NULL;
445 
446     utf16_str = mac_enc_to_utf16(from, fromLen, &utf16_len);
447     if (utf16_str)
448     {
449 	result = CFStringCreateWithCharacters(NULL, utf16_str, utf16_len/sizeof(UniChar));
450 	vim_free(utf16_str);
451     }
452 
453     return (void *)result;
454 }
455 
456 /*
457  * Converts a decomposed HFS+ UTF-8 path to precomposed UTF-8
458  */
459     char_u *
460 mac_precompose_path(decompPath, decompLen, precompLen)
461     char_u  *decompPath;
462     size_t  decompLen;
463     size_t  *precompLen;
464 {
465     char_u  *result = NULL;
466     size_t  actualLen = 0;
467 
468     if (gPathConverter)
469     {
470 	result = alloc(decompLen);
471 	if (result)
472 	{
473 	    if (TECConvertText(gPathConverter, decompPath,
474 			decompLen, &decompLen, result,
475 			decompLen, &actualLen) != noErr)
476 	    {
477 		vim_free(result);
478 		result = NULL;
479 	    }
480 	}
481     }
482 
483     if (precompLen)
484 	*precompLen = actualLen;
485 
486     return result;
487 }
488 
489 /*
490  * Converts from UTF-16 UniChars to precomposed UTF-8
491  */
492     static char_u *
493 mac_utf16_to_utf8(from, fromLen, actualLen)
494     UniChar *from;
495     size_t fromLen;
496     size_t *actualLen;
497 {
498     ByteCount		utf8_len;
499     ByteCount		inputRead;
500     char_u		*result;
501 
502     if (gUTF16ToUTF8Converter)
503     {
504 	result = alloc(fromLen * 6 + 1);
505 	if (result && TECConvertText(gUTF16ToUTF8Converter, (ConstTextPtr)from,
506 		    fromLen, &inputRead, result,
507 		    (fromLen*6+1)*sizeof(char_u), &utf8_len) == noErr)
508 	{
509 	    TECFlushText(gUTF16ToUTF8Converter, result, (fromLen*6+1)*sizeof(char_u), &inputRead);
510 	    utf8_len += inputRead;
511 	}
512 	else
513 	{
514 	    vim_free(result);
515 	    result = NULL;
516 	}
517     }
518     else
519     {
520 	result = NULL;
521     }
522 
523     if (actualLen)
524 	*actualLen = result ? utf8_len : 0;
525 
526     return result;
527 }
528 
529 /*
530  * Converts from UTF-8 to UTF-16 UniChars
531  */
532     static UniChar *
533 mac_utf8_to_utf16(from, fromLen, actualLen)
534     char_u *from;
535     size_t fromLen;
536     size_t *actualLen;
537 {
538     CFStringRef  utf8_str;
539     CFRange      convertRange;
540     UniChar      *result = NULL;
541 
542     utf8_str = CFStringCreateWithBytes(NULL, from, fromLen,
543 	    kCFStringEncodingUTF8, FALSE);
544 
545     if (utf8_str == NULL) {
546 	if (actualLen)
547 	    *actualLen = 0;
548 	return NULL;
549     }
550 
551     convertRange = CFRangeMake(0, CFStringGetLength(utf8_str));
552     result = (UniChar *)alloc(convertRange.length * sizeof(UniChar));
553 
554     CFStringGetCharacters(utf8_str, convertRange, result);
555 
556     CFRelease(utf8_str);
557 
558     if (actualLen)
559 	*actualLen = convertRange.length * sizeof(UniChar);
560 
561     return result;
562 }
563 
564 /*
565  * Sets LANG environment variable in Vim from Mac locale
566  */
567     void
568 mac_lang_init() {
569     if (mch_getenv((char_u *)"LANG") == NULL)
570     {
571 	char	buf[20];
572 	if (LocaleRefGetPartString(NULL,
573 		    kLocaleLanguageMask | kLocaleLanguageVariantMask |
574 		    kLocaleRegionMask | kLocaleRegionVariantMask,
575 		    sizeof buf, buf) == noErr && *buf)
576 	{
577 	    vim_setenv((char_u *)"LANG", (char_u *)buf);
578 #   ifdef HAVE_LOCALE_H
579 	    setlocale(LC_ALL, "");
580 #   endif
581 	}
582     }
583 }
584 #endif /* MACOS_CONVERT */
585