Browse Source

Fix fixed-width CJK formatting (#523)

* WIP: Fix fixed-width CJK formatting

* Consistent docstring; Remove unused printf()

* Support Python 2 (what about the python3 shebang?)

* Add a comment about named arguments

* Add lib/gis/printa.c (G_*printa() functions) for printing aligned wide characters

* Add a blank line after declarations

* Comment on snprintf()

* Rename _*printf => o*printf; Error messages

* Comments

* malloc => G_malloc

* Increase the size of spec buffer and fatal error on too long a spec

* Fix precision

* spaces

* Consistent style

* Rename wide_count to count_wide_chars

* Rename *printa => *aprinf

* Consolidate count_*_in_cols() functions; Add comments; 80 columns

* Update lib/gis/aprintf.c

Co-authored-by: Markus Neteler <neteler@gmail.com>

* Update lib/gis/aprintf.c

Co-authored-by: Markus Neteler <neteler@gmail.com>

Co-authored-by: Markus Neteler <neteler@gmail.com>
Huidae Cho 4 years ago
parent
commit
229dd3ea77
4 changed files with 573 additions and 40 deletions
  1. 10 0
      include/defs/gis.h
  2. 485 0
      lib/gis/aprintf.c
  3. 42 4
      lib/init/grass.py
  4. 36 36
      vector/v.info/print.c

+ 10 - 0
include/defs/gis.h

@@ -161,6 +161,16 @@ int G_asprintf(char **, const char *, ...)
 int G_rasprintf(char **, size_t *,const char *, ...)
     __attribute__ ((format(printf, 3, 4)));
 
+/* aprintf.c */
+int G_aprintf(const char *, ...);
+int G_faprintf(FILE *, const char *, ...);
+int G_saprintf(char *, const char *, ...);
+int G_snaprintf(char *, size_t, const char *, ...);
+int G_vaprintf(const char *, va_list);
+int G_vfaprintf(FILE *, const char *, va_list);
+int G_vsaprintf(char *, const char *, va_list);
+int G_vsnaprintf(char *, size_t, const char *, va_list);
+
 /* bands.c */
 int G__read_band_reference(FILE *, struct Key_Value **);
 int G__write_band_reference(FILE *, const char *, const char *);

+ 485 - 0
lib/gis/aprintf.c

@@ -0,0 +1,485 @@
+/*!
+ * \file lib/gis/aprintf.c
+ *
+ * \brief GIS Library - Print functions for aligning wide characters.
+ *
+ * Extracted from the aligned printf C library (libaprintf under GPL v3+) by
+ * Huidae Cho.
+ *
+ * (C) 2020 by the GRASS Development Team
+ *
+ * This program is free software under the GNU General Public License
+ * (>=v2). Read the file COPYING that comes with GRASS for details.
+ *
+ * \author Huidae Cho
+ *
+ * \date 2020
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include <grass/gis.h>
+#include <grass/glocale.h>
+
+/* printf(3) man page */
+#define CONVS "diouxXeEfFgGaAcsCSpnm%"
+
+/* % + flags + width + precision + length + conversion + NULL */
+#define SPEC_BUF_SIZE 16
+
+struct options
+{
+    FILE *stream;
+    char *str, *_str;
+    size_t size, _size;
+};
+
+static int count_wide_chars(const char *);
+static int count_wide_chars_in_cols(const char *, int, int *);
+static int ovprintf(struct options *, const char *, va_list);
+static int oprintf(struct options *, const char *, ...);
+static int oaprintf(struct options *, const char *, va_list);
+
+/*!
+ * \brief Count the number of wide characters in a string.
+ *
+ * \param[in] str input string
+ * \return number of wide characters in str
+ */
+static int count_wide_chars(const char *str)
+{
+    int nwchars = 0, lead = 0;
+
+    while (*str)
+	/* if the first two bits are 10 (0x80 = 1000 0000), this byte is
+	 * following a previous multi-byte character */
+	if ((*str++ & 0xc0) != 0x80)
+	    lead = 1;
+	else if (lead) {
+	    /* only count the second byte of a multi-byte character */
+	    lead = 0;
+	    nwchars++;
+	}
+
+    return nwchars;
+}
+
+/*!
+ * \brief Count the numbers of wide characters and bytes in a string in a
+ * number of columns.
+ *
+ * \param[in] str input string
+ * \param[in] ncols number of columns
+ * \param[out] nbytes number of bytes (NULL for not counting)
+ * \return number of wide characters in str
+ */
+static int count_wide_chars_in_cols(const char *str, int ncols, int *nbytes)
+{
+    const char *p = str - 1;
+    int lead = 0, nwchars = 0;
+
+    /* count the numbers of wide characters and bytes in one loop */
+    while (ncols >= 0 && *++p)
+	if ((*p & 0xc0) != 0x80) {
+	    /* a single-byte character or the leading byte of a multi-byte
+	     * character; don't count it */
+	    lead = 1;
+	    ncols--;
+	} else if (lead) {
+	    /* only count the second byte of a multi-byte character; don't
+	     * consume more than two columns (leading and second bytes) */
+	    lead = 0;
+	    ncols--;
+	    nwchars++;
+	}
+
+    /* if the current byte after ncols is still part of a multi-byte character,
+     * trash it because it's not a full wide character */
+    if ((*p & 0xc0) == 0x80)
+	nwchars--;
+
+    /* see how many bytes we have advanced */
+    *nbytes = p - str;
+
+    return nwchars;
+}
+
+/*!
+ * \brief Branch into vprintf(), vfprintf(), or vsprintf() depending on passed
+ * options.
+ *
+ * \param[in] opts options for branching
+ * \param[in] format string format
+ * \param[in] ap variable argument list for the format string
+ * \return number of bytes printed or fatal error on error
+ */
+static int ovprintf(struct options *opts, const char *format, va_list ap)
+{
+    int nbytes;
+
+    if (opts == NULL || (opts->stream == NULL && opts->_str == NULL))
+	nbytes = vprintf(format, ap);
+    else if (opts->stream)
+	nbytes = vfprintf(opts->stream, format, ap);
+    else {
+	if ((long int)opts->size >= 0) {
+	    /* snprintf(str, 0, ...) does not alter str */
+	    nbytes = vsnprintf(opts->_str, opts->_size, format, ap);
+	    opts->_size -= nbytes;
+	} else
+	    /* snprintf(str, negative, ...) is equivalent to snprintf(str, ...)
+	     * because size_t is unsigned */
+	    nbytes = vsprintf(opts->_str, format, ap);
+	opts->_str += nbytes;
+    }
+
+    if (nbytes < 0)
+	G_fatal_error(_("Failed to print %s"), format);
+
+    return nbytes;
+}
+
+/*!
+ * \brief Invoke ovprintf() for branching into different *printf() functions.
+ *
+ * \param[in] opts options for branching
+ * \param[in] format string format
+ * \param[in] ... arguments for the format string
+ * \return number of bytes printed or fatal error on error
+ */
+static int oprintf(struct options *opts, const char *format, ...)
+{
+    va_list ap;
+    int nbytes;
+
+    va_start(ap, format);
+    nbytes = ovprintf(opts, format, ap);
+    va_end(ap);
+
+    return nbytes;
+}
+
+/*!
+ * \brief Core function for aligning wide characters with Latin characters
+ * using %s specifiers. G_aprintf(), G_faprintf(), and G_saprintf() wrap around
+ * this function to implement printf(), fprintf(), and sprintf() counterparts,
+ * respectively.
+ *
+ * \param[in] opts options for branching
+ * \param[in] format string format
+ * \param[in] ap variable argument list for the format string
+ * \return number of bytes printed or fatal error on error
+ */
+static int oaprintf(struct options *opts, const char *format, va_list ap)
+{
+    char *fmt, *asis, *p, spec[SPEC_BUF_SIZE];
+    int nbytes = 0;
+
+    /* make a copy so we can temporarily change the format string */
+    p = asis = fmt = (char *)G_malloc(strlen(format) + 1);
+    strcpy(fmt, format);
+
+    while (*p) {
+	if (*p == '%') {
+	    char *q = p, *p_spec = spec;
+
+	    /* print the string before this specifier */
+	    *p = 0;
+	    nbytes += oprintf(opts, asis);
+	    *p = '%';
+
+	    /* skip % */
+	    while (*++q) {
+		char *c = CONVS - 1;
+
+		while (*++c && *q != *c);
+		if (*c) {
+		    char tmp;
+		    /* found a conversion specifier */
+		    if (*c == 's') {
+			/* if this is a string specifier */
+			int width = -1, prec = -1, use_ovprintf = 1;
+			char *p_tmp, *s;
+			va_list ap_copy;
+
+			/* save this ap and use ovprintf() for non-wide
+			 * characters */
+			va_copy(ap_copy, ap);
+
+			*p_spec = 0;
+			p_spec = spec;
+			if (*p_spec == '-')
+			    /* alignment */
+			    p_spec++;
+			if (*p_spec == '*') {
+			    /* read width from next argument */
+			    width = va_arg(ap, int);
+			    p_spec++;
+			} else if (*p_spec >= '0' && *p_spec <= '9') {
+			    /* read width */
+			    p_tmp = p_spec;
+			    while (*p_spec >= '0' && *p_spec <= '9')
+				p_spec++;
+			    tmp = *p_spec;
+			    *p_spec = 0;
+			    width = atoi(p_tmp);
+			    *p_spec = tmp;
+			}
+			if (*p_spec == '.') {
+			    /* precision */
+			    p_spec++;
+			    if (*p_spec == '*') {
+				/* read precision from next argument */
+				prec = va_arg(ap, int);
+				p_spec++;
+			    } else if (*p_spec >= '0' && *p_spec <= '9') {
+				/* read precision */
+				p_tmp = p_spec;
+				while (*p_spec >= '0' && *p_spec <= '9')
+				    p_spec++;
+				tmp = *p_spec;
+				*p_spec = 0;
+				prec = atoi(p_tmp);
+				*p_spec = tmp;
+			    }
+			}
+			if (*p_spec) {
+			    /* illegal string specifier? */
+			    va_end(ap_copy);
+			    *(q + 1) = 0;
+			    G_fatal_error(
+				    _("Failed to parse string specifier: %s"),
+				    p);
+			}
+
+			s = va_arg(ap, char *);
+			if (width > 0) {
+			    /* if width is specified */
+			    int wcount = count_wide_chars(s);
+
+			    if (wcount) {
+				/* if there are wide characters */
+				if (prec > 0)
+				    width += count_wide_chars_in_cols(s, prec,
+								      &prec);
+				else if (prec < 0)
+				    width += wcount;
+				p_spec = spec;
+				p_spec += sprintf(p_spec, "%%%s%d",
+					spec[0] == '-' ? "-" : "", width);
+				if (prec >= 0)
+				    p_spec += sprintf(p_spec, ".%d", prec);
+				*p_spec++ = 's';
+				*p_spec = 0;
+				nbytes += oprintf(opts, spec, s);
+				use_ovprintf = 0;
+			    }
+			    /* else use ovprintf() as much as possible */
+			}
+			/* else use ovprintf() as much as possible */
+			if (use_ovprintf) {
+			    tmp = *(q + 1);
+			    *(q + 1) = 0;
+			    nbytes += ovprintf(opts, p, ap_copy);
+			    *(q + 1) = tmp;
+			}
+
+			va_end(ap_copy);
+		    } else {
+			/* else use ovprintf() for non-string specifiers */
+			tmp = *(q + 1);
+			*(q + 1) = 0;
+			nbytes += ovprintf(opts, p, ap);
+			*(q + 1) = tmp;
+		    }
+		    break;
+		} else if (p_spec - spec < SPEC_BUF_SIZE - 2)
+		    /* 2 reserved for % and NULL */
+		    *p_spec++ = *q;
+		else
+		    G_fatal_error(
+			    _("Format specifier exceeds the buffer size (%d)"),
+			    SPEC_BUF_SIZE);
+	    }
+	    asis = (p = q) + 1;
+	}
+	p++;
+    }
+
+    /* print the remaining string */
+    *p = 0;
+    nbytes += oprintf(opts, asis);
+    *p = '%';
+
+    return nbytes;
+}
+
+/*!
+ * \brief vprintf() version of G_aprintf(). See G_aprintf() for more details.
+ *
+ * \param[in] format string format
+ * \param[in] ap variable argument list for the format string
+ * \return number of bytes printed or fatal error on error
+ */
+int G_vaprintf(const char *format, va_list ap)
+{
+    return oaprintf(NULL, format, ap);
+}
+
+/*!
+ * \brief vfprintf() version of G_aprintf(). See G_aprintf() for more details.
+ *
+ * \param[in] stream file pointer
+ * \param[in] format string format
+ * \param[in] ap variable argument list for the format string
+ * \return number of bytes printed or fatal error on error
+ */
+int G_vfaprintf(FILE *stream, const char *format, va_list ap)
+{
+    struct options opts;
+
+    opts.stream = stream;
+    opts.str = NULL;
+    opts.size = -1;
+
+    return oaprintf(&opts, format, ap);
+}
+
+/*!
+ * \brief vsprintf() version of G_aprintf(). See G_aprintf() for more details.
+ *
+ * \param[in] str string buffer
+ * \param[in] format string format
+ * \param[in] ap variable argument list for the format string
+ * \return number of bytes printed or fatal error on error
+ */
+int G_vsaprintf(char *str, const char *format, va_list ap)
+{
+    struct options opts;
+
+    opts.stream = NULL;
+    opts.str = opts._str = str;
+    opts.size = -1;
+
+    return oaprintf(&opts, format, ap);
+}
+
+/*!
+ * \brief vsnprintf() version of G_aprintf(). See G_aprintf() for more details.
+ *
+ * \param[in] str string buffer
+ * \param[in] size string buffer size
+ * \param[in] format string format
+ * \param[in] ap variable argument list for the format string
+ * \return number of bytes that would be printed if size was big enough or
+ *	   fatal error on error
+ */
+int G_vsnaprintf(char *str, size_t size, const char *format, va_list ap)
+{
+    struct options opts;
+
+    opts.stream = NULL;
+    opts.str = opts._str = str;
+    opts.size = opts._size = size;
+
+    return oaprintf(&opts, format, ap);
+}
+
+/*!
+ * \brief Adjust the width of string specifiers to the display space instead of
+ * the number of bytes for wide characters and print them formatted using the
+ * adjusted display width.
+ *
+ * compare
+ *	printf("%10s|\n%10s|\n", "ABCD", "가나");
+-----------
+      ABCD|
+    가나|
+-----------
+ * and
+ *	G_aprintf("%10s|\n%10s|\n", "ABCD", "가나");
+-----------
+      ABCD|
+      가나|
+-----------
+ *
+ * \param[in] format string format
+ * \param[in] ... arguments for the format string
+ * \return number of bytes printed or fatal error on error
+ */
+int G_aprintf(const char *format, ...)
+{
+    va_list ap;
+    int nbytes;
+
+    va_start(ap, format);
+    nbytes = G_vaprintf(format, ap);
+    va_end(ap);
+
+    return nbytes;
+}
+
+/*!
+ * \brief fprintf() version of G_aprintf(). See G_aprintf() for more details.
+ *
+ * \param[in] stream file pointer
+ * \param[in] format string format
+ * \param[in] ... arguments for the format string
+ * \return number of bytes printed or fatal error on error
+ */
+int G_faprintf(FILE *stream, const char *format, ...)
+{
+    va_list ap;
+    int nbytes;
+
+    va_start(ap, format);
+    nbytes = G_vfaprintf(stream, format, ap);
+    va_end(ap);
+
+    return nbytes;
+}
+
+/*!
+ * \brief sprintf() version of G_aprintf(). See G_aprintf() for more details.
+ *
+ * \param[in] str string buffer
+ * \param[in] format string format
+ * \param[in] ... arguments for the format string
+ * \return number of bytes printed or fatal error on error
+ */
+int G_saprintf(char *str, const char *format, ...)
+{
+    va_list ap;
+    int nbytes;
+
+    va_start(ap, format);
+    nbytes = G_vsaprintf(str, format, ap);
+    va_end(ap);
+
+    return nbytes;
+}
+
+/*!
+ * \brief snprintf() version of G_aprintf(). See G_aprintf() for more details.
+ *
+ * \param[in] str string buffer
+ * \param[in] size string buffer size
+ * \param[in] format string format
+ * \param[in] ... arguments for the format string
+ * \return number of bytes that would be printed if size was big enough or
+ *	   fatal error on error
+ */
+int G_snaprintf(char *str, size_t size, const char *format, ...)
+{
+    va_list ap;
+    int nbytes;
+
+    va_start(ap, format);
+    nbytes = G_vsnaprintf(str, size, format, ap);
+    va_end(ap);
+
+    return nbytes;
+}

+ 42 - 4
lib/init/grass.py

@@ -54,6 +54,7 @@ import platform
 import tempfile
 import locale
 import uuid
+import unicodedata
 
 
 # mechanism meant for debugging this script (only)
@@ -264,6 +265,43 @@ def wxpath(*args):
     return os.path.join(_WXPYTHON_BASE, *args)
 
 
+def count_wide_chars(s):
+    """Returns the number of wide CJK characters in a string.
+
+    :param str s: string
+    """
+    return sum(unicodedata.east_asian_width(c) in 'WF' for c in
+            (s if sys.version_info.major >= 3 else unicode(s)))
+
+
+def f(fmt, *args):
+    """Adjusts fixed-width string specifiers for wide CJK characters and
+    returns a formatted string. Does not support named arguments yet.
+
+    :param str fmt: format string
+    :param *args: arguments for the format string
+    """
+    matches = []
+    # https://docs.python.org/3/library/stdtypes.html#old-string-formatting
+    for m in re.finditer('%([#0 +-]*)([0-9]*)(\.[0-9]*)?([hlL]?[diouxXeEfFgGcrsa%])', fmt):
+        matches.append(m)
+
+    if len(matches) != len(args):
+        raise Exception('The numbers of format specifiers and arguments do not match')
+
+    i = len(args) - 1
+    for m in reversed(matches):
+        f = m.group(1)
+        w = m.group(2)
+        p = m.group(3) or ''
+        c = m.group(4)
+        if c == 's' and w:
+            w = str(int(w) - count_wide_chars(args[i]))
+            fmt = ''.join((fmt[:m.start()], '%', f, w, p, c, fmt[m.end():]))
+        i -= 1
+    return fmt % args
+
+
 # using format for most but leaving usage of template for the dynamic ones
 # two different methods are easy way to implement two phase construction
 HELP_TEXT = r"""GRASS GIS $VERSION_NUMBER
@@ -1796,7 +1834,7 @@ INFO_TEXT = r"""
 
 def show_info(shellname, grass_gui, default_gui):
     """Write basic info about GRASS GIS and GRASS session to stderr"""
-    sys.stderr.write(INFO_TEXT % (
+    sys.stderr.write(f(INFO_TEXT,
         _("GRASS GIS homepage:"),
         # GTC Running through: SHELL NAME
         _("This version running through:"),
@@ -1806,11 +1844,11 @@ def show_info(shellname, grass_gui, default_gui):
         _("See citation options with:")))
 
     if grass_gui == 'wxpython':
-        message("%-41sg.gui wxpython" % _("If required, restart the GUI with:"))
+        message(f("%-41sg.gui wxpython", _("If required, restart the GUI with:")))
     else:
-        message("%-41sg.gui %s" % (_("Start the GUI with:"), default_gui))
+        message(f("%-41sg.gui %s", _("Start the GUI with:"), default_gui))
 
-    message("%-41sexit" % _("When ready to quit enter:"))
+    message(f("%-41sexit", _("When ready to quit enter:")))
     message("")
 
 

+ 36 - 36
vector/v.info/print.c

@@ -7,7 +7,7 @@
 
 #include "local_proto.h"
 
-#define printline(x) fprintf (stdout, " | %-74.74s |\n", x)
+#define printline(x) G_faprintf (stdout, " | %-74.74s |\n", x)
 #define divider(x) \
         fprintf (stdout, " %c", x); \
         for (i = 0; i < 76; i++ ) \
@@ -314,41 +314,41 @@ void print_info(const struct Map_info *Map)
     }
 
     divider('+');
-    sprintf(line, "%-17s%s", _("Name:"),
+    G_saprintf(line, "%-17s%s", _("Name:"),
             Vect_get_name(Map));
     printline(line);
-    sprintf(line, "%-17s%s", _("Mapset:"),
+    G_saprintf(line, "%-17s%s", _("Mapset:"),
             Vect_get_mapset(Map));
     printline(line);
     
-    sprintf(line, "%-17s%s", _("Location:"),
+    G_saprintf(line, "%-17s%s", _("Location:"),
             G_location());
     printline(line);
-    sprintf(line, "%-17s%s", _("Database:"),
+    G_saprintf(line, "%-17s%s", _("Database:"),
             G_gisdbase());
     printline(line);
 
-    sprintf(line, "%-17s%s", _("Title:"),
+    G_saprintf(line, "%-17s%s", _("Title:"),
             Vect_get_map_name(Map));
     printline(line);
-    sprintf(line, "%-17s1:%d", _("Map scale:"),
+    G_saprintf(line, "%-17s1:%d", _("Map scale:"),
             Vect_get_scale(Map));
     printline(line);
 
-    sprintf(line, "%-17s%s", _("Name of creator:"),
+    G_saprintf(line, "%-17s%s", _("Name of creator:"),
             Vect_get_person(Map));
     printline(line);
-    sprintf(line, "%-17s%s", _("Organization:"),
+    G_saprintf(line, "%-17s%s", _("Organization:"),
             Vect_get_organization(Map));
     printline(line);
-    sprintf(line, "%-17s%s", _("Source date:"),
+    G_saprintf(line, "%-17s%s", _("Source date:"),
             Vect_get_map_date(Map));
     printline(line);
 
     /* This shows the TimeStamp (if present) */
     if (time_ok  == TRUE && (first_time_ok || second_time_ok)) {
         G_format_timestamp(&ts, timebuff);
-        sprintf(line, "%-17s%s", _("Timestamp (first layer): "), timebuff);
+        G_saprintf(line, "%-17s%s", _("Timestamp (first layer): "), timebuff);
         printline(line);
     }
     else {
@@ -360,18 +360,18 @@ void print_info(const struct Map_info *Map)
     
     if (map_type == GV_FORMAT_OGR ||
         map_type == GV_FORMAT_OGR_DIRECT) {
-        sprintf(line, "%-17s%s (%s)", _("Map format:"),
+        G_saprintf(line, "%-17s%s (%s)", _("Map format:"),
                 Vect_maptype_info(Map), Vect_get_finfo_format_info(Map));
         printline(line);
         
         /* for OGR format print also datasource and layer */
-        sprintf(line, "%-17s%s", _("OGR layer:"),
+        G_saprintf(line, "%-17s%s", _("OGR layer:"),
                 Vect_get_finfo_layer_name(Map));
         printline(line);
-        sprintf(line, "%-17s%s", _("OGR datasource:"),
+        G_saprintf(line, "%-17s%s", _("OGR datasource:"),
                 Vect_get_finfo_dsn_name(Map));
         printline(line);
-        sprintf(line, "%-17s%s", _("Feature type:"),
+        G_saprintf(line, "%-17s%s", _("Feature type:"),
                 Vect_get_finfo_geometry_type(Map));
         printline(line);
     }
@@ -384,23 +384,23 @@ void print_info(const struct Map_info *Map)
 
         finfo = Vect_get_finfo(Map);
         
-        sprintf(line, "%-17s%s (%s)", _("Map format:"),
+        G_saprintf(line, "%-17s%s (%s)", _("Map format:"),
                 Vect_maptype_info(Map), Vect_get_finfo_format_info(Map));
         printline(line);
         
         /* for PostGIS format print also datasource and layer */
-        sprintf(line, "%-17s%s", _("DB table:"),
+        G_saprintf(line, "%-17s%s", _("DB table:"),
                 Vect_get_finfo_layer_name(Map));
         printline(line);
-        sprintf(line, "%-17s%s", _("DB name:"),
+        G_saprintf(line, "%-17s%s", _("DB name:"),
                 Vect_get_finfo_dsn_name(Map));
         printline(line);
 
-        sprintf(line, "%-17s%s", _("Geometry column:"),
+        G_saprintf(line, "%-17s%s", _("Geometry column:"),
                 finfo->pg.geom_column);
         printline(line);
 
-        sprintf(line, "%-17s%s", _("Feature type:"),
+        G_saprintf(line, "%-17s%s", _("Feature type:"),
                 Vect_get_finfo_geometry_type(Map));
         printline(line);
 
@@ -410,21 +410,21 @@ void print_info(const struct Map_info *Map)
                                                    &toposchema_name, &topogeom_column,
                                                    &topo_geo_only);
         if (topo_format == GV_TOPO_POSTGIS) {
-            sprintf(line, "%-17s%s (%s %s%s)", _("Topology:"), "PostGIS",
+            G_saprintf(line, "%-17s%s (%s %s%s)", _("Topology:"), "PostGIS",
                     _("schema:"), toposchema_name,
                     topo_geo_only ? ", topo-geo-only: yes" : "");
             printline(line);
 
-            sprintf(line, "%-17s%s", _("Topology column:"),
+            G_saprintf(line, "%-17s%s", _("Topology column:"),
                     topogeom_column);
         }
         else
-            sprintf(line, "%-17s%s", _("Topology:"), "pseudo (simple features)");
+            G_saprintf(line, "%-17s%s", _("Topology:"), "pseudo (simple features)");
         
         printline(line);
     }
     else {
-        sprintf(line, "%-17s%s", _("Map format:"),
+        G_saprintf(line, "%-17s%s", _("Map format:"),
                 Vect_maptype_info(Map));
         printline(line);
     }
@@ -432,27 +432,27 @@ void print_info(const struct Map_info *Map)
 
     divider('|');
     
-    sprintf(line, "  %s: %s (%s: %i)",
+    G_saprintf(line, "  %s: %s (%s: %i)",
             _("Type of map"), _("vector"), _("level"), Vect_level(Map));
     printline(line);
     
     if (Vect_level(Map) > 0) {
         printline("");
-        sprintf(line,
+        G_saprintf(line,
                 "  %-24s%-9d       %-22s%-9d",
                 _("Number of points:"), 
                 Vect_get_num_primitives(Map, GV_POINT),
                 _("Number of centroids:"),
                 Vect_get_num_primitives(Map, GV_CENTROID));
         printline(line);
-        sprintf(line,
+        G_saprintf(line,
                 "  %-24s%-9d       %-22s%-9d",
                 _("Number of lines:"),
                 Vect_get_num_primitives(Map, GV_LINE),
                 _("Number of boundaries:"),
                 Vect_get_num_primitives(Map, GV_BOUNDARY));
         printline(line);
-        sprintf(line,
+        G_saprintf(line,
                 "  %-24s%-9d       %-22s%-9d",
                 _("Number of areas:"),
                 Vect_get_num_areas(Map),
@@ -460,14 +460,14 @@ void print_info(const struct Map_info *Map)
                 Vect_get_num_islands(Map));
         printline(line);
         if (Vect_is_3d(Map)) {
-            sprintf(line,
+            G_saprintf(line,
                     "  %-24s%-9d       %-22s%-9d",
                     _("Number of faces:"),
                     Vect_get_num_primitives(Map, GV_FACE),
                     _("Number of kernels:"),
                     Vect_get_num_primitives(Map, GV_KERNEL));
             printline(line);
-            sprintf(line,
+            G_saprintf(line,
                     "  %-24s%-9d       %-22s%-9d",
                     _("Number of volumes:"),
                     Vect_get_num_volumes(Map),
@@ -477,11 +477,11 @@ void print_info(const struct Map_info *Map)
         }
         printline("");
 
-        sprintf(line, "  %-24s%s",
+        G_saprintf(line, "  %-24s%s",
                 _("Map is 3D:"),
                 Vect_is_3d(Map) ? _("Yes") : _("No"));
         printline(line);
-        sprintf(line, "  %-24s%-9d",
+        G_saprintf(line, "  %-24s%-9d",
                 _("Number of dblinks:"),
                 Vect_get_num_dblinks(Map));
         printline(line);
@@ -501,12 +501,12 @@ void print_info(const struct Map_info *Map)
         else
             sprintf(tmp1, "%d", utm_zone);
 
-        sprintf(line, "  %s: %s (%s %s)",
+        G_saprintf(line, "  %s: %s (%s %s)",
                 _("Projection"), Vect_get_proj_name(Map),
                 _("zone"), tmp1);
     }
     else
-        sprintf(line, "  %s: %s",
+        G_saprintf(line, "  %s: %s",
                 _("Projection"), Vect_get_proj_name(Map));
 
     printline(line);
@@ -536,9 +536,9 @@ void print_info(const struct Map_info *Map)
     printline("");
 
     format_double(Vect_get_thresh(Map), tmp1);
-    sprintf(line, "  %s: %s", _("Digitization threshold"), tmp1);
+    G_saprintf(line, "  %s: %s", _("Digitization threshold"), tmp1);
     printline(line);
-    sprintf(line, "  %s:", _("Comment"));
+    G_saprintf(line, "  %s:", _("Comment"));
     printline(line);
     sprintf(line, "    %s", Vect_get_comment(Map));
     printline(line);