123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525 |
- /*!
- * \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) {
- 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;
- }
|