From 24c40f899ebef2fa54423ca32c118da63f9113f0 Mon Sep 17 00:00:00 2001 From: Xavier G Date: Sat, 16 Apr 2016 19:58:11 +0200 Subject: [PATCH 1/8] get_alignment: handle UTF-8 strings. When using irssi along with some tools like e.g. BitlBee, one can encounter nicknames or channels with UTF-8 multibyte characters. It becomes therefore necessary for some irssi functions to better handle UTF-8, starting with get_alignment. Indeed, get_alignment was liable to mess up terminals by splitting strings in the middle of a multibyte character. --- src/core/special-vars.c | 61 +++++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/src/core/special-vars.c b/src/core/special-vars.c index 4dcc3d2f..638ab0bd 100644 --- a/src/core/special-vars.c +++ b/src/core/special-vars.c @@ -318,29 +318,60 @@ static int get_alignment_args(char **data, int *align, int *flags, char *pad) /* return the aligned text */ static char *get_alignment(const char *text, int align, int flags, char pad) { - GString *str; - char *ret; + gchar *ret; + gchar *ret_p; + uint i; + uint start_length; + uint cut_length; + uint final_length; + uint pad_count; g_return_val_if_fail(text != NULL, NULL); - str = g_string_new(text); + /* abort if non-valid UTF-8 */ + if (!g_utf8_validate(text, -1, NULL)) + return NULL; - /* cut */ - if ((flags & ALIGN_CUT) && align > 0 && str->len > align) - g_string_truncate(str, align); + /* how many characters do we have in the first place? */ + start_length = g_utf8_strlen(text, 1024); + /* how many characters will we have after the cut? */ + cut_length = start_length; + if ((flags & ALIGN_CUT) && align > 0 && start_length > align) + cut_length = align; + /* how many characters will we have after the pad? */ + final_length = cut_length; + pad_count = 0; + if ((flags & ALIGN_PAD) && align > cut_length) { + final_length = align; + pad_count = final_length - cut_length; + } - /* add pad characters */ - if (flags & ALIGN_PAD) { - while (str->len < align) { - if (flags & ALIGN_RIGHT) - g_string_prepend_c(str, pad); - else - g_string_append_c(str, pad); + /* allocate 4 bytes for each character we will have in the end */ + ret = g_malloc((4 * final_length) + 1); + ret_p = ret; + + /* left pad to align right, if necessary */ + if (pad_count && (flags & ALIGN_RIGHT)) { + for (i = 0; i < pad_count; i++) { + *ret_p = pad; + ret_p ++; } } - ret = str->str; - g_string_free(str, FALSE); + /* copy the original string (either n characters or the whole of it) */ + g_utf8_strncpy(ret_p, text, cut_length); + /* sadly, at this point, we have no idea where g_utf8_strncpy put the trailing \0... */ + ret_p += strlen(ret_p); + + /* right pad to align left, if necessary */ + if (pad_count && !(flags & ALIGN_RIGHT)) { + for (i = 0; i < pad_count; i++) { + *ret_p = pad; + ret_p ++; + } + *ret_p = '\0'; + } + return ret; } From 3e10659b64a5a6aa525e8b68a116a52c32a510c9 Mon Sep 17 00:00:00 2001 From: Xavier G Date: Mon, 18 Apr 2016 01:39:03 +0200 Subject: [PATCH 2/8] Fix indentation of display_sorted_nicks(). This was done assuming an "indent with tab, align with spaces" approach. --- src/fe-common/core/fe-channels.c | 64 ++++++++++++++++---------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/fe-common/core/fe-channels.c b/src/fe-common/core/fe-channels.c index 046d641a..fd44be11 100644 --- a/src/fe-common/core/fe-channels.c +++ b/src/fe-common/core/fe-channels.c @@ -328,35 +328,35 @@ static int get_nick_length(void *data) static void display_sorted_nicks(CHANNEL_REC *channel, GSList *nicklist) { - WINDOW_REC *window; + WINDOW_REC *window; TEXT_DEST_REC dest; GString *str; GSList *tmp; - char *format, *stripped, *prefix_format; + char *format, *stripped, *prefix_format; char *linebuf, nickmode[2] = { 0, 0 }; int *columns, cols, rows, last_col_rows, col, row, max_width; - int item_extra, linebuf_size, formatnum; + int item_extra, linebuf_size, formatnum; window = window_find_closest(channel->server, channel->visible_name, - MSGLEVEL_CLIENTCRAP); - max_width = window->width; + MSGLEVEL_CLIENTCRAP); + max_width = window->width; - /* get the length of item extra stuff ("[ ] ") */ + /* get the length of item extra stuff ("[ ] ") */ format = format_get_text(MODULE_NAME, NULL, - channel->server, channel->visible_name, - TXT_NAMES_NICK, " ", ""); + channel->server, channel->visible_name, + TXT_NAMES_NICK, " ", ""); stripped = strip_codes(format); item_extra = strlen(stripped); - g_free(stripped); + g_free(stripped); g_free(format); if (settings_get_int("names_max_width") > 0 && settings_get_int("names_max_width") < max_width) max_width = settings_get_int("names_max_width"); - /* remove width of the timestamp from max_width */ + /* remove width of the timestamp from max_width */ format_create_dest(&dest, channel->server, channel->visible_name, - MSGLEVEL_CLIENTCRAP, NULL); + MSGLEVEL_CLIENTCRAP, NULL); format = format_get_line_start(current_theme, &dest, time(NULL)); if (format != NULL) { stripped = strip_codes(format); @@ -365,11 +365,11 @@ static void display_sorted_nicks(CHANNEL_REC *channel, GSList *nicklist) g_free(format); } - /* remove width of the prefix from max_width */ + /* remove width of the prefix from max_width */ prefix_format = format_get_text(MODULE_NAME, NULL, - channel->server, channel->visible_name, - TXT_NAMES_PREFIX, - channel->visible_name); + channel->server, channel->visible_name, + TXT_NAMES_PREFIX, + channel->visible_name); if (prefix_format != NULL) { stripped = strip_codes(prefix_format); max_width -= strlen(stripped); @@ -384,19 +384,19 @@ static void display_sorted_nicks(CHANNEL_REC *channel, GSList *nicklist) /* calculate columns */ cols = get_max_column_count(nicklist, get_nick_length, max_width, - settings_get_int("names_max_columns"), - item_extra, 3, &columns, &rows); + settings_get_int("names_max_columns"), + item_extra, 3, &columns, &rows); nicklist = columns_sort_list(nicklist, rows); - /* rows in last column */ + /* rows in last column */ last_col_rows = rows-(cols*rows-g_slist_length(nicklist)); if (last_col_rows == 0) - last_col_rows = rows; + last_col_rows = rows; str = g_string_new(prefix_format); linebuf_size = max_width+1; linebuf = g_malloc(linebuf_size); - col = 0; row = 0; + col = 0; row = 0; for (tmp = nicklist; tmp != NULL; tmp = tmp->next) { NICK_REC *rec = tmp->data; @@ -407,39 +407,39 @@ static void display_sorted_nicks(CHANNEL_REC *channel, GSList *nicklist) if (linebuf_size < columns[col]-item_extra+1) { linebuf_size = (columns[col]-item_extra+1)*2; - linebuf = g_realloc(linebuf, linebuf_size); + linebuf = g_realloc(linebuf, linebuf_size); } memset(linebuf, ' ', columns[col]-item_extra); linebuf[columns[col]-item_extra] = '\0'; memcpy(linebuf, rec->nick, strlen(rec->nick)); - formatnum = rec->op ? TXT_NAMES_NICK_OP : - rec->halfop ? TXT_NAMES_NICK_HALFOP : - rec->voice ? TXT_NAMES_NICK_VOICE : - TXT_NAMES_NICK; + formatnum = rec->op ? TXT_NAMES_NICK_OP : + rec->halfop ? TXT_NAMES_NICK_HALFOP : + rec->voice ? TXT_NAMES_NICK_VOICE : + TXT_NAMES_NICK; format = format_get_text(MODULE_NAME, NULL, - channel->server, - channel->visible_name, - formatnum, nickmode, linebuf); + channel->server, + channel->visible_name, + formatnum, nickmode, linebuf); g_string_append(str, format); g_free(format); if (++col == cols) { printtext(channel->server, channel->visible_name, - MSGLEVEL_CLIENTCRAP, "%s", str->str); + MSGLEVEL_CLIENTCRAP, "%s", str->str); g_string_truncate(str, 0); if (prefix_format != NULL) - g_string_assign(str, prefix_format); + g_string_assign(str, prefix_format); col = 0; row++; if (row == last_col_rows) - cols--; + cols--; } } if (str->len > strlen(prefix_format)) { printtext(channel->server, channel->visible_name, - MSGLEVEL_CLIENTCRAP, "%s", str->str); + MSGLEVEL_CLIENTCRAP, "%s", str->str); } g_slist_free(nicklist); From cf643fa814d91e5abdcff1c48b4d9d8873547631 Mon Sep 17 00:00:00 2001 From: Xavier G Date: Mon, 18 Apr 2016 01:39:23 +0200 Subject: [PATCH 3/8] Make get_alignment available outside special-vars.c Since get_alignment handles UTF-8, it can be useful in other parts of irssi, e.g. display_sorted_nicks(). --- src/core/special-vars.c | 6 +----- src/core/special-vars.h | 8 ++++++++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/core/special-vars.c b/src/core/special-vars.c index 638ab0bd..32a855c8 100644 --- a/src/core/special-vars.c +++ b/src/core/special-vars.c @@ -26,10 +26,6 @@ #include "servers.h" #include "misc.h" -#define ALIGN_RIGHT 0x01 -#define ALIGN_CUT 0x02 -#define ALIGN_PAD 0x04 - #define isvarchar(c) \ (i_isalnum(c) || (c) == '_') @@ -316,7 +312,7 @@ static int get_alignment_args(char **data, int *align, int *flags, char *pad) } /* return the aligned text */ -static char *get_alignment(const char *text, int align, int flags, char pad) +char *get_alignment(const char *text, int align, int flags, char pad) { gchar *ret; gchar *ret_p; diff --git a/src/core/special-vars.h b/src/core/special-vars.h index 11262dad..621357e2 100644 --- a/src/core/special-vars.h +++ b/src/core/special-vars.h @@ -9,9 +9,17 @@ #define PARSE_FLAG_ESCAPE_THEME 0x08 /* if any arguments/variables contain { or } chars, escape them with % */ #define PARSE_FLAG_ONLY_ARGS 0x10 /* expand only arguments ($0 $1 etc.) but no other $variables */ +#define ALIGN_RIGHT 0x01 +#define ALIGN_CUT 0x02 +#define ALIGN_PAD 0x04 + typedef char* (*SPECIAL_HISTORY_FUNC) (const char *text, void *item, int *free_ret); + +/* Cut and/or pad text so it takes exactly "align" characters on the screen */ +char *get_alignment(const char *text, int align, int flags, char pad); + /* Parse and expand text after '$' character. return value has to be g_free()'d if `free_ret' is TRUE. */ char *parse_special(char **cmd, SERVER_REC *server, void *item, From d14690767c2dc8a8a42e292d350fbe0fb36f3c21 Mon Sep 17 00:00:00 2001 From: Xavier G Date: Mon, 18 Apr 2016 01:39:27 +0200 Subject: [PATCH 4/8] Improve UTF-8 handling in display_sorted_nicks(). This commit aims at improving the way irssi generates the output of /names with UTF-8 nicks. However, it is only a partial fix: instead of counting bytes, irssi now counts characters; but it should ideally count columns, since Unicode characters are liable to spread accross several columns. Alas, as this message is being written, the whole ecosystem is far from being able to deal with that: some terminals handle multi-columns characters, some others don't; various ncurses programs (vim, tmux, etc.) suffer from bugs on terminals who do handle them. --- src/fe-common/core/fe-channels.c | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/fe-common/core/fe-channels.c b/src/fe-common/core/fe-channels.c index fd44be11..a1bc5d75 100644 --- a/src/fe-common/core/fe-channels.c +++ b/src/fe-common/core/fe-channels.c @@ -26,6 +26,7 @@ #include "levels.h" #include "misc.h" #include "settings.h" +#include "special-vars.h" #include "chat-protocols.h" #include "chatnets.h" @@ -323,7 +324,7 @@ static void cmd_channel_remove(const char *data) static int get_nick_length(void *data) { - return strlen(((NICK_REC *) data)->nick); + return g_utf8_strlen(((NICK_REC *) data)->nick, 1024); } static void display_sorted_nicks(CHANNEL_REC *channel, GSList *nicklist) @@ -333,9 +334,9 @@ static void display_sorted_nicks(CHANNEL_REC *channel, GSList *nicklist) GString *str; GSList *tmp; char *format, *stripped, *prefix_format; - char *linebuf, nickmode[2] = { 0, 0 }; + char *aligned_nick, nickmode[2] = { 0, 0 }; int *columns, cols, rows, last_col_rows, col, row, max_width; - int item_extra, linebuf_size, formatnum; + int item_extra, formatnum; window = window_find_closest(channel->server, channel->visible_name, MSGLEVEL_CLIENTCRAP); @@ -394,7 +395,6 @@ static void display_sorted_nicks(CHANNEL_REC *channel, GSList *nicklist) last_col_rows = rows; str = g_string_new(prefix_format); - linebuf_size = max_width+1; linebuf = g_malloc(linebuf_size); col = 0; row = 0; for (tmp = nicklist; tmp != NULL; tmp = tmp->next) { @@ -405,13 +405,9 @@ static void display_sorted_nicks(CHANNEL_REC *channel, GSList *nicklist) else nickmode[0] = ' '; - if (linebuf_size < columns[col]-item_extra+1) { - linebuf_size = (columns[col]-item_extra+1)*2; - linebuf = g_realloc(linebuf, linebuf_size); - } - memset(linebuf, ' ', columns[col]-item_extra); - linebuf[columns[col]-item_extra] = '\0'; - memcpy(linebuf, rec->nick, strlen(rec->nick)); + aligned_nick = get_alignment(rec->nick, + columns[col]-item_extra, + ALIGN_PAD, ' '); formatnum = rec->op ? TXT_NAMES_NICK_OP : rec->halfop ? TXT_NAMES_NICK_HALFOP : @@ -420,8 +416,9 @@ static void display_sorted_nicks(CHANNEL_REC *channel, GSList *nicklist) format = format_get_text(MODULE_NAME, NULL, channel->server, channel->visible_name, - formatnum, nickmode, linebuf); + formatnum, nickmode, aligned_nick); g_string_append(str, format); + g_free(aligned_nick); g_free(format); if (++col == cols) { @@ -446,7 +443,6 @@ static void display_sorted_nicks(CHANNEL_REC *channel, GSList *nicklist) g_string_free(str, TRUE); g_free_not_null(columns); g_free_not_null(prefix_format); - g_free(linebuf); } void fe_channels_nicklist(CHANNEL_REC *channel, int flags) From d5e0f8097c1e404a6afe82220c7e6e4f474c5d36 Mon Sep 17 00:00:00 2001 From: Xavier G Date: Fri, 22 Apr 2016 20:05:08 +0200 Subject: [PATCH 5/8] Add functions get_utf8_{string,char}_width(). --- src/fe-common/core/utf8.c | 51 +++++++++++++++++++++++++++++++++++++++ src/fe-common/core/utf8.h | 4 +++ 2 files changed, 55 insertions(+) diff --git a/src/fe-common/core/utf8.c b/src/fe-common/core/utf8.c index 2d07ea8e..49efae63 100644 --- a/src/fe-common/core/utf8.c +++ b/src/fe-common/core/utf8.c @@ -22,5 +22,56 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include #include "module.h" +/* + * Return the width (number of columns when displayed) of the character pointed + * by c. + */ +int get_utf8_char_width(const gchar *c) { + gunichar uc; + int char_width; + uc = g_utf8_get_char(c); + char_width = g_unichar_isprint(uc) ? (1 + g_unichar_iswide(uc)) : 0; + return char_width; +} + +/* + * Return the number of columns taken by the s string, assuming its end is + * marked with a '\0' character and assuming it is a UTF-8 (multibyte) string. + * If s is NULL, this function returns -1. + * By default, this function takes care to validate s by calling + * g_utf8_validate(s, -1, NULL). If this check is deemed unnecessary, passing a + * non-zero value as skip_validation will skip that step. If the validation + * fails, this function returns -1. Otherwise, it will strive to provide a + * value as close as possible to what is expected. + */ +int get_utf8_string_width(const gchar *s, int skip_validation) { + const gchar *c; + int str_width; + + /* Ensure s is non-NULL: */ + if (!s) { + return -1; + } + + /* Validate the string, unless required otherwise: */ + if (!skip_validation) { + if (!g_utf8_validate(s, -1, NULL)) { + /* Another possibility here would be to return strlen(s). */ + return -1; + } + } + + /* Iterate over characters to determine the width: */ + str_width = 0; + for (c = s; *c; c = g_utf8_next_char(c)) { + str_width += get_utf8_char_width(c); + } + /* Note: there probably are some Unicode subtleties (Fitzpatrick + * modifiers?) that make the above implementation somewhat naive, but we + * have to start somewhere. + */ + return str_width; +} diff --git a/src/fe-common/core/utf8.h b/src/fe-common/core/utf8.h index 3c15dc7d..31ee5117 100644 --- a/src/fe-common/core/utf8.h +++ b/src/fe-common/core/utf8.h @@ -11,6 +11,10 @@ /* Returns width for character (0-2). */ int mk_wcwidth(unichar c); +/* Return the number of columns occupied by a given string. */ +int get_utf8_char_width(const gchar *); +int get_utf8_string_width(const gchar *); + #define unichar_isprint(c) (((c) & ~0x80) >= 32) #define is_utf8_leading(c) (((c) & 0xc0) != 0x80) From 2b92e43d3734f92873c9867eae0d397bc9b23fcd Mon Sep 17 00:00:00 2001 From: Xavier G Date: Fri, 22 Apr 2016 23:09:56 +0200 Subject: [PATCH 6/8] Add function get_utf8_chars_for_width(). --- src/fe-common/core/utf8.c | 47 +++++++++++++++++++++++++++++++++++++++ src/fe-common/core/utf8.h | 1 + 2 files changed, 48 insertions(+) diff --git a/src/fe-common/core/utf8.c b/src/fe-common/core/utf8.c index 49efae63..54a4d15b 100644 --- a/src/fe-common/core/utf8.c +++ b/src/fe-common/core/utf8.c @@ -75,3 +75,50 @@ int get_utf8_string_width(const gchar *s, int skip_validation) { */ return str_width; } + +/* Return the amount of characters from s it takes to reach n columns, or -1 if + * s is NULL. + */ +int get_utf8_chars_for_width(const gchar *s, unsigned int n, int skip_validation, unsigned int *delta) { + const gchar *c; + int str_width, char_width, char_count; + + /* Ensure s is non-NULL: */ + if (!s) { + return -1; + } + + /* Handle the dummy case where n is 0: */ + if (!n) { + return 0; + } + + /* Validate the string, unless required otherwise: */ + if (!skip_validation) { + if (!g_utf8_validate(s, -1, NULL)) { + /* Another possibility here would be to return strlen(s). */ + return -1; + } + } + + /* Iterate over characters until we reach n: */ + char_count = 0; + str_width = 0; + for (c = s; *c; c = g_utf8_next_char(c)) { + char_width = get_utf8_char_width(c); + if (str_width + char_width > n) { + /* We are about to exceed n, stop here. */ + break; + } + ++ char_count; + str_width += char_width; + } + /* At this point, we know that char_count characters reach str_width + * columns, which is less than or equal to n. */ + + /* Optionally provide the delta between str_width and n */ + if (delta) { + *delta = n - str_width; + } + return char_count; +} diff --git a/src/fe-common/core/utf8.h b/src/fe-common/core/utf8.h index 31ee5117..972a8848 100644 --- a/src/fe-common/core/utf8.h +++ b/src/fe-common/core/utf8.h @@ -14,6 +14,7 @@ int mk_wcwidth(unichar c); /* Return the number of columns occupied by a given string. */ int get_utf8_char_width(const gchar *); int get_utf8_string_width(const gchar *); +int get_utf8_chars_for_width(const gchar *, unsigned int, int, unsigned int *); #define unichar_isprint(c) (((c) & ~0x80) >= 32) #define is_utf8_leading(c) (((c) & 0xc0) != 0x80) From e720a48ee9ca1e53bf7333b801bf7b6cbc6148b7 Mon Sep 17 00:00:00 2001 From: Xavier G Date: Sat, 23 Apr 2016 00:51:55 +0200 Subject: [PATCH 7/8] Move wcwidth.c and utf8.{h,c} to core. This move makes sense since these files contain rather fundamental functions (fundamental here means that we shouldn't have had to implement them) which are required by other functions located in core/special-vars.c. --- src/core/Makefile.am | 2 ++ src/{fe-common => }/core/utf8.c | 0 src/{fe-common => }/core/utf8.h | 4 +++- src/{fe-common => }/core/wcwidth.c | 1 + src/fe-common/core/Makefile.am | 5 ----- src/fe-common/core/module.h | 2 +- src/fe-text/term.h | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) rename src/{fe-common => }/core/utf8.c (100%) rename src/{fe-common => }/core/utf8.h (91%) rename src/{fe-common => }/core/wcwidth.c (99%) diff --git a/src/core/Makefile.am b/src/core/Makefile.am index fc32e17e..2918c6ef 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -44,6 +44,8 @@ libcore_a_SOURCES = \ settings.c \ signals.c \ special-vars.c \ + utf8.c \ + wcwidth.c \ write-buffer.c structure_headers = \ diff --git a/src/fe-common/core/utf8.c b/src/core/utf8.c similarity index 100% rename from src/fe-common/core/utf8.c rename to src/core/utf8.c diff --git a/src/fe-common/core/utf8.h b/src/core/utf8.h similarity index 91% rename from src/fe-common/core/utf8.h rename to src/core/utf8.h index 972a8848..6462f579 100644 --- a/src/fe-common/core/utf8.h +++ b/src/core/utf8.h @@ -8,12 +8,14 @@ #define is_big5_hi(hi) (0x81 <= (hi) && (hi) <= 0xFE) #define is_big5(hi,lo) (is_big5_hi(hi) && is_big5_lo(lo)) +typedef guint32 unichar; + /* Returns width for character (0-2). */ int mk_wcwidth(unichar c); /* Return the number of columns occupied by a given string. */ int get_utf8_char_width(const gchar *); -int get_utf8_string_width(const gchar *); +int get_utf8_string_width(const gchar *, int); int get_utf8_chars_for_width(const gchar *, unsigned int, int, unsigned int *); #define unichar_isprint(c) (((c) & ~0x80) >= 32) diff --git a/src/fe-common/core/wcwidth.c b/src/core/wcwidth.c similarity index 99% rename from src/fe-common/core/wcwidth.c rename to src/core/wcwidth.c index 80d20fa1..4988ba93 100644 --- a/src/fe-common/core/wcwidth.c +++ b/src/core/wcwidth.c @@ -60,6 +60,7 @@ */ #include "module.h" +#include "utf8.h" struct interval { int first; diff --git a/src/fe-common/core/Makefile.am b/src/fe-common/core/Makefile.am index e755b510..63f91fa6 100644 --- a/src/fe-common/core/Makefile.am +++ b/src/fe-common/core/Makefile.am @@ -24,8 +24,6 @@ libfe_common_core_a_SOURCES = \ fe-queries.c \ fe-server.c \ fe-settings.c \ - utf8.c \ - wcwidth.c \ formats.c \ hilight-text.c \ keyboard.c \ @@ -62,6 +60,3 @@ pkginc_fe_common_core_HEADERS = \ window-items.h \ windows-layout.h \ fe-windows.h - -noinst_HEADERS = \ - utf8.h diff --git a/src/fe-common/core/module.h b/src/fe-common/core/module.h index 51b61b3e..db712ec7 100644 --- a/src/fe-common/core/module.h +++ b/src/fe-common/core/module.h @@ -2,7 +2,7 @@ #define MODULE_NAME "fe-common/core" -typedef guint32 unichar; +#include "utf8.h" typedef struct { time_t time; char *nick; diff --git a/src/fe-text/term.h b/src/fe-text/term.h index 9b726d82..0c7847f6 100644 --- a/src/fe-text/term.h +++ b/src/fe-text/term.h @@ -27,7 +27,7 @@ typedef struct _TERM_WINDOW TERM_WINDOW; #define TERM_TYPE_UTF8 1 #define TERM_TYPE_BIG5 2 -typedef guint32 unichar; +#include "utf8.h" extern TERM_WINDOW *root_window; extern int term_width, term_height; From f5033031b18a5c047fccae44bda03c0a83e68db5 Mon Sep 17 00:00:00 2001 From: Xavier G Date: Sat, 23 Apr 2016 02:18:40 +0200 Subject: [PATCH 8/8] Improve get_alignment so it works with columns (width), not chars (length). --- src/core/special-vars.c | 42 +++++++++++++++++++++++--------- src/fe-common/core/fe-channels.c | 3 ++- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/core/special-vars.c b/src/core/special-vars.c index 32a855c8..00d0f9fc 100644 --- a/src/core/special-vars.c +++ b/src/core/special-vars.c @@ -25,6 +25,7 @@ #include "settings.h" #include "servers.h" #include "misc.h" +#include "utf8.h" #define isvarchar(c) \ (i_isalnum(c) || (c) == '_') @@ -317,10 +318,14 @@ char *get_alignment(const char *text, int align, int flags, char pad) gchar *ret; gchar *ret_p; uint i; - uint start_length; - uint cut_length; - uint final_length; + /* Terminology: width is an amount of columns, length is an amount of + * characters. + */ + uint start_width, start_length; + uint cut_width, cut_length; + uint final_width, final_length; uint pad_count; + uint delta; g_return_val_if_fail(text != NULL, NULL); @@ -328,18 +333,31 @@ char *get_alignment(const char *text, int align, int flags, char pad) if (!g_utf8_validate(text, -1, NULL)) return NULL; - /* how many characters do we have in the first place? */ + /* how many columns and chars do we have in the first place? */ + start_width = get_utf8_string_width(text, 1 /* skip UTF-8 validation */); start_length = g_utf8_strlen(text, 1024); - /* how many characters will we have after the cut? */ + /* how many columns and chars will we have after the cut? */ + cut_width = start_width; cut_length = start_length; - if ((flags & ALIGN_CUT) && align > 0 && start_length > align) - cut_length = align; - /* how many characters will we have after the pad? */ + delta = 0; + if ((flags & ALIGN_CUT) && align > 0 && start_width > align) { + cut_width = align; + cut_length = get_utf8_chars_for_width(text, + align, + 1 /* skip UTF-8 validation */, + &delta); + /* At this point, we know that we have to take "cut_length" chars from + * "text" and append "delta" padding chars to reach "align" columns. + */ + } + /* how many columns will we have after the pad? */ + final_width = cut_width; final_length = cut_length; - pad_count = 0; - if ((flags & ALIGN_PAD) && align > cut_length) { - final_length = align; - pad_count = final_length - cut_length; + pad_count = delta; + if ((flags & ALIGN_PAD) && align > cut_width) { + final_width = align; + pad_count += (final_width - cut_width); + final_length += pad_count; } /* allocate 4 bytes for each character we will have in the end */ diff --git a/src/fe-common/core/fe-channels.c b/src/fe-common/core/fe-channels.c index a1bc5d75..c406ac73 100644 --- a/src/fe-common/core/fe-channels.c +++ b/src/fe-common/core/fe-channels.c @@ -27,6 +27,7 @@ #include "misc.h" #include "settings.h" #include "special-vars.h" +#include "utf8.h" #include "chat-protocols.h" #include "chatnets.h" @@ -324,7 +325,7 @@ static void cmd_channel_remove(const char *data) static int get_nick_length(void *data) { - return g_utf8_strlen(((NICK_REC *) data)->nick, 1024); + return get_utf8_string_width(((NICK_REC *) data)->nick, 1024); } static void display_sorted_nicks(CHANNEL_REC *channel, GSList *nicklist)