]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - unix/gtkfont.c
Implemented a Pango back end. GTK 2 PuTTY can now switch seamlessly
[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; import the icky
29  *    pixmap stretch code on to the X11 side, and do something
30  *    nicer in Pango.
31  * 
32  *  - unified font selector dialog, arrgh!
33  */
34
35 /*
36  * Future work:
37  * 
38  *  - all the GDK font functions used in the x11font subclass are
39  *    deprecated, so one day they may go away. When this happens -
40  *    or before, if I'm feeling proactive - it oughtn't to be too
41  *    difficult in principle to convert the whole thing to use
42  *    actual Xlib font calls.
43  */
44
45 /*
46  * Ad-hoc vtable mechanism to allow font structures to be
47  * polymorphic.
48  * 
49  * Any instance of `unifont' used in the vtable functions will
50  * actually be the first element of a larger structure containing
51  * data specific to the subtype. This is permitted by the ISO C
52  * provision that one may safely cast between a pointer to a
53  * structure and a pointer to its first element.
54  */
55
56 struct unifont_vtable {
57     /*
58      * `Methods' of the `class'.
59      */
60     unifont *(*create)(GtkWidget *widget, char *name, int wide, int bold,
61                        int shadowoffset, int shadowalways);
62     void (*destroy)(unifont *font);
63     void (*draw_text)(GdkDrawable *target, GdkGC *gc, unifont *font,
64                       int x, int y, const char *string, int len, int wide,
65                       int bold, int cellwidth);
66     /*
67      * `Static data members' of the `class'.
68      */
69     const char *prefix;
70 };
71
72 /* ----------------------------------------------------------------------
73  * GDK-based X11 font implementation.
74  */
75
76 static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
77                               int x, int y, const char *string, int len,
78                               int wide, int bold, int cellwidth);
79 static unifont *x11font_create(GtkWidget *widget, char *name,
80                                int wide, int bold,
81                                int shadowoffset, int shadowalways);
82 static void x11font_destroy(unifont *font);
83
84 struct x11font {
85     struct unifont u;
86     /*
87      * Actual font objects. We store a number of these, for
88      * automatically guessed bold and wide variants.
89      * 
90      * The parallel array `allocated' indicates whether we've
91      * tried to fetch a subfont already (thus distinguishing NULL
92      * because we haven't tried yet from NULL because we tried and
93      * failed, so that we don't keep trying and failing
94      * subsequently).
95      */
96     GdkFont *fonts[4];
97     int allocated[4];
98     /*
99      * `sixteen_bit' is true iff the font object is indexed by
100      * values larger than a byte. That is, this flag tells us
101      * whether we use gdk_draw_text_wc() or gdk_draw_text().
102      */
103     int sixteen_bit;
104     /*
105      * Data passed in to unifont_create().
106      */
107     int wide, bold, shadowoffset, shadowalways;
108 };
109
110 static const struct unifont_vtable x11font_vtable = {
111     x11font_create,
112     x11font_destroy,
113     x11font_draw_text,
114     "x11"
115 };
116
117 char *x11_guess_derived_font_name(GdkFont *font, int bold, int wide)
118 {
119     XFontStruct *xfs = GDK_FONT_XFONT(font);
120     Display *disp = GDK_FONT_XDISPLAY(font);
121     Atom fontprop = XInternAtom(disp, "FONT", False);
122     unsigned long ret;
123     if (XGetFontProperty(xfs, fontprop, &ret)) {
124         char *name = XGetAtomName(disp, (Atom)ret);
125         if (name && name[0] == '-') {
126             char *strings[13];
127             char *dupname, *extrafree = NULL, *ret;
128             char *p, *q;
129             int nstr;
130
131             p = q = dupname = dupstr(name); /* skip initial minus */
132             nstr = 0;
133
134             while (*p && nstr < lenof(strings)) {
135                 if (*p == '-') {
136                     *p = '\0';
137                     strings[nstr++] = p+1;
138                 }
139                 p++;
140             }
141
142             if (nstr < lenof(strings))
143                 return NULL;           /* XLFD was malformed */
144
145             if (bold)
146                 strings[2] = "bold";
147
148             if (wide) {
149                 /* 4 is `wideness', which obviously may have changed. */
150                 /* 5 is additional style, which may be e.g. `ja' or `ko'. */
151                 strings[4] = strings[5] = "*";
152                 strings[11] = extrafree = dupprintf("%d", 2*atoi(strings[11]));
153             }
154
155             ret = dupcat("-", strings[ 0], "-", strings[ 1], "-", strings[ 2],
156                          "-", strings[ 3], "-", strings[ 4], "-", strings[ 5],
157                          "-", strings[ 6], "-", strings[ 7], "-", strings[ 8],
158                          "-", strings[ 9], "-", strings[10], "-", strings[11],
159                          "-", strings[12], NULL);
160             sfree(extrafree);
161             sfree(dupname);
162
163             return ret;
164         }
165     }
166     return NULL;
167 }
168
169 static int x11_font_width(GdkFont *font, int sixteen_bit)
170 {
171     if (sixteen_bit) {
172         XChar2b space;
173         space.byte1 = 0;
174         space.byte2 = ' ';
175         return gdk_text_width(font, (const gchar *)&space, 2);
176     } else {
177         return gdk_char_width(font, ' ');
178     }
179 }
180
181 static unifont *x11font_create(GtkWidget *widget, char *name,
182                                int wide, int bold,
183                                int shadowoffset, int shadowalways)
184 {
185     struct x11font *xfont;
186     GdkFont *font;
187     XFontStruct *xfs;
188     Display *disp;
189     Atom charset_registry, charset_encoding;
190     unsigned long registry_ret, encoding_ret;
191     int pubcs, realcs, sixteen_bit;
192     int i;
193
194     font = gdk_font_load(name);
195     if (!font)
196         return NULL;
197
198     xfs = GDK_FONT_XFONT(font);
199     disp = GDK_FONT_XDISPLAY(font);
200
201     charset_registry = XInternAtom(disp, "CHARSET_REGISTRY", False);
202     charset_encoding = XInternAtom(disp, "CHARSET_ENCODING", False);
203
204     pubcs = realcs = CS_NONE;
205     sixteen_bit = FALSE;
206
207     if (XGetFontProperty(xfs, charset_registry, &registry_ret) &&
208         XGetFontProperty(xfs, charset_encoding, &encoding_ret)) {
209         char *reg, *enc;
210         reg = XGetAtomName(disp, (Atom)registry_ret);
211         enc = XGetAtomName(disp, (Atom)encoding_ret);
212         if (reg && enc) {
213             char *encoding = dupcat(reg, "-", enc, NULL);
214             pubcs = realcs = charset_from_xenc(encoding);
215
216             /*
217              * iso10646-1 is the only wide font encoding we
218              * support. In this case, we expect clients to give us
219              * UTF-8, which this module must internally convert
220              * into 16-bit Unicode.
221              */
222             if (!strcasecmp(encoding, "iso10646-1")) {
223                 sixteen_bit = TRUE;
224                 pubcs = realcs = CS_UTF8;
225             }
226
227             /*
228              * Hack for X line-drawing characters: if the primary
229              * font is encoded as ISO-8859-1, and has valid glyphs
230              * in the first 32 char positions, it is assumed that
231              * those glyphs are the VT100 line-drawing character
232              * set.
233              * 
234              * Actually, we'll hack even harder by only checking
235              * position 0x19 (vertical line, VT100 linedrawing
236              * `x'). Then we can check it easily by seeing if the
237              * ascent and descent differ.
238              */
239             if (pubcs == CS_ISO8859_1) {
240                 int lb, rb, wid, asc, desc;
241                 gchar text[2];
242
243                 text[1] = '\0';
244                 text[0] = '\x12';
245                 gdk_string_extents(font, text, &lb, &rb, &wid, &asc, &desc);
246                 if (asc != desc)
247                     realcs = CS_ISO8859_1_X11;
248             }
249
250             sfree(encoding);
251         }
252     }
253
254     xfont = snew(struct x11font);
255     xfont->u.vt = &x11font_vtable;
256     xfont->u.width = x11_font_width(font, sixteen_bit);
257     xfont->u.ascent = font->ascent;
258     xfont->u.descent = font->descent;
259     xfont->u.height = xfont->u.ascent + xfont->u.descent;
260     xfont->u.public_charset = pubcs;
261     xfont->u.real_charset = realcs;
262     xfont->fonts[0] = font;
263     xfont->allocated[0] = TRUE;
264     xfont->sixteen_bit = sixteen_bit;
265     xfont->wide = wide;
266     xfont->bold = bold;
267     xfont->shadowoffset = shadowoffset;
268     xfont->shadowalways = shadowalways;
269
270     for (i = 1; i < lenof(xfont->fonts); i++) {
271         xfont->fonts[i] = NULL;
272         xfont->allocated[i] = FALSE;
273     }
274
275     return (unifont *)xfont;
276 }
277
278 static void x11font_destroy(unifont *font)
279 {
280     struct x11font *xfont = (struct x11font *)font;
281     int i;
282
283     for (i = 0; i < lenof(xfont->fonts); i++)
284         if (xfont->fonts[i])
285             gdk_font_unref(xfont->fonts[i]);
286     sfree(font);
287 }
288
289 static void x11_alloc_subfont(struct x11font *xfont, int sfid)
290 {
291     char *derived_name = x11_guess_derived_font_name
292         (xfont->fonts[0], sfid & 1, !!(sfid & 2));
293     xfont->fonts[sfid] = gdk_font_load(derived_name);   /* may be NULL */
294     xfont->allocated[sfid] = TRUE;
295     sfree(derived_name);
296 }
297
298 static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
299                               int x, int y, const char *string, int len,
300                               int wide, int bold, int cellwidth)
301 {
302     struct x11font *xfont = (struct x11font *)font;
303     int sfid;
304     int shadowbold = FALSE;
305
306     wide -= xfont->wide;
307     bold -= xfont->bold;
308
309     /*
310      * Decide which subfont we're using, and whether we have to
311      * use shadow bold.
312      */
313     if (xfont->shadowalways && bold) {
314         shadowbold = TRUE;
315         bold = 0;
316     }
317     sfid = 2 * wide + bold;
318     if (!xfont->allocated[sfid])
319         x11_alloc_subfont(xfont, sfid);
320     if (bold && !xfont->fonts[sfid]) {
321         bold = 0;
322         shadowbold = TRUE;
323         sfid = 2 * wide + bold;
324         if (!xfont->allocated[sfid])
325             x11_alloc_subfont(xfont, sfid);
326     }
327
328     if (!xfont->fonts[sfid])
329         return;                        /* we've tried our best, but no luck */
330
331     if (xfont->sixteen_bit) {
332         /*
333          * This X font has 16-bit character indices, which means
334          * we expect our string to have been passed in UTF-8.
335          */
336         XChar2b *xcs;
337         wchar_t *wcs;
338         int nchars, maxchars, i;
339
340         /*
341          * Convert the input string to wide-character Unicode.
342          */
343         maxchars = 0;
344         for (i = 0; i < len; i++)
345             if ((unsigned char)string[i] <= 0x7F ||
346                 (unsigned char)string[i] >= 0xC0)
347                 maxchars++;
348         wcs = snewn(maxchars+1, wchar_t);
349         nchars = charset_to_unicode((char **)&string, &len, wcs, maxchars,
350                                     CS_UTF8, NULL, NULL, 0);
351         assert(nchars <= maxchars);
352         wcs[nchars] = L'\0';
353
354         xcs = snewn(nchars, XChar2b);
355         for (i = 0; i < nchars; i++) {
356             xcs[i].byte1 = wcs[i] >> 8;
357             xcs[i].byte2 = wcs[i];
358         }
359
360         gdk_draw_text(target, xfont->fonts[sfid], gc,
361                       x, y, (gchar *)xcs, nchars*2);
362         if (shadowbold)
363             gdk_draw_text(target, xfont->fonts[sfid], gc,
364                           x + xfont->shadowoffset, y, (gchar *)xcs, nchars*2);
365         sfree(xcs);
366         sfree(wcs);
367     } else {
368         gdk_draw_text(target, xfont->fonts[sfid], gc, x, y, string, len);
369         if (shadowbold)
370             gdk_draw_text(target, xfont->fonts[sfid], gc,
371                           x + xfont->shadowoffset, y, string, len);
372     }
373 }
374
375 /* ----------------------------------------------------------------------
376  * Pango font implementation.
377  */
378
379 static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
380                                 int x, int y, const char *string, int len,
381                                 int wide, int bold, int cellwidth);
382 static unifont *pangofont_create(GtkWidget *widget, char *name,
383                                  int wide, int bold,
384                                  int shadowoffset, int shadowalways);
385 static void pangofont_destroy(unifont *font);
386
387 struct pangofont {
388     struct unifont u;
389     /*
390      * Pango objects.
391      */
392     PangoFontDescription *desc;
393     PangoFontset *fset;
394     /*
395      * The containing widget.
396      */
397     GtkWidget *widget;
398     /*
399      * Data passed in to unifont_create().
400      */
401     int bold, shadowoffset, shadowalways;
402 };
403
404 static const struct unifont_vtable pangofont_vtable = {
405     pangofont_create,
406     pangofont_destroy,
407     pangofont_draw_text,
408     "pango"
409 };
410
411 static unifont *pangofont_create(GtkWidget *widget, char *name,
412                                  int wide, int bold,
413                                  int shadowoffset, int shadowalways)
414 {
415     struct pangofont *pfont;
416     PangoContext *ctx;
417     PangoFontMap *map;
418     PangoFontDescription *desc;
419     PangoFontset *fset;
420     PangoFontMetrics *metrics;
421
422     desc = pango_font_description_from_string(name);
423     if (!desc)
424         return NULL;
425     ctx = gtk_widget_get_pango_context(widget);
426     if (!ctx) {
427         pango_font_description_free(desc);
428         return NULL;
429     }
430     map = pango_context_get_font_map(ctx);
431     if (!map) {
432         pango_font_description_free(desc);
433         return NULL;
434     }
435     fset = pango_font_map_load_fontset(map, ctx, desc,
436                                        pango_context_get_language(ctx));
437     if (!fset) {
438         pango_font_description_free(desc);
439         return NULL;
440     }
441     metrics = pango_fontset_get_metrics(fset);
442     if (!metrics ||
443         pango_font_metrics_get_approximate_digit_width(metrics) == 0) {
444         pango_font_description_free(desc);
445         g_object_unref(fset);
446         return NULL;
447     }
448
449     pfont = snew(struct pangofont);
450     pfont->u.vt = &pangofont_vtable;
451     pfont->u.width =
452         PANGO_PIXELS(pango_font_metrics_get_approximate_digit_width(metrics));
453     pfont->u.ascent = PANGO_PIXELS(pango_font_metrics_get_ascent(metrics));
454     pfont->u.descent = PANGO_PIXELS(pango_font_metrics_get_descent(metrics));
455     pfont->u.height = pfont->u.ascent + pfont->u.descent;
456     /* The Pango API is hardwired to UTF-8 */
457     pfont->u.public_charset = CS_UTF8;
458     pfont->u.real_charset = CS_UTF8;
459     pfont->desc = desc;
460     pfont->fset = fset;
461     pfont->widget = widget;
462     pfont->bold = bold;
463     pfont->shadowoffset = shadowoffset;
464     pfont->shadowalways = shadowalways;
465
466     return (unifont *)pfont;
467 }
468
469 static void pangofont_destroy(unifont *font)
470 {
471     struct pangofont *pfont = (struct pangofont *)font;
472     pfont = pfont;                     /* FIXME */
473     pango_font_description_free(pfont->desc);
474     g_object_unref(pfont->fset);
475     sfree(font);
476 }
477
478 static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
479                                 int x, int y, const char *string, int len,
480                                 int wide, int bold, int cellwidth)
481 {
482     struct pangofont *pfont = (struct pangofont *)font;
483     PangoLayout *layout;
484     PangoRectangle rect;
485     int shadowbold = FALSE;
486
487     if (wide)
488         cellwidth *= 2;
489
490     y -= pfont->u.ascent;
491
492     layout = pango_layout_new(gtk_widget_get_pango_context(pfont->widget));
493     pango_layout_set_font_description(layout, pfont->desc);
494     if (bold > pfont->bold) {
495         if (pfont->shadowalways)
496             shadowbold = TRUE;
497         else {
498             PangoFontDescription *desc2 =
499                 pango_font_description_copy_static(pfont->desc);
500             pango_font_description_set_weight(desc2, PANGO_WEIGHT_BOLD);
501             pango_layout_set_font_description(layout, desc2);
502         }
503     }
504
505     while (len > 0) {
506         int clen;
507
508         /*
509          * Extract a single UTF-8 character from the string.
510          */
511         clen = 1;
512         while (clen < len &&
513                (unsigned char)string[clen] >= 0x80 &&
514                (unsigned char)string[clen] < 0xC0)
515             clen++;
516
517         pango_layout_set_text(layout, string, clen);
518         pango_layout_get_pixel_extents(layout, NULL, &rect);
519         gdk_draw_layout(target, gc, x + (cellwidth - rect.width)/2,
520                         y + (pfont->u.height - rect.height)/2, layout);
521         if (shadowbold)
522             gdk_draw_layout(target, gc, x + (cellwidth - rect.width)/2 + pfont->shadowoffset,
523                             y + (pfont->u.height - rect.height)/2, layout);
524
525         len -= clen;
526         string += clen;
527         x += cellwidth;
528     }
529
530     g_object_unref(layout);
531 }
532
533 /* ----------------------------------------------------------------------
534  * Outermost functions which do the vtable dispatch.
535  */
536
537 /*
538  * This function is the only one which needs to know the full set
539  * of font implementations available, because it has to try each
540  * in turn until one works, or condition on an override prefix in
541  * the font name.
542  */
543 static const struct unifont_vtable *unifont_types[] = {
544     &pangofont_vtable,
545     &x11font_vtable,
546 };
547 unifont *unifont_create(GtkWidget *widget, char *name, int wide, int bold,
548                         int shadowoffset, int shadowalways)
549 {
550     int colonpos = strcspn(name, ":");
551     int i;
552
553     if (name[colonpos]) {
554         /*
555          * There's a colon prefix on the font name. Use it to work
556          * out which subclass to try to create.
557          */
558         for (i = 0; i < lenof(unifont_types); i++) {
559             if (strlen(unifont_types[i]->prefix) == colonpos &&
560                 !strncmp(unifont_types[i]->prefix, name, colonpos))
561                 break;
562         }
563         if (i == lenof(unifont_types))
564             return NULL;               /* prefix not recognised */
565         return unifont_types[i]->create(widget, name+colonpos+1, wide, bold,
566                                         shadowoffset, shadowalways);
567     } else {
568         /*
569          * No colon prefix, so just go through all the subclasses.
570          */
571         for (i = 0; i < lenof(unifont_types); i++) {
572             unifont *ret = unifont_types[i]->create(widget, name, wide, bold,
573                                                     shadowoffset,
574                                                     shadowalways);
575             if (ret)
576                 return ret;
577         }
578         return NULL;                   /* font not found in any scheme */
579     }
580 }
581
582 void unifont_destroy(unifont *font)
583 {
584     font->vt->destroy(font);
585 }
586
587 void unifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
588                        int x, int y, const char *string, int len,
589                        int wide, int bold, int cellwidth)
590 {
591     font->vt->draw_text(target, gc, font, x, y, string, len,
592                         wide, bold, cellwidth);
593 }