X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/22b9fa74de8e80471a5033ea067d3b360930b91d..f32583326d21b9a17862bdd375fb307175fd6b27:/lib/charset.c diff --git a/lib/charset.c b/lib/charset.c index 2a38fbf..3f0e8bc 100644 --- a/lib/charset.c +++ b/lib/charset.c @@ -33,6 +33,7 @@ #include "configuration.h" #include "utf8.h" #include "vector.h" +#include "unidata.h" /** @brief Low-level converstion routine * @param from Source encoding @@ -91,6 +92,33 @@ uint32_t *utf82ucs4(const char *mb) { return d.vec; } +/** @brief Convert one UCS-4 character to UTF-8 + * @param c Character to convert + * @param d Dynamic string to append UTF-8 sequence to + * @return 0 on success, -1 on error + */ +int one_ucs42utf8(uint32_t c, struct dynstr *d) { + 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 %#"PRIx32, c); + return -1; + } + return 0; +} + /** @brief Convert UCS-4 to UTF-8 * @param u Pointer to 0-terminated UCS-4 string * @return Pointer to 0-terminated UTF-8 string @@ -103,24 +131,8 @@ char *ucs42utf8(const uint32_t *u) { 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"); + if(one_ucs42utf8(c, &d)) return 0; - } } dynstr_terminate(&d); return d.vec; @@ -166,6 +178,66 @@ int ucs4cmp(const uint32_t *a, const uint32_t *b) { 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 / 256][c % 256]; + + return ud->gc == unicode_gc_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; +} + /* Local Variables: c-basic-offset:2