+
+static void draw_update(struct draw_ctx *dctx, int x, int y, int w, int h)
+{
+#if defined DRAW_TEXT_CAIRO && !defined NO_BACKING_PIXMAPS
+ if (dctx->uctx.type == DRAWTYPE_CAIRO) {
+ /*
+ * If inst->surface and inst->pixmap both exist, then we've
+ * just drawn new content to the former which we must copy to
+ * the latter.
+ */
+ cairo_t *cr = gdk_cairo_create(dctx->inst->pixmap);
+ cairo_set_source_surface(cr, dctx->inst->surface, 0, 0);
+ cairo_rectangle(cr, x, y, w, h);
+ cairo_fill(cr);
+ cairo_destroy(cr);
+ }
+#endif
+
+ /*
+ * Now we just queue a window redraw, which will cause
+ * inst->surface or inst->pixmap (whichever is appropriate for our
+ * compile mode) to be copied to the real window when we receive
+ * the resulting "expose" or "draw" event.
+ *
+ * Amazingly, this one API call is actually valid in all versions
+ * of GTK :-)
+ */
+ gtk_widget_queue_draw_area(dctx->inst->area, x, y, w, h);
+}
+
+static void draw_set_colour(struct draw_ctx *dctx, int col)
+{
+#ifdef DRAW_TEXT_GDK
+ if (dctx->uctx.type == DRAWTYPE_GDK) {
+ gdk_gc_set_foreground(dctx->uctx.u.gdk.gc, &dctx->inst->cols[col]);
+ }
+#endif
+#ifdef DRAW_TEXT_CAIRO
+ if (dctx->uctx.type == DRAWTYPE_CAIRO) {
+ cairo_set_source_rgb(dctx->uctx.u.cairo.cr,
+ dctx->inst->cols[col].red / 65535.0,
+ dctx->inst->cols[col].green / 65535.0,
+ dctx->inst->cols[col].blue / 65535.0);
+ }
+#endif
+}
+
+static void draw_rectangle(struct draw_ctx *dctx, int filled,
+ int x, int y, int w, int h)
+{
+#ifdef DRAW_TEXT_GDK
+ if (dctx->uctx.type == DRAWTYPE_GDK) {
+ gdk_draw_rectangle(dctx->uctx.u.gdk.target, dctx->uctx.u.gdk.gc,
+ filled, x, y, w, h);
+ }
+#endif
+#ifdef DRAW_TEXT_CAIRO
+ if (dctx->uctx.type == DRAWTYPE_CAIRO) {
+ cairo_new_path(dctx->uctx.u.cairo.cr);
+ if (filled) {
+ cairo_rectangle(dctx->uctx.u.cairo.cr, x, y, w, h);
+ cairo_fill(dctx->uctx.u.cairo.cr);
+ } else {
+ cairo_rectangle(dctx->uctx.u.cairo.cr,
+ x + 0.5, y + 0.5, w, h);
+ cairo_close_path(dctx->uctx.u.cairo.cr);
+ cairo_stroke(dctx->uctx.u.cairo.cr);
+ }
+ }
+#endif
+}
+
+static void draw_clip(struct draw_ctx *dctx, int x, int y, int w, int h)
+{
+#ifdef DRAW_TEXT_GDK
+ if (dctx->uctx.type == DRAWTYPE_GDK) {
+ GdkRectangle r;
+
+ r.x = x;
+ r.y = y;
+ r.width = w;
+ r.height = h;
+
+ gdk_gc_set_clip_rectangle(dctx->uctx.u.gdk.gc, &r);
+ }
+#endif
+#ifdef DRAW_TEXT_CAIRO
+ if (dctx->uctx.type == DRAWTYPE_CAIRO) {
+ cairo_reset_clip(dctx->uctx.u.cairo.cr);
+ cairo_new_path(dctx->uctx.u.cairo.cr);
+ cairo_rectangle(dctx->uctx.u.cairo.cr, x, y, w, h);
+ cairo_clip(dctx->uctx.u.cairo.cr);
+ }
+#endif
+}
+
+static void draw_point(struct draw_ctx *dctx, int x, int y)
+{
+#ifdef DRAW_TEXT_GDK
+ if (dctx->uctx.type == DRAWTYPE_GDK) {
+ gdk_draw_point(dctx->uctx.u.gdk.target, dctx->uctx.u.gdk.gc, x, y);
+ }
+#endif
+#ifdef DRAW_TEXT_CAIRO
+ if (dctx->uctx.type == DRAWTYPE_CAIRO) {
+ cairo_new_path(dctx->uctx.u.cairo.cr);
+ cairo_rectangle(dctx->uctx.u.cairo.cr, x, y, 1, 1);
+ cairo_fill(dctx->uctx.u.cairo.cr);
+ }
+#endif
+}
+
+static void draw_line(struct draw_ctx *dctx, int x0, int y0, int x1, int y1)
+{
+#ifdef DRAW_TEXT_GDK
+ if (dctx->uctx.type == DRAWTYPE_GDK) {
+ gdk_draw_line(dctx->uctx.u.gdk.target, dctx->uctx.u.gdk.gc,
+ x0, y0, x1, y1);
+ }
+#endif
+#ifdef DRAW_TEXT_CAIRO
+ if (dctx->uctx.type == DRAWTYPE_CAIRO) {
+ cairo_new_path(dctx->uctx.u.cairo.cr);
+ cairo_move_to(dctx->uctx.u.cairo.cr, x0 + 0.5, y0 + 0.5);
+ cairo_line_to(dctx->uctx.u.cairo.cr, x1 + 0.5, y1 + 0.5);
+ cairo_stroke(dctx->uctx.u.cairo.cr);
+ }
+#endif
+}
+
+static void draw_stretch_before(struct draw_ctx *dctx, int x, int y,
+ int w, int wdouble,
+ int h, int hdouble, int hbothalf)
+{
+#ifdef DRAW_TEXT_CAIRO
+ if (dctx->uctx.type == DRAWTYPE_CAIRO) {
+ cairo_matrix_t matrix;
+
+ matrix.xy = 0;
+ matrix.yx = 0;
+
+ if (wdouble) {
+ matrix.xx = 2;
+ matrix.x0 = -x;
+ } else {
+ matrix.xx = 1;
+ matrix.x0 = 0;
+ }
+
+ if (hdouble) {
+ matrix.yy = 2;
+ if (hbothalf) {
+ matrix.y0 = -(y+h);
+ } else {
+ matrix.y0 = -y;
+ }
+ } else {
+ matrix.yy = 1;
+ matrix.y0 = 0;
+ }
+ cairo_transform(dctx->uctx.u.cairo.cr, &matrix);
+ }
+#endif
+}
+
+static void draw_stretch_after(struct draw_ctx *dctx, int x, int y,
+ int w, int wdouble,
+ int h, int hdouble, int hbothalf)
+{
+#ifdef DRAW_TEXT_GDK
+#ifndef NO_BACKING_PIXMAPS
+ if (dctx->uctx.type == DRAWTYPE_GDK) {
+ /*
+ * I can't find any plausible StretchBlt equivalent in the X
+ * server, so I'm going to do this the slow and painful way.
+ * This will involve repeated calls to gdk_draw_pixmap() to
+ * stretch the text horizontally. It's O(N^2) in time and O(N)
+ * in network bandwidth, but you try thinking of a better way.
+ * :-(
+ */
+ int i;
+ if (wdouble) {
+ for (i = 0; i < w; i++) {
+ gdk_draw_pixmap(dctx->uctx.u.gdk.target,
+ dctx->uctx.u.gdk.gc,
+ dctx->uctx.u.gdk.target,
+ x + 2*i, y,
+ x + 2*i+1, y,
+ w - i, h);
+ }
+ w *= 2;
+ }
+
+ if (hdouble) {
+ int dt, db;
+ /* Now stretch vertically, in the same way. */
+ if (hbothalf)
+ dt = 0, db = 1;
+ else
+ dt = 1, db = 0;
+ for (i = 0; i < h; i += 2) {
+ gdk_draw_pixmap(dctx->uctx.u.gdk.target,
+ dctx->uctx.u.gdk.gc,
+ dctx->uctx.u.gdk.target,
+ x, y + dt*i + db,
+ x, y + dt*(i+1),
+ w, h-i-1);
+ }
+ }
+ }
+#else
+#error No way to implement stretching in GDK without a reliable backing pixmap
+#endif
+#endif /* DRAW_TEXT_GDK */
+#ifdef DRAW_TEXT_CAIRO
+ if (dctx->uctx.type == DRAWTYPE_CAIRO) {
+ cairo_set_matrix(dctx->uctx.u.cairo.cr,
+ &dctx->uctx.u.cairo.origmatrix);
+ }
+#endif
+}
+
+static void draw_backing_rect(struct gui_data *inst)
+{
+ struct draw_ctx *dctx = get_ctx(inst);
+ int w = inst->width * inst->font_width + 2*inst->window_border;
+ int h = inst->height * inst->font_height + 2*inst->window_border;
+ draw_set_colour(dctx, 258);
+ draw_rectangle(dctx, 1, 0, 0, w, h);
+ draw_update(dctx, 0, 0, w, h);
+ free_ctx(dctx);
+}
+