]> asedeno.scripts.mit.edu Git - PuTTY.git/commitdiff
Refactor the font handling code: I've moved all the code that
authorSimon Tatham <anakin@pobox.com>
Sat, 22 Mar 2008 11:40:23 +0000 (11:40 +0000)
committerSimon Tatham <anakin@pobox.com>
Sat, 22 Mar 2008 11:40:23 +0000 (11:40 +0000)
explicitly deals with GdkFont out into a new module, behind a
polymorphic interface (done by ad-hoc explicit vtable management in
C). This should allow me to drop in a Pango font handling module in
parallel with the existing one, meaning that GTK2 PuTTY will be able
to seamlessly switch between X11 server-side fonts and Pango client-
side ones as the user chooses, or even use a mixture of the two
(e.g. an X11 font for narrow characters and a Pango one for wide
characters, or vice versa).

In the process, incidentally, I got to the bottom of the `weird bug'
mentioned in the old do_text_internal(). It's not a bug in
gdk_draw_text_wc() as I had thought: it's simply that GdkWChar is a
32-bit type rather than a 16-bit one, so no wonder you have to
specify twice the length to find all the characters in the string!
However, there _is_ a bug in GTK2's gdk_draw_text_wc(), which causes
it to strip off everything above the low byte of each GdkWChar,
sigh. Solution to both problems is to use an array of the underlying
Xlib type XChar2b instead, and pass it to gdk_draw_text() cast to
gchar *. Grotty, but it works. (And it'll become significantly less
grotty if and when we have to stop using the GDK font handling
wrappers in favour of going direct to Xlib.)

[originally from svn r7933]

Recipe
unix/gtkfont.c [new file with mode: 0644]
unix/gtkfont.h [new file with mode: 0644]
unix/gtkwin.c

diff --git a/Recipe b/Recipe
index fab512bcc11c885e2f2eb44347877e4786d24890..8854c402e1cd5097c3493f5c4762527656c8daaa 100644 (file)
--- a/Recipe
+++ b/Recipe
@@ -245,7 +245,7 @@ GUITERM  = TERMINAL window windlg winctrls sizetip winucs winprint
 
 # Same thing on Unix.
 UXTERM   = TERMINAL uxcfg sercfg uxucs uxprint timing
-GTKTERM  = UXTERM gtkwin gtkcfg gtkdlg gtkcols xkeysym
+GTKTERM  = UXTERM gtkwin gtkcfg gtkdlg gtkfont gtkcols xkeysym
 OSXTERM  = UXTERM osxwin osxdlg osxctrls
 
 # Non-SSH back ends (putty, puttytel, plink).
diff --git a/unix/gtkfont.c b/unix/gtkfont.c
new file mode 100644 (file)
index 0000000..8fabdd2
--- /dev/null
@@ -0,0 +1,435 @@
+/*
+ * Unified font management for GTK.
+ * 
+ * PuTTY is willing to use both old-style X server-side bitmap
+ * fonts _and_ GTK2/Pango client-side fonts. This requires us to
+ * do a bit of work to wrap the two wildly different APIs into
+ * forms the rest of the code can switch between seamlessly, and
+ * also requires a custom font selector capable of handling both
+ * types of font.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <gdk/gdkx.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+
+#include "putty.h"
+#include "gtkfont.h"
+
+/*
+ * To do:
+ * 
+ *  - import flags to do VT100 double-width, and import the icky
+ *    pixmap stretch code for it.
+ * 
+ *  - add the Pango back end!
+ */
+
+/*
+ * Future work:
+ * 
+ *  - all the GDK font functions used in the x11font subclass are
+ *    deprecated, so one day they may go away. When this happens -
+ *    or before, if I'm feeling proactive - it oughtn't to be too
+ *    difficult in principle to convert the whole thing to use
+ *    actual Xlib font calls.
+ */
+
+/*
+ * Ad-hoc vtable mechanism to allow font structures to be
+ * polymorphic.
+ * 
+ * Any instance of `unifont' used in the vtable functions will
+ * actually be the first element of a larger structure containing
+ * data specific to the subtype. This is permitted by the ISO C
+ * provision that one may safely cast between a pointer to a
+ * structure and a pointer to its first element.
+ */
+
+struct unifont_vtable {
+    /*
+     * `Methods' of the `class'.
+     */
+    unifont *(*create)(char *name, int wide, int bold,
+                      int shadowoffset, int shadowalways);
+    void (*destroy)(unifont *font);
+    void (*draw_text)(GdkDrawable *target, GdkGC *gc, unifont *font,
+                     int x, int y, const char *string, int len, int wide,
+                     int bold);
+    /*
+     * `Static data members' of the `class'.
+     */
+    const char *prefix;
+};
+
+/* ----------------------------------------------------------------------
+ * GDK-based X11 font implementation.
+ */
+
+static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
+                             int x, int y, const char *string, int len,
+                             int wide, int bold);
+static unifont *x11font_create(char *name, int wide, int bold,
+                              int shadowoffset, int shadowalways);
+static void x11font_destroy(unifont *font);
+
+struct x11font {
+    struct unifont u;
+    /*
+     * Actual font objects. We store a number of these, for
+     * automatically guessed bold and wide variants.
+     * 
+     * The parallel array `allocated' indicates whether we've
+     * tried to fetch a subfont already (thus distinguishing NULL
+     * because we haven't tried yet from NULL because we tried and
+     * failed, so that we don't keep trying and failing
+     * subsequently).
+     */
+    GdkFont *fonts[4];
+    int allocated[4];
+    /*
+     * `sixteen_bit' is true iff the font object is indexed by
+     * values larger than a byte. That is, this flag tells us
+     * whether we use gdk_draw_text_wc() or gdk_draw_text().
+     */
+    int sixteen_bit;
+    /*
+     * Font charsets. public_charset and real_charset can differ
+     * for X11 fonts, because many X fonts use CS_ISO8859_1_X11.
+     */
+    int public_charset, real_charset;
+    /*
+     * Data passed in to unifont_create().
+     */
+    int wide, bold, shadowoffset, shadowalways;
+};
+
+static const struct unifont_vtable x11font_vtable = {
+    x11font_create,
+    x11font_destroy,
+    x11font_draw_text,
+    "x11"
+};
+
+char *x11_guess_derived_font_name(GdkFont *font, int bold, int wide)
+{
+    XFontStruct *xfs = GDK_FONT_XFONT(font);
+    Display *disp = GDK_FONT_XDISPLAY(font);
+    Atom fontprop = XInternAtom(disp, "FONT", False);
+    unsigned long ret;
+    if (XGetFontProperty(xfs, fontprop, &ret)) {
+       char *name = XGetAtomName(disp, (Atom)ret);
+       if (name && name[0] == '-') {
+           char *strings[13];
+           char *dupname, *extrafree = NULL, *ret;
+           char *p, *q;
+           int nstr;
+
+           p = q = dupname = dupstr(name); /* skip initial minus */
+           nstr = 0;
+
+           while (*p && nstr < lenof(strings)) {
+               if (*p == '-') {
+                   *p = '\0';
+                   strings[nstr++] = p+1;
+               }
+               p++;
+           }
+
+           if (nstr < lenof(strings))
+               return NULL;           /* XLFD was malformed */
+
+           if (bold)
+               strings[2] = "bold";
+
+           if (wide) {
+               /* 4 is `wideness', which obviously may have changed. */
+               /* 5 is additional style, which may be e.g. `ja' or `ko'. */
+               strings[4] = strings[5] = "*";
+               strings[11] = extrafree = dupprintf("%d", 2*atoi(strings[11]));
+           }
+
+           ret = dupcat("-", strings[ 0], "-", strings[ 1], "-", strings[ 2],
+                        "-", strings[ 3], "-", strings[ 4], "-", strings[ 5],
+                        "-", strings[ 6], "-", strings[ 7], "-", strings[ 8],
+                        "-", strings[ 9], "-", strings[10], "-", strings[11],
+                        "-", strings[12], NULL);
+           sfree(extrafree);
+           sfree(dupname);
+
+           return ret;
+       }
+    }
+    return NULL;
+}
+
+static int x11_font_width(GdkFont *font, int sixteen_bit)
+{
+    if (sixteen_bit) {
+       XChar2b space;
+       space.byte1 = 0;
+       space.byte2 = ' ';
+       return gdk_text_width(font, (const gchar *)&space, 2);
+    } else {
+       return gdk_char_width(font, ' ');
+    }
+}
+
+static unifont *x11font_create(char *name, int wide, int bold,
+                              int shadowoffset, int shadowalways)
+{
+    struct x11font *xfont;
+    GdkFont *font;
+    XFontStruct *xfs;
+    Display *disp;
+    Atom charset_registry, charset_encoding;
+    unsigned long registry_ret, encoding_ret;
+    int pubcs, realcs, sixteen_bit;
+    int i;
+
+    font = gdk_font_load(name);
+    if (!font)
+       return NULL;
+
+    xfs = GDK_FONT_XFONT(font);
+    disp = GDK_FONT_XDISPLAY(font);
+
+    charset_registry = XInternAtom(disp, "CHARSET_REGISTRY", False);
+    charset_encoding = XInternAtom(disp, "CHARSET_ENCODING", False);
+
+    pubcs = realcs = CS_NONE;
+    sixteen_bit = FALSE;
+
+    if (XGetFontProperty(xfs, charset_registry, &registry_ret) &&
+       XGetFontProperty(xfs, charset_encoding, &encoding_ret)) {
+       char *reg, *enc;
+       reg = XGetAtomName(disp, (Atom)registry_ret);
+       enc = XGetAtomName(disp, (Atom)encoding_ret);
+       if (reg && enc) {
+           char *encoding = dupcat(reg, "-", enc, NULL);
+           pubcs = realcs = charset_from_xenc(encoding);
+
+           /*
+            * iso10646-1 is the only wide font encoding we
+            * support. In this case, we expect clients to give us
+            * UTF-8, which this module must internally convert
+            * into 16-bit Unicode.
+            */
+           if (!strcasecmp(encoding, "iso10646-1")) {
+               sixteen_bit = TRUE;
+               pubcs = realcs = CS_UTF8;
+           }
+
+           /*
+            * Hack for X line-drawing characters: if the primary
+            * font is encoded as ISO-8859-1, and has valid glyphs
+            * in the first 32 char positions, it is assumed that
+            * those glyphs are the VT100 line-drawing character
+            * set.
+            * 
+            * Actually, we'll hack even harder by only checking
+            * position 0x19 (vertical line, VT100 linedrawing
+            * `x'). Then we can check it easily by seeing if the
+            * ascent and descent differ.
+            */
+           if (pubcs == CS_ISO8859_1) {
+               int lb, rb, wid, asc, desc;
+               gchar text[2];
+
+               text[1] = '\0';
+               text[0] = '\x12';
+               gdk_string_extents(font, text, &lb, &rb, &wid, &asc, &desc);
+               if (asc != desc)
+                   realcs = CS_ISO8859_1_X11;
+           }
+
+           sfree(encoding);
+       }
+    }
+
+    xfont = snew(struct x11font);
+    xfont->u.vt = &x11font_vtable;
+    xfont->u.width = x11_font_width(font, sixteen_bit);
+    xfont->u.ascent = font->ascent;
+    xfont->u.descent = font->descent;
+    xfont->u.height = xfont->u.ascent + xfont->u.descent;
+    xfont->u.public_charset = pubcs;
+    xfont->u.real_charset = realcs;
+    xfont->fonts[0] = font;
+    xfont->allocated[0] = TRUE;
+    xfont->sixteen_bit = sixteen_bit;
+    xfont->wide = wide;
+    xfont->bold = bold;
+    xfont->shadowoffset = shadowoffset;
+    xfont->shadowalways = shadowalways;
+
+    for (i = 1; i < lenof(xfont->fonts); i++) {
+       xfont->fonts[i] = NULL;
+       xfont->allocated[i] = FALSE;
+    }
+
+    return (unifont *)xfont;
+}
+
+static void x11font_destroy(unifont *font)
+{
+    struct x11font *xfont = (struct x11font *)font;
+    int i;
+
+    for (i = 0; i < lenof(xfont->fonts); i++)
+       if (xfont->fonts[i])
+           gdk_font_unref(xfont->fonts[i]);
+    sfree(font);
+}
+
+static void x11_alloc_subfont(struct x11font *xfont, int sfid)
+{
+    char *derived_name = x11_guess_derived_font_name
+       (xfont->fonts[0], sfid & 1, !!(sfid & 2));
+    xfont->fonts[sfid] = gdk_font_load(derived_name);   /* may be NULL */
+    xfont->allocated[sfid] = TRUE;
+    sfree(derived_name);
+}
+
+static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
+                             int x, int y, const char *string, int len,
+                             int wide, int bold)
+{
+    struct x11font *xfont = (struct x11font *)font;
+    int sfid;
+    int shadowbold = FALSE;
+
+    wide -= xfont->wide;
+    bold -= xfont->bold;
+
+    /*
+     * Decide which subfont we're using, and whether we have to
+     * use shadow bold.
+     */
+    if (xfont->shadowalways && bold) {
+       shadowbold = TRUE;
+       bold = 0;
+    }
+    sfid = 2 * wide + bold;
+    if (!xfont->allocated[sfid])
+       x11_alloc_subfont(xfont, sfid);
+    if (bold && !xfont->fonts[sfid]) {
+       bold = 0;
+       shadowbold = TRUE;
+       sfid = 2 * wide + bold;
+       if (!xfont->allocated[sfid])
+           x11_alloc_subfont(xfont, sfid);
+    }
+
+    if (!xfont->fonts[sfid])
+       return;                        /* we've tried our best, but no luck */
+
+    if (xfont->sixteen_bit) {
+       /*
+        * This X font has 16-bit character indices, which means
+        * we expect our string to have been passed in UTF-8.
+        */
+       XChar2b *xcs;
+       wchar_t *wcs;
+       int nchars, maxchars, i;
+
+       /*
+        * Convert the input string to wide-character Unicode.
+        */
+       maxchars = 0;
+       for (i = 0; i < len; i++)
+           if ((unsigned char)string[i] <= 0x7F ||
+               (unsigned char)string[i] >= 0xC0)
+               maxchars++;
+       wcs = snewn(maxchars+1, wchar_t);
+       nchars = charset_to_unicode((char **)&string, &len, wcs, maxchars,
+                                   CS_UTF8, NULL, NULL, 0);
+       assert(nchars <= maxchars);
+       wcs[nchars] = L'\0';
+
+       xcs = snewn(nchars, XChar2b);
+       for (i = 0; i < nchars; i++) {
+           xcs[i].byte1 = wcs[i] >> 8;
+           xcs[i].byte2 = wcs[i];
+       }
+
+       gdk_draw_text(target, xfont->fonts[sfid], gc,
+                     x, y, (gchar *)xcs, nchars*2);
+       if (shadowbold)
+           gdk_draw_text(target, xfont->fonts[sfid], gc,
+                         x + xfont->shadowoffset, y, (gchar *)xcs, nchars*2);
+       sfree(xcs);
+       sfree(wcs);
+    } else {
+       gdk_draw_text(target, xfont->fonts[sfid], gc, x, y, string, len);
+       if (shadowbold)
+           gdk_draw_text(target, xfont->fonts[sfid], gc,
+                         x + xfont->shadowoffset, y, string, len);
+    }
+}
+
+/* ----------------------------------------------------------------------
+ * Outermost functions which do the vtable dispatch.
+ */
+
+/*
+ * This function is the only one which needs to know the full set
+ * of font implementations available, because it has to try each
+ * in turn until one works, or condition on an override prefix in
+ * the font name.
+ */
+static const struct unifont_vtable *unifont_types[] = {
+    &x11font_vtable,
+};
+unifont *unifont_create(char *name, int wide, int bold,
+                       int shadowoffset, int shadowalways)
+{
+    int colonpos = strcspn(name, ":");
+    int i;
+
+    if (name[colonpos]) {
+       /*
+        * There's a colon prefix on the font name. Use it to work
+        * out which subclass to try to create.
+        */
+       for (i = 0; i < lenof(unifont_types); i++) {
+           if (strlen(unifont_types[i]->prefix) == colonpos &&
+               !strncmp(unifont_types[i]->prefix, name, colonpos))
+               break;
+       }
+       if (i == lenof(unifont_types))
+           return NULL;               /* prefix not recognised */
+       return unifont_types[i]->create(name+colonpos+1, wide, bold,
+                                       shadowoffset, shadowalways);
+    } else {
+       /*
+        * No colon prefix, so just go through all the subclasses.
+        */
+       for (i = 0; i < lenof(unifont_types); i++) {
+           unifont *ret = unifont_types[i]->create(name, wide, bold,
+                                                   shadowoffset,
+                                                   shadowalways);
+           if (ret)
+               return ret;
+       }
+       return NULL;                   /* font not found in any scheme */
+    }
+}
+
+void unifont_destroy(unifont *font)
+{
+    font->vt->destroy(font);
+}
+
+void unifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
+                      int x, int y, const char *string, int len,
+                      int wide, int bold)
+{
+    font->vt->draw_text(target, gc, font, x, y, string, len, wide, bold);
+}
diff --git a/unix/gtkfont.h b/unix/gtkfont.h
new file mode 100644 (file)
index 0000000..9cb6678
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Header file for gtkfont.c. Has to be separate from unix.h
+ * because it depends on GTK data types, hence can't be included
+ * from cross-platform code (which doesn't go near GTK).
+ */
+
+#ifndef PUTTY_GTKFONT_H
+#define PUTTY_GTKFONT_H
+
+/*
+ * Exports from gtkfont.c.
+ */
+struct unifont_vtable;                /* contents internal to gtkfont.c */
+typedef struct unifont {
+    const struct unifont_vtable *vt;
+    /*
+     * `Non-static data members' of the `class', accessible to
+     * external code.
+     */
+
+    /*
+     * public_charset is the charset used when the user asks for
+     * `Use font encoding'.
+     * 
+     * real_charset is the charset used when translating text into
+     * a form suitable for sending to unifont_draw_text().
+     * 
+     * They can differ. For example, public_charset might be
+     * CS_ISO8859_1 while real_charset is CS_ISO8859_1_X11.
+     */
+    int public_charset, real_charset;
+
+    /*
+     * Font dimensions needed by clients.
+     */
+    int width, height, ascent, descent;
+} unifont;
+
+unifont *unifont_create(char *name, int wide, int bold,
+                       int shadowoffset, int shadowalways);
+void unifont_destroy(unifont *font);
+void unifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
+                      int x, int y, const char *string, int len,
+                      int wide, int bold);
+
+#endif /* PUTTY_GTKFONT_H */
index e6ff13c352c0d9066c0a9c32cfc96d438e26ea4a..2972dba11a5fba3145cb3fd712f638445ff58198 100644 (file)
@@ -28,6 +28,7 @@
 
 #include "putty.h"
 #include "terminal.h"
+#include "gtkfont.h"
 
 #define CAT2(x,y) x ## y
 #define CAT(x,y) CAT2(x,y)
@@ -58,11 +59,7 @@ struct gui_data {
        *restartitem;
     GtkWidget *sessionsmenu;
     GdkPixmap *pixmap;
-    GdkFont *fonts[4];                 /* normal, bold, wide, widebold */
-    struct {
-       int charset;
-       int is_wide;
-    } fontinfo[4];
+    unifont *fonts[4];                 /* normal, bold, wide, widebold */
     int xpos, ypos, gotpos, gravity;
     GdkCursor *rawcursor, *textcursor, *blankcursor, *waitcursor, *currcursor;
     GdkColor cols[NALLCOLOURS];
@@ -1880,6 +1877,8 @@ int char_width(Context ctx, int uc)
      * Under X, any fixed-width font really _is_ fixed-width.
      * Double-width characters will be dealt with using a separate
      * font. For the moment we can simply return 1.
+     * 
+     * FIXME: but is that also true of Pango?
      */
     return 1;
 }
@@ -1920,7 +1919,7 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len,
     struct gui_data *inst = dctx->inst;
     GdkGC *gc = dctx->gc;
     int ncombining, combining;
-    int nfg, nbg, t, fontid, shadow, rlen, widefactor;
+    int nfg, nbg, t, fontid, shadow, rlen, widefactor, bold;
     int monochrome = gtk_widget_get_visual(inst->area)->depth == 1;
 
     if (attr & TATTR_COMBINING) {
@@ -1959,10 +1958,27 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len,
     }
 
     if ((attr & ATTR_BOLD) && !inst->cfg.bold_colour) {
-       if (inst->fonts[fontid | 1])
-           fontid |= 1;
-       else
-           shadow = 1;
+       bold = 1;
+       fontid |= 1;
+    } else {
+       bold = 0;
+    }
+
+    if (!inst->fonts[fontid]) {
+       int i;
+       /*
+        * Fall back through font ids with subsets of this one's
+        * set bits, in order.
+        */
+       for (i = fontid; i-- > 0 ;) {
+           if (i & ~fontid)
+               continue;              /* some other bit is set */
+           if (inst->fonts[i]) {
+               fontid = i;
+               break;
+           }
+       }
+       assert(inst->fonts[fontid]);   /* we should at least have hit zero */
     }
 
     if ((lattr & LATTR_MODE) != LATTR_NORM) {
@@ -1993,82 +2009,27 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len,
 
     gdk_gc_set_foreground(gc, &inst->cols[nfg]);
     {
-       GdkWChar *gwcs;
        gchar *gcs;
-       wchar_t *wcs;
-       int i;
-
-       wcs = snewn(len*ncombining+1, wchar_t);
-       for (i = 0; i < len*ncombining; i++) {
-           wcs[i] = text[i];
-       }
 
-       if (inst->fonts[fontid] == NULL && (fontid & 2)) {
-           /*
-            * We've been given ATTR_WIDE, but have no wide font.
-            * Fall back to the non-wide font.
-            */
-           fontid &= ~2;
-       }
+       /*
+        * FIXME: this length is hardwired on the assumption that
+        * conversions from wide to multibyte characters will
+        * never generate more than 10 bytes for a single wide
+        * character.
+        */
+       gcs = snewn(len*10+1, gchar);
 
-       if (inst->fonts[fontid] == NULL) {
-           /*
-            * The font for this contingency does not exist. So we
-            * display nothing at all; such is life.
-            */
-       } else if (inst->fontinfo[fontid].is_wide) {
-           /*
-            * At least one version of gdk_draw_text_wc() has a
-            * weird bug whereby it reads `len' elements of the
-            * input string, but only draws `len/2'. Hence I'm
-            * going to make its input array twice as long as it
-            * theoretically needs to be, and pass in twice the
-            * actual number of characters. If a fixed gdk actually
-            * takes the doubled length seriously, then (a) the
-            * array will stand scrutiny up to the full length, (b)
-            * the spare elements of the array are full of zeroes
-            * which will probably be an empty glyph in the font,
-            * and (c) the clip rectangle should prevent it causing
-            * trouble anyway.
-            */
-           gwcs = snewn(len*2+1, GdkWChar);
-           memset(gwcs, 0, sizeof(GdkWChar) * (len*2+1));
-           /*
-            * FIXME: when we have a wide-char equivalent of
-            * from_unicode, use it instead of this.
-            */
-           for (combining = 0; combining < ncombining; combining++) {
-               for (i = 0; i <= len; i++)
-                   gwcs[i] = wcs[i + combining];
-               gdk_draw_text_wc(inst->pixmap, inst->fonts[fontid], gc,
-                                x*inst->font_width+inst->cfg.window_border,
-                                y*inst->font_height+inst->cfg.window_border+inst->fonts[0]->ascent,
-                                gwcs, len*2);
-               if (shadow)
-                   gdk_draw_text_wc(inst->pixmap, inst->fonts[fontid], gc,
-                                    x*inst->font_width+inst->cfg.window_border+inst->cfg.shadowboldoffset,
-                                    y*inst->font_height+inst->cfg.window_border+inst->fonts[0]->ascent,
-                                    gwcs, len*2);
-           }
-           sfree(gwcs);
-       } else {
-           gcs = snewn(len+1, gchar);
-           for (combining = 0; combining < ncombining; combining++) {
-               wc_to_mb(inst->fontinfo[fontid].charset, 0,
-                        wcs + combining, len, gcs, len, ".", NULL, NULL);
-               gdk_draw_text(inst->pixmap, inst->fonts[fontid], gc,
+       for (combining = 0; combining < ncombining; combining++) {
+           int mblen = wc_to_mb(inst->fonts[fontid]->real_charset, 0,
+                                text + combining, len, gcs, len*10+1, ".",
+                                NULL, NULL);
+           unifont_draw_text(inst->pixmap, gc, inst->fonts[fontid],
                              x*inst->font_width+inst->cfg.window_border,
                              y*inst->font_height+inst->cfg.window_border+inst->fonts[0]->ascent,
-                             gcs, len);
-               if (shadow)
-                   gdk_draw_text(inst->pixmap, inst->fonts[fontid], gc,
-                                 x*inst->font_width+inst->cfg.window_border+inst->cfg.shadowboldoffset,
-                                 y*inst->font_height+inst->cfg.window_border+inst->fonts[0]->ascent,
-                                 gcs, len);
-           }
-           sfree(gcs);
+                             gcs, mblen, widefactor > 1, bold);
        }
-       sfree(wcs);
+
+       sfree(gcs);
     }
 
     if (attr & ATTR_UNDER) {
@@ -2633,70 +2594,6 @@ int do_cmdline(int argc, char **argv, int do_everything, int *allow_launch,
     return err;
 }
 
-/*
- * This function retrieves the character set encoding of a font. It
- * returns the character set without the X11 hack (in case the user
- * asks to use the font's own encoding).
- */
-static int set_font_info(struct gui_data *inst, int fontid)
-{
-    GdkFont *font = inst->fonts[fontid];
-    XFontStruct *xfs = GDK_FONT_XFONT(font);
-    Display *disp = GDK_FONT_XDISPLAY(font);
-    Atom charset_registry, charset_encoding;
-    unsigned long registry_ret, encoding_ret;
-    int retval = CS_NONE;
-
-    charset_registry = XInternAtom(disp, "CHARSET_REGISTRY", False);
-    charset_encoding = XInternAtom(disp, "CHARSET_ENCODING", False);
-    inst->fontinfo[fontid].charset = CS_NONE;
-    inst->fontinfo[fontid].is_wide = 0;
-    if (XGetFontProperty(xfs, charset_registry, &registry_ret) &&
-       XGetFontProperty(xfs, charset_encoding, &encoding_ret)) {
-       char *reg, *enc;
-       reg = XGetAtomName(disp, (Atom)registry_ret);
-       enc = XGetAtomName(disp, (Atom)encoding_ret);
-       if (reg && enc) {
-           char *encoding = dupcat(reg, "-", enc, NULL);
-           retval = inst->fontinfo[fontid].charset =
-               charset_from_xenc(encoding);
-           /* FIXME: when libcharset supports wide encodings fix this. */
-           if (!strcasecmp(encoding, "iso10646-1")) {
-               inst->fontinfo[fontid].is_wide = 1;
-               retval = CS_UTF8;
-           }
-
-           /*
-            * Hack for X line-drawing characters: if the primary
-            * font is encoded as ISO-8859-anything, and has valid
-            * glyphs in the first 32 char positions, it is assumed
-            * that those glyphs are the VT100 line-drawing
-            * character set.
-            * 
-            * Actually, we'll hack even harder by only checking
-            * position 0x19 (vertical line, VT100 linedrawing
-            * `x'). Then we can check it easily by seeing if the
-            * ascent and descent differ.
-            */
-           if (inst->fontinfo[fontid].charset == CS_ISO8859_1) {
-               int lb, rb, wid, asc, desc;
-               gchar text[2];
-
-               text[1] = '\0';
-               text[0] = '\x12';
-               gdk_string_extents(inst->fonts[fontid], text,
-                                  &lb, &rb, &wid, &asc, &desc);
-               if (asc != desc)
-                   inst->fontinfo[fontid].charset = CS_ISO8859_1_X11;
-           }
-
-           sfree(encoding);
-       }
-    }
-
-    return retval;
-}
-
 int uxsel_input_add(int fd, int rwx) {
     int flags = 0;
     if (rwx & 1) flags |= GDK_INPUT_READ;
@@ -2710,173 +2607,71 @@ void uxsel_input_remove(int id) {
     gdk_input_remove(id);
 }
 
-char *guess_derived_font_name(GdkFont *font, int bold, int wide)
-{
-    XFontStruct *xfs = GDK_FONT_XFONT(font);
-    Display *disp = GDK_FONT_XDISPLAY(font);
-    Atom fontprop = XInternAtom(disp, "FONT", False);
-    unsigned long ret;
-    if (XGetFontProperty(xfs, fontprop, &ret)) {
-       char *name = XGetAtomName(disp, (Atom)ret);
-       if (name && name[0] == '-') {
-           char *strings[13];
-           char *dupname, *extrafree = NULL, *ret;
-           char *p, *q;
-           int nstr;
-
-           p = q = dupname = dupstr(name); /* skip initial minus */
-           nstr = 0;
-
-           while (*p && nstr < lenof(strings)) {
-               if (*p == '-') {
-                   *p = '\0';
-                   strings[nstr++] = p+1;
-               }
-               p++;
-           }
-
-           if (nstr < lenof(strings))
-               return NULL;           /* XLFD was malformed */
-
-           if (bold)
-               strings[2] = "bold";
-
-           if (wide) {
-               /* 4 is `wideness', which obviously may have changed. */
-               /* 5 is additional style, which may be e.g. `ja' or `ko'. */
-               strings[4] = strings[5] = "*";
-               strings[11] = extrafree = dupprintf("%d", 2*atoi(strings[11]));
-           }
-
-           ret = dupcat("-", strings[ 0], "-", strings[ 1], "-", strings[ 2],
-                        "-", strings[ 3], "-", strings[ 4], "-", strings[ 5],
-                        "-", strings[ 6], "-", strings[ 7], "-", strings[ 8],
-                        "-", strings[ 9], "-", strings[10], "-", strings[11],
-                        "-", strings[12], NULL);
-           sfree(extrafree);
-           sfree(dupname);
-
-           return ret;
-       }
-    }
-    return NULL;
-}
-
 void setup_fonts_ucs(struct gui_data *inst)
 {
-    int font_charset;
-    char *name;
-    int guessed;
-
     if (inst->fonts[0])
-        gdk_font_unref(inst->fonts[0]);
+        unifont_destroy(inst->fonts[0]);
     if (inst->fonts[1])
-        gdk_font_unref(inst->fonts[1]);
+        unifont_destroy(inst->fonts[1]);
     if (inst->fonts[2])
-        gdk_font_unref(inst->fonts[2]);
+        unifont_destroy(inst->fonts[2]);
     if (inst->fonts[3])
-        gdk_font_unref(inst->fonts[3]);
+        unifont_destroy(inst->fonts[3]);
 
-    inst->fonts[0] = gdk_font_load(inst->cfg.font.name);
+    inst->fonts[0] = unifont_create(inst->cfg.font.name, FALSE, FALSE,
+                                   inst->cfg.shadowboldoffset,
+                                   inst->cfg.shadowbold);
     if (!inst->fonts[0]) {
        fprintf(stderr, "%s: unable to load font \"%s\"\n", appname,
                inst->cfg.font.name);
        exit(1);
     }
-    gdk_font_ref(inst->fonts[0]);
-    font_charset = set_font_info(inst, 0);
 
-    if (inst->cfg.shadowbold) {
+    if (inst->cfg.shadowbold || !inst->cfg.boldfont.name[0]) {
        inst->fonts[1] = NULL;
     } else {
-       if (inst->cfg.boldfont.name[0]) {
-           name = inst->cfg.boldfont.name;
-           guessed = FALSE;
-       } else {
-           name = guess_derived_font_name(inst->fonts[0], TRUE, FALSE);
-           guessed = TRUE;
-       }
-       inst->fonts[1] = name ? gdk_font_load(name) : NULL;
-       if (inst->fonts[1]) {
-           gdk_font_ref(inst->fonts[1]);
-           set_font_info(inst, 1);
-       } else if (!guessed) {
+       inst->fonts[1] = unifont_create(inst->cfg.boldfont.name, FALSE, TRUE,
+                                       inst->cfg.shadowboldoffset,
+                                       inst->cfg.shadowbold);
+       if (!inst->fonts[1]) {
            fprintf(stderr, "%s: unable to load bold font \"%s\"\n", appname,
                    inst->cfg.boldfont.name);
            exit(1);
        }
-       if (guessed)
-           sfree(name);
     }
 
     if (inst->cfg.widefont.name[0]) {
-       name = inst->cfg.widefont.name;
-       guessed = FALSE;
+       inst->fonts[2] = unifont_create(inst->cfg.widefont.name, TRUE, FALSE,
+                                       inst->cfg.shadowboldoffset,
+                                       inst->cfg.shadowbold);
+       if (!inst->fonts[2]) {
+           fprintf(stderr, "%s: unable to load wide font \"%s\"\n", appname,
+                   inst->cfg.widefont.name);
+           exit(1);
+       }
     } else {
-       name = guess_derived_font_name(inst->fonts[0], FALSE, TRUE);
-       guessed = TRUE;
-    }
-    inst->fonts[2] = name ? gdk_font_load(name) : NULL;
-    if (inst->fonts[2]) {
-       gdk_font_ref(inst->fonts[2]);
-       set_font_info(inst, 2);
-    } else if (!guessed) {
-       fprintf(stderr, "%s: unable to load wide font \"%s\"\n", appname,
-               inst->cfg.widefont.name);
-       exit(1);
+       inst->fonts[2] = NULL;
     }
-    if (guessed)
-       sfree(name);
 
-    if (inst->cfg.shadowbold) {
+    if (inst->cfg.shadowbold || !inst->cfg.wideboldfont.name[0]) {
        inst->fonts[3] = NULL;
     } else {
-       if (inst->cfg.wideboldfont.name[0]) {
-           name = inst->cfg.wideboldfont.name;
-           guessed = FALSE;
-       } else {
-           /*
-            * Here we have some choices. We can widen the bold font,
-            * bolden the wide font, or widen and bolden the standard
-            * font. Try them all, in that order!
-            */
-           if (inst->cfg.widefont.name[0])
-               name = guess_derived_font_name(inst->fonts[2], TRUE, FALSE);
-           else if (inst->cfg.boldfont.name[0])
-               name = guess_derived_font_name(inst->fonts[1], FALSE, TRUE);
-           else
-               name = guess_derived_font_name(inst->fonts[0], TRUE, TRUE);
-           guessed = TRUE;
-       }
-       inst->fonts[3] = name ? gdk_font_load(name) : NULL;
-       if (inst->fonts[3]) {
-           gdk_font_ref(inst->fonts[3]);
-           set_font_info(inst, 3);
-       } else if (!guessed) {
-           fprintf(stderr, "%s: unable to load wide/bold font \"%s\"\n", appname,
-                   inst->cfg.wideboldfont.name);
+       inst->fonts[3] = unifont_create(inst->cfg.wideboldfont.name, TRUE,
+                                       TRUE, inst->cfg.shadowboldoffset,
+                                       inst->cfg.shadowbold);
+       if (!inst->fonts[3]) {
+           fprintf(stderr, "%s: unable to load wide bold font \"%s\"\n", appname,
+                   inst->cfg.boldfont.name);
            exit(1);
        }
-       if (guessed)
-           sfree(name);
     }
 
-    inst->font_width = gdk_char_width(inst->fonts[0], ' ');
-    if (!inst->font_width) {
-       /* Maybe this is a 16-bit font? If so, GDK 2 actually expects a
-        * pointer to an XChar2b. This is pretty revolting. Can Pango do
-        * this more neatly even for server-side fonts?
-        */
-       XChar2b space;
-       space.byte1 = 0;
-       space.byte2 = ' ';
-       inst->font_width = gdk_text_width(inst->fonts[0],
-                                         (const gchar *)&space, 2);
-    }
-    inst->font_height = inst->fonts[0]->ascent + inst->fonts[0]->descent;
+    inst->font_width = inst->fonts[0]->width;
+    inst->font_height = inst->fonts[0]->height;
 
     inst->direct_to_font = init_ucs(&inst->ucsdata, inst->cfg.line_codepage,
-                                   inst->cfg.utf8_override, font_charset,
+                                   inst->cfg.utf8_override,
+                                   inst->fonts[0]->public_charset,
                                    inst->cfg.vtmode);
 }