Skip to content

Instantly share code, notes, and snippets.

@unix-junkie
Created May 13, 2016 17:38
Show Gist options
  • Save unix-junkie/9666928acab34bff186264a0a130998a to your computer and use it in GitHub Desktop.
Save unix-junkie/9666928acab34bff186264a0a130998a to your computer and use it in GitHub Desktop.
Locale-specific XFontSet expansion
/*
* $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