]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - unix/gtkfont.c
Refactor the font handling code: I've moved all the code that
[PuTTY.git] / unix / gtkfont.c
1 /*
2  * Unified font management for GTK.
3  * 
4  * PuTTY is willing to use both old-style X server-side bitmap
5  * fonts _and_ GTK2/Pango client-side fonts. This requires us to
6  * do a bit of work to wrap the two wildly different APIs into
7  * forms the rest of the code can switch between seamlessly, and
8  * also requires a custom font selector capable of handling both
9  * types of font.
10  */
11
12 #include <assert.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <gtk/gtk.h>
16 #include <gdk/gdkkeysyms.h>
17 #include <gdk/gdkx.h>
18 #include <X11/Xlib.h>
19 #include <X11/Xutil.h>
20 #include <X11/Xatom.h>
21
22 #include "putty.h"
23 #include "gtkfont.h"
24
25 /*
26  * To do:
27  * 
28  *  - import flags to do VT100 double-width, and import the icky
29  *    pixmap stretch code for it.
30  * 
31  *  - add the Pango back end!
32  */
33
34 /*
35  * Future work:
36  * 
37  *  - all the GDK font functions used in the x11font subclass are
38  *    deprecated, so one day they may go away. When this happens -
39  *    or before, if I'm feeling proactive - it oughtn't to be too
40  *    difficult in principle to convert the whole thing to use
41  *    actual Xlib font calls.
42  */
43
44 /*
45  * Ad-hoc vtable mechanism to allow font structures to be
46  * polymorphic.
47  * 
48  * Any instance of `unifont' used in the vtable functions will
49  * actually be the first element of a larger structure containing
50  * data specific to the subtype. This is permitted by the ISO C
51  * provision that one may safely cast between a pointer to a
52  * structure and a pointer to its first element.
53  */
54
55 struct unifont_vtable {
56     /*
57      * `Methods' of the `class'.
58      */
59     unifont *(*create)(char *name, int wide, int bold,
60                        int shadowoffset, int shadowalways);
61     void (*destroy)(unifont *font);
62     void (*draw_text)(GdkDrawable *target, GdkGC *gc, unifont *font,
63                       int x, int y, const char *string, int len, int wide,
64                       int bold);
65     /*
66      * `Static data members' of the `class'.
67      */
68     const char *prefix;
69 };
70
71 /* ----------------------------------------------------------------------
72  * GDK-based X11 font implementation.
73  */
74
75 static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
76                               int x, int y, const char *string, int len,
77                               int wide, int bold);
78 static unifont *x11font_create(char *name, int wide, int bold,
79                                int shadowoffset, int shadowalways);
80 static void x11font_destroy(unifont *font);
81
82 struct x11font {
83     struct unifont u;
84     /*
85      * Actual font objects. We store a number of these, for
86      * automatically guessed bold and wide variants.
87      * 
88      * The parallel array `allocated' indicates whether we've
89      * tried to fetch a subfont already (thus distinguishing NULL
90      * because we haven't tried yet from NULL because we tried and
91      * failed, so that we don't keep trying and failing
92      * subsequently).
93      */
94     GdkFont *fonts[4];
95     int allocated[4];
96     /*
97      * `sixteen_bit' is true iff the font object is indexed by
98      * values larger than a byte. That is, this flag tells us
99      * whether we use gdk_draw_text_wc() or gdk_draw_text().
100      */
101     int sixteen_bit;
102     /*
103      * Font charsets. public_charset and real_charset can differ
104      * for X11 fonts, because many X fonts use CS_ISO8859_1_X11.
105      */
106     int public_charset, real_charset;
107     /*
108      * Data passed in to unifont_create().
109      */
110     int wide, bold, shadowoffset, shadowalways;
111 };
112
113 static const struct unifont_vtable x11font_vtable = {
114     x11font_create,
115     x11font_destroy,
116     x11font_draw_text,
117     "x11"
118 };
119
120 char *x11_guess_derived_font_name(GdkFont *font, int bold, int wide)
121 {
122     XFontStruct *xfs = GDK_FONT_XFONT(font);
123     Display *disp = GDK_FONT_XDISPLAY(font);
124     Atom fontprop = XInternAtom(disp, "FONT", False);
125     unsigned long ret;
126     if (XGetFontProperty(xfs, fontprop, &ret)) {
127         char *name = XGetAtomName(disp, (Atom)ret);
128         if (name && name[0] == '-') {
129             char *strings[13];
130             char *dupname, *extrafree = NULL, *ret;
131             char *p, *q;
132             int nstr;
133
134             p = q = dupname = dupstr(name); /* skip initial minus */
135             nstr = 0;
136
137             while (*p && nstr < lenof(strings)) {
138                 if (*p == '-') {
139                     *p = '\0';
140                     strings[nstr++] = p+1;
141                 }
142                 p++;
143             }
144
145             if (nstr < lenof(strings))
146                 return NULL;           /* XLFD was malformed */
147
148             if (bold)
149                 strings[2] = "bold";
150
151             if (wide) {
152                 /* 4 is `wideness', which obviously may have changed. */
153                 /* 5 is additional style, which may be e.g. `ja' or `ko'. */
154                 strings[4] = strings[5] = "*";
155                 strings[11] = extrafree = dupprintf("%d", 2*atoi(strings[11]));
156             }
157
158             ret = dupcat("-", strings[ 0], "-", strings[ 1], "-", strings[ 2],
159                          "-", strings[ 3], "-", strings[ 4], "-", strings[ 5],
160                          "-", strings[ 6], "-", strings[ 7], "-", strings[ 8],
161                          "-", strings[ 9], "-", strings[10], "-", strings[11],
162                          "-", strings[12], NULL);
163             sfree(extrafree);
164             sfree(dupname);
165
166             return ret;
167         }
168     }
169     return NULL;
170 }
171
172 static int x11_font_width(GdkFont *font, int sixteen_bit)
173 {
174     if (sixteen_bit) {
175         XChar2b space;
176         space.byte1 = 0;
177         space.byte2 = ' ';
178         return gdk_text_width(font, (const gchar *)&space, 2);
179     } else {
180         return gdk_char_width(font, ' ');
181     }
182 }
183
184 static unifont *x11font_create(char *name, int wide, int bold,
185                                int shadowoffset, int shadowalways)
186 {
187     struct x11font *xfont;
188     GdkFont *font;
189     XFontStruct *xfs;
190     Display *disp;
191     Atom charset_registry, charset_encoding;
192     unsigned long registry_ret, encoding_ret;
193     int pubcs, realcs, sixteen_bit;
194     int i;
195
196     font = gdk_font_load(name);
197     if (!font)
198         return NULL;
199
200     xfs = GDK_FONT_XFONT(font);
201     disp = GDK_FONT_XDISPLAY(font);
202
203     charset_registry = XInternAtom(disp, "CHARSET_REGISTRY", False);
204     charset_encoding = XInternAtom(disp, "CHARSET_ENCODING", False);
205
206     pubcs = realcs = CS_NONE;
207     sixteen_bit = FALSE;
208
209     if (XGetFontProperty(xfs, charset_registry, &registry_ret) &&
210         XGetFontProperty(xfs, charset_encoding, &encoding_ret)) {
211         char *reg, *enc;
212         reg = XGetAtomName(disp, (Atom)registry_ret);
213         enc = XGetAtomName(disp, (Atom)encoding_ret);
214         if (reg && enc) {
215             char *encoding = dupcat(reg, "-", enc, NULL);
216             pubcs = realcs = charset_from_xenc(encoding);
217
218             /*
219              * iso10646-1 is the only wide font encoding we
220              * support. In this case, we expect clients to give us
221              * UTF-8, which this module must internally convert
222              * into 16-bit Unicode.
223              */
224             if (!strcasecmp(encoding, "iso10646-1")) {
225                 sixteen_bit = TRUE;
226                 pubcs = realcs = CS_UTF8;
227             }
228
229             /*
230              * Hack for X line-drawing characters: if the primary
231              * font is encoded as ISO-8859-1, and has valid glyphs
232              * in the first 32 char positions, it is assumed that
233              * those glyphs are the VT100 line-drawing character
234              * set.
235              * 
236              * Actually, we'll hack even harder by only checking
237              * position 0x19 (vertical line, VT100 linedrawing
238              * `x'). Then we can check it easily by seeing if the
239              * ascent and descent differ.
240              */
241             if (pubcs == CS_ISO8859_1) {
242                 int lb, rb, wid, asc, desc;
243                 gchar text[2];
244
245                 text[1] = '\0';
246                 text[0] = '\x12';
247                 gdk_string_extents(font, text, &lb, &rb, &wid, &asc, &desc);
248                 if (asc != desc)
249                     realcs = CS_ISO8859_1_X11;
250             }
251
252             sfree(encoding);
253         }
254     }
255
256     xfont = snew(struct x11font);
257     xfont->u.vt = &x11font_vtable;
258     xfont->u.width = x11_font_width(font, sixteen_bit);
259     xfont->u.ascent = font->ascent;
260     xfont->u.descent = font->descent;
261     xfont->u.height = xfont->u.ascent + xfont->u.descent;
262     xfont->u.public_charset = pubcs;
263     xfont->u.real_charset = realcs;
264     xfont->fonts[0] = font;
265     xfont->allocated[0] = TRUE;
266     xfont->sixteen_bit = sixteen_bit;
267     xfont->wide = wide;
268     xfont->bold = bold;
269     xfont->shadowoffset = shadowoffset;
270     xfont->shadowalways = shadowalways;
271
272     for (i = 1; i < lenof(xfont->fonts); i++) {
273         xfont->fonts[i] = NULL;
274         xfont->allocated[i] = FALSE;
275     }
276
277     return (unifont *)xfont;
278 }
279
280 static void x11font_destroy(unifont *font)
281 {
282     struct x11font *xfont = (struct x11font *)font;
283     int i;
284
285     for (i = 0; i < lenof(xfont->fonts); i++)
286         if (xfont->fonts[i])
287             gdk_font_unref(xfont->fonts[i]);
288     sfree(font);
289 }
290
291 static void x11_alloc_subfont(struct x11font *xfont, int sfid)
292 {
293     char *derived_name = x11_guess_derived_font_name
294         (xfont->fonts[0], sfid & 1, !!(sfid & 2));
295     xfont->fonts[sfid] = gdk_font_load(derived_name);   /* may be NULL */
296     xfont->allocated[sfid] = TRUE;
297     sfree(derived_name);
298 }
299
300 static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
301                               int x, int y, const char *string, int len,
302                               int wide, int bold)
303 {
304     struct x11font *xfont = (struct x11font *)font;
305     int sfid;
306     int shadowbold = FALSE;
307
308     wide -= xfont->wide;
309     bold -= xfont->bold;
310
311     /*
312      * Decide which subfont we're using, and whether we have to
313      * use shadow bold.
314      */
315     if (xfont->shadowalways && bold) {
316         shadowbold = TRUE;
317         bold = 0;
318     }
319     sfid = 2 * wide + bold;
320     if (!xfont->allocated[sfid])
321         x11_alloc_subfont(xfont, sfid);
322     if (bold && !xfont->fonts[sfid]) {
323         bold = 0;
324         shadowbold = TRUE;
325         sfid = 2 * wide + bold;
326         if (!xfont->allocated[sfid])
327             x11_alloc_subfont(xfont, sfid);
328     }
329
330     if (!xfont->fonts[sfid])
331         return;                        /* we've tried our best, but no luck */
332
333     if (xfont->sixteen_bit) {
334         /*
335          * This X font has 16-bit character indices, which means
336          * we expect our string to have been passed in UTF-8.
337          */
338         XChar2b *xcs;
339         wchar_t *wcs;
340         int nchars, maxchars, i;
341
342         /*
343          * Convert the input string to wide-character Unicode.
344          */
345         maxchars = 0;
346         for (i = 0; i < len; i++)
347             if ((unsigned char)string[i] <= 0x7F ||
348                 (unsigned char)string[i] >= 0xC0)
349                 maxchars++;
350         wcs = snewn(maxchars+1, wchar_t);
351         nchars = charset_to_unicode((char **)&string, &len, wcs, maxchars,
352                                     CS_UTF8, NULL, NULL, 0);
353         assert(nchars <= maxchars);
354         wcs[nchars] = L'\0';
355
356         xcs = snewn(nchars, XChar2b);
357         for (i = 0; i < nchars; i++) {
358             xcs[i].byte1 = wcs[i] >> 8;
359             xcs[i].byte2 = wcs[i];
360         }
361
362         gdk_draw_text(target, xfont->fonts[sfid], gc,
363                       x, y, (gchar *)xcs, nchars*2);
364         if (shadowbold)
365             gdk_draw_text(target, xfont->fonts[sfid], gc,
366                           x + xfont->shadowoffset, y, (gchar *)xcs, nchars*2);
367         sfree(xcs);
368         sfree(wcs);
369     } else {
370         gdk_draw_text(target, xfont->fonts[sfid], gc, x, y, string, len);
371         if (shadowbold)
372             gdk_draw_text(target, xfont->fonts[sfid], gc,
373                           x + xfont->shadowoffset, y, string, len);
374     }
375 }
376
377 /* ----------------------------------------------------------------------
378  * Outermost functions which do the vtable dispatch.
379  */
380
381 /*
382  * This function is the only one which needs to know the full set
383  * of font implementations available, because it has to try each
384  * in turn until one works, or condition on an override prefix in
385  * the font name.
386  */
387 static const struct unifont_vtable *unifont_types[] = {
388     &x11font_vtable,
389 };
390 unifont *unifont_create(char *name, int wide, int bold,
391                         int shadowoffset, int shadowalways)
392 {
393     int colonpos = strcspn(name, ":");
394     int i;
395
396     if (name[colonpos]) {
397         /*
398          * There's a colon prefix on the font name. Use it to work
399          * out which subclass to try to create.
400          */
401         for (i = 0; i < lenof(unifont_types); i++) {
402             if (strlen(unifont_types[i]->prefix) == colonpos &&
403                 !strncmp(unifont_types[i]->prefix, name, colonpos))
404                 break;
405         }
406         if (i == lenof(unifont_types))
407             return NULL;               /* prefix not recognised */
408         return unifont_types[i]->create(name+colonpos+1, wide, bold,
409                                         shadowoffset, shadowalways);
410     } else {
411         /*
412          * No colon prefix, so just go through all the subclasses.
413          */
414         for (i = 0; i < lenof(unifont_types); i++) {
415             unifont *ret = unifont_types[i]->create(name, wide, bold,
416                                                     shadowoffset,
417                                                     shadowalways);
418             if (ret)
419                 return ret;
420         }
421         return NULL;                   /* font not found in any scheme */
422     }
423 }
424
425 void unifont_destroy(unifont *font)
426 {
427     font->vt->destroy(font);
428 }
429
430 void unifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
431                        int x, int y, const char *string, int len,
432                        int wide, int bold)
433 {
434     font->vt->draw_text(target, gc, font, x, y, string, len, wide, bold);
435 }