/*************************************************************************/
/* Copyright (c) 2004 */
/* Daniel Sleator, David Temperley, and John Lafferty */
/* Copyright (c) 2013 Linas Vepstas */
/* All rights reserved */
/* */
/* Use of the link grammar parsing system is subject to the terms of the */
/* license set forth in the LICENSE file included with this software. */
/* This license allows free redistribution and use in source and binary */
/* forms, with or without modification, subject to certain conditions. */
/* */
/*************************************************************************/
#include <limits.h>
#include <stdarg.h>
#include <stdbool.h>
#include <errno.h>
#include "print-util.h"
#include "utilities.h"
#include "wcwidth.h"
/**
* Return the width, in text-column-widths, of the utf8-encoded
* string. This is needed when printing formatted strings.
* European languages will typically have widths equal to the
* `mblen` value below (returned by mbsrtowcs); they occupy one
* column-width per code-point. The CJK ideographs occupy two
* column-widths per code-point. No clue about what happens for
* Arabic, or others. See wcwidth.c for details.
*/
size_t utf8_strwidth(const char *s)
{
size_t mblen;
#ifdef _WIN32
mblen = MultiByteToWideChar(CP_UTF8, 0, s, -1, NULL, 0) - 1;
#else
mblen = mbsrtowcs(NULL, &s, 0, NULL);
#endif
if ((int)mblen < 0)
{
prt_error("Warning: Error in utf8_strwidth(%s)\n", s);
return 1 /* XXX */;
}
wchar_t *ws = alloca((mblen + 1) * sizeof(wchar_t));
#ifdef _WIN32
MultiByteToWideChar(CP_UTF8, 0, s, -1, ws, mblen);
#else
mbstate_t mbss;
memset(&mbss, 0, sizeof(mbss));
mbsrtowcs(ws, &s, mblen, &mbss);
#endif /* _WIN32 */
int glyph_width = 0;
for (size_t i = 0; i < mblen; i++)
{
int w = mk_wcwidth(ws[i]);
// If w<0 then we do not know what the correct glyph
// width should be for this codepoint. Many terminals
// will print this with a weird boxed font that is two
// columns wide, showing the hex value in it.
if (w < 0) w = 2;
glyph_width += w;
}
return glyph_width;
}
/**
* Return the width, in text-column-widths, of the utf8-encoded
* character. The will return a NEGATIVE VALUE if the character
* width is not know (no glyph for a valid UTF-8 codepoint) or
* if the input is not a valid UTF-8 character!
*
* The mbstate_t argument is not used, since we convert only from utf-8.
* FIXME: This function (along with other places that use mbrtowc()) need
* to be fixed for Windows (utf-16 wchar_t).
* Use char32_t with mbrtoc32() instead of mbrtowc().
*/
int utf8_charwidth(const char *s)
{
wchar_t wc;
int n = mbrtowc(&wc, s, MB_LEN_MAX, NULL);
if (n == 0) return 0;
if (n < 0)
{
// prt_error("Error: charwidth(%s): mbrtowc() returned %d\n", s, n);
return -2 /* Yes, we want this! It signals the error! */;
}
return mk_wcwidth(wc);
}
/**
* Return the number of characters in the longest initial substring
* which has a text-column-width of not greater than max_width.
*/
size_t utf8_chars_in_width(const char *s, size_t max_width)
{
size_t total_bytes = 0;
size_t glyph_width = 0;
int n = 0;
wchar_t wc;
do
{
total_bytes += n;
n = mbrtowc(&wc, s+total_bytes, MB_LEN_MAX, NULL);
if (n == 0) break;
if (n < 0)
{
// Allow for double-column-wide box-font printing
// i.e. box with the hex code inside.
glyph_width += 2;
n = 1;
}
else
{
// If we are here, it was a valid UTF-8 code point,
// but we do not know the width of the corresponding
// glyph. Just like above, assume a double-wide box
// font will be printed.
int gw = mk_wcwidth(wc);
if (0 <= gw)
glyph_width += gw;
else
glyph_width += 2;
}
//printf("N %zu G %zu;", total_bytes, glyph_width);
}
while (glyph_width <= max_width);
// printf("\n");
return total_bytes;
}
/* ============================================================= */
/**
* Append to a dynamic string with vprintf-like formatting.
* @return The number of appended bytes, or a negative value on error.
*
* Note: As in the rest of the LG library, we assume here C99 library
* compliance (without it, this code would be buggy).
*/
int vappend_string(dyn_str * string, const char *fmt, va_list args)
{
#define TMPLEN 1024 /* Big enough for a possible error message, see below */
char temp_buffer[TMPLEN];
char *temp_string = temp_buffer;
int templen;
va_list copy_args;
va_copy(copy_args, args);
templen = vsnprintf(temp_string, TMPLEN, fmt, copy_args);
va_end(copy_args);
if (templen < 0) goto error;
if (0)
{
if (fmt[0] == '(') { errno=2; goto error;} /* Test the error reporting. */
}
if (templen >= TMPLEN)
{
/* TMPLEN is too small - use a bigger buffer. This may happen
* when printing dictionary words using !! with a wildcard
* or when debug-printing all the connectors. */
temp_string = malloc(templen+1);
templen = vsnprintf(temp_string, templen+1, fmt, args);
if (templen < 0)
{
free(temp_string);
goto error;
}
}
va_end(args);
patch_subscript_marks(temp_string);
dyn_strcat(string, temp_string);
if (templen >= TMPLEN) free(temp_string);
return templen;
error:
{
/* Some error has occurred */
const char msg[] = "[vappend_string(): ";
strcpy(temp_buffer, msg);
lg_strerror(errno, temp_buffer+sizeof(msg)-1, TMPLEN-sizeof(msg));
strcat(temp_buffer, "]");
dyn_strcat(string, temp_buffer);
va_end(args);
return templen;
}
}
/**
* Append to a dynamic string with printf-like formatting.
* @return The number of appended bytes, or a negative value on error.
*/
int append_string(dyn_str * string, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
return vappend_string(string, fmt, args);
}
/**
* Append exactly one UTF-8 character to the string.
* Return the number of bytes to advance, until the
* next UTF-8 character. This might NOT be the same as
* number of bytes actually appended. Two things might
* happen:
* a) Invalid UTF-8 values are copied, but only one byte,
* followed by an additional blank.
* b) Valid UTF-8 code-points that do not have a known
* glyph are copied, followed by an additional blank.
* This additional blanks allows proper printing of these
* two cases, by allowing the terminal to display with
* "box fonts" - boxes containing hex code, usually two
* column-widths wide.
*/
size_t append_utf8_char(dyn_str * string, const char * mbs)
{
/* Copy exactly one multi-byte character to buf */
char buf[12];
assert('\0' != *mbs, "Null string");
int nb = utf8_charlen(mbs);
int n = nb;
if (n < 0) n = 1; // charlen is negative if it's not a valid UTF-8
assert((size_t)n<sizeof(buf), "Multi-byte character is too long!");
memcpy(buf, mbs, n);
// Whitespace pad if its a bad value
if (nb < 0) { buf[n] = ' '; n++; }
// Whitespace pad if not a known UTF-8 glyph.
if (0 < nb && utf8_charwidth(mbs) < 0) { buf[n] = ' '; n++; }
// Null terminate.
buf[n] = 0;
dyn_strcat(string, buf);
// How many bytes did we hoover in?
n = nb;
if (n < 0) n = 1; // advance exactly one byte, if invalid UTF-8
return n;
}