X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/f96e9a61341021787a57c54c106831cfbdedd5c7..038ec85e825fc940c1387f64a88ae73b75f6822b:/unix/gtkfont.c diff --git a/unix/gtkfont.c b/unix/gtkfont.c index fb756309..d241db0e 100644 --- a/unix/gtkfont.c +++ b/unix/gtkfont.c @@ -30,12 +30,6 @@ * 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 @@ -47,6 +41,11 @@ * I haven't the energy. */ +#if !GLIB_CHECK_VERSION(1,3,7) +#define g_ascii_strcasecmp g_strcasecmp +#define g_ascii_strncasecmp g_strncasecmp +#endif + /* * Ad-hoc vtable mechanism to allow font structures to be * polymorphic. @@ -77,9 +76,12 @@ struct unifont_vtable { */ unifont *(*create)(GtkWidget *widget, const char *name, int wide, int bold, int shadowoffset, int shadowalways); + unifont *(*create_fallback)(GtkWidget *widget, int height, int wide, + int bold, int shadowoffset, int shadowalways); void (*destroy)(unifont *font); + int (*has_glyph)(unifont *font, wchar_t glyph); void (*draw_text)(GdkDrawable *target, GdkGC *gc, unifont *font, - int x, int y, const char *string, int len, int wide, + int x, int y, const wchar_t *string, int len, int wide, int bold, int cellwidth); void (*enum_fonts)(GtkWidget *widget, fontsel_add_entry callback, void *callback_ctx); @@ -94,11 +96,12 @@ struct unifont_vtable { }; /* ---------------------------------------------------------------------- - * GDK-based X11 font implementation. + * X11 font implementation, directly using Xlib calls. */ +static int x11font_has_glyph(unifont *font, wchar_t glyph); static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, - int x, int y, const char *string, int len, + int x, int y, const wchar_t *string, int len, int wide, int bold, int cellwidth); static unifont *x11font_create(GtkWidget *widget, const char *name, int wide, int bold, @@ -124,12 +127,12 @@ struct x11font { * failed, so that we don't keep trying and failing * subsequently). */ - GdkFont *fonts[4]; + XFontStruct *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(). + * whether we use XDrawString or XDrawString16, etc. */ int sixteen_bit; /* @@ -139,6 +142,14 @@ struct x11font { */ int variable; /* + * real_charset is the charset used when translating text into the + * font's internal encoding inside draw_text(). This need not be + * the same as the public_charset provided to the client; for + * example, public_charset might be CS_ISO8859_1 while + * real_charset is CS_ISO8859_1_X11. + */ + int real_charset; + /* * Data passed in to unifont_create(). */ int wide, bold, shadowoffset, shadowalways; @@ -146,7 +157,9 @@ struct x11font { static const struct unifont_vtable x11font_vtable = { x11font_create, + NULL, /* no fallback fonts in X11 */ x11font_destroy, + x11font_has_glyph, x11font_draw_text, x11font_enum_fonts, x11font_canonify_fontname, @@ -154,10 +167,9 @@ static const struct unifont_vtable x11font_vtable = { "server", }; -char *x11_guess_derived_font_name(GdkFont *font, int bold, int wide) +static char *x11_guess_derived_font_name(XFontStruct *xfs, int bold, int wide) { - XFontStruct *xfs = GDK_FONT_XFONT(font); - Display *disp = GDK_FONT_XDISPLAY(font); + Display *disp = GDK_DISPLAY(); Atom fontprop = XInternAtom(disp, "FONT", False); unsigned long ret; if (XGetFontProperty(xfs, fontprop, &ret)) { @@ -179,8 +191,10 @@ char *x11_guess_derived_font_name(GdkFont *font, int bold, int wide) p++; } - if (nstr < lenof(strings)) + if (nstr < lenof(strings)) { + sfree(dupname); return NULL; /* XLFD was malformed */ + } if (bold) strings[2] = "bold"; @@ -206,16 +220,78 @@ char *x11_guess_derived_font_name(GdkFont *font, int bold, int wide) return NULL; } -static int x11_font_width(GdkFont *font, int sixteen_bit) +static int x11_font_width(XFontStruct *xfs, int sixteen_bit) { if (sixteen_bit) { XChar2b space; space.byte1 = 0; space.byte2 = '0'; - return gdk_text_width(font, (const gchar *)&space, 2); + return XTextWidth16(xfs, &space, 1); + } else { + return XTextWidth(xfs, "0", 1); + } +} + +static int x11_font_has_glyph(XFontStruct *xfs, int byte1, int byte2) +{ + int index; + + /* + * Not to be confused with x11font_has_glyph, which is a method of + * the x11font 'class' and hence takes a unifont as argument. This + * is the low-level function which grubs about in an actual + * XFontStruct to see if a given glyph exists. + * + * We must do this ourselves rather than letting Xlib's + * XTextExtents16 do the job, because XTextExtents will helpfully + * substitute the font's default_char for any missing glyph and + * not tell us it did so, which precisely won't help us find out + * which glyphs _are_ missing. + * + * The man page for XQueryFont is rather confusing about how the + * per_char array in the XFontStruct is laid out, because it gives + * formulae for determining the two-byte X character code _from_ + * an index into the per_char array. Going the other way, it's + * rather simpler: + * + * The valid character codes have byte1 between min_byte1 and + * max_byte1 inclusive, and byte2 between min_char_or_byte2 and + * max_char_or_byte2 inclusive. This gives a rectangle of size + * (max_byte2-min_byte1+1) by + * (max_char_or_byte2-min_char_or_byte2+1), which is precisely the + * rectangle encoded in the per_char array. Hence, given a + * character code which is valid in the sense that it falls + * somewhere in that rectangle, its index in per_char is given by + * setting + * + * x = byte2 - min_char_or_byte2 + * y = byte1 - min_byte1 + * index = y * (max_char_or_byte2-min_char_or_byte2+1) + x + * + * If min_byte1 and min_byte2 are both zero, that's a special case + * which can be treated as if min_byte2 was 1 instead, i.e. the + * per_char array just runs from min_char_or_byte2 to + * max_char_or_byte2 inclusive, and byte1 should always be zero. + */ + + if (byte2 < xfs->min_char_or_byte2 || byte2 > xfs->max_char_or_byte2) + return FALSE; + + if (xfs->min_byte1 == 0 && xfs->max_byte1 == 0) { + index = byte2 - xfs->min_char_or_byte2; } else { - return gdk_char_width(font, '0'); + if (byte1 < xfs->min_byte1 || byte1 > xfs->max_byte1) + return FALSE; + index = ((byte2 - xfs->min_char_or_byte2) + + ((byte1 - xfs->min_byte1) * + (xfs->max_char_or_byte2 - xfs->min_char_or_byte2 + 1))); } + + if (!xfs->per_char) /* per_char NULL => everything in range exists */ + return TRUE; + + return (xfs->per_char[index].ascent + xfs->per_char[index].descent > 0 || + xfs->per_char[index].width > 0); } static unifont *x11font_create(GtkWidget *widget, const char *name, @@ -223,21 +299,17 @@ static unifont *x11font_create(GtkWidget *widget, const char *name, int shadowoffset, int shadowalways) { struct x11font *xfont; - GdkFont *font; XFontStruct *xfs; - Display *disp; + Display *disp = GDK_DISPLAY(); 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) + xfs = XLoadQueryFont(disp, name); + if (!xfs) 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); @@ -266,27 +338,19 @@ static unifont *x11font_create(GtkWidget *widget, const char *name, } /* - * 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. + * Hack for X line-drawing characters: if the primary font + * is encoded as ISO-8859-1, and has valid glyphs in the + * low character positions, it is assumed that those + * glyphs are the VT100 line-drawing character set. */ 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; - } + int ch; + for (ch = 1; ch < 32; ch++) + if (!x11_font_has_glyph(xfs, 0, ch)) + break; + if (ch == 32) + realcs = CS_ISO8859_1_X11; + } sfree(encoding); } @@ -303,13 +367,14 @@ static unifont *x11font_create(GtkWidget *widget, const char *name, 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.width = x11_font_width(xfs, sixteen_bit); + xfont->u.ascent = xfs->ascent; + xfont->u.descent = xfs->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->u.want_fallback = TRUE; + xfont->real_charset = realcs; + xfont->fonts[0] = xfs; xfont->allocated[0] = TRUE; xfont->sixteen_bit = sixteen_bit; xfont->variable = variable; @@ -328,63 +393,150 @@ static unifont *x11font_create(GtkWidget *widget, const char *name, static void x11font_destroy(unifont *font) { + Display *disp = GDK_DISPLAY(); 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]); + XFreeFont(disp, xfont->fonts[i]); sfree(font); } static void x11_alloc_subfont(struct x11font *xfont, int sfid) { + Display *disp = GDK_DISPLAY(); 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->fonts[sfid] = XLoadQueryFont(disp, derived_name); xfont->allocated[sfid] = TRUE; sfree(derived_name); + /* Note that xfont->fonts[sfid] may still be NULL, if XLQF failed. */ } -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) +static int x11font_has_glyph(unifont *font, wchar_t glyph) { - int step = clen * nchars, nsteps = 1, centre = FALSE; + struct x11font *xfont = (struct x11font *)font; + + if (xfont->sixteen_bit) { + /* + * This X font has 16-bit character indices, which means + * we can directly use our Unicode input value. + */ + return x11_font_has_glyph(xfont->fonts[0], glyph >> 8, glyph & 0xFF); + } else { + /* + * This X font has 8-bit indices, so we must convert to the + * appropriate character set. + */ + char sbstring[2]; + int sblen = wc_to_mb(xfont->real_charset, 0, &glyph, 1, + sbstring, 2, "", NULL, NULL); + if (sblen == 0 || !sbstring[0]) + return FALSE; /* not even in the charset */ + + return x11_font_has_glyph(xfont->fonts[0], 0, + (unsigned char)sbstring[0]); + } +} + +#if !GTK_CHECK_VERSION(2,0,0) +#define GDK_DRAWABLE_XID(d) GDK_WINDOW_XWINDOW(d) /* GTK1's name for this */ +#endif + +static void x11font_really_draw_text_16(GdkDrawable *target, XFontStruct *xfs, + GC gc, int x, int y, + const XChar2b *string, int nchars, + int shadowoffset, + int fontvariable, int cellwidth) +{ + Display *disp = GDK_DISPLAY(); + int step, nsteps, centre; if (fontvariable) { /* * In a variable-pitch font, we draw one character at a * time, and centre it in the character cell. */ - step = clen; + step = 1; nsteps = nchars; centre = TRUE; + } else { + /* + * In a fixed-pitch font, we can draw the whole lot in one go. + */ + step = nchars; + nsteps = 1; + centre = FALSE; } while (nsteps-- > 0) { int X = x; if (centre) - X += (cellwidth - gdk_text_width(font, string, step)) / 2; + X += (cellwidth - XTextWidth16(xfs, 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); + XDrawString16(disp, GDK_DRAWABLE_XID(target), gc, + X, y, string, step); + if (shadowoffset) + XDrawString16(disp, GDK_DRAWABLE_XID(target), 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, +static void x11font_really_draw_text(GdkDrawable *target, XFontStruct *xfs, + GC gc, int x, int y, + const char *string, int nchars, + int shadowoffset, + int fontvariable, int cellwidth) +{ + Display *disp = GDK_DISPLAY(); + int step, nsteps, centre; + + if (fontvariable) { + /* + * In a variable-pitch font, we draw one character at a + * time, and centre it in the character cell. + */ + step = 1; + nsteps = nchars; + centre = TRUE; + } else { + /* + * In a fixed-pitch font, we can draw the whole lot in one go. + */ + step = nchars; + nsteps = 1; + centre = FALSE; + } + + while (nsteps-- > 0) { + int X = x; + if (centre) + X += (cellwidth - XTextWidth(xfs, string, step)) / 2; + + XDrawString(disp, GDK_DRAWABLE_XID(target), gc, + X, y, string, step); + if (shadowoffset) + XDrawString(disp, GDK_DRAWABLE_XID(target), gc, + X + shadowoffset, y, string, step); + + x += cellwidth; + string += step; + } +} + +static void x11font_draw_text(GdkDrawable *target, GdkGC *gdkgc, unifont *font, + int x, int y, const wchar_t *string, int len, int wide, int bold, int cellwidth) { + Display *disp = GDK_DISPLAY(); struct x11font *xfont = (struct x11font *)font; + GC gc = GDK_GC_XGC(gdkgc); int sfid; - int shadowbold = FALSE; + int shadowoffset = 0; int mult = (wide ? 2 : 1); wide -= xfont->wide; @@ -395,7 +547,7 @@ static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, * use shadow bold. */ if (xfont->shadowalways && bold) { - shadowbold = TRUE; + shadowoffset = xfont->shadowoffset; bold = 0; } sfid = 2 * wide + bold; @@ -403,7 +555,7 @@ static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, x11_alloc_subfont(xfont, sfid); if (bold && !xfont->fonts[sfid]) { bold = 0; - shadowbold = TRUE; + shadowoffset = xfont->shadowoffset; sfid = 2 * wide + bold; if (!xfont->allocated[sfid]) x11_alloc_subfont(xfont, sfid); @@ -412,46 +564,38 @@ static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, if (!xfont->fonts[sfid]) return; /* we've tried our best, but no luck */ + XSetFont(disp, gc, xfont->fonts[sfid]->fid); + 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. + * we can directly use our Unicode input string. */ XChar2b *xcs; - wchar_t *wcs; - int nchars, maxchars, i; + int 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]; + xcs = snewn(len, XChar2b); + for (i = 0; i < len; i++) { + xcs[i].byte1 = string[i] >> 8; + xcs[i].byte2 = string[i]; } - x11font_really_draw_text(target, xfont->fonts[sfid], gc, x, y, - (gchar *)xcs, 2, nchars, - shadowbold, xfont->shadowoffset, - xfont->variable, cellwidth * mult); + x11font_really_draw_text_16(target, xfont->fonts[sfid], gc, x, y, + xcs, len, shadowoffset, + xfont->variable, cellwidth * mult); sfree(xcs); - sfree(wcs); } else { + /* + * This X font has 8-bit indices, so we must convert to the + * appropriate character set. + */ + char *sbstring = snewn(len+1, char); + int sblen = wc_to_mb(xfont->real_charset, 0, string, len, + sbstring, len+1, ".", NULL, NULL); x11font_really_draw_text(target, xfont->fonts[sfid], gc, x, y, - string, 1, len, - shadowbold, xfont->shadowoffset, + sbstring, sblen, shadowoffset, xfont->variable, cellwidth * mult); + sfree(sbstring); } } @@ -524,21 +668,21 @@ static void x11font_enum_fonts(GtkWidget *widget, style = p; p += sprintf(p, "%s", components[2][0] ? components[2] : "regular"); - if (!g_strcasecmp(components[3], "i")) + if (!g_ascii_strcasecmp(components[3], "i")) p += sprintf(p, " italic"); - else if (!g_strcasecmp(components[3], "o")) + else if (!g_ascii_strcasecmp(components[3], "o")) p += sprintf(p, " oblique"); - else if (!g_strcasecmp(components[3], "ri")) + else if (!g_ascii_strcasecmp(components[3], "ri")) p += sprintf(p, " reverse italic"); - else if (!g_strcasecmp(components[3], "ro")) + else if (!g_ascii_strcasecmp(components[3], "ro")) p += sprintf(p, " reverse oblique"); - else if (!g_strcasecmp(components[3], "ot")) + else if (!g_ascii_strcasecmp(components[3], "ot")) p += sprintf(p, " other-slant"); - if (components[4][0] && g_strcasecmp(components[4], "normal")) + if (components[4][0] && g_ascii_strcasecmp(components[4], "normal")) p += sprintf(p, " %s", components[4]); - if (!g_strcasecmp(components[10], "m")) + if (!g_ascii_strcasecmp(components[10], "m")) p += sprintf(p, " [M]"); - if (!g_strcasecmp(components[10], "c")) + if (!g_ascii_strcasecmp(components[10], "c")) p += sprintf(p, " [C]"); if (components[5][0]) p += sprintf(p, " %s", components[5]); @@ -550,23 +694,23 @@ static void x11font_enum_fonts(GtkWidget *widget, */ p++; stylekey = p; - if (!g_strcasecmp(components[2], "medium") || - !g_strcasecmp(components[2], "regular") || - !g_strcasecmp(components[2], "normal") || - !g_strcasecmp(components[2], "book")) + if (!g_ascii_strcasecmp(components[2], "medium") || + !g_ascii_strcasecmp(components[2], "regular") || + !g_ascii_strcasecmp(components[2], "normal") || + !g_ascii_strcasecmp(components[2], "book")) weightkey = 0; - else if (!g_strncasecmp(components[2], "demi", 4) || - !g_strncasecmp(components[2], "semi", 4)) + else if (!g_ascii_strncasecmp(components[2], "demi", 4) || + !g_ascii_strncasecmp(components[2], "semi", 4)) weightkey = 1; else weightkey = 2; - if (!g_strcasecmp(components[3], "r")) + if (!g_ascii_strcasecmp(components[3], "r")) slantkey = 0; - else if (!g_strncasecmp(components[3], "r", 1)) + else if (!g_ascii_strncasecmp(components[3], "r", 1)) slantkey = 2; else slantkey = 1; - if (!g_strcasecmp(components[4], "normal")) + if (!g_ascii_strcasecmp(components[4], "normal")) setwidthkey = 0; else setwidthkey = 1; @@ -638,19 +782,16 @@ static char *x11font_canonify_fontname(GtkWidget *widget, const char *name, * _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; + Display *disp = GDK_DISPLAY(); Atom fontprop, fontprop2; unsigned long ret; - if (!font) - return NULL; /* didn't make sense to us, sorry */ + xfs = XLoadQueryFont(disp, name); - gdk_font_ref(font); + if (!xfs) + return NULL; /* didn't make sense to us, sorry */ - xfs = GDK_FONT_XFONT(font); - disp = GDK_FONT_XDISPLAY(font); fontprop = XInternAtom(disp, "FONT", False); if (XGetFontProperty(xfs, fontprop, &ret)) { @@ -661,7 +802,7 @@ static char *x11font_canonify_fontname(GtkWidget *widget, const char *name, fontprop2 = XInternAtom(disp, "PIXEL_SIZE", False); if (XGetFontProperty(xfs, fontprop2, &fsize) && fsize > 0) { *size = fsize; - gdk_font_unref(font); + XFreeFont(disp, xfs); if (flags) { if (name[0] == '-' || resolve_aliases) *flags = FONTFLAG_SERVERSIDE; @@ -674,7 +815,8 @@ static char *x11font_canonify_fontname(GtkWidget *widget, const char *name, } } - gdk_font_unref(font); + XFreeFont(disp, xfs); + return NULL; /* something went wrong */ } @@ -694,12 +836,16 @@ static char *x11font_scale_fontname(GtkWidget *widget, const char *name, #define PANGO_PRE_1POINT6 /* make life easier for pre-1.4 folk */ #endif +static int pangofont_has_glyph(unifont *font, wchar_t glyph); static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, - int x, int y, const char *string, int len, + int x, int y, const wchar_t *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 unifont *pangofont_create_fallback(GtkWidget *widget, int height, + 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); @@ -728,7 +874,9 @@ struct pangofont { static const struct unifont_vtable pangofont_vtable = { pangofont_create, + pangofont_create_fallback, pangofont_destroy, + pangofont_has_glyph, pangofont_draw_text, pangofont_enum_fonts, pangofont_canonify_fontname, @@ -774,8 +922,8 @@ static int pangofont_check_desc_makes_sense(PangoContext *ctx, matched = FALSE; for (i = 0; i < nfamilies; i++) { - if (!g_strcasecmp(pango_font_family_get_name(families[i]), - pango_font_description_get_family(desc))) { + if (!g_ascii_strcasecmp(pango_font_family_get_name(families[i]), + pango_font_description_get_family(desc))) { matched = TRUE; break; } @@ -785,31 +933,19 @@ static int pangofont_check_desc_makes_sense(PangoContext *ctx, return matched; } -static unifont *pangofont_create(GtkWidget *widget, const char *name, - int wide, int bold, - int shadowoffset, int shadowalways) +static unifont *pangofont_create_internal(GtkWidget *widget, + PangoContext *ctx, + PangoFontDescription *desc, + 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) { @@ -841,9 +977,9 @@ static unifont *pangofont_create(GtkWidget *widget, const char *name, 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; + pfont->u.want_fallback = FALSE; /* 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; @@ -856,6 +992,49 @@ static unifont *pangofont_create(GtkWidget *widget, const char *name, return (unifont *)pfont; } +static unifont *pangofont_create(GtkWidget *widget, const char *name, + int wide, int bold, + int shadowoffset, int shadowalways) +{ + PangoContext *ctx; + PangoFontDescription *desc; + + 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; + } + return pangofont_create_internal(widget, ctx, desc, wide, bold, + shadowoffset, shadowalways); +} + +static unifont *pangofont_create_fallback(GtkWidget *widget, int height, + int wide, int bold, + int shadowoffset, int shadowalways) +{ + PangoContext *ctx; + PangoFontDescription *desc; + + desc = pango_font_description_from_string("Monospace"); + if (!desc) + return NULL; + ctx = gtk_widget_get_pango_context(widget); + if (!ctx) { + pango_font_description_free(desc); + return NULL; + } + pango_font_description_set_absolute_size(desc, height * PANGO_SCALE); + return pangofont_create_internal(widget, ctx, desc, wide, bold, + shadowoffset, shadowalways); +} + static void pangofont_destroy(unifont *font) { struct pangofont *pfont = (struct pangofont *)font; @@ -864,13 +1043,21 @@ static void pangofont_destroy(unifont *font) sfree(font); } +static int pangofont_has_glyph(unifont *font, wchar_t glyph) +{ + /* Pango implements font fallback, so assume it has everything */ + return TRUE; +} + static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, - int x, int y, const char *string, int len, + int x, int y, const wchar_t *string, int len, int wide, int bold, int cellwidth) { struct pangofont *pfont = (struct pangofont *)font; PangoLayout *layout; PangoRectangle rect; + char *utfstring, *utfptr; + int utflen; int shadowbold = FALSE; if (wide) @@ -891,31 +1078,106 @@ static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, } } - while (len > 0) { - int clen; + /* + * Pango always expects UTF-8, so convert the input wide character + * string to UTF-8. + */ + utfstring = snewn(len*6+1, char); /* UTF-8 has max 6 bytes/char */ + utflen = wc_to_mb(CS_UTF8, 0, string, len, + utfstring, len*6+1, ".", NULL, NULL); + + utfptr = utfstring; + while (utflen > 0) { + 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) + while (clen < utflen && + (unsigned char)utfptr[clen] >= 0x80 && + (unsigned char)utfptr[clen] < 0xC0) clen++; + n = 1; - pango_layout_set_text(layout, string, clen); + /* + * If it's a right-to-left character, we must display it on + * its own, to stop Pango helpfully re-reversing our already + * reversed text. + */ + if (!is_rtl(string[0])) { + + /* + * See if that character has the width we expect. + */ + pango_layout_set_text(layout, utfptr, 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 < utflen) { + int oldclen = clen; + clen++; /* skip UTF-8 introducer byte */ + while (clen < utflen && + (unsigned char)utfptr[clen] >= 0x80 && + (unsigned char)utfptr[clen] < 0xC0) + clen++; + n++; + pango_layout_set_text(layout, utfptr, clen); + pango_layout_get_pixel_extents(layout, NULL, &rect); + if (rect.width != n * cellwidth) { + clen = oldclen; + n--; + break; + } + } + } + } + + pango_layout_set_text(layout, utfptr, 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; + utflen -= clen; + utfptr += clen; + string += n; + x += n * cellwidth; } + sfree(utfstring); + g_object_unref(layout); } @@ -1178,6 +1440,8 @@ static char *pangofont_scale_fontname(GtkWidget *widget, const char *name, * 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.) + * + * The 'multifont' subclass is omitted here, as discussed above. */ static const struct unifont_vtable *unifont_types[] = { #if GTK_CHECK_VERSION(2,0,0) @@ -1254,13 +1518,131 @@ 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 x, int y, const wchar_t *string, int len, int wide, int bold, int cellwidth) { font->vt->draw_text(target, gc, font, x, y, string, len, wide, bold, cellwidth); } +/* ---------------------------------------------------------------------- + * Multiple-font wrapper. This is a type of unifont which encapsulates + * up to two other unifonts, permitting missing glyphs in the main + * font to be filled in by a fallback font. + * + * This is a type of unifont just like the previous two, but it has a + * separate constructor which is manually called by the client, so it + * doesn't appear in the list of available font types enumerated by + * unifont_create. This means it's not used by unifontsel either, so + * it doesn't need to support any methods except draw_text and + * destroy. + */ + +static void multifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const wchar_t *string, int len, + int wide, int bold, int cellwidth); +static void multifont_destroy(unifont *font); + +struct multifont { + struct unifont u; + unifont *main; + unifont *fallback; +}; + +static const struct unifont_vtable multifont_vtable = { + NULL, /* creation is done specially */ + NULL, + multifont_destroy, + NULL, + multifont_draw_text, + NULL, + NULL, + NULL, + "client", +}; + +unifont *multifont_create(GtkWidget *widget, const char *name, + int wide, int bold, + int shadowoffset, int shadowalways) +{ + int i; + unifont *font, *fallback; + struct multifont *mfont; + + font = unifont_create(widget, name, wide, bold, + shadowoffset, shadowalways); + if (!font) + return NULL; + + fallback = NULL; + if (font->want_fallback) { + for (i = 0; i < lenof(unifont_types); i++) { + if (unifont_types[i]->create_fallback) { + fallback = unifont_types[i]->create_fallback + (widget, font->height, wide, bold, + shadowoffset, shadowalways); + if (fallback) + break; + } + } + } + + /* + * Construct our multifont. Public members are all copied from the + * primary font we're wrapping. + */ + mfont = snew(struct multifont); + mfont->u.vt = &multifont_vtable; + mfont->u.width = font->width; + mfont->u.ascent = font->ascent; + mfont->u.descent = font->descent; + mfont->u.height = font->height; + mfont->u.public_charset = font->public_charset; + mfont->u.want_fallback = FALSE; /* shouldn't be needed, but just in case */ + mfont->main = font; + mfont->fallback = fallback; + + return (unifont *)mfont; +} + +static void multifont_destroy(unifont *font) +{ + struct multifont *mfont = (struct multifont *)font; + unifont_destroy(mfont->main); + if (mfont->fallback) + unifont_destroy(mfont->fallback); + sfree(font); +} + +static void multifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const wchar_t *string, int len, + int wide, int bold, int cellwidth) +{ + struct multifont *mfont = (struct multifont *)font; + int ok, i; + + while (len > 0) { + /* + * Find a maximal sequence of characters which are, or are + * not, supported by our main font. + */ + ok = mfont->main->vt->has_glyph(mfont->main, string[0]); + for (i = 1; + i < len && + !mfont->main->vt->has_glyph(mfont->main, string[i]) == !ok; + i++); + + /* + * Now display it. + */ + unifont_draw_text(target, gc, ok ? mfont->main : mfont->fallback, + x, y, string, i, wide, bold, cellwidth); + string += i; + len -= i; + x += i * cellwidth; + } +} + #if GTK_CHECK_VERSION(2,0,0) /* ---------------------------------------------------------------------- @@ -1338,7 +1720,7 @@ static int strnullcasecmp(const char *a, const char *b) /* * Otherwise, ordinary strcasecmp. */ - return g_strcasecmp(a, b); + return g_ascii_strcasecmp(a, b); } static int fontinfo_realname_compare(void *av, void *bv) @@ -1637,11 +2019,11 @@ static void unifontsel_draw_preview_text(unifontsel_internal *fs) */ info->fontclass->draw_text(fs->preview_pixmap, gc, font, 0, font->ascent, - "bankrupt jilted showmen quiz convex fogey", + L"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", + L"BANKRUPT JILTED SHOWMEN QUIZ CONVEX FOGEY", 41, FALSE, FALSE, font->width); /* * The ordering of punctuation here is also selected @@ -1657,7 +2039,7 @@ static void unifontsel_draw_preview_text(unifontsel_internal *fs) */ info->fontclass->draw_text(fs->preview_pixmap, gc, font, 0, font->ascent + font->height * 2, - "0123456789!?,.:;<>()[]{}\\/`'\"+*-=~#_@|%&^$", + L"0123456789!?,.:;<>()[]{}\\/`'\"+*-=~#_@|%&^$", 42, FALSE, FALSE, font->width); } gdk_gc_unref(gc); @@ -1908,6 +2290,8 @@ static fontinfo *update_for_intended_size(unifontsel_internal *fs, */ below = findrelpos234(fs->fonts_by_selorder, &info2, NULL, REL234_LE, &pos); + if (!below) + pos = -1; above = index234(fs->fonts_by_selorder, pos+1); /* @@ -1915,7 +2299,7 @@ static fontinfo *update_for_intended_size(unifontsel_internal *fs, * 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)) + if (below && !fontinfo_selorder_compare(&info2, below)) return below; /*