1
0
mirror of https://git.zap.org.au/git/trader.git synced 2025-02-02 15:08:13 -05:00

Handle multibyte characters correctly in mkchstr()

Internal processing is now done in terms of wide characters (type
wchar_t).
This commit is contained in:
John Zaitseff 2011-08-19 16:52:27 +10:00
parent 74031a0415
commit 8a7dfcaf99
2 changed files with 395 additions and 198 deletions

View File

@ -44,25 +44,31 @@ typedef struct txwin {
// Declarations for argument processing in mkchstr() // Declarations for argument processing in mkchstr()
#define EILSEQ_REPL '?' // Illegal byte sequence replacement character
#define MAXFMTARGS 8 // Maximum number of positional arguments #define MAXFMTARGS 8 // Maximum number of positional arguments
enum argument_type { enum argument_type {
TYPE_NONE, // No type yet assigned TYPE_NONE, // No type yet assigned
TYPE_CHAR, // char TYPE_CHAR, // char
TYPE_WCHAR, // wchar_t
TYPE_INT, // int TYPE_INT, // int
TYPE_LONGINT, // long int TYPE_LONGINT, // long int
TYPE_DOUBLE, // double TYPE_DOUBLE, // double
TYPE_STRING // const char * TYPE_STRING, // const char *
TYPE_WSTRING // const wchar_t *
}; };
struct argument { struct argument {
enum argument_type a_type; enum argument_type a_type;
union a { union a {
char a_char; char a_char;
wchar_t a_wchar;
int a_int; int a_int;
long int a_longint; long int a_longint;
double a_double; double a_double;
const char *a_string; const char *a_string;
const wchar_t *a_wstring;
} a; } a;
}; };
@ -70,9 +76,9 @@ struct argument {
#define MAXFMTSPECS 16 // Maximum number of conversion specifiers #define MAXFMTSPECS 16 // Maximum number of conversion specifiers
struct convspec { struct convspec {
char spec; // Conversion specifier: c d f N s wchar_t spec; // Conversion specifier: c d f N s
int arg_num; // Which variable argument to use int arg_num; // Which variable argument to use
int len; // Length of conversion specifier, 0 = unused ptrdiff_t len; // Length of conversion specifier, 0 = unused
int precision; // Precision value int precision; // Precision value
bool flag_group; // Flag "'" (thousands grouping) bool flag_group; // Flag "'" (thousands grouping)
bool flag_nosym; // Flag "!" (omit currency symbol) bool flag_nosym; // Flag "!" (omit currency symbol)
@ -173,37 +179,39 @@ static void txresize (void);
/* /*
Function: mkchstr_addch - Add a character to the mkchstr buffer Function: mkchstr_add - Add one character to the mkchstr() buffers
Parameters: chbuf - Pointer to chtype pointer in which to store string Parameters: outbuf - Pointer to wchar_t pointer in which to store char
chbufsize - Pointer to number of chtype elements in chbuf attrbuf - Pointer to chtype pointer in which to store attr
count - Pointer to number of wchar_t elements left in outbuf
attr - Character rendition to use attr - Character rendition to use
maxlines - Maximum number of screen lines to use maxlines - Maximum number of screen lines to use
maxwidth - Maximum width of each line, in chars maxwidth - Maximum width of each line, in column positions
line - Pointer to current line number line - Pointer to current line number
width - Pointer to current line width width - Pointer to current line width
lastspc - Pointer to const char * pointer to last space lastspc - Pointer to wchar_t * pointer to last space
spcattr - Pointer to corresponding place in attrbuf
widthspc - Pointer to width just before last space widthspc - Pointer to width just before last space
widthbuf - Pointer to buffer to store widths of each line widthbuf - Pointer to buffer to store widths of each line
widthbufsize - Number of int elements in widthbuf widthbufsize - Number of int elements in widthbuf
str - Pointer to const char * pointer to string str - Pointer to const wchar_t * pointer to string
Returns: int - -1 on error (with errno set), 0 otherwise Returns: int - -1 on error (with errno set), 0 otherwise
This helper function adds the character **str to **chbuf, using attr as This helper function adds one wide character from **str to **outbuf,
the character rendition (attributes), incrementing both *str and *chbuf and the character rendition attr to **attrbuf, incrementing *str and
and decrementing *chbufsize. If a string is too long for the current *outbuf and decrementing *count. If a string is too long for the
line, a previous space in the current line is converted to a new line current line, a previous space in the current line is converted to a
(if possible), else a new line is inserted into the current location new line (if possible), else a new line is inserted into the current
(if not on the last line). *line, *width, *lastspc, *widthspc and location (if not on the last line). *line, *width, *lastspc, *widthspc
widthbuf[] are all updated appropriately. and widthbuf[] are all updated appropriately.
*/ */
static int mkchstr_addch (chtype *restrict *restrict chbuf, static int mkchstr_add (wchar_t *restrict *restrict outbuf,
int *restrict chbufsize, chtype attr, chtype *restrict *restrict attrbuf,
int maxlines, int maxwidth, int *restrict line, int *restrict count, chtype attr, int maxlines,
int *restrict width, int maxwidth, int *restrict line, int *restrict width,
chtype *restrict *restrict lastspc, wchar_t *restrict *restrict lastspc,
chtype *restrict *restrict spcattr,
int *restrict widthspc, int *restrict widthbuf, int *restrict widthspc, int *restrict widthbuf,
int widthbufsize, int widthbufsize, const wchar_t *restrict *restrict str);
const char *restrict *restrict str);
/* /*
@ -217,12 +225,27 @@ static int mkchstr_addch (chtype *restrict *restrict chbuf,
This helper function parses the format string passed to mkchstr(), This helper function parses the format string passed to mkchstr(),
setting the format_arg and format_spec arrays appropriately. setting the format_arg and format_spec arrays appropriately.
*/ */
static int mkchstr_parse (const char *restrict format, static int mkchstr_parse (const wchar_t *restrict format,
struct argument *restrict format_arg, struct argument *restrict format_arg,
struct convspec *restrict format_spec, struct convspec *restrict format_spec,
va_list args); va_list args);
/*
Function: mkchstr_conv - Convert (wcbuf, attrbuf) to chbuf
Parameters: chbuf - Pointer to chtype buffer in which to store string
chbufsize - Number of chtype elements in chbuf
wcbuf - Wide-character string from which to convert
attrbuf - Associated character rendition array
Returns: (nothing)
This helper function converts the wide-character string in wcbuf and
the array of character renditions in attrbuf to a chtype * string.
*/
static void mkchstr_conv (chtype *restrict chbuf, int chbufsize,
wchar_t *restrict wcbuf, chtype *restrict attrbuf);
/* /*
Function: txinput_fixup - Copy strings with fixup Function: txinput_fixup - Copy strings with fixup
Parameters: dest - Destination buffer of size BUFSIZE Parameters: dest - Destination buffer of size BUFSIZE
@ -649,15 +672,19 @@ int mkchstr (chtype *restrict chbuf, int chbufsize, chtype attr_norm,
/***********************************************************************/ /***********************************************************************/
// mkchstr_addch: Add a character to the mkchstr buffer // mkchstr_add: Add a character to the mkchstr buffer
int mkchstr_addch (chtype *restrict *restrict chbuf, int *restrict chbufsize, int mkchstr_add (wchar_t *restrict *restrict outbuf,
chtype attr, int maxlines, int maxwidth, chtype *restrict *restrict attrbuf, int *restrict count,
int *restrict line, int *restrict width, chtype attr, int maxlines, int maxwidth, int *restrict line,
chtype *restrict *restrict lastspc, int *restrict widthspc, int *restrict width, wchar_t *restrict *restrict lastspc,
chtype *restrict *restrict spcattr, int *restrict widthspc,
int *restrict widthbuf, int widthbufsize, int *restrict widthbuf, int widthbufsize,
const char *restrict *restrict str) const wchar_t *restrict *restrict str)
{ {
int w, wspc;
if (*line < 0) { if (*line < 0) {
// First character in buffer: start line 0 // First character in buffer: start line 0
*line = 0; *line = 0;
@ -667,67 +694,88 @@ int mkchstr_addch (chtype *restrict *restrict chbuf, int *restrict chbufsize,
// Start a new line // Start a new line
if (*line < maxlines - 1) { if (*line < maxlines - 1) {
*(*chbuf)++ = '\n'; *(*outbuf)++ = '\n';
(*chbufsize)--; *(*attrbuf)++ = 0;
(*count)--;
} }
widthbuf[*line] = *width; widthbuf[*line] = *width;
*width = 0; *width = 0;
*lastspc = NULL; *lastspc = NULL;
*spcattr = NULL;
*widthspc = 0; *widthspc = 0;
(*line)++; (*line)++;
(*str)++; (*str)++;
} else if (*width == maxwidth) { } else {
// Current line is now too long w = wcwidth(**str);
if (w < 0) {
// We don't support control or non-printable characters
errno = EILSEQ;
return -1;
}
if (! isspace(**str) && *lastspc != NULL && *line < maxlines - 1) { if (*width + w > maxwidth) {
// Current line would be too long to fit in **str
if (! iswspace(**str) && *lastspc != NULL && *line < maxlines - 1) {
// Break on the last space in this line // Break on the last space in this line
wspc = wcwidth(**lastspc);
**lastspc = '\n'; **lastspc = '\n';
**spcattr = 0;
widthbuf[*line] = *widthspc; widthbuf[*line] = *widthspc;
*width -= *widthspc + 1; *width -= *widthspc + wspc;
*lastspc = NULL; *lastspc = NULL;
*spcattr = NULL;
*widthspc = 0; *widthspc = 0;
(*line)++; (*line)++;
} else { } else {
// Insert a new-line character (if not on last line) // Insert a new-line character (if not on last line)
if (*line < maxlines - 1) { if (*line < maxlines - 1) {
*(*chbuf)++ = '\n'; *(*outbuf)++ = '\n';
(*chbufsize)--; *(*attrbuf)++ = 0;
(*count)--;
} }
widthbuf[*line] = *width; widthbuf[*line] = *width;
*width = 0; *width = 0;
*lastspc = NULL; *lastspc = NULL;
*spcattr = NULL;
*widthspc = 0; *widthspc = 0;
(*line)++; (*line)++;
// Skip any following spaces /* Skip any following spaces. This assumes that no-one
while (isspace(**str)) { will ever have combining diacritical marks following a
(line-breaking) space! */
while (iswspace(**str)) {
if (*(*str)++ == '\n') { if (*(*str)++ == '\n') {
break; break;
} }
} }
} }
} else { } else {
// Insert an ordinary character into the output string // Insert an ordinary character into the output buffer
if (isspace(**str)) { if (iswspace(**str)) {
*lastspc = *chbuf; *lastspc = *outbuf;
*spcattr = *attrbuf;
*widthspc = *width; *widthspc = *width;
} }
*(*chbuf)++ = (unsigned char) **str | attr; *(*outbuf)++ = **str;
(*chbufsize)--; *(*attrbuf)++ = attr;
(*width)++; (*count)--;
*width += w;
(*str)++; (*str)++;
} }
}
return 0; return 0;
} }
@ -736,7 +784,7 @@ int mkchstr_addch (chtype *restrict *restrict chbuf, int *restrict chbufsize,
/***********************************************************************/ /***********************************************************************/
// mkchstr_parse: Parse the format string for mkchstr() // mkchstr_parse: Parse the format string for mkchstr()
int mkchstr_parse (const char *restrict format, int mkchstr_parse (const wchar_t *restrict format,
struct argument *restrict format_arg, struct argument *restrict format_arg,
struct convspec *restrict format_spec, va_list args) struct convspec *restrict format_spec, va_list args)
{ {
@ -770,7 +818,7 @@ int mkchstr_parse (const char *restrict format,
// Ignore "%%" specifier // Ignore "%%" specifier
format++; format++;
} else { } else {
const char *start = format; const wchar_t *start = format;
enum argument_type arg_type; enum argument_type arg_type;
bool inspec = true; bool inspec = true;
bool flag_posn = false; // Have we already seen "$"? bool flag_posn = false; // Have we already seen "$"?
@ -778,8 +826,8 @@ int mkchstr_parse (const char *restrict format,
int count = 0; int count = 0;
while (inspec && *format != '\0') { while (inspec && *format != '\0') {
char c = *format++; wchar_t wc = *format++;
switch (c) { switch (wc) {
case '0': case '0':
// Zero flag, or part of numeric count // Zero flag, or part of numeric count
if (count == 0) { if (count == 0) {
@ -801,7 +849,7 @@ int mkchstr_parse (const char *restrict format,
case '8': case '8':
case '9': case '9':
// Part of some numeric count // Part of some numeric count
count = count * 10 + (c - '0'); count = count * 10 + (wc - '0');
break; break;
case '$': case '$':
@ -867,15 +915,15 @@ int mkchstr_parse (const char *restrict format,
break; break;
case 'c': case 'c':
// Insert a character (char) // Insert a character (char or wchar_t)
if (format_spec->flag_group || format_spec->flag_nosym if (format_spec->flag_group || format_spec->flag_nosym
|| format_spec->flag_prec || format_spec->flag_long || format_spec->flag_prec || count != 0) {
|| count != 0) {
errno = EINVAL; errno = EINVAL;
return -1; return -1;
} }
arg_type = TYPE_CHAR; arg_type = format_spec->flag_long ?
TYPE_WCHAR : TYPE_CHAR;
goto handlefmt; goto handlefmt;
case 'd': case 'd':
@ -915,15 +963,15 @@ int mkchstr_parse (const char *restrict format,
goto handlefmt; goto handlefmt;
case 's': case 's':
// Insert a string (const char *) // Insert a string (const char * or const wchar_t *)
if (format_spec->flag_group || format_spec->flag_nosym if (format_spec->flag_group || format_spec->flag_nosym
|| format_spec->flag_prec || format_spec->flag_long || format_spec->flag_prec || count != 0) {
|| count != 0) {
errno = EINVAL; errno = EINVAL;
return -1; return -1;
} }
arg_type = TYPE_STRING; arg_type = format_spec->flag_long ?
TYPE_WSTRING : TYPE_STRING;
handlefmt: handlefmt:
if (arg_num >= MAXFMTARGS || specs_left == 0) { if (arg_num >= MAXFMTARGS || specs_left == 0) {
@ -940,7 +988,7 @@ int mkchstr_parse (const char *restrict format,
format_spec->len = format - start; format_spec->len = format - start;
format_spec->arg_num = arg_num; format_spec->arg_num = arg_num;
format_spec->spec = c; format_spec->spec = wc;
arg_num++; arg_num++;
num_args = MAX(num_args, arg_num); num_args = MAX(num_args, arg_num);
@ -975,6 +1023,10 @@ int mkchstr_parse (const char *restrict format,
format_arg->a.a_char = (char) va_arg(args, int); format_arg->a.a_char = (char) va_arg(args, int);
break; break;
case TYPE_WCHAR:
format_arg->a.a_wchar = va_arg(args, wchar_t);
break;
case TYPE_INT: case TYPE_INT:
format_arg->a.a_int = va_arg(args, int); format_arg->a.a_int = va_arg(args, int);
break; break;
@ -991,6 +1043,10 @@ int mkchstr_parse (const char *restrict format,
format_arg->a.a_string = va_arg(args, const char *); format_arg->a.a_string = va_arg(args, const char *);
break; break;
case TYPE_WSTRING:
format_arg->a.a_wstring = va_arg(args, const wchar_t *);
break;
default: default:
/* Cannot allow unused arguments, as we have no way of /* Cannot allow unused arguments, as we have no way of
knowing how much space they take (cf. int vs. long long knowing how much space they take (cf. int vs. long long
@ -1004,6 +1060,77 @@ int mkchstr_parse (const char *restrict format,
} }
/***********************************************************************/
// mkchstr_conv: Convert (wcbuf, attrbuf) to chbuf
void mkchstr_conv (chtype *restrict chbuf, int chbufsize,
wchar_t *restrict wcbuf, chtype *restrict attrbuf)
{
char *convbuf = xmalloc(chbufsize);
mbstate_t mbstate;
wchar_t *wp;
char *p;
bool done;
size_t n;
/* Perform a preliminary conversion to weed out any problems with
EILSEQ and insufficient buffer space. */
while (true) {
memset(&mbstate, 0, sizeof(mbstate));
wp = wcbuf;
if (wcsrtombs(convbuf, (const wchar_t **) &wp, chbufsize, &mbstate)
== (size_t) -1) {
if (errno == EILSEQ) {
/* Replace problematic wide characters with a known-good
(ASCII) one. This is better than terminating! */
*wp = EILSEQ_REPL;
} else {
errno_exit("mkchstr_conv: `%ls'", wcbuf);
}
} else if (wp != NULL) {
// convbuf is too small: truncate wcbuf if possible
if (wp == wcbuf) {
errno = E2BIG;
errno_exit("mkchstr_conv: `%ls'", wcbuf);
} else {
*(wp - 1) = '\0';
}
} else {
// wcbuf CAN fit into convbuf when converted
break;
}
}
// Convert for real, combining each multibyte character with attrbuf
memset(&mbstate, 0, sizeof(mbstate));
done = false;
while (! done) {
// Yes, we want to convert a wide NUL, too!
if ((n = wcrtomb(convbuf, *wcbuf, &mbstate)) == (size_t) -1) {
errno_exit("mkchstr_conv: `%ls'", wcbuf);
}
for (p = convbuf; n > 0; n--, p++, chbuf++) {
if (*p == '\0' || *p == '\n') {
/* This code assumes '\n' can never appear in a multibyte
string except as a control character---which is true
of all multibyte encodings (I believe!) */
*chbuf = (unsigned char) *p;
} else {
*chbuf = (unsigned char) *p | *attrbuf;
}
}
done = (*wcbuf == '\0');
wcbuf++;
attrbuf++;
}
free(convbuf);
}
/***********************************************************************/ /***********************************************************************/
// vmkchstr: Prepare a string for printing to screen // vmkchstr: Prepare a string for printing to screen
@ -1012,12 +1139,20 @@ int vmkchstr (chtype *restrict chbuf, int chbufsize, chtype attr_norm,
int *restrict widthbuf, int widthbufsize, int *restrict widthbuf, int widthbufsize,
const char *restrict format, va_list args) const char *restrict format, va_list args)
{ {
const char *orig_format = format;
struct argument format_arg[MAXFMTARGS]; struct argument format_arg[MAXFMTARGS];
struct convspec format_spec[MAXFMTSPECS]; struct convspec format_spec[MAXFMTSPECS];
struct convspec *spec; struct convspec *spec;
int line, width; const wchar_t *wcformat;
chtype *lastspc; wchar_t *orig_wcformat;
mbstate_t mbstate;
wchar_t *outbuf, *orig_outbuf;
chtype *attrbuf, *orig_attrbuf;
wchar_t *fmtbuf;
int count, line, width;
wchar_t *lastspc;
chtype *spcattr;
int widthspc; int widthspc;
chtype curattr; chtype curattr;
int saved_errno; int saved_errno;
@ -1025,55 +1160,78 @@ int vmkchstr (chtype *restrict chbuf, int chbufsize, chtype attr_norm,
assert(chbuf != NULL); assert(chbuf != NULL);
assert(chbufsize > 0); assert(chbufsize > 0);
assert(chbufsize <= BUFSIZE);
assert(maxlines > 0); assert(maxlines > 0);
assert(maxwidth > 0); assert(maxwidth > 0);
assert(widthbuf != NULL); assert(widthbuf != NULL);
assert(widthbufsize >= maxlines); assert(widthbufsize >= maxlines);
assert(format != NULL); assert(format != NULL);
if (mkchstr_parse(format, format_arg, format_spec, args) < 0) { outbuf = orig_outbuf = xmalloc(BUFSIZE * sizeof(wchar_t));
attrbuf = orig_attrbuf = xmalloc(BUFSIZE * sizeof(chtype));
wcformat = orig_wcformat = xmalloc(chbufsize * sizeof(wchar_t));
fmtbuf = xmalloc(BUFSIZE * sizeof(wchar_t));
// Convert format to a wide-character string
{
memset(&mbstate, 0, sizeof(mbstate));
const char *p = format;
if (mbsrtowcs(orig_wcformat, &p, BUFSIZE, &mbstate) == (size_t) -1) {
goto error;
} else if (p != NULL) {
errno = E2BIG;
goto error; goto error;
} }
}
if (mkchstr_parse(wcformat, format_arg, format_spec, args) < 0) {
goto error;
}
// Construct the (outbuf, attrbuf) pair of arrays
spec = format_spec; spec = format_spec;
curattr = attr_norm; curattr = attr_norm;
count = BUFSIZE; // Space left in outbuf
line = -1; // Current line number (0 = first) line = -1; // Current line number (0 = first)
width = 0; // Width of the current line width = 0; // Width of the current line
lastspc = NULL; // Pointer to last space in line lastspc = NULL; // Pointer to last space in line
spcattr = NULL; // Equivalent in attrbuf
widthspc = 0; // Width of line before last space widthspc = 0; // Width of line before last space
while (*format != '\0' && chbufsize > 1 && line < maxlines) { while (*wcformat != '\0' && count > 1 && line < maxlines) {
switch (*format) { switch (*wcformat) {
case '^': case '^':
// Switch to a different character rendition // Switch to a different character rendition
if (*++format == '\0') { if (*++wcformat == '\0') {
goto error_inval; goto error_inval;
} else { } else {
switch (*format) { switch (*wcformat) {
case '^': case '^':
if (mkchstr_addch(&chbuf, &chbufsize, curattr, maxlines, if (mkchstr_add(&outbuf, &attrbuf, &count, curattr,
maxwidth, &line, &width, &lastspc, maxlines, maxwidth, &line, &width,
&widthspc, widthbuf, widthbufsize, &lastspc, &spcattr, &widthspc, widthbuf,
&format) < 0) { widthbufsize, &wcformat) < 0) {
goto error; goto error;
} }
break; break;
case '{': case '{':
curattr = attr_alt1; curattr = attr_alt1;
format++; wcformat++;
break; break;
case '[': case '[':
curattr = attr_alt2; curattr = attr_alt2;
format++; wcformat++;
break; break;
case '}': case '}':
case ']': case ']':
curattr = attr_norm; curattr = attr_norm;
format++; wcformat++;
break; break;
default: default:
@ -1084,86 +1242,82 @@ int vmkchstr (chtype *restrict chbuf, int chbufsize, chtype attr_norm,
case '%': case '%':
// Process a conversion specifier // Process a conversion specifier
if (*++format == '\0') { if (*++wcformat == '\0') {
goto error_inval; goto error_inval;
} else if (*format == '%') { } else if (*wcformat == '%') {
if (mkchstr_addch(&chbuf, &chbufsize, curattr, maxlines, if (mkchstr_add(&outbuf, &attrbuf, &count, curattr, maxlines,
maxwidth, &line, &width, &lastspc, &widthspc, maxwidth, &line, &width, &lastspc, &spcattr,
widthbuf, widthbufsize, &format) < 0) { &widthspc, widthbuf, widthbufsize, &wcformat)
< 0) {
goto error; goto error;
} }
} else { } else {
assert(spec->len != 0); assert(spec->len != 0);
const wchar_t *str;
const char *str; wint_t wc;
char *buf = xmalloc(BUFSIZE);
switch (spec->spec) { switch (spec->spec) {
case 'c': case 'c':
// Insert a character (char) into the output // Insert a character (char or wchar_t) into the output
if (snprintf(buf, BUFSIZE, "%c", if (spec->flag_long) {
format_arg[spec->arg_num].a.a_char) < 0) { wc = format_arg[spec->arg_num].a.a_wchar;
saved_errno = errno; } else {
free(buf); wc = btowc(format_arg[spec->arg_num].a.a_char);
errno = saved_errno; }
if (wc == '\0' || wc == WEOF) {
errno = EILSEQ;
goto error; goto error;
} }
str = buf; fmtbuf[0] = wc;
fmtbuf[1] = '\0';
str = fmtbuf;
goto insertstr; goto insertstr;
case 'd': case 'd':
// Insert an integer (int or long int) into the output // Insert an integer (int or long int) into the output
if (spec->flag_long) { if (spec->flag_long) {
if (snprintf(buf, BUFSIZE, spec->flag_group ? if (swprintf(fmtbuf, BUFSIZE, spec->flag_group ?
"%'ld" : "%ld", L"%'ld" : L"%ld",
format_arg[spec->arg_num].a.a_longint) < 0) { format_arg[spec->arg_num].a.a_longint) < 0)
saved_errno = errno;
free(buf);
errno = saved_errno;
goto error; goto error;
}
} else { } else {
if (snprintf(buf, BUFSIZE, spec->flag_group ? if (swprintf(fmtbuf, BUFSIZE, spec->flag_group ?
"%'d" : "%d", L"%'d" : L"%d",
format_arg[spec->arg_num].a.a_int) < 0) { format_arg[spec->arg_num].a.a_int) < 0)
saved_errno = errno;
free(buf);
errno = saved_errno;
goto error; goto error;
} }
}
str = buf; str = fmtbuf;
goto insertstr; goto insertstr;
case 'f': case 'f':
// Insert a floating-point number (double) into the output // Insert a floating-point number (double) into the output
if (spec->flag_prec) { if (spec->flag_prec) {
if (snprintf(buf, BUFSIZE, spec->flag_group ? if (swprintf(fmtbuf, BUFSIZE, spec->flag_group ?
"%'.*f" : "%.*f", spec->precision, L"%'.*f" : L"%.*f", spec->precision,
format_arg[spec->arg_num].a.a_double) < 0) { format_arg[spec->arg_num].a.a_double) < 0)
saved_errno = errno;
free(buf);
errno = saved_errno;
goto error; goto error;
}
} else { } else {
if (snprintf(buf, BUFSIZE, spec->flag_group ? if (swprintf(fmtbuf, BUFSIZE, spec->flag_group ?
"%'f" : "%f", L"%'f" : L"%f",
format_arg[spec->arg_num].a.a_double) < 0) { format_arg[spec->arg_num].a.a_double) < 0)
saved_errno = errno;
free(buf);
errno = saved_errno;
goto error; goto error;
} }
}
str = buf; str = fmtbuf;
goto insertstr; goto insertstr;
case 'N': case 'N':
// Insert a monetary amount (double) into the output // Insert a monetary amount (double) into the output
{
/* strfmon() is not available in a wide-char
version, so we need a multibyte char buffer */
char *buf = xmalloc(BUFSIZE);
const char *p = buf;
if (l_strfmon(buf, BUFSIZE, spec->flag_nosym ? "%!n" : "%n", if (l_strfmon(buf, BUFSIZE, spec->flag_nosym ? "%!n" : "%n",
format_arg[spec->arg_num].a.a_double) < 0) { format_arg[spec->arg_num].a.a_double) < 0) {
saved_errno = errno; saved_errno = errno;
@ -1172,54 +1326,83 @@ int vmkchstr (chtype *restrict chbuf, int chbufsize, chtype attr_norm,
goto error; goto error;
} }
str = buf; memset(&mbstate, 0, sizeof(mbstate));
goto insertstr; if (mbsrtowcs(fmtbuf, &p, BUFSIZE, &mbstate)
== (size_t) -1) {
case 's':
// Insert a string (const char *) into the output
str = format_arg[spec->arg_num].a.a_string;
if (str == NULL) {
str = "(null)"; // As per GNU printf()
}
insertstr:
// Insert the string pointed to by str
while (*str != '\0' && chbufsize > 1 && line < maxlines) {
if (mkchstr_addch(&chbuf, &chbufsize, curattr,
maxlines, maxwidth, &line, &width,
&lastspc, &widthspc, widthbuf,
widthbufsize, &str) < 0) {
saved_errno = errno; saved_errno = errno;
free(buf); free(buf);
errno = saved_errno; errno = saved_errno;
goto error; goto error;
} else if (p != NULL) {
free(buf);
errno = E2BIG;
goto error;
}
free(buf);
}
str = fmtbuf;
goto insertstr;
case 's':
// Insert a string (const char * or const wchar_t *)
if (spec->flag_long) {
str = format_arg[spec->arg_num].a.a_wstring;
} else {
const char *p = format_arg[spec->arg_num].a.a_string;
if (p == NULL) {
str = NULL;
} else {
memset(&mbstate, 0, sizeof(mbstate));
if (mbsrtowcs(fmtbuf, &p, BUFSIZE, &mbstate)
== (size_t) -1) {
goto error;
} else if (p != NULL) {
errno = E2BIG;
goto error;
}
str = fmtbuf;
} }
} }
format += spec->len; if (str == NULL) {
str = L"(null)"; // As per GNU printf()
}
insertstr:
// Insert the string pointed to by str
while (*str != '\0' && count > 1 && line < maxlines) {
if (mkchstr_add(&outbuf, &attrbuf, &count, curattr,
maxlines, maxwidth, &line, &width,
&lastspc, &spcattr, &widthspc,
widthbuf, widthbufsize, &str) < 0) {
goto error;
}
}
wcformat += spec->len;
spec++; spec++;
break; break;
default: default:
assert(spec->spec); assert(spec->spec);
} }
free(buf);
} }
break; break;
default: default:
// Process an ordinary character (including new-line) // Process an ordinary character (including new-line)
if (mkchstr_addch(&chbuf, &chbufsize, curattr, maxlines, maxwidth, if (mkchstr_add(&outbuf, &attrbuf, &count, curattr, maxlines,
&line, &width, &lastspc, &widthspc, widthbuf, maxwidth, &line, &width, &lastspc, &spcattr,
widthbufsize, &format) < 0) { &widthspc, widthbuf, widthbufsize, &wcformat) < 0) {
goto error; goto error;
} }
} }
} }
*chbuf = 0; // Terminating NUL byte *outbuf = '\0'; // Terminating NUL character
*attrbuf = 0;
if (line >= 0 && line < maxlines) { if (line >= 0 && line < maxlines) {
widthbuf[line] = width; widthbuf[line] = width;
@ -1227,6 +1410,14 @@ int vmkchstr (chtype *restrict chbuf, int chbufsize, chtype attr_norm,
line = maxlines - 1; line = maxlines - 1;
} }
// Convert the (outbuf, attrbuf) pair of arrays to chbuf
mkchstr_conv(chbuf, chbufsize, orig_outbuf, orig_attrbuf);
free(fmtbuf);
free(orig_wcformat);
free(orig_attrbuf);
free(orig_outbuf);
return line + 1; return line + 1;
@ -1234,7 +1425,14 @@ error_inval:
errno = EINVAL; errno = EINVAL;
error: error:
errno_exit(_("mkchstr: `%s'"), orig_format); saved_errno = errno;
free(fmtbuf);
free(orig_wcformat);
free(orig_attrbuf);
free(orig_outbuf);
errno = saved_errno;
errno_exit(_("mkchstr: `%s'"), format);
} }

View File

@ -43,8 +43,7 @@
/* /*
This version of Star Traders only utilises WIN_COLS x WIN_LINES of a This version of Star Traders only utilises WIN_COLS x WIN_LINES of a
terminal screen; this terminal must be at least MIN_COLS x MIN_LINES in terminal screen; this terminal must be at least MIN_COLS x MIN_LINES in
size. The newtxwin() function automatically places a new window in the size. Windows are placed in the centre-top of the terminal screen.
centre-top of the terminal screen.
*/ */
#define MIN_LINES 24 // Minimum number of lines in terminal #define MIN_LINES 24 // Minimum number of lines in terminal
@ -57,6 +56,7 @@
#define MAX_DLG_LINES 10 // Default maximum lines of text in dialog box #define MAX_DLG_LINES 10 // Default maximum lines of text in dialog box
// Space (number of terminal columns) to allow for various fields
#define YESNO_COLS 4 // Space to allow for "Yes" or "No" response #define YESNO_COLS 4 // Space to allow for "Yes" or "No" response
#define ORDINAL_COLS 5 // Space for ordinals (1st, 2nd, etc) #define ORDINAL_COLS 5 // Space for ordinals (1st, 2nd, etc)
#define TOTAL_VALUE_COLS 18 // Space for total value (monetary) #define TOTAL_VALUE_COLS 18 // Space for total value (monetary)
@ -288,13 +288,13 @@ extern int txrefresh (void);
alt2_attr - Alternate character rendition 2 (more highlighted) alt2_attr - Alternate character rendition 2 (more highlighted)
keywait_attr - "Press any key" character rendition keywait_attr - "Press any key" character rendition
boxtitle - Dialog box title (may be NULL) boxtitle - Dialog box title (may be NULL)
format - Dialog box text, as passed to prepstr() format - Dialog box text, as passed to mkchstr()
... - Dialog box text format parameters ... - Dialog box text format parameters
Returns: int - OK is always returned Returns: int - OK is always returned
This function creates a dialog box window using newtxwin(), displays This function creates a dialog box window using newtxwin(), displays
boxtitle centred on the first line (if boxtitle is not NULL), displays boxtitle centred on the first line (if boxtitle is not NULL), displays
format (and associated parameters) centred using prepstr(), then waits format (and associated parameters) centred using mkchstr(), then waits
for the user to press any key before closing the dialog box window. for the user to press any key before closing the dialog box window.
Note that txrefresh() is NOT called once the window is closed. Note that txrefresh() is NOT called once the window is closed.
*/ */
@ -330,10 +330,12 @@ extern int txdlgbox (int maxlines, int ncols, int begin_y, int begin_x,
The format string is similar to but more limited than printf(). In The format string is similar to but more limited than printf(). In
particular, only the following conversion specifiers are understood: particular, only the following conversion specifiers are understood:
%% - Print the ASCII percent sign (ASCII code U+0025) %% - Insert the ASCII percent sign (ASCII code U+0025)
%c - Insert the next parameter as a character (type char) %c - Insert the next parameter as a character (type char)
%lc - Insert the next parameter as a wide char (type wchar_t)
%s - Insert the next parameter as a string (type char *) %s - Insert the next parameter as a string (type char *)
%ls - Insert the next parameter as a wide string (type wchar_t *)
%d - Insert the next parameter as an integer (type int) %d - Insert the next parameter as an integer (type int)
%'d - As above, using the locale's thousands group separator %'d - As above, using the locale's thousands group separator
%ld - Insert the next parameter as a long int %ld - Insert the next parameter as a long int
@ -360,19 +362,16 @@ extern int txdlgbox (int maxlines, int ncols, int begin_y, int begin_x,
rendition flags are understood, where the "^" character is a literal rendition flags are understood, where the "^" character is a literal
ASCII circumflex accent: ASCII circumflex accent:
^^ - Print the circumflex accent (ASCII code U+005E) ^^ - Insert the circumflex accent (ASCII code U+005E)
^{ - Switch to using attr_alt1 character rendition (alternate mode 1) ^{ - Switch to using attr_alt1 character rendition (alternate mode 1)
^} - Switch to using attr_norm character rendition ^} - Switch to using attr_norm character rendition
^[ - Switch to using attr_alt2 character rendition (alternate mode 2) ^[ - Switch to using attr_alt2 character rendition (alternate mode 2)
^] - Switch to using attr_norm character rendition ^] - Switch to using attr_norm character rendition
Characters other than these are inserted as literals, except that '\n' Printable characters other than these are inserted as literals. The
will force the start of a new line. By default, attr_norm is used as character '\n' will force the start of a new line; no other control (or
the character rendition (attributes). non-printable) characters are allowed. By default, attr_norm is used
as the character rendition (attributes).
Please note that this function does NOT handle multibyte characters
correctly: widths may be incorrect (byte count, not actual width) and
multibyte characters may be split over two lines.
This function returns the actual number of lines used (from 0 to This function returns the actual number of lines used (from 0 to
maxlines). If an error is detected, the application terminates. maxlines). If an error is detected, the application terminates.