diff options
author | Eduardo Chappa <echappa@gmx.com> | 2013-02-03 00:59:38 -0700 |
---|---|---|
committer | Eduardo Chappa <echappa@gmx.com> | 2013-02-03 00:59:38 -0700 |
commit | 094ca96844842928810f14844413109fc6cdd890 (patch) | |
tree | e60efbb980f38ba9308ccb4fb2b77b87bbc115f3 /pith/charconv/utf8.c | |
download | alpine-094ca96844842928810f14844413109fc6cdd890.tar.xz |
Initial Alpine Version
Diffstat (limited to 'pith/charconv/utf8.c')
-rw-r--r-- | pith/charconv/utf8.c | 2512 |
1 files changed, 2512 insertions, 0 deletions
diff --git a/pith/charconv/utf8.c b/pith/charconv/utf8.c new file mode 100644 index 00000000..411e1ddd --- /dev/null +++ b/pith/charconv/utf8.c @@ -0,0 +1,2512 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: utf8.c 1184 2008-12-16 23:52:15Z hubert@u.washington.edu $"; +#endif + +/* + * ======================================================================== + * Copyright 2006-2008 University of Washington + * Copyright 2013 Eduardo Chappa + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * ======================================================================== + */ + + +/* includable WITHOUT dependency on c-client */ +#include "../../c-client/mail.h" +#include "../../c-client/utf8.h" + +#ifdef _WINDOWS +/* wingdi.h uses ERROR (!) and we aren't using the c-client ERROR so... */ +#undef ERROR +#endif + +#include <system.h> + +#include "../../c-client/fs.h" + +/* includable WITHOUT dependency on pico */ +#include "../../pico/keydefs.h" + +#include "../osdep/collate.h" +#include "../filttype.h" + +#include "utf8.h" + +#include <stdarg.h> + + +unsigned single_width_chars_a_to_b(UCS *, int, int); + + +static char locale_charmap[50]; + +static int native_utf8; +static void *display_data; + +void +init_utf8_display(int utf8, void *rmap) +{ + native_utf8 = utf8; + display_data = rmap; +} + + +/* + * Argument is a UCS-4 wide character. + * Returns the environment dependent cell width of the + * character when printed to the screen. + * This will be -1 if the character is not printable. + * It will be >= zero if it is printable. + * + * Note that in the case it is not printable but it is still sent to + * Writechar, Writechar will print a '?' with width 1. + */ +int +wcellwidth(UCS ucs) +{ + char dummy[32]; + long w; + + /* + * We believe that on modern unix systems wchar_t is a UCS-4 character. + * That's the assumption here. + */ + + if(native_utf8){ /* display is UTF-8 capable */ + w = ucs4_width((unsigned long) ucs); + return((w & U4W_ERROR) ? -1 : w); + } + else if(display_data){ + if(wtomb(dummy, ucs) < 0) + return(-1); + else{ + w = ucs4_width((unsigned long) ucs); + return((w & U4W_ERROR) ? -1 : w); + } + } +#ifndef _WINDOWS + else + return(wcwidth((wchar_t) ucs)); +#else + return(0); +#endif +} + + +/* + * Argument is a UCS-4 wide character. + * It is converted to the multibyte version (for example UTF8 or EUC-JP). + * Dest is a buffer at least xx chars wide where the multi-byte version + * of the wide character will be written. + * The returned value is the number of bytes written to dest or -1 + * if the conversion can't be done. + */ +int +wtomb(char *dest, UCS ucs) +{ + /* + * We believe that on modern unix systems wchar_t is a UCS-4 character. + * That's the assumption here. + */ + + if(native_utf8){ + unsigned char *newdptr; + + newdptr = utf8_put((unsigned char *) dest, (unsigned long) ucs); + return((newdptr == (unsigned char *) dest) ? -1 : newdptr - (unsigned char *) dest); + } + else if(display_data){ + unsigned long ucs4; + int ret; + + ucs4 = (unsigned long) ucs; + ret = ucs4_rmaplen(&ucs4, 1, (unsigned short *) display_data, 0); + if(ret >= 0) + ucs4_rmapbuf((unsigned char *) dest, &ucs4, 1, (unsigned short *) display_data, 0); + else + ret = -1; + + return(ret); + } + else + return(wcrtomb(dest, (wchar_t) ucs, NULL)); +} + + +/* + * This function does not necessarily update inputp and remaining_octets, so + * don't rely on that. The c-client version does but the other doesn't. + */ +UCS +mbtow(void *input_cs, unsigned char **inputp, unsigned long *remaining_octets) +{ + UCS ucs; + + if(input_cs){ + CHARSET *cast_input_cs; + + cast_input_cs = (CHARSET *) input_cs; + + switch((ucs = (UCS) ucs4_cs_get(cast_input_cs, inputp, remaining_octets))){ + case U8G_ENDSTRG: + case U8G_ENDSTRI: + return(CCONV_NEEDMORE); + + default: + if(ucs & U8G_ERROR || ucs == UBOGON) + return(CCONV_BADCHAR); + + return(ucs); + } + } + else{ + size_t ret; + wchar_t w; + + /* + * Warning: input_cs and remaining_octets are unused in this + * half of the if/else. + * + * Unfortunately, we can't tell the difference between a source string + * that is just not long enough and one that has characters that can't + * be converted even though it is long enough. We return NEEDMORE in both cases. + */ + ret = mbstowcs(&w, (char *) (*inputp), 1); + if(ret == (size_t)(-1)) + return(CCONV_NEEDMORE); + else{ + ucs = (UCS) w; + return(ucs); + } + } +} + + +void +set_locale_charmap(char *charmap) +{ + if(charmap){ + strncpy(locale_charmap, charmap, sizeof(locale_charmap)); + locale_charmap[sizeof(locale_charmap)-1] = '\0'; + } + else + locale_charmap[0] = '\0'; +} + + +/* + * This ensures that the string is UTF-8. If str is already a UTF-8 string, + * NULL is returned. Otherwise, an allocated string which is UTF-8 is returned. + * The caller is responsible for freeing the returned value. + * + * Args str -- the string to convert + */ +char * +convert_to_utf8(char *str, char *fromcharset, int flags) +{ + char *ret = NULL; + char *fcharset; + SIZEDTEXT src, result; + const CHARSET *cs; + int try; + + src.data = (unsigned char *) str; + src.size = strlen(str); + + /* already UTF-8, return NULL */ + if(!(flags & CU8_NOINFER) + && (cs = utf8_infercharset(&src)) + && (cs->type == CT_ASCII || cs->type == CT_UTF8)) + return(ret); + + try = 1; + while(try < 5){ + switch(try){ + case 1: + fcharset = fromcharset; + if(fcharset && strucmp("UTF-8", fcharset) != 0) + break; /* give it a try */ + else + try++; /* fall through */ + + case 2: + if(!(flags & CU8_NOINFER)){ + fcharset = cs ? cs->name : NULL; + if(fcharset && strucmp("UTF-8", fcharset) != 0) + break; + else + try++; /* fall through */ + } + else + try++; /* fall through */ + + case 3: + fcharset = locale_charmap; + if(fcharset && strucmp("UTF-8", fcharset) != 0) + break; + else + try++; /* fall through */ + + default: + fcharset = "ISO-8859-1"; /* this will "work" */ + break; + } + + memset(&result, 0, sizeof(result)); + + if(fcharset && utf8_text(&src, fcharset, &result, 0L)){ + if(!(result.size == src.size && result.data == src.data)){ + ret = (char *) fs_get((result.size+1) * sizeof(char)); + strncpy(ret, (char *) result.data, result.size); + ret[result.size] = '\0'; + } + /* else no conversion necessary */ + + return(ret); + } + + try++; + } + + /* won't make it to here */ + return(ret); +} + + +/* + * Convert from UTF-8 to user's locale charset. + * This actually uses the wtomb routine to do the conversion, and that + * relies on setup_for_input_output having been called. + * If no conversion is necessary, NULL is returned, otherwise an allocated + * string in the locale charset is returned and the caller is responsible + * for freeing it. + */ +char * +convert_to_locale(char *utf8str) +{ +#define CHNK 500 + char *inp, *retp, *ret = NULL; + CBUF_S cb; + int r, alloced; + + if(native_utf8 || !utf8str || !utf8str[0]) + return(NULL); + + cb.cbuf[0] = '\0'; + cb.cbufp = cb.cbufend = cb.cbuf; + inp = utf8str; + + alloced = CHNK; + ret = (char *) fs_get(alloced * sizeof(char)); + retp = ret; + + /* + * There's gotta be a better way to do this but utf8_to_locale was + * available and everything looks like a nail when all you have + * is a hammer. + */ + while(*inp){ + /* + * We're placing the outgoing stream of characters in ret, a multi-byte + * array of characters in the user's locale charset. See if there is + * enough room for the next wide characters worth of output chars + * and allocate more space if not. + */ + if((alloced - (retp-ret)) < MAX(MB_LEN_MAX,32)){ + alloced += CHNK; + fs_resize((void **) &ret, alloced * sizeof(char)); + } + + r = utf8_to_locale((int) *inp++, &cb, + (unsigned char *) retp, alloced-(retp-ret)); + + retp += r; + } + + *retp = '\0'; + + fs_resize((void **) &ret, strlen(ret)+1); + + return(ret); +} + + +/* + * Pass in a stream of UTF-8 characters in 'c' and return obuf + * filled in with multi-byte characters. The return value is the + * number of valid characters in obuf to be used. + */ +int +utf8_to_locale(int c, CBUF_S *cb, unsigned char obuf[], size_t obuf_size) +{ + int outchars = 0; + + if(!(cb && cb->cbufp)) + return(0); + + if(cb->cbufp < cb->cbuf+sizeof(cb->cbuf)){ + unsigned char *inputp; + unsigned long remaining_octets; + UCS ucs; + + *(cb->cbufp)++ = (unsigned char) c; + inputp = cb->cbuf; + remaining_octets = (cb->cbufp - cb->cbuf) * sizeof(unsigned char); + ucs = (UCS) utf8_get(&inputp, &remaining_octets); + + switch(ucs){ + case U8G_ENDSTRG: /* incomplete character, wait */ + case U8G_ENDSTRI: /* incomplete character, wait */ + break; + + default: + if(ucs & U8G_ERROR || ucs == UBOGON){ + /* + * None of these cases is supposed to happen. If it + * does happen then the input stream isn't UTF-8 + * so something is wrong. Treat each character in the + * input buffer as a separate error character and + * print a '?' for each. + */ + for(inputp = cb->cbuf; inputp < cb->cbufp; inputp++) + obuf[outchars++] = '?'; + + cb->cbufp = cb->cbuf; + } + else{ + if(ucs >= 0x80 && wcellwidth(ucs) < 0){ + /* + * This happens when we have a UTF-8 character that + * we aren't able to print in our locale. For example, + * if the locale is setup with the terminal + * expecting ISO-8859-1 characters then there are + * lots of UTF-8 characters that can't be printed. + * Print a '?' instead. + */ + obuf[outchars++] = '?'; + } + else{ + /* + * Convert the ucs into the multibyte + * character that corresponds to the + * ucs in the users locale. + */ + outchars = wtomb((char *) obuf, ucs); + if(outchars < 0){ + obuf[0] = '?'; + outchars = 1; + } + } + + /* update the input buffer */ + if(inputp >= cb->cbufp) /* this should be the case */ + cb->cbufp = cb->cbuf; + else{ /* extra chars for some reason? */ + unsigned char *q, *newcbufp; + + newcbufp = (cb->cbufp - inputp) + cb->cbuf; + q = cb->cbuf; + while(inputp < cb->cbufp) + *q++ = *inputp++; + + cb->cbufp = newcbufp; + } + } + + break; + } + } + else{ /* error */ + obuf[0] = '?'; + outchars = 1; + cb->cbufp = cb->cbuf; /* start over */ + } + + return(outchars); +} + + +/* + * Returns the screen cells width of the UCS-4 string argument. + * The source string is zero terminated. + */ +unsigned +ucs4_str_width(UCS *ucsstr) +{ + unsigned width = 0; + int w; + + if(ucsstr) + while(*ucsstr){ + w = wcellwidth(*ucsstr++); + if(w != U4W_CTLSRGT) + width += (w < 0 ? 1 : w); + } + + return width; +} + + +/* + * Returns the screen cells width of the UCS-4 string argument + * from ucsstr[a] through (inclusive) ucsstr[b]. + * No checking is done to make sure a starts in the middle + * of a UCS-4 array. + */ +unsigned +ucs4_str_width_a_to_b(UCS *ucsstr, int a, int b) +{ + unsigned width = 0; + int i, w; + + if(ucsstr) + for(i = a; i <= b && ucsstr[i]; i++){ + w = wcellwidth(ucsstr[i]); + if(w != U4W_CTLSRGT) + width += (w < 0 ? 1 : w); + } + + return width; +} + + +/* + * Returns the screen cells width of the UCS-4 string argument + * from ustart through (exclusive) uend. + * No checking is done to make sure it starts in the middle + * of a UCS-4 array. + */ +unsigned +ucs4_str_width_ptr_to_ptr(UCS *ustart, UCS *uend) +{ + UCS *u; + unsigned width = 0; + int w; + + if(!ustart) + return width; + + if(ustart) + for(u = ustart; u < uend; u++){ + w = wcellwidth(*u); + if(w != U4W_CTLSRGT) + width += (w < 0 ? 1 : w); + } + + return(width); +} + + +/* + * Return the largest possible pointer into ucs4str so that the width + * of the string from ucs4str to the pointer (exclusive) + * is maxwidth or less. Also stops at a null character. + */ +UCS * +ucs4_particular_width(UCS *ucs4str, int maxwidth) +{ + UCS *u; + int w_consumed = 0, w, done = 0; + + u = ucs4str; + + if(u) + while(!done && *u && w_consumed <= maxwidth){ + w = wcellwidth(*u); + w = (w >= 0 ? w : 1); + if(w_consumed + w <= maxwidth){ + w_consumed += w; + ++u; + } + else + ++done; + } + + return(u); +} + + +/* + * Convert and copy a UTF-8 string into a UCS-4 NULL + * terminated array. Just like cpystr only it converts + * from UTF-8 to UCS-4. + * + * Returned UCS-4 string needs to be freed by caller. + */ +UCS * +utf8_to_ucs4_cpystr(char *utf8src) +{ + size_t retsize; + UCS *ret = NULL; + UCS ucs; + unsigned long remaining_octets; + unsigned char *readptr; + size_t arrayindex; + + /* + * We don't know how big to allocate the return array + * because variable numbers of octets in the src array + * will combine to make UCS-4 characters. The number of + * UCS-4 characters is less than or equal to the number + * of src characters, though. + */ + + if(!utf8src) + return NULL; + + retsize = strlen(utf8src) + 1; + + ret = (UCS *) fs_get(retsize * sizeof(*ret)); + memset(ret, 0, retsize * sizeof(*ret)); + + readptr = (unsigned char *) utf8src; + remaining_octets = retsize-1; + arrayindex = 0; + + while(remaining_octets > 0 && *readptr && arrayindex < retsize-1){ + ucs = (UCS) utf8_get(&readptr, &remaining_octets); + + if(ucs & U8G_ERROR || ucs == UBOGON) + remaining_octets = 0; + else + ret[arrayindex++] = ucs; + } + + ret[arrayindex] = '\0'; + + /* get rid of excess size */ + if(arrayindex+1 < retsize) + fs_resize((void **) &ret, (arrayindex + 1) * sizeof(*ret)); + + return ret; +} + + +/* + * Convert and copy a UCS-4 zero-terminated array into a UTF-8 NULL + * terminated string. Just like cpystr only it converts + * from UCS-4 to UTF-8. + * + * Returned UTF-8 string needs to be freed by caller. + */ +char * +ucs4_to_utf8_cpystr(UCS *ucs4src) +{ + unsigned char *ret = NULL; + unsigned char *writeptr; + int i; + + if(!ucs4src) + return NULL; + + /* + * Over-allocate and then resize at the end. + */ + + /* count characters in source */ + for(i = 0; ucs4src[i]; i++) + ; + + ret = (unsigned char *) fs_get((6*i + 1) * sizeof(*ret)); + memset(ret, 0, (6*i + 1) * sizeof(*ret)); + + writeptr = ret; + for(i = 0; ucs4src[i]; i++) + writeptr = utf8_put(writeptr, (unsigned long) ucs4src[i]); + + /* get rid of excess size */ + fs_resize((void **) &ret, (writeptr - ret + 1) * sizeof(*ret)); + + return ((char *) ret); +} + + +/* + * Similar to above but copy a fixed number of source + * characters instead of going until null terminator. + */ +char * +ucs4_to_utf8_cpystr_n(UCS *ucs4src, int ucs4src_len) +{ + unsigned char *ret = NULL; + unsigned char *writeptr; + int i; + + if(!ucs4src) + return NULL; + + /* + * Over-allocate and then resize at the end. + */ + + ret = (unsigned char *) fs_get((6*ucs4src_len + 1) * sizeof(*ret)); + memset(ret, 0, (6*ucs4src_len + 1) * sizeof(*ret)); + + writeptr = ret; + for(i = 0; i < ucs4src_len; i++) + writeptr = utf8_put(writeptr, (unsigned long) ucs4src[i]); + + /* get rid of excess size */ + fs_resize((void **) &ret, (writeptr - ret + 1) * sizeof(*ret)); + + return ((char *) ret); +} + + +#ifdef _WINDOWS +/* + * Convert a UTF-8 argument into an LPTSTR version + * of that argument. The result is allocated here + * and should be freed by the caller. + */ +LPTSTR +utf8_to_lptstr(LPSTR arg_utf8) +{ + int lptstr_len; + LPTSTR lptstr_ret = NULL; + + lptstr_len = MultiByteToWideChar( CP_UTF8, 0, arg_utf8, -1, NULL, 0 ); + if(lptstr_len > 0) + { + lptstr_ret = (LPTSTR)fs_get(lptstr_len * sizeof(TCHAR)); + lptstr_len = MultiByteToWideChar( CP_UTF8, 0, + arg_utf8, -1, lptstr_ret, lptstr_len ); + } + + if(!lptstr_len) + { + /* check GetLastError()? */ + lptstr_ret = (LPTSTR)fs_get(sizeof(TCHAR)); + lptstr_ret[0] = 0; + } + + return lptstr_ret; +} + + +/* + * Convert an LPTSTR argument into a UTF-8 version + * of that argument. The result is allocated here + * and should be freed by the caller. + */ +LPSTR +lptstr_to_utf8(LPTSTR arg_lptstr) +{ + int utf8str_len; + LPSTR utf8str_ret = NULL; + + utf8str_len = WideCharToMultiByte( CP_UTF8, 0, arg_lptstr, -1, NULL, 0, NULL, NULL ); + if(utf8str_len > 0) + { + utf8str_ret = (LPSTR)fs_get(utf8str_len * sizeof(CHAR)); + utf8str_len = WideCharToMultiByte( CP_UTF8, 0, + arg_lptstr, -1, utf8str_ret, utf8str_len, NULL, NULL ); + } + + if(!utf8str_len) + { + /* check GetLastError()? */ + utf8str_ret = (LPSTR)fs_get(sizeof(CHAR)); + utf8str_ret[0] = 0; + } + + return utf8str_ret; +} + + +/* + * Convert a UCS4 argument into an LPTSTR version + * of that argument. The result is allocated here + * and should be freed by the caller. + */ +LPTSTR +ucs4_to_lptstr(UCS *arg_ucs4) +{ + LPTSTR ret_lptstr = NULL; + size_t len; + size_t i; + + if(arg_ucs4){ + len = ucs4_strlen(arg_ucs4); + ret_lptstr = (LPTSTR) fs_get((len+1) * sizeof(TCHAR)); + /* bogus conversion ignores UTF-16 */ + for(i = 0; i < len; i++) + ret_lptstr[i] = arg_ucs4[i]; + + ret_lptstr[len] = '\0'; + } + + return(ret_lptstr); +} + + +/* + * Convert an LPTSTR argument into a UCS4 version + * of that argument. The result is MemAlloc'd here + * and should be freed by the caller. + */ +UCS * +lptstr_to_ucs4(LPTSTR arg_lptstr) +{ + UCS *ret_ucs4 = NULL; + size_t len; + size_t i; + + if(arg_lptstr){ + len = _tcslen(arg_lptstr); + ret_ucs4 = (UCS *) fs_get((len+1)*sizeof(UCS)); + /* bogus conversion ignores UTF-16 */ + for(i = 0; i < len; i++) + ret_ucs4[i] = arg_lptstr[i]; + + ret_ucs4[len] = '\0'; + } + + return(ret_ucs4); +} + +#endif /* _WINDOWS */ + + +/* + * Pass in a stream of UTF-8 characters 1-at-a-time in 'c' and return obuf + * 1-at-a-time filled in with UCS characters. The return value is the + * number of valid characters in obuf to be used. It can only + * be 1 or 0 characters since we're only getting one UTF-8 character + * at a time. + */ +int +utf8_to_ucs4_oneatatime(int c, CBUF_S *cb, UCS *obuf, int *obufwidth) +{ + int width = 0, outchars = 0; + + if(!(cb && cb->cbufp)) + return(0); + + if(cb->cbufp < cb->cbuf+sizeof(cb->cbuf)){ + unsigned char *inputp; + unsigned long remaining_octets; + UCS ucs; + + *cb->cbufp++ = (unsigned char) c; + inputp = cb->cbuf; + remaining_octets = (cb->cbufp - cb->cbuf) * sizeof(unsigned char); + ucs = (UCS) utf8_get(&inputp, &remaining_octets); + + switch(ucs){ + case U8G_ENDSTRG: /* incomplete character, wait */ + case U8G_ENDSTRI: /* incomplete character, wait */ + break; + + default: + if(ucs & U8G_ERROR || ucs == UBOGON){ + /* + * None of these cases is supposed to happen. If it + * does happen then the input stream isn't UTF-8 + * so something is wrong. + */ + outchars++; + *obuf = '?'; + cb->cbufp = cb->cbuf; + width = 1; + } + else{ + outchars++; + if(ucs < 0x80 && ucs >= 0x20) + width = 1; + + if(ucs >= 0x80 && (width=wcellwidth(ucs)) < 0){ + /* + * This happens when we have a UTF-8 character that + * we aren't able to print in our locale. For example, + * if the locale is setup with the terminal + * expecting ISO-8859-1 characters then there are + * lots of UTF-8 characters that can't be printed. + * Print a '?' instead. + * Don't think this should happen in Windows. + */ + *obuf = '?'; + } + else{ + *obuf = ucs; + } + + /* update the input buffer */ + if(inputp >= cb->cbufp) /* this should be the case */ + cb->cbufp = cb->cbuf; + else{ /* extra chars for some reason? */ + unsigned char *q, *newcbufp; + + newcbufp = (cb->cbufp - inputp) + cb->cbuf; + q = cb->cbuf; + while(inputp < cb->cbufp) + *q++ = *inputp++; + + cb->cbufp = newcbufp; + } + } + + break; + } + } + else{ /* error */ + *obuf = '?'; + outchars = 1; + width = 1; + cb->cbufp = cb->cbuf; /* start over */ + } + + if(obufwidth) + *obufwidth = width; + + return(outchars); +} + + +/* + * Return an allocated copy of a zero-terminated UCS-4 string. + */ +UCS * +ucs4_cpystr(UCS *ucs4src) +{ + size_t arraysize; + UCS *ret = NULL; + size_t i; + + if(!ucs4src) + return NULL; + + arraysize = ucs4_strlen(ucs4src); + + ret = (UCS *) fs_get((arraysize+1) * sizeof(*ret)); + memset(ret, 0, (arraysize+1) * sizeof(*ret)); + + for(i = 0; i < arraysize; i++) + ret[i] = ucs4src[i]; + + return ret; +} + + +UCS * +ucs4_strncpy(UCS *ucs4dst, UCS *ucs4src, size_t n) +{ + size_t i; + + if(ucs4src && ucs4dst){ + for(i = 0; i < n; i++){ + ucs4dst[i] = ucs4src[i]; + if(ucs4dst[i] == '\0') + break; + } + } + + return ucs4dst; +} + + +UCS * +ucs4_strncat(UCS *ucs4dst, UCS *ucs4src, size_t n) +{ + size_t i; + UCS *u; + + if(ucs4src && ucs4dst){ + for(u = ucs4dst; *u; u++) + ; + + for(i = 0; i < n; i++){ + u[i] = ucs4src[i]; + if(u[i] == '\0') + break; + } + + if(i == n) + u[i] = '\0'; + } + + return ucs4dst; +} + + +/* + * Like strlen only this returns the number of non-zero characters + * in a zero-terminated UCS-4 array. + */ +size_t +ucs4_strlen(UCS *ucs4str) +{ + size_t i = 0; + + if(ucs4str) + while(ucs4str[i]) + i++; + + return(i); +} + + +int +ucs4_strcmp(UCS *s1, UCS *s2) +{ + for(; *s1 == *s2; s1++, s2++) + if(*s1 == '\0') + return 0; + + return((*s1 < *s2) ? -1 : 1); +} + + +UCS * +ucs4_strchr(UCS *s, UCS c) +{ + if(!s) + return NULL; + + while(*s && *s != c) + s++; + + if(*s || !c) + return s; + else + return NULL; +} + + +UCS * +ucs4_strrchr(UCS *s, UCS c) +{ + UCS *ret = NULL; + + if(!s) + return ret; + + while(*s){ + if(*s == c) + ret = s; + + s++; + } + + return ret; +} + + +/* + * Returns the screen cells width of the UTF-8 string argument. + */ +unsigned +utf8_width(char *str) +{ + unsigned width = 0; + int this_width; + UCS ucs; + unsigned long remaining_octets; + char *readptr; + + if(!(str && *str)) + return(width); + + readptr = str; + remaining_octets = readptr ? strlen(readptr) : 0; + + while(remaining_octets > 0 && *readptr){ + + ucs = (UCS) utf8_get((unsigned char **) &readptr, &remaining_octets); + + if(ucs & U8G_ERROR || ucs == UBOGON){ + /* + * This should not happen, but do something to handle it anyway. + * Treat each character as a single width character, which is what should + * probably happen when we actually go to write it out. + */ + remaining_octets--; + readptr++; + this_width = 1; + } + else{ + this_width = wcellwidth(ucs); + + /* + * If this_width is -1 that means we can't print this character + * with our current locale. Writechar will print a '?'. + */ + if(this_width < 0) + this_width = 1; + } + + width += (unsigned) this_width; + } + + return(width); +} + + +/* + * Copy UTF-8 characters from src into dst. + * This is intended to be used if you want to truncate a string at + * the start instead of the end. For example, you have a long string + * like + * this_is_a_long_string + * but not enough space to fit it into a particular field. You want to + * end up with + * s_a_long_string + * where that fits in a particular width. Perhaps you'd use this with ... + * to get + * ...s_a_long_string + * This right adjusts the end of the string in the width space and + * cuts it off at the start. If there is enough width for the whole + * string it will copy the string into dst with no padding. + * + * Copy enough characters so that the result will have screen width of + * want_width screen cells in current locale. + * + * Dstlen is the available space in dst. No more than dstlen bytes will be written + * to dst. This is just for protection, it shouldn't be relied on to + * do anything useful. Dstlen should be large enough. Otherwise you'll get + * characters truncated in the middle or something like that. + * + * Returned value is the number of bytes written to dst, not including + * the possible terminating null. + * + * If we can't hit want_width exactly because of double width characters + * then we will pad the end of the string with space in order to make + * the width exact. + */ +size_t +utf8_to_width_rhs(char *dst, /* destination buffer */ + char *src, /* source string */ + size_t dstlen, /* space in dest */ + unsigned want_width) /* desired screen width */ +{ + int this_width; + unsigned width_consumed = 0; + UCS ucs; + unsigned long remaining_octets; + char *readptr, *goodreadptr, *savereadptr, *endptr; + size_t nb = 0; + + if(!src){ + if(dstlen > 0) + dst[0] = '\0'; + + return nb; + } + + /* + * Start at the end of the source string and go backwards until we + * get to the desired width, but not more than the width. + */ + readptr = src + strlen(src); + endptr = readptr; + goodreadptr = readptr; + width_consumed = 0; + savereadptr = readptr; + + for(readptr = savereadptr-1; readptr >= src && width_consumed < want_width && (endptr - readptr) < dstlen; + readptr = savereadptr-1){ + + savereadptr = readptr; + remaining_octets = goodreadptr - readptr; + ucs = (UCS) utf8_get((unsigned char **) &readptr, &remaining_octets); + + /* + * Handling the error case is tough because an error will be the normal thing that + * happens as we back through the string. So we're just going to punt on the + * error for now. + */ + if(!(ucs & U8G_ERROR || ucs == UBOGON)){ + if(remaining_octets > 0){ + /* + * This means there are some bad octets after this good + * character so things are not going to work out well. + * Bail out. + */ + savereadptr = src; /* we're done */ + } + else{ + this_width = wcellwidth(ucs); + + if(this_width < 0) + this_width = 1; + + if(width_consumed + (unsigned) this_width <= want_width){ /* ok */ + width_consumed += (unsigned) this_width; + goodreadptr = savereadptr; + } + else + savereadptr = src; /* we're done */ + } + } + } + + /* + * Copy characters from goodreadptr to endptr into dst. + */ + nb = MIN(endptr-goodreadptr, dstlen-1); + strncpy(dst, goodreadptr, nb); + dst[nb] = '\0'; + + /* + * Pad out with spaces in order to hit width exactly. + */ + while(width_consumed < want_width && nb < dstlen-1){ + dst[nb++] = ' '; + dst[nb] = '\0'; + width_consumed++; + } + + return nb; +} + + +/* + * The arguments being converted are UTF-8 strings. + * This routine attempts to make it possible to use screen cell + * widths in a format specifier. In a one-byte per screen cell + * world we might have used %10.10s to cause a string to occupy + * 10 screen positions. Since the width and precision are really + * referring to numbers of bytes instead of screen positions that + * won't work with UTF-8 input. We emulate that behavior with + * the format string %w. %m.nw means to use the m and n as + * screen width indicators instead of bytes indicators. + * + * There is no reason to use this routine unless you want to use + * min field with or precision with the specifier. A plain %w without + * widths is equivalent exactly to a plain %s in a regular printf. + * + * Double-width characters complicate things. It may not be possible + * to satisfy the request exactly. For example, %3w for an input + * string that is made up of two double-width characters. + * This routine will arbitrarily use a trailing space character if + * needed to make the width come out correctly where a half of a + * double-width character would have been needed. We'll see how + * that works for us. + * + * %w only works for strings (it's a %s replacement). + * + * Buffer overflow is handled by the size argument. %.30s will work + * to limit a particular string to 30 bytes, but you lose that + * ability with %w, since it may write more than precision bytes + * in order to get to the desired width. It is best to choose + * size large enough so that it doesn't come into play, otherwise + * it may be possible to get partial UTF-8 characters because of + * the truncation. + * + * The return value isn't quite the same as the return value + * of snprintf. It is the number of bytes written, not counting + * the trailing null, just like snprintf. However, if it is + * truncated due to size then the output is size, not the + * number of characters that would have been written. + */ +int +utf8_snprintf(char *dest, size_t size, char *fmt, ...) +{ + char newfmt[100], buf[20], *q, *pdest, *width_str, *end; + char *start_of_specifier; + char *input_str; + int int_arg; + double double_arg; + void *ptr_arg; + unsigned got_width; + int more_flags, ret, w; + int min_field_width, field_precision, modifier; + int flags_minus, flags_plus, flags_space, flags_zero, flags_pound; + va_list args; + + newfmt[0] = '\0'; + q = newfmt; + + pdest = dest; + +#define IS_ROOM_IN_DEST(n_more_chars) \ + ((pdest - dest + (n_more_chars) <= size) ? 1 : 0) + + /* + * Strategy: Look through the fmt string for %w's. Replace the + * %w's in the format string with %s's but with possibly different + * width and precision arguments which will make it come out right. + * Then call the regular system vsnprintf with the altered format + * string but same arguments. + * + * That would be nice but it doesn't quite work. Why? Because a + * %*w will need to have the value in the integer argument the * + * refers to modified. Can't do it as far as I can tell. Or we could + * remove the integer argument somehow before calling printf. Can't + * do it. Or we could somehow add an additional conversion specifier + * that caused nothing to be printed but ate up the integer arg. + * Can't figure out how to do that either. + * + * Since we can't figure out how to do it, the alternative is to + * construct the result one piece at a time, pasting together the + * pieces from the different conversions. + */ + va_start(args, fmt); + + while(*fmt && IS_ROOM_IN_DEST(1)){ + if(*fmt == '%'){ + start_of_specifier = fmt++; + + min_field_width = field_precision = -1; + flags_minus = flags_plus = flags_space = flags_zero = flags_pound = 0; + + /* flags */ + more_flags = 1; + while(more_flags){ + switch(*fmt){ + case '-': + flags_minus++; + fmt++; + break; + + case '+': + flags_plus++; + fmt++; + break; + + case ' ': + flags_space++; + fmt++; + break; + + case '0': + flags_zero++; + fmt++; + break; + + case '#': + flags_pound++; + fmt++; + break; + + default: + more_flags = 0; + break; + } + } + + /* minimum field width */ + if(*fmt == '*'){ + min_field_width = va_arg(args, int); + fmt++; + } + else if(*fmt >= '0' && *fmt <= '9'){ + width_str = fmt; + while (*fmt >= '0' && *fmt <= '9') + fmt++; + + strncpy(buf, width_str, MIN(fmt-width_str,sizeof(buf))); + if(sizeof(buf) > fmt-width_str) + buf[fmt-width_str] = '\0'; + + buf[sizeof(buf)-1] = '\0'; + + min_field_width = atoi(width_str); + } + + /* field precision */ + if(*fmt == '.'){ + fmt++; + if(*fmt == '*'){ + field_precision = va_arg(args, int); + fmt++; + } + else if(*fmt >= '0' && *fmt <= '9'){ + width_str = fmt; + while (*fmt >= '0' && *fmt <= '9') + fmt++; + + strncpy(buf, width_str, MIN(fmt-width_str,sizeof(buf))); + if(sizeof(buf) > fmt-width_str) + buf[fmt-width_str] = '\0'; + + buf[sizeof(buf)-1] = '\0'; + + field_precision = atoi(width_str); + } + } + + /* length modifier */ + if(*fmt == 'h' || *fmt == 'l' || *fmt == 'L') + modifier = *fmt++; + + /* conversion character */ + switch(*fmt){ + case 'w': + /* + * work with va_arg(char *) to figure out width + * and precision needed to produce the screen width + * and precision asked for in %w using some of the + * utf8 width routines we have. + */ + + input_str = va_arg(args, char *); + if(field_precision >=0 || min_field_width >= 0) + w = utf8_width(input_str); + + if(field_precision >= 0){ + if(w <= field_precision) + field_precision = -1; /* print it all */ + else{ + /* + * We need to cut off some of the input_str + * in this case. + */ + end = utf8_count_forw_width(input_str, field_precision, &got_width); + field_precision = (int) (end - input_str); + /* new w with this field_precision */ + w = got_width; + } + } + + /* need some padding */ + if(min_field_width >= 0) + min_field_width = ((field_precision >= 0) ? field_precision : strlen(input_str)) + + MAX(0, min_field_width - w); + + /* + * Now we just need to get the new format string + * set correctly in newfmt. + */ + q = newfmt; + if(q-newfmt < sizeof(newfmt)) + *q++ = '%'; + + if(flags_minus && q-newfmt < sizeof(newfmt)) + *q++ = '-'; + if(flags_plus && q-newfmt < sizeof(newfmt)) + *q++ = '+'; + if(flags_space && q-newfmt < sizeof(newfmt)) + *q++ = ' '; + if(flags_zero && q-newfmt < sizeof(newfmt)) + *q++ = '0'; + if(flags_pound && q-newfmt < sizeof(newfmt)) + *q++ = '#'; + + if(min_field_width >= 0){ + snprintf(buf, sizeof(buf), "%d", min_field_width); + sstrncpy(&q, buf, sizeof(newfmt)-(q-newfmt)); + } + + if(field_precision >= 0){ + if(q-newfmt < sizeof(newfmt)) + *q++ = '.'; + + snprintf(buf, sizeof(buf), "%d", field_precision); + sstrncpy(&q, buf, sizeof(newfmt)-(q-newfmt)); + } + + if(q-newfmt < sizeof(newfmt)) + *q++ = 's'; + + if(q-newfmt < sizeof(newfmt)) + *q++ = '\0'; + + snprintf(pdest, size - (pdest-dest), newfmt, input_str); + pdest += strlen(pdest); + + break; + + case '\0': + fmt--; + break; + + default: + /* make a new format which leaves out the dynamic '*' arguments */ + q = newfmt; + if(q-newfmt < sizeof(newfmt)) + *q++ = '%'; + + if(flags_minus && q-newfmt < sizeof(newfmt)) + *q++ = '-'; + if(flags_plus && q-newfmt < sizeof(newfmt)) + *q++ = '+'; + if(flags_space && q-newfmt < sizeof(newfmt)) + *q++ = ' '; + if(flags_zero && q-newfmt < sizeof(newfmt)) + *q++ = '0'; + if(flags_pound && q-newfmt < sizeof(newfmt)) + *q++ = '#'; + + if(min_field_width >= 0){ + snprintf(buf, sizeof(buf), "%d", min_field_width); + sstrncpy(&q, buf, sizeof(newfmt)-(q-newfmt)); + } + + if(field_precision >= 0){ + if(q-newfmt < sizeof(newfmt)) + *q++ = '.'; + + snprintf(buf, sizeof(buf), "%d", field_precision); + sstrncpy(&q, buf, sizeof(newfmt)-(q-newfmt)); + } + + if(q-newfmt < sizeof(newfmt)) + *q++ = *fmt; + + if(q-newfmt < sizeof(newfmt)) + *q++ = '\0'; + + switch(*fmt){ + case 'd': case 'i': case 'o': + case 'x': case 'X': case 'u': case 'c': + int_arg = va_arg(args, int); + snprintf(pdest, size - (pdest-dest), newfmt, int_arg); + pdest += strlen(pdest); + break; + + case 's': + input_str = va_arg(args, char *); + snprintf(pdest, size - (pdest-dest), newfmt, input_str); + pdest += strlen(pdest); + break; + + case 'f': case 'e': case 'E': + case 'g': case 'G': + double_arg = va_arg(args, double); + snprintf(pdest, size - (pdest-dest), newfmt, double_arg); + pdest += strlen(pdest); + break; + + case 'p': + ptr_arg = va_arg(args, void *); + snprintf(pdest, size - (pdest-dest), newfmt, ptr_arg); + pdest += strlen(pdest); + break; + + case '%': + if(IS_ROOM_IN_DEST(1)) + *pdest++ = '%'; + + break; + + default: + /* didn't think of this type */ + assert(0); + break; + } + + break; + } + + fmt++; + } + else{ + if(IS_ROOM_IN_DEST(1)) + *pdest++ = *fmt++; + } + } + + ret = pdest - dest; + + if(IS_ROOM_IN_DEST(1)) + *pdest++ = '\0'; + + va_end(args); + + return ret; +} + + +/* + * Copy UTF-8 characters from src into dst. + * Copy enough characters so that the result will have (<=) screen width of + * want_width screen cells in current locale. + * + * Dstlen is the available space in dst. No more than dstlen bytes will be written + * to dst. + * + * Returned value is the number of bytes written to dst, not including + * the possible terminating null. + * Got_width is another returned value. It is the width in screen cells of + * the string placed in dst. It will be the same as want_width if there + * are enough characters in the src to do that and if the character widths + * hit the width exactly. It will be less than want_width if we run out + * of src characters or if the next character width would skip over the + * width we want, because it is double width. + * + * Zero width characters are collected and included at the end of the string. + * That is, if we make it to want_width but there is still a zero length + * character sitting in src, we add that to dst. This might be an accent + * or something like that. + */ +size_t +utf8_to_width(char *dst, /* destination buffer */ + char *src, /* source string */ + size_t dstlen, /* space in dst */ + unsigned want_width, /* desired screen width */ + unsigned *got_width) /* returned screen width in dst */ +{ + int this_width; + unsigned width_consumed = 0; + UCS ucs; + unsigned long remaining_octets; + char *writeptr, *readptr, *savereadptr, *endptr; + int ran_out_of_space = 0; + + readptr = src; + + remaining_octets = readptr ? strlen(readptr) : 0; + + writeptr = dst; + endptr = writeptr + dstlen; + + if(readptr && writeptr){ + while(width_consumed <= want_width && remaining_octets > 0 && writeptr < dst + dstlen && !ran_out_of_space){ + savereadptr = readptr; + ucs = (UCS) utf8_get((unsigned char **) &readptr, &remaining_octets); + + if(ucs & U8G_ERROR || ucs == UBOGON) + remaining_octets = 0; + else{ + this_width = wcellwidth(ucs); + + /* + * If this_width is -1 that means we can't print this character + * with our current locale. Writechar will print a '?'. + */ + if(this_width < 0) + this_width = 1; + + if(width_consumed + (unsigned) this_width <= want_width){ + /* append this utf8 character to dst if it will fit */ + if(writeptr + (readptr - savereadptr) < endptr){ + width_consumed += this_width; + while(savereadptr < readptr) + *writeptr++ = *savereadptr++; + } + else + ran_out_of_space++; /* no more utf8 to dst */ + } + else + remaining_octets = 0; /* we're done */ + } + } + + if(writeptr < endptr) + *writeptr = '\0'; + } + + if(got_width) + *got_width = width_consumed; + + return(writeptr ? (writeptr - dst) : 0); +} + + +/* + * Str is a UTF-8 string. + * Count forward width screencell positions and return a pointer to the + * end of the string that is width wide. + * The returned pointer points at the next character (where the null would + * be placed). + * + * Got_width is another returned value. It is the width in screen cells of + * the string from str to the returned pointer. It will be the same as + * want_width if there are enough characters in the str to do that + * and if the character widths hit the width exactly. It will be less + * than want_width if we run out of characters or if the next character + * width would skip over the width we want, because it is double width. + */ +char * +utf8_count_forw_width(char *str, unsigned want_width, unsigned *got_width) +{ + int this_width; + unsigned width_consumed = 0; + UCS ucs; + unsigned long remaining_octets; + char *readptr; + char *retptr; + + retptr = readptr = str; + + remaining_octets = readptr ? strlen(readptr) : 0; + + while(width_consumed <= want_width && remaining_octets > 0){ + + ucs = (UCS) utf8_get((unsigned char **) &readptr, &remaining_octets); + + if(ucs & U8G_ERROR || ucs == UBOGON){ + /* + * This should not happen, but do something to handle it anyway. + * Treat each character as a single width character, which is what should + * probably happen when we actually go to write it out. + */ + remaining_octets--; + readptr++; + this_width = 1; + } + else{ + this_width = wcellwidth(ucs); + + /* + * If this_width is -1 that means we can't print this character + * with our current locale. Writechar will print a '?'. + */ + if(this_width < 0) + this_width = 1; + } + + if(width_consumed + (unsigned) this_width <= want_width){ + width_consumed += (unsigned) this_width; + retptr = readptr; + } + else + remaining_octets = 0; /* we're done */ + } + + if(got_width) + *got_width = width_consumed; + + return(retptr); +} + + +/* + * Copy a null terminator into a UTF-8 string in place so that the string is + * no more than a certain screen width wide. If the string is already less + * than or equal in width to the requested width, no change is made. + * + * The actual width accomplished is returned. Note that it may be less than + * max_width due to double width characters as well as due to the fact that + * it fits wholly in the max_width. + * + * Returned value is the actual screen width of str when done. + * + * A side effect is that a terminating null may have been written into + * the passed in string. + */ +unsigned +utf8_truncate(char *str, unsigned max_width) +{ + int this_width; + unsigned width_consumed = 0; + UCS ucs; + unsigned long remaining_octets; + char *readptr, *savereadptr; + + readptr = str; + + remaining_octets = readptr ? strlen(readptr) : 0; + + if(readptr){ + while(width_consumed <= max_width && remaining_octets > 0){ + + savereadptr = readptr; + ucs = (UCS) utf8_get((unsigned char **) &readptr, &remaining_octets); + + if(ucs & U8G_ERROR || ucs == UBOGON){ + /* + * This should not happen, but do something to handle it anyway. + * Treat each character as a single width character, which is what should + * probably happen when we actually go to write it out. + */ + remaining_octets--; + readptr++; + this_width = 1; + } + else{ + this_width = wcellwidth(ucs); + + /* + * If this_width is -1 that means we can't print this character + * with our current locale. Writechar will print a '?'. + */ + if(this_width < 0) + this_width = 1; + } + + if(width_consumed + (unsigned) this_width <= max_width){ + width_consumed += (unsigned) this_width; + } + else{ + remaining_octets = 0; /* we're done */ + *savereadptr = '\0'; + } + } + } + + return(width_consumed); +} + + +/* + * Copy UTF-8 characters from src into dst. + * Copy enough characters so that the result will have screen width of + * want_width screen cells in current locale. + * If there aren't enough characters in src to get to want_width, pad on + * left or right according to left_adjust argument. + * + * Dstlen is the available space in dst. No more than dstlen bytes will be written + * to dst. Dst will be null terminated if there is enough room, but not + * if that would overflow dst's len. + * + * Returned value is the number of bytes written to dst, not including + * the possible terminating null. + */ +size_t +utf8_pad_to_width(char *dst, /* destination buffer */ + char *src, /* source string */ + size_t dstlen, /* space in dst */ + unsigned want_width, /* desired screen width */ + int left_adjust) /* adjust left or right in want_width columns */ +{ + unsigned got_width = 0; + int need_more, howmany; + size_t len_left, bytes_used; + + bytes_used = utf8_to_width(dst, src, dstlen, want_width, &got_width); + len_left = dstlen - bytes_used; + + need_more = want_width - got_width; + howmany = MIN(need_more, len_left); + + if(howmany > 0){ + char *end, *newend, *p, *q; + + end = dst + bytes_used; + newend = end + howmany; + if(left_adjust){ + /* + * Add padding to end of string. Simply append + * the needed number of spaces, or however many will fit + * if we don't have enough space. + */ + for(q = end; q < newend; q++) + *q = ' '; + } + else{ + /* + * Add padding to start of string. + */ + + /* slide existing string over */ + for(p = end - 1, q = newend - 1; p >= dst; p--, q--) + *q = *p; + + /* fill rest with spaces */ + for(; q >= dst; q--) + *q = ' '; + } + + bytes_used += howmany; + } + + if(bytes_used < dstlen) + dst[bytes_used] = '\0'; + + return(bytes_used); +} + + +/* + * Str is a UTF-8 string. + * Start_here is a pointer into the string. It points one position past + * the last byte that should be considered a part of the length string. + * Count back want_width screencell positions and return a pointer to the + * start of the string that is want_width wide and ends with start_here. + * + * Since characters may be more than one cell width wide we may end up + * skipping over the exact width. That is, if we need to we'll go back + * too far (by one cell width). Account for that in the call by looking + * at got_width. + * + * Note that this call gives a possible got_width == want_width+1 as + * opposed to utf8_count_forw_width which gives got_width == want-1 instead. + * That was just what was needed at the time, maybe it needs to be + * optional. + */ +char * +utf8_count_back_width(char *str, char *start_here, unsigned want_width, unsigned *got_width) +{ + unsigned width_consumed = 0; + int this_width; + UCS ucs; + unsigned long remaining_octets; + char *ptr, *savereadptr, *goodreadptr; + + savereadptr = start_here; + goodreadptr = start_here; + + for(ptr = savereadptr - 1; width_consumed < want_width && ptr >= str; ptr = savereadptr - 1){ + + savereadptr = ptr; + remaining_octets = goodreadptr - ptr; + ucs = (UCS) utf8_get((unsigned char **) &ptr, &remaining_octets); + + if(!(ucs & U8G_ERROR || ucs == UBOGON)){ + if(remaining_octets > 0){ + /* + * This means there are some bad octets after this good + * character so things are not going to work out well. + * Bail out. + */ + savereadptr = str; /* we're done */ + } + else{ + this_width = wcellwidth(ucs); + + /* + * If this_width is -1 that means we can't print this character + * with our current locale. Writechar will print a '?'. + */ + if(this_width < 0) + this_width = 1; + + width_consumed += (unsigned) this_width; + goodreadptr = savereadptr; + } + } + } + + if(got_width) + *got_width = width_consumed; + + return(savereadptr); +} + + +/*---------------------------------------------------------------------- + copy the source string onto the destination string returning with + the destination string pointer at the end of the destination text + + motivation for this is to avoid twice passing over a string that's + being appended to twice (i.e., strcpy(t, x); t += strlen(t)) + + This doesn't really belong here but it is used here. + ----*/ +void +sstrncpy(char **d, char *s, int n) +{ + while(n-- > 0 && (**d = *s++) != '\0') + (*d)++; +} + + +/* + * If use_system_routines is set then NULL is the return value and it is + * not an error. Display_charmap and keyboard_charmap should come over as + * malloced strings and will be filled in with the result. + * + * Returns a void pointer to the input_cs CHARSET which is + * passed to mbtow via kbseq(). + * If !use_system_routines && NULL is returned, that is an error and err should + * have a message. + * display_charmap and keyboard_charmap should be malloced data and may be + * realloced and changed here. + */ +int +setup_for_input_output(int use_system_routines, char **display_charmap, + char **keyboard_charmap, void **input_cs_arg, char **err) +{ + const CHARSET *cs; + const CHARSET *input_cs = NULL; + int already_tried = 0; + int supported = 0; + char buf[1000]; + +#define cpstr(s) strcpy((char *)fs_get(1+strlen(s)), s) + + if(err) + *err = NULL; + + if(!display_charmap || !keyboard_charmap || !input_cs_arg){ + *err = cpstr("Bad call to setup_for_input_output"); + return(-1); + } + + if(use_system_routines){ +#if PREREQ_FOR_SYS_TRANSLATION + char *dcm; + + dcm = nl_langinfo_codeset_wrapper(); + dcm = dcm ? dcm : "US-ASCII"; + + init_utf8_display(0, NULL); + if(*display_charmap){ + if(dcm && strucmp(*display_charmap, dcm)){ + snprintf(buf, sizeof(buf), + _("Display character set \"%s\" is ignored when using system translation"), + *display_charmap); + + *err = cpstr(buf); + } + + fs_give((void **) display_charmap); + } + + if(*keyboard_charmap){ + if(!*err && dcm && strucmp(*keyboard_charmap, dcm)){ + snprintf(buf, sizeof(buf), + _("Keyboard character set \"%s\" is ignored when using system translation"), + *keyboard_charmap); + + *err = cpstr(buf); + } + + fs_give((void **) keyboard_charmap); + } + + *display_charmap = cpstr(dcm); + *keyboard_charmap = cpstr(dcm); +#else + *err = cpstr("Bad call to setup_for_input_output"); +#endif + + *input_cs_arg = NULL; + return(0); + } + + +try_again1: + if(!(*display_charmap)) + *display_charmap = cpstr("US-ASCII"); + + if(!(*keyboard_charmap)) + *keyboard_charmap = cpstr(*display_charmap); + + if(*keyboard_charmap){ + supported = input_charset_is_supported(*keyboard_charmap); + + if(supported){ + if(!strucmp(*keyboard_charmap, "utf-8")) + input_cs = utf8_charset(*keyboard_charmap); + else if((cs = utf8_charset(*keyboard_charmap)) != NULL) + input_cs = cs; + } + else{ + if(err && !*err){ + int iso2022jp = 0; + + if(!strucmp(*keyboard_charmap, "ISO-2022-JP")) + iso2022jp = 1; + + snprintf(buf, sizeof(buf), + /* TRANSLATORS: The first argument is the name of the character + set the user is trying to use (which is unsupported by alpine). + The second argument is " (except for posting)" if they are + trying to use ISO-2022-JP for something other than posting. */ + _("Character set \"%s\" is unsupported%s, using US-ASCII"), + *keyboard_charmap, + iso2022jp ? _(" (except for posting)") : ""); + + *err = cpstr(buf); + } + + input_cs = NULL; + fs_give((void **) keyboard_charmap); + *keyboard_charmap = cpstr("US-ASCII"); + if(!already_tried){ + already_tried++; + goto try_again1; + } + } + } + + +try_again2: + if(!(*display_charmap)) + *display_charmap = cpstr("US-ASCII"); + + if(*display_charmap){ + supported = output_charset_is_supported(*display_charmap); + if(supported){ + if(!strucmp(*display_charmap, "utf-8")) + init_utf8_display(1, NULL); + else if((cs = utf8_charset(*display_charmap)) != NULL) + init_utf8_display(0, utf8_rmap_gen(cs, NULL)); + } + else{ + if(err && !*err){ + int iso2022jp = 0; + + if(!strucmp(*display_charmap, "ISO-2022-JP")) + iso2022jp = 1; + + snprintf(buf, sizeof(buf), + _("Character set \"%s\" is unsupported%s, using US-ASCII"), + *display_charmap, + iso2022jp ? _(" (except for posting)") : ""); + + *err = cpstr(buf); + } + + fs_give((void **) display_charmap); + if(!already_tried){ + already_tried++; + goto try_again2; + } + } + } + else{ + if(err && !*err) + *err = cpstr(_("Help, can't figure out display character set or even use US-ASCII.")); + } + +#undef cpstr + + *input_cs_arg = (void *) input_cs; + + return(0); +} + + +int +input_charset_is_supported(char *input_charset) +{ + const CHARSET *cs; + + if(!(input_charset && *input_charset)) + return 0; + + if(!strucmp(input_charset, "utf-8")) + return 1; + + if((cs = utf8_charset(input_charset)) != NULL){ + + /* + * This was true 2006-09-25. + */ + switch(cs->type){ + case CT_ASCII: case CT_1BYTE0: case CT_1BYTE: + case CT_1BYTE8: case CT_EUC: case CT_DBYTE: + case CT_DBYTE2: case CT_SJIS: case CT_UCS2: + case CT_UCS4: case CT_UTF16: + return 1; + break; + + default: + break; + } + } + + return 0; +} + + +int +output_charset_is_supported(char *output_charset) +{ + const CHARSET *cs; + + if(!(output_charset && *output_charset)) + return 0; + + if(!strucmp(output_charset, "utf-8")) + return 1; + + if((cs = utf8_charset(output_charset)) != NULL && utf8_rmap_gen(cs, NULL)) + return 1; + + return 0; +} + + +int +posting_charset_is_supported(char *posting_charset) +{ + return(posting_charset && *posting_charset + && (!strucmp(posting_charset, "ISO-2022-JP") + || output_charset_is_supported(posting_charset))); +} + + +/* + * This function is only defined in this special case and so calls + * to it should be wrapped in the same macro conditionals. + * + * Returns the default display charset for a UNIX terminal emulator, + * it is what nl_langinfo(CODESET) should return but we need to + * wrap nl_langinfo because we know of strange behaving implementations. + */ +#if !defined(_WINDOWS) && HAVE_LANGINFO_H && defined(CODESET) +char * +nl_langinfo_codeset_wrapper(void) +{ + char *ret = NULL; + + ret = nl_langinfo(CODESET); + + /* + * If the value returned from nl_langinfo() is not a real charset, + * see if we can figure out what they meant. If we can't figure it + * out return NULL and let the caller decide what to do. + */ + if(ret && *ret && !output_charset_is_supported(ret)){ + if(!strcmp("ANSI_X3.4-1968", ret) + || !strcmp("646", ret) + || !strcmp("ASCII", ret) + || !strcmp("C", ret) + || !strcmp("POSIX", ret)) + ret = "US-ASCII"; + else if(!strucmp(ret, "UTF8")) + ret = "UTF-8"; + else if(!strucmp(ret, "EUCJP")) + ret = "EUC-JP"; + else if(!strucmp(ret, "EUCKP")) + ret = "EUC-KP"; + else if(!strucmp(ret, "SJIS")) + ret = "SHIFT-JIS"; + else if(strstr(ret, "8859")){ + char *p; + + /* check for digits after 8859 */ + p = strstr(ret, "8859"); + p += 4; + if(!isdigit(*p)) + p++; + + if(isdigit(*p)){ + static char buf[12]; + + memset(buf, 0, sizeof(buf)); + strncpy(buf, "ISO-8859-", sizeof(buf)); + buf[9] = *p++; + if(isdigit(*p)) + buf[10] = *p; + + ret = buf; + } + } + } + + if(ret && !output_charset_is_supported(ret)) + ret = NULL; + + return(ret); +} +#endif + + +/* + * Convert the "orig" string from UTF-8 to "charset". If no conversion is + * needed the return value will point to orig. If a conversion is done, + * the return string should be freed by the caller. + * If not possible, returns NULL. + */ +char * +utf8_to_charset(char *orig, char *charset, int report_err) +{ + SIZEDTEXT src, dst; + char *ret = orig; + + if(!charset || !charset[0] || !orig || !orig[0] || !strucmp(charset, "utf-8")) + return ret; + + src.size = strlen(orig); + src.data = (unsigned char *) orig; + + if(!strucmp(charset, "us-ascii")){ + size_t i; + + for(i = 0; i < src.size; i++) + if(src.data[i] & 0x80) + return NULL; + + return ret; + } + + /* + * This works for ISO-2022-JP because of special code in utf8_cstext + * but not for other 2022 charsets. + */ + memset(&dst, 0, sizeof(dst)); + if(utf8_cstext(&src, charset, &dst, report_err ? 0 : '?') && dst.size > 0 && dst.data) + ret = (char *) dst.data; /* c-client already null terminates it */ + else + ret = NULL; + + if((unsigned char *) ret != dst.data && dst.data) + fs_give((void **) &dst.data); + + return ret; +} + + +/* + * Turn a number into a string with comma's + * + * Args: number -- The long to be turned into a string. + * + * Result: pointer to static string representing number with commas + * Can use up to 3 comatose results at once. + */ +char * +comatose(long int number) +{ + long i, x, done_one; + static char buf[3][50]; + static int whichbuf = 0; + char *b; + + whichbuf = (whichbuf + 1) % 3; + + if(number == 0){ + strncpy(buf[whichbuf], "0", sizeof(buf[0])); + buf[whichbuf][sizeof(buf[0])-1] = '\0'; + return(buf[whichbuf]); + } + + done_one = 0; + b = buf[whichbuf]; + for(i = 1000000000; i >= 1; i /= 1000) { + x = number / i; + number = number % i; + if(x != 0 || done_one) { + if(b != buf[whichbuf] && (b-buf[whichbuf]) < sizeof(buf[0])) + *b++ = ','; + + snprintf(b, sizeof(buf[0])-(b-buf[whichbuf]), done_one ? "%03ld" : "%ld", x); + b += strlen(b); + done_one = 1; + } + } + + if(b-buf[whichbuf] < sizeof(buf[0])) + *b = '\0'; + + return(buf[whichbuf]); +} + + +/* leave out the commas */ +char * +tose(long int number) +{ + static char buf[3][50]; + static int whichbuf = 0; + + whichbuf = (whichbuf + 1) % 3; + + snprintf(buf[whichbuf], sizeof(buf[0]), "%ld", number); + + return(buf[whichbuf]); +} + + +/* + * line_paint - where the real work of managing what is displayed gets done. + */ +void +line_paint(int offset, /* current dot offset into vl */ + struct display_line *displ, + int *passwd) /* flag to hide display of chars */ +{ + int i, w, w2, already_got_one = 0; + int vfirst, vlast, dfirst, dlast, vi, di; + int new_vbase; + unsigned (*width_a_to_b)(UCS *, int, int); + + /* + * Set passwd to 10 in caller if you want to conceal the + * password but not print asterisks for feedback. + * + * Set passwd to 1 in caller to conceal by printing asterisks. + */ + if(passwd && *passwd >= 10){ /* don't show asterisks */ + if(*passwd > 10) + return; + else + *passwd = 11; /* only blat once */ + + i = 0; + (*displ->movecursor)(displ->row, displ->col); + while(i++ <= displ->dwid) + (*displ->writechar)(' '); + + (*displ->movecursor)(displ->row, displ->col); + return; + } + + if(passwd && *passwd) + width_a_to_b = single_width_chars_a_to_b; + else + width_a_to_b = ucs4_str_width_a_to_b; + + /* + * vl is the virtual line (the actual data). We operate on it by typing + * characters to be added and deleting and so forth. In this routine we + * copy a subset of those UCS-4 characters in vl into dl, the display + * array, and show that subset on the screen. + * + * Offset is the location of the cursor in vl. + * + * We will display the string starting from vbase. + * We have dwid screen cells to work in. + * We may have to adjust vbase in order to display the + * part of the string that contains the cursor. + * + * We'll make the display look like + * vl a b c d e f g h i j k l m + * xxxxxxxxxxxxx <- width dwid window + * < d e f g h > + * | + * vbase + * The < will be there if vbase > 0. + * The > will be there if the string from vbase to the + * end can't all fit in the window. + */ + + memset(displ->dl, 0, displ->dlen * sizeof(UCS)); + + /* + * Adjust vbase so offset is not out of the window to the right. + * (The +2 in w + 2 is for a possible " >" if the string goes past + * the right hand edge of the window and if the last visible character + * is double wide. We don't want the offset to be under that > character.) + */ + for(w = (*width_a_to_b)(displ->vl, displ->vbase, offset); + w + 2 + (displ->vbase ? 1 : 0) > displ->dwid; + w = (*width_a_to_b)(displ->vl, displ->vbase, offset)){ + /* + * offset is off the window to the right + * It looks like a b c d e f g h + * | | + * vbase offset + * and offset is either past the right edge, + * or right at the right edge (and maybe under >), + * or one before right at the edge (and maybe on space + * for half a character). + * + * Since the characters may be double width it is slightly + * complicated to figure out how far to increase vbase. + * We're going to scoot over past width w/2 characters and + * then see if that's sufficient. + */ + new_vbase = displ->vbase + 1; + for(w2 = (*width_a_to_b)(displ->vl, displ->vbase+1, new_vbase); + w2 < displ->dwid/2; + w2 = (*width_a_to_b)(displ->vl, displ->vbase+1, new_vbase)) + new_vbase++; + + displ->vbase = new_vbase; + } + + /* adjust so offset is not out of the window to the left */ + while(displ->vbase > 0 && displ->vbase >= offset){ + /* add about dwid/2 more width */ + new_vbase = displ->vbase - 1; + for(w2 = (*width_a_to_b)(displ->vl, new_vbase, displ->vbase); + w2 < (displ->dwid+1)/2 && new_vbase > 0; + w2 = (*width_a_to_b)(displ->vl, new_vbase, displ->vbase)) + new_vbase--; + + /* but don't let it get too small, recheck off right end */ + for(w = (*width_a_to_b)(displ->vl, new_vbase, offset); + w + 2 + (new_vbase ? 1 : 0) > displ->dwid; + w = (*width_a_to_b)(displ->vl, displ->vbase, offset)) + new_vbase++; + + displ->vbase = MAX(new_vbase, 0); + } + + if(displ->vbase == 1 && ((passwd && *passwd) || wcellwidth(displ->vl[0]) == 1)) + displ->vbase = 0; + + vfirst = displ->vbase; + dfirst = 0; + if(displ->vbase > 0){ /* off screen cue left */ + dfirst = 1; /* index which matches vfirst */ + displ->dl[0] = '<'; + } + + vlast = displ->vused-1; /* end */ + w = (*width_a_to_b)(displ->vl, vfirst, vlast); + + if(w + dfirst > displ->dwid){ /* off window right */ + + /* find last ucs character to be printed */ + while(w + dfirst > displ->dwid - 1) /* -1 for > */ + w = (*width_a_to_b)(displ->vl, vfirst, --vlast); + + /* worry about double-width characters */ + if(w + dfirst == displ->dwid - 1){ /* no prob, hit it exactly */ + dlast = dfirst + vlast - vfirst + 1; /* +1 for > */ + displ->dl[dlast] = '>'; + } + else{ + dlast = dfirst + vlast - vfirst + 1; + displ->dl[dlast++] = ' '; + displ->dl[dlast] = '>'; + } + } + else + dlast = dfirst + vlast - vfirst; + + /* + * Copy the relevant part of the virtual line into the display line. + */ + for(vi = vfirst, di = dfirst; vi <= vlast; vi++, di++) + if(passwd && *passwd) + displ->dl[di] = '*'; /* to conceal password */ + else + displ->dl[di] = displ->vl[vi]; + + /* + * Add spaces to clear the rest of the line. + * We have dwid total space to fill. + */ + w = (*width_a_to_b)(displ->dl, 0, dlast); /* width through dlast */ + for(di = dlast+1, i = displ->dwid - w; i > 0 ; i--) + displ->dl[di++] = ' '; + + /* + * Draw from left to right, skipping until we get to + * something that is different. Characters may be different + * widths than they were initially so paint from there the + * rest of the way. + */ + for(di = 0; displ->dl[di]; di++){ + if(already_got_one || displ->dl[di] != displ->olddl[di]){ + /* move cursor first time */ + if(!already_got_one++){ + w = (di > 0) ? (*width_a_to_b)(displ->dl, 0, di-1) : 0; + (*displ->movecursor)(displ->row, displ->col + w); + } + + (*displ->writechar)(displ->dl[di]); + displ->olddl[di] = displ->dl[di]; + } + } + + memset(&displ->olddl[di], 0, (displ->dlen - di) * sizeof(UCS)); + + /* + * Move the cursor to the offset. + * + * The offset is relative to the start of the virtual array. We need + * to find the location on the screen. The offset into the display array + * will be offset-vbase+dfirst. We want to be at the start of that + * character, so we need to find the width of all the characters up + * to that point. + */ + w = (offset > 0) ? (*width_a_to_b)(displ->dl, 0, offset-displ->vbase+dfirst-1) : 0; + + (*displ->movecursor)(displ->row, displ->col + w); +} + + +/* + * This is just like ucs4_str_width_a_to_b() except all of the characters + * are assumed to be of width 1. This is for printing out *'s when user + * enters a password, while still managing to use the same code to do the + * display. + */ +unsigned +single_width_chars_a_to_b(UCS *ucsstr, int a, int b) +{ + unsigned width = 0; + int i; + + if(ucsstr) + for(i = a; i <= b && ucsstr[i]; i++) + width++; + + return width; +} |