UTF-8 support for para_gui.
[paraslash.git] / string.c
index 283c46e23442f41dbfc4ad0721fe60cd3f16e397..21c674ec3753f64c9115556ca80bb3e751e42ed3 100644 (file)
--- a/string.c
+++ b/string.c
@@ -6,12 +6,19 @@
 
 /** \file string.c Memory allocation and string handling functions. */
 
 
 /** \file string.c Memory allocation and string handling functions. */
 
+#define _GNU_SOURCE
+
 #include <sys/time.h> /* gettimeofday */
 #include <pwd.h>
 #include <sys/utsname.h> /* uname() */
 #include <sys/time.h> /* gettimeofday */
 #include <pwd.h>
 #include <sys/utsname.h> /* uname() */
+
 #include <string.h>
 #include <regex.h>
 
 #include <string.h>
 #include <regex.h>
 
+#include <langinfo.h>
+#include <wchar.h>
+#include <wctype.h>
+
 #include "para.h"
 #include "string.h"
 #include "error.h"
 #include "para.h"
 #include "string.h"
 #include "error.h"
@@ -982,3 +989,145 @@ char *key_value_copy(const char *src, size_t len, const char *key)
                return NULL;
        return safe_strdup(src + keylen + 1, len - keylen - 1);
 }
                return NULL;
        return safe_strdup(src + keylen + 1, len - keylen - 1);
 }
+
+static bool utf8_mode(void)
+{
+       static bool initialized, have_utf8;
+
+       if (!initialized) {
+               char *info = nl_langinfo(CODESET);
+               have_utf8 = (info && strcmp(info, "UTF-8") == 0);
+               initialized = true;
+               PARA_INFO_LOG("%susing UTF-8 character encoding\n",
+                       have_utf8? "" : "not ");
+       }
+       return have_utf8;
+}
+
+/*
+ * glibc's wcswidth returns -1 if the string contains a tab character, which
+ * makes the function next to useless. The two functions below are taken from
+ * mutt.
+ */
+
+#define IsWPrint(wc) (iswprint(wc) || wc >= 0xa0)
+
+static int mutt_wcwidth(wchar_t wc, size_t pos)
+{
+       int n;
+
+       if (wc == 0x09) /* tab */
+               return (pos | 7) + 1 - pos;
+       n = wcwidth(wc);
+       if (IsWPrint(wc) && n > 0)
+               return n;
+       if (!(wc & ~0x7f))
+               return 2;
+       if (!(wc & ~0xffff))
+               return 6;
+       return 10;
+}
+
+static size_t mutt_wcswidth(const wchar_t *s, size_t n)
+{
+       size_t w = 0;
+
+       while (n--)
+               w += mutt_wcwidth(*s++, w);
+       return w;
+}
+
+/**
+ * Skip a given number of cells at the beginning of a string.
+ *
+ * \param s The input string.
+ * \param cells_to_skip Desired number of cells that should be skipped.
+ * \param bytes_to_skip Result.
+ *
+ * This function computes how many input bytes must be skipped to advance a
+ * string by the given width. If the current character encoding is not UTF-8,
+ * this is simply the given number of cells, i.e. \a cells_to_skip. Otherwise,
+ * \a s is treated as a multibyte string and on successful return, \a s +
+ * bytes_to_skip points to the start of a multibyte string such that the total
+ * width of the multibyte characters that are skipped by advancing \a s that
+ * many bytes equals at least \a cells_to_skip.
+ *
+ * \return Standard.
+ */
+int skip_cells(const char *s, size_t cells_to_skip, size_t *bytes_to_skip)
+{
+       wchar_t wc;
+       mbstate_t ps;
+       size_t n, bytes_parsed, cells_skipped;
+
+       *bytes_to_skip = 0;
+       if (cells_to_skip == 0)
+               return 0;
+       if (!utf8_mode()) {
+               *bytes_to_skip = cells_to_skip;
+               return 0;
+       }
+       bytes_parsed = cells_skipped = 0;
+       memset(&ps, 0, sizeof(ps));
+       n = strlen(s);
+       while (cells_to_skip > cells_skipped) {
+               size_t mbret;
+
+               mbret = mbrtowc(&wc, s + bytes_parsed, n - bytes_parsed, &ps);
+               assert(mbret != 0);
+               if (mbret == (size_t)-1 || mbret == (size_t)-2)
+                       return -ERRNO_TO_PARA_ERROR(EILSEQ);
+               bytes_parsed += mbret;
+               cells_skipped += mutt_wcwidth(wc, cells_skipped);
+       }
+       *bytes_to_skip = bytes_parsed;
+       return 1;
+}
+
+/**
+ * Compute the width of an UTF-8 string.
+ *
+ * \param s The string.
+ * \param result The width of \a s is returned here.
+ *
+ * If not in UTF8-mode. this function is just a wrapper for strlen(3).
+ * Otherwise \s is treated as an UTF-8 string and its display width is
+ * computed. Note that this function may fail if the underlying call to
+ * mbsrtowcs(3) fails, so the caller must check the return value.
+ *
+ * \sa nl_langinfo(3), wcswidth(3).
+ *
+ * \return Standard.
+ */
+__must_check int strwidth(const char *s, size_t *result)
+{
+       const char *src = s;
+       mbstate_t state;
+       static wchar_t *dest;
+       size_t num_wchars;
+
+       /*
+        * Never call any log function here. This may result in an endless loop
+        * as para_gui's para_log() calls this function.
+        */
+
+       if (!utf8_mode()) {
+               *result = strlen(s);
+               return 0;
+       }
+       memset(&state, 0, sizeof(state));
+       *result = 0;
+       num_wchars = mbsrtowcs(NULL, &src, 0, &state);
+       if (num_wchars == (size_t)-1)
+               return -ERRNO_TO_PARA_ERROR(errno);
+       if (num_wchars == 0)
+               return 0;
+       dest = para_malloc(num_wchars * sizeof(*dest));
+       src = s;
+       memset(&state, 0, sizeof(state));
+       num_wchars = mbsrtowcs(dest, &src, num_wchars, &state);
+       assert(num_wchars > 0 && num_wchars != (size_t)-1);
+       *result = mutt_wcswidth(dest, num_wchars);
+       free(dest);
+       return 1;
+}