diff --git a/doc/man/man3/notcurses_metric.3.md b/doc/man/man3/notcurses_metric.3.md index c684327d2..0257a71e3 100644 --- a/doc/man/man3/notcurses_metric.3.md +++ b/doc/man/man3/notcurses_metric.3.md @@ -33,12 +33,12 @@ notcurses_metric - fixed-width numeric output with metric suffixes # DESCRIPTION -**ncmetric** (and the helper wrappers **qprefix** and **bprefix**) accept -very large (or very small) non-negative numbers, and prepare formatted output +**ncnmetric** (and the helper wrappers **nc[qib]prefix**) accept +very large (or very small) non-negative integers, and prepare formatted output of a maximum width using metric suffixes. The suffix can represent arbitrary amounts of growth, but is designed for 1000 (**NCPREFIX**) or 1024 (**NCIPREFIX**). 1024 is used for "digital units of information", i.e. kibibytes -and gibibits. **ncmetric** supports the large suffixes KMGTPEZY (Kilo, Mega, +and gibibits. **ncnmetric** supports the large suffixes KMGTPEZY (Kilo, Mega, Giga, Tera, Peta, Exa, Zetta, and Yotta) and the small suffixes mµnpfazy (Milli, Micro, Nano, Pico, Femto, Atto, Zepto, and Yocto). This covers the range 1e24 (one septillion) through 1e-24, sufficing for all possible values of @@ -62,19 +62,19 @@ Three helper functions are provided to simplify these common cases: // Mega, kilo, gigafoo. Use NCPREFIXSTRLEN + 1 and NCPREFIXCOLUMNS. static inline const char* ncqprefix(uintmax_t val, uintmax_t decimal, char* buf, int omitdec){ - return ncmetric(val, decimal, buf, omitdec, 1000, '\0'); + return ncnmetric(val, decimal, buf, omitdec, 1000, '\0'); } // Mibi, kebi, gibibytes sans 'i' suffix. Use NCIPREFIXSTRLEN + 1. static inline const char* nciprefix(uintmax_t val, uintmax_t decimal, char* buf, int omitdec){ - return ncmetric(val, decimal, buf, omitdec, 1024, '\0'); + return ncnmetric(val, decimal, buf, omitdec, 1024, '\0'); } // Mibi, kebi, gibibytes. Use NCBPREFIXSTRLEN + 1 and NCBPREFIXCOLUMNS. static inline const char* ncbprefix(uintmax_t val, uintmax_t decimal, char* buf, int omitdec){ - return ncmetric(val, decimal, buf, omitdec, 1024, 'i'); + return ncnmetric(val, decimal, buf, omitdec, 1024, 'i'); } ``` @@ -101,7 +101,7 @@ suffix, and u is the ***uprefix***. The minimum-width output will take the form single-column value such as 5 is passed for ***val***. Three more defines are provided to simplify formatted fixed-width output using -the results of **ncmetric**. Each of these macros accepts a character buffer +the results of **ncnmetric**. Each of these macros accepts a character buffer holding the result of the call, and expand to *two* arguments: * **NCPREFIXFMT(x)** @@ -116,7 +116,8 @@ to ensure that the output is always **NCPREFIXCOLUMNS** wide. # RETURN VALUES -**NULL** if input parameters were invalid. Otherwise, a pointer to ***buf***, +**NULL** if input parameters were invalid, or if **notcurses_init** +has not been successfully called. Otherwise, a pointer to ***buf***, filled in with the formatted output. # EXAMPLES @@ -139,7 +140,13 @@ filled in with the formatted output. # BUGS -This function is difficult to understand. +This function is difficult to understand, and takes too many arguments. + +If UTF-8 is available, 'µ' (U+00B5 MICRO SIGN) will be used in place of +'u' (U+0075 LATIN SMALL LETTER U) for the 'micro-' prefix. This is +determined by **notcurses_init**/**ncdirect_init**. Once UTF-8 is detected, +it is used for the lifetime of the process. The alternative would require +accepting a **const struct notcurses** (and associated API break). # SEE ALSO @@ -147,4 +154,5 @@ This function is difficult to understand. **notcurses(3)**, **notcurses_output(3)**, **setlocale(3)**, -**snprintf(3)** +**snprintf(3)**, +**utf-8(7)** diff --git a/include/notcurses/notcurses.h b/include/notcurses/notcurses.h index c2e1b4c41..9a859e9a0 100644 --- a/include/notcurses/notcurses.h +++ b/include/notcurses/notcurses.h @@ -3790,8 +3790,7 @@ API struct ncplane* nctablet_plane(struct nctablet* t); // // You are encouraged to consult notcurses_metric(3). API const char* ncnmetric(uintmax_t val, size_t s, uintmax_t decimal, - char* buf, int omitdec, uintmax_t mult, - int uprefix) + char* buf, int omitdec, uintmax_t mult, int uprefix) __attribute__ ((nonnull (4))); // The number of columns is one fewer, as the STRLEN expressions must leave diff --git a/src/lib/direct.c b/src/lib/direct.c index c77fd0d44..1667acd24 100644 --- a/src/lib/direct.c +++ b/src/lib/direct.c @@ -893,8 +893,9 @@ ncdirect* ncdirect_core_init(const char* termtype, FILE* outfp, uint64_t flags){ } const char* encoding = nl_langinfo(CODESET); bool utf8 = false; - if(encoding && strcmp(encoding, "UTF-8") == 0){ + if(encoding && encoding_is_utf8(encoding)){ utf8 = true; + ncmetric_use_utf8(); } if(setup_signals(ret, (flags & NCDIRECT_OPTION_NO_QUIT_SIGHANDLERS), true, ncdirect_stop_minimal)){ diff --git a/src/lib/internal.h b/src/lib/internal.h index 168d3f1a9..49a08bbba 100644 --- a/src/lib/internal.h +++ b/src/lib/internal.h @@ -1889,6 +1889,33 @@ int putenv_term(const char* termname) __attribute__ ((nonnull (1))); int set_loglevel_from_env(ncloglevel_e* loglevel) __attribute__ ((nonnull (1))); +// glibc's _nl_normalize_charset() converts to lowercase, removing everything +// but alnums. furthermore, "cs" is a valid prefix meaning "character set". +static inline bool +encoding_is_utf8(const char *enc){ + if(tolower(enc[0]) == 'c' && tolower(enc[1]) == 's'){ // strncasecmp() isn't ansi/iso + enc += 2; // skip initial "cs" if present. + } + const char utfstr[] = "utf8"; + const char* match = utfstr; + while(*enc){ + if(isalnum(*enc)){ // we only care about alnums + if(tolower(*enc) != tolower(*match)){ + return false; + } + ++match; + } + ++enc; + } + if(*match){ + return false; + } + return true; +} + +// tell ncmetric that utf8 is available. should be per-context, but isn't. +void ncmetric_use_utf8(void); + #undef API #undef ALLOC diff --git a/src/lib/metric.c b/src/lib/metric.c index ab78ed751..26bb9d6f9 100644 --- a/src/lib/metric.c +++ b/src/lib/metric.c @@ -8,26 +8,27 @@ static const wchar_t UTF8_SUBPREFIX[] = L"mµnpfazy"; // 10^24-1 static const wchar_t ASCII_SUBPREFIX[] = L"munpfazy"; // 10^24-1 + +// we want to use UTF8_SUBPREFIX if we have utf8 available to us. we could +// pull this out of const struct notcurses*, except these ncnmetric() doesn't +// take one, and we don't want to break the API. instead, we call this from +// notcurses_init() when we create a utf8 context. a gross hack =\. +static pthread_once_t utf8_verdict = PTHREAD_ONCE_INIT; static const wchar_t* SUBPREFIXES = ASCII_SUBPREFIX; -static pthread_once_t utf8_detector = PTHREAD_ONCE_INIT; -// sure hope we've called setlocale() by the time we hit this! static void -detect_utf8(void){ - const char* encoding = nl_langinfo(CODESET); - if(encoding){ - if(strcmp(encoding, "UTF-8") == 0){ - SUBPREFIXES = UTF8_SUBPREFIX; - } - } +ncmetric_use_utf8_internal(void){ + SUBPREFIXES = UTF8_SUBPREFIX; +} + +void ncmetric_use_utf8(void){ + pthread_once(&utf8_verdict, ncmetric_use_utf8_internal); } const char* ncnmetric(uintmax_t val, size_t s, uintmax_t decimal, char* buf, int omitdec, uintmax_t mult, int uprefix){ - // FIXME this is global to the process...ick :/ - fesetround(FE_TONEAREST); - pthread_once(&utf8_detector, detect_utf8); + fesetround(FE_TONEAREST); // FIXME global to the process...ick :/ // these two must have the same number of elements const wchar_t* subprefixes = SUBPREFIXES; const wchar_t prefixes[] = L"KMGTPEZY"; // 10^21-1 encompasses 2^64-1 diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c index c5b96c7cb..95ad3a693 100644 --- a/src/lib/notcurses.c +++ b/src/lib/notcurses.c @@ -1095,30 +1095,6 @@ int ncplane_destroy_family(ncplane *ncp){ return ret; } -// glibc's _nl_normalize_charset() converts to lowercase, removing everything -// but alnums. furthermore, "cs" is a valid prefix meaning "character set". -static bool -encoding_is_utf8(const char *enc){ - if(tolower(enc[0]) == 'c' && tolower(enc[1]) == 's'){ // strncasecmp() isn't ansi/iso - enc += 2; // skip initial "cs" if present. - } - const char utfstr[] = "utf8"; - const char* match = utfstr; - while(*enc){ - if(isalnum(*enc)){ // we only care about alnums - if(tolower(*enc) != tolower(*match)){ - return false; - } - ++match; - } - ++enc; - } - if(*match){ - return false; - } - return true; -} - // it's critical that we're using UTF-8 encoding if at all possible. since the // client might not have called setlocale(2) (if they weren't reading the // directions...), go ahead and try calling setlocale(LC_ALL, "") and then @@ -1271,6 +1247,9 @@ notcurses_early_init(const struct notcurses_options* opts, FILE* fp, unsigned* u free(ret); return NULL; } + if(utf8){ + ncmetric_use_utf8(); + } return ret; } diff --git a/src/lib/plot.c b/src/lib/plot.c index 99954d017..e240d0e06 100644 --- a/src/lib/plot.c +++ b/src/lib/plot.c @@ -89,7 +89,7 @@ typedef struct nc##X##plot { \ ncplot plot; \ } nc##X##plot; \ \ -int redraw_pixelplot_##T(nc##X##plot* ncp){ \ +static int redraw_pixelplot_##T(nc##X##plot* ncp){ \ if(calculate_gradient_vector(&ncp->plot, 1)){ \ return -1; \ } \ @@ -256,7 +256,7 @@ int redraw_pixelplot_##T(nc##X##plot* ncp){ \ return 0; \ } \ \ -int redraw_plot_##T(nc##X##plot* ncp){ \ +static int redraw_plot_##T(nc##X##plot* ncp){ \ if(ncp->plot.bset->geom == NCBLIT_PIXEL){ \ return redraw_pixelplot_##T(ncp); \ } \ @@ -429,8 +429,8 @@ int redraw_plot_##T(nc##X##plot* ncp){ \ } \ \ static const struct blitset* \ -create_##T(nc##X##plot* ncpp, ncplane* n, const ncplot_options* opts, const T miny, const T maxy, \ - const T trueminy, const T truemaxy){ \ +create_##T(nc##X##plot* ncpp, ncplane* n, const ncplot_options* opts, \ + const T miny, const T maxy, const T trueminy, const T truemaxy){ \ /* set up ->plot.ncp first so it gets destroyed on error */ \ ncpp->plot.ncp = n; \ if(ncplane_set_widget(ncpp->plot.ncp, ncpp, (void(*)(void*))nc##X##plot_destroy)){ \