#include "gtkfont.h"
#include "tree234.h"
-/*
- * TODO on fontsel
- * ---------------
- *
- * - generalised style and padding polish
- * + work out why the list boxes don't go all the way to the
- * RHS of the dialog box
- *
- * - big testing and shakedown!
- */
-
/*
* 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
#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,
void (*enum_fonts)(GtkWidget *widget,
fontsel_add_entry callback, void *callback_ctx);
char *(*canonify_fontname)(GtkWidget *widget, const char *name, int *size,
- int resolve_aliases);
+ int *flags, int resolve_aliases);
char *(*scale_fontname)(GtkWidget *widget, const char *name, int size);
/*
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 resolve_aliases);
+ int *size, int *flags,
+ int resolve_aliases);
static char *x11font_scale_fontname(GtkWidget *widget, const char *name,
int size);
x11font_enum_fonts,
x11font_canonify_fontname,
x11font_scale_fontname,
- "server"
+ "server",
};
char *x11_guess_derived_font_name(GdkFont *font, int bold, int wide)
p += sprintf(p, "%04d%04d%s%04d%04d%s%04d%04d%s%04d%s%04d%s",
weightkey,
- strlen(components[2]), components[2],
+ (int)strlen(components[2]), components[2],
slantkey,
- strlen(components[3]), components[3],
+ (int)strlen(components[3]), components[3],
setwidthkey,
- strlen(components[4]), components[4],
- strlen(components[10]), components[10],
- strlen(components[5]), components[5]);
+ (int)strlen(components[4]), components[4],
+ (int)strlen(components[10]), components[10],
+ (int)strlen(components[5]), components[5]);
assert(p - tmp < thistmpsize);
}
static char *x11font_canonify_fontname(GtkWidget *widget, const char *name,
- int *size, int resolve_aliases)
+ int *size, int *flags,
+ int resolve_aliases)
{
/*
* When given an X11 font name to try to make sense of for a
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);
}
return NULL; /* shan't */
}
+#if GTK_CHECK_VERSION(2,0,0)
+
/* ----------------------------------------------------------------------
- * Pango font implementation.
+ * 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 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 resolve_aliases);
+ int *size, int *flags,
+ int resolve_aliases);
static char *pangofont_scale_fontname(GtkWidget *widget, const char *name,
int size);
pangofont_enum_fonts,
pangofont_canonify_fontname,
pangofont_scale_fontname,
- "client"
+ "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)
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) {
}
while (len > 0) {
- int clen;
+ int clen, n;
/*
- * Extract a single UTF-8 character from the string.
+ * We want to display every character from this string in
+ * the centre of its own character cell. In the worst case,
+ * this requires a separate text-drawing call for each
+ * character; but in the common case where the font is
+ * properly fixed-width, we can draw many characters in one
+ * go which is much faster.
+ *
+ * This still isn't really ideal. If you look at what
+ * happens in the X protocol as a result of all of this, you
+ * find - naturally enough - that each call to
+ * gdk_draw_layout() generates a separate set of X RENDER
+ * operations involving creating a picture, setting a clip
+ * rectangle, doing some drawing and undoing the whole lot.
+ * In an ideal world, we should _always_ be able to turn the
+ * contents of this loop into a single RenderCompositeGlyphs
+ * operation which internally specifies inter-character
+ * deltas to get the spacing right, which would give us full
+ * speed _even_ in the worst case of a non-fixed-width font.
+ * However, Pango's architecture and documentation are so
+ * unhelpful that I have no idea how if at all to persuade
+ * them to do that.
+ */
+
+ /*
+ * Start by extracting a single UTF-8 character from the
+ * string.
*/
clen = 1;
while (clen < len &&
(unsigned char)string[clen] >= 0x80 &&
(unsigned char)string[clen] < 0xC0)
clen++;
+ n = 1;
+
+ /*
+ * See if that character has the width we expect.
+ */
+ pango_layout_set_text(layout, string, clen);
+ pango_layout_get_pixel_extents(layout, NULL, &rect);
+
+ if (rect.width == cellwidth) {
+ /*
+ * Try extracting more characters, for as long as they
+ * stay well-behaved.
+ */
+ while (clen < len) {
+ int oldclen = clen;
+ clen++; /* skip UTF-8 introducer byte */
+ while (clen < len &&
+ (unsigned char)string[clen] >= 0x80 &&
+ (unsigned char)string[clen] < 0xC0)
+ clen++;
+ n++;
+ pango_layout_set_text(layout, string, clen);
+ pango_layout_get_pixel_extents(layout, NULL, &rect);
+ if (rect.width != n * cellwidth) {
+ clen = oldclen;
+ n--;
+ break;
+ }
+ }
+ }
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,
+ gdk_draw_layout(target, gc, x + (n*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,
+ gdk_draw_layout(target, gc, x + (n*cellwidth - rect.width)/2 + pfont->shadowoffset,
y + (pfont->u.height - rect.height)/2, layout);
len -= clen;
string += clen;
- x += cellwidth;
+ x += n * cellwidth;
}
g_object_unref(layout);
}
static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name,
- int *size, int resolve_aliases)
+ int *size, int *flags,
+ int resolve_aliases)
{
/*
* When given a Pango font name to try to make sense of for a
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) {
}
*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);
return retname;
}
+#endif /* GTK_CHECK_VERSION(2,0,0) */
+
/* ----------------------------------------------------------------------
* Outermost functions which do the vtable dispatch.
*/
* 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,
};
wide, bold, cellwidth);
}
+#if GTK_CHECK_VERSION(2,0,0)
+
/* ----------------------------------------------------------------------
- * Implementation of a unified font selector.
+ * 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;
const struct unifont_vtable *fontclass;
};
-static int fontinfo_realname_compare(void *av, void *bv)
-{
- fontinfo *a = (fontinfo *)av;
- fontinfo *b = (fontinfo *)bv;
- return g_strcasecmp(a->realname, b->realname);
-}
-
-static int fontinfo_realname_find(void *av, void *bv)
-{
- const char *a = (const char *)av;
- fontinfo *b = (fontinfo *)bv;
- return g_strcasecmp(a, b->realname);
-}
+struct fontinfo_realname_find {
+ const char *realname;
+ int flags;
+};
static int strnullcasecmp(const char *a, const char *b)
{
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;
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 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);
info->familyindex = -1;
continue; /* we're filtering out this font */
}
- if (!info || strnullcasecmp(currfamily, info->family)) {
+ 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 (info) {
minpos = i;
currfamily = info->family;
+ currflags = info->flags & FONTFLAG_SORT_MASK;
}
}
if (!info)
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,
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.
*/
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, TRUE);
- newinfo = find234(fs->fonts_by_realname, (char *)newname,
- fontinfo_realname_find);
+ (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 */
int i;
fs->inhibit_response = FALSE;
+ fs->selected = NULL;
{
/*
* Now set up the internal fields, including in particular all
* the controls that actually allow the user to select fonts.
*/
- table = gtk_table_new(3, 8, FALSE);
+ 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),
- table, TRUE, TRUE, 0);
+ w, TRUE, TRUE, 0);
label = gtk_label_new_with_mnemonic("_Font:");
gtk_widget_show(label);
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;
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_table_attach(GTK_TABLE(table), w, 0, 3, 3, 4,
GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 8);
- /*
- * FIXME: preview widget
- */
i = 0;
w = gtk_check_button_new_with_label("Show client-side fonts");
gtk_object_set_data(GTK_OBJECT(w), "user-data",
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;
+ fs->filter_flags = FONTFLAG_CLIENTSIDE | FONTFLAG_SERVERSIDE |
+ FONTFLAG_SERVERALIAS;
unifontsel_set_filter_buttons(fs);
/*
*/
unifontsel_setup_familylist(fs);
- fs->selected = NULL;
+ fs->selsize = fs->intendedsize = 13; /* random default */
+ gtk_widget_set_sensitive(fs->u.ok_button, FALSE);
return (unifontsel *)fs;
}
void unifontsel_set_name(unifontsel *fontsel, const char *fontname)
{
unifontsel_internal *fs = (unifontsel_internal *)fontsel;
- int i, start, end, size;
+ 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 = "fixed"; /* Pango zealots might prefer "Monospace 12" */
+ 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, FALSE);
+ (GTK_WIDGET(fs->u.window), fontname, &size, &flags, FALSE);
if (fontname2)
break;
}
/*
* Now look up the canonified font name in our index.
*/
- info = find234(fs->fonts_by_realname, (char *)fontname2,
- fontinfo_realname_find);
+ {
+ 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
* font name instead.
*/
if (!info || (info->size != size && info->size != 0)) {
- info = find234(fs->fonts_by_realname, (char *)fontname,
- fontinfo_realname_find);
+ 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 */
}
unifontsel_internal *fs = (unifontsel_internal *)fontsel;
char *name;
- assert(fs->selected);
+ 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)
- return name;
+ if (name) {
+ char *ret = dupcat(fs->selected->fontclass->prefix, ":",
+ name, NULL);
+ sfree(name);
+ return ret;
+ }
}
- return dupstr(fs->selected->realname);
+ return dupcat(fs->selected->fontclass->prefix, ":",
+ fs->selected->realname, NULL);
}
+
+#endif /* GTK_CHECK_VERSION(2,0,0) */