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/core/special-vars.c b/src/core/special-vars.c index 4dcc3d2f..00d0f9fc 100644 --- a/src/core/special-vars.c +++ b/src/core/special-vars.c @@ -25,10 +25,7 @@ #include "settings.h" #include "servers.h" #include "misc.h" - -#define ALIGN_RIGHT 0x01 -#define ALIGN_CUT 0x02 -#define ALIGN_PAD 0x04 +#include "utf8.h" #define isvarchar(c) \ (i_isalnum(c) || (c) == '_') @@ -316,31 +313,79 @@ 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) { - GString *str; - char *ret; + gchar *ret; + gchar *ret_p; + uint i; + /* 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); - 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 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 columns and chars will we have after the cut? */ + cut_width = start_width; + cut_length = start_length; + 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 = delta; + if ((flags & ALIGN_PAD) && align > cut_width) { + final_width = align; + pad_count += (final_width - cut_width); + final_length += pad_count; + } - /* 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; } 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, diff --git a/src/core/utf8.c b/src/core/utf8.c new file mode 100644 index 00000000..54a4d15b --- /dev/null +++ b/src/core/utf8.c @@ -0,0 +1,124 @@ +/* utf8.c - Operations on UTF-8 strings. + * + * Copyright (C) 2002 Timo Sirainen + * + * Based on GLib code by + * + * Copyright (C) 1999 Tom Tromey + * Copyright (C) 2000 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 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; +} + +/* 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/core/utf8.h similarity index 70% rename from src/fe-common/core/utf8.h rename to src/core/utf8.h index 3c15dc7d..6462f579 100644 --- a/src/fe-common/core/utf8.h +++ b/src/core/utf8.h @@ -8,9 +8,16 @@ #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); +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) 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/fe-channels.c b/src/fe-common/core/fe-channels.c index 046d641a..c406ac73 100644 --- a/src/fe-common/core/fe-channels.c +++ b/src/fe-common/core/fe-channels.c @@ -26,6 +26,8 @@ #include "levels.h" #include "misc.h" #include "settings.h" +#include "special-vars.h" +#include "utf8.h" #include "chat-protocols.h" #include "chatnets.h" @@ -323,40 +325,40 @@ static void cmd_channel_remove(const char *data) static int get_nick_length(void *data) { - return strlen(((NICK_REC *) data)->nick); + return get_utf8_string_width(((NICK_REC *) data)->nick, 1024); } 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 *linebuf, nickmode[2] = { 0, 0 }; + char *format, *stripped, *prefix_format; + 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); - 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 +367,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 +386,18 @@ 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; @@ -405,48 +406,44 @@ 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 : - 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, aligned_nick); g_string_append(str, format); + g_free(aligned_nick); 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); 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) 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-common/core/utf8.c b/src/fe-common/core/utf8.c deleted file mode 100644 index 2d07ea8e..00000000 --- a/src/fe-common/core/utf8.c +++ /dev/null @@ -1,26 +0,0 @@ -/* utf8.c - Operations on UTF-8 strings. - * - * Copyright (C) 2002 Timo Sirainen - * - * Based on GLib code by - * - * Copyright (C) 1999 Tom Tromey - * Copyright (C) 2000 Red Hat, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "module.h" - 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;