/* Copyright (C) 1989, 1995, 1996, 1997, 1998, 1999 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: gdevpcfb.c,v 1.2.6.1.2.1 2003/01/17 00:49:01 giles Exp $ */
/* IBM PC frame buffer (EGA/VGA) drivers */
#include "memory_.h"
#include "gconfigv.h"		/* for USE_ASM */
#include "gx.h"
#include "gserrors.h"
#include "gsparam.h"
#include "gxdevice.h"
#include "gdevpccm.h"
#include "gdevpcfb.h"
 
/* We may compile this in a non-segmented environment.... */
#ifndef _ss
#define _ss
#endif

/* Macro for casting gx_device argument */
#define fb_dev ((gx_device_ega *)dev)

/* Procedure record */
private dev_proc_map_rgb_color(ega0_map_rgb_color);
private dev_proc_map_rgb_color(ega1_map_rgb_color);
#define ega2_map_rgb_color pc_4bit_map_rgb_color
private dev_proc_map_color_rgb(ega01_map_color_rgb);
#define ega2_map_color_rgb pc_4bit_map_color_rgb
#if ega_bits_of_color == 0
#   define ega_map_rgb_color ega0_map_rgb_color
#   define ega_map_color_rgb ega01_map_color_rgb
#else
# if ega_bits_of_color == 1
#   define ega_map_rgb_color ega1_map_rgb_color
#   define ega_map_color_rgb ega01_map_color_rgb
# else
#   define ega_map_rgb_color ega2_map_rgb_color
#   define ega_map_color_rgb ega2_map_color_rgb
# endif
#endif
#define ega_std_procs(get_params, put_params)\
	ega_open,\
	NULL,			/* get_initial_matrix */\
	NULL,			/* sync_output */\
	NULL,			/* output_page */\
	ega_close,\
	ega_map_rgb_color,\
	ega_map_color_rgb,\
	ega_fill_rectangle,\
	ega_tile_rectangle,\
	ega_copy_mono,\
	ega_copy_color,\
	NULL,			/* draw_line */\
	ega_get_bits,\
	get_params,\
	put_params,\
	NULL,			/* map_cmyk_color */\
	NULL,			/* get_xfont_procs */\
	NULL,			/* get_xfont_device */\
	NULL,			/* map_rgb_alpha_color */\
	gx_page_device_get_page_device

private const gx_device_procs ega_procs =
{
    ega_std_procs(NULL, NULL)
};

private dev_proc_get_params(svga16_get_params);
private dev_proc_put_params(svga16_put_params);
private const gx_device_procs svga16_procs =
{
    ega_std_procs(svga16_get_params, svga16_put_params)
};

/* All the known instances */
		/* EGA */
gx_device_ega far_data gs_ega_device =
ega_device("ega", ega_procs, 80, 350, 48.0 / 35.0, 0x10);

		/* VGA */
gx_device_ega far_data gs_vga_device =
ega_device("vga", ega_procs, 80, 480, 1.0, 0x12);

		/* Generic SuperVGA, 800x600, 16-color mode */
gx_device_ega far_data gs_svga16_device =
ega_device("svga16", svga16_procs, 100, 600, 1.0, 0x29 /*Tseng */ );

/* Save the BIOS state */
private pcfb_bios_state pcfb_save_state =
{-1};

/* Initialize the EGA for graphics mode */
int
ega_open(gx_device * dev)
{				/* Adjust the device resolution. */
    /* This is a hack, pending refactoring of the put_params machinery. */
    switch (fb_dev->video_mode) {
	case 0x10:		/* EGA */
	    gx_device_adjust_resolution(dev, 640, 350, 1);
	    break;
	case 0x12:		/* VGA */
	    gx_device_adjust_resolution(dev, 640, 480, 1);
	    break;
	default:		/* 800x600 SuperVGA */
	    gx_device_adjust_resolution(dev, 800, 600, 1);
	    break;
    }
    if (pcfb_save_state.display_mode < 0)
	pcfb_get_state(&pcfb_save_state);
    /* Do implementation-specific initialization */
    pcfb_set_signals(dev);
    pcfb_set_mode(fb_dev->video_mode);
    set_s_map(-1);		/* enable all maps */
    return 0;
}

/* Reinitialize the EGA for text mode */
int
ega_close(gx_device * dev)
{
    if (pcfb_save_state.display_mode >= 0)
	pcfb_set_state(&pcfb_save_state);
    return 0;
}

/* Get/put the display mode parameter. */
private int
svga16_get_params(gx_device * dev, gs_param_list * plist)
{
    int code = gx_default_get_params(dev, plist);

    if (code < 0)
	return code;
    return param_write_int(plist, "DisplayMode", &fb_dev->video_mode);
}
private int
svga16_put_params(gx_device * dev, gs_param_list * plist)
{
    int ecode = 0;
    int code;
    int imode = fb_dev->video_mode;
    const char *param_name;

    switch (code = param_read_int(plist, (param_name = "DisplayMode"), &imode)) {
	default:
	    ecode = code;
	    param_signal_error(plist, param_name, ecode);
	case 0:
	case 1:
	    break;
    }

    if (ecode < 0)
	return ecode;
    code = gx_default_put_params(dev, plist);
    if (code < 0)
	return code;

    if (imode != fb_dev->video_mode) {
	if (dev->is_open)
	    gs_closedevice(dev);
	fb_dev->video_mode = imode;
    }
    return 0;
}

/* Map a r-g-b color to an EGA color code. */
#define Nb gx_color_value_bits
private gx_color_index
ega0_map_rgb_color(gx_device * dev, gx_color_value r, gx_color_value g,
		   gx_color_value b)
{
    return pc_4bit_map_rgb_color(dev, r, r, r);
}
private gx_color_index
ega1_map_rgb_color(gx_device * dev, gx_color_value r, gx_color_value g,
		   gx_color_value b)
{
#define cvtop (gx_color_value)(1 << (Nb - 1))
    return pc_4bit_map_rgb_color(dev, r & cvtop, g & cvtop, b & cvtop);
}
#undef Nb

/* Map a color code to r-g-b. */
#define icolor (int)color
private int
ega01_map_color_rgb(gx_device * dev, gx_color_index color,
		    gx_color_value prgb[3])
{
#define one (gx_max_color_value / 2 + 1)
    prgb[0] = (icolor & 4 ? one : 0);
    prgb[1] = (icolor & 2 ? one : 0);
    prgb[2] = (icolor & 1 ? one : 0);
    return 0;
#undef one
}
#undef icolor

/* ------ Internal routines ------ */

/* Structure for operation parameters. */
/* Note that this structure is known to assembly code. */
/* Not all parameters are used for every operation. */
typedef struct rop_params_s {
    fb_ptr dest;		/* pointer to frame buffer */
    int draster;		/* raster of frame buffer */
    const byte *src;		/* pointer to source data */
    int sraster;		/* source raster */
    int width;			/* width in bytes */
    int height;			/* height in scan lines */
    int shift;			/* amount to right shift source */
    int invert;			/* 0 or -1 to invert source */
    int data;			/* data for fill */
} rop_params;
typedef rop_params _ss *rop_ptr;

/* Assembly language routines */

#if USE_ASM
void memsetcol(P1(rop_ptr));	/* dest, draster, height, data */
#else
#define memsetcol cmemsetcol
private void
cmemsetcol(rop_ptr rop)
{
    byte *addr = rop->dest;
    int yc = rop->height;
    byte data = rop->data;
    int draster = rop->draster;

    while (yc--) {
	byte_discard(*addr);
	*addr = data;
	addr += draster;
    }
}
#endif

#if USE_ASM
void memsetrect(P1(rop_ptr));	/* dest, draster, width, height, data */
#else
#define memsetrect cmemsetrect
private void
cmemsetrect(rop_ptr rop)
{
    int yc = rop->height;
    int width = rop->width;

    if (yc <= 0 || width <= 0)
	return;
    {
	byte *addr = rop->dest;
	byte data = rop->data;

	if (width > 5) {	/* use memset */
	    int skip = rop->draster;

	    do {
		memset(addr, data, width);
		addr += skip;
	    }
	    while (--yc);
	} else {		/* avoid the fixed overhead */
	    int skip = rop->draster - width;

	    do {
		int cnt = width;

		do {
		    *addr++ = data;
		} while (--cnt);
		addr += skip;
	    }
	    while (--yc);
	}
    }
}
#endif

#if USE_ASM
void memrwcol(P1(rop_ptr));	/* dest, draster, src, sraster, height, shift, invert */
#  define memrwcol0(rop) memrwcol(rop)	/* same except shift = 0 */
#else
#  define memrwcol cmemrwcol
#  define memrwcol0 cmemrwcol0
private void
cmemrwcol(rop_ptr rop)
{
    byte *dp = rop->dest;
    const byte *sp = rop->src;
    int yc = rop->height;
    int shift = rop->shift;
    byte invert = rop->invert;
    int sraster = rop->sraster, draster = rop->draster;

    while (yc--) {
	byte_discard(*dp);
	*dp = ((*sp >> shift) + (*sp << (8 - shift))) ^ invert;
	dp += draster, sp += sraster;
    }
}
private void
cmemrwcol0(rop_ptr rop)
{
    byte *dp = rop->dest;
    const byte *sp = rop->src;
    int yc = rop->height;
    byte invert = rop->invert;
    int sraster = rop->sraster, draster = rop->draster;

    if (yc > 0)
	do {
	    byte_discard(*dp);
	    *dp = *sp ^ invert;
	    dp += draster, sp += sraster;
	}
	while (--yc);
}
#endif

#if USE_ASM
void memrwcol2(P1(rop_ptr));	/* dest, draster, src, sraster, height, shift, invert */
#else
#define memrwcol2 cmemrwcol2
private void
cmemrwcol2(rop_ptr rop)
{
    byte *dp = rop->dest;
    const byte *sp = rop->src;
    int yc = rop->height;
    int shift = rop->shift;
    byte invert = rop->invert;
    int sraster = rop->sraster, draster = rop->draster;

    while (yc--) {
	byte_discard(*dp);
	*dp = ((sp[1] >> shift) + (*sp << (8 - shift))) ^ invert;
	dp += draster, sp += sraster;
    }
}
#endif

/* Forward definitions */
int ega_write_dot(P4(gx_device *, int, int, gx_color_index));
private void fill_rectangle(P4(rop_ptr, int, int, int));
private void fill_row_only(P4(byte *, int, int, int));

/* Clean up after writing */
#define dot_end()\
  set_g_mask(0xff)		/* all bits on */

/* Write a dot using the EGA color codes. */
/* This doesn't have to be efficient. */
int
ega_write_dot(gx_device * dev, int x, int y, gx_color_index color)
{
    byte data[4];

    data[0] = (byte) color;
    return ega_copy_color(dev, data, 1, 4, gx_no_bitmap_id, x, y, 1, 1);
}

/* Macro for testing bit-inclusion */
#define bit_included_in(x,y) !((x)&~(y))

/* Copy a monochrome bitmap.  The colors are given explicitly. */
/* Color = gx_no_color_index means transparent (no effect on the image). */
int
ega_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 izero, gx_color_index ione)
{
    rop_params params;

#define czero (int)izero
#define cone (int)ione
    int dleft, count;
    byte mask, rmask;
    fb_ptr save_dest;
    int other_color = -1;

    fit_copy(dev, base, sourcex, raster, id, x, y, w, h);
    params.dest = mk_fb_ptr(x, y);
    params.draster = fb_dev->raster;
    params.src = base + (sourcex >> 3);
    params.sraster = raster;
    params.height = h;
    params.shift = (x - sourcex) & 7;
    /* Analyze the 16 possible cases: each of izero and ione may be */
    /* 0, 0xf, transparent, or some other color. */
    switch (czero) {
	case no_color:
	    switch (cone) {
		default:	/* (T, other) */
		    /* Must do 2 passes */
		    other_color = cone;
		    save_dest = params.dest;
		    /* falls through */
		case 0:	/* (T, 0) */
		    set_g_function(gf_AND);
		    params.invert = -1;
		    break;
		case 0xf:	/* (T, 0xf) */
		    set_g_function(gf_OR);
		    params.invert = 0;
		    break;
		case no_color:	/* (T, T) */
		    return 0;	/* nothing to do */
	    }
	    break;
	case 0:
	    params.invert = 0;
	    switch (cone) {
		default:	/* (0, other) */
		    set_g_const(0);
		    set_g_const_map(cone ^ 0xf);
		    /* falls through */
		case 0xf:	/* (0, 0xf) */
		    break;
		case no_color:	/* (0, T) */
		    set_g_function(gf_AND);
		    break;
	    }
	    break;
	case 0xf:
	    params.invert = -1;
	    switch (cone) {
		case 0:	/* (0xf, 0) */
		    break;
		default:	/* (0xf, other) */
		    set_g_const(0xf);
		    set_g_const_map(cone);
		    break;
		case no_color:	/* (0xf, T) */
		    set_g_function(gf_OR);
		    /* falls through */
	    }
	    break;
	default:
	    switch (cone) {
		default:	/* (other, not T) */
		    if (bit_included_in(czero, cone)) {
			set_g_const(czero);
			set_g_const_map(czero ^ cone ^ 0xf);
			params.invert = 0;
			break;
		    } else if (bit_included_in(cone, czero)) {
			set_g_const(cone);
			set_g_const_map(cone ^ czero ^ 0xf);
			params.invert = -1;
			break;
		    }
		    /* No way around it, fill with one color first. */
		    save_dest = params.dest;
		    fill_rectangle((rop_ptr) & params, x & 7, w, cone);
		    params.dest = save_dest;
		    set_g_function(gf_XOR);
		    set_s_map(czero ^ cone);
		    other_color = -2;	/* must reset s_map at end */
		    params.invert = -1;
		    break;
		case no_color:	/* (other, T) */
		    /* Must do 2 passes */
		    other_color = czero;
		    save_dest = params.dest;
		    set_g_function(gf_AND);
		    params.invert = 0;
		    break;
	    }
	    break;
    }
    /* Actually copy the bits. */
    dleft = 8 - (x & 7);
    mask = 0xff >> (8 - dleft);
    count = w - dleft;
    if (count < 0)
	mask -= mask >> w,
	    rmask = 0;
    else
	rmask = 0xff00 >> (count & 7);
    /* params: dest, src, sraster, height, shift, invert */
    /* Smashes params.src, params.dest, count. */
  copy:set_g_mask(mask);
    if (params.shift == 0) {	/* optimize the aligned case *//* Do left column */
	memrwcol0((rop_ptr) & params);
	/* Do center */
	if ((count -= 8) >= 0) {
	    out_g_mask(0xff);
	    do {
		params.src++, params.dest++;
		memrwcol0((rop_ptr) & params);
	    }
	    while ((count -= 8) >= 0);
	}
	/* Do right column */
	if (rmask) {
	    params.src++, params.dest++;
	    out_g_mask(rmask);
	    memrwcol0((rop_ptr) & params);
	}
    } else {			/* Do left column */
	int sleft = 8 - (sourcex & 7);

	if (sleft >= dleft) {	/* Source fits in one byte */
	    memrwcol((rop_ptr) & params);
	} else if (w <= sleft) {	/* Source fits in one byte, thin case */
	    memrwcol((rop_ptr) & params);
	    goto fin;
	} else {
	    memrwcol2((rop_ptr) & params);
	    params.src++;
	}
	/* Do center */
	if ((count -= 8) >= 0) {
	    out_g_mask(0xff);
	    do {
		params.dest++;
		memrwcol2((rop_ptr) & params);
		params.src++;
	    }
	    while ((count -= 8) >= 0);
	}
	/* Do right column */
	if (rmask) {
	    out_g_mask(rmask);
	    params.dest++;
	    if (count + 8 <= params.shift)
		memrwcol((rop_ptr) & params);
	    else
		memrwcol2((rop_ptr) & params);
	}
    }
  fin:if (other_color != -1) {
	if (other_color >= 0) {	/* Do the second pass on (T, other) or (other, T). */
	    count = w - dleft;
	    params.src = base + (sourcex >> 3);
	    params.dest = save_dest;
	    params.invert ^= -1;
	    set_s_map(other_color);
	    set_g_function(gf_OR);
	    other_color = -2;
	    goto copy;
	} else {		/* Finished second pass, restore s_map */
	    set_s_map(-1);
	}
    }
    set_g_function(gf_WRITE);
    set_g_const_map(0);
    dot_end();
    return 0;
#undef czero
#undef cone
}

/* Copy a color pixelmap.  This is just like a bitmap, */
/* except that each pixel takes 4 bits instead of 1. */
int
ega_copy_color(gx_device * dev,
	       const byte * base, int sourcex, int raster, gx_bitmap_id id,
	       int x, int y, int w, int h)
{
    const byte *line = base + (sourcex >> 1);
    unsigned mask = 0x80 >> (x & 7);
    int px = sourcex & 1;
    fb_ptr fb_line;
    int fb_raster = fb_dev->raster;

    fit_copy(dev, base, sourcex, raster, id, x, y, w, h);
    fb_line = mk_fb_ptr(x, y);
    set_g_mode(gm_FILL);
    select_g_mask();
    for (;; px++) {
	const byte *bptr = line;
	fb_ptr fbptr = fb_line;
	int py = h;

	out_g_mask(mask);
	if (px & 1) {
	    do {
		byte_discard(*fbptr);	/* latch frame buffer data */
		*fbptr = *bptr;
		bptr += raster;
		fbptr += fb_raster;
	    }
	    while (--py);
	    line++;
	} else {
	    do {
		byte_discard(*fbptr);	/* latch frame buffer data */
		*fbptr = *bptr >> 4;
		bptr += raster;
		fbptr += fb_raster;
	    }
	    while (--py);
	}
	if (!--w)
	    break;
	if ((mask >>= 1) == 0)
	    mask = 0x80, fb_line++;
    }
    set_g_mode(gm_DATA);
    dot_end();
    return 0;
}

/* Fill a rectangle. */
int
ega_fill_rectangle(gx_device * dev, int x, int y, int w, int h,
		   gx_color_index color)
{
    rop_params params;

    fit_fill(dev, x, y, w, h);
    params.dest = mk_fb_ptr(x, y);
    if (h == 1)
	fill_row_only(params.dest, x & 7, w, (int)color);
    else {
	params.draster = fb_dev->raster;
	params.height = h;
	fill_rectangle((rop_ptr) & params, x & 7, w, (int)color);
	dot_end();
    }
    return 0;
}

/* Tile a rectangle.  Note that the two colors must both be supplied, */
/* i.e. neither one can be gx_no_color_index (transparent): */
/* a transparent color means that the tile is colored, not a mask. */
int
ega_tile_rectangle(gx_device * dev, const gx_tile_bitmap * tile,
      int x, int y, int w, int h, gx_color_index czero, gx_color_index cone,
		   int px, int py)
#define zero (int)czero
#define one (int)cone
{
    rop_params params;
    int xmod, width_bytes;
    int tile_height = tile->size.y;
    int xbit;
    int lcount;
    int mask, rmask;
    byte narrow;
    byte again;
    int const_bits, maps;
    int ymod, yleft;

    fit_fill(dev, x, y, w, h);
    /* We only handle the easiest cases directly. */
    if ((tile->size.x & 7) || one == -1 || zero == -1 || px || py)
	return gx_default_tile_rectangle(dev, tile, x, y, w, h,
					 czero, cone, px, py);
    /* Following is similar to aligned case of copy_mono */
    params.dest = mk_fb_ptr(x, y);
    params.draster = fb_dev->raster;
    params.sraster = tile->raster;
    params.shift = 0;
    xbit = x & 7;
    /* Set up the graphics registers */
    const_bits = (zero ^ one) ^ 0xf;
    if (const_bits) {
	set_g_const(zero);	/* either color will do */
	set_g_const_map(const_bits);
    }
    if ((maps = zero & ~one) != 0) {
	set_s_map(maps += const_bits);
	params.invert = -1;
	again = one & ~zero;
    } else {
	maps = one & ~zero;
	set_s_map(maps += const_bits);
	params.invert = 0;
	again = 0;
    }
    xmod = (x % tile->size.x) >> 3;
    width_bytes = tile->size.x >> 3;
    mask = 0xff >> xbit;
    if (w + xbit <= 8)
	mask -= mask >> w,
	    rmask = 0,
	    narrow = 1;
    else {
	rmask = (0xff00 >> ((w + x) & 7)) & 0xff;
	if (xbit)
	    w += xbit - 8;
	else
	    mask = 0, --xmod, --params.dest;
	narrow = 0;
    }
    ymod = y % tile_height;
  tile:yleft = tile_height - ymod;
    params.src = tile->data + ymod * params.sraster + xmod;
    lcount = h;
    if (narrow) {		/* Optimize narrow case */
	set_g_mask(mask);
	if (lcount > yleft) {
	    params.height = yleft;
	    memrwcol0((rop_ptr) & params);
	    params.dest += yleft * params.draster;
	    params.src = tile->data + xmod;
	    params.height = tile_height;
	    lcount -= yleft;
	    while (lcount >= tile_height) {
		memrwcol0((rop_ptr) & params);
		params.dest += tile_height * params.draster;
		lcount -= tile_height;
	    }
	}
	if (lcount) {
	    params.height = lcount;
	    memrwcol0((rop_ptr) & params);
	}
    } else {
	fb_ptr line = params.dest;
	int xpos = width_bytes - xmod;

	while (1) {
	    int xleft = xpos;
	    int count = w;

	    params.height = (lcount > yleft ? yleft : lcount);
	    /* Do first byte, if not a full byte. */
	    if (mask) {
		set_g_mask(mask);
		memrwcol0((rop_ptr) & params);
	    }
	    /* Do full bytes */
	    if ((count -= 8) >= 0) {
		set_g_mask(0xff);
		do {
		    if (!--xleft)
			xleft = width_bytes,
			    params.src -= width_bytes;
		    ++params.src, ++params.dest;
		    memrwcol0((rop_ptr) & params);
		}
		while ((count -= 8) >= 0);
	    }
	    /* Do last byte */
	    if (rmask) {
		if (!--xleft)
		    xleft = width_bytes,
			params.src -= width_bytes;
		set_g_mask(rmask);
		++params.src, ++params.dest;
		memrwcol0((rop_ptr) & params);
	    }
	    if ((lcount -= params.height) == 0)
		break;
	    params.dest = line += params.height * params.draster;
	    params.src = tile->data + xmod;
	    yleft = tile_height;
	}
    }
    /* Now do the second color if needed */
    if (again) {
	maps = again + const_bits;
	set_s_map(maps);
	again = 0;
	params.dest = mk_fb_ptr(x, y);
	if (mask == 0)
	    params.dest--;
	params.invert = 0;
	goto tile;
    }
    if (maps != 0xf)
	set_s_map(-1);
    if (const_bits)
	set_g_const_map(0);
    dot_end();
    return 0;
}

/* Read scan lines back from the frame buffer. */
int
ega_get_bits(gx_device * dev, int y, byte * data, byte ** actual_data)
{				/* The maximum width for an EGA/VGA device is 800 pixels.... */
    int width_bytes = (dev->width + 7) >> 3;
    int i;
    bits32 *dest;
    const byte *src;
    const byte *end;
    byte planes[100 * 4];

    /* Plane 0 is the least significant plane. */
    /* We know we're on a little-endian machine.... */
#define spread4(v)\
 v+0x00000000, v+0x08000000, v+0x80000000, v+0x88000000,\
 v+0x00080000, v+0x08080000, v+0x80080000, v+0x88080000,\
 v+0x00800000, v+0x08800000, v+0x80800000, v+0x88800000,\
 v+0x00880000, v+0x08880000, v+0x80880000, v+0x88880000
    static const bits32 spread8[256] =
    {spread4(0x0000), spread4(0x0800),
     spread4(0x8000), spread4(0x8800),
     spread4(0x0008), spread4(0x0808),
     spread4(0x8008), spread4(0x8808),
     spread4(0x0080), spread4(0x0880),
     spread4(0x8080), spread4(0x8880),
     spread4(0x0088), spread4(0x0888),
     spread4(0x8088), spread4(0x8888)
    };

    if (y < 0 || y >= dev->height || dev->width > 800)
	return_error(gs_error_rangecheck);
    /* Read 4 planes into the holding buffer. */
    for (i = 0; i < 4; ++i) {
	set_g_read_plane(i);
	memcpy(planes + 100 * i, mk_fb_ptr(0, y), width_bytes);
    }
    /* Now assemble the final data from the planes. */
    for (dest = (bits32 *) data, src = planes, end = src + width_bytes;
	 src < end; ++dest, ++src
	)
	*dest = (((((spread8[src[0]] >> 1) | spread8[src[100]]) >> 1) |
		  spread8[src[200]]) >> 1) | spread8[src[300]];
    if (actual_data != 0)
	*actual_data = data;
    return 0;
}

/* ------ Internal routines ------ */

/* Mask table for rectangle fill. */
static const byte rmask_tab[9] =
{0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff
};

/* Fill a rectangle specified by pointer into frame buffer, */
/* starting bit within byte, width, and height. */
/* Smashes rop->dest. */
private void
fill_rectangle(register rop_ptr rop, int bit, int w, int color)
  /* rop: dest, draster, height */
{
    set_g_const(color);
    set_g_const_map(0xf);
    select_g_mask();
    if (bit + w <= 8) {		/* Less than one byte */
	out_g_mask(rmask_tab[w] >> bit);
	memsetcol(rop);
    } else {
	byte right_mask;

	if (bit) {
	    out_g_mask(0xff >> bit);
	    memsetcol(rop);
	    rop->dest++;
	    w += bit - 8;
	}
	if (w >= 8) {
	    out_g_mask(0xff);	/* all bits */
	    rop->width = w >> 3;
	    memsetrect(rop);
	    rop->dest += rop->width;
	    w &= 7;
	}
	if ((right_mask = rmask_tab[w]) != 0) {
	    out_g_mask(right_mask);
	    memsetcol(rop);
	}
    }
    set_g_const_map(0);
}

/* Fill a single row specified by pointer into frame buffer, */
/* starting bit within byte, and width; clean up afterwards. */
#define r_m_w(ptr) (*(ptr))++	/* read & write, data irrelevant */
private void
fill_row_only(byte * dest, int bit, int w, int color)
  /* rop: dest */
{
    if (bit + w <= 8) {		/* Less than one byte. */
	/* Optimize filling with black or white. */
	switch (color) {
	    case 0:
		set_g_mask(rmask_tab[w] >> bit);
		*dest &= color;	/* read, then write 0s; */
		/* some compilers optimize &= 0 to a store. */
		out_g_mask(0xff);	/* dot_end */
		break;
	    case 0xf:
		set_g_mask(rmask_tab[w] >> bit);
		*dest |= 0xff;	/* read, then write 1s; */
		/* some compilers optimize &= 0 to a store. */
		out_g_mask(0xff);	/* dot_end */
		break;
	    default:
		set_g_const(color);
		set_g_const_map(0xf);
		set_g_mask(rmask_tab[w] >> bit);
		r_m_w(dest);
		out_g_mask(0xff);	/* dot_end */
		set_g_const_map(0);
	}
    } else {
	byte right_mask;
	int byte_count;

	set_g_const(color);
	set_g_const_map(0xf);
	select_g_mask();
	if (bit) {
	    out_g_mask(0xff >> bit);
	    r_m_w(dest);
	    dest++;
	    w += bit - 8;
	}
	byte_count = w >> 3;
	if ((right_mask = rmask_tab[w & 7]) != 0) {
	    out_g_mask(right_mask);
	    r_m_w(dest + byte_count);
	}
	out_g_mask(0xff);
	if (byte_count) {
	    memset(dest, 0, byte_count);	/* data irrelevant */
	}
	set_g_const_map(0);
    }
}
