Hello David, hello other remind fellows,

first of all thank you for this awesome program.

I'm using remind now for some years primarily by printing the calendar
to stdout. When I first tried to include chinese/japanese characters I
was sad to see that despite UTF-8 support the calendar was malformed.
Obviously characters occupying multiple columns are treated like single
column characters resulting in too much padding or even printing into
the next day's box. Last week I found some time to fix this.

I've mainly incorporated the wcwidth/wcswidth functions to determine
the characters column width by rewriting the PrintLeft, PrintCentered
and PrintOneColLine functions in calendar.c. If this is useful please
use the attached patch (hopefully mailman doesn't filters it) to make
remind even more awesome. If there are some problems with code style or
regressions (I tested multiple corner cases) I'm happy to comply with
changes.

Regards,

Martin
From ecd9ca0f8778d2a587a8c17d6dfe5303122c1836 Mon Sep 17 00:00:00 2001
From: Martin Roedel <[email protected]>
Date: Tue, 21 Oct 2014 17:20:43 +0200
Subject: [PATCH] fix UTF-8 character column width

---
 src/calendar.c | 365 ++++++++++++++++++++++++++++-----------------------------
 1 file changed, 177 insertions(+), 188 deletions(-)

diff --git a/src/calendar.c b/src/calendar.c
index 0b4ff6e..65ff314 100644
--- a/src/calendar.c
+++ b/src/calendar.c
@@ -10,6 +10,8 @@
 /*                                                             */
 /***************************************************************/
 
+#define _XOPEN_SOURCE
+
 #include "config.h"
 
 #include <stdio.h>
@@ -20,6 +22,7 @@
 
 #ifdef REM_USE_WCHAR
 #include <wctype.h>
+#include <wchar.h>
 #endif
 
 #include "types.h"
@@ -503,6 +506,44 @@ static int WriteCalendarRow(void)
 
 /***************************************************************/
 /*                                                             */
+/*  GetStrWidth                                                */
+/*                                                             */
+/*  Calculate column width of potential MBCS string.           */
+/*                                                             */
+/***************************************************************/
+static size_t GetStrWidth(char const *s)
+{
+#ifndef REM_USE_WCHAR
+    return strlen(s);
+#else
+    size_t bytes = strlen(s);
+    size_t len = mbstowcs(NULL, s, 0);
+    wchar_t *buf, static_buf[128];
+    
+    if(len == -1)
+        return 0;
+    else if(bytes == len)
+        return len;
+    else if(len+1 <= 128)
+        buf = static_buf;
+    else if(!(buf = calloc(len+1, sizeof(wchar_t))))
+    {
+        fprintf(stderr, "%s\n", ErrMsg[E_NO_MEM]);
+        exit(1);
+    }
+    
+    mbstowcs(buf, s, len+1);
+    len = wcswidth(buf, len);
+    
+    if(buf != static_buf)
+        free(buf);
+    
+    return len;
+#endif
+}
+
+/***************************************************************/
+/*                                                             */
 /*  PrintLeft                                                  */
 /*                                                             */
 /*  Left-justify a piece of text.                              */
@@ -510,9 +551,12 @@ static int WriteCalendarRow(void)
 /***************************************************************/
 static void PrintLeft(char const *s, int width, char pad)
 {
-    int len = strlen(s);
+    size_t len = GetStrWidth(s);
+    
     printf("%s", s);
-    while (len++ < width) PutChar(pad);
+    
+    while(len++ < width)
+        PutChar(pad);
 }
 
 /***************************************************************/
@@ -524,50 +568,24 @@ static void PrintLeft(char const *s, int width, char pad)
 /***************************************************************/
 static void PrintCentered(char const *s, int width, char *pad)
 {
-#ifndef REM_USE_WCHAR
-    int len = strlen(s);
-    int d = (width - len) / 2;
-    int i;
-
-    for (i=0; i<d; i++) fputs(pad, stdout);
-    for (i=0; i<width; i++) {
-	if (*s) PutChar(*s++); else break;
-    }
-    for (i=d+len; i<width; i++) fputs(pad, stdout);
-#else
-    size_t len = mbstowcs(NULL, s, 0);
-    int i;
-    wchar_t static_buf[128];
-    wchar_t *buf;
-    wchar_t *ws;
-    int d;
-
-    if (!len) {
-	for (i=0; i<width; i++) {
-	    fputs(pad, stdout);
-	}
-	return;
-    }
-    if (len + 1 <= 128) {
-	buf = static_buf;
-    } else {
-	buf = calloc(len+1, sizeof(wchar_t));
-	if (!buf) {
-	    /* Uh-oh... cannot recover */
-	    fprintf(stderr, "%s\n", ErrMsg[E_NO_MEM]);
-	    exit(1);
-	}
-    }
-    (void) mbstowcs(buf, s, len+1);
-    d = (width - len) / 2;
-    ws = buf;
-    for (i=0; i<d; i++) fputs(pad, stdout);
-    for (i=0; i<width; i++) {
-	if (*ws) PutWideChar(*ws++); else break;
+    size_t len = GetStrWidth(s);
+    size_t i, d = len > width ? 0 : (width - len) / 2;
+    
+    if(!len)
+    {
+        while(len++ < width)
+            fputs(pad, stdout);
+        return;
     }
-    for (i=d+len; i<width; i++) fputs(pad, stdout);
-    if (buf != static_buf) free(buf);
-#endif
+    
+    for(i=0; i<d; i++)
+        fputs(pad, stdout);
+    
+    while(*s)
+        PutChar(*s++);
+    
+    for(i=d+len; i<width; i++)
+        fputs(pad, stdout);
 }
 
 /***************************************************************/
@@ -611,160 +629,131 @@ static int WriteOneCalLine(void)
 static int WriteOneColLine(int col)
 {
     CalEntry *e = CalColumn[col];
-    char const *s;
-    char const *space;
-
+    char const *s = 0, *space = 0;
+    size_t width = 0;
+    
 #ifdef REM_USE_WCHAR
-    wchar_t const *ws;
-    wchar_t const *wspace;
+    size_t w = 0;
+    wchar_t const *ws = 0, *wspace = 0;
 #endif
-
-    int numwritten = 0;
-
-    /* Print as many characters as possible within the column */
+    
+    /* If we're at the end, and there's another entry, do a blank
+       line and move to next entry. */
 #ifdef REM_USE_WCHAR
-    if (e->wc_text) {
-	wspace = NULL;
-	ws = e->wc_pos;
-
-	/* If we're at the end, and there's another entry, do a blank
-	   line and move to next entry. */
-	if (!*ws && e->next) {
-	    PrintLeft("", ColSpaces, ' ');
-	    CalColumn[col] = e->next;
-	    free((void *)e->text);
-	    free((void *)e->filename);
-	    if (e->wc_text) free((void *)e->wc_text);
-	    free(e);
-	    return 1;
-	}
-
-	/* Find the last space char within the column. */
-	while (ws - e->wc_pos <= ColSpaces) {
-	    if (!*ws) {wspace = ws; break;}
-	    if (iswspace(*ws)) wspace = ws;
-	    ws++;
-	}
-
-	/* Colorize reminder if necessary */
-	if (UseVTColors && e->is_color) {
-	    Colorize(e);
-	}
-
-	/* If we couldn't find a space char, print what we have. */
-	if (!wspace) {
-	    for (ws = e->wc_pos; ws - e->wc_pos < ColSpaces; ws++) {
-		if (!*ws) break;
-		numwritten++;
-		PutWideChar(*ws);
-	    }
-	    e->wc_pos = ws;
-	} else {
-	    /* We found a space - print everything before it. */
-	    for (ws = e->wc_pos; ws<wspace; ws++) {
-		if (!*ws) break;
-		numwritten++;
-		PutWideChar(*ws);
-	    }
-	}
-
-	/* Decolorize reminder if necessary */
-	if (UseVTColors && e->is_color) {
-	    Decolorize();
-	}
-
-	/* Flesh out the rest of the column */
-	while(numwritten++ < ColSpaces) PutChar(' ');
-
-	/* Skip any spaces before next word */
-	while (iswspace(*ws)) ws++;
-
-	/* If done, free memory if no next entry. */
-	if (!*ws && !e->next) {
-	    CalColumn[col] = e->next;
-	    free((void *)e->text);
-	    free((void *)e->filename);
-	    if (e->wc_text) free((void *)e->wc_text);
-	    free(e);
-	} else {
-	    e->wc_pos = ws;
-	}
-	if (CalColumn[col]) return 1; else return 0;
-    } else {
+    if(((e->wc_pos && !*e->wc_pos) || (!e->wc_pos && !*e->pos)) && e->next)
+#else
+    if(!*e->pos && e->next)
 #endif
-	space = NULL;
-	s = e->pos;
-
-	/* If we're at the end, and there's another entry, do a blank
-	   line and move to next entry. */
-	if (!*s && e->next) {
-	    PrintLeft("", ColSpaces, ' ');
-	    CalColumn[col] = e->next;
-	    free((void *)e->text);
-	    free((void *)e->filename);
+    {
+        PrintLeft("", ColSpaces, ' ');
+        CalColumn[col] = e->next;
+        free((void*)e->text);
+        free((void*)e->filename);
 #ifdef REM_USE_WCHAR
-	    if (e->wc_text) free((void *)e->wc_text);
+        if(e->wc_text)
+            free((void *)e->wc_text);
 #endif
-	    free(e);
-	    return 1;
-	}
-
-	/* Find the last space char within the column. */
-	while (s - e->pos <= ColSpaces) {
-	    if (!*s) {space = s; break;}
-	    if (*s == ' ') space = s;
-	    s++;
-	}
-
-	/* Colorize reminder if necessary */
-	if (UseVTColors && e->is_color) {
-	    Colorize(e);
-	}
-
-	/* If we couldn't find a space char, print what we have. */
-	if (!space) {
-	    for (s = e->pos; s - e->pos < ColSpaces; s++) {
-		if (!*s) break;
-		numwritten++;
-		PutChar(*s);
-	    }
-	    e->pos = s;
-	} else {
-	    /* We found a space - print everything before it. */
-	    for (s = e->pos; s<space; s++) {
-		if (!*s) break;
-		numwritten++;
-		PutChar(*s);
-	    }
-	}
-
-	/* Decolorize reminder if necessary */
-	if (UseVTColors && e->is_color) {
-	    Decolorize();
-	}
-
-	/* Flesh out the rest of the column */
-	while(numwritten++ < ColSpaces) PutChar(' ');
-
-	/* Skip any spaces before next word */
-	while (*s == ' ') s++;
-
-	/* If done, free memory if no next entry. */
-	if (!*s && !e->next) {
-	    CalColumn[col] = e->next;
-	    free((void *)e->text);
-	    free((void *)e->filename);
+        free(e);
+        return 1;
+    }
+    
+    /* Find the last space char within the column. */
 #ifdef REM_USE_WCHAR
-	    if (e->wc_text) free((void *)e->wc_text);
+    if(e->wc_pos)
+    {
+        for(ws = e->wc_pos, w = 0; *ws; ws++)
+        {
+            if(iswspace(*ws))
+            {
+                wspace = ws;
+                width = w;
+            }
+            if((w += wcwidth(*ws)) > ColSpaces)
+            {
+                w -= wcwidth(*ws);
+                break;
+            }
+        }
+    }
+    else
 #endif
-	    free(e);
-	} else {
-	    e->pos = s;
-	}
-	if (CalColumn[col]) return 1; else return 0;
+        for(s = e->pos; *s && s - e->pos < ColSpaces; s++)
+            if(isspace(*s))
+            {
+                space = s;
+                width = s - e->pos;
+            }
+    
+    /* If no space is found or the string fits into ColSpaces
+       position space at end of string
+       or e->pos + ColSpaces whichever comes first. */
 #ifdef REM_USE_WCHAR
+    if(e->wc_pos)
+    {
+        if(!wspace || !*ws)
+        {
+            wspace = ws;
+            width = w;
+        }
     }
+    else
+#endif
+        if(!space || !*s)
+        {
+            space = s;
+            width = s - e->pos;
+        }
+    
+    /* Colorize reminder if necessary */
+    if(UseVTColors && e->is_color)
+        Colorize(e);
+    
+    /* Print everything until space, end of string or ColSpaces reached */
+#ifdef REM_USE_WCHAR
+    if(e->wc_pos)
+        while(e->wc_pos < wspace)
+            PutWideChar(*e->wc_pos++);
+    else
+#endif
+        while(e->pos < space)
+            PutChar(*e->pos++);
+    
+    /* Decolorize reminder if necessary */
+    if(UseVTColors && e->is_color)
+        Decolorize();
+    
+    /* Flesh out the rest of the column */
+    while(width++ < ColSpaces)
+        PutChar(' ');
+    
+    /* Skip any spaces before next word */
+#ifdef REM_USE_WCHAR
+    if(e->wc_pos)
+        while(iswspace(*e->wc_pos))
+            e->wc_pos++;
+    else
+#endif
+        while(isspace(*e->pos))
+            e->pos++;
+    
+    /* If done, free memory if no next entry. */
+#ifdef REM_USE_WCHAR
+    if(((e->wc_pos && !*e->wc_pos) || (!e->wc_pos && !*e->pos)) && !e->next)
+#else
+    if(!*e->pos && !e->next)
+#endif
+    {
+        CalColumn[col] = e->next;
+        free((void*)e->text);
+        free((void*)e->filename);
+#ifdef REM_USE_WCHAR
+        if(e->wc_text)
+            free((void *)e->wc_text);
 #endif
+        free(e);
+    }
+    
+    return !!CalColumn[col];
 }
 
 /***************************************************************/
-- 
1.8.3.1

Attachment: signature.asc
Description: PGP signature

_______________________________________________
Remind-fans mailing list
[email protected]
http://lists.roaringpenguin.com/cgi-bin/mailman/listinfo/remind-fans
Remind is at http://www.roaringpenguin.com/products/remind

Reply via email to