X-Git-Url: https://git.distorted.org.uk/~mdw/disorder/blobdiff_plain/763d5e6ad88ef3ba1cd1d7742d060e4f1e54c6b8..5617aaff51ba333441230e3808bc697e66540492:/lib/charset.c diff --git a/lib/charset.c b/lib/charset.c index f101ec2..c1b07bf 100644 --- a/lib/charset.c +++ b/lib/charset.c @@ -17,6 +17,7 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA */ +/** @file lib/charset.c @brief Character set conversion */ #include #include "types.h" @@ -32,7 +33,15 @@ #include "configuration.h" #include "utf8.h" #include "vector.h" - +#include "unidata.h" + +/** @brief Low-level converstion routine + * @param from Source encoding + * @param to Destination encoding + * @param ptr First byte to convert + * @param n Number of bytes to convert + * @return Converted text, 0-terminated; or NULL on error. + */ static void *convert(const char *from, const char *to, const void *ptr, size_t n) { iconv_t i; @@ -61,68 +70,28 @@ static void *convert(const char *from, const char *to, return buf; } -/* not everybody's iconv supports UCS-4, and it's inconvenient to have to know - * our endianness, and it's easy to convert it ourselves, so we do */ -uint32_t *utf82ucs4(const char *mb) { - struct dynstr_ucs4 d; - uint32_t c; - - dynstr_ucs4_init(&d); - while(*mb) { - PARSE_UTF8(mb, c, - error(0, "invalid UTF-8 sequence"); return 0;); - dynstr_ucs4_append(&d, c); - } - dynstr_ucs4_terminate(&d); - return d.vec; -} - -char *ucs42utf8(const uint32_t *u) { - struct dynstr d; - uint32_t c; - - dynstr_init(&d); - while((c = *u++)) { - if(c < 0x80) - dynstr_append(&d, c); - else if(c < 0x800) { - dynstr_append(&d, 0xC0 | (c >> 6)); - dynstr_append(&d, 0x80 | (c & 0x3F)); - } else if(c < 0x10000) { - dynstr_append(&d, 0xE0 | (c >> 12)); - dynstr_append(&d, 0x80 | ((c >> 6) & 0x3F)); - dynstr_append(&d, 0x80 | (c & 0x3F)); - } else if(c < 0x110000) { - dynstr_append(&d, 0xF0 | (c >> 18)); - dynstr_append(&d, 0x80 | ((c >> 12) & 0x3F)); - dynstr_append(&d, 0x80 | ((c >> 6) & 0x3F)); - dynstr_append(&d, 0x80 | (c & 0x3F)); - } else { - error(0, "invalid UCS-4 character"); - return 0; - } - } - dynstr_terminate(&d); - return d.vec; -} - +/** @brief Convert from the local multibyte encoding to UTF-8 */ char *mb2utf8(const char *mb) { return convert(nl_langinfo(CODESET), "UTF-8", mb, strlen(mb) + 1); } +/** @brief Convert from UTF-8 to the local multibyte encoding */ char *utf82mb(const char *utf8) { return convert("UTF-8", nl_langinfo(CODESET), utf8, strlen(utf8) + 1); } +/** @brief Convert from encoding @p from to UTF-8 */ char *any2utf8(const char *from, const char *any) { return convert(from, "UTF-8", any, strlen(any) + 1); } +/** @brief Convert from encoding @p from to the local multibyte encoding */ char *any2mb(const char *from, const char *any) { if(from) return convert(from, nl_langinfo(CODESET), any, strlen(any) + 1); else return xstrdup(any); } +/** @brief Convert from encoding @p from to encoding @p to */ char *any2any(const char *from, const char *to, const char *any) { @@ -130,11 +99,64 @@ char *any2any(const char *from, else return xstrdup(any); } -int ucs4cmp(const uint32_t *a, const uint32_t *b) { - while(*a && *b && *a == *b) ++a, ++b; - if(*a > *b) return 1; - else if(*a < *b) return -1; - else return 0; +/** @brief Return nonzero if @p c is a combining character */ +static int combining(int c) { + if(c < UNICODE_NCHARS) { + const struct unidata *const ud = &unidata[c / UNICODE_MODULUS][c % UNICODE_MODULUS]; + + return ud->general_category == unicode_General_Category_Mn || ud->ccc != 0; + } + /* Assume unknown characters are noncombining */ + return 0; +} + +/** @brief Truncate a string for display purposes + * @param s Pointer to UTF-8 string + * @param max Maximum number of columns + * @return @p or truncated string (never NULL) + * + * We don't correctly support bidi or double-width characters yet, nor + * locate default grapheme cluster boundaries for saner truncation. + */ +const char *truncate_for_display(const char *s, long max) { + const char *t = s, *r, *cut = 0; + char *truncated; + uint32_t c; + long n = 0; + + /* We need to discover two things: firstly whether the string is + * longer than @p max glyphs and secondly if it is not, where to cut + * the string. + * + * Combining characters follow their base character (unicode + * standard 5.0 s2.11), so after each base character we must + */ + while(*t) { + PARSE_UTF8(t, c, return s); + if(combining(c)) + /* This must be an initial combining character. We just skip it. */ + continue; + /* So c must be a base character. It may be followed by any + * number of combining characters. We advance past them. */ + do { + r = t; + PARSE_UTF8(t, c, return s); + } while(combining(c)); + /* Last character wasn't a combining character so back up */ + t = r; + ++n; + /* So now there are N glyphs before position T. We might + * therefore have reached the cut position. */ + if(n == max - 3) + cut = t; + } + /* If the string is short enough we return it unmodified */ + if(n < max) + return s; + truncated = xmalloc_noptr(cut - s + 4); + memcpy(truncated, s, cut - s); + strcpy(truncated + (cut - s), "..."); + return truncated; } /*