/* Copyright (C) 1989, 2000 Aladdin Enterprises.  All rights reserved.
  
  This file is part of AFPL Ghostscript.
  
  AFPL Ghostscript is distributed with NO WARRANTY OF ANY KIND.  No author or
  distributor accepts any responsibility for the consequences of using it, or
  for whether it serves any particular purpose or works at all, unless he or
  she says so in writing.  Refer to the Aladdin Free Public License (the
  "License") for full details.
  
  Every copy of AFPL Ghostscript must include a copy of the License, normally
  in a plain ASCII text file named PUBLIC.  The License grants you the right
  to copy, modify and redistribute AFPL Ghostscript, but only under certain
  conditions described in the License.  Among other things, the License
  requires that the copyright notice and this notice be preserved on all
  copies.
*/

/*$Id: gdevx.c,v 1.8.2.1 2002/01/30 20:09:26 raph 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 = 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
}
