From 9bd1b234a098e366a16a02dc87b22fb500060dd3 Mon Sep 17 00:00:00 2001 From: Ben Harris Date: Fri, 25 Apr 2025 20:12:12 +0100 Subject: [PATCH] GTK: purely server-side X bitmap font rendering with Cairo This is a fairly radical change of how X bitmap fonts are handled when using Cairo for rendering. Before, we would download each glyph to the client on first use and then composite those glyphs into the terminal's backing surface. This worked pretty well when we were keeping an image of the whole screen on the client anyway, but once I'd pushed all the other Cairo rendering onto the X server, it meant that the character bitmaps had to be repeatedly pushed to the X server. The new arrangement just renders each string into a temporary Pixmap using the usual X text-drawing calls and then asks Cairo to paste it into the main backing Pixmap. It's tempting to draw the text straight into the backing Pixmap, but that would require dealing directly with X colour management. This way, we get to leave colours in the hands of Cairo (and hence the Render extension). There are still fragments of the old system around. Those should go in the next commit. --- unix/unifont.c | 133 ++++++++++++++++++++++--------------------------- 1 file changed, 60 insertions(+), 73 deletions(-) diff --git a/unix/unifont.c b/unix/unifont.c index 0ac77120..fdc3d7c1 100644 --- a/unix/unifont.c +++ b/unix/unifont.c @@ -685,70 +685,41 @@ static void x11font_cairo_setup( } } -static void x11font_cairo_cache_glyph( - Display *disp, x11font_individual *xfi, int glyphindex) -{ - if (xfi->nglyphs <= glyphindex) { - /* Round up to the next multiple of 256 on the general - * principle that Unicode characters come in contiguous blocks - * often used together */ - int old_nglyphs = xfi->nglyphs; - xfi->nglyphs = (glyphindex + 0x100) & ~0xFF; - xfi->glyphcache = sresize(xfi->glyphcache, xfi->nglyphs, - struct cairo_cached_glyph); - - while (old_nglyphs < xfi->nglyphs) { - xfi->glyphcache[old_nglyphs].surface = NULL; - old_nglyphs++; - } - } - cairo_surface_mark_dirty(xfi->pixmap_surface); - xfi->glyphcache[glyphindex].surface = cairo_image_surface_create( - CAIRO_FORMAT_A1, xfi->pixwidth, xfi->pixheight); - cairo_t *cr = cairo_create(xfi->glyphcache[glyphindex].surface); - cairo_set_source_surface(cr, xfi->pixmap_surface, 0, 0); - cairo_paint(cr); - cairo_destroy(cr); -} - -static void x11font_cairo_draw_glyph(unifont_drawctx *ctx, - x11font_individual *xfi, int x, int y, - int glyphindex) -{ - if (xfi->glyphcache[glyphindex].surface) { - cairo_pattern_t *glyph_pattern = cairo_pattern_create_for_surface( - xfi->glyphcache[glyphindex].surface); - cairo_matrix_t xfrm; - /* We really don't want bilinear interpolation of bitmap fonts. */ - cairo_pattern_set_filter(glyph_pattern, CAIRO_FILTER_NEAREST); - cairo_matrix_init_translate(&xfrm, -(x - xfi->pixoriginx), - -(y - xfi->pixoriginy)); - cairo_pattern_set_matrix(glyph_pattern, &xfrm); - cairo_mask(ctx->u.cairo.cr, glyph_pattern); - cairo_pattern_destroy(glyph_pattern); - } -} - static void x11font_cairo_draw_16( unifont_drawctx *ctx, x11font_individual *xfi, Display *disp, int x, int y, const void *vstring, int start, int length) { const XChar2b *string = (const XChar2b *)vstring + start; - int i; - for (i = 0; i < length; i++) { - if (x11_font_has_glyph(xfi->xfs, string[i].byte1, string[i].byte2)) { - int glyphindex = (256 * (unsigned char)string[i].byte1 + - (unsigned char)string[i].byte2); - if (glyphindex >= xfi->nglyphs || - !xfi->glyphcache[glyphindex].surface) { - XDrawImageString16(disp, xfi->pixmap, xfi->gc, - xfi->pixoriginx, xfi->pixoriginy, - string+i, 1); - x11font_cairo_cache_glyph(disp, xfi, glyphindex); - } - x11font_cairo_draw_glyph(ctx, xfi, x, y, glyphindex); - x += XTextWidth16(xfi->xfs, string+i, 1); - } + GdkWindow *widgetwin = gtk_widget_get_window(ctx->u.cairo.widget); + int widgetscr = GDK_SCREEN_XNUMBER(gdk_window_get_screen(widgetwin)); + XCharStruct bounds; + int pixwidth, pixheight, direction, font_ascent, font_descent; + Pixmap pixmap; + + XTextExtents16(xfi->xfs, string + start, length, + &direction, &font_ascent, &font_descent, &bounds); + pixwidth = bounds.rbearing - bounds.lbearing; + pixheight = bounds.ascent + bounds.descent; + if (pixwidth > 0 && pixheight > 0) { + pixmap = XCreatePixmap(disp, GDK_DRAWABLE_XID(widgetwin), + pixwidth, pixheight, 1); + XDrawImageString16(disp, pixmap, xfi->gc, + -bounds.lbearing, bounds.ascent, + string+start, length); + cairo_surface_t *surface = cairo_xlib_surface_create_for_bitmap( + disp, pixmap, ScreenOfDisplay(disp, widgetscr), + pixwidth, pixheight); + cairo_pattern_t *pattern = cairo_pattern_create_for_surface(surface); + /* We really don't want bilinear interpolation of bitmap fonts. */ + cairo_pattern_set_filter(pattern, CAIRO_FILTER_NEAREST); + cairo_matrix_t xfrm; + cairo_matrix_init_translate(&xfrm, + -x - bounds.lbearing, -y + bounds.ascent); + cairo_pattern_set_matrix(pattern, &xfrm); + cairo_mask(ctx->u.cairo.cr, pattern); + cairo_pattern_destroy(pattern); + cairo_surface_destroy(surface); + XFreePixmap(disp, pixmap); } } @@ -757,20 +728,36 @@ static void x11font_cairo_draw_8( int x, int y, const void *vstring, int start, int length) { const char *string = (const char *)vstring + start; - int i; - for (i = 0; i < length; i++) { - if (x11_font_has_glyph(xfi->xfs, 0, string[i])) { - int glyphindex = (unsigned char)string[i]; - if (glyphindex >= xfi->nglyphs || - !xfi->glyphcache[glyphindex].surface) { - XDrawImageString(disp, xfi->pixmap, xfi->gc, - xfi->pixoriginx, xfi->pixoriginy, - string+i, 1); - x11font_cairo_cache_glyph(disp, xfi, glyphindex); - } - x11font_cairo_draw_glyph(ctx, xfi, x, y, glyphindex); - x += XTextWidth(xfi->xfs, string+i, 1); - } + GdkWindow *widgetwin = gtk_widget_get_window(ctx->u.cairo.widget); + int widgetscr = GDK_SCREEN_XNUMBER(gdk_window_get_screen(widgetwin)); + XCharStruct bounds; + int pixwidth, pixheight, direction, font_ascent, font_descent; + Pixmap pixmap; + + XTextExtents(xfi->xfs, string + start, length, + &direction, &font_ascent, &font_descent, &bounds); + pixwidth = bounds.rbearing - bounds.lbearing; + pixheight = bounds.ascent + bounds.descent; + if (pixwidth > 0 && pixheight > 0) { + pixmap = XCreatePixmap(disp, GDK_DRAWABLE_XID(widgetwin), + pixwidth, pixheight, 1); + XDrawImageString(disp, pixmap, xfi->gc, + -bounds.lbearing, bounds.ascent, + string+start, length); + cairo_surface_t *surface = cairo_xlib_surface_create_for_bitmap( + disp, pixmap, ScreenOfDisplay(disp, widgetscr), + pixwidth, pixheight); + cairo_pattern_t *pattern = cairo_pattern_create_for_surface(surface); + /* We really don't want bilinear interpolation of bitmap fonts. */ + cairo_pattern_set_filter(pattern, CAIRO_FILTER_NEAREST); + cairo_matrix_t xfrm; + cairo_matrix_init_translate(&xfrm, + -x - bounds.lbearing, -y + bounds.ascent); + cairo_pattern_set_matrix(pattern, &xfrm); + cairo_mask(ctx->u.cairo.cr, pattern); + cairo_pattern_destroy(pattern); + cairo_surface_destroy(surface); + XFreePixmap(disp, pixmap); } } #endif /* DRAW_TEXT_CAIRO */