Created
May 13, 2016 17:38
Locale-specific XFontSet expansion
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* $Id$ | |
* | |
* This test uses Monotype Arial font (<http://www.fonts.com/font/monotype/arial>, | |
* <https://en.wikipedia.org/wiki/Arial>). | |
* | |
* Availability: | |
* - Cygwin/X: out of the box (v 6.87, shipped with Windows); | |
* - Xsun: out of the box (shipped with Solaris); | |
* - X.Org on Debian Linux: either of the following packages: | |
* * ttf-mscorefonts-installer (v2.82, Debian 5.0+, [contrib] section), | |
* * msttcorefonts (Debian 3.0 through 4.0, [contrib] section), | |
* * ttf-root-installer (v2.50, no Cyrillic glyphs, Debian 5.0, 7.0+, , [contrib] section); | |
* - X.org on Gentoo Linux: | |
* * media-fonts/corefonts. | |
* | |
* On Debian Linux, if both ttf-mscorefonts-installer and ttf-root-installer | |
* packages are installed, the former should precede in X11 font path, as the | |
* latter has older font versions without Cyrillic glyphs. | |
*/ | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <strings.h> | |
#include <wchar.h> | |
#include <errno.h> | |
#include <locale.h> | |
#include <langinfo.h> | |
#include <iconv.h> | |
#include <assert.h> | |
#include <X11/Xatom.h> | |
#include <Xm/XmAll.h> | |
wchar_t * const wideText = L"\u0410\u0411\u0412\u0413\u0414\u0415\u0401\u0416\u0417\u0418\u0419\u041a\u041b\u041c\u041d\u041e\u041f\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042a\u042b\u042c\u042d\u042e\u042f\u0430\u0431\u0432\u0433\u0434\u0435\u0451\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044a\u044b\u044c\u044d\u044e\u044f"; | |
#if defined(__sun) && defined(__SVR4) | |
/* | |
* In Solaris, sizeof(wchar_t) is normally 4. | |
* | |
* Both UCS-2 and UCS-4 can be converted to UTF-8, | |
* while UTF-16 can only be converted to UCS-4 (see iconv_unicode(5)), | |
* so UCS-2 is preferred to UTF-16. | |
* | |
* "WCHAR_T" is a sane default which will be rejected by Solaris' iconv_open(), | |
* but still is a valid input for iconv_open() from GNU libiconv. | |
*/ | |
#define ICONV_WCHAR_T sizeof(wchar_t) == 4 ? "UCS-4" : sizeof(wchar_t) == 2 ? "UCS-2" : "WCHAR_T" | |
#else | |
#define ICONV_WCHAR_T "WCHAR_T" | |
#endif | |
#if defined(__sun) && defined(__SVR4) | |
#define PRINTF_SIZE_T "%u" | |
#else | |
#define PRINTF_SIZE_T "%zu" | |
#endif | |
char *iconvToNewA(const char * const source, const char *sourceCharset, const char *targetCharset); | |
char *iconvToNewW(const wchar_t * const source, const char *targetCharset); | |
/* | |
* UTF-8 may use up to 6 bytes to encode a char. | |
* For Cyrillic, 2 bytes is enough though. | |
*/ | |
const unsigned int bytesPerChar = 2; | |
void addLineWithCharset(const Widget parent, | |
const XmTextType type, | |
int * const columnCount); | |
void addLineWithCharsetCreate(const Widget parent, | |
int * const columnCount); | |
void addLineWithCharsetCreateLocalized(const Widget parent, | |
int * const columnCount); | |
void addLine(const Widget parent, | |
char * const keyWidgetName, | |
char * const keyLabel, | |
const XmString valueLabel); | |
void printFontList(const Widget widget); | |
int main(int argc, char *argv[]) { | |
const char *locale = setlocale(LC_ALL, ""); | |
if (locale == NULL) { | |
fprintf(stderr, "Locale unavailable.\n"); | |
} | |
char *localeCharset = nl_langinfo(CODESET); | |
printf("locale: %s\n", locale); | |
printf("run-time charset: %s\n", localeCharset); | |
XtAppContext appContext; | |
XtSetLanguageProc(NULL, (XtLanguageProc) NULL, (XtPointer) NULL); | |
const char *applicationClass = "XmStringTest"; | |
String fallbackResources[] = { | |
NULL, | |
}; | |
const Widget topLevel = XtVaAppInitialize(&appContext, applicationClass, NULL, 0, &argc, argv, fallbackResources, NULL); | |
XtVaSetValues(topLevel, XmNtitle, "Bug #1653, " XmVERSION_STRING, NULL); | |
XtVaSetValues(topLevel, XmNiconName, "Bug #1653, " XmVERSION_STRING " (Icon)", NULL); | |
const Widget mainWindow = XmCreateMainWindow(topLevel, "mainWindow", NULL, 0); | |
XtManageChild(mainWindow); | |
const Cardinal rowColumnPropertyCount = 1; | |
Arg rowColumnProperties[rowColumnPropertyCount]; | |
Cardinal i = 0; | |
/* | |
* Since XtSetArg is a macro, it's impossible to use i++ inline: the | |
* count will be incremented twice. | |
*/ | |
XtSetArg(rowColumnProperties[i], XmNrowColumnType, XmWORK_AREA); i++; | |
assert(i == rowColumnPropertyCount); | |
const Widget rowColumn = XmCreateRowColumn(mainWindow, "rowColumn", rowColumnProperties, rowColumnPropertyCount); | |
XtManageChild(rowColumn); | |
XtVaSetValues(rowColumn, XmNpacking, XmPACK_COLUMN, NULL); | |
XtVaSetValues(rowColumn, XmNorientation, XmHORIZONTAL, NULL); | |
int columnCount = 0; | |
/* | |
* Generic fontset ("labelGeneric") will only work with XmCHARSET_TEXT | |
* mode in non-UTF-8 locales (broken since Motif 2.3). | |
* | |
* Additionally, this won't work in ru_RU locale (same as ru_RU.ISO-8859-5, | |
* charset specified implicitly, broken since Motif 2.2). | |
* | |
* This is because fontset is incorrectly populated with fonts. | |
*/ | |
addLineWithCharsetCreateLocalized(rowColumn, &columnCount); | |
addLineWithCharsetCreate(rowColumn, &columnCount); | |
addLineWithCharset(rowColumn, XmCHARSET_TEXT, &columnCount); | |
addLineWithCharset(rowColumn, XmMULTIBYTE_TEXT, &columnCount); | |
XtVaSetValues(rowColumn, XmNnumColumns, columnCount, NULL); // For XmHORIZONTAL orientation, this is actually a number of rows | |
XtRealizeWidget(topLevel); | |
XtAppMainLoop(appContext); | |
return 0; | |
} | |
void addLineWithCharset(const Widget parent, | |
const XmTextType type, | |
int * const columnCount) { | |
char * const keyLabel = type == XmMULTIBYTE_TEXT | |
? "XmStringGenerate(XmMULTIBYTE_TEXT):" | |
: "XmStringGenerate(XmCHARSET_TEXT):"; | |
char * const text = iconvToNewW(wideText, nl_langinfo(CODESET)); | |
if (text != NULL) { | |
if (type == XmCHARSET_TEXT) { | |
XmString labelString = XmStringGenerate(text, XmFONTLIST_DEFAULT_TAG, type, NULL); | |
addLine(parent, keyLabel, keyLabel, labelString); | |
XmStringFree(labelString); | |
(*columnCount)++; | |
} else if (type == XmMULTIBYTE_TEXT && strcasecmp(nl_langinfo(CODESET), "UTF-8") == 0) { | |
XmString labelString = XmStringGenerate(text, _MOTIF_DEFAULT_LOCALE, type, NULL); | |
addLine(parent, keyLabel, keyLabel, labelString); | |
XmStringFree(labelString); | |
(*columnCount)++; | |
} | |
} | |
free(text); | |
} | |
void addLineWithCharsetCreate(const Widget parent, | |
int * const columnCount) { | |
char * const keyLabel = "XmStringCreate():"; | |
char * const text = iconvToNewW(wideText, nl_langinfo(CODESET)); | |
XmString labelString = XmStringCreate(text, XmFONTLIST_DEFAULT_TAG); | |
addLine(parent, keyLabel, keyLabel, labelString); | |
XmStringFree(labelString); | |
free(text); | |
(*columnCount)++; | |
} | |
void addLineWithCharsetCreateLocalized(const Widget parent, | |
int * const columnCount) { | |
char * const keyLabel = "XmStringCreateLocalized():"; | |
char * const text = iconvToNewW(wideText, nl_langinfo(CODESET)); | |
XmString labelString = XmStringCreateLocalized(text); | |
addLine(parent, keyLabel, keyLabel, labelString); | |
XmStringFree(labelString); | |
free(text); | |
(*columnCount)++; | |
} | |
void addLine(const Widget parent, | |
char * const keyWidgetName, | |
char * const keyLabel, | |
const XmString valueLabel) { | |
const Widget label0 = XmCreateLabel(parent, keyWidgetName, NULL, 0); | |
XtManageChild(label0); | |
const XmString labelString0 = XmStringCreate(keyLabel, XmSTRING_DEFAULT_CHARSET); | |
XtVaSetValues(label0, XmNlabelString, labelString0, NULL); | |
XmStringFree(labelString0); | |
const Widget label1 = XmCreateLabel(parent, "value", NULL, 0); | |
XtManageChild(label1); | |
XtVaSetValues(label1, XmNlabelString, valueLabel, NULL); | |
printf("%s\n", keyLabel); | |
char ** missingCharsets; | |
int missingCharsetCount = 0; | |
char * defaultString; | |
XFontSet fontSet = XCreateFontSet(XtDisplay(label1), | |
"-monotype-arial-medium-r-normal--*-90-*-*-p-0-*-*, \ | |
-monotype-arial-regular-r-normal--*-90-*-*-p-0-*-*", // Xsun | |
&missingCharsets, | |
&missingCharsetCount, | |
&defaultString); | |
if (missingCharsetCount > 0) { | |
/* | |
* For Arial in UTF-8 locale, glyphs for only the following | |
* charsets are missing (irrelevant for Cyrillic): | |
* | |
* - ISO8859-14 | |
* - JISX0208.1983-0 | |
* - KSC5601.1987-0 | |
* - GB2312.1980-0 | |
* - JISX0201.1976-0 | |
*/ | |
for (int i = 0; i < missingCharsetCount; i++) { | |
char * missingCharset = *(missingCharsets + i); | |
printf("\tMissing charset: %s\n", missingCharset); | |
} | |
XFreeStringList(missingCharsets); | |
} | |
if (fontSet != NULL) { | |
XmFontListEntry fontListEntry = XmFontListEntryCreate(XmFONTLIST_DEFAULT_TAG, | |
XmFONT_IS_FONTSET, | |
fontSet); | |
XmFontList fontList = XmFontListAppendEntry(NULL, fontListEntry); | |
XtVaSetValues(label1, XmNfontList, fontList, NULL); | |
XmFontListEntryFree(&fontListEntry); | |
XmFontListFree(fontList); | |
} | |
printFontList(label1); | |
} | |
void printFontList(const Widget widget) { | |
XmFontList fontList; | |
XtVaGetValues(widget, XmNfontList, &fontList, NULL); | |
if (fontList != NULL) { | |
Display * const dpy = XtDisplay(widget); | |
XmFontContext fontCtx; | |
XmFontListInitFontContext(&fontCtx, fontList); | |
XmFontListEntry entry; | |
while ((entry = XmFontListNextEntry(fontCtx)) != NULL) { | |
XmFontType fontType; | |
const XtPointer fontPtr = XmFontListEntryGetFont(entry, &fontType); | |
const char *fontTypeString = fontType == XmFONT_IS_FONT ? "XmFONT_IS_FONT" : fontType == XmFONT_IS_FONTSET ? "XmFONT_IS_FONTSET" : "XmFONT_IS_XFT"; | |
if (fontType == XmFONT_IS_FONT) { | |
XFontStruct *font = fontPtr; | |
unsigned long fontNameAtom; | |
if (!XGetFontProperty(font, XA_FONT, &fontNameAtom)) { | |
printf("\t%s: XGetFontProperty(XA_FONT) failed\n", fontTypeString); | |
} else { | |
char * const fontName = XGetAtomName(dpy, (Atom) fontNameAtom); | |
printf("\t%s: %s\n", fontTypeString, fontName); | |
XFree(fontName); | |
} | |
} else if (fontType == XmFONT_IS_FONTSET) { | |
XFontSet fontSet = fontPtr; | |
printf("\t%s (%s): %s\n", fontTypeString, XLocaleOfFontSet(fontSet), XBaseFontNameListOfFontSet(fontSet)); | |
XFontStruct** fonts; | |
char ** fontNames; | |
int count = XFontsOfFontSet(fontSet, &fonts, &fontNames); | |
for (int i = 0; i < count; i++) { | |
XFontStruct * const font = *(fonts + i); | |
char * const fallbackFontName = *(fontNames + i); | |
unsigned long fontNameAtom; | |
if (!XGetFontProperty(font, XA_FONT, &fontNameAtom)) { | |
printf("\t\tXGetFontProperty(XA_FONT) failed\n"); | |
printf("\t\t%s\n", fallbackFontName); | |
} else { | |
char * const fontName = XGetAtomName(dpy, (Atom) fontNameAtom); | |
printf("\t\t%s\n", fontName); | |
XFree(fontName); | |
} | |
#ifdef FONT_DEBUG_INFO | |
/* | |
* Has no effect for non-TrueType fonts. | |
*/ | |
int i; | |
XFontProp *fontProp; | |
for (i = 0, fontProp = font->properties; i < font->n_properties; i++, fontProp++) { | |
char* fontPropName = XGetAtomName(dpy, fontProp->name); | |
if (strcmp(fontPropName, "_ADOBE_POSTSCRIPT_FONTNAME") == 0 | |
|| strcmp(fontPropName, "COPYRIGHT") == 0 | |
|| strcmp(fontPropName, "FACE_NAME") == 0 | |
|| strcmp(fontPropName, "FONT_TYPE") == 0 | |
|| strcmp(fontPropName, "RASTERIZER_NAME") == 0) { | |
const char *fontPropValue = XGetAtomName(dpy, (Atom) fontProp->card32); | |
printf("\t\t\t%s = %s\n", XGetAtomName(dpy, fontProp->name), fontPropValue); | |
} | |
} | |
#endif // FONT_DEBUG_INFO | |
} | |
} else { | |
printf("\t%s (%ud)\n", fontTypeString, fontType); | |
} | |
} | |
} | |
} | |
char *iconvToNewA(const char * const source, const char *sourceCharset, const char *targetCharset) { | |
const int wideChar = !strcmp(sourceCharset, ICONV_WCHAR_T); | |
/* | |
* bytesPerChar only makes sense when converting to UTF-8. | |
*/ | |
const size_t sourceLength = wideChar ? wcslen((wchar_t *) source) : strlen(source); // w/o trailing \0 | |
const size_t targetLength = sourceLength * (wideChar || strcmp(sourceCharset, targetCharset) | |
? bytesPerChar | |
: 1); // w/o trailing \0 | |
char * const target = malloc(targetLength + 1); | |
#if defined(__sun) && defined(__SVR4) | |
/* | |
* On Solaris, iconv_open() fails if the source and target charsets are | |
* the same, so using memcpy() instead. | |
*/ | |
if (!wideChar && !strcmp(sourceCharset, targetCharset)) { | |
/* | |
* Conversion to the same charset. | |
*/ | |
return memcpy(target, source, sourceLength); | |
} | |
#endif | |
errno = 0; | |
const iconv_t descriptor = iconv_open(targetCharset, sourceCharset); | |
if (descriptor == (iconv_t) -1) { | |
#if defined(__sun) && defined(__SVR4) | |
/* | |
* Solaris can't convert from a single-byte charset to another | |
* single-byte charset, so UTF-8 must be used for intermediate | |
* conversion. | |
*/ | |
if (errno == EINVAL && strcmp(targetCharset, "UTF-8")) { | |
char * const utf8String = iconvToNewA(source, sourceCharset, "UTF-8"); | |
char * const s = iconvToNewA(utf8String, "UTF-8", targetCharset); | |
free(utf8String); | |
return s; | |
} | |
#endif | |
fprintf(stderr, "iconv_open(\"%s\", \"%s\") failed: errno = %d\n", targetCharset, sourceCharset, errno); | |
perror("iconv_open() failed"); | |
return NULL; | |
} | |
#if defined(__sun) && defined(__SVR4) | |
const char *inBuf = source; | |
#else | |
char *inBuf = (char *) source; | |
#endif | |
char *outBuf = target; | |
size_t inBytesLeft = (sourceLength + 1) * (wideChar ? sizeof(wchar_t) : sizeof(char)); | |
size_t outBytesLeft = targetLength + 1; | |
errno = 0; | |
if (iconv(descriptor, &inBuf, &inBytesLeft, &outBuf, &outBytesLeft) == (size_t) -1) { | |
fprintf(stderr, "iconv() failed: errno = %d\n", errno); | |
perror("iconv() failed"); | |
return NULL; | |
} | |
errno = 0; | |
if (iconv_close(descriptor) == -1) { | |
fprintf(stderr, "iconv_close() failed: errno = %d\n", errno); | |
perror("iconv_close() failed"); | |
return NULL; | |
} | |
return target; | |
} | |
char *iconvToNewW(const wchar_t * const source, const char *targetCharset) { | |
return iconvToNewA((char *) source, ICONV_WCHAR_T, targetCharset); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment