/*! * \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 #include #include #include #include #include /* 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) { va_list ap_copy; char tmp; /* copy ap for ovprintf() */ va_copy(ap_copy, ap); /* 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; *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; } } else { /* else use ovprintf() for non-string specifiers */ tmp = *(q + 1); *(q + 1) = 0; nbytes += ovprintf(opts, p, ap_copy); *(q + 1) = tmp; /* once ap is passed to another function that calls * va_arg() on it, its value becomes undefined * (printf(3) man page) or indeterminate * (http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf * section 7.15 paragraph 3) after the callee function * returns; simply passing ap to ovprintf() works on * Linux, but it doesn't on MinGW on Windows; pass its * copy and skip an argument manually; argument types * from printf(3) man page */ switch (*c) { case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': case 'c': case 'C': case 'S': va_arg(ap, int); break; case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': case 'a': case 'A': va_arg(ap, double); break; case 'p': va_arg(ap, void *); break; case 'n': va_arg(ap, int *); break; /* otherwise, no argument is required for m% */ } } va_end(ap_copy); 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; }