blob: 29731e2ff44248d8c012c84714e54bdc2ad50548 [file]
/*
* R : A Computer Language for Statistical Data Analysis
* Copyright (C) 2005-2021 The R Core Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, a copy is available at
* https://www.R-project.org/Licenses/
*/
/* The original version of this file was contributed in 2005 by Ei-ji
* Nakama, along with rlocale_data.h and ../include/rlocale.h.
*
* The naming is misleading: apart from the width data this is not
* locale-specfic. It is rather about the use of non-Latin
* characters (including symbols, emojis ...).
*
* It provides replacements for the wctype (iswxxxxx) functions on
* Windows (where they are not correct in e.g. Japanese)
* AIX (missing)
* macOS in CJK (where these just called the ctype functions)
*
* It also provides wc[s]width, where widths of CJK fonts are often
* wrong in vendor-supplied versions and in Markus Kuhn's version
* used for Windows in R 2.[12].x.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef HAVE_VISIBILITY_ATTRIBUTE
# define attribute_hidden __attribute__ ((visibility ("hidden")))
#else
# define attribute_hidden
#endif
#include <string.h>
#include <stdlib.h>
#define IN_RLOCALE_C 1 /* used in rlocale.h */
#include <rlocale.h>
#include <wctype.h>
#include <wchar.h>
#include <ctype.h>
#include <locale.h>
#include <limits.h>
#include <R_ext/Riconv.h>
#if defined(USE_RI18N_WIDTH) || defined(USE_RI18N_FNS)
/* used for zero-width table and in rlocale_data.h */
struct interval {
int first;
int last;
};
// This seems based on Markus Kuhn's function but with 1-based 'max'
static int wcsearch(int wint, const struct interval *table, int max)
{
int min = 0;
int mid;
max--;
if (wint < table[0].first || wint > table[max].last)
return 0;
while (max >= min) {
mid = (min + max) / 2;
if (wint > table[mid].last)
min = mid + 1;
else if (wint < table[mid].first)
max = mid - 1;
else
return 1;
}
return 0;
}
#endif
// ------------------------- width functions --------------------
#ifdef USE_RI18N_WIDTH
/* wcwidth and wcswidth, from POSIX 2001 (formerly in some draft C
* standards but not implemented in Windows). One could argue that
* the width of non-printable / unassigned characters is immaterial
* (they will be represented by escapes) so could be given a
* conventional value such as 0 or 1. POSIX suggests returning -1 for
* non-printable characters, but these were not written that way in
* 2005.
*
* It is not always clear what to do for unassigned code points
* (especially 'private use' ones).
*
* Although what a character represents may be locale-specific,
* reference images are available at
* https://www.unicode.org/charts/PDF/ whose width can be assessed.
*
* There is a problem with character-by-character appoaches: apart
* from surrogate pairs, some glyphs are defined in combinations of
* others. For human languages this is largely (but not entirely)
* covered by giving combining characters zero width. However, quite
* a few emoji are defined as combinations of others: e.g.
* 'polar bear' is bear+snowflake with a zero-width joiner,
* "\U1f43b\u200d\u2744" which gets width 2+0+1.
* (https://emojipedia.org/polar-bear/,
* https://emojipedia.org/emoji-zwj-sequence/). There are a few such
* in human languages:
* https://en.wikipedia.org/wiki/Zero-width_joiner.
*/
#include "rlocale_widths.h"
static int wcwidthsearch(int wint, const struct interval_wcwidth *table,
int max, int locale)
{
int min = 0;
int mid;
max--;
/* This quickly gives one for printing ASCII characters */
if (wint > 0x1F && wint < 0x7F) return 1;
else if (wint < table[min].first || wint > table[max].last) return -1;
while (max >= min) {
mid = (min + max) / 2;
if (wint > table[mid].last)
min = mid + 1;
else if (wint < table[mid].first)
max = mid - 1;
else{
return(table[mid].mb[locale]);
}
}
return -1;
}
/* The idea here here has never been explained.
See also the comments in ../include/rlocale.h.
That does not explain the separate entries for Singapore
(simplified) and Hong Kong/Macau (traditional) where it seems the
Windows system font is not different from PRC/Taiwan respectively,
nor what font was used for non-Windows, nor where the values came
from.
Except perhaps on macOS, the non-Windows locale names are for the
default MBCS encodings (e.g. GBK, GB1312, BIG5, EUCJP, EUCKR).
There are other non-UTF-8 encodings for those locales,
e.g. ja_JP.SJIS, ko_KR.CP949, zh_CN.eucCN, zh_HK.Big5HKSCS.
*/
typedef struct {
char *name;
int locale;
} cjk_locale_name_t;
static cjk_locale_name_t cjk_locale_name[] = {
// Windows locale names
{"CHINESE(SINGAPORE)_SIGNAPORE", MB_zh_SG},
{"CHINESE_SIGNAPORE", MB_zh_SG},
{"CHINESE(PRC)_PEOPLE'S REPUBLIC OF CHINA", MB_zh_CN},
{"CHINESE_PEOPLE'S REPUBLIC OF CHINA", MB_zh_CN},
{"CHINESE_MACAU S.A.R.", MB_zh_HK},
{"CHINESE(PRC)_HONG KONG", MB_zh_HK},
{"CHINESE_HONG KONG S.A.R.", MB_zh_HK},
{"CHINESE(TAIWAN)_TAIWAN", MB_zh_TW},
{"CHINESE_TAIWAN", MB_zh_TW},
{"CHINESE-S", MB_zh_CN},
{"CHINESE-T", MB_zh_TW},
{"JAPANESE_JAPAN", MB_ja_JP},
{"JAPANESE", MB_ja_JP},
{"KOREAN_KOREA", MB_ko_KR},
{"KOREAN", MB_ko_KR},
// Other OSes, but only in default encodings.
{"ZH_TW", MB_zh_TW},
{"ZH_CN", MB_zh_CN},
{"ZH_CN.BIG5", MB_zh_TW},
{"ZH_HK", MB_zh_HK},
{"ZH_SG", MB_zh_SG},
{"JA_JP", MB_ja_JP},
{"KO_KR", MB_ko_KR},
{"ZH", MB_zh_CN},
{"JA", MB_ja_JP},
{"KO", MB_ko_KR},
// Default, where all EA Ambiguous characters have width one.
{"", MB_Default},
};
/* used in character.c, ../gnuwin32/console.c (for an MBCS locale) ,
../library/grDevices/src/devP*.c
Unlike the POSIX description this does not return -1 for
non-printable Unicode points.
NB: Windows (at least MinGW-W64) does not have this function.
*/
int Ri18n_wcwidth(R_wchar_t c)
{
char lc_str[128];
unsigned int i, j;
static char *lc_cache = "";
static int lc = 0;
if (0 != strcmp(setlocale(LC_CTYPE, NULL), lc_cache)) {
strncpy(lc_str, setlocale(LC_CTYPE, NULL), sizeof(lc_str) - 1);
lc_str[sizeof(lc_str) - 1] = '\0';
for (i = 0, j = (int) strlen(lc_str); i < j && i < sizeof(lc_str); i++)
lc_str[i] = (char) toupper(lc_str[i]);
for (i = 0; i < (sizeof(cjk_locale_name)/sizeof(cjk_locale_name_t));
i++) {
if (0 == strncmp(cjk_locale_name[i].name, lc_str,
strlen(cjk_locale_name[i].name))) {
lc = cjk_locale_name[i].locale;
break;
}
}
}
int wd = wcwidthsearch(c, table_wcwidth,
(sizeof(table_wcwidth)/sizeof(struct interval_wcwidth)),
lc);
if (wd >= 0) return wd; // currently all are 1 or 2.
int zw = wcsearch(c, zero_width, zero_width_count);
return zw ? 0 : 1; // assume unknown chars are width one.
}
/* Used in character.c, errors.c, ../gnuwin32/console.c (for an MBCS locale)
Strings in R are restricted to 2^31-1 bytes but could conceivably
have a width exceeding that.
Unlike the POSIX description this does not return -1 for strings
containing non-printable Unicode points.
NB: Windows (at least MinGW-W64) does not have this function.
*/
attribute_hidden
int Ri18n_wcswidth (const wchar_t *wc, size_t n)
{
int rs = 0;
while ((n-- > 0) && (*wc != L'\0'))
{
if (IS_SURROGATE_PAIR(*wc, *(wc+1))) {
/* surrogate pairs should only occur with 'short' wchar_t,
* that is Windows and perhaps 32-bit AIX */
R_wchar_t val =
((*wc & 0x3FF) << 10) + (*(wc+1) & 0x3FF) + 0x010000;
int now = Ri18n_wcwidth (val);
if (now == -1) return -1;
rs += now;
wc += 2;
} else {
int now = Ri18n_wcwidth (*wc);
if (now == -1) return -1;
rs += now;
wc++;
}
}
return rs;
}
#endif
// ------------------- end of width functions --------------------
/*********************************************************************
* macOS's wide character type functions are based on NetBSD
* and only work(ed) correctly for Latin-1 characters.
* So we replace them. May also be needed on *BSD, and are on AIX
********************************************************************/
#ifdef USE_RI18N_FNS
# define ISWFUNC(ISWNAME) static int Ri18n_isw ## ISWNAME (wint_t wc) \
{ \
return wcsearch(wc,table_w ## ISWNAME , table_w ## ISWNAME ## _count); \
}
#include "rlocale_data.h"
/* These are the functions which C99 and POSIX define. However,
not all are used elsewhere in R (so are static),
but they are used in Ri18n_iswctype. */
ISWFUNC(upper)
ISWFUNC(lower)
ISWFUNC(alpha)
ISWFUNC(digit)
ISWFUNC(xdigit)
ISWFUNC(space)
ISWFUNC(print)
/* derived below from print and space
ISWFUNC(graph)
*/
ISWFUNC(blank)
ISWFUNC(cntrl)
ISWFUNC(punct)
/* defined below in terms of digit and alpha
ISWFUNC(alnum)
*/
wctype_t Ri18n_wctype(const char *);
int Ri18n_iswctype(wint_t, wctype_t);
static int Ri18n_iswalnum (wint_t wc)
{
return (Ri18n_iswctype(wc, Ri18n_wctype("digit")) ||
Ri18n_iswctype(wc, Ri18n_wctype("alpha")) );
}
/* Defined in the C99 standard as
'any wide character for which iswprint is true and iswspace is false'
As this is used rarely (and iswprint is used a lot), we chose to
derive this one.
*/
static int Ri18n_iswgraph (wint_t wc)
{
return (Ri18n_iswctype(wc, Ri18n_wctype("print")) &&
!Ri18n_iswctype(wc, Ri18n_wctype("space")) );
}
/*
* iswctype
*/
typedef struct {
char * name;
wctype_t wctype;
int(*func)(wint_t);
} Ri18n_wctype_func_l ;
static const Ri18n_wctype_func_l Ri18n_wctype_func[] = {
{"upper", 1<<0, Ri18n_iswupper},
{"lower", 1<<1, Ri18n_iswlower},
{"alpha", 1<<2, Ri18n_iswalpha},
{"digit", 1<<3, Ri18n_iswdigit},
{"xdigit", 1<<4, Ri18n_iswxdigit},
{"space", 1<<5, Ri18n_iswspace},
{"print", 1<<6, Ri18n_iswprint},
{"graph", 1<<7, Ri18n_iswgraph},
{"blank", 1<<8, Ri18n_iswblank},
{"cntrl", 1<<9, Ri18n_iswcntrl},
{"punct", 1<<10, Ri18n_iswpunct},
{"alnum", 1<<11, Ri18n_iswalnum},
{NULL, 0, NULL}
};
/* These two are used (via macros) in X11 dataentry so need to be visible. */
wctype_t Ri18n_wctype(const char *name)
{
int i;
for (i = 0 ; Ri18n_wctype_func[i].name != NULL &&
0 != strcmp(Ri18n_wctype_func[i].name, name) ; i++ );
return Ri18n_wctype_func[i].wctype;
}
int Ri18n_iswctype(wint_t wc, wctype_t desc)
{
int i;
for (i = 0 ; Ri18n_wctype_func[i].wctype != 0 &&
Ri18n_wctype_func[i].wctype != desc ; i++ );
return (*Ri18n_wctype_func[i].func)(wc);
}
#endif
// ------------------------- tolower/upper functions --------------------
#ifdef USE_RI18N_CASE
/*
These tables were prepared by the R code
tab <- read.table('UnicodeData.txt', sep = ';', header = FALSE)
tab <- tab[, c("V1", "V13", "V14")]
names(tab) <- c('pt', 'uc', 'lc')
toupper <- tab[tab$uc !="", 1:2]
tolower <- tab[tab$lc !="", c(1,3)]
cat(with(toupper, sprintf(" { 0x%s, 0x%s },", pt, uc)),
sep = "\n", file = "rlocale_toupper.h")
cat(with(tolower, sprintf(" { 0x%s, 0x%s },", pt, lc)),
sep = "\n", file = "rlocale_tolower.h")
from https://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt
*/
struct pair {int from; int to;};
static const struct pair table_toupper[] = {
#include "rlocale_toupper.h"
};
static const struct pair table_tolower[] = {
#include "rlocale_tolower.h"
};
static int tlsearch(int wint, const struct pair *table, int max)
{
int min = 0, mid;
max--;
if (wint < table[0].from || wint > table[max].from)
return -1;
while (max >= min) {
mid = (min + max) / 2;
if (wint > table[mid].from)
min = mid + 1;
else if (wint < table[mid].from)
max = mid - 1;
else
return table[mid].to;
}
return -1;
}
R_wchar_t Ri18n_towupper(R_wchar_t wc)
{
int res = tlsearch(wc, table_toupper,
sizeof(table_toupper)/sizeof(struct pair));
return (res >= 0 ? res : wc);
}
R_wchar_t Ri18n_towlower(R_wchar_t wc)
{
int res = tlsearch(wc, table_tolower,
sizeof(table_tolower)/sizeof(struct pair));
return (res >= 0 ? res : wc);
}
// ----------------- end of tolower/upper functions --------------------
#endif