LINUX.ORG.RU

XFontSet и все что с ним связано

 , xfontset,


1

5

Разбираюсь с функциями вывода текста в XLib и не могу понять почему текст выводится некорректно когда в фонтсете имеется только ISO10646 шрифт. Локаль en_GB.UTF-8, тестирую на следующем фрагменте

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xresource.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>

static XrmOptionDescRec xrmTable[] = {
	{"-bg", "*background", XrmoptionSepArg, NULL},
	{"-fg", "*foreground", XrmoptionSepArg, NULL},
	{"-bc", "*bordercolour", XrmoptionSepArg, NULL},
	{"-font", "*font", XrmoptionSepArg, NULL},
};

unsigned long getColour(Display *dpy,  XrmDatabase db, char *name,
			char *cl, char *def){
	XrmValue v;
	XColor col1, col2;
	Colormap cmap = DefaultColormap(dpy, DefaultScreen(dpy));
	char * type;

	if (XrmGetResource(db, name, cl, &type, &v)
			&& XAllocNamedColor(dpy, cmap, v.addr, &col1, &col2)) {
	} else {
		XAllocNamedColor(dpy, cmap, def, &col1, &col2);
	}
	return col2.pixel;
}

XFontSet getFont(Display *dpy, XrmDatabase db, char *name,
		char *cl, char *def){
	XrmValue v;
	char * type;
	XFontSet font = NULL;
	int nmissing;
	char **missing;
	char *def_string;

	if (XrmGetResource(db, name, cl, &type, &v)){
		if (v.addr)
			font = XCreateFontSet(dpy, v.addr, &missing, &nmissing, &def_string);
	}
	if (!font) {
		if (v.addr)
		fprintf(stderr, "unable to load preferred font: %s using fixed\n", v.addr);
		else 
		fprintf(stderr, "couldn't figure out preferred font\n");
		font = XCreateFontSet(dpy, def, &missing, &nmissing, &def_string);
	}
	XFreeStringList(missing);
	return font;
}


GC setup(Display * dpy, int argc, char ** argv, int *width_r, int *height_r,
		XFontSet *font_r){
	int width, height;
	unsigned long background, border;
	Window win;
	GC pen;
	XGCValues values;

	XFontSet font;
	XrmDatabase db;

	XrmInitialize();
	db = XrmGetDatabase(dpy);
	XrmParseCommand(&db, xrmTable, sizeof(xrmTable)/sizeof(xrmTable[0]),
		"xtut7", &argc, argv);

	font = getFont(dpy, db, "xtut7.font", "xtut7.Font", "fixed");
	background = getColour(dpy,  db, "xtut7.background", "xtut7.BackGround", "DarkGreen");
	border = getColour(dpy,  db, "xtut7.border", "xtut7.Border", "LightGreen");
	values.foreground = getColour(dpy,  db, "xtut7.foreground", "xtut7.ForeGround", "Red");


	width = 400;
	height = 400;

	win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), /* display, parent */
		0,0, /* x, y: the window manager will place the window elsewhere */
		width, height, /* width, height */
		2, border, /* border width & colour, unless you have a window manager */
		background); /* background colour */

	Xutf8SetWMProperties(dpy, win, "XTut7", "xtut7", argv, argc,
		NULL, NULL, NULL);

	/* create the pen to draw lines with */
	values.line_width = 1;
	values.line_style = LineSolid;
	/*values.font = font->fid; */
	pen = XCreateGC(dpy, win, GCForeground|GCLineWidth|GCLineStyle,&values);

	/* tell the display server what kind of events we would like to see */
	XSelectInput(dpy, win, ButtonPressMask|ButtonReleaseMask|StructureNotifyMask|ExposureMask);

	/* okay, put the window on the screen, please */
	XMapWindow(dpy, win);

	*width_r = width; *height_r = height;
	*font_r = font;

	return pen;
}

int main_loop(Display *dpy, XFontSet font, GC pen, int width, int height,
		 char *text){
	int text_width;
	int textx, texty;
	XEvent ev;
	int font_ascent;
	XFontStruct **fonts;
	char **font_names;
	int nfonts;
	int j;

	printf("%s:%d\n", text, (int) strlen(text));
	text_width = XmbTextEscapement(font, text, strlen(text));
	font_ascent = 0;
	nfonts = XFontsOfFontSet(font, &fonts, &font_names);
	for(j = 0; j < nfonts; j += 1){
		if (font_ascent < fonts[j]->ascent) font_ascent = fonts[j]->ascent;
		printf("Font: %s\n", font_names[j]);
	}


	/* as each event that we asked about occurs, we respond. */
	while(1){
		XNextEvent(dpy, &ev);
		switch(ev.type){
		case Expose:
			if (ev.xexpose.count > 0) break;
			XDrawLine(dpy, ev.xany.window, pen, 0, 0, width/2-text_width/2, height/2);
			XDrawLine(dpy, ev.xany.window, pen, width, 0, width/2+text_width/2, height/2);
			XDrawLine(dpy, ev.xany.window, pen, 0, height, width/2-text_width/2, height/2);
			XDrawLine(dpy, ev.xany.window, pen, width, height, width/2+text_width/2, height/2);
   			textx = (width - text_width)/2;
   			texty = (height + font_ascent)/2;
   			XmbDrawString(dpy, ev.xany.window, font, pen, textx, texty, text, strlen(text));
			break;
		case ConfigureNotify:
			if (width != ev.xconfigure.width
					|| height != ev.xconfigure.height) {
				width = ev.xconfigure.width;
				height = ev.xconfigure.height;
				XClearWindow(dpy, ev.xany.window);
			}
			break;
		case ButtonRelease:
			XCloseDisplay(dpy);
			return 0;
		}
	}
}

int main(int argc, char ** argv){
	int width, height;
	Display *dpy;
	GC pen;
	XFontSet font;
	char *text = "Hello World ñ! ";
	setlocale(LC_ALL, getenv("LANG"));

	/* First connect to the display server */
	dpy = XOpenDisplay(NULL);
	if (!dpy) {fprintf(stderr, "unable to connect to display\n");return 7;}
	pen = setup(dpy, argc, argv, &width, &height, &font);
	if (argv[1] && argv[1][0]) text = argv[1];
	return main_loop(dpy, font, pen, width, height, text);
}

Если в фонтсете есть все кодировки (например для fixed) то текст выводится правильно. Если же имеется только ISO10646, то некорректно выводится даже латиница (вместо нее иероглифы). Причем поведение идентично при использовании Xmb*, Xwc* и Xutf8* вариантов функций. Не понятно почему при использовании юникодной локали и юникодного шрифта текст выводится неправильно.

★★★★★

Для определения шрифтов содержащихся в фонтсете использую такой код

#include <stdio.h>

#include <locale.h>
#include <langinfo.h>

#include <X11/Xlib.h>
#include <X11/Intrinsic.h>
#include <X11/Xatom.h>

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);

	XtSetLanguageProc(NULL, (XtLanguageProc) NULL, (XtPointer) NULL);

	Display* dpy = XOpenDisplay(NULL);

	char ** missingCharsets;
	int missingCharsetCount = 0;
	char * defaultString;
    char *fontList = "-misc-fixed-medium-r-*-*-14-*-*-*-*-*-*-*";
    if (argv[1] && argv[1][0]) {
        fontList = argv[1];
    }
	XFontSet fontSet = XCreateFontSet(dpy,
            fontList,
			&missingCharsets,
			&missingCharsetCount,
			&defaultString);

	printf("X11 locale: %s\n", XLocaleOfFontSet(fontSet));
	printf("XFontSet: %s\n", XBaseFontNameListOfFontSet(fontSet));

	if (missingCharsetCount > 0) {
		printf("Charsets missing from XFontSet:\n");
		for (int i = 0; i < missingCharsetCount; i++) {
			char * missingCharset = *(missingCharsets + i);
			printf("\t%s\n", missingCharset);
		}
		XFreeStringList(missingCharsets);
	}

	printf("XFontStruct entries in XFontSet:\n");

	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("\tXGetFontProperty(XA_FONT) failed\n");
			printf("\t%s\n", fallbackFontName);
		} else {
			char * const fontName = XGetAtomName(dpy, (Atom) fontNameAtom);
			printf("\t%s\n", fontName);
			XFree(fontName);
		}
	}

	XCloseDisplay(dpy);

	return 0;
}

fat_angel ★★★★★
() автор топика

Иксы немного мертвы. И некорректно — это как?

batya
()

Если же имеется только ISO10646, то некорректно выводится даже латиница (вместо нее иероглифы).

А что за шрифт такой? Можно примеры? А то, может, это какой-то специфический шрифт без латиницы под какой-то хитрый язык. Типа -mutt-clearlyu...

Zubok ★★★★★
()
Ответ на: комментарий от Zubok

GNU Unifont например. по умолчанию там ставиться pcf и ttf версии с соответствующими алиасами на недостающие кодировки. Если в fonts.dir оставить только pcf версию шрифта и убрать алиасы, оставив только строку

unifont.pcf.gz -gnu-unifont-medium-r-normal-sans-16-160-75-75-c-80-iso10646-1

то фонтсет будет содержать только ISO10646 шрифт. Вот вывод второй программы на моей системе:

% ./test-xcreatefontset '-gnu-unifont-medium-r-*-*-*-*-*-*-*-*-*-*'                                                                       
locale: LC_CTYPE=en_GB.UTF-8;LC_NUMERIC=C;LC_TIME=en_GB.UTF-8;LC_COLLATE=en_GB.UTF-8;LC_MONETARY=en_GB.UTF-8;LC_MESSAGES=en_GB.UTF-8;LC_PAPER=en_GB.UTF-8;LC_NAME=en_GB.UTF-8;LC_ADDRESS=en_GB.UTF-8;LC_TELEPHONE=en_GB.UTF-8;LC_MEASUREMENT=en_GB.UTF-8;LC_IDENTIFICATION=en_GB.UTF-8
run-time charset: UTF-8
X11 locale: en_GB.UTF-8
XFontSet: -gnu-unifont-medium-r-*-*-*-*-*-*-*-*-*-*
Charsets missing from XFontSet:
	ISO8859-1
	ISO8859-1
	ISO8859-2
	ISO8859-3
	ISO8859-4
	ISO8859-5
	KOI8-R
	ISO8859-7
	ISO8859-9
	ISO8859-13
	ISO8859-14
	ISO8859-15
	JISX0208.1983-0
	KSC5601.1987-0
	GB2312.1980-0
	JISX0201.1976-0
XFontStruct entries in XFontSet:
	-gnu-Unifont-Medium-R-Normal-Sans-16-160-75-75-c-80-iso10646-1

Соответственно XmbDrawString вместо, даже, латиницы рисует кашу (иероглифы на моей системе). Если в фонтсете есть соответствующие кодировки то все нормально.

Вот мне и непонятно как ISO8859-* или KOI8-R участвуют в рисовке юникодных строк.

fat_angel ★★★★★
() автор топика
Ответ на: комментарий от fat_angel

Вот мне и непонятно как ISO8859-* или KOI8-R участвуют в рисовке юникодных строк.

А вот, похоже, что так и есть. Тут один человечек пишет, что покопался в Xlib в части преобразования строчки из UTF-8. Утверждает, что Xlib почему-то пытается преобразовать строчку не к iso10646-1, хотя ты его и выбрал.

Вот ссылочка: https://blog.summercat.com/x-fonts-and-rpbar.html Смотри раздел под названием «Xlib and iso10646-1 charset fonts». Выдержка:

I figured this out through examining the Xlib code.

The reason is similar to the problem I describe with the problem characters: Xlib's conversion code does not care what charsets are available in the fontset you are using. It converts to various charsets, in order, based on those listed in the X Locale Database. If you then try to display text with your fontset, then it's just too bad if it converted to a charset not available in your fontset.

Take the character a. If we load only an ISO10646-1 charset font into the fontset, and then try to display it, Xlib takes the input as UTF-8 and converts it internally to ISO8859-1, and then tries to display it using this charset in your fontset. But your fontset does not have a charset listed for ISO8859-1 as that is one of the charsets the fontset is missing.

Even though Xlib would be able to convert the a to ISO10646-1, it accepts the match to ISO8859-1 first and does not try any others.

We might think that a is the same in both of these encodings (if we take ISO10646-1 to be UTF-8), but in Xlib this is not the case. I suspect it may be because internally ISO10646-1 is actually UCS-2, at least for fonts. There are comments to that effect in lcUTF8.c anyway.

You can see this is the problem by playing with lcUTF8.c's create_tofontcs_conv() where we set up the encodings to try.

Zubok ★★★★★
()
Последнее исправление: Zubok (всего исправлений: 2)
Ответ на: комментарий от Zubok

Причем такая же проблема вылезает и в Xaw. Вот тестовая прога:

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Xaw/Label.h>

int main(int argc, char* argv[])
{
    XtAppContext app_context;
    Widget toplevel, hello;

    XtSetLanguageProc(NULL, NULL, NULL);

    toplevel = XtVaAppInitialize(
        &app_context,
        "XHello",
        NULL, 0,
        &argc, argv,
        NULL,
        NULL);

    hello = XtVaCreateManagedWidget("hello", labelWidgetClass, toplevel, 
            XtNinternational, True, NULL);

    XtRealizeWidget(toplevel);
    XtAppMainLoop(app_context);
}

Тестировалось так:

% ./xaw-hello -xrm '*fontSet: -gnu-unifont-medium-r-*-*-*-*-*-*-*-*-*-*' -xrm '*.label: Goodbye world!'
то вылезают иероглифы. Если запускать так
./xaw-hello -xrm '*fontSet: -misc-fixed-medium-r-*-*-14-*-*-*-*-*-*-*' -xrm '*.label: Прощай мир!'
то все Ok.

fat_angel ★★★★★
() автор топика
Ответ на: комментарий от fat_angel

А вот в xterm не вылезает. И я думаю вот почему: они сами преобразовывают строчку в UTF-8 в XChar2b, а потом выводят XDrawString16. С unifont работает, я проверил.

$ xterm -fn "-gnu-unifont-medium-r-normal-sans-16-160-75-75-c-80-iso10646-1"
Zubok ★★★★★
()
Ответ на: комментарий от Zubok

О! XTerm был первым куда я полез смотреть и офигел от количества функций по работе с кодировками. Особенно улыбнул комментарий:

/*
 *				 W A R N I N G
 *
 * If you think you know what all of this code is doing, you are
 * probably very mistaken.  There be serious and nasty dragons here.
 *
 * This client is *not* to be taken as an example of how to write X
 * Toolkit applications.  It is in need of a substantial rewrite,
 * ideally to create a generic tty widget with several different parsing
 * widgets so that you can plug 'em together any way you want.  Don't
 * hold your breath, though....
 */

fat_angel ★★★★★
() автор топика
Последнее исправление: fat_angel (всего исправлений: 1)
Ответ на: комментарий от fat_angel

Ну это еще в Emacs загляни (в нем, к слову, тоже все выводится XDrawString16) :)

Zubok ★★★★★
()
Ответ на: комментарий от fat_angel

О! XTerm был первым куда я полез смотреть и офигел от количества функций по работе с кодировками. Особенно улыбнул комментарий:

Я очень бегло посмотрел. Мне кажется, что keysym2ucs.c имеет отношение к твоему случаю. И xutf8.c.

Zubok ★★★★★
()

Я бы на твоем месте забил бы на server-side fonts. Они были хороши, когда их настраивали на каждом X Terminal, когда каждые поставщики терминалов приносили свои шрифты и надо было отрисовывать X-клиентов не с тем шрифтом, которым ему хочется отрисоваться, а с тем, которые принят в среде этого XTerminal. Сейчас это уже неактуально. Хотя, конечно, клиентам было удобно не рендерить шрифты где-то у себя, а просто строчки передавать, указать название фонта, а голова пусть болит у X Server.

К тому же, server-side fonts имеют и недостаток - большое число раундтрипов. То клиенту нужно метрику запросить у сервера, то еще что. В случае же client-side fonts, да, на X-server будет рисоваться так, как хочет клиент, но зато нет раундтрипов, потому что шрифты на стороне клиента. Ну и плюс сглаживание, хинтинг и пр. С server-side fonts это все не доделали и бросили. Были попытки Sun, но бросили.

Так что, используй лучше Xft, как это делает и xterm и Emacs (хотя они могут и по-старому, без Xft) и все вообще, кроме старых тулкитов типа Xaw.

Zubok ★★★★★
()
Последнее исправление: Zubok (всего исправлений: 3)
Ответ на: комментарий от Zubok

Мдээ... Спасибо за статейку, познавательно. После прочтения возникла мысль, а не подправить ли XLC_LOCALE и выпилить оттуда все проверки кроме ISO10646?..

fat_angel ★★★★★
() автор топика
Ответ на: комментарий от Zubok

Ну с Xft это понятно, что никаких проблем, тут скорее из чистого эстетства захотелось разобраться с нормальным отображение server-side fonts.

fat_angel ★★★★★
() автор топика
Ответ на: комментарий от fat_angel

После прочтения возникла мысль, а не подправить ли XLC_LOCALE и выпилить оттуда все проверки кроме ISO10646?..

Можешь еще, кстати, глянуть на код xfontsel. Если там выбираешь -gnu-unifont, то он его отображает. А так как программка небольшая, то можно оттуда подчерпнуть. Она на Xaw

Zubok ★★★★★
()
Последнее исправление: Zubok (всего исправлений: 2)
Ответ на: комментарий от Zubok

Можешь еще, кстати, глянуть на код xfontsel

Тоже мысль. Хотел посмотреть но руки не дошли. Завтра гляну.

В любом случае проблему с использование шрифтов из других локалей удалось победить правкой XLC_LOCALE. Я упражнялся на /usr/share/X11/locale/ru_RU.UTF-8/XLC_LOCALE. После приведения его к виду

#
# 	XLC_FONTSET category
#
XLC_FONTSET

on_demand_loading	True

object_name		generic

# 	fs0 class
fs0	{
	charset	{
		name	ISO10646-1
	}
	font	{
		primary	ISO10646-1
	}
}
END XLC_FONTSET

#
# 	XLC_XLOCALE category
#
XLC_XLOCALE

encoding_name		UTF-8
mb_cur_max		6
state_depend_encoding	False

#	cs0 class
cs0 {
	side		none
	ct_encoding	ISO10646-1
}

END XLC_XLOCALE
вышеприведенные примеры работают нормально. Так же в создаваемых фонтсетах нет отсутствующих чарсетов. Еще протестирую, но думаю проблема решена.

fat_angel ★★★★★
() автор топика
Последнее исправление: fat_angel (всего исправлений: 1)
Ответ на: комментарий от Zubok

Кстати в файлике /usr/share/X11/locale/locale.dir нашел еще один замечательный комментарий:

# Note: The UTF-8 locales don't work correctly yet. Work in progress.

fat_angel ★★★★★
() автор топика
Ответ на: комментарий от fat_angel

Завтра гляну.

Там можно произвольную строчку указывать, если что. Параметры -sample, -sample16 и -sampleUCS.

Вот так работают шрифты, у которых есть iso10646-1 (в том числе и -gnu-unifont-*):

xfontsel -sampleUCS "абвгдейка"
Zubok ★★★★★
()
Ответ на: комментарий от Zubok

Там можно произвольную строчку указывать, если что. Параметры -sample, -sample16 и -sampleUCS.

Вот, собственно, процедура:

static void _XawLabelDrawUCS(Display *dpy, Drawable d, GC gc,
			     int x, int y, char *str, int n)
{
    char *ep;
    unsigned short codepoint;
    XChar2b *ptr;

    /*
     * Convert to UCS2 string on the fly.
     */

    if (n > buf2blen) {
	buf2b = (XChar2b *)XtRealloc((char *)buf2b, n * sizeof(XChar2b));
	buf2blen = n;
    }
    ep = str + n;
    for (ptr = buf2b; str < ep; ptr++) {
        if((str[0]&0x80)==0) {
            codepoint=str[0];
            str++;
        } else if((str[0]&0x20)==0) {
            codepoint=(str[0]&0x1F)<<6 | (str[1]&0x3F);
            str+=2;
        } else if((str[0]&0x10)==0) {
            codepoint=(str[0]&0x0F)<<12 | (str[1]&0x3F)<<6 | (str[2]&0x3F);
            str+=3;
        } else {                    /* wrong UTF-8 */
            codepoint=(unsigned)'?';
            str++;
        }
	ptr->byte1 = (codepoint >> 8) & 0xff;;
	ptr->byte2 = codepoint & 0xff;
    }
    XDrawString16(dpy, d, gc, x, y, buf2b, ptr - buf2b);
}
Zubok ★★★★★
()
Последнее исправление: Zubok (всего исправлений: 1)
Ответ на: комментарий от Zubok

Спасибо. Остается неясным как при такой методике можно вывести символы >65536.

И меня не покидает ощущение некоторой костыльности подобного метода вывода. XmbDrawString с правкой XLC_LOCALE представляются более правильным способом.

fat_angel ★★★★★
() автор топика
Ответ на: комментарий от fat_angel

Спасибо. Остается неясным как при такой методике можно вывести символы >65536.

А где ты их возьмешь? -iso10646-1 — это только BMP.

$ man unicode

The UCS standard (ISO 10646) describes a 31-bit character set architec‐
ture  consisting  of  128  24-bit  groups, each divided into 256 16-bit
planes made up of 256 8-bit rows with 256  column  positions,  one  for
each character.  Part 1 of the standard (ISO 10646-1) defines the first
65534 code positions (0x0000 to 0xfffd), which form the Basic Multilin‐
gual  Plane  (BMP), that is plane 0 in group 0.

UPD. К слову, xlib внутри своих функций оперирует UCS-4, а не UCS-2 (перекодирование из UTF-8 в UCS-2 и UCS-4 - это RFC 2279). см. преобразование /lib/libX11/src/xlibi18n/lcUniConv/utf8.h

Zubok ★★★★★
()
Последнее исправление: Zubok (всего исправлений: 2)
Ответ на: комментарий от fat_angel

И меня не покидает ощущение некоторой костыльности подобного метода вывода. XmbDrawString с правкой XLC_LOCALE представляются более правильным способом.

Вообще, конечно, это был бы хороший вопрос в девелоперскую рассылку xorg. Рассказать про ситуацию, что фонт имеет только iso10646-1 и что до него дело не доходит.

Я уверен, что такое поведение, какое есть, оно неспроста. Видимо, оно свзяано с Compound Text Encoding, которая используется в X11 (это связано со стандартном ISO 2022). Это все было разработано еще тогда, когда уникода никакого не внедрялось.

Zubok ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.