/* Copyright (C) 1989, 2000 artofcode LLC. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA, 02111-1307. */ /*$Id: gdevx.c,v 1.8.2.3.2.1 2003/01/17 00:49:02 giles Exp $ */ /* X Windows driver for Ghostscript library */ #include "gx.h" /* for gx_bitmap; includes std.h */ #include "math_.h" #include "memory_.h" #include "x_.h" #include "gserrors.h" #include "gsmatrix.h" /* for gscoord.h */ #include "gscoord.h" /* for gs_currentmatrix */ #include "gsdevice.h" /* for gs_currentdevice */ #include "gsparam.h" #include "gxdevice.h" #include "gxpath.h" #include "gxgetbit.h" #include "gxiparam.h" #include "gsiparm2.h" #include "gxdevmem.h" #include "gdevx.h" /* Define whether to try to read back exposure events after XGetImage. */ /****** THIS IS USELESS. XGetImage DOES NOT GENERATE EXPOSURE EVENTS. ******/ #define GET_IMAGE_EXPOSURES 0 /* GC descriptors */ private_st_device_X(); /* Forward references */ private int x_copy_image(P8(gx_device_X * xdev, const byte * base, int sourcex, int raster, int x, int y, int w, int h)); private int set_tile(P2(gx_device *, const gx_strip_bitmap *)); private void free_cp(P1(gx_device *)); /* Screen updating machinery */ private void update_init(P1(gx_device_X *)); private void update_do_flush(P1(gx_device_X *)); #define flush_text(xdev)\ if (IN_TEXT(xdev)) do_flush_text(xdev) private void do_flush_text(P1(gx_device_X *)); /* Driver procedures */ /* (External procedures are declared in gdevx.h.) */ /*extern int gdev_x_open(P1(gx_device_X *));*/ private dev_proc_open_device(x_open); private dev_proc_get_initial_matrix(x_get_initial_matrix); private dev_proc_sync_output(x_sync); private dev_proc_output_page(x_output_page); /*extern int gdev_x_close(P1(gx_device_X *));*/ private dev_proc_close_device(x_close); /*extern dev_proc_map_rgb_color(gdev_x_map_rgb_color);*/ /*extern dev_proc_map_color_rgb(gdev_x_map_color_rgb);*/ private dev_proc_fill_rectangle(x_fill_rectangle); private dev_proc_copy_mono(x_copy_mono); private dev_proc_copy_color(x_copy_color); /*extern dev_proc_get_params(gdev_x_get_params);*/ /*extern dev_proc_put_params(gdev_x_put_params);*/ /*extern dev_proc_get_xfont_procs(gdev_x_get_xfont_procs);*/ private dev_proc_get_page_device(x_get_page_device); private dev_proc_strip_tile_rectangle(x_strip_tile_rectangle); private dev_proc_begin_typed_image(x_begin_typed_image); private dev_proc_get_bits_rectangle(x_get_bits_rectangle); /*extern dev_proc_get_xfont_procs(gdev_x_finish_copydevice);*/ /* The device descriptor */ #define x_device(this_device, dev_body, max_bitmap) \ const gx_device_X this_device = { \ dev_body, \ { /* std_procs */ \ x_open, \ x_get_initial_matrix, \ x_sync, \ x_output_page, \ x_close, \ gdev_x_map_rgb_color, \ gdev_x_map_color_rgb, \ x_fill_rectangle, \ NULL, /* tile_rectangle */ \ x_copy_mono, \ x_copy_color, \ NULL, /* draw_line */ \ NULL, /* get_bits */ \ gdev_x_get_params, \ gdev_x_put_params, \ NULL, /* map_cmyk_color */ \ gdev_x_get_xfont_procs, \ NULL, /* get_xfont_device */ \ NULL, /* map_rgb_alpha_color */ \ x_get_page_device, \ NULL, /* get_alpha_bits */ \ NULL, /* copy_alpha */ \ NULL, /* get_band */ \ NULL, /* copy_rop */ \ NULL, /* fill_path */ \ NULL, /* stroke_path */ \ NULL, /* fill_mask */ \ NULL, /* fill_trapezoid */ \ NULL, /* fill_parallelogram */ \ NULL, /* fill_triangle */ \ NULL, /* draw_thin_line */ \ NULL, /* begin_image */ \ NULL, /* image_data */ \ NULL, /* end_image */ \ x_strip_tile_rectangle, \ NULL, /* strip_copy_rop */ \ NULL, /* get_clipping_box */ \ x_begin_typed_image, \ x_get_bits_rectangle, \ NULL, /* map_color_rgb_alpha */ \ NULL, /* create_compositor */ \ NULL, /* get_hardware_params */ \ NULL, /* text_begin */ \ gdev_x_finish_copydevice \ }, \ gx_device_bbox_common_initial(0 /*false*/, 1 /*true*/, 1 /*true*/), \ 0 /*false*/, /* is_buffered */ \ 1 /*true*/, /* IsPageDevice */ \ max_bitmap, /* MaxBitmap */ \ NULL, /* buffer */ \ 0, /* buffer_size */ \ { /* image */ \ 0, 0, /* width, height */ \ 0, XYBitmap, NULL, /* xoffset, format, data */ \ MSBFirst, 8, /* byte-order, bitmap-unit */ \ MSBFirst, 8, 1, /* bitmap-bit-order, bitmap-pad, depth */ \ 0, 1, /* bytes_per_line, bits_per_pixel */ \ 0, 0, 0, /* red_mask, green_mask, blue_mask */ \ NULL, /* *obdata */ \ {NULL, /* *(*create_image)() */ \ NULL, /* (*destroy_image)() */ \ NULL, /* (*get_pixel)() */ \ NULL, /* (*put_pixel)() */ \ NULL, /* *(*sub_image)() */ \ NULL /* (*add_pixel)() */ \ }, \ }, \ NULL, NULL, /* dpy, scr */ \ /* (connection not initialized) */ \ NULL, /* vinfo */ \ (Colormap) None, /* cmap */ \ (Window) None, /* win */ \ NULL, /* gc */ \ (Window) None, /* pwin */ \ (Pixmap) 0, /* bpixmap */ \ 0, /* ghostview */ \ (Window) None, /* mwin */ \ {identity_matrix_body}, /* initial matrix (filled in) */ \ (Atom) 0, (Atom) 0, (Atom) 0, /* Atoms: NEXT, PAGE, DONE */ \ { /* update */ \ { /* box */ \ {max_int_in_fixed, max_int_in_fixed}, \ {min_int_in_fixed, min_int_in_fixed} \ }, \ 0, /* area */ \ 0, /* total */ \ 0 /* count */ \ }, \ (Pixmap) 0, /* dest */ \ 0L, (ulong)~0L, /* colors_or, colors_and */ \ { /* cp */ \ (Pixmap) 0, /* pixmap */ \ NULL, /* gc */ \ -1, -1 /* raster, height */ \ }, \ { /* ht */ \ (Pixmap) None, /* pixmap */ \ (Pixmap) None, /* no_pixmap */ \ gx_no_bitmap_id, /* id */ \ 0, 0, 0, /* width, height, raster */ \ 0, 0 /* fore_c, back_c */ \ }, \ GXcopy, /* function */ \ FillSolid, /* fill_style */ \ 0, /* font */ \ 0, 0, /* back_color, fore_color */ \ 0, 0, /* background, foreground */ \ { 0 }, /* cman */ \ 0, 0, /* borderColor, borderWidth */ \ NULL, /* geometry */ \ 128, 5, /* maxGrayRamp, maxRGBRamp */ \ NULL, /* palette */ \ NULL, NULL, NULL, /* regularFonts, symbolFonts, dingbatFonts */ \ NULL, NULL, NULL, /* regular_fonts, symbol_fonts, dingbat_fonts */ \ 1, 1, /* useXFonts, useFontExtensions */ \ 1, 0, /* useScalableFonts, logXFonts */ \ 0.0, 0.0, /* xResolution, yResolution */ \ 1, /* useBackingPixmap */ \ 1, 1, /* useXPutImage, useXSetTile */ \ \ 0 /*false*/, /* AlwaysUpdate */ \ 20000, /* MaxTempPixmap */ \ 5000, /* MaxTempImage */ \ 100000, /* MaxBufferedTotal */ \ 100000, /* MaxBufferedArea */ \ max_int, /* MaxBufferedCount */ \ \ { /* text */ \ 0, /* item_count */ \ 0, /* char_count */ \ {0, 0}, /* origin */ \ 0, /* x */ \ { \ {0}}, /* items */ \ {0} /* chars */ \ } \ }; x_device(gs_x11_device, std_device_color_stype_body(gx_device_X, 0, "x11", &st_device_X, FAKE_RES * DEFAULT_WIDTH_10THS / 10, FAKE_RES * DEFAULT_HEIGHT_10THS / 10, /* x and y extent (nominal) */ FAKE_RES, FAKE_RES, /* x and y density (nominal) */ 24, 255, 256 ), 0); x_device(gs_x11alpha_device, std_device_dci_alpha_type_body(gx_device_X, 0, "x11alpha", &st_device_X, FAKE_RES * DEFAULT_WIDTH_10THS / 10, FAKE_RES * DEFAULT_HEIGHT_10THS / 10, /* x and y extent (nominal) */ FAKE_RES, FAKE_RES, /* x and y density (nominal) */ 3, 24, 255, 255, 256, 256, 4, 4 ), 50000000); /* If XPutImage doesn't work, do it ourselves. */ private int alt_put_image(P11(gx_device * dev, Display * dpy, Drawable win, GC gc, XImage * pi, int sx, int sy, int dx, int dy, unsigned w, unsigned h)); #define put_image(dpy,win,gc,im,sx,sy,x,y,w,h)\ BEGIN\ if ( xdev->useXPutImage ) {\ if (XInitImage(im) == 0)\ return_error(gs_error_unknownerror);\ XPutImage(dpy,win,gc,im,sx,sy,x,y,w,h);\ } else {\ int code_ = alt_put_image(dev,dpy,win,gc,im,sx,sy,x,y,w,h);\ if ( code_ < 0 ) return code_;\ }\ END /* Open the device. Most of the code is in gdevxini.c. */ private int x_open(gx_device * dev) { gx_device_X *xdev = (gx_device_X *) dev; int code = gdev_x_open(xdev); if (code < 0) return code; update_init(xdev); return 0; } /* Close the device. */ private int x_close(gx_device * dev) { gx_device_X *xdev = (gx_device_X *) dev; return gdev_x_close(xdev); } /* Get initial matrix for X device. */ /* This conflicts seriously with the code for page devices; */ /* we only do it if Ghostview is active. */ private void x_get_initial_matrix(gx_device * dev, gs_matrix * pmat) { gx_device_X *xdev = (gx_device_X *) dev; if (!xdev->ghostview) { gx_default_get_initial_matrix(dev, pmat); return; } pmat->xx = xdev->initial_matrix.xx; pmat->xy = xdev->initial_matrix.xy; pmat->yx = xdev->initial_matrix.yx; pmat->yy = xdev->initial_matrix.yy; pmat->tx = xdev->initial_matrix.tx; pmat->ty = xdev->initial_matrix.ty; } /* Synchronize the display with the commands already given. */ private int x_sync(gx_device * dev) { gx_device_X *xdev = (gx_device_X *) dev; update_do_flush(xdev); XSync(xdev->dpy, False); return 0; } /* Send event to ghostview process */ void gdev_x_send_event(gx_device_X *xdev, Atom msg) { XEvent event; event.xclient.type = ClientMessage; event.xclient.display = xdev->dpy; event.xclient.window = xdev->win; event.xclient.message_type = msg; event.xclient.format = 32; event.xclient.data.l[0] = xdev->mwin; event.xclient.data.l[1] = xdev->dest; XSendEvent(xdev->dpy, xdev->win, False, 0, &event); } /* Output "page" */ private int x_output_page(gx_device * dev, int num_copies, int flush) { gx_device_X *xdev = (gx_device_X *) dev; x_sync(dev); /* Send ghostview a "page" client event */ /* Wait for a "next" client event */ if (xdev->ghostview) { XEvent event; gdev_x_send_event(xdev, xdev->PAGE); XNextEvent(xdev->dpy, &event); while (event.type != ClientMessage || event.xclient.message_type != xdev->NEXT) { XNextEvent(xdev->dpy, &event); } } return gx_finish_output_page(dev, num_copies, flush); } /* Fill a rectangle with a color. */ private int x_fill_rectangle(gx_device * dev, int x, int y, int w, int h, gx_color_index color) { gx_device_X *xdev = (gx_device_X *) dev; fit_fill(dev, x, y, w, h); flush_text(xdev); X_SET_FILL_STYLE(xdev, FillSolid); X_SET_FORE_COLOR(xdev, color); X_SET_FUNCTION(xdev, GXcopy); XFillRectangle(xdev->dpy, xdev->dest, xdev->gc, x, y, w, h); /* If we are filling the entire screen, reset */ /* colors_or and colors_and. It's wasteful to test this */ /* on every operation, but there's no separate driver routine */ /* for erasepage (yet). */ if (x == 0 && y == 0 && w == xdev->width && h == xdev->height) { if (color == xdev->foreground || color == xdev->background) gdev_x_free_dynamic_colors(xdev); xdev->colors_or = xdev->colors_and = color; } if (xdev->bpixmap != (Pixmap) 0) { x_update_add(xdev, x, y, w, h); } if_debug5('F', "[F] fill (%d,%d):(%d,%d) %ld\n", x, y, w, h, (long)color); return 0; } /* Copy a monochrome bitmap. */ private int x_copy_mono(gx_device * dev, const byte * base, int sourcex, int raster, gx_bitmap_id id, int x, int y, int w, int h, gx_color_index zero, gx_color_index one) /* * X doesn't directly support the simple operation of writing a color * through a mask specified by an image. The plot is the following: * If neither color is gx_no_color_index ("transparent"), * use XPutImage with the "copy" function as usual. * If the color either bitwise-includes or is bitwise-included-in * every color written to date * (a special optimization for writing black/white on color displays), * use XPutImage with an appropriate Boolean function. * Otherwise, do the following complicated stuff: * Create pixmap of depth 1 if necessary. * If foreground color is "transparent" then * invert the raster data. * Use XPutImage to copy the raster image to the newly * created Pixmap. * Install the Pixmap as the clip_mask in the X GC and * tweak the clip origin. * Do an XFillRectangle, fill style=solid, specifying a * rectangle the same size as the original raster data. * De-install the clip_mask. */ { gx_device_X *xdev = (gx_device_X *) dev; int function = GXcopy; x_pixel bc = zero, fc = one; fit_copy(dev, base, sourcex, raster, id, x, y, w, h); flush_text(xdev); xdev->image.width = sourcex + w; xdev->image.height = h; xdev->image.data = (char *)base; xdev->image.bytes_per_line = raster; X_SET_FILL_STYLE(xdev, FillSolid); /* Check for null, easy 1-color, hard 1-color, and 2-color cases. */ if (zero != gx_no_color_index) { if (one != gx_no_color_index) { /* 2-color case. */ /* Simply replace existing bits with what's in the image. */ } else if (!(~xdev->colors_and & bc)) { function = GXand; fc = ~(x_pixel) 0; } else if (!(~bc & xdev->colors_or)) { function = GXor; fc = 0; } else { goto hard; } } else { if (one == gx_no_color_index) { /* no-op */ return 0; } else if (!(~xdev->colors_and & fc)) { function = GXand; bc = ~(x_pixel) 0; } else if (!(~fc & xdev->colors_or)) { function = GXor; bc = 0; } else { goto hard; } } xdev->image.format = XYBitmap; X_SET_FUNCTION(xdev, function); if (bc != xdev->back_color) { XSetBackground(xdev->dpy, xdev->gc, (xdev->back_color = bc)); } if (fc != xdev->fore_color) { XSetForeground(xdev->dpy, xdev->gc, (xdev->fore_color = fc)); } if (zero != gx_no_color_index) NOTE_COLOR(xdev, zero); if (one != gx_no_color_index) NOTE_COLOR(xdev, one); put_image(xdev->dpy, xdev->dest, xdev->gc, &xdev->image, sourcex, 0, x, y, w, h); goto out; hard: /* Handle the hard 1-color case. */ if (raster > xdev->cp.raster || h > xdev->cp.height) { /* Must allocate a new pixmap and GC. */ /* Release the old ones first. */ free_cp(dev); /* Create the clipping pixmap, depth must be 1. */ xdev->cp.pixmap = XCreatePixmap(xdev->dpy, xdev->win, raster << 3, h, 1); if (xdev->cp.pixmap == (Pixmap) 0) { lprintf("x_copy_mono: can't allocate pixmap\n"); return_error(gs_error_VMerror); } xdev->cp.gc = XCreateGC(xdev->dpy, xdev->cp.pixmap, 0, 0); if (xdev->cp.gc == (GC) 0) { lprintf("x_copy_mono: can't allocate GC\n"); return_error(gs_error_VMerror); } xdev->cp.raster = raster; xdev->cp.height = h; } /* Initialize static mask image params */ xdev->image.format = XYBitmap; X_SET_FUNCTION(xdev, GXcopy); /* Select polarity based on fg/bg transparency. */ if (one == gx_no_color_index) { /* invert */ XSetBackground(xdev->dpy, xdev->cp.gc, (x_pixel) 1); XSetForeground(xdev->dpy, xdev->cp.gc, (x_pixel) 0); X_SET_FORE_COLOR(xdev, zero); } else { XSetBackground(xdev->dpy, xdev->cp.gc, (x_pixel) 0); XSetForeground(xdev->dpy, xdev->cp.gc, (x_pixel) 1); X_SET_FORE_COLOR(xdev, one); } put_image(xdev->dpy, xdev->cp.pixmap, xdev->cp.gc, &xdev->image, sourcex, 0, 0, 0, w, h); /* Install as clipmask. */ XSetClipMask(xdev->dpy, xdev->gc, xdev->cp.pixmap); XSetClipOrigin(xdev->dpy, xdev->gc, x, y); /* * Draw a solid rectangle through the raster clip mask. * Note fill style is guaranteed to be solid from above. */ XFillRectangle(xdev->dpy, xdev->dest, xdev->gc, x, y, w, h); /* Tidy up. Free the pixmap if it's big. */ XSetClipMask(xdev->dpy, xdev->gc, None); if (raster * h > xdev->MaxTempPixmap) free_cp(dev); out:if (xdev->bpixmap != (Pixmap) 0) { /* We wrote to the pixmap, so update the display now. */ x_update_add(xdev, x, y, w, h); } return 0; } /* Internal routine to free the GC and pixmap used for copying. */ private void free_cp(gx_device * dev) { gx_device_X *xdev = (gx_device_X *) dev; if (xdev->cp.gc != NULL) { XFreeGC(xdev->dpy, xdev->cp.gc); xdev->cp.gc = NULL; } if (xdev->cp.pixmap != (Pixmap) 0) { XFreePixmap(xdev->dpy, xdev->cp.pixmap); xdev->cp.pixmap = (Pixmap) 0; } xdev->cp.raster = -1; /* mark as unallocated */ } /* Copy a color bitmap. */ private int x_copy_image(gx_device_X * xdev, const byte * base, int sourcex, int raster, int x, int y, int w, int h) { int depth = xdev->color_info.depth; X_SET_FILL_STYLE(xdev, FillSolid); X_SET_FUNCTION(xdev, GXcopy); /* Filling with a colored halftone often gives rise to */ /* copy_color calls for a single pixel. Check for this now. */ if (h == 1 && w == 1) { uint sbit = sourcex * depth; const byte *ptr = base + (sbit >> 3); x_pixel pixel; if (depth < 8) pixel = (byte) (*ptr << (sbit & 7)) >> (8 - depth); else { pixel = *ptr++; while ((depth -= 8) > 0) pixel = (pixel << 8) + *ptr++; } X_SET_FORE_COLOR(xdev, pixel); XDrawPoint(xdev->dpy, xdev->dest, xdev->gc, x, y); } else { xdev->image.width = sourcex + w; xdev->image.height = h; xdev->image.format = ZPixmap; xdev->image.data = (char *)base; xdev->image.depth = xdev->vinfo->depth; xdev->image.bytes_per_line = raster; xdev->image.bits_per_pixel = depth; if (XInitImage(&xdev->image) == 0) return_error(gs_error_unknownerror); XPutImage(xdev->dpy, xdev->dest, xdev->gc, &xdev->image, sourcex, 0, x, y, w, h); xdev->image.depth = xdev->image.bits_per_pixel = 1; } return 0; } private int x_copy_color(gx_device * dev, const byte * base, int sourcex, int raster, gx_bitmap_id id, int x, int y, int w, int h) { gx_device_X *xdev = (gx_device_X *) dev; int code; fit_copy(dev, base, sourcex, raster, id, x, y, w, h); flush_text(xdev); code = x_copy_image(xdev, base, sourcex, raster, x, y, w, h); if (xdev->bpixmap != (Pixmap) 0) x_update_add(xdev, x, y, w, h); if_debug4('F', "[F] copy_color (%d,%d):(%d,%d)\n", x, y, w, h); return code; } /* Get the page device. We reimplement this so that we can make this */ /* device be a page device conditionally. */ private gx_device * x_get_page_device(gx_device * dev) { return (((gx_device_X *) dev)->IsPageDevice ? dev : (gx_device *) 0); } /* Tile a rectangle. */ private int x_strip_tile_rectangle(gx_device * dev, const gx_strip_bitmap * tiles, int x, int y, int w, int h, gx_color_index zero, gx_color_index one, int px, int py) { gx_device_X *xdev = (gx_device_X *) dev; /* Give up if one color is transparent, or if the tile is colored. */ /* We should implement the latter someday, since X can handle it. */ if (one == gx_no_color_index || zero == gx_no_color_index) return gx_default_strip_tile_rectangle(dev, tiles, x, y, w, h, zero, one, px, py); /* For the moment, give up if the phase or shift is non-zero. */ if (tiles->shift | px | py) return gx_default_strip_tile_rectangle(dev, tiles, x, y, w, h, zero, one, px, py); fit_fill(dev, x, y, w, h); flush_text(xdev); /* Imaging with a halftone often gives rise to very small */ /* tile_rectangle calls. Check for this now. */ if (h <= 2 && w <= 2) { int j; X_SET_FILL_STYLE(xdev, FillSolid); X_SET_FUNCTION(xdev, GXcopy); for (j = y + h; --j >= y;) { const byte *ptr = tiles->data + (j % tiles->rep_height) * tiles->raster; int i; for (i = x + w; --i >= x;) { uint tx = i % tiles->rep_width; byte mask = 0x80 >> (tx & 7); x_pixel pixel = (ptr[tx >> 3] & mask ? one : zero); X_SET_FORE_COLOR(xdev, pixel); XDrawPoint(xdev->dpy, xdev->dest, xdev->gc, i, j); } } if (xdev->bpixmap != (Pixmap) 0) { x_update_add(xdev, x, y, w, h); } return 0; } /* * Remember, an X tile is already filled with particular * pixel values (i.e., colors). Therefore if we are changing * fore/background color, we must invalidate the tile (using * the same technique as in set_tile). This problem only * bites when using grayscale -- you may want to change * fg/bg but use the same halftone screen. */ if ((zero != xdev->ht.back_c) || (one != xdev->ht.fore_c)) xdev->ht.id = ~tiles->id; /* force reload */ X_SET_BACK_COLOR(xdev, zero); X_SET_FORE_COLOR(xdev, one); if (!set_tile(dev, tiles)) { /* Bad news. Fall back to the default algorithm. */ return gx_default_strip_tile_rectangle(dev, tiles, x, y, w, h, zero, one, px, py); } /* Use the tile to fill the rectangle */ X_SET_FILL_STYLE(xdev, FillTiled); X_SET_FUNCTION(xdev, GXcopy); XFillRectangle(xdev->dpy, xdev->dest, xdev->gc, x, y, w, h); if (xdev->bpixmap != (Pixmap) 0) { x_update_add(xdev, x, y, w, h); } if_debug6('F', "[F] tile (%d,%d):(%d,%d) %ld,%ld\n", x, y, w, h, (long)zero, (long)one); return 0; } /* Implement ImageType 2 using CopyArea if possible. */ /* Note that since ImageType 2 images don't have any source data, */ /* this procedure does all the work. */ private int x_begin_typed_image(gx_device * dev, const gs_imager_state * pis, const gs_matrix * pmat, const gs_image_common_t * pic, const gs_int_rect * prect, const gx_drawing_color * pdcolor, const gx_clip_path * pcpath, gs_memory_t * mem, gx_image_enum_common_t ** pinfo) { gx_device_X *xdev = (gx_device_X *) dev; const gs_image2_t *pim; gs_state *pgs; gx_device *sdev; gs_matrix smat, dmat; if (pic->type->index != 2) goto punt; pim = (const gs_image2_t *)pic; if (!pim->PixelCopy) goto punt; pgs = pim->DataSource; sdev = gs_currentdevice(pgs); if (dev->dname != sdev->dname || memcmp(&dev->color_info, &sdev->color_info, sizeof(dev->color_info)) ) goto punt; flush_text(xdev); gs_currentmatrix(pgs, &smat); /* * Figure 7.2 of the Adobe 3010 Supplement says that we should * compute CTM x ImageMatrix here, but I'm almost certain it * should be the other way around. Also see gximage2.c. */ gs_matrix_multiply(&pim->ImageMatrix, &smat, &smat); if (pis == 0) dmat = *pmat; else gs_currentmatrix((const gs_state *)pis, &dmat); if (!((is_xxyy(&dmat) || is_xyyx(&dmat)) && #define eqe(e) smat.e == dmat.e eqe(xx) && eqe(xy) && eqe(yx) && eqe(yy)) #undef eqe ) goto punt; { gs_rect rect, src, dest; gs_int_point size; int srcx, srcy, destx, desty; rect.p.x = rect.p.y = 0; rect.q.x = pim->Width, rect.q.y = pim->Height; gs_bbox_transform(&rect, &dmat, &dest); if (pcpath != NULL && !gx_cpath_includes_rectangle(pcpath, float2fixed(dest.p.x), float2fixed(dest.p.y), float2fixed(dest.q.x), float2fixed(dest.q.y)) ) goto punt; rect.q.x += (rect.p.x = pim->XOrigin); rect.q.y += (rect.p.y = pim->YOrigin); gs_bbox_transform(&rect, &smat, &src); (*pic->type->source_size) (pis, pic, &size); X_SET_FILL_STYLE(xdev, FillSolid); X_SET_FUNCTION(xdev, GXcopy); srcx = (int)(src.p.x + 0.5); srcy = (int)(src.p.y + 0.5); destx = (int)(dest.p.x + 0.5); desty = (int)(dest.p.y + 0.5); XCopyArea(xdev->dpy, xdev->bpixmap, xdev->bpixmap, xdev->gc, srcx, srcy, size.x, size.y, destx, desty); x_update_add(xdev, destx, desty, size.x, size.y); } return 0; punt:return gx_default_begin_typed_image(dev, pis, pmat, pic, prect, pdcolor, pcpath, mem, pinfo); } /* Read bits back from the screen. */ private int x_get_bits_rectangle(gx_device * dev, const gs_int_rect * prect, gs_get_bits_params_t * params, gs_int_rect ** unread) { gx_device_X *xdev = (gx_device_X *) dev; int depth = dev->color_info.depth; int x0 = prect->p.x, y0 = prect->p.y, x1 = prect->q.x, y1 = prect->q.y; uint width_bytes = ((x1 - x0) * depth + 7) >> 3; uint band = xdev->MaxTempImage / width_bytes; uint default_raster = bitmap_raster((x1 - x0) * depth); gs_get_bits_options_t options = params->options; uint raster = (options & GB_RASTER_SPECIFIED ? params->raster : (params->raster = default_raster)); long plane_mask = (1L << depth) - 1; int y, h; XImage *image; int code = 0; #if GET_IMAGE_EXPOSURES XWindowAttributes attributes; #endif /* GET_IMAGE_EXPOSURES */ if (x0 < 0 || y0 < 0 || x1 > dev->width || y1 > dev->height) return_error(gs_error_rangecheck); /* XGetImage can only handle x_offset = 0. */ if ((options & GB_OFFSET_SPECIFIED) && params->x_offset == 0) options = (options & ~GB_OFFSET_SPECIFIED) | GB_OFFSET_0; if (~options & (GB_RETURN_COPY | GB_OFFSET_0 | GB_PACKING_CHUNKY | GB_COLORS_NATIVE) || !(options & GB_ALIGN_ALL) || !(options & GB_RASTER_ALL) ) return gx_default_get_bits_rectangle(dev, prect, params, unread); params->options = GB_COLORS_NATIVE | GB_ALPHA_NONE | GB_PACKING_CHUNKY | GB_RETURN_COPY | GB_OFFSET_0 | (options & GB_ALIGN_ALL) | (options & GB_RASTER_SPECIFIED ? GB_RASTER_SPECIFIED : GB_RASTER_STANDARD); if (x0 >= x1 || y0 >= y1) return 0; if (x1 <= xdev->update.box.p.x || x0 >= xdev->update.box.q.x || y1 <= xdev->update.box.p.y || y0 >= xdev->update.box.q.y ) { /* * The area being read back doesn't overlap the pending update: * just flush text. */ flush_text(xdev); } else update_do_flush(xdev); /* * If we want a list of unread rectangles, turn on graphics * exposures, and accept exposure events. */ /****** ****** FOLLOWING IS WRONG. XGetImage DOES NOT GENERATE ****** EXPOSURE EVENTS. ******/ #if GET_IMAGE_EXPOSURES if (unread) { XSetGraphicsExposures(xdev->dpy, xdev->gc, True); XGetWindowAttributes(xdev->dpy, xdev->win, &attributes); XSelectInput(xdev->dpy, xdev->win, attributes.your_event_mask | ExposureMask); } #endif /* GET_IMAGE_EXPOSURES */ /* * The X library doesn't provide any way to specify the desired * bit or byte ordering for the result, so we may have to swap the * bit or byte order. */ if (band == 0) band = 1; for (y = y0; y < y1; y += h) { int cy; h = min(band, y1 - y); image = XGetImage(xdev->dpy, xdev->dest, x0, y, x1 - x0, h, plane_mask, ZPixmap); for (cy = y; cy < y + h; ++cy) { const byte *source = (const byte *)image->data + (cy - y) * image->bytes_per_line; byte *dest = params->data[0] + (cy - y0) * raster; /* * XGetImage can return an image with any bit order, byte order, * unit size (for bitmaps), and bits_per_pixel it wants: it's up * to us to convert the results. (It's a major botch in the X * design that even though the server has to have the ability to * convert images from any format to any format, there's no way * to specify a requested format for XGetImage.) */ if (image->bits_per_pixel == image->depth && (image->depth > 1 || image->bitmap_bit_order == MSBFirst) && (image->byte_order == MSBFirst || image->depth <= 8) ) { /* * The server has been nice enough to return an image in the * format we use. */ memcpy(dest, source, width_bytes); } else { /* * We need to swap byte order and/or bit order. What a * totally unnecessary nuisance! For the moment, the only * cases we deal with are 16- and 24-bit images with padding * and/or byte swapping. */ if (image->depth == 24) { int cx; const byte *p = source; byte *q = dest; int step = image->bits_per_pixel >> 3; if (image->byte_order == MSBFirst) { p += step - 3; for (cx = x0; cx < x1; p += step, q += 3, ++cx) q[0] = p[0], q[1] = p[1], q[2] = p[2]; } else { for (cx = x0; cx < x1; p += step, q += 3, ++cx) q[0] = p[2], q[1] = p[1], q[2] = p[0]; } } else if (image->depth == 16) { int cx; const byte *p = source; byte *q = dest; int step = image->bits_per_pixel >> 3; if (image->byte_order == MSBFirst) { p += step - 2; for (cx = x0; cx < x1; p += step, q += 2, ++cx) q[0] = p[0], q[1] = p[1]; } else { for (cx = x0; cx < x1; p += step, q += 2, ++cx) q[0] = p[1], q[1] = p[0]; } } else code = gs_note_error(gs_error_rangecheck); } } XDestroyImage(image); } if (unread) { #if GET_IMAGE_EXPOSURES XEvent event; #endif /* GET_IMAGE_EXPOSURES */ *unread = 0; #if GET_IMAGE_EXPOSURES /* Read any exposure events. */ XWindowEvent(xdev->dpy, xdev->win, ExposureMask, &event); if (event.type == GraphicsExpose) { gs_int_rect *rects = (gs_int_rect *) gs_alloc_bytes(dev->memory, sizeof(gs_int_rect), "x_get_bits_rectangle"); int num_rects = 0; for (;;) { if (rects == 0) { code = gs_note_error(gs_error_VMerror); break; } #define xevent (*(XGraphicsExposeEvent *)&event) rects[num_rects].q.x = xevent.width + (rects[num_rects].p.x = xevent.x); rects[num_rects].q.y = xevent.height + (rects[num_rects].p.y = xevent.y); ++num_rects; if (!xevent.count) break; #undef xevent rects = gs_resize_object(dev->memory, rects, (num_rects + 1) * sizeof(gs_int_rect), "x_get_bits_rectangle"); } if (code >= 0) { *unread = rects; code = num_rects; } } /* Restore the window state. */ XSetGraphicsExposures(xdev->dpy, xdev->gc, False); XSelectInput(xdev->dpy, xdev->win, attributes.your_event_mask); #endif /* GET_IMAGE_EXPOSURES */ } return code; } /* Set up with a specified tile. */ /* Return false if we can't do it for some reason. */ private int set_tile(gx_device * dev, const gx_strip_bitmap * tile) { gx_device_X *xdev = (gx_device_X *) dev; #ifdef DEBUG if (gs_debug['T']) return 0; #endif if (tile->id == xdev->ht.id && tile->id != gx_no_bitmap_id) return xdev->useXSetTile; /* Set up the tile Pixmap */ if (tile->size.x != xdev->ht.width || tile->size.y != xdev->ht.height || xdev->ht.pixmap == (Pixmap) 0) { if (xdev->ht.pixmap != (Pixmap) 0) XFreePixmap(xdev->dpy, xdev->ht.pixmap); xdev->ht.pixmap = XCreatePixmap(xdev->dpy, xdev->win, tile->size.x, tile->size.y, xdev->vinfo->depth); if (xdev->ht.pixmap == (Pixmap) 0) return 0; xdev->ht.width = tile->size.x, xdev->ht.height = tile->size.y; xdev->ht.raster = tile->raster; } xdev->ht.fore_c = xdev->fore_color; xdev->ht.back_c = xdev->back_color; /* Copy the tile into the Pixmap */ xdev->image.data = (char *)tile->data; xdev->image.width = tile->size.x; xdev->image.height = tile->size.y; xdev->image.bytes_per_line = tile->raster; xdev->image.format = XYBitmap; X_SET_FILL_STYLE(xdev, FillSolid); #ifdef DEBUG if (gs_debug['H']) { int i; dlprintf4("[H] 0x%lx: width=%d height=%d raster=%d\n", (ulong) tile->data, tile->size.x, tile->size.y, tile->raster); dlputs(""); for (i = 0; i < tile->raster * tile->size.y; i++) dprintf1(" %02x", tile->data[i]); dputc('\n'); } #endif XSetTile(xdev->dpy, xdev->gc, xdev->ht.no_pixmap); /* *** X bug *** */ X_SET_FUNCTION(xdev, GXcopy); put_image(xdev->dpy, xdev->ht.pixmap, xdev->gc, &xdev->image, 0, 0, 0, 0, tile->size.x, tile->size.y); XSetTile(xdev->dpy, xdev->gc, xdev->ht.pixmap); xdev->ht.id = tile->id; return xdev->useXSetTile; } /* ------ Screen update procedures ------ */ /* Initialize the update machinery. */ private void update_init(gx_device_X *xdev) { xdev->update.box.p.x = xdev->update.box.p.y = max_int_in_fixed; xdev->update.box.q.x = xdev->update.box.q.y = min_int_in_fixed; xdev->update.area = xdev->update.total = xdev->update.count = 0; } /* Flush updates to the screen if needed. */ private void update_do_flush(gx_device_X * xdev) { flush_text(xdev); if (xdev->update.count != 0) { int x = xdev->update.box.p.x, y = xdev->update.box.p.y; int w = xdev->update.box.q.x - x, h = xdev->update.box.q.y - y; fit_fill_xywh(xdev, x, y, w, h); if (w > 0 && h > 0) { if (xdev->is_buffered) { /* Copy from memory image to X server. */ const gx_device_memory *mdev = (const gx_device_memory *)xdev->target; /* * The bbox device may have set the target to NULL * temporarily. If this is the case, defer the screen * update. */ if (mdev == NULL) return; /* don't reset */ x_copy_image(xdev, mdev->line_ptrs[y], x, mdev->raster, x, y, w, h); } if (xdev->bpixmap) { /* Copy from X backing pixmap to screen. */ X_SET_FUNCTION(xdev, GXcopy); XCopyArea(xdev->dpy, xdev->bpixmap, xdev->win, xdev->gc, x, y, w, h, x, y); } } update_init(xdev); } } /* Add a region to be updated, after writing to that region. */ void x_update_add(gx_device_X * xdev, int xo, int yo, int w, int h) { int xe = xo + w, ye = yo + h; long added = (long)w * h; long old_area = xdev->update.area; gs_int_rect u; int nw, nh; long new_up_area; u.p.x = min(xo, xdev->update.box.p.x); u.p.y = min(yo, xdev->update.box.p.y); u.q.x = max(xe, xdev->update.box.q.x); u.q.y = max(ye, xdev->update.box.q.y); nw = u.q.x - u.p.x; nh = u.q.y - u.p.y; new_up_area = (long)nw * nh; xdev->update.count++; xdev->update.area = new_up_area; xdev->update.total += added; if (!xdev->AlwaysUpdate && xdev->update.count < xdev->MaxBufferedCount && xdev->update.area < xdev->MaxBufferedArea && xdev->update.total < xdev->MaxBufferedTotal ) { /* * Test whether adding this rectangle would result in too much being * copied unnecessarily. The fraction of new_up_area used in the * following test is not particularly critical; using a denominator * that is a power of 2 eliminates a divide. */ if (nw + nh >= 70 && (nw | nh) >= 16 && old_area + added < new_up_area - (new_up_area >> 2) ) DO_NOTHING; else { xdev->update.box = u; return; } } if (xdev->is_buffered && (xdev->target == NULL)) xdev->update.box = u; /* update deferred since bbox has target disabled */ else { update_do_flush(xdev); xdev->update.box.p.x = xo, xdev->update.box.p.y = yo; xdev->update.box.q.x = xe, xdev->update.box.q.y = ye; xdev->update.count = 1; xdev->update.area = xdev->update.total = added; } } /* Flush buffered text to the screen. */ private void do_flush_text(gx_device_X * xdev) { if (!IN_TEXT(xdev)) return; DRAW_TEXT(xdev); xdev->text.item_count = xdev->text.char_count = 0; } /* Bounding box device procedures (only used when buffering) */ private bool x_bbox_init_box(void *pdata) { gx_device_X *const xdev = pdata; update_init(xdev); return true; } private void x_bbox_get_box(const void *pdata, gs_fixed_rect *pbox) { const gx_device_X *const xdev = pdata; pbox->p.x = int2fixed(xdev->update.box.p.x); pbox->p.y = int2fixed(xdev->update.box.p.y); pbox->q.x = int2fixed(xdev->update.box.q.x); pbox->q.y = int2fixed(xdev->update.box.q.y); } private void x_bbox_add_rect(void *pdata, fixed x0, fixed y0, fixed x1, fixed y1) { gx_device_X *const xdev = pdata; int x = fixed2int(x0), y = fixed2int(y0); x_update_add(xdev, x, y, fixed2int_ceiling(x1) - x, fixed2int_ceiling(y1) - y); } private bool x_bbox_in_rect(const void *pdata, const gs_fixed_rect *pbox) { gs_fixed_rect box; x_bbox_get_box(pdata, &box); return rect_within(*pbox, box); } const gx_device_bbox_procs_t gdev_x_box_procs = { x_bbox_init_box, x_bbox_get_box, x_bbox_add_rect, x_bbox_in_rect }; /* ------ Internal procedures ------ */ /* * Substitute for XPutImage using XFillRectangle. This is a hack to get * around an apparent bug in some X servers. It only works with the * specific parameters (bit/byte order, padding) used above. */ private int alt_put_image(gx_device *dev, Display *dpy, Drawable win, GC gc, XImage *pi, int sx, int sy, int dx, int dy, unsigned w, unsigned h) { int raster = pi->bytes_per_line; byte *data = (byte *) pi->data + sy * raster + (sx >> 3); int init_mask = 0x80 >> (sx & 7); int invert = 0; int yi; #define NUM_RECTS 40 XRectangle rects[NUM_RECTS]; XRectangle *rp = rects; XGCValues gcv; #ifdef DEBUG if (pi->format != XYBitmap || pi->byte_order != MSBFirst || pi->bitmap_bit_order != MSBFirst || pi->depth != 1 ) { lprintf("alt_put_image: unimplemented parameter values!\n"); return_error(gs_error_rangecheck); } #endif XGetGCValues(dpy, gc, (GCFunction | GCForeground | GCBackground), &gcv); if (gcv.function == GXcopy) { XSetForeground(dpy, gc, gcv.background); XFillRectangle(dpy, win, gc, dx, dy, w, h); XSetForeground(dpy, gc, gcv.foreground); } else if (gcv.function == GXand) { /* The only cases used above are fc = ~0 or bc = ~0. */ #ifdef DEBUG if (gcv.foreground != ~(x_pixel)0 && gcv.background != ~(x_pixel)0) { lprintf("alt_put_image: unimplemented GXand case!\n"); return_error(gs_error_rangecheck); } #endif if (gcv.background != ~(x_pixel) 0) { XSetForeground(dpy, gc, gcv.background); invert = 0xff; } } else if (gcv.function == GXor) { /* The only cases used above are fc = 0 or bc = 0. */ #ifdef DEBUG if (gcv.foreground != 0 && gcv.background != 0) { lprintf("alt_put_image: unimplemented GXor case!\n"); return_error(gs_error_rangecheck); } #endif if (gcv.background != 0) { XSetForeground(dpy, gc, gcv.background); invert = 0xff; } } else { lprintf("alt_put_image: unimplemented function.\n"); return_error(gs_error_rangecheck); } for (yi = 0; yi < h; yi++, data += raster) { int mask = init_mask; byte *dp = data; int xi = 0; while (xi < w) { if ((*dp ^ invert) & mask) { int xleft = xi; if (rp == &rects[NUM_RECTS]) { XFillRectangles(dpy, win, gc, rects, NUM_RECTS); rp = rects; } /* Scan over a run of 1-bits */ rp->x = dx + xi, rp->y = dy + yi; do { if (!(mask >>= 1)) mask = 0x80, dp++; xi++; } while (xi < w && ((*dp ^ invert) & mask)); rp->width = xi - xleft, rp->height = 1; rp++; } else { if (!(mask >>= 1)) mask = 0x80, dp++; xi++; } } } XFillRectangles(dpy, win, gc, rects, rp - rects); if (invert) XSetForeground(dpy, gc, gcv.foreground); return 0; #undef NUM_RECTS }