Fix double- and zero-wide characters

It's not perfect but seems to work well enough.
This commit is contained in:
Přemysl Eric Janouch 2013-06-02 00:01:23 +02:00
parent d3b966a93f
commit 1cc91a4b17
3 changed files with 78 additions and 17 deletions

View File

@ -17,6 +17,10 @@ set (project_VERSION "${project_VERSION_MAJOR}")
set (project_VERSION "${project_VERSION}.${project_VERSION_MINOR}")
set (project_VERSION "${project_VERSION}.${project_VERSION_PATCH}")
# Configuration
include (CheckFunctionExists)
CHECK_FUNCTION_EXISTS ("wcwidth" HAVE_WCWIDTH)
# Dependecies
find_package (PkgConfig REQUIRED)
pkg_check_modules (dependencies REQUIRED ncursesw glib-2.0 gio-2.0 pango)

View File

@ -8,5 +8,7 @@
#define GETTEXT_PACKAGE PROJECT_NAME
#define GETTEXT_DIRNAME "${CMAKE_INSTALL_PREFIX}/share/locale"
#cmakedefine HAVE_WCWIDTH
#endif /* ! CONFIG_H */

View File

@ -18,7 +18,8 @@
*
*/
#define _XOPEN_SOURCE_EXTENDED /**< Yes, we want ncursesw. */
#define _XOPEN_SOURCE 500 //!< wcwidth
#define _XOPEN_SOURCE_EXTENDED //!< Yes, we want ncursesw.
#include <stdio.h>
#include <stdlib.h>
@ -98,6 +99,18 @@ struct curses_event
MEVENT mouse;
};
static size_t
unichar_width (gunichar ch)
{
if (g_unichar_iszerowidth (ch))
return 0;
return 1 + g_unichar_iswide (ch);
}
#ifndef HAVE_WCWIDTH
#define wcwidth(x) 1
#endif // ! HAVE_WCWIDTH
static gboolean
is_character_in_locale (wchar_t c)
{
@ -353,31 +366,68 @@ app_add_utf8_string (Application *self, const gchar *str, int n)
ssize_t wide_len = wcslen (wide_str);
wchar_t padding = L' ', error = L'?', ellipsis = L'';
if (n < 0)
n = wide_len;
if (!n)
return 0;
if (wide_len > n)
// Compute how many wide characters fit in the limit
gint cols, i;
for (cols = i = 0; i < wide_len; i++)
{
if (is_character_in_locale (ellipsis) && n > 0)
wide_str[n - 1] = ellipsis;
else if (n >= 3)
if (!is_character_in_locale (wide_str[i]))
wide_str[i] = error;
gint width = wcwidth (wide_str[i]);
if (n >= 0 && cols + width > n)
break;
cols += width;
}
if (n < 0)
n = cols;
// Append ellipsis if the whole string didn't fit
gint len = i;
if (len != wide_len)
{
if (is_character_in_locale (ellipsis))
{
wide_str[n - 1] = L'.';
wide_str[n - 2] = L'.';
wide_str[n - 3] = L'.';
if (cols + wcwidth (ellipsis) > n)
cols -= wcwidth (wide_str[len - 1]);
else
len++;
wide_str[len - 1] = ellipsis;
cols += wcwidth (ellipsis);
}
else if (n >= 3 && len >= 3)
{
// With zero-width characters this overflows
// It's just a fallback anyway
cols -= wcwidth (wide_str[len - 1]);
cols -= wcwidth (wide_str[len - 2]);
cols -= wcwidth (wide_str[len - 3]);
cols += 3;
wide_str[len - 1] = L'.';
wide_str[len - 2] = L'.';
wide_str[len - 3] = L'.';
}
}
gint i;
cchar_t cch;
for (i = 0; i < n; i++)
for (i = 0; i < len; i++)
{
if (setcchar (&cch, (i < wide_len ? &wide_str[i] : &padding),
A_NORMAL, 0, NULL) == ERR)
setcchar (&cch, &error, A_NORMAL, 0, NULL);
add_wch (&cch);
if (setcchar (&cch, &wide_str[i], A_NORMAL, 0, NULL) == OK)
add_wch (&cch);
else
// This shouldn't happen
cols -= wcwidth (wide_str[i]);
}
setcchar (&cch, &padding, A_NORMAL, 0, NULL);
while (cols++ < n)
add_wch (&cch);
g_free (wide_str);
return n;
}
@ -399,7 +449,12 @@ app_redraw_top (Application *self)
app_add_utf8_string (self, input_utf8, COLS - indent);
g_free (input_utf8);
move (0, indent + self->input_pos);
guint offset, i;
for (offset = i = 0; i < self->input_pos; i++)
// This may be inconsistent with the output of app_add_utf8_string()
offset += unichar_width (g_array_index (self->input, gunichar, i));
move (0, indent + offset);
refresh ();
}