From f96e9a61341021787a57c54c106831cfbdedd5c7 Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 4 Jun 2008 23:12:30 +0000 Subject: [PATCH] Bah, there's always something. Check in missing files. git-svn-id: svn://svn.tartarus.org/sgt/putty@8040 cda61777-01e9-0310-a592-d414129be87e --- unix/gtkfont.c | 2520 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ unix/gtkfont.h | 67 ++ 2 files changed, 2587 insertions(+) create mode 100644 unix/gtkfont.c create mode 100644 unix/gtkfont.h diff --git a/unix/gtkfont.c b/unix/gtkfont.c new file mode 100644 index 00000000..fb756309 --- /dev/null +++ b/unix/gtkfont.c @@ -0,0 +1,2520 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "gtkfont.h" +#include "tree234.h" + +/* + * Future work: + * + * - it would be nice to have a display of the current font name, + * and in particular whether it's client- or server-side, + * during the progress of the font selector. + * + * - 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. + * + * - it would be nice if we could move the processing of + * underline and VT100 double width into this module, so that + * instead of using the ghastly pixmap-stretching technique + * everywhere we could tell the Pango backend to scale its + * fonts to double size properly and at full resolution. + * However, this requires me to learn how to make Pango stretch + * text to an arbitrary aspect ratio (for double-width only + * text, which perversely is harder than DW+DH), and right now + * I haven't the energy. + */ + +/* + * 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. + */ + +#define FONTFLAG_CLIENTSIDE 0x0001 +#define FONTFLAG_SERVERSIDE 0x0002 +#define FONTFLAG_SERVERALIAS 0x0004 +#define FONTFLAG_NONMONOSPACED 0x0008 + +#define FONTFLAG_SORT_MASK 0x0007 /* used to disambiguate font families */ + +typedef void (*fontsel_add_entry)(void *ctx, const char *realfontname, + const char *family, const char *charset, + const char *style, const char *stylekey, + int size, int flags, + const struct unifont_vtable *fontclass); + +struct unifont_vtable { + /* + * `Methods' of the `class'. + */ + unifont *(*create)(GtkWidget *widget, const 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, int cellwidth); + void (*enum_fonts)(GtkWidget *widget, + fontsel_add_entry callback, void *callback_ctx); + char *(*canonify_fontname)(GtkWidget *widget, const char *name, int *size, + int *flags, int resolve_aliases); + char *(*scale_fontname)(GtkWidget *widget, const char *name, int size); + + /* + * `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, int cellwidth); +static unifont *x11font_create(GtkWidget *widget, const char *name, + int wide, int bold, + int shadowoffset, int shadowalways); +static void x11font_destroy(unifont *font); +static void x11font_enum_fonts(GtkWidget *widget, + fontsel_add_entry callback, void *callback_ctx); +static char *x11font_canonify_fontname(GtkWidget *widget, const char *name, + int *size, int *flags, + int resolve_aliases); +static char *x11font_scale_fontname(GtkWidget *widget, const char *name, + int size); + +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; + /* + * `variable' is true iff the font is non-fixed-pitch. This + * enables some code which takes greater care over character + * positioning during text drawing. + */ + int variable; + /* + * 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, + x11font_enum_fonts, + x11font_canonify_fontname, + x11font_scale_fontname, + "server", +}; + +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 = '0'; + return gdk_text_width(font, (const gchar *)&space, 2); + } else { + return gdk_char_width(font, '0'); + } +} + +static unifont *x11font_create(GtkWidget *widget, const char *name, + int wide, int bold, + int shadowoffset, int shadowalways) +{ + struct x11font *xfont; + GdkFont *font; + XFontStruct *xfs; + Display *disp; + Atom charset_registry, charset_encoding, spacing; + unsigned long registry_ret, encoding_ret, spacing_ret; + int pubcs, realcs, sixteen_bit, variable; + 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; + variable = TRUE; + + if (XGetFontProperty(xfs, charset_registry, ®istry_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); + } + } + + spacing = XInternAtom(disp, "SPACING", False); + if (XGetFontProperty(xfs, spacing, &spacing_ret)) { + char *spc; + spc = XGetAtomName(disp, (Atom)spacing_ret); + + if (spc && strchr("CcMm", spc[0])) + variable = FALSE; + } + + 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->variable = variable; + 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_really_draw_text(GdkDrawable *target, GdkFont *font, + GdkGC *gc, int x, int y, + const gchar *string, int clen, int nchars, + int shadowbold, int shadowoffset, + int fontvariable, int cellwidth) +{ + int step = clen * nchars, nsteps = 1, centre = FALSE; + + if (fontvariable) { + /* + * In a variable-pitch font, we draw one character at a + * time, and centre it in the character cell. + */ + step = clen; + nsteps = nchars; + centre = TRUE; + } + + while (nsteps-- > 0) { + int X = x; + if (centre) + X += (cellwidth - gdk_text_width(font, string, step)) / 2; + + gdk_draw_text(target, font, gc, X, y, string, step); + if (shadowbold) + gdk_draw_text(target, font, gc, X + shadowoffset, y, string, step); + + x += cellwidth; + string += step; + } +} + +static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const char *string, int len, + int wide, int bold, int cellwidth) +{ + struct x11font *xfont = (struct x11font *)font; + int sfid; + int shadowbold = FALSE; + int mult = (wide ? 2 : 1); + + 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]; + } + + x11font_really_draw_text(target, xfont->fonts[sfid], gc, x, y, + (gchar *)xcs, 2, nchars, + shadowbold, xfont->shadowoffset, + xfont->variable, cellwidth * mult); + sfree(xcs); + sfree(wcs); + } else { + x11font_really_draw_text(target, xfont->fonts[sfid], gc, x, y, + string, 1, len, + shadowbold, xfont->shadowoffset, + xfont->variable, cellwidth * mult); + } +} + +static void x11font_enum_fonts(GtkWidget *widget, + fontsel_add_entry callback, void *callback_ctx) +{ + char **fontnames; + char *tmp = NULL; + int nnames, i, max, tmpsize; + + max = 32768; + while (1) { + fontnames = XListFonts(GDK_DISPLAY(), "*", max, &nnames); + if (nnames >= max) { + XFreeFontNames(fontnames); + max *= 2; + } else + break; + } + + tmpsize = 0; + + for (i = 0; i < nnames; i++) { + if (fontnames[i][0] == '-') { + /* + * Dismember an XLFD and convert it into the format + * we'll be using in the font selector. + */ + char *components[14]; + char *p, *font, *style, *stylekey, *charset; + int j, weightkey, slantkey, setwidthkey; + int thistmpsize, fontsize, flags; + + thistmpsize = 4 * strlen(fontnames[i]) + 256; + if (tmpsize < thistmpsize) { + tmpsize = thistmpsize; + tmp = sresize(tmp, tmpsize, char); + } + strcpy(tmp, fontnames[i]); + + p = tmp; + for (j = 0; j < 14; j++) { + if (*p) + *p++ = '\0'; + components[j] = p; + while (*p && *p != '-') + p++; + } + *p++ = '\0'; + + /* + * Font name is made up of fields 0 and 1, in reverse + * order with parentheses. (This is what the GTK 1.2 X + * font selector does, and it seems to come out + * looking reasonably sensible.) + */ + font = p; + p += 1 + sprintf(p, "%s (%s)", components[1], components[0]); + + /* + * Charset is made up of fields 12 and 13. + */ + charset = p; + p += 1 + sprintf(p, "%s-%s", components[12], components[13]); + + /* + * Style is a mixture of quite a lot of the fields, + * with some strange formatting. + */ + style = p; + p += sprintf(p, "%s", components[2][0] ? components[2] : + "regular"); + if (!g_strcasecmp(components[3], "i")) + p += sprintf(p, " italic"); + else if (!g_strcasecmp(components[3], "o")) + p += sprintf(p, " oblique"); + else if (!g_strcasecmp(components[3], "ri")) + p += sprintf(p, " reverse italic"); + else if (!g_strcasecmp(components[3], "ro")) + p += sprintf(p, " reverse oblique"); + else if (!g_strcasecmp(components[3], "ot")) + p += sprintf(p, " other-slant"); + if (components[4][0] && g_strcasecmp(components[4], "normal")) + p += sprintf(p, " %s", components[4]); + if (!g_strcasecmp(components[10], "m")) + p += sprintf(p, " [M]"); + if (!g_strcasecmp(components[10], "c")) + p += sprintf(p, " [C]"); + if (components[5][0]) + p += sprintf(p, " %s", components[5]); + + /* + * Style key is the same stuff as above, but with a + * couple of transformations done on it to make it + * sort more sensibly. + */ + p++; + stylekey = p; + if (!g_strcasecmp(components[2], "medium") || + !g_strcasecmp(components[2], "regular") || + !g_strcasecmp(components[2], "normal") || + !g_strcasecmp(components[2], "book")) + weightkey = 0; + else if (!g_strncasecmp(components[2], "demi", 4) || + !g_strncasecmp(components[2], "semi", 4)) + weightkey = 1; + else + weightkey = 2; + if (!g_strcasecmp(components[3], "r")) + slantkey = 0; + else if (!g_strncasecmp(components[3], "r", 1)) + slantkey = 2; + else + slantkey = 1; + if (!g_strcasecmp(components[4], "normal")) + setwidthkey = 0; + else + setwidthkey = 1; + + p += sprintf(p, "%04d%04d%s%04d%04d%s%04d%04d%s%04d%s%04d%s", + weightkey, + (int)strlen(components[2]), components[2], + slantkey, + (int)strlen(components[3]), components[3], + setwidthkey, + (int)strlen(components[4]), components[4], + (int)strlen(components[10]), components[10], + (int)strlen(components[5]), components[5]); + + assert(p - tmp < thistmpsize); + + /* + * Size is in pixels, for our application, so we + * derive it directly from the pixel size field, + * number 6. + */ + fontsize = atoi(components[6]); + + /* + * Flags: we need to know whether this is a monospaced + * font, which we do by examining the spacing field + * again. + */ + flags = FONTFLAG_SERVERSIDE; + if (!strchr("CcMm", components[10][0])) + flags |= FONTFLAG_NONMONOSPACED; + + /* + * Not sure why, but sometimes the X server will + * deliver dummy font types in which fontsize comes + * out as zero. Filter those out. + */ + if (fontsize) + callback(callback_ctx, fontnames[i], font, charset, + style, stylekey, fontsize, flags, &x11font_vtable); + } else { + /* + * This isn't an XLFD, so it must be an alias. + * Transmit it with mostly null data. + * + * It would be nice to work out if it's monospaced + * here, but at the moment I can't see that being + * anything but computationally hideous. Ah well. + */ + callback(callback_ctx, fontnames[i], fontnames[i], NULL, + NULL, NULL, 0, FONTFLAG_SERVERALIAS, &x11font_vtable); + } + } + XFreeFontNames(fontnames); +} + +static char *x11font_canonify_fontname(GtkWidget *widget, const char *name, + int *size, int *flags, + int resolve_aliases) +{ + /* + * When given an X11 font name to try to make sense of for a + * font selector, we must attempt to load it (to see if it + * exists), and then canonify it by extracting its FONT + * property, which should give its full XLFD even if what we + * originally had was a wildcard. + * + * However, we must carefully avoid canonifying font + * _aliases_, unless specifically asked to, because the font + * selector treats them as worthwhile in their own right. + */ + GdkFont *font = gdk_font_load(name); + XFontStruct *xfs; + Display *disp; + Atom fontprop, fontprop2; + unsigned long ret; + + if (!font) + return NULL; /* didn't make sense to us, sorry */ + + gdk_font_ref(font); + + xfs = GDK_FONT_XFONT(font); + disp = GDK_FONT_XDISPLAY(font); + fontprop = XInternAtom(disp, "FONT", False); + + if (XGetFontProperty(xfs, fontprop, &ret)) { + char *newname = XGetAtomName(disp, (Atom)ret); + if (newname) { + unsigned long fsize = 12; + + fontprop2 = XInternAtom(disp, "PIXEL_SIZE", False); + if (XGetFontProperty(xfs, fontprop2, &fsize) && fsize > 0) { + *size = fsize; + gdk_font_unref(font); + if (flags) { + if (name[0] == '-' || resolve_aliases) + *flags = FONTFLAG_SERVERSIDE; + else + *flags = FONTFLAG_SERVERALIAS; + } + return dupstr(name[0] == '-' || resolve_aliases ? + newname : name); + } + } + } + + gdk_font_unref(font); + return NULL; /* something went wrong */ +} + +static char *x11font_scale_fontname(GtkWidget *widget, const char *name, + int size) +{ + return NULL; /* shan't */ +} + +#if GTK_CHECK_VERSION(2,0,0) + +/* ---------------------------------------------------------------------- + * Pango font implementation (for GTK 2 only). + */ + +#if defined PANGO_PRE_1POINT4 && !defined PANGO_PRE_1POINT6 +#define PANGO_PRE_1POINT6 /* make life easier for pre-1.4 folk */ +#endif + +static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const char *string, int len, + int wide, int bold, int cellwidth); +static unifont *pangofont_create(GtkWidget *widget, const char *name, + int wide, int bold, + int shadowoffset, int shadowalways); +static void pangofont_destroy(unifont *font); +static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback, + void *callback_ctx); +static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name, + int *size, int *flags, + int resolve_aliases); +static char *pangofont_scale_fontname(GtkWidget *widget, const char *name, + int size); + +struct pangofont { + struct unifont u; + /* + * Pango objects. + */ + PangoFontDescription *desc; + PangoFontset *fset; + /* + * The containing widget. + */ + GtkWidget *widget; + /* + * Data passed in to unifont_create(). + */ + int bold, shadowoffset, shadowalways; +}; + +static const struct unifont_vtable pangofont_vtable = { + pangofont_create, + pangofont_destroy, + pangofont_draw_text, + pangofont_enum_fonts, + pangofont_canonify_fontname, + pangofont_scale_fontname, + "client", +}; + +/* + * This function is used to rigorously validate a + * PangoFontDescription. Later versions of Pango have a nasty + * habit of accepting _any_ old string as input to + * pango_font_description_from_string and returning a font + * description which can actually be used to display text, even if + * they have to do it by falling back to their most default font. + * This is doubtless helpful in some situations, but not here, + * because we need to know if a Pango font string actually _makes + * sense_ in order to fall back to treating it as an X font name + * if it doesn't. So we check that the font family is actually one + * supported by Pango. + */ +static int pangofont_check_desc_makes_sense(PangoContext *ctx, + PangoFontDescription *desc) +{ +#ifndef PANGO_PRE_1POINT6 + PangoFontMap *map; +#endif + PangoFontFamily **families; + int i, nfamilies, matched; + + /* + * Ask Pango for a list of font families, and iterate through + * them to see if one of them matches the family in the + * PangoFontDescription. + */ +#ifndef PANGO_PRE_1POINT6 + map = pango_context_get_font_map(ctx); + if (!map) + return FALSE; + pango_font_map_list_families(map, &families, &nfamilies); +#else + pango_context_list_families(ctx, &families, &nfamilies); +#endif + + matched = FALSE; + for (i = 0; i < nfamilies; i++) { + if (!g_strcasecmp(pango_font_family_get_name(families[i]), + pango_font_description_get_family(desc))) { + matched = TRUE; + break; + } + } + g_free(families); + + return matched; +} + +static unifont *pangofont_create(GtkWidget *widget, const char *name, + int wide, int bold, + int shadowoffset, int shadowalways) +{ + struct pangofont *pfont; + PangoContext *ctx; +#ifndef PANGO_PRE_1POINT6 + PangoFontMap *map; +#endif + PangoFontDescription *desc; + PangoFontset *fset; + PangoFontMetrics *metrics; + + desc = pango_font_description_from_string(name); + if (!desc) + return NULL; + ctx = gtk_widget_get_pango_context(widget); + if (!ctx) { + pango_font_description_free(desc); + return NULL; + } + if (!pangofont_check_desc_makes_sense(ctx, desc)) { + pango_font_description_free(desc); + return NULL; + } +#ifndef PANGO_PRE_1POINT6 + map = pango_context_get_font_map(ctx); + if (!map) { + pango_font_description_free(desc); + return NULL; + } + fset = pango_font_map_load_fontset(map, ctx, desc, + pango_context_get_language(ctx)); +#else + fset = pango_context_load_fontset(ctx, desc, + pango_context_get_language(ctx)); +#endif + if (!fset) { + pango_font_description_free(desc); + return NULL; + } + metrics = pango_fontset_get_metrics(fset); + if (!metrics || + pango_font_metrics_get_approximate_digit_width(metrics) == 0) { + pango_font_description_free(desc); + g_object_unref(fset); + return NULL; + } + + pfont = snew(struct pangofont); + pfont->u.vt = &pangofont_vtable; + pfont->u.width = + PANGO_PIXELS(pango_font_metrics_get_approximate_digit_width(metrics)); + pfont->u.ascent = PANGO_PIXELS(pango_font_metrics_get_ascent(metrics)); + pfont->u.descent = PANGO_PIXELS(pango_font_metrics_get_descent(metrics)); + pfont->u.height = pfont->u.ascent + pfont->u.descent; + /* The Pango API is hardwired to UTF-8 */ + pfont->u.public_charset = CS_UTF8; + pfont->u.real_charset = CS_UTF8; + pfont->desc = desc; + pfont->fset = fset; + pfont->widget = widget; + pfont->bold = bold; + pfont->shadowoffset = shadowoffset; + pfont->shadowalways = shadowalways; + + pango_font_metrics_unref(metrics); + + return (unifont *)pfont; +} + +static void pangofont_destroy(unifont *font) +{ + struct pangofont *pfont = (struct pangofont *)font; + pango_font_description_free(pfont->desc); + g_object_unref(pfont->fset); + sfree(font); +} + +static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const char *string, int len, + int wide, int bold, int cellwidth) +{ + struct pangofont *pfont = (struct pangofont *)font; + PangoLayout *layout; + PangoRectangle rect; + int shadowbold = FALSE; + + if (wide) + cellwidth *= 2; + + y -= pfont->u.ascent; + + layout = pango_layout_new(gtk_widget_get_pango_context(pfont->widget)); + pango_layout_set_font_description(layout, pfont->desc); + if (bold > pfont->bold) { + if (pfont->shadowalways) + shadowbold = TRUE; + else { + PangoFontDescription *desc2 = + pango_font_description_copy_static(pfont->desc); + pango_font_description_set_weight(desc2, PANGO_WEIGHT_BOLD); + pango_layout_set_font_description(layout, desc2); + } + } + + while (len > 0) { + int clen; + + /* + * Extract a single UTF-8 character from the string. + */ + clen = 1; + while (clen < len && + (unsigned char)string[clen] >= 0x80 && + (unsigned char)string[clen] < 0xC0) + clen++; + + pango_layout_set_text(layout, string, clen); + pango_layout_get_pixel_extents(layout, NULL, &rect); + gdk_draw_layout(target, gc, x + (cellwidth - rect.width)/2, + y + (pfont->u.height - rect.height)/2, layout); + if (shadowbold) + gdk_draw_layout(target, gc, x + (cellwidth - rect.width)/2 + pfont->shadowoffset, + y + (pfont->u.height - rect.height)/2, layout); + + len -= clen; + string += clen; + x += cellwidth; + } + + g_object_unref(layout); +} + +/* + * Dummy size value to be used when converting a + * PangoFontDescription of a scalable font to a string for + * internal use. + */ +#define PANGO_DUMMY_SIZE 12 + +static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback, + void *callback_ctx) +{ + PangoContext *ctx; +#ifndef PANGO_PRE_1POINT6 + PangoFontMap *map; +#endif + PangoFontFamily **families; + int i, nfamilies; + + ctx = gtk_widget_get_pango_context(widget); + if (!ctx) + return; + + /* + * Ask Pango for a list of font families, and iterate through + * them. + */ +#ifndef PANGO_PRE_1POINT6 + map = pango_context_get_font_map(ctx); + if (!map) + return; + pango_font_map_list_families(map, &families, &nfamilies); +#else + pango_context_list_families(ctx, &families, &nfamilies); +#endif + for (i = 0; i < nfamilies; i++) { + PangoFontFamily *family = families[i]; + const char *familyname; + int flags; + PangoFontFace **faces; + int j, nfaces; + + /* + * Set up our flags for this font family, and get the name + * string. + */ + flags = FONTFLAG_CLIENTSIDE; +#ifndef PANGO_PRE_1POINT4 + /* + * In very early versions of Pango, we can't tell + * monospaced fonts from non-monospaced. + */ + if (!pango_font_family_is_monospace(family)) + flags |= FONTFLAG_NONMONOSPACED; +#endif + familyname = pango_font_family_get_name(family); + + /* + * Go through the available font faces in this family. + */ + pango_font_family_list_faces(family, &faces, &nfaces); + for (j = 0; j < nfaces; j++) { + PangoFontFace *face = faces[j]; + PangoFontDescription *desc; + const char *facename; + int *sizes; + int k, nsizes, dummysize; + + /* + * Get the face name string. + */ + facename = pango_font_face_get_face_name(face); + + /* + * Set up a font description with what we've got so + * far. We'll fill in the size field manually and then + * call pango_font_description_to_string() to give the + * full real name of the specific font. + */ + desc = pango_font_face_describe(face); + + /* + * See if this font has a list of specific sizes. + */ +#ifndef PANGO_PRE_1POINT4 + pango_font_face_list_sizes(face, &sizes, &nsizes); +#else + /* + * In early versions of Pango, that call wasn't + * supported; we just have to assume everything is + * scalable. + */ + sizes = NULL; +#endif + if (!sizes) { + /* + * Write a single entry with a dummy size. + */ + dummysize = PANGO_DUMMY_SIZE * PANGO_SCALE; + sizes = &dummysize; + nsizes = 1; + } + + /* + * If so, go through them one by one. + */ + for (k = 0; k < nsizes; k++) { + char *fullname; + char stylekey[128]; + + pango_font_description_set_size(desc, sizes[k]); + + fullname = pango_font_description_to_string(desc); + + /* + * Construct the sorting key for font styles. + */ + { + char *p = stylekey; + int n; + + n = pango_font_description_get_weight(desc); + /* Weight: normal, then lighter, then bolder */ + if (n <= PANGO_WEIGHT_NORMAL) + n = PANGO_WEIGHT_NORMAL - n; + p += sprintf(p, "%4d", n); + + n = pango_font_description_get_style(desc); + p += sprintf(p, " %2d", n); + + n = pango_font_description_get_stretch(desc); + /* Stretch: closer to normal sorts earlier */ + n = 2 * abs(PANGO_STRETCH_NORMAL - n) + + (n < PANGO_STRETCH_NORMAL); + p += sprintf(p, " %2d", n); + + n = pango_font_description_get_variant(desc); + p += sprintf(p, " %2d", n); + + } + + /* + * Got everything. Hand off to the callback. + * (The charset string is NULL, because only + * server-side X fonts use it.) + */ + callback(callback_ctx, fullname, familyname, NULL, facename, + stylekey, + (sizes == &dummysize ? 0 : PANGO_PIXELS(sizes[k])), + flags, &pangofont_vtable); + + g_free(fullname); + } + if (sizes != &dummysize) + g_free(sizes); + + pango_font_description_free(desc); + } + g_free(faces); + } + g_free(families); +} + +static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name, + int *size, int *flags, + int resolve_aliases) +{ + /* + * When given a Pango font name to try to make sense of for a + * font selector, we must normalise it to PANGO_DUMMY_SIZE and + * extract its original size (in pixels) into the `size' field. + */ + PangoContext *ctx; +#ifndef PANGO_PRE_1POINT6 + PangoFontMap *map; +#endif + PangoFontDescription *desc; + PangoFontset *fset; + PangoFontMetrics *metrics; + char *newname, *retname; + + desc = pango_font_description_from_string(name); + if (!desc) + return NULL; + ctx = gtk_widget_get_pango_context(widget); + if (!ctx) { + pango_font_description_free(desc); + return NULL; + } + if (!pangofont_check_desc_makes_sense(ctx, desc)) { + pango_font_description_free(desc); + return NULL; + } +#ifndef PANGO_PRE_1POINT6 + map = pango_context_get_font_map(ctx); + if (!map) { + pango_font_description_free(desc); + return NULL; + } + fset = pango_font_map_load_fontset(map, ctx, desc, + pango_context_get_language(ctx)); +#else + fset = pango_context_load_fontset(ctx, desc, + pango_context_get_language(ctx)); +#endif + if (!fset) { + pango_font_description_free(desc); + return NULL; + } + metrics = pango_fontset_get_metrics(fset); + if (!metrics || + pango_font_metrics_get_approximate_digit_width(metrics) == 0) { + pango_font_description_free(desc); + g_object_unref(fset); + return NULL; + } + + *size = PANGO_PIXELS(pango_font_description_get_size(desc)); + *flags = FONTFLAG_CLIENTSIDE; + pango_font_description_set_size(desc, PANGO_DUMMY_SIZE * PANGO_SCALE); + newname = pango_font_description_to_string(desc); + retname = dupstr(newname); + g_free(newname); + + pango_font_metrics_unref(metrics); + pango_font_description_free(desc); + g_object_unref(fset); + + return retname; +} + +static char *pangofont_scale_fontname(GtkWidget *widget, const char *name, + int size) +{ + PangoFontDescription *desc; + char *newname, *retname; + + desc = pango_font_description_from_string(name); + if (!desc) + return NULL; + pango_font_description_set_size(desc, size * PANGO_SCALE); + newname = pango_font_description_to_string(desc); + retname = dupstr(newname); + g_free(newname); + pango_font_description_free(desc); + + return retname; +} + +#endif /* GTK_CHECK_VERSION(2,0,0) */ + +/* ---------------------------------------------------------------------- + * Outermost functions which do the vtable dispatch. + */ + +/* + * Complete list of font-type subclasses. Listed in preference + * order for unifont_create(). (That is, in the extremely unlikely + * event that the same font name is valid as both a Pango and an + * X11 font, it will be interpreted as the former in the absence + * of an explicit type-disambiguating prefix.) + */ +static const struct unifont_vtable *unifont_types[] = { +#if GTK_CHECK_VERSION(2,0,0) + &pangofont_vtable, +#endif + &x11font_vtable, +}; + +/* + * Function which takes a font name and processes the optional + * scheme prefix. Returns the tail of the font name suitable for + * passing to individual font scheme functions, and also provides + * a subrange of the unifont_types[] array above. + * + * The return values `start' and `end' denote a half-open interval + * in unifont_types[]; that is, the correct way to iterate over + * them is + * + * for (i = start; i < end; i++) {...} + */ +static const char *unifont_do_prefix(const char *name, int *start, int *end) +{ + 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 use. + */ + for (i = 0; i < lenof(unifont_types); i++) { + if (strlen(unifont_types[i]->prefix) == colonpos && + !strncmp(unifont_types[i]->prefix, name, colonpos)) { + *start = i; + *end = i+1; + return name + colonpos + 1; + } + } + /* + * None matched, so return an empty scheme list to prevent + * any scheme from being called at all. + */ + *start = *end = 0; + return name + colonpos + 1; + } else { + /* + * No colon prefix, so just use all the subclasses. + */ + *start = 0; + *end = lenof(unifont_types); + return name; + } +} + +unifont *unifont_create(GtkWidget *widget, const char *name, int wide, + int bold, int shadowoffset, int shadowalways) +{ + int i, start, end; + + name = unifont_do_prefix(name, &start, &end); + + for (i = start; i < end; i++) { + unifont *ret = unifont_types[i]->create(widget, 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, int cellwidth) +{ + font->vt->draw_text(target, gc, font, x, y, string, len, + wide, bold, cellwidth); +} + +#if GTK_CHECK_VERSION(2,0,0) + +/* ---------------------------------------------------------------------- + * Implementation of a unified font selector. Used on GTK 2 only; + * for GTK 1 we still use the standard font selector. + */ + +typedef struct fontinfo fontinfo; + +typedef struct unifontsel_internal { + /* This must be the structure's first element, for cross-casting */ + unifontsel u; + GtkListStore *family_model, *style_model, *size_model; + GtkWidget *family_list, *style_list, *size_entry, *size_list; + GtkWidget *filter_buttons[4]; + GtkWidget *preview_area; + GdkPixmap *preview_pixmap; + int preview_width, preview_height; + GdkColor preview_fg, preview_bg; + int filter_flags; + tree234 *fonts_by_realname, *fonts_by_selorder; + fontinfo *selected; + int selsize, intendedsize; + int inhibit_response; /* inhibit callbacks when we change GUI controls */ +} unifontsel_internal; + +/* + * The structure held in the tree234s. All the string members are + * part of the same allocated area, so don't need freeing + * separately. + */ +struct fontinfo { + char *realname; + char *family, *charset, *style, *stylekey; + int size, flags; + /* + * Fallback sorting key, to permit multiple identical entries + * to exist in the selorder tree. + */ + int index; + /* + * Indices mapping fontinfo structures to indices in the list + * boxes. sizeindex is irrelevant if the font is scalable + * (size==0). + */ + int familyindex, styleindex, sizeindex; + /* + * The class of font. + */ + const struct unifont_vtable *fontclass; +}; + +struct fontinfo_realname_find { + const char *realname; + int flags; +}; + +static int strnullcasecmp(const char *a, const char *b) +{ + int i; + + /* + * If exactly one of the inputs is NULL, it compares before + * the other one. + */ + if ((i = (!b) - (!a)) != 0) + return i; + + /* + * NULL compares equal. + */ + if (!a) + return 0; + + /* + * Otherwise, ordinary strcasecmp. + */ + return g_strcasecmp(a, b); +} + +static int fontinfo_realname_compare(void *av, void *bv) +{ + fontinfo *a = (fontinfo *)av; + fontinfo *b = (fontinfo *)bv; + int i; + + if ((i = strnullcasecmp(a->realname, b->realname)) != 0) + return i; + if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK)) + return ((a->flags & FONTFLAG_SORT_MASK) < + (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1); + return 0; +} + +static int fontinfo_realname_find(void *av, void *bv) +{ + struct fontinfo_realname_find *a = (struct fontinfo_realname_find *)av; + fontinfo *b = (fontinfo *)bv; + int i; + + if ((i = strnullcasecmp(a->realname, b->realname)) != 0) + return i; + if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK)) + return ((a->flags & FONTFLAG_SORT_MASK) < + (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1); + return 0; +} + +static int fontinfo_selorder_compare(void *av, void *bv) +{ + fontinfo *a = (fontinfo *)av; + fontinfo *b = (fontinfo *)bv; + int i; + if ((i = strnullcasecmp(a->family, b->family)) != 0) + return i; + /* + * Font class comes immediately after family, so that fonts + * from different classes with the same family + */ + if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK)) + return ((a->flags & FONTFLAG_SORT_MASK) < + (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1); + if ((i = strnullcasecmp(a->charset, b->charset)) != 0) + return i; + if ((i = strnullcasecmp(a->stylekey, b->stylekey)) != 0) + return i; + if ((i = strnullcasecmp(a->style, b->style)) != 0) + return i; + if (a->size != b->size) + return (a->size < b->size ? -1 : +1); + if (a->index != b->index) + return (a->index < b->index ? -1 : +1); + return 0; +} + +static void unifontsel_deselect(unifontsel_internal *fs) +{ + fs->selected = NULL; + gtk_list_store_clear(fs->style_model); + gtk_list_store_clear(fs->size_model); + gtk_widget_set_sensitive(fs->u.ok_button, FALSE); + gtk_widget_set_sensitive(fs->size_entry, FALSE); +} + +static void unifontsel_setup_familylist(unifontsel_internal *fs) +{ + GtkTreeIter iter; + int i, listindex, minpos = -1, maxpos = -1; + char *currfamily = NULL; + int currflags = -1; + fontinfo *info; + + gtk_list_store_clear(fs->family_model); + listindex = 0; + + /* + * Search through the font tree for anything matching our + * current filter criteria. When we find one, add its font + * name to the list box. + */ + for (i = 0 ;; i++) { + info = (fontinfo *)index234(fs->fonts_by_selorder, i); + /* + * info may be NULL if we've just run off the end of the + * tree. We must still do a processing pass in that + * situation, in case we had an unfinished font record in + * progress. + */ + if (info && (info->flags &~ fs->filter_flags)) { + info->familyindex = -1; + continue; /* we're filtering out this font */ + } + if (!info || strnullcasecmp(currfamily, info->family) || + currflags != (info->flags & FONTFLAG_SORT_MASK)) { + /* + * We've either finished a family, or started a new + * one, or both. + */ + if (currfamily) { + gtk_list_store_append(fs->family_model, &iter); + gtk_list_store_set(fs->family_model, &iter, + 0, currfamily, 1, minpos, 2, maxpos+1, -1); + listindex++; + } + if (info) { + minpos = i; + currfamily = info->family; + currflags = info->flags & FONTFLAG_SORT_MASK; + } + } + if (!info) + break; /* now we're done */ + info->familyindex = listindex; + maxpos = i; + } + + /* + * If we've just filtered out the previously selected font, + * deselect it thoroughly. + */ + if (fs->selected && fs->selected->familyindex < 0) + unifontsel_deselect(fs); +} + +static void unifontsel_setup_stylelist(unifontsel_internal *fs, + int start, int end) +{ + GtkTreeIter iter; + int i, listindex, minpos = -1, maxpos = -1, started = FALSE; + char *currcs = NULL, *currstyle = NULL; + fontinfo *info; + + gtk_list_store_clear(fs->style_model); + listindex = 0; + started = FALSE; + + /* + * Search through the font tree for anything matching our + * current filter criteria. When we find one, add its charset + * and/or style name to the list box. + */ + for (i = start; i <= end; i++) { + if (i == end) + info = NULL; + else + info = (fontinfo *)index234(fs->fonts_by_selorder, i); + /* + * info may be NULL if we've just run off the end of the + * relevant data. We must still do a processing pass in + * that situation, in case we had an unfinished font + * record in progress. + */ + if (info && (info->flags &~ fs->filter_flags)) { + info->styleindex = -1; + continue; /* we're filtering out this font */ + } + if (!info || !started || strnullcasecmp(currcs, info->charset) || + strnullcasecmp(currstyle, info->style)) { + /* + * We've either finished a style/charset, or started a + * new one, or both. + */ + started = TRUE; + if (currstyle) { + gtk_list_store_append(fs->style_model, &iter); + gtk_list_store_set(fs->style_model, &iter, + 0, currstyle, 1, minpos, 2, maxpos+1, + 3, TRUE, -1); + listindex++; + } + if (info) { + minpos = i; + if (info->charset && strnullcasecmp(currcs, info->charset)) { + gtk_list_store_append(fs->style_model, &iter); + gtk_list_store_set(fs->style_model, &iter, + 0, info->charset, 1, -1, 2, -1, + 3, FALSE, -1); + listindex++; + } + currcs = info->charset; + currstyle = info->style; + } + } + if (!info) + break; /* now we're done */ + info->styleindex = listindex; + maxpos = i; + } +} + +static const int unifontsel_default_sizes[] = { 10, 12, 14, 16, 20, 24, 32 }; + +static void unifontsel_setup_sizelist(unifontsel_internal *fs, + int start, int end) +{ + GtkTreeIter iter; + int i, listindex; + char sizetext[40]; + fontinfo *info; + + gtk_list_store_clear(fs->size_model); + listindex = 0; + + /* + * Search through the font tree for anything matching our + * current filter criteria. When we find one, add its font + * name to the list box. + */ + for (i = start; i < end; i++) { + info = (fontinfo *)index234(fs->fonts_by_selorder, i); + if (info->flags &~ fs->filter_flags) { + info->sizeindex = -1; + continue; /* we're filtering out this font */ + } + if (info->size) { + sprintf(sizetext, "%d", info->size); + info->sizeindex = listindex; + gtk_list_store_append(fs->size_model, &iter); + gtk_list_store_set(fs->size_model, &iter, + 0, sizetext, 1, i, 2, info->size, -1); + listindex++; + } else { + int j; + + assert(i == start); + assert(i+1 == end); + + for (j = 0; j < lenof(unifontsel_default_sizes); j++) { + sprintf(sizetext, "%d", unifontsel_default_sizes[j]); + gtk_list_store_append(fs->size_model, &iter); + gtk_list_store_set(fs->size_model, &iter, 0, sizetext, 1, i, + 2, unifontsel_default_sizes[j], -1); + listindex++; + } + } + } +} + +static void unifontsel_set_filter_buttons(unifontsel_internal *fs) +{ + int i; + + for (i = 0; i < lenof(fs->filter_buttons); i++) { + int flagbit = GPOINTER_TO_INT(gtk_object_get_data + (GTK_OBJECT(fs->filter_buttons[i]), + "user-data")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(fs->filter_buttons[i]), + !!(fs->filter_flags & flagbit)); + } +} + +static void unifontsel_draw_preview_text(unifontsel_internal *fs) +{ + unifont *font; + char *sizename = NULL; + fontinfo *info = fs->selected; + + if (info) { + sizename = info->fontclass->scale_fontname + (GTK_WIDGET(fs->u.window), info->realname, fs->selsize); + font = info->fontclass->create(GTK_WIDGET(fs->u.window), + sizename ? sizename : info->realname, + FALSE, FALSE, 0, 0); + } else + font = NULL; + + if (fs->preview_pixmap) { + GdkGC *gc = gdk_gc_new(fs->preview_pixmap); + gdk_gc_set_foreground(gc, &fs->preview_bg); + gdk_draw_rectangle(fs->preview_pixmap, gc, 1, 0, 0, + fs->preview_width, fs->preview_height); + gdk_gc_set_foreground(gc, &fs->preview_fg); + if (font) { + /* + * The pangram used here is rather carefully + * constructed: it contains a sequence of very narrow + * letters (`jil') and a pair of adjacent very wide + * letters (`wm'). + * + * If the user selects a proportional font, it will be + * coerced into fixed-width character cells when used + * in the actual terminal window. We therefore display + * it the same way in the preview pane, so as to show + * it the way it will actually be displayed - and we + * deliberately pick a pangram which will show the + * resulting miskerning at its worst. + * + * We aren't trying to sell people these fonts; we're + * trying to let them make an informed choice. Better + * that they find out the problems with using + * proportional fonts in terminal windows here than + * that they go to the effort of selecting their font + * and _then_ realise it was a mistake. + */ + info->fontclass->draw_text(fs->preview_pixmap, gc, font, + 0, font->ascent, + "bankrupt jilted showmen quiz convex fogey", + 41, FALSE, FALSE, font->width); + info->fontclass->draw_text(fs->preview_pixmap, gc, font, + 0, font->ascent + font->height, + "BANKRUPT JILTED SHOWMEN QUIZ CONVEX FOGEY", + 41, FALSE, FALSE, font->width); + /* + * The ordering of punctuation here is also selected + * with some specific aims in mind. I put ` and ' + * together because some software (and people) still + * use them as matched quotes no matter what Unicode + * might say on the matter, so people can quickly + * check whether they look silly in a candidate font. + * The sequence #_@ is there to let people judge the + * suitability of the underscore as an effectively + * alphabetic character (since that's how it's often + * used in practice, at least by programmers). + */ + info->fontclass->draw_text(fs->preview_pixmap, gc, font, + 0, font->ascent + font->height * 2, + "0123456789!?,.:;<>()[]{}\\/`'\"+*-=~#_@|%&^$", + 42, FALSE, FALSE, font->width); + } + gdk_gc_unref(gc); + gdk_window_invalidate_rect(fs->preview_area->window, NULL, FALSE); + } + if (font) + info->fontclass->destroy(font); + + sfree(sizename); +} + +static void unifontsel_select_font(unifontsel_internal *fs, + fontinfo *info, int size, int leftlist, + int size_is_explicit) +{ + int index; + int minval, maxval; + GtkTreePath *treepath; + GtkTreeIter iter; + + fs->inhibit_response = TRUE; + + fs->selected = info; + fs->selsize = size; + if (size_is_explicit) + fs->intendedsize = size; + + gtk_widget_set_sensitive(fs->u.ok_button, TRUE); + + /* + * Find the index of this fontinfo in the selorder list. + */ + index = -1; + findpos234(fs->fonts_by_selorder, info, NULL, &index); + assert(index >= 0); + + /* + * Adjust the font selector flags and redo the font family + * list box, if necessary. + */ + if (leftlist <= 0 && + (fs->filter_flags | info->flags) != fs->filter_flags) { + fs->filter_flags |= info->flags; + unifontsel_set_filter_buttons(fs); + unifontsel_setup_familylist(fs); + } + + /* + * Find the appropriate family name and select it in the list. + */ + assert(info->familyindex >= 0); + treepath = gtk_tree_path_new_from_indices(info->familyindex, -1); + gtk_tree_selection_select_path + (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->family_list)), + treepath); + gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->family_list), + treepath, NULL, FALSE, 0.0, 0.0); + gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model), &iter, treepath); + gtk_tree_path_free(treepath); + + /* + * Now set up the font style list. + */ + gtk_tree_model_get(GTK_TREE_MODEL(fs->family_model), &iter, + 1, &minval, 2, &maxval, -1); + if (leftlist <= 1) + unifontsel_setup_stylelist(fs, minval, maxval); + + /* + * Find the appropriate style name and select it in the list. + */ + if (info->style) { + assert(info->styleindex >= 0); + treepath = gtk_tree_path_new_from_indices(info->styleindex, -1); + gtk_tree_selection_select_path + (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->style_list)), + treepath); + gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->style_list), + treepath, NULL, FALSE, 0.0, 0.0); + gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->style_model), + &iter, treepath); + gtk_tree_path_free(treepath); + + /* + * And set up the size list. + */ + gtk_tree_model_get(GTK_TREE_MODEL(fs->style_model), &iter, + 1, &minval, 2, &maxval, -1); + if (leftlist <= 2) + unifontsel_setup_sizelist(fs, minval, maxval); + + /* + * Find the appropriate size, and select it in the list. + */ + if (info->size) { + assert(info->sizeindex >= 0); + treepath = gtk_tree_path_new_from_indices(info->sizeindex, -1); + gtk_tree_selection_select_path + (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->size_list)), + treepath); + gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->size_list), + treepath, NULL, FALSE, 0.0, 0.0); + gtk_tree_path_free(treepath); + size = info->size; + } else { + int j; + for (j = 0; j < lenof(unifontsel_default_sizes); j++) + if (unifontsel_default_sizes[j] == size) { + treepath = gtk_tree_path_new_from_indices(j, -1); + gtk_tree_view_set_cursor(GTK_TREE_VIEW(fs->size_list), + treepath, NULL, FALSE); + gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->size_list), + treepath, NULL, FALSE, 0.0, + 0.0); + gtk_tree_path_free(treepath); + } + } + + /* + * And set up the font size text entry box. + */ + { + char sizetext[40]; + sprintf(sizetext, "%d", size); + gtk_entry_set_text(GTK_ENTRY(fs->size_entry), sizetext); + } + } else { + if (leftlist <= 2) + unifontsel_setup_sizelist(fs, 0, 0); + gtk_entry_set_text(GTK_ENTRY(fs->size_entry), ""); + } + + /* + * Grey out the font size edit box if we're not using a + * scalable font. + */ + gtk_entry_set_editable(GTK_ENTRY(fs->size_entry), fs->selected->size == 0); + gtk_widget_set_sensitive(fs->size_entry, fs->selected->size == 0); + + unifontsel_draw_preview_text(fs); + + fs->inhibit_response = FALSE; +} + +static void unifontsel_button_toggled(GtkToggleButton *tb, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + int newstate = gtk_toggle_button_get_active(tb); + int newflags; + int flagbit = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(tb), + "user-data")); + + if (newstate) + newflags = fs->filter_flags | flagbit; + else + newflags = fs->filter_flags & ~flagbit; + + if (fs->filter_flags != newflags) { + fs->filter_flags = newflags; + unifontsel_setup_familylist(fs); + } +} + +static void unifontsel_add_entry(void *ctx, const char *realfontname, + const char *family, const char *charset, + const char *style, const char *stylekey, + int size, int flags, + const struct unifont_vtable *fontclass) +{ + unifontsel_internal *fs = (unifontsel_internal *)ctx; + fontinfo *info; + int totalsize; + char *p; + + totalsize = sizeof(fontinfo) + strlen(realfontname) + + (family ? strlen(family) : 0) + (charset ? strlen(charset) : 0) + + (style ? strlen(style) : 0) + (stylekey ? strlen(stylekey) : 0) + 10; + info = (fontinfo *)smalloc(totalsize); + info->fontclass = fontclass; + p = (char *)info + sizeof(fontinfo); + info->realname = p; + strcpy(p, realfontname); + p += 1+strlen(p); + if (family) { + info->family = p; + strcpy(p, family); + p += 1+strlen(p); + } else + info->family = NULL; + if (charset) { + info->charset = p; + strcpy(p, charset); + p += 1+strlen(p); + } else + info->charset = NULL; + if (style) { + info->style = p; + strcpy(p, style); + p += 1+strlen(p); + } else + info->style = NULL; + if (stylekey) { + info->stylekey = p; + strcpy(p, stylekey); + p += 1+strlen(p); + } else + info->stylekey = NULL; + assert(p - (char *)info <= totalsize); + info->size = size; + info->flags = flags; + info->index = count234(fs->fonts_by_selorder); + + /* + * It's just conceivable that a misbehaving font enumerator + * might tell us about the same font real name more than once, + * in which case we should silently drop the new one. + */ + if (add234(fs->fonts_by_realname, info) != info) { + sfree(info); + return; + } + /* + * However, we should never get a duplicate key in the + * selorder tree, because the index field carefully + * disambiguates otherwise identical records. + */ + add234(fs->fonts_by_selorder, info); +} + +static fontinfo *update_for_intended_size(unifontsel_internal *fs, + fontinfo *info) +{ + fontinfo info2, *below, *above; + int pos; + + /* + * Copy the info structure. This doesn't copy its dynamic + * string fields, but that's unimportant because all we're + * going to do is to adjust the size field and use it in one + * tree search. + */ + info2 = *info; + info2.size = fs->intendedsize; + + /* + * Search in the tree to find the fontinfo structure which + * best approximates the size the user last requested. + */ + below = findrelpos234(fs->fonts_by_selorder, &info2, NULL, + REL234_LE, &pos); + above = index234(fs->fonts_by_selorder, pos+1); + + /* + * See if we've found it exactly, which is an easy special + * case. If we have, it'll be in `below' and not `above', + * because we did a REL234_LE rather than REL234_LT search. + */ + if (!fontinfo_selorder_compare(&info2, below)) + return below; + + /* + * Now we've either found two suitable fonts, one smaller and + * one larger, or we're at one or other extreme end of the + * scale. Find out which, by NULLing out either of below and + * above if it differs from this one in any respect but size + * (and the disambiguating index field). Bear in mind, also, + * that either one might _already_ be NULL if we're at the + * extreme ends of the font list. + */ + if (below) { + info2.size = below->size; + info2.index = below->index; + if (fontinfo_selorder_compare(&info2, below)) + below = NULL; + } + if (above) { + info2.size = above->size; + info2.index = above->index; + if (fontinfo_selorder_compare(&info2, above)) + above = NULL; + } + + /* + * Now return whichever of above and below is non-NULL, if + * that's unambiguous. + */ + if (!above) + return below; + if (!below) + return above; + + /* + * And now we really do have to make a choice about whether to + * round up or down. We'll do it by rounding to nearest, + * breaking ties by rounding up. + */ + if (above->size - fs->intendedsize <= fs->intendedsize - below->size) + return above; + else + return below; +} + +static void family_changed(GtkTreeSelection *treeselection, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + GtkTreeModel *treemodel; + GtkTreeIter treeiter; + int minval; + fontinfo *info; + + if (fs->inhibit_response) /* we made this change ourselves */ + return; + + if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter)) + return; + + gtk_tree_model_get(treemodel, &treeiter, 1, &minval, -1); + info = (fontinfo *)index234(fs->fonts_by_selorder, minval); + info = update_for_intended_size(fs, info); + if (!info) + return; /* _shouldn't_ happen unless font list is completely funted */ + if (!info->size) + fs->selsize = fs->intendedsize; /* font is scalable */ + unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize, + 1, FALSE); +} + +static void style_changed(GtkTreeSelection *treeselection, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + GtkTreeModel *treemodel; + GtkTreeIter treeiter; + int minval; + fontinfo *info; + + if (fs->inhibit_response) /* we made this change ourselves */ + return; + + if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter)) + return; + + gtk_tree_model_get(treemodel, &treeiter, 1, &minval, -1); + if (minval < 0) + return; /* somehow a charset heading got clicked */ + info = (fontinfo *)index234(fs->fonts_by_selorder, minval); + info = update_for_intended_size(fs, info); + if (!info) + return; /* _shouldn't_ happen unless font list is completely funted */ + if (!info->size) + fs->selsize = fs->intendedsize; /* font is scalable */ + unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize, + 2, FALSE); +} + +static void size_changed(GtkTreeSelection *treeselection, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + GtkTreeModel *treemodel; + GtkTreeIter treeiter; + int minval, size; + fontinfo *info; + + if (fs->inhibit_response) /* we made this change ourselves */ + return; + + if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter)) + return; + + gtk_tree_model_get(treemodel, &treeiter, 1, &minval, 2, &size, -1); + info = (fontinfo *)index234(fs->fonts_by_selorder, minval); + unifontsel_select_font(fs, info, info->size ? info->size : size, 3, TRUE); +} + +static void size_entry_changed(GtkEditable *ed, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + const char *text; + int size; + + if (fs->inhibit_response) /* we made this change ourselves */ + return; + + text = gtk_entry_get_text(GTK_ENTRY(ed)); + size = atoi(text); + + if (size > 0) { + assert(fs->selected->size == 0); + unifontsel_select_font(fs, fs->selected, size, 3, TRUE); + } +} + +static void alias_resolve(GtkTreeView *treeview, GtkTreePath *path, + GtkTreeViewColumn *column, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + GtkTreeIter iter; + int minval, newsize; + fontinfo *info, *newinfo; + char *newname; + + if (fs->inhibit_response) /* we made this change ourselves */ + return; + + gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model), &iter, path); + gtk_tree_model_get(GTK_TREE_MODEL(fs->family_model), &iter, 1,&minval, -1); + info = (fontinfo *)index234(fs->fonts_by_selorder, minval); + if (info) { + int flags; + struct fontinfo_realname_find f; + + newname = info->fontclass->canonify_fontname + (GTK_WIDGET(fs->u.window), info->realname, &newsize, &flags, TRUE); + + f.realname = newname; + f.flags = flags; + newinfo = find234(fs->fonts_by_realname, &f, fontinfo_realname_find); + + sfree(newname); + if (!newinfo) + return; /* font name not in our index */ + if (newinfo == info) + return; /* didn't change under canonification => not an alias */ + unifontsel_select_font(fs, newinfo, + newinfo->size ? newinfo->size : newsize, + 1, TRUE); + } +} + +static gint unifontsel_expose_area(GtkWidget *widget, GdkEventExpose *event, + gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + + if (fs->preview_pixmap) { + gdk_draw_pixmap(widget->window, + widget->style->fg_gc[GTK_WIDGET_STATE(widget)], + fs->preview_pixmap, + event->area.x, event->area.y, + event->area.x, event->area.y, + event->area.width, event->area.height); + } + return TRUE; +} + +static gint unifontsel_configure_area(GtkWidget *widget, + GdkEventConfigure *event, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + int ox, oy, nx, ny, x, y; + + /* + * Enlarge the pixmap, but never shrink it. + */ + ox = fs->preview_width; + oy = fs->preview_height; + x = event->width; + y = event->height; + if (x > ox || y > oy) { + if (fs->preview_pixmap) + gdk_pixmap_unref(fs->preview_pixmap); + + nx = (x > ox ? x : ox); + ny = (y > oy ? y : oy); + fs->preview_pixmap = gdk_pixmap_new(widget->window, nx, ny, -1); + fs->preview_width = nx; + fs->preview_height = ny; + + unifontsel_draw_preview_text(fs); + } + + gdk_window_invalidate_rect(widget->window, NULL, FALSE); + + return TRUE; +} + +unifontsel *unifontsel_new(const char *wintitle) +{ + unifontsel_internal *fs = snew(unifontsel_internal); + GtkWidget *table, *label, *w, *ww, *scroll; + GtkListStore *model; + GtkTreeViewColumn *column; + int lists_height, preview_height, font_width, style_width, size_width; + int i; + + fs->inhibit_response = FALSE; + fs->selected = NULL; + + { + /* + * Invent some magic size constants. + */ + GtkRequisition req; + label = gtk_label_new("Quite Long Font Name (Foundry)"); + gtk_widget_size_request(label, &req); + font_width = req.width; + lists_height = 14 * req.height; + preview_height = 5 * req.height; + gtk_label_set_text(GTK_LABEL(label), "Italic Extra Condensed"); + gtk_widget_size_request(label, &req); + style_width = req.width; + gtk_label_set_text(GTK_LABEL(label), "48000"); + gtk_widget_size_request(label, &req); + size_width = req.width; +#if GTK_CHECK_VERSION(2,10,0) + g_object_ref_sink(label); + g_object_unref(label); +#else + gtk_object_sink(GTK_OBJECT(label)); +#endif + } + + /* + * Create the dialog box and initialise the user-visible + * fields in the returned structure. + */ + fs->u.user_data = NULL; + fs->u.window = GTK_WINDOW(gtk_dialog_new()); + gtk_window_set_title(fs->u.window, wintitle); + fs->u.cancel_button = gtk_dialog_add_button + (GTK_DIALOG(fs->u.window), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL); + fs->u.ok_button = gtk_dialog_add_button + (GTK_DIALOG(fs->u.window), GTK_STOCK_OK, GTK_RESPONSE_OK); + gtk_widget_grab_default(fs->u.ok_button); + + /* + * Now set up the internal fields, including in particular all + * the controls that actually allow the user to select fonts. + */ + table = gtk_table_new(8, 3, FALSE); + gtk_widget_show(table); + gtk_table_set_col_spacings(GTK_TABLE(table), 8); +#if GTK_CHECK_VERSION(2,4,0) + /* GtkAlignment seems to be the simplest way to put padding round things */ + w = gtk_alignment_new(0, 0, 1, 1); + gtk_alignment_set_padding(GTK_ALIGNMENT(w), 8, 8, 8, 8); + gtk_container_add(GTK_CONTAINER(w), table); + gtk_widget_show(w); +#else + w = table; +#endif + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fs->u.window)->vbox), + w, TRUE, TRUE, 0); + + label = gtk_label_new_with_mnemonic("_Font:"); + gtk_widget_show(label); + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0); + + /* + * The Font list box displays only a string, but additionally + * stores two integers which give the limits within the + * tree234 of the font entries covered by this list entry. + */ + model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT); + w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE); + gtk_label_set_mnemonic_widget(GTK_LABEL(label), w); + gtk_widget_show(w); + column = gtk_tree_view_column_new_with_attributes + ("Font", gtk_cell_renderer_text_new(), + "text", 0, (char *)NULL); + gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); + gtk_tree_view_append_column(GTK_TREE_VIEW(w), column); + g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))), + "changed", G_CALLBACK(family_changed), fs); + g_signal_connect(G_OBJECT(w), "row-activated", + G_CALLBACK(alias_resolve), fs); + + scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll), + GTK_SHADOW_IN); + gtk_container_add(GTK_CONTAINER(scroll), w); + gtk_widget_show(scroll); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), + GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + gtk_widget_set_size_request(scroll, font_width, lists_height); + gtk_table_attach(GTK_TABLE(table), scroll, 0, 1, 1, 3, GTK_FILL, + GTK_EXPAND | GTK_FILL, 0, 0); + fs->family_model = model; + fs->family_list = w; + + label = gtk_label_new_with_mnemonic("_Style:"); + gtk_widget_show(label); + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0); + gtk_table_attach(GTK_TABLE(table), label, 1, 2, 0, 1, GTK_FILL, 0, 0, 0); + + /* + * The Style list box can contain insensitive elements + * (character set headings for server-side fonts), so we add + * an extra column to the list store to hold that information. + */ + model = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT, + G_TYPE_BOOLEAN); + w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE); + gtk_label_set_mnemonic_widget(GTK_LABEL(label), w); + gtk_widget_show(w); + column = gtk_tree_view_column_new_with_attributes + ("Style", gtk_cell_renderer_text_new(), + "text", 0, "sensitive", 3, (char *)NULL); + gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); + gtk_tree_view_append_column(GTK_TREE_VIEW(w), column); + g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))), + "changed", G_CALLBACK(style_changed), fs); + + scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll), + GTK_SHADOW_IN); + gtk_container_add(GTK_CONTAINER(scroll), w); + gtk_widget_show(scroll); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), + GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + gtk_widget_set_size_request(scroll, style_width, lists_height); + gtk_table_attach(GTK_TABLE(table), scroll, 1, 2, 1, 3, GTK_FILL, + GTK_EXPAND | GTK_FILL, 0, 0); + fs->style_model = model; + fs->style_list = w; + + label = gtk_label_new_with_mnemonic("Si_ze:"); + gtk_widget_show(label); + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0); + gtk_table_attach(GTK_TABLE(table), label, 2, 3, 0, 1, GTK_FILL, 0, 0, 0); + + /* + * The Size label attaches primarily to a text input box so + * that the user can select a size of their choice. The list + * of available sizes is secondary. + */ + fs->size_entry = w = gtk_entry_new(); + gtk_label_set_mnemonic_widget(GTK_LABEL(label), w); + gtk_widget_set_size_request(w, size_width, -1); + gtk_widget_show(w); + gtk_table_attach(GTK_TABLE(table), w, 2, 3, 1, 2, GTK_FILL, 0, 0, 0); + g_signal_connect(G_OBJECT(w), "changed", G_CALLBACK(size_entry_changed), + fs); + + model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT); + w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE); + gtk_widget_show(w); + column = gtk_tree_view_column_new_with_attributes + ("Size", gtk_cell_renderer_text_new(), + "text", 0, (char *)NULL); + gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); + gtk_tree_view_append_column(GTK_TREE_VIEW(w), column); + g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))), + "changed", G_CALLBACK(size_changed), fs); + + scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll), + GTK_SHADOW_IN); + gtk_container_add(GTK_CONTAINER(scroll), w); + gtk_widget_show(scroll); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), + GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + gtk_table_attach(GTK_TABLE(table), scroll, 2, 3, 2, 3, GTK_FILL, + GTK_EXPAND | GTK_FILL, 0, 0); + fs->size_model = model; + fs->size_list = w; + + /* + * Preview widget. + */ + fs->preview_area = gtk_drawing_area_new(); + fs->preview_pixmap = NULL; + fs->preview_width = 0; + fs->preview_height = 0; + fs->preview_fg.pixel = fs->preview_bg.pixel = 0; + fs->preview_fg.red = fs->preview_fg.green = fs->preview_fg.blue = 0x0000; + fs->preview_bg.red = fs->preview_bg.green = fs->preview_bg.blue = 0xFFFF; + gdk_colormap_alloc_color(gdk_colormap_get_system(), &fs->preview_fg, + FALSE, FALSE); + gdk_colormap_alloc_color(gdk_colormap_get_system(), &fs->preview_bg, + FALSE, FALSE); + gtk_signal_connect(GTK_OBJECT(fs->preview_area), "expose_event", + GTK_SIGNAL_FUNC(unifontsel_expose_area), fs); + gtk_signal_connect(GTK_OBJECT(fs->preview_area), "configure_event", + GTK_SIGNAL_FUNC(unifontsel_configure_area), fs); + gtk_widget_set_size_request(fs->preview_area, 1, preview_height); + gtk_widget_show(fs->preview_area); + ww = fs->preview_area; + w = gtk_frame_new(NULL); + gtk_container_add(GTK_CONTAINER(w), ww); + gtk_widget_show(w); +#if GTK_CHECK_VERSION(2,4,0) + ww = w; + /* GtkAlignment seems to be the simplest way to put padding round things */ + w = gtk_alignment_new(0, 0, 1, 1); + gtk_alignment_set_padding(GTK_ALIGNMENT(w), 8, 8, 8, 8); + gtk_container_add(GTK_CONTAINER(w), ww); + gtk_widget_show(w); +#endif + ww = w; + w = gtk_frame_new("Preview of font"); + gtk_container_add(GTK_CONTAINER(w), ww); + gtk_widget_show(w); + gtk_table_attach(GTK_TABLE(table), w, 0, 3, 3, 4, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 8); + + i = 0; + w = gtk_check_button_new_with_label("Show client-side fonts"); + gtk_object_set_data(GTK_OBJECT(w), "user-data", + GINT_TO_POINTER(FONTFLAG_CLIENTSIDE)); + gtk_signal_connect(GTK_OBJECT(w), "toggled", + GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs); + gtk_widget_show(w); + fs->filter_buttons[i++] = w; + gtk_table_attach(GTK_TABLE(table), w, 0, 3, 4, 5, GTK_FILL, 0, 0, 0); + w = gtk_check_button_new_with_label("Show server-side fonts"); + gtk_object_set_data(GTK_OBJECT(w), "user-data", + GINT_TO_POINTER(FONTFLAG_SERVERSIDE)); + gtk_signal_connect(GTK_OBJECT(w), "toggled", + GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs); + gtk_widget_show(w); + fs->filter_buttons[i++] = w; + gtk_table_attach(GTK_TABLE(table), w, 0, 3, 5, 6, GTK_FILL, 0, 0, 0); + w = gtk_check_button_new_with_label("Show server-side font aliases"); + gtk_object_set_data(GTK_OBJECT(w), "user-data", + GINT_TO_POINTER(FONTFLAG_SERVERALIAS)); + gtk_signal_connect(GTK_OBJECT(w), "toggled", + GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs); + gtk_widget_show(w); + fs->filter_buttons[i++] = w; + gtk_table_attach(GTK_TABLE(table), w, 0, 3, 6, 7, GTK_FILL, 0, 0, 0); + w = gtk_check_button_new_with_label("Show non-monospaced fonts"); + gtk_object_set_data(GTK_OBJECT(w), "user-data", + GINT_TO_POINTER(FONTFLAG_NONMONOSPACED)); + gtk_signal_connect(GTK_OBJECT(w), "toggled", + GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs); + gtk_widget_show(w); + fs->filter_buttons[i++] = w; + gtk_table_attach(GTK_TABLE(table), w, 0, 3, 7, 8, GTK_FILL, 0, 0, 0); + + assert(i == lenof(fs->filter_buttons)); + fs->filter_flags = FONTFLAG_CLIENTSIDE | FONTFLAG_SERVERSIDE | + FONTFLAG_SERVERALIAS; + unifontsel_set_filter_buttons(fs); + + /* + * Go and find all the font names, and set up our master font + * list. + */ + fs->fonts_by_realname = newtree234(fontinfo_realname_compare); + fs->fonts_by_selorder = newtree234(fontinfo_selorder_compare); + for (i = 0; i < lenof(unifont_types); i++) + unifont_types[i]->enum_fonts(GTK_WIDGET(fs->u.window), + unifontsel_add_entry, fs); + + /* + * And set up the initial font names list. + */ + unifontsel_setup_familylist(fs); + + fs->selsize = fs->intendedsize = 13; /* random default */ + gtk_widget_set_sensitive(fs->u.ok_button, FALSE); + + return (unifontsel *)fs; +} + +void unifontsel_destroy(unifontsel *fontsel) +{ + unifontsel_internal *fs = (unifontsel_internal *)fontsel; + fontinfo *info; + + if (fs->preview_pixmap) + gdk_pixmap_unref(fs->preview_pixmap); + + freetree234(fs->fonts_by_selorder); + while ((info = delpos234(fs->fonts_by_realname, 0)) != NULL) + sfree(info); + freetree234(fs->fonts_by_realname); + + gtk_widget_destroy(GTK_WIDGET(fs->u.window)); + sfree(fs); +} + +void unifontsel_set_name(unifontsel *fontsel, const char *fontname) +{ + unifontsel_internal *fs = (unifontsel_internal *)fontsel; + int i, start, end, size, flags; + const char *fontname2 = NULL; + fontinfo *info; + + /* + * Provide a default if given an empty or null font name. + */ + if (!fontname || !*fontname) + fontname = "server:fixed"; + + /* + * Call the canonify_fontname function. + */ + fontname = unifont_do_prefix(fontname, &start, &end); + for (i = start; i < end; i++) { + fontname2 = unifont_types[i]->canonify_fontname + (GTK_WIDGET(fs->u.window), fontname, &size, &flags, FALSE); + if (fontname2) + break; + } + if (i == end) + return; /* font name not recognised */ + + /* + * Now look up the canonified font name in our index. + */ + { + struct fontinfo_realname_find f; + f.realname = fontname2; + f.flags = flags; + info = find234(fs->fonts_by_realname, &f, fontinfo_realname_find); + } + + /* + * If we've found the font, and its size field is either + * correct or zero (the latter indicating a scalable font), + * then we're done. Otherwise, try looking up the original + * font name instead. + */ + if (!info || (info->size != size && info->size != 0)) { + struct fontinfo_realname_find f; + f.realname = fontname; + f.flags = flags; + + info = find234(fs->fonts_by_realname, &f, fontinfo_realname_find); + if (!info || info->size != size) + return; /* font name not in our index */ + } + + /* + * Now we've got a fontinfo structure and a font size, so we + * know everything we need to fill in all the fields in the + * dialog. + */ + unifontsel_select_font(fs, info, size, 0, TRUE); +} + +char *unifontsel_get_name(unifontsel *fontsel) +{ + unifontsel_internal *fs = (unifontsel_internal *)fontsel; + char *name; + + if (!fs->selected) + return NULL; + + if (fs->selected->size == 0) { + name = fs->selected->fontclass->scale_fontname + (GTK_WIDGET(fs->u.window), fs->selected->realname, fs->selsize); + if (name) { + char *ret = dupcat(fs->selected->fontclass->prefix, ":", + name, NULL); + sfree(name); + return ret; + } + } + + return dupcat(fs->selected->fontclass->prefix, ":", + fs->selected->realname, NULL); +} + +#endif /* GTK_CHECK_VERSION(2,0,0) */ diff --git a/unix/gtkfont.h b/unix/gtkfont.h new file mode 100644 index 00000000..f414594e --- /dev/null +++ b/unix/gtkfont.h @@ -0,0 +1,67 @@ +/* + * 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(GtkWidget *widget, const 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, int cellwidth); + +/* + * Unified font selector dialog. I can't be bothered to do a + * proper GTK subclassing today, so this will just be an ordinary + * data structure with some useful members. + * + * (Of course, these aren't the only members; this structure is + * contained within a bigger one which holds data visible only to + * the implementation.) + */ +typedef struct unifontsel { + void *user_data; /* settable by the user */ + GtkWindow *window; + GtkWidget *ok_button, *cancel_button; +} unifontsel; + +unifontsel *unifontsel_new(const char *wintitle); +void unifontsel_destroy(unifontsel *fontsel); +void unifontsel_set_name(unifontsel *fontsel, const char *fontname); +char *unifontsel_get_name(unifontsel *fontsel); + +#endif /* PUTTY_GTKFONT_H */ -- 2.11.0