/* Copyright (C) 1992, 1995 Aladdin Enterprises.  All rights reserved.

  This file is part of Aladdin Ghostscript.

  Aladdin 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 Ghostscript Free Public
  License (the "License") for full details.

  Every copy of Aladdin 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 Aladdin 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.
*/

/* gdevamiga.c */
/* Amiga driver for Ghostscript library, requires Kickstart 2.04 or higher */

/* Original development and maintenance up to and including gs3.5.3:
 *
 *	Olaf 'Olsen' Barthel
 *
 *	Address:	Olaf Barthel
 *				Brabeckstrasse 35
 *				D-30559 Hannover
 *	Internet:	olsen@sourcery.han.de
 *
 *
 * Current maintenance, libnix integration and 'autoconfiscation':
 *
 *	Joop van de Wege
 *
 *	Internet:	JoopvandeWege@mail.mococo.nl
 *
 *
 * Revised device/size/resolution handling:
 *
 *	Steffen Opel
 *
 *	Internet:   opel@rumms.uni-mannheim.de
 */

#include <clib/macros.h>
#include <datatypes/pictureclass.h>
#include <dos/dostags.h>
#include <dos/rdargs.h>
#include <dos/var.h>
#include <dos/dos.h>
#include <devices/printer.h>
#include <devices/prtbase.h>
#include <devices/prtgfx.h>
#include <exec/errors.h>
#include <exec/memory.h>
#include <graphics/displayinfo.h>
#include <graphics/gfxbase.h>
#include <intuition/intuitionbase.h>
#include <intuition/gadgetclass.h>
#include <intuition/imageclass.h>
#include <intuition/icclass.h>
#include <libraries/asl.h>
#include <libraries/iffparse.h>

#include <proto/asl.h>
#include <proto/dos.h>
#include <proto/exec.h>
#define BltBitMap BlitBitMap	/* Hide weird compiler error! */
#include <proto/graphics.h>
#undef BltBitMap
#include <proto/iffparse.h>
#include <proto/intuition.h>
#include <proto/layers.h>
#include <proto/utility.h>

#include "gscdefs.h"
#include "gserrors.h"
#include "gx.h"		/* for gx_bitmap; includes std.h */
#include "gxdevice.h"
#include "gxfixed.h"	/* needed for amiga_put_params only */
#include "gsparam.h"

#include <string.h>
#include <signal.h>

	/* Magic value to compute the resolution from the display database.
	 *
	 * To compute the dots_per_inch resolution from the ticks_per_pixel 
	 * resolution in the display database the common base is needed. 
	 * These base ensures that all Display Modes will be displayed with 
	 * the same absolute aspect ratio. Don't ask me how the ticks_per_pixel 
	 * entries are computed exactly, the supplied value has been determined 
	 * with respect to GfxBase->NormalDisplay[Rows|Columns], some monitors
	 * and a meter. Therefore it may be fine tuned to fit any specific 
	 * monitor/gfx-board environment.
	 *
	 * TODO: Resolve the constitutional calculation for those entries
	 * in the display database!
	 */

#define MAGIC_DPI_BASE_VALUE	1447

	/* Initial values for resolution and size. */

#define INITIAL_X_DPI		72
#define INITIAL_Y_DPI		72

#ifdef A4
/* A4 paper (210mm x 297mm).  The dimensions are off by a few nm.... */
#define INITIAL_WIDTH		(int)(8.27 * INITIAL_X_DPI)
#define INITIAL_HEIGHT		(int)(11.7 * INITIAL_Y_DPI)
#else
/* U.S. letter paper (8.5" x 11"). */
#define INITIAL_WIDTH		(int)(8.5 * INITIAL_X_DPI)
#define INITIAL_HEIGHT		(int)(11.0 * INITIAL_Y_DPI)
#endif

	/* Default Display Mode. */

#define DEFAULT_DISPLAYMODE	""

	/* Default output file name. */

#define DEFAULT_FILENAME	"gs_page"

	/* Turn a byte into a 24 bit colour value. */

#define SPREAD(i)	((ULONG)(i) << 24 | (ULONG)(i) << 16 | (ULONG)(i) << 8 | (i))

	/* Scroller gadget IDs. */

enum	{	VERTICAL_SCROLLER,	HORIZONTAL_SCROLLER,
		UP_ARROW,		DOWN_ARROW,
		LEFT_ARROW,		RIGHT_ARROW,

		GADGET_COUNT
	};

	/* Scroller arrow IDs. */

enum	{	UP_IMAGE,		DOWN_IMAGE,
		LEFT_IMAGE,		RIGHT_IMAGE,

		IMAGE_COUNT
	};

	/* Codes for the MoveAround() routine. */

enum	{	MOVE_MIN,MOVE_FAR_DOWN,MOVE_DOWN,MOVE_UP,MOVE_FAR_UP,MOVE_MAX };

	/* Some handy bit masks. */

#define SIG_KILL	SIGBREAKF_CTRL_C
#define SIG_HANDSHAKE	SIGF_SINGLE
#define SIG_WAKEUP	SIGBREAKF_CTRL_E

	/* Static dimensions of scroller arrows. */

#define ARROW_WIDTH	16
#define ARROW_HEIGHT	11

	/* The `Help' key raw code. */

#define HELP_CODE	95

	/* Minimum window inner area dimension. */

#define MINIMUM_WIDTH	64
#define MINIMUM_HEIGHT	32

	/* Handy superbitmap window macros. */

#define LAYERXOFFSET(w)	((w) -> RPort -> Layer -> Scroll_X)
#define LAYERYOFFSET(w)	((w) -> RPort -> Layer -> Scroll_Y)

	/* User input to listen to. */

#define IDCMP_FLAGS	(IDCMP_IDCMPUPDATE | IDCMP_GADGETUP | IDCMP_GADGETDOWN | IDCMP_MOUSEMOVE | IDCMP_NEWSIZE | IDCMP_CLOSEWINDOW | IDCMP_VANILLAKEY | IDCMP_RAWKEY)

	/* Chunk IDs. */

#define ID_ANNO		MAKE_ID('A','N','N','O')
#define ID_DPI		MAKE_ID('D','P','I',' ')

typedef struct
{
	UWORD		dpi_x;
	UWORD		dpi_y;
} DPIHeader;

	/* Packer modes. */

#define DUMP		0
#define RUN		1

	/* Minimum data run size, maximum data run size and maximum cache size. */

#define MINRUN		3
#define MAXRUN		128
#define MAXDAT		128

	/* This module actually implements four different Amiga based
	 * devices. As the rendering operations are all the same,
	 * one single device definition is sufficient.
	 */

typedef struct gx_device_amiga
{
	gx_device_common;

	struct Screen	*screen;	/* Any screen */
	struct Window	*window;	/* Some window to be opened on the Workbench screen */

	struct BitMap	*super_bitmap;	/* Window superbitmap area */
	struct Gadget	**gadget;	/* Scroller gadgets */
	struct Image	**image;	/* Scroller arrow images */

	struct Task	*dispatcher;	/* Slider dispatch task */
	struct Process	*main;		/* Main program */

	struct RastPort	*rport;		/* Rendering area */

	struct IODRPReq *printer;	/* Printer interface data */
	struct MsgPort	*port;		/* Printer io data */

	struct ColorMap	*colormap;	/* A black/white colour map */
	struct BitMap	*bitmap;	/* Rendering bitmap data */
	PLANEPTR	 bitplane;	/* Rendering raster */

	gx_color_index	 last_pen;	/* The last colour set */

	char		display_mode[DISPLAYNAMELEN + 1];	/* The Display Mode */
	char		file_name[256];/* The output file name */
	int		 page_count;	/* The page number counter */

	int		 cube_size;	/* Colour cube size, 0 for b/w */
	struct RastPort	*temp_rport;	/* Temporary raster port for pixmap imaging. */
	UBYTE		*temp_array;	/* Temporary colour manipulation array. */
	LONG		*pens;
} gx_device_amiga;

	/* Cheap, but effective macro for casting the device argument ;-) */

#define xdev ((gx_device_amiga *)dev)

	/* Support functions */

private int amiga_open(gx_device *dev, ULONG Mode);
private VOID set_mono_device(gx_device_amiga *dev);
private VOID set_colour_device(gx_device_amiga *dev, int cube_size, 
	LONG *pens);
private VOID set_colour_printer_device(gx_device_amiga *dev, LONG CubeSize);
private LONG * AllocatePens(struct ViewPort *VPort, LONG CubeSize);
private VOID DeleteBitMap(struct BitMap *BitMap, BOOL Private);
private struct BitMap * CreateBitMap(LONG Width, LONG Height, LONG Depth, 
	ULONG Flags, struct BitMap *Friend, BOOL Private);
private VOID DeleteTempRPort(struct RastPort *Temp);
private struct RastPort * CreateTempRPort(struct RastPort *Source);
private LONG Euclid(LONG a, LONG b);
private BYTE * PutDump(register BYTE *Destination, register LONG Count);
private BYTE * PutRun(register BYTE *Destination, LONG Count, WORD Char);
private LONG PackRow(PLANEPTR *SourcePtr, register BYTE *Destination, 
	LONG RowSize);
private BOOL PutBODY(struct IFFHandle *Handle, struct BitMap *BitMap);
private BOOL PutANNO(struct IFFHandle *Handle);
private BOOL PutCAMG(struct IFFHandle *Handle);
private BOOL PutCMAP(struct IFFHandle *Handle);
private BOOL PutDPI(struct IFFHandle *Handle, UWORD X_DPI, UWORD Y_DPI);
private BOOL PutBMHD(struct IFFHandle *Handle, LONG Width, LONG Height, 
	UWORD X_DPI, UWORD Y_DPI);
private BOOL SaveBitMap(STRPTR Name, struct BitMap *BitMap, LONG Width, 
	LONG Height, UWORD X_DPI, UWORD Y_DPI);
private VOID DispatchTask(VOID);
private VOID DeleteScrollers(gx_device *dev);
private BOOL CreateScrollers(gx_device *dev, struct Screen *Screen);
private VOID WindowResize(gx_device *dev);
private VOID WindowUpdate(struct Gadget *Gadget, gx_device *dev);
private VOID MoveAround(struct Gadget *Gadget, LONG How, gx_device *dev);
private VOID DispatchSuperWindow(gx_device *dev);
private VOID PrintPrinterError(const char *header, LONG io_Error);
void devcleanup(VOID);

	/* Device procedures (see gxdevice.h for the definitions) */

private dev_proc_open_device(amiga_open_default);
private dev_proc_open_device(amiga_open_custom);
private dev_proc_open_device(amiga_open_printer);
private dev_proc_open_device(amiga_open_ilbm);
private dev_proc_output_page(amiga_output_page);
private dev_proc_output_page(amiga_output_page_printer);
private dev_proc_output_page(amiga_output_page_ilbm);
private dev_proc_close_device(amiga_close);
private dev_proc_close_device(amiga_close_printer);
private dev_proc_close_device(amiga_close_ilbm);
private dev_proc_map_rgb_color(amiga_map_rgb_color);
private dev_proc_map_rgb_color(amiga_color_map_rgb_color);
private dev_proc_map_rgb_color(amiga_color_map_rgb_color_pen);
private dev_proc_map_color_rgb(amiga_map_color_rgb);
private dev_proc_map_color_rgb(amiga_color_map_color_rgb);
private dev_proc_map_color_rgb(amiga_color_map_color_rgb_pen);
private dev_proc_fill_rectangle(amiga_fill_rectangle);
private dev_proc_fill_rectangle(amiga_fill_rectangle_raw);
private dev_proc_fill_rectangle(amiga_fill_rectangle_raw_color);
private dev_proc_copy_mono(amiga_copy_mono);
private dev_proc_copy_mono(amiga_copy_mono_raw);
private dev_proc_copy_mono(amiga_copy_mono_raw_color);
private dev_proc_copy_color(amiga_copy_color);
private dev_proc_copy_color(amiga_copy_color_raw);
private dev_proc_copy_color(amiga_copy_color8);
private dev_proc_copy_color(amiga_copy_color_raw_color16);
private dev_proc_draw_line(amiga_draw_line);
private dev_proc_draw_line(amiga_draw_line_raw);
private dev_proc_draw_line(amiga_draw_line_raw_color);
private dev_proc_get_bits(amiga_get_bits);
private dev_proc_get_params(amiga_get_params);
private dev_proc_put_params(amiga_put_params);

	/* External reference to some libraries, required for version checking, etc. */

extern struct GfxBase *GfxBase;
extern struct Library *AslBase;

	/* Detect wether user has set -g and/or -r at the command line */

private BOOL ResolutionSwitch = FALSE;
private BOOL GeometrySwitch = FALSE;

	/* Number of packed bytes and pack buffer. */

private LONG PackedBytes;
private BYTE Buffer[MAXDAT + 1];

	/* Bit masks. */

private UBYTE shift[8] = { 128, 64, 32, 16,  8,  4,  2,  1 };
private UBYTE masks[8] = { 127,191,223,239,247,251,253,254 };

	/* Dark (black) and light (white) rendering colours; the default device
	 * determines the actual colours to be used by looking into the screen
	 * colour lookup table, the other device drivers leave these values
	 * untouched.
	 */

private UBYTE DarkPen = 0;
private UBYTE LightPen = 1;

	/* Device routine jump tables */

private gx_device_procs amiga_default_procs =
{
	amiga_open_default,
	NULL,			/* get_initial_matrix */
	NULL,			/* sync_output */
	amiga_output_page,
	amiga_close,
	amiga_map_rgb_color,
	amiga_map_color_rgb,
	amiga_fill_rectangle,
	NULL,			/* tile_rectangle */
	amiga_copy_mono,
	amiga_copy_color,
	amiga_draw_line,
	NULL,			/* get_bits */
	amiga_get_params,
	amiga_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,			/* 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 */
	NULL,			/* strip_tile_rectangle */
	NULL,			/* strip_copy_rop */
};

private gx_device_procs amiga_custom_procs =
{
	amiga_open_custom,
	NULL,			/* get_initial_matrix */
	NULL,			/* sync_output */
	amiga_output_page,
	amiga_close,
	amiga_map_rgb_color,
	amiga_map_color_rgb,
	amiga_fill_rectangle,
	NULL,			/* tile_rectangle */
	amiga_copy_mono,
	amiga_copy_color,
	amiga_draw_line,
	NULL,			/* get_bits */
	amiga_get_params,
	amiga_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,			/* 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 */
	NULL,			/* strip_tile_rectangle */
	NULL,			/* strip_copy_rop */
};

private gx_device_procs amiga_printer_procs =
{
	amiga_open_printer,
	NULL,			/* get_initial_matrix */
	NULL,			/* sync_output */
	amiga_output_page_printer,
	amiga_close_printer,
	amiga_map_rgb_color,
	amiga_map_color_rgb,
	amiga_fill_rectangle_raw,
	NULL,			/* tile_rectangle */
	amiga_copy_mono_raw,
	amiga_copy_color_raw,
	amiga_draw_line_raw,
	amiga_get_bits,
	amiga_get_params,
	amiga_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,			/* 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 */
	NULL,			/* strip_tile_rectangle */
	NULL,			/* strip_copy_rop */
};

private gx_device_procs amiga_ilbm_procs =
{
	amiga_open_ilbm,
	NULL,			/* get_initial_matrix */
	NULL,			/* sync_output */
	amiga_output_page_ilbm,
	amiga_close_ilbm,
	amiga_map_rgb_color,
	amiga_map_color_rgb,
	amiga_fill_rectangle_raw,
	NULL,			/* tile_rectangle */
	amiga_copy_mono_raw,
	amiga_copy_color_raw,
	amiga_draw_line_raw,
	amiga_get_bits,
	amiga_get_params,
	amiga_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,			/* 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 */
	NULL,			/* strip_tile_rectangle */
	NULL,			/* strip_copy_rop */
};

	/* Default device: opens a window on the Workbench screen and renders into it */

gx_device_amiga gs_amiga_device =
{
	std_device_std_body(gx_device_amiga, &amiga_default_procs, "amiga", 
		INITIAL_WIDTH, INITIAL_HEIGHT, INITIAL_X_DPI, INITIAL_Y_DPI),
	{ 0 },			/* std_procs */

	NULL,				/* screen */
	NULL,				/* window */

	NULL,				/* super_bitmap */
	NULL,				/* gadget */
	NULL,				/* image */

	NULL,				/* dispatcher */
	NULL,				/* main */

	NULL,				/* rport */

	NULL,				/* printer */
	NULL,				/* port */
	NULL,				/* colormap */
	NULL,				/* bitmap */
	NULL,				/* bitplane */

	1,				/* last_pen */

	DEFAULT_DISPLAYMODE,	/* display_mode */
	DEFAULT_FILENAME,		/* output file */
	1,				/* page counter */

	0,				/* cube_size */
	NULL,				/* temp_rport */
	NULL,				/* temp_array */
	NULL				/* pens */
};

	/* Custom device: opens a custom screen, will ask for screen mode or check env variable. */

gx_device_amiga gs_amiga_custom_device =
{
	std_device_std_body(gx_device_amiga, &amiga_custom_procs, "amiga_custom", 
		INITIAL_WIDTH, INITIAL_HEIGHT, INITIAL_X_DPI, INITIAL_Y_DPI),
	{ 0 },			/* std_procs */

	NULL,				/* screen */
	NULL,				/* window */

	NULL,				/* super_bitmap */
	NULL,				/* gadget */
	NULL,				/* image */

	NULL,				/* dispatcher */
	NULL,				/* main */

	NULL,				/* rport */

	NULL,				/* printer */
	NULL,				/* port */
	NULL,				/* colormap */
	NULL,				/* bitmap */
	NULL,				/* bitplane */

	1,				/* last_pen */

	DEFAULT_DISPLAYMODE,	/* display_mode */
	DEFAULT_FILENAME,		/* output file */
	1,				/* page counter */

	0,				/* cube_size */
	NULL,				/* temp_rport */
	NULL,				/* temp_array */
	NULL				/* pens */
};

	/* Printer device: renders the imagery and sends it to the printer */

gx_device_amiga gs_amiga_printer_device =
{
	std_device_std_body(gx_device_amiga, &amiga_printer_procs, "amiga_printer", 
		INITIAL_WIDTH, INITIAL_HEIGHT, INITIAL_X_DPI, INITIAL_Y_DPI),
	{ 0 },			/* std_procs */

	NULL,				/* screen */
	NULL,				/* window */

	NULL,				/* super_bitmap */
	NULL,				/* gadget */
	NULL,				/* image */

	NULL,				/* dispatcher */
	NULL,				/* main */

	NULL,				/* rport */

	NULL,				/* printer */
	NULL,				/* port */
	NULL,				/* colormap */
	NULL,				/* bitmap */
	NULL,				/* bitplane */

	1,				/* last_pen */

	DEFAULT_DISPLAYMODE,	/* display_mode */
	DEFAULT_FILENAME,		/* output file */
	1,				/* page counter */

	0,				/* cube_size */
	NULL,				/* temp_rport */
	NULL,				/* temp_array */
	NULL				/* pens */
};

	/* ILBM device: renders the imagery and saves it to an IFF-ILBM file. */

gx_device_amiga gs_amiga_ilbm_device =
{
	std_device_std_body(gx_device_amiga, &amiga_ilbm_procs, "amiga_ilbm", 
		INITIAL_WIDTH, INITIAL_HEIGHT, INITIAL_X_DPI, INITIAL_Y_DPI),
	{ 0 },			/* std_procs */

	NULL,				/* screen */
	NULL,				/* window */

	NULL,				/* super_bitmap */
	NULL,				/* gadget */
	NULL,				/* image */

	NULL,				/* dispatcher */
	NULL,				/* main */

	NULL,				/* rport */

	NULL,				/* printer */
	NULL,				/* port */
	NULL,				/* colormap */
	NULL,				/* bitmap */
	NULL,				/* bitplane */

	1,				/* last_pen */

	DEFAULT_DISPLAYMODE,	/* display_mode */
	DEFAULT_FILENAME,		/* output file */
	1,				/* page counter */

	0,				/* cube_size */
	NULL,				/* temp_rport */
	NULL,				/* temp_array */
	NULL				/* pens */
};

	/* set_mono_device(gx_device_amiga *dev,int cube_size,LONG *pens):
	 *
	 *	Reconfigure a device for monochrome output.
	 */

private VOID
set_mono_device(gx_device_amiga *dev)
{
	xdev -> color_info . num_components	= 1;
	xdev -> color_info . depth			= 1;
	xdev -> color_info . max_gray		= 1;
	xdev -> color_info . max_color		= 0;
	xdev -> color_info . dither_grays	= 2;
	xdev -> color_info . dither_colors	= 0;

	xdev -> std_procs . copy_color		= amiga_copy_color;
	xdev -> std_procs . map_rgb_color	= amiga_map_rgb_color;
	xdev -> std_procs . map_color_rgb	= amiga_map_color_rgb;

	xdev -> cube_size					= 0;
}

	/* set_colour_device(gx_device_amiga *dev,int cube_size,LONG *pens):
	 *
	 *	Reconfigure a device for colour output.
	 */

private VOID
set_colour_device(gx_device_amiga *dev,int cube_size,LONG *pens)
{
	xdev -> color_info . num_components	= 3;
	xdev -> color_info . depth			= 8;
	xdev -> color_info . max_gray		= cube_size - 1;
	xdev -> color_info . max_color		= cube_size - 1;
	xdev -> color_info . dither_grays	= cube_size;
	xdev -> color_info . dither_colors	= cube_size;

	xdev -> std_procs . copy_color		= amiga_copy_color8;

		/* Any colours to be remapped? */

	if(pens)
	{
		xdev -> std_procs . map_rgb_color	= amiga_color_map_rgb_color_pen;
		xdev -> std_procs . map_color_rgb	= amiga_color_map_color_rgb_pen;
		xdev -> pens						= pens;
	}
	else
	{
		xdev -> std_procs . map_rgb_color	= amiga_color_map_rgb_color;
		xdev -> std_procs . map_color_rgb	= amiga_color_map_color_rgb;
	}

		/* Remember the size of the RGB cube. */

	xdev -> cube_size					= cube_size;
}

	/* set_colour_printer_device(gx_device_amiga *dev,LONG CubeSize):
	 *
	 *	Configure the printer device for colour output.
	 */

private VOID
set_colour_printer_device(gx_device_amiga *dev,LONG CubeSize)
{
	xdev -> color_info . num_components	= 3;
	xdev -> color_info . depth			= 16;
	xdev -> color_info . max_gray		= CubeSize - 1;
	xdev -> color_info . max_color		= CubeSize - 1;
	xdev -> color_info . dither_grays	= CubeSize;
	xdev -> color_info . dither_colors	= CubeSize;

	xdev -> std_procs . fill_rectangle	= amiga_fill_rectangle_raw_color;
	xdev -> std_procs . copy_mono		= amiga_copy_mono_raw_color;
	xdev -> std_procs . copy_color		= amiga_copy_color_raw_color16;
	xdev -> std_procs . draw_line		= amiga_draw_line_raw_color;
	xdev -> std_procs . map_rgb_color	= amiga_color_map_rgb_color;
	xdev -> std_procs . map_color_rgb	= amiga_color_map_color_rgb;
	xdev -> cube_size					= CubeSize;
}

	/* DeleteBitMap(struct BitMap *BitMap,BOOL Private):
	 *
	 *	Free memory associated with a custom rendering bitmap.
	 */

private VOID
DeleteBitMap(struct BitMap *BitMap,BOOL Private)
{
	if(GfxBase -> LibNode . lib_Version >= 39 && !Private)
		FreeBitMap(BitMap);
	else
	{
		LONG i;

		for(i = 0 ; i < BitMap -> Depth ; i++)
		{
			if(BitMap -> Planes[i])
				FreeVec(BitMap -> Planes[i]);
		}

		FreeVec(BitMap);
	}
}

	/* CreateBitMap(LONG Width,LONG Height,LONG Depth,ULONG Flags,struct BitMap *Friend,BOOL Private):
	 *
	 *	Create a custom rendering bitmap.
	 */

private struct BitMap *
CreateBitMap(LONG Width,LONG Height,LONG Depth,ULONG Flags,struct BitMap *Friend,BOOL Private)
{
	if(GfxBase -> LibNode . lib_Version >= 39 && !Private)
		return(AllocBitMap(Width,Height,Depth,Flags,Friend));
	else
	{
		struct BitMap	*BitMap;
		LONG		 Plus;
		ULONG		 MemType;

			/* Bitmap structure needs to be padded if more
			 * than the standard eight bitplanes are to be
			 * allocated.
			 */

		if(Depth > 8)
			Plus = (Depth - 8) * sizeof(PLANEPTR);
		else
			Plus = 0;

		if(Private)
			MemType = MEMF_ANY;
		else
			MemType = MEMF_CHIP;

		BitMap = (struct BitMap *)AllocVec(sizeof(struct BitMap) + Plus, 
			MEMF_ANY | MEMF_CLEAR);
		if(BitMap)
		{
			LONG i,PageSize;

			InitBitMap(BitMap,Depth,Width,Height);

			PageSize = BitMap -> BytesPerRow * BitMap -> Rows;

			for(i = 0 ; i < BitMap -> Depth ; i++)
			{
				if(!(BitMap -> Planes[i] = (PLANEPTR)AllocVec(PageSize,MemType)))
				{
					LONG j;

					for(j = 0 ; j < i ; j++)
						FreeVec(BitMap -> Planes[j]);

					FreeVec(BitMap);

					return(NULL);
				}
			}

			return(BitMap);
		}
		else
		{
			return(NULL);
		}
	}
}

	/* DeleteTempRPort(struct RastPort *Temp):
	 *
	 *	Free memory associated with a temporary raster port.
	 */

private VOID
DeleteTempRPort(struct RastPort *Temp)
{
	DeleteBitMap(Temp -> BitMap,FALSE);

	FreeVec(Temp);
}

	/* CreateTempRPort(struct RastPort *Source):
	 *
	 *	Allocate memory for temporary raster port (one line high).
	 */

private struct RastPort *
CreateTempRPort(struct RastPort *Source)
{
	struct RastPort *Temp;

	Temp = (struct RastPort *)AllocVec(sizeof(struct RastPort), MEMF_ANY);
	if(Temp)
	{
		LONG Width,Depth;

		CopyMem(Source,Temp,sizeof(struct RastPort));

		Temp -> Layer = NULL;

		if(GfxBase -> LibNode . lib_Version >= 39)
		{
			Width	= GetBitMapAttr(Source -> BitMap,BMA_WIDTH);
			Depth	= GetBitMapAttr(Source -> BitMap,BMA_DEPTH);
		}
		else
		{
			Width	= Source -> BitMap -> BytesPerRow * 8;
			Depth	= Source -> BitMap -> Depth;
		}

		Temp->BitMap = CreateBitMap(Width, 1, Depth, NULL, Source->BitMap, 
			FALSE);
		if(Temp -> BitMap)
			return(Temp);
		else
			FreeVec(Temp);
	}

	return(NULL);
}

	/* Euclid(LONG a,LONG b):
	 *
	 *	Compute the greatest common divisor of two integers.
	 */

private LONG
Euclid(LONG a,LONG b)
{
	do
	{
		if(a < b)
		{
			LONG t;

			t = a;
			a = b;
			b = t;
		}

		a = a % b;
	}
	while(a);

	return(b);
}

	/* PutDump(register BYTE *Destination,register LONG Count):
	 *
	 *	Store a byte dump.
	 */

private BYTE *
PutDump(register BYTE *Destination,register LONG Count)
{
	register BYTE *Source = Buffer;

	*Destination++	 = Count - 1;
	 PackedBytes	+= Count + 1;

	while(Count--)
		*Destination++ = *Source++;

	return(Destination);
}

	/* PutRun(register BYTE *Destination,LONG Count,WORD Char):
	 *
	 *	Store a byte run.
	 */

private BYTE *
PutRun(register BYTE *Destination,LONG Count,WORD Char)
{
	*Destination++	 = -(Count - 1);
	*Destination++	 = Char;
	 PackedBytes	+= 2;

	return(Destination);
}

	/* PackRow(PLANEPTR *SourcePtr,register BYTE *Destination,LONG RowSize):
	 *
	 *	Pack a raster line using the CmpByteRun1 algorithm.
	 */

private LONG
PackRow(PLANEPTR *SourcePtr,register BYTE *Destination,LONG RowSize)
{
	register BYTE *Source = *SourcePtr;

	WORD	Buffered	= 1,
		RunStart	= 0;
	BYTE	Mode		= DUMP,
		LastChar,
		Char;

	PackedBytes = 0;

	Buffer[0] = LastChar = Char = *Source++;

	RowSize--;

	while(RowSize--)
	{
		Buffer[Buffered++] = Char = *Source++;

		if(Mode)
		{
			if((Char != LastChar) || (Buffered - RunStart > MAXRUN))
			{
				Destination	= PutRun(Destination,Buffered - 1 - RunStart,LastChar);
				Buffer[0]	= Char;
				Buffered	= 1;
				RunStart	= 0;
				Mode		= DUMP;
			}
		}
		else
		{
			if(Buffered > MAXDAT)
			{
				Destination	= PutDump(Destination,Buffered - 1);
				Buffer[0]	= Char;
				Buffered	= 1;
				RunStart	= 0;
			}
			else
			{
				if(Char == LastChar)
				{
					if(Buffered - RunStart >= MINRUN)
					{
						if(RunStart)
							Destination = PutDump(Destination,RunStart);

						Mode = RUN;
					}
					else
					{
						if(!RunStart)
							Mode = RUN;
					}
				}
				else
					RunStart = Buffered - 1;
			}
		}

		LastChar = Char;
	}

	if(Mode)
		PutRun(Destination,Buffered - RunStart,LastChar);
	else
		PutDump(Destination,Buffered);

	*SourcePtr = Source;

	return(PackedBytes);
}

	/* PutBODY(struct IFFHandle *Handle,struct BitMap *BitMap):
	 *
	 *	Store a bitmap in a BODY chunk.
	 */

private BOOL
PutBODY(struct IFFHandle *Handle,struct BitMap *BitMap)
{
	PLANEPTR	*Planes;
	BYTE		*PackBuffer;
	BOOL		 Success = FALSE;
	LONG		 PackedBytes,
			 i,j;

		/* Allocate the bitplane information. */

	Planes = (PLANEPTR *)AllocVec(BitMap->Depth * sizeof(PLANEPTR *), 
		MEMF_ANY | MEMF_CLEAR);
	if(Planes)
	{
			/* Allocate the compression buffer. */

		PackBuffer = (BYTE *)AllocVec(BitMap->BytesPerRow * 2, MEMF_ANY);
		if(PackBuffer)
		{
				/* Copy the planes over. */

			for(i = 0 ; i < BitMap -> Depth ; i++)
				Planes[i] = BitMap -> Planes[i];

			if(!PushChunk(Handle,0,ID_BODY,IFFSIZE_UNKNOWN))
			{
				Success = TRUE;

					/* Run down the rows. */

				for(i = 0 ; Success && i < BitMap -> Rows ; i++)
				{
					for(j = 0 ; Success && j < BitMap -> Depth ; j++)
					{
							/* Pack the data. */

						PackedBytes = PackRow(&Planes[j],PackBuffer,BitMap -> BytesPerRow);

							/* Write it to disk. */

						if(WriteChunkRecords(Handle,PackBuffer,PackedBytes,1) != 1)
							Success = FALSE;
					}
				}

				if(PopChunk(Handle))
					Success = FALSE;
			}

			FreeVec(PackBuffer);
		}

		FreeVec(Planes);
	}

	return(Success);
}

	/* PutANNO(struct IFFHandle *Handle):
	 *
	 *	Store annotation chunk.
	 */

private BOOL
PutANNO(struct IFFHandle *Handle)
{
	STATIC unsigned char Note[256];

	sprintf(Note, "Image generated by %s %d.%02d (device=amiga_ilbm)", 
		gs_product, (int)(gs_revision / 100), (int)(gs_revision % 100));

	if(!PushChunk(Handle,0,ID_ANNO,strlen(Note)))
	{
		if(WriteChunkRecords(Handle,Note,strlen(Note),1) == 1)
		{
			if(!PopChunk(Handle))
				return(TRUE);
		}
	}

	return(FALSE);
}

	/* PutCAMG(struct IFFHandle *Handle):
	 *
	 *	Store display mode chunk.
	 */

private BOOL
PutCAMG(struct IFFHandle *Handle)
{
	ULONG ViewModes = HIRESLACE_KEY;

	if(!PushChunk(Handle,0,ID_CAMG,sizeof(ULONG)))
	{
		if(WriteChunkRecords(Handle,&ViewModes,sizeof(ULONG),1) == 1)
		{
			if(!PopChunk(Handle))
				return(TRUE);
		}
	}

	return(FALSE);
}

	/* PutCMAP(struct IFFHandle *Handle):
	 *
	 *	Store colour map chunk.
	 */

private BOOL
PutCMAP(struct IFFHandle *Handle)
{
	STATIC UBYTE Colours[2][3] =
	{
		{ 0x00,0x00,0x00, },
		{ 0xFF,0xFF,0xFF, },
	};

	if(!PushChunk(Handle,0,ID_CMAP,sizeof(Colours)))
	{
		if(WriteChunkRecords(Handle,Colours,2,3) == 3)
		{
			if(!PopChunk(Handle))
				return(TRUE);
		}
	}

	return(FALSE);
}

	/* PutDPI(struct IFFHandle *Handle,UWORD X_DPI,UWORD Y_DPI):
	 *
	 *	Store DPI chunk.
	 */

private BOOL
PutDPI(struct IFFHandle *Handle,UWORD X_DPI,UWORD Y_DPI)
{
	DPIHeader Header;

	Header . dpi_x = X_DPI;
	Header . dpi_y = Y_DPI;

	if(!PushChunk(Handle,0,ID_DPI,sizeof(Header)))
	{
		if(WriteChunkRecords(Handle,&Header,sizeof(Header),1) == 1)
		{
			if(!PopChunk(Handle))
				return(TRUE);
		}
	}

	return(FALSE);
}

	/* PutBMHD(struct IFFHandle *Handle,LONG Width,LONG Height,UWORD X_DPI,UWORD Y_DPI):
	 *
	 *	Store BMHD chunk.
	 */

private BOOL
PutBMHD(struct IFFHandle *Handle,LONG Width,LONG Height,UWORD X_DPI,UWORD Y_DPI)
{
		/* Valid parameters? */

	if(X_DPI > 0 && Y_DPI > 0 && Width > 0 && Height > 0)
	{
		struct BitMapHeader	Header;
		UWORD		gcd;

			/* So we can store neat & small
			 * aspect ration values.
			 */

		gcd = Euclid(X_DPI,Y_DPI);

		Header . bmh_Width			= Width;
		Header . bmh_Height			= Height;
		Header . bmh_Left			= 0;
		Header . bmh_Top			= 0;
		Header . bmh_Depth			= 1;
		Header . bmh_Masking		= 0;
		Header . bmh_Compression	= 1;
		Header . bmh_Pad			= 0;
		Header . bmh_Transparent	= 0;
		Header . bmh_XAspect		= X_DPI / gcd;
		Header . bmh_YAspect		= Y_DPI / gcd;
		Header . bmh_PageWidth		= Width;
		Header . bmh_PageHeight		= Height;

		if(!PushChunk(Handle,0,ID_BMHD,sizeof(Header)))
		{
			if(WriteChunkRecords(Handle,&Header,sizeof(Header),1) == 1)
			{
				if(!PopChunk(Handle))
					return(TRUE);
			}
		}
	}

	return(FALSE);
}

	/* SaveBitMap(STRPTR Name,struct BitMap *BitMap,LONG Width,LONG Height,UWORD X_DPI,UWORD Y_DPI):
	 *
	 *	Store a bitmap in an IFF-ILBM file.
	 */

private BOOL
SaveBitMap(STRPTR Name,struct BitMap *BitMap,LONG Width,LONG Height,UWORD X_DPI,UWORD Y_DPI)
{
	struct IFFHandle	*Handle;
	BOOL			 Success = FALSE;

	Handle = AllocIFF();
	if(Handle)
	{
		Handle -> iff_Stream = Open(Name, MODE_NEWFILE);
		if(Handle)
		{
			InitIFFasDOS(Handle);

			if(!OpenIFF(Handle,IFFF_WRITE))
			{
				if(!PushChunk(Handle,ID_ILBM,ID_FORM,IFFSIZE_UNKNOWN))
				{
					if(PutBMHD(Handle,Width,Height,X_DPI,Y_DPI))
					{
						if(PutANNO(Handle))
						{
							if(PutCMAP(Handle))
							{
								if(PutCAMG(Handle))
								{
									if(PutDPI(Handle,X_DPI,Y_DPI))
									{
										if(PutBODY(Handle,BitMap))
											Success = TRUE;
									}
								}
							}
						}
					}

					if(PopChunk(Handle))
						Success = FALSE;
				}

				CloseIFF(Handle);
			}

			Close(Handle -> iff_Stream);

			if(!Success)
				DeleteFile(Name);
		}

		FreeIFF(Handle);
	}

	return(Success);
}

	/* DispatchTask():
	 *
	 *	Asynchronous window message dispatcher.
	 */

private VOID
DispatchTask()
{
	struct Task	*me;
	gx_device	*dev;

		/* Set up global data area base register. */
#ifdef IXEMUL
	ix_geta4();
#endif
		/* Who am I? */

	me = FindTask(NULL);

		/* Wait for wakeup call. */

	Wait(SIG_WAKEUP);

		/* Obtain device pointer. */

	dev = me -> tc_UserData;

		/* Enable user input. */

	if(ModifyIDCMP(xdev -> window,IDCMP_FLAGS))
	{
		ULONG	Mask = 1 << xdev -> window -> UserPort -> mp_SigBit,
			Set;
		BOOL	Done = FALSE;

			/* Fill in the dispatcher entry. */

		xdev -> dispatcher = me;

			/* Ring back. */

		Signal((struct Task *)xdev -> main,SIG_HANDSHAKE);

			/* Wait for input... */

		do
		{
			Set = Wait(Mask | SIG_KILL);

			if(Set & Mask)
				DispatchSuperWindow(dev);

			if(Set & SIG_KILL)
				Done = TRUE;
		}
		while(!Done);

			/* Disable user input. */

		ModifyIDCMP(xdev -> window,NULL);
	}

		/* Disable task switching. */

	Forbid();

		/* Clear the dispatcher entry. */

	xdev -> dispatcher = NULL;

		/* Signal the main process that we are done. */

	Signal((struct Task *)xdev -> main,SIG_HANDSHAKE);

		/* Enable task switching. */

	Permit();

		/* Remove ourselves. */

	RemTask(NULL);
}

	/* DeleteScrollers(gx_device *dev):
	 *
	 *	Delete the window border scrollers.
	 */

private VOID
DeleteScrollers(gx_device *dev)
{
	if(xdev -> gadget)
	{
		if(xdev -> gadget[HORIZONTAL_SCROLLER])
			DisposeObject(xdev -> gadget[HORIZONTAL_SCROLLER]);

		if(xdev -> gadget[VERTICAL_SCROLLER])
			DisposeObject(xdev -> gadget[VERTICAL_SCROLLER]);

		if(xdev -> gadget[UP_ARROW])
			DisposeObject(xdev -> gadget[UP_ARROW]);

		if(xdev -> gadget[DOWN_ARROW])
			DisposeObject(xdev -> gadget[DOWN_ARROW]);

		if(xdev -> gadget[LEFT_ARROW])
			DisposeObject(xdev -> gadget[LEFT_ARROW]);

		if(xdev -> gadget[RIGHT_ARROW])
			DisposeObject(xdev -> gadget[RIGHT_ARROW]);

		FreeVec(xdev -> gadget);

		xdev -> gadget = NULL;
	}

	if(xdev -> image)
	{
		if(xdev -> image[UP_IMAGE])
			DisposeObject(xdev -> image[UP_IMAGE]);

		if(xdev -> image[DOWN_IMAGE])
			DisposeObject(xdev -> image[DOWN_IMAGE]);

		if(xdev -> image[LEFT_IMAGE])
			DisposeObject(xdev -> image[LEFT_IMAGE]);

		if(xdev -> image[RIGHT_IMAGE])
			DisposeObject(xdev -> image[RIGHT_IMAGE]);

		FreeVec(xdev -> image);

		xdev -> image = NULL;
	}
}

	/* CreateScrollers(gx_device *dev,struct Screen *Screen):
	 *
	 *	Create the window border scroller handles.
	 */

private BOOL
CreateScrollers(gx_device *dev,struct Screen *Screen)
{
	BOOL Result = FALSE;

	xdev -> gadget = (struct Gadget **)AllocVec(sizeof(struct Gadget *) 
		* GADGET_COUNT, MEMF_ANY | MEMF_CLEAR | MEMF_PUBLIC);
	if(xdev -> gadget)
	{
		xdev -> image = (struct Image **)AllocVec(sizeof(struct Image *) 
			* IMAGE_COUNT, MEMF_ANY | MEMF_CLEAR | MEMF_PUBLIC);
		if(xdev -> image)
		{
			struct DrawInfo *DrawInfo;

			DrawInfo = GetScreenDrawInfo(Screen);
			if(DrawInfo)
			{
				ULONG	 SizeWidth,
					 SizeHeight;
				ULONG	 ArrowWidth,
					 ArrowHeight;
				UWORD	 SizeType;
				Object	*SizeImage;

				if(Screen -> Flags & SCREENHIRES)
				{
					SizeWidth	= 18;
					SizeHeight	= 10;

					SizeType	= SYSISIZE_MEDRES;
				}
				else
				{
					SizeWidth	= 13;
					SizeHeight	= 11;

					SizeType	= SYSISIZE_LOWRES;
				}

				SizeImage = NewObject(NULL, SYSICLASS, 
					SYSIA_Size,	SizeType,
					SYSIA_Which,	SIZEIMAGE,
					SYSIA_DrawInfo,	(ULONG)DrawInfo,
					TAG_DONE);
				if(SizeImage)
				{
					GetAttr(IA_Width,	SizeImage,&SizeWidth);
					GetAttr(IA_Height,	SizeImage,&SizeHeight);

					DisposeObject(SizeImage);
				}

				xdev -> image[UP_IMAGE] = (struct Image *)NewObject(NULL, SYSICLASS,
					SYSIA_Size,	SizeType,
					SYSIA_Which,	UPIMAGE,
					SYSIA_DrawInfo,	(ULONG)DrawInfo,
					TAG_DONE);
				if(xdev -> image[UP_IMAGE])
				{
					GetAttr(IA_Height,xdev -> image[UP_IMAGE],&ArrowHeight);

					xdev -> image[DOWN_IMAGE] = (struct Image *)NewObject(NULL, SYSICLASS,
						SYSIA_Size,	SizeType,
						SYSIA_Which,	DOWNIMAGE,
						SYSIA_DrawInfo,	(ULONG)DrawInfo,
						TAG_DONE);
					if(xdev -> image[DOWN_IMAGE])
					{
						xdev -> image[LEFT_IMAGE] = (struct Image *)NewObject(NULL, SYSICLASS,
							SYSIA_Size,	SizeType,
							SYSIA_Which,	LEFTIMAGE,
							SYSIA_DrawInfo,	(ULONG)DrawInfo,
							TAG_DONE);
						if(xdev -> image[LEFT_IMAGE])
						{
							GetAttr(IA_Width,xdev -> image[LEFT_IMAGE],&ArrowWidth);

							xdev -> image[RIGHT_IMAGE] = (struct Image *)NewObject(NULL, SYSICLASS,
								SYSIA_Size,	SizeType,
								SYSIA_Which,	RIGHTIMAGE,
								SYSIA_DrawInfo,	(ULONG)DrawInfo,
								TAG_DONE);
							if(xdev -> image[RIGHT_IMAGE])
							{
								xdev -> gadget[VERTICAL_SCROLLER] = NewObject(NULL, PROPGCLASS,
									GA_ID,		VERTICAL_SCROLLER,

									GA_Top,		Screen -> WBorTop + Screen -> Font -> ta_YSize + 2,
									GA_RelHeight,	-(Screen -> WBorTop + Screen -> Font -> ta_YSize + 2 + SizeHeight + 1 + 2 * ArrowHeight),
									GA_Width,	SizeWidth - 8,
									GA_RelRight,	-(SizeWidth - 5),

									GA_GZZGadget,	TRUE,
									GA_Immediate,	TRUE,
									GA_FollowMouse,	TRUE,
									GA_RelVerify,	TRUE,
									GA_RightBorder,	TRUE,

									PGA_Freedom,	FREEVERT,
									PGA_NewLook,	TRUE,
									PGA_Borderless,	TRUE,

									PGA_Visible,	1,
									PGA_Total,	1,
									TAG_DONE);
								if(xdev -> gadget[VERTICAL_SCROLLER])
								{
									xdev -> gadget[HORIZONTAL_SCROLLER] = NewObject(NULL, PROPGCLASS,
										GA_ID,		HORIZONTAL_SCROLLER,
										GA_Previous,	(ULONG)(xdev -> gadget[VERTICAL_SCROLLER]),

										GA_Height,	SizeHeight - 4,
										GA_RelBottom,	-(SizeHeight - 4 + 1),
										GA_Left,	4,
										GA_RelWidth,	-(2 + SizeWidth + 4 + 2 * ArrowWidth),

										GA_GZZGadget,	TRUE,
										GA_Immediate,	TRUE,
										GA_FollowMouse,	TRUE,
										GA_RelVerify,	TRUE,
										GA_BottomBorder,TRUE,

										PGA_Freedom,	FREEHORIZ,
										PGA_NewLook,	TRUE,
										PGA_Borderless,	TRUE,

										PGA_Visible,	1,
										PGA_Total,	1,
										TAG_DONE);
									if(xdev -> gadget[HORIZONTAL_SCROLLER])
									{
										STATIC struct TagItem ArrowMappings[] = 
										{ 
											{ GA_ID, GA_ID, },
											{ TAG_END, },
										};

										xdev -> gadget[UP_ARROW] = NewObject(NULL, BUTTONGCLASS,
											GA_ID,		UP_ARROW,
											GA_Previous,	(ULONG)(xdev -> gadget[HORIZONTAL_SCROLLER]),

											GA_GZZGadget,	TRUE,
											GA_Image,	(ULONG)(xdev -> image[UP_IMAGE]),
											GA_RelRight,	-(SizeWidth - 1),
											GA_RelBottom,	-(SizeHeight - 1 + 2 * ArrowHeight),
											GA_Height,	ArrowHeight,
											GA_Width,	SizeWidth,
											GA_Immediate,	TRUE,
											GA_RelVerify,	TRUE,
											GA_RightBorder,	TRUE,

											ICA_TARGET,	ICTARGET_IDCMP,
											ICA_MAP,	(ULONG)ArrowMappings,
											TAG_DONE);
										if(xdev -> gadget[UP_ARROW])
										{
											xdev -> gadget[DOWN_ARROW] = NewObject(NULL, BUTTONGCLASS,
												GA_ID,		DOWN_ARROW,
												GA_Previous,	(ULONG)(xdev -> gadget[UP_ARROW]),

												GA_GZZGadget,	TRUE,
												GA_Image,	(ULONG)(xdev -> image[DOWN_IMAGE]),
												GA_RelRight,	-(SizeWidth - 1),
												GA_RelBottom,	-(SizeHeight - 1 + ArrowHeight),
												GA_Height,	ArrowHeight,
												GA_Width,	SizeWidth,
												GA_Immediate,	TRUE,
												GA_RelVerify,	TRUE,
												GA_RightBorder,	TRUE,

												ICA_TARGET,	ICTARGET_IDCMP,
												ICA_MAP,	(ULONG)ArrowMappings,
												TAG_DONE);
											if(xdev -> gadget[DOWN_ARROW])
											{
												xdev -> gadget[LEFT_ARROW] = NewObject(NULL, BUTTONGCLASS,
													GA_ID,		LEFT_ARROW,
													GA_Previous,	(ULONG)(xdev -> gadget[DOWN_ARROW]),

													GA_GZZGadget,	TRUE,
													GA_Image,	(ULONG)(xdev -> image[LEFT_IMAGE]),
													GA_RelRight,	-(SizeWidth - 1 + 2 * ArrowWidth),
													GA_RelBottom,	-(SizeHeight - 1),
													GA_Height,	SizeHeight,
													GA_Width,	ArrowWidth,
													GA_Immediate,	TRUE,
													GA_RelVerify,	TRUE,
													GA_BottomBorder,TRUE,

													ICA_TARGET,	ICTARGET_IDCMP,
													ICA_MAP,	(ULONG)ArrowMappings,
													TAG_DONE);
												if(xdev -> gadget[LEFT_ARROW])
												{
													xdev -> gadget[RIGHT_ARROW] = NewObject(NULL, BUTTONGCLASS,
														GA_ID,		RIGHT_ARROW,
														GA_Previous,	(ULONG)(xdev -> gadget[LEFT_ARROW]),

														GA_GZZGadget,	TRUE,
														GA_Image,	(ULONG)(xdev -> image[RIGHT_IMAGE]),
														GA_RelRight,	-(SizeWidth - 1 + ArrowWidth),
														GA_RelBottom,	-(SizeHeight - 1),
														GA_Height,	SizeHeight,
														GA_Width,	ArrowWidth,
														GA_Immediate,	TRUE,
														GA_RelVerify,	TRUE,
														GA_BottomBorder,TRUE,

														ICA_TARGET,	ICTARGET_IDCMP,
														ICA_MAP,	(ULONG)ArrowMappings,
														TAG_DONE);
													if(xdev -> gadget[RIGHT_ARROW])
														Result = TRUE;
												}
											}
										}
									}
								}
							}
						}
					}
				}

				FreeScreenDrawInfo(Screen,DrawInfo);
			}
		}
	}

	return(Result);
}

	/* WindowResize(gx_device *dev):
	 *
	 *	Update the slider sizes and positions after the window
	 *	was resized.
	 */

private VOID
WindowResize(gx_device *dev)
{
	LONG	DeltaX,
		DeltaY,
		Temp;

		/* Query the current horizontal slider position. */

	if((Temp = LAYERXOFFSET(xdev -> window) + xdev -> window -> GZZWidth) > xdev -> width)
		DeltaX = xdev -> width - Temp;
	else
		DeltaX = 0;

		/* Query the current vertical slider position. */

	if((Temp = LAYERYOFFSET(xdev -> window) + xdev -> window -> GZZHeight) > xdev -> height)
		DeltaY = xdev -> height - Temp;
	else
		DeltaY = 0;

		/* Move the currently displayed window area around. */

	if(DeltaX || DeltaY)
		ScrollLayer(NULL,xdev -> window -> RPort -> Layer,DeltaX,DeltaY);

		/* Update the new horizontal slider position and size. */

	SetGadgetAttrs(xdev -> gadget[HORIZONTAL_SCROLLER],xdev -> window,NULL,
		PGA_Top,	LAYERXOFFSET(xdev -> window),
		PGA_Visible,	xdev -> window -> GZZWidth,
		PGA_Total,	xdev -> width,
	TAG_DONE);

		/* Update the new vertical slider position and size. */

	SetGadgetAttrs(xdev -> gadget[VERTICAL_SCROLLER],xdev -> window,NULL,
		PGA_Top,	LAYERYOFFSET(xdev -> window),
		PGA_Visible,	xdev -> window -> GZZHeight,
		PGA_Total,	xdev -> height,
	TAG_DONE);
}

	/* WindowUpdate(struct Gadget *Gadget,gx_device *dev):
	 *
	 *	Move the currently visible portion of the
	 *	window according to the current slider
	 *	position.
	 */

private VOID
WindowUpdate(struct Gadget *Gadget,gx_device *dev)
{
	LONG Storage;

	switch(Gadget -> GadgetID)
	{
		case HORIZONTAL_SCROLLER:

			if(GetAttr(PGA_Top,Gadget,&Storage))
				ScrollLayer(NULL,xdev -> window -> RPort -> Layer,Storage - LAYERXOFFSET(xdev -> window),0);

			break;

		case VERTICAL_SCROLLER:

			if(GetAttr(PGA_Top,Gadget,&Storage))
				ScrollLayer(NULL,xdev -> window -> RPort -> Layer,0,Storage - LAYERYOFFSET(xdev -> window));

			break;
	}
}

	/* MoveAround(struct Gadget *Gadget,int How,gx_device *dev):
	 *
	 *	Move the currently visible window area according to
	 *	user input.
	 */

private VOID
MoveAround(struct Gadget *Gadget,LONG How,gx_device *dev)
{
	LONG Storage;

	if(GetAttr(PGA_Top,Gadget,&Storage))
	{
		LONG Max = 0;

		switch(Gadget -> GadgetID)
		{
			case HORIZONTAL_SCROLLER:

				Max = xdev -> width - xdev -> window -> GZZWidth;
				break;

			case VERTICAL_SCROLLER:

				Max = xdev -> height - xdev -> window -> GZZHeight;
				break;
		}

		switch(How)
		{
			case MOVE_MIN:

				Storage = 0;
				break;

			case MOVE_MAX:

				Storage = Max;
				break;

			case MOVE_DOWN:

				if(Storage > xdev -> height / 100)
					Storage -= xdev -> height / 100;
				else
					Storage = 0;

				break;

			case MOVE_FAR_DOWN:

				if(Storage > xdev -> height / 10)
					Storage -= xdev -> height / 10;
				else
					Storage = 0;

				break;

			case MOVE_FAR_UP:

				if(Storage + xdev -> width / 10 < Max)
					Storage += xdev -> width / 10;
				else
					Storage = Max;

				break;

			case MOVE_UP:

				if(Storage + xdev -> width / 100 < Max)
					Storage += xdev -> width / 100;
				else
					Storage = Max;

				break;
		}

		switch(Gadget -> GadgetID)
		{
			case HORIZONTAL_SCROLLER:

				if(LAYERXOFFSET(xdev -> window) != Storage)
				{
					ScrollLayer(NULL,xdev -> window -> RPort -> Layer,Storage - LAYERXOFFSET(xdev -> window),0);

					SetGadgetAttrs(Gadget,xdev -> window,NULL,
						PGA_Top,Storage,
					TAG_DONE);
				}

				break;

			case VERTICAL_SCROLLER:

				if(LAYERYOFFSET(xdev -> window) != Storage)
				{
					ScrollLayer(NULL,xdev -> window -> RPort -> Layer,0,Storage - LAYERYOFFSET(xdev -> window));

					SetGadgetAttrs(Gadget,xdev -> window,NULL,
						PGA_Top,Storage,
					TAG_DONE);
				}

				break;
		}
	}
}

	/* DispatchSuperWindow(gx_device *dev):
	 *
	 *	Dispatch user window input.
	 */

private VOID
DispatchSuperWindow(gx_device *dev)
{
	STATIC struct Gadget	*CurrentGadget = NULL;

	struct IntuiMessage	*IntuiMessage;
	ULONG			 MsgClass,
				 MsgCode,
				 MsgQualifier;
	struct Gadget		*MsgGadget;

	while(NULL != (IntuiMessage = (struct IntuiMessage *)GetMsg(
		xdev -> window -> UserPort)))
	{
		MsgClass	= IntuiMessage -> Class;
		MsgCode		= IntuiMessage -> Code;
		MsgQualifier	= IntuiMessage -> Qualifier;
		MsgGadget	= IntuiMessage -> IAddress;

		ReplyMsg((struct Message *)IntuiMessage);

		switch(MsgClass)
		{
			case IDCMP_VANILLAKEY:

				if(MsgCode == '\033' || MsgCode == '\003')
					Signal((struct Task *)xdev -> main,SIG_KILL);

				break;

			case IDCMP_RAWKEY:

				switch(MsgCode)
				{
					case HELP_CODE:

						DisplayBeep(xdev -> window -> WScreen);

						break;

					case CURSORUP:

						if(MsgQualifier & IEQUALIFIER_CONTROL)
							MoveAround(xdev -> gadget[VERTICAL_SCROLLER],MOVE_MIN,dev);
						else
						{
							if(MsgQualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT))
								MoveAround(xdev -> gadget[VERTICAL_SCROLLER],MOVE_FAR_DOWN,dev);
							else
								MoveAround(xdev -> gadget[VERTICAL_SCROLLER],MOVE_DOWN,dev);
						}

						break;

					case CURSORLEFT:

						if(MsgQualifier & IEQUALIFIER_CONTROL)
							MoveAround(xdev -> gadget[HORIZONTAL_SCROLLER],MOVE_MIN,dev);
						else
						{
							if(MsgQualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT))
								MoveAround(xdev -> gadget[HORIZONTAL_SCROLLER],MOVE_FAR_DOWN,dev);
							else
								MoveAround(xdev -> gadget[HORIZONTAL_SCROLLER],MOVE_DOWN,dev);
						}

						break;

					case CURSORRIGHT:

						if(MsgQualifier & IEQUALIFIER_CONTROL)
							MoveAround(xdev -> gadget[HORIZONTAL_SCROLLER],MOVE_MAX,dev);
						else
						{
							if(MsgQualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT))
								MoveAround(xdev -> gadget[HORIZONTAL_SCROLLER],MOVE_FAR_UP,dev);
							else
								MoveAround(xdev -> gadget[HORIZONTAL_SCROLLER],MOVE_UP,dev);
						}

						break;

					case CURSORDOWN:

						if(MsgQualifier & IEQUALIFIER_CONTROL)
							MoveAround(xdev -> gadget[VERTICAL_SCROLLER],MOVE_MAX,dev);
						else
						{
							if(MsgQualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT))
								MoveAround(xdev -> gadget[VERTICAL_SCROLLER],MOVE_FAR_UP,dev);
							else
								MoveAround(xdev -> gadget[VERTICAL_SCROLLER],MOVE_UP,dev);
						}

						break;

					default:

						break;
				}

				break;

			case IDCMP_CLOSEWINDOW:

				Signal((struct Task *)xdev -> main,SIG_KILL);

				break;

			case IDCMP_GADGETDOWN:

				CurrentGadget = MsgGadget;

				WindowUpdate(MsgGadget,dev);

				break;

			case IDCMP_GADGETUP:

				CurrentGadget = NULL;

				WindowUpdate(MsgGadget,dev);

				break;

			case IDCMP_MOUSEMOVE:

				if(CurrentGadget)
					WindowUpdate(CurrentGadget,dev);

				break;

			case IDCMP_IDCMPUPDATE:

				switch(GetTagData(GA_ID,0,(struct TagItem *)MsgGadget))
				{
					case UP_ARROW:

						MoveAround(xdev -> gadget[VERTICAL_SCROLLER],MOVE_DOWN,dev);
						break;

					case DOWN_ARROW:

						MoveAround(xdev -> gadget[VERTICAL_SCROLLER],MOVE_UP,dev);
						break;

					case LEFT_ARROW:

						MoveAround(xdev -> gadget[HORIZONTAL_SCROLLER],MOVE_DOWN,dev);
						break;

					case RIGHT_ARROW:

						MoveAround(xdev -> gadget[HORIZONTAL_SCROLLER],MOVE_UP,dev);
						break;

					default:

						break;
				}

				break;

			case IDCMP_NEWSIZE:

				WindowResize(dev);

				break;

			default:

				break;
		}
	}
}

	/* PrintPrinterError(const char *header, LONG io_Error):
	 *
	 *	Print a real error message for Printer.device errors.
	 */

private VOID
PrintPrinterError(const char *header, LONG io_Error)
{
	char buffer[256];
	char defaultmessage[64];
	const char *errormessage;
					
	switch (io_Error)
	{
		case PDERR_CANCEL:
			errormessage = "user cancelled print";
			break;
		case PDERR_NOTGRAPHICS:
			errormessage = "printer cannot output graphics";
			break;
		case PDERR_BADDIMENSION:
			errormessage = "print dimensions illegal";
			break;
		case PDERR_INTERNALMEMORY:
			errormessage = "no memory for internal variables";
			break;
		case PDERR_BUFFERMEMORY:
			errormessage = "no memory for print buffer";
			break;
		case IOERR_OPENFAIL:
			errormessage = "device/unit failed to open";
			break;
		case IOERR_ABORTED:
			errormessage = "request terminated early [after AbortIO()]";
			break;
		case IOERR_NOCMD:
			errormessage = "command not supported by device";
			break;
		case IOERR_BADLENGTH:
			errormessage = "not a valid length (usually IO_LENGTH)";
			break;
		case IOERR_BADADDRESS:
			errormessage = "invalid address (misaligned or bad range)";
			break;
		case IOERR_UNITBUSY:
			errormessage = "device opens ok, but requested unit is busy";
			break;
		case IOERR_SELFTEST:
			errormessage = "hardware failed self-test";
			break;
		default:
			sprintf(defaultmessage, "unknown I/O error #%ld", io_Error);
			errormessage = defaultmessage;
			break;
	}
	sprintf(buffer, "%s (%s)\n", header, errormessage);
	eprintf(buffer);
}

	/* Simple routine to call the cleanup routine of a device,
	 * all devices are smart enough to handle shutdown in
	 * case they have not been opened yet.
	 */

private void 
close_device(gx_device_amiga *dev)
{
	if(xdev -> std_procs . close_device)
		(*xdev -> std_procs . close_device)((gx_device *)dev);
}

	/* devcleanup():
	 *
	 *	Clean up all devices, free all resources.
	 */

void
devcleanup()
{
	close_device(&gs_amiga_device);
	close_device(&gs_amiga_custom_device);
	close_device(&gs_amiga_printer_device);
	close_device(&gs_amiga_ilbm_device);
}

	/* amiga_set_pen(gx_device *dev,gx_color_index color):
	 *
	 *	Sets the rendering pen and remembers the current
	 *	settings.
	 */

private VOID
amiga_set_pen(gx_device *dev,gx_color_index color)
{
	if(xdev -> last_pen != color)
		SetAPen(xdev -> rport,xdev -> last_pen = color);
}

	/* amiga_map_rgb_color(gx_device *dev,gx_color_value red,gx_color_value green,gx_color_value blue):
	 *
	 *	Map a colour either to the black or the light rendering pen.
	 */

private gx_color_index
amiga_map_rgb_color(gx_device *dev,gx_color_value red,gx_color_value green,gx_color_value blue)
{
	if((red | green | blue) > gx_max_color_value / 2)
		return(LightPen);
	else
		return(DarkPen);
}

	/* amiga_map_color_rgb(gx_device *dev,gx_color_index color,gx_color_value rgb[3]):
	 *
	 *	Map the light/dark rendering pen to RGB values.
	 */

private int
amiga_map_color_rgb(gx_device *dev,gx_color_index color,gx_color_value rgb[3])
{
	int i;

	if(color == LightPen)
	{
		for(i = 0 ; i < 3 ; i++)
			rgb[i] = gx_max_color_value;
	}
	else
	{
		for(i = 0 ; i < 3 ; i++)
			rgb[i] = 0;
	}

	return(0);
}

	/* AllocatePens(struct ViewPort *VPort,LONG CubeSize):
	 *
	 *	Allocate shareable viewport pens.
	 */

private LONG *
AllocatePens(struct ViewPort *VPort,LONG CubeSize)
{
	if(GfxBase -> LibNode . lib_Version >= 39)
	{
		LONG Total = CubeSize * CubeSize * CubeSize,*Pens;

		Pens = (LONG *)AllocVec(sizeof(LONG) * Total, MEMF_ANY);
		if(Pens)
		{
			LONG i,r,g,b,max = CubeSize - 1;

			for(i = 0 ; i < Total ; i++)
				Pens[i] = -1;

			i = 0;

			for(r = 0 ; r < CubeSize ; r++)
			{
				for(g = 0 ; g < CubeSize ; g++)
				{
					for(b = 0 ; b < CubeSize ; b++)
					{
						if((Pens[i++] = ObtainBestPen(VPort -> ColorMap,SPREAD((255 * r) / max),SPREAD((255 * g) / max),SPREAD((255 * b) / max),
							OBP_FailIfBad,	TRUE,
							OBP_Precision,	PRECISION_IMAGE,
						TAG_DONE)) == -1)
						{
							FreeVec(Pens);

							return(NULL);
						}
					}
				}
			}

			return(Pens);
		}
	}

	return(NULL);
}

	/* amiga_open_default(gx_device *dev):
	 *
	 *	Open the default device, i.e. a window on the Workbench screen.
	 */

private int
amiga_open_default(gx_device *dev)
{
	struct Screen *DefaultScreen;

		/* Get a lock on the default public screen. */

	DefaultScreen = LockPubScreen(NULL);
	if(DefaultScreen)
	{
		struct DisplayInfo	DisplayInfo;
		ULONG		 	Mode;

			/* Get the default public screen display mode. */

		Mode = GetVPModeID(&DefaultScreen -> ViewPort);

			/* Inquire display mode information. */

		if(GetDisplayInfoData(NULL,(APTR)&DisplayInfo,sizeof(struct DisplayInfo),DTAG_DISP,Mode))
		{
			LONG	ScreenWidth,
				ScreenHeight;
			LONG	i;
			LONG	Depth;

			if(GfxBase -> LibNode . lib_Version >= 39)
				Depth = GetBitMapAttr(DefaultScreen -> RastPort . BitMap,BMA_DEPTH);
			else
				Depth = DefaultScreen -> RastPort . BitMap -> Depth;

				/* Determine screen view dimensions. */

			if(DefaultScreen -> ViewPort . ColorMap -> cm_vpe)
			{
				struct ViewPortExtra *Extra = DefaultScreen -> ViewPort . ColorMap -> cm_vpe;

				ScreenWidth	= Extra -> DisplayClip . MaxX - Extra -> DisplayClip . MinX + 1;
				ScreenHeight	= Extra -> DisplayClip . MaxY - Extra -> DisplayClip . MinY + 1;
			}
			else
			{
				struct ViewPortExtra *Extra;

				Extra = (struct ViewPortExtra *)GfxLookUp(
					&DefaultScreen -> ViewPort);
				if(Extra)
				{
					ScreenWidth	= Extra -> DisplayClip . MaxX - Extra -> DisplayClip . MinX + 1;
					ScreenHeight	= Extra -> DisplayClip . MaxY - Extra -> DisplayClip . MinY + 1;
				}
				else
				{
					ScreenWidth	= DefaultScreen -> Width;
					ScreenHeight	= DefaultScreen -> Height;
				}
			}

				/* Adjust device to screen resolution. */

			if (!ResolutionSwitch)	/* Has user set -r at command line? */
			{	/* no */

				/* Compute the dots_per_inch resolution from the 
				 * ticks_per_pixel resolution in the display database.
				 * See #define MAGIC_... for a comment.
				 */

				if (DisplayInfo.Resolution.x && DisplayInfo.Resolution.y)
				{
					dev->x_pixels_per_inch = MAGIC_DPI_BASE_VALUE 
						/ DisplayInfo.Resolution.x;
					dev->y_pixels_per_inch = MAGIC_DPI_BASE_VALUE 
						/ DisplayInfo.Resolution.y;

					if (GeometrySwitch)	/* Has user set -g at command line? */
					{	/* yes => update MediaSize to remain consistent */
						gx_device_set_width_height(dev, dev->width, 
							dev->height);
					}
					else
					{	/* no => update width/height to remain consistent */
						gx_device_set_media_size(dev, dev->MediaSize[0], 
							dev->MediaSize[1]);
					}
				}
			}

				/* Allocate a bitmap ready to be used for
				 * rendering.
				 */

			xdev -> super_bitmap = CreateBitMap(xdev -> width, 
				xdev -> height, Depth, BMF_DISPLAYABLE, 
				DefaultScreen -> RastPort . BitMap, FALSE);
			if(xdev -> super_bitmap)
			{
					/* Clear the bitplanes. */

				BltBitMap(xdev -> super_bitmap,0,0,xdev -> super_bitmap,0,0,xdev -> width,xdev -> height,0x00,(1 << xdev -> super_bitmap -> Depth) - 1,NULL);

					/* Create the scroller handles. */

				if(CreateScrollers(dev,DefaultScreen))
				{
					struct IBox ZoomBox;

						/* Set up the window alternate
						 * position.
						 */

					ZoomBox . Left		= 0;
					ZoomBox . Top		= DefaultScreen -> BarHeight + 1;
					ZoomBox . Width		= ScreenWidth;
					ZoomBox . Height	= ScreenHeight - ZoomBox . Top;

						/* Eventually, open the display window. */

					xdev -> window = OpenWindowTags(NULL,
						WA_InnerWidth,		MIN(DefaultScreen->Width / 2, 
							xdev -> width),
						WA_InnerHeight,		MIN(DefaultScreen->Height / 2, 
							xdev -> height),
						WA_CloseGadget,		TRUE,
						WA_DepthGadget,		TRUE,
						WA_SizeGadget,		TRUE,
						WA_SizeBRight,		TRUE,
						WA_SizeBBottom,		TRUE,
						WA_Zoom,		(ULONG)(&ZoomBox),
						WA_DragBar,		TRUE,
						WA_NoCareRefresh,	TRUE,
						WA_GimmeZeroZero,	TRUE,
						WA_RMBTrap,		TRUE,
						WA_SuperBitMap,		(ULONG)(xdev->super_bitmap),
						WA_Gadgets,	(ULONG)(xdev->gadget[VERTICAL_SCROLLER]),
						WA_CustomScreen,	(ULONG)DefaultScreen,
						WA_Title,	(ULONG)"Ghostscript Amiga output window",
						TAG_DONE);
					if(xdev -> window)
					{
						xdev -> temp_rport = CreateTempRPort(
							xdev -> window -> RPort);
						if(xdev -> temp_rport)
						{
							xdev -> temp_array = (UBYTE *)AllocVec(
								(xdev -> window -> WScreen -> Width + 15) & ~15, 
								MEMF_ANY);
							if(xdev -> temp_array)
							{
								const sigset_t trapped = sigmask(SIGINT);
								struct Task *Task;
								long TaskPri;

									/* Don't let anybody interrupt us! */
#ifdef IXEMUL
								sigprocmask(SIG_BLOCK,&trapped,NULL);
#endif
									/* Who's calling? */

								xdev -> main = (struct Process *)FindTask(NULL);
								
									/* Bring the window dispatcher task
									 * to life with +1 priority...
									 */

								Forbid();

								TaskPri = 1 + 
									xdev->main->pr_Task.tc_Node.ln_Pri;
								if (127 < TaskPri)	/* Ouch... */
									TaskPri = 127;	/* ;-) */
								
								Task = CreateTask(
									"Ghostscript window dispatcher", 
									TaskPri, DispatchTask, 8192);
								if(Task)
								{
										/* Cheap... */

									Task -> tc_UserData = dev;

										/* Clear the handshake bit. */

									SetSignal(0,SIG_HANDSHAKE);

										/* Wake it up. */

									Signal(Task,SIG_HANDSHAKE);

										/* Wake the task up. */

									Signal(Task,SIG_WAKEUP);

										/* Wait for the report. */

									Wait(SIG_HANDSHAKE);

										/* Get the result. */

									Task = xdev -> dispatcher;
								}

								Permit();

									/* Unblock signals. */
#ifdef IXEMUL
								sigprocmask(SIG_UNBLOCK,&trapped,NULL);
#endif
									/* Did we succeed in creating
									 * the dispatcher task?
									 */

								if(Task)
								{
									UWORD	MaxValue = 0,
										MinValue = 15000,
										Value,
										R,G,B;

										/* Set the window limits. */

									WindowLimits(xdev -> window,xdev -> window -> BorderLeft + MINIMUM_WIDTH + xdev -> window -> BorderRight,xdev -> window -> BorderTop + MINIMUM_HEIGHT + xdev -> window -> BorderBottom,xdev -> window -> BorderLeft + xdev -> width + xdev -> window -> BorderRight,xdev -> window -> BorderTop + xdev -> height + xdev -> window -> BorderBottom);

										/* Update the sliders. */

									WindowResize(dev);

										/* Look for the darkest and the lightest screen colours. */

									for(i = 0 ; i < MIN(xdev -> window -> WScreen -> ViewPort . ColorMap -> Count,(1 << Depth)) ; i++)
									{
										Value = GetRGB4(xdev -> window -> WScreen -> ViewPort . ColorMap,i);

										R = (Value >> 8) & 0xF;
										G = (Value >> 4) & 0xF;
										B =  Value       & 0xF;

											/* Luminance conversion included */

										Value = R * 299 + G * 588 + B * 113;

										if(Value > MaxValue)
										{
											MaxValue = Value;

											LightPen = i;
										}

										if(Value < MinValue)
										{
											MinValue = Value;

											DarkPen = i;
										}
									}

										/* Fill in the rest. */

									xdev -> rport	= xdev -> window -> RPort;

										/* Does the display support
										 * at least eight colours?
										 */

									if(Depth >= 3)
									{
										LONG cube_size,max;

											/* Set up a fitting colour cube. */

										for(cube_size = 6 ; cube_size >= 2 ; cube_size--)
										{
											if((max = cube_size * cube_size * cube_size) <= 1 << Depth)
												break;
										}

											/* Got enough colours? */

										if(cube_size != 1)
										{
											LONG *Pens;

												/* Try to grab the cube colours,
												 * making a colour display.
												 */

											Pens = AllocatePens(&xdev -> window -> WScreen -> ViewPort,cube_size);
											if(Pens)
												set_colour_device((gx_device_amiga *)dev,cube_size,Pens);
										}
									}

									SetBPen(xdev -> rport,0);
									SetDrMd(xdev -> rport,JAM2);

									amiga_set_pen(dev,DarkPen);

									UnlockPubScreen(NULL,DefaultScreen);

									return(0);
								}
								else
									eprintf("Ghostscript: failed to create dispatcher task\n");
							}
							else
								eprintf("Ghostscript: failed to create temporary line buffer\n");
						}
						else
							eprintf("Ghostscript: failed to create temporary raster port\n");
					}
					else
						eprintf("Ghostscript: failed to open window\n");
				}
				else
					eprintf("Ghostscript: failed to allocate scrollers\n");
			}
			else
				eprintf("Ghostscript: failed to allocate bitmap\n");
		}
		else
			eprintf("Ghostscript: failed to get display mode information\n");

		UnlockPubScreen(NULL,DefaultScreen);
	}
	else
		eprintf("Ghostscript: failed to lock default public screen\n");

	return_error(gs_error_unknownerror);
}

private int
amiga_open_custom(gx_device *dev)
{
	UBYTE	Buffer[DISPLAYNAMELEN + 1] = "";	/* see graphics/displayinfo.h */
	ULONG	ScreenID	= INVALID_ID;
	BOOL	DontTouch	= FALSE;

		/* Has user supplied the ID of a Display Mode? */

	if (1 == sscanf(xdev->display_mode, "0x%lx", &ScreenID)
		|| 1 == sscanf(xdev->display_mode, "0X%lx", &ScreenID))
	{	/* yes => check wether it is available */

		if (ModeNotAvailable(ScreenID))
		{
			ScreenID = INVALID_ID;
		}
		else
		{
			DontTouch = TRUE;	/* Don't overwrite the env. variable. */
		}
	}
	else
	{	/* no */

			/* Has user supplied the name of a Display Mode? */

		if ('\0' != xdev->display_mode[0])
		{	/* yes */

			strcpy(Buffer, xdev->display_mode);
		}
		else
		{	/* no => try to read it from the environment variable */

			GetVar("GS_DISPLAYMODE", Buffer, sizeof (Buffer), NULL);
		}

		if ('\0' != Buffer[0])
		{
			UBYTE PatternBuffer[(sizeof (Buffer) * 2) + 2];
	
				/* Set up the search pattern. */
	
			if (0 <= ParsePatternNoCase(Buffer, PatternBuffer, 
				sizeof (PatternBuffer)))
			{
				ULONG CurrentID = INVALID_ID;
	
					/* Scan the entire list. */
	
				while (INVALID_ID != (CurrentID = NextDisplayInfo(CurrentID)))
				{
					struct NameInfo	NameInfo;
	
						/* Is this mode available? */
	
					if (ModeNotAvailable(CurrentID))
					{	/* no => continue loop */
						continue;
					}
	
						/* Get the name information. */
	
					if (GetDisplayInfoData(NULL, (UBYTE *)&NameInfo, 
						sizeof (struct NameInfo), DTAG_NAME, CurrentID))
					{
							/* Does the mode name match the pattern given? */
						if (MatchPatternNoCase(PatternBuffer, NameInfo.Name))
						{
							ScreenID = CurrentID;
	
								/* Don't overwrite the env. variable. */
	
							DontTouch = TRUE;
							break;
						}
					}
				}
			}
		}
	}

	if (INVALID_ID == ScreenID && AslBase)
	{
		struct ScreenModeRequester *ScreenModeRequester;

		ScreenModeRequester = (struct ScreenModeRequester *)
			AllocAslRequestTags(ASL_ScreenModeRequest, TAG_DONE);
		if (ScreenModeRequester)
		{
			if (AslRequestTags(ScreenModeRequester,
				ASLSM_TitleText,	(ULONG)"Select Ghostscript Display Mode",
				ASLSM_MinDepth,		1,
				ASLSM_MaxDepth,		8,
				TAG_DONE))
			{
				ScreenID = ScreenModeRequester->sm_DisplayID;
				FreeAslRequest(ScreenModeRequester);
			}
			else
			{
					/* Check wether any error occured (V38 only) */
				
				if (38 <= AslBase->lib_Version)
				{
					if (0 != IoErr())
					{	/* yes => print additional error message */
						eprintf("Ghostscript: failed to display screenmode "
							"requester\n");
					}
				}
				 
				/* User cancelled or IoErr() => fall back to default */

				FreeAslRequest(ScreenModeRequester);
				return (amiga_open_default(dev));
			}
		}
	}

		/* Is selected mode available? */

	if (ModeNotAvailable(ScreenID))
	{	/* no => fall back to default */
		return (amiga_open_default(dev));
	}
	else
	{	/* yes => try to open the screen */
		int result = amiga_open(dev, ScreenID);

			/* If successful store the name of the
			 * screen mode selected.
			 */

		if(!result && !DontTouch)
		{
			struct NameInfo	NameInfo;

			if (GetDisplayInfoData(NULL, (UBYTE *)&NameInfo, 
				sizeof (struct NameInfo), DTAG_NAME, ScreenID))
			{
				SetVar("GS_DISPLAYMODE", NameInfo.Name, -1, NULL);
			}
		}

		return (result);
	}
}

	/* amiga_open_printer(gx_device *dev):
	 *
	 *	Open the printer device.
	 */

private int
amiga_open_printer(gx_device *dev)
{
	xdev->port = CreateMsgPort();
	if (xdev->port)
	{
		xdev->printer = (struct IODRPReq *)CreateIORequest(xdev->port, 
			sizeof(struct IODRPReq));
		if (xdev->printer)
		{
			if (!OpenDevice("printer.device", 0, 
				(struct IORequest *)xdev->printer, 0))
			{
				struct PrinterData *PD;
				struct PrinterExtendedData *PED;
				struct Preferences *Prefs;

					/* Get the printer internal data. */
					
				PD = (struct PrinterData *)xdev->printer->io_Device;
				PED	= &PD->pd_SegmentData->ps_PED;
				Prefs = &PD->pd_Preferences;

					/* Adjust device to current printer resolution. */

				if (!ResolutionSwitch)	/* Has user set -r at command line? */
				{	/* no */
					dev->x_pixels_per_inch = (float)PED->ped_XDotsInch;
					dev->y_pixels_per_inch = (float)PED->ped_YDotsInch;
		
					if (GeometrySwitch)	/* Has user set -g at command line? */
					{	/* yes => update MediaSize to remain consistent */
						gx_device_set_width_height(dev, dev->width, 
							dev->height);
					}
					else
					{	/* no => update width/height to remain consistent */
						gx_device_set_media_size(dev, dev->MediaSize[0], 
							dev->MediaSize[1]);
					}
				}

				xdev->rport = (struct RastPort *)AllocVec(
					sizeof(struct RastPort), MEMF_ANY);
				if (xdev->rport)
				{
					const sigset_t	trapped = sigmask(SIGINT);
					struct BitMap	DummyBitMap;
					UWORD		DummyLine[12];

					InitRastPort(xdev -> rport);

						/* Compensate for the non-printable are of the page:
						 * 
						 * Unfortunately the standard Amiga printer drivers 
						 * are too poorly designed for that matter and do just 
						 * provide MaxXDots/MaxYDots, which is obviously not 
						 * enough to place the image correctly on the paper.
						 * Therefore, a good approximation seems to be to 
						 * substract half of these values on either side of
						 * the bitmap. Then a SPECIAL_NOPRINT dump is 
						 * performed to see what the actual printsize in 
						 * printerpixels would be. If this reveals, that the
						 * user has set preferences to produce a smaller
						 * image the margins will be ignored for the real dump.
						 * (Of course, one could compute all this without the
						 * NOPRINT dump, it's just more convenient this way 
						 * and blames the OS for all its buggy calculations ;-)
						 *
						 * In general this provides a policy which tries to do
						 * a 1:1 dump prior to exact placement (which rules if
						 * the real non-printable margins of the printer don't
						 * fit to the estimated halves on either side).
						 *
						 * One should probably use the gs printer drivers 
						 * anyway if accuracy matters!
						 */

/* NOTE: Unfortunately the printer device corrects the aspect ratio based on 
 * a ModeID instead of just the supplied dimensions when a dump without 
 * SPECIAL_ASPECT is requested and ABSOLUTE_DIMENSIONS is set in preferences. 
 * Therefore it seems best to supply a ModeID which fits to the default gs 
 * device settings as well as to many printers (1:1); this may lead to wrong 
 * but expectable results in case the device resolution has a different aspect 
 * ratio.
 * TODO: Maybe the device settings could be 'normalized' to 1:1 to circumvent 
 * this problem.
 */

					xdev->printer->io_Command	= PRD_DUMPRPORT;
					xdev->printer->io_RastPort	= xdev->rport;
					xdev->printer->io_Modes		= VGAPRODUCT_KEY;	/* 1:1 */
					xdev->printer->io_SrcX	= 
						MAX((LONG)(dev->width - PED->ped_MaxXDots), 0) / 2;
					xdev->printer->io_SrcY	= PED->ped_MaxYDots ?
						MAX((LONG)(dev->height - PED->ped_MaxYDots), 0) / 2 
						: 0;
					xdev->printer->io_SrcWidth	= 
						MIN(dev->width, PED->ped_MaxXDots);
					xdev->printer->io_SrcHeight	= PED->ped_MaxYDots ? 
						MIN(dev->height, PED->ped_MaxYDots) : dev->height;
					xdev->printer->io_DestCols	= xdev->printer->io_SrcWidth;
					xdev->printer->io_DestRows	= xdev->printer->io_SrcHeight;
					xdev->printer->io_Special	= (SPECIAL_NOPRINT);

						/* Cook up a dummy bitmap to keep
						 * `smart' drivers from complaining.
						 */

					InitBitMap(&DummyBitMap, 12, dev->width, dev->height);

					DummyBitMap.Planes[0] = (PLANEPTR)&DummyLine;

					xdev->rport->BitMap = &DummyBitMap;

						/* Don't let them stop us now! */
#ifdef IXEMUL
					sigprocmask(SIG_BLOCK,&trapped,NULL);
#endif
						/* Ask for it... */

					if(!DoIO((struct IORequest *)xdev -> printer))
					{
						LONG				 Depth,
										 NumColours,
										 CubeSize;

							/* Unblock ^C signal. */
#ifdef IXEMUL
						sigprocmask(SIG_UNBLOCK,&trapped,NULL);
#endif
							 /* Provide everything to perform the real dump:
							  *
							  * Check wether preference settings leaded to
							  * an image smaller than the largest possible one.
							  * If this is the case the margins determined
							  * above are ignored, as the image will most 
							  * likely fit within the printable area of the
							  * page (except for those rare cases where a
							  * printout is requested whith boundaries just
							  * smaller than the largest possible ones by 
							  * values within the determined margins).
							  */

						if (xdev->printer->io_DestCols 
							< xdev->printer->io_SrcWidth)
						{	/* reset dimensions to print the entire image */
							xdev->printer->io_SrcX	= 0;
							xdev->printer->io_SrcWidth	= xdev->width;
						}
						if (xdev->printer->io_DestRows 
							< xdev->printer->io_SrcHeight)
						{	/* reset dimensions to print the entire image */
							xdev->printer->io_SrcY	= 0;
							xdev->printer->io_SrcHeight	= xdev->height;
						}
						xdev->printer->io_DestCols	= 
							xdev->printer->io_SrcWidth;
						xdev->printer->io_DestRows	= 
							xdev->printer->io_SrcHeight;
						xdev->printer->io_Special	&= ~(SPECIAL_NOPRINT);

							/* Set up the default colour values. */

						if(Prefs -> PrintShade == SHADE_BW)
						{
							Depth		= 1;
							NumColours	= 2;
							CubeSize	= 0;
						}
						else
						{
							Depth		= 12;
							NumColours	= 4096;
							CubeSize	= 16;
						}

							/* Try to allocate a suitable bitmap.
							 * If an allocation fails, rescale the
							 * colour cube and bitmap depth and
							 * retry. Minimum are eight colours.
							 */

						do
						{
								/* Try to allocate the raster... */

							xdev->bitmap = CreateBitMap(xdev->width, 
								xdev->height, Depth, NULL, NULL, TRUE);
							if (!(xdev->bitmap))
							{
									/* Any chance to rescale the cube? */

								if(Depth < 2)
									break;
								else
								{
										/* One plane less... */

									Depth--;

										/* Rescale the cube. */

									while(CubeSize >= 2)
									{
										NumColours = CubeSize * CubeSize 
											* CubeSize;
										if(NumColours <= (1 << Depth))
											break;
										else
											CubeSize--;
									}

										/* Less than eight colours? */

									if(CubeSize < 2)
										break;
								}
							}
						}
						while(!xdev -> bitmap);

							/* Got the bitmap? */

						if(xdev -> bitmap)
						{
								/* Allocate a suitable colour map. */

							xdev -> colormap = GetColorMap(NumColours);
							if(xdev -> colormap)
							{
									/* Black & white only? */

								if(NumColours == 2)
								{
									SetRGB4CM(xdev -> colormap,0,0x0,0x0,0x0);
									SetRGB4CM(xdev -> colormap,1,0xF,0xF,0xF);
								}
								else
								{
									LONG i = 0,r,g,b,max = CubeSize - 1;

										/* Fill in the colour cube. */

									for(r = 0 ; r < CubeSize ; r++)
									{
										for(g = 0 ; g < CubeSize ; g++)
										{
											for(b = 0 ; b < CubeSize ; b++)
												SetRGB4CM(xdev->colormap, i++, 
													(15 * r) / max, 
													(15 * g) / max, 
													(15 * b) / max);
										}
									}

									set_colour_printer_device(
										(gx_device_amiga *)dev, CubeSize);
								}

								xdev->printer->io_ColorMap = xdev->colormap;
								xdev->rport->BitMap = xdev->bitmap;

								return(0);
							}
							else
								eprintf("Ghostscript: failed to allocate "
									"colour map\n");
						}
						else
						{
							char buffer[256];

							sprintf(buffer, "Ghostscript: failed to allocate "
								"raster (wanted %ld, largest %ld)\n", 
								(dev->width + 15) / 8 * dev->height * Depth, 
								AvailMem(MEMF_ANY | MEMF_LARGEST));

							eprintf(buffer);
						}
					}
					else
					{
#ifdef IXEMUL
						sigprocmask(SIG_UNBLOCK,&trapped,NULL);
#endif
						PrintPrinterError("Ghostscript: failed to query "
							"printer page size ", xdev->printer->io_Error);
					}
				}
				else
					eprintf("Ghostscript: failed to allocate raster port\n");
			}
			else
			{
				PrintPrinterError("Ghostscript: printer.device ", 
					xdev->printer->io_Error);
			}
		}
		else
			eprintf("Ghostscript: failed to allocate device driver\n");
	}
	else
		eprintf("Ghostscript: failed to create io port\n");

	return_error(gs_error_unknownerror);
}

	/* amiga_output_page_printer(gx_device *dev,int,int):
	 *
	 *	Send a bitmap to the printer.
	 */

private int
amiga_output_page_printer(gx_device *dev,int num_copies,int flush)
{
	const sigset_t trapped = sigmask(SIGINT);
	int result = gs_error_unknownerror, i;
	ULONG Signals;

		/* We cannot possibly allow being interrupted in the middle
		 * of a raster dump!
		 */
#ifdef IXEMUL
	sigprocmask(SIG_BLOCK,&trapped,NULL);
#endif
	for(i = 0 ; i < num_copies ; i++)
	{
		SetSignal(0,SIGBREAKF_CTRL_C | (1L << xdev -> port -> mp_SigBit));

		SendIO((struct IORequest *)xdev -> printer);

		Signals = Wait(SIGBREAKF_CTRL_C | (1L << xdev -> port -> mp_SigBit));

		if(Signals & SIGBREAKF_CTRL_C)
		{
			if(!CheckIO((struct IORequest *)xdev -> printer))
				AbortIO((struct IORequest *)xdev -> printer);

			WaitIO((struct IORequest *)xdev -> printer);

			eprintf("Ghostscript: printing aborted\n");

			result = gs_error_unknownerror;

			break;
		}

		if(Signals & (1L << xdev -> port -> mp_SigBit))
		{
			if(WaitIO((struct IORequest *)xdev -> printer))
			{
				result = gs_error_unknownerror;

				PrintPrinterError("Ghostscript: failed to print raster ", 
					xdev->printer->io_Error);

				break;
			}
			else
				result = 0;
		}
	}
#ifdef IXEMUL
	sigprocmask(SIG_UNBLOCK,&trapped,NULL);
#endif

	if (result)
		return_error(result);
	else
		return (0);
}

	/* amiga_close_printer(gx_device *dev):
	 *
	 *	Close the printer driver.
	 */

private int
amiga_close_printer(gx_device *dev)
{
	if(xdev -> bitmap)
	{
		DeleteBitMap(xdev -> bitmap,TRUE);

		xdev -> bitmap = NULL;
	}

	if(xdev -> rport)
	{
		FreeVec(xdev -> rport);

		xdev -> rport = NULL;
	}

	if(xdev -> colormap)
	{
		FreeColorMap(xdev -> colormap);

		xdev -> colormap = NULL;
	}

	if(xdev -> printer)
	{
		if(xdev -> printer -> io_Device)
			CloseDevice((struct IORequest *)xdev -> printer);

		DeleteIORequest(xdev -> printer);

		xdev -> printer = NULL;
	}

	if(xdev -> port)
	{
		DeleteMsgPort(xdev -> port);

		xdev -> port = NULL;
	}

	return(0);
}

	/* amiga_get_bits(gx_device *dev,int y,byte *str,byte **actual_data):
	 *
	 *	Read the raster bits into a buffer.
	 */

private int
amiga_get_bits(gx_device *dev,int y,byte *str,byte **actual_data)
{
	if(y < 0 || y > xdev -> height)
		return_error(gs_error_unknownerror);
	else
	{
		if(actual_data)
			*actual_data = (byte *)(xdev -> bitmap -> Planes[0] + xdev -> bitmap -> BytesPerRow * y);
		else
			memcpy(str,xdev -> bitmap -> Planes[0] + xdev -> bitmap -> BytesPerRow * y,xdev -> bitmap -> BytesPerRow);

		return(0);
	}
}

	/* amiga_open(gx_device *dev,ULONG Mode):
	 *
	 *	Open a custom screen.
	 */

private int
amiga_open(gx_device *dev,ULONG Mode)
{
	struct DisplayInfo	DisplayInfo;
	struct DimensionInfo	DimensionInfo;

		/* Get the display dimensions. */

	if(GetDisplayInfoData(NULL,(APTR)&DisplayInfo,sizeof(struct DisplayInfo),DTAG_DISP,Mode) && GetDisplayInfoData(NULL,(APTR)&DimensionInfo,sizeof(struct DimensionInfo),DTAG_DIMS,Mode))
	{
			/* Two shades only, black & white */

		STATIC struct ColorSpec Colours[] =
		{
			{ 0, 0x0000, 0x0000, 0x0000, },
			{ 1, 0xFFFF, 0xFFFF, 0xFFFF, },
			{ -1, },
		};

		LONG	i,cube_size,max;
		LONG	ScreenDepth;

			/* Start up with a maximum depth display. */

		ScreenDepth = DimensionInfo . MaxDepth;

			/* Check to see whether we will be able to
			 * build a colour display or not.
			 */

		for(cube_size = 6 ; cube_size >= 2 ; cube_size--)
		{
			if((max = cube_size * cube_size * cube_size) <= 1 << ScreenDepth)
				break;
		}

			/* Got enough colours? */

		if(cube_size != 1)
			set_colour_device((gx_device_amiga *)dev,cube_size,NULL);
		else
		{
			ScreenDepth = 1;

			set_mono_device((gx_device_amiga *)dev);
		}

			/* Adjust device to screen resolution. */

		if (!ResolutionSwitch)	/* Has user set -r at command line? */
		{	/* no */
	
				/* Compute the dots_per_inch resolution from the 
				 * ticks_per_pixel resolution in the display database.
				 * See #define MAGIC_... for a comment.
				 */

			if (DisplayInfo.Resolution.x && DisplayInfo.Resolution.y)
			{
				dev->x_pixels_per_inch = MAGIC_DPI_BASE_VALUE 
					/ DisplayInfo.Resolution.x;
				dev->y_pixels_per_inch = MAGIC_DPI_BASE_VALUE 
					/ DisplayInfo.Resolution.y;

				if (GeometrySwitch)	/* Has user set -g at command line? */
				{	/* yes => update MediaSize to remain consistent */
					gx_device_set_width_height(dev, dev->width, 
						dev->height);
				}
				else
				{	/* no => update width/height to remain consistent */
					gx_device_set_media_size(dev, dev->MediaSize[0], 
						dev->MediaSize[1]);
				}
			}
		}

			/* Ensure the computed values will fit on the screen */
			
		if (dev->width < DimensionInfo.MinRasterWidth)
			gx_device_set_width_height(dev, DimensionInfo.MinRasterWidth, 
				dev->height);
		if (dev->width > DimensionInfo.MaxRasterWidth)
			gx_device_set_width_height(dev, DimensionInfo.MaxRasterWidth, 
				dev->height);
		if (dev->height < DimensionInfo.MinRasterHeight)
			gx_device_set_width_height(dev, dev->width, 
				DimensionInfo.MinRasterHeight);
		if (dev->height > DimensionInfo.MaxRasterHeight)
			gx_device_set_width_height(dev, dev->width, 
				DimensionInfo.MaxRasterHeight);

			/* Try to open a custom screen; if this fails, try to
			 * rescale the colour cube and retry.
			 */

		do
		{
			xdev -> screen = OpenScreenTags(NULL,
				SA_Depth,	ScreenDepth,
				SA_Overscan,	OSCAN_TEXT,
				SA_Quiet,	TRUE,
				SA_Behind,	TRUE,
				SA_DisplayID,	Mode,
				SA_Colors,	(ULONG)Colours,
				SA_AutoScroll,	TRUE,
				SA_ShowTitle,	FALSE,
				SA_Title,	(ULONG)"Ghostscript Amiga output screen",
				SA_Width,	dev->width,
				SA_Height,	dev->height,
				TAG_DONE);
			if(!(xdev -> screen))
			{
				if(ScreenDepth < 2)
					break;
				else
				{
					ScreenDepth--;

						/* Check to see whether we will be able to
						 * build a colour display or not.
						 */

					while(cube_size >= 2)
					{
						if((max = cube_size * cube_size * cube_size) <= 1 << ScreenDepth)
							break;
						else
							cube_size--;
					}

						/* Got enough colours? */

					if(cube_size == 1 || ScreenDepth == 1)
					{
							/* Obviously not. */

						ScreenDepth = 1;

						set_mono_device((gx_device_amiga *)dev);
					}
				}
			}
		}
		while(!xdev -> screen);

			/* Did we succeed in opening the screen? */

		if(xdev -> screen)
		{
				/* OpenScreenTags above really shouldn't have changed these,
				 * but better be safe...
				 */
				
			gx_device_set_width_height(dev, xdev->screen->Width, 
				xdev->screen->Height);

			xdev -> window = OpenWindowTags(NULL,
				WA_Left,	0,
				WA_Top,		0,
				WA_Width,	dev -> width,
				WA_Height,	dev -> height,
				WA_Backdrop,	TRUE,
				WA_RMBTrap,	TRUE,
				WA_Borderless,	TRUE,
				WA_CustomScreen,(ULONG)(xdev -> screen),
				TAG_DONE);
			if(xdev -> window)
			{
				xdev -> rport	= xdev -> window -> RPort;
			}
			else
			{
				xdev -> rport	= &xdev -> screen -> RastPort;
			}

				/* Establish defaults. */

			DarkPen		= 0;
			LightPen	= 1;

			SetBPen(xdev -> rport,0);
			SetDrMd(xdev -> rport,JAM2);

				/* Create the temporary drawing area. */

			xdev -> temp_rport = CreateTempRPort(xdev -> rport);
			if(xdev -> temp_rport)
			{
				xdev -> temp_array = (UBYTE *)AllocVec((dev -> width + 15) 
					& ~15, MEMF_ANY);
				if(xdev -> temp_array)
				{
						/* Colour output enabled? */

					if(xdev -> cube_size > 0)
					{
						LONG r,g,b,max = xdev -> cube_size - 1;

						i = 0;

							/* Build a suitable colour map. */

						if(GfxBase -> LibNode . lib_Version >= 39)
						{
							for(r = 0 ; r < xdev -> cube_size ; r++)
							{
								for(g = 0 ; g < xdev -> cube_size ; g++)
								{
									for(b = 0 ; b < xdev -> cube_size ; b++)
										SetRGB32(&xdev -> screen -> ViewPort,i++,SPREAD((255 * r) / max),SPREAD((255 * g) / max),SPREAD((255 * b) / max));
								}
							}
						}
						else
						{
							for(r = 0 ; r < xdev -> cube_size ; r++)
							{
								for(g = 0 ; g < xdev -> cube_size ; g++)
								{
									for(b = 0 ; b < xdev -> cube_size ; b++)
										SetRGB4(&xdev -> screen -> ViewPort,i++,(15 * r) / max,(15 * g) / max,(15 * b) / max);
								}
							}
						}
					}
				}
				else
				{
					eprintf("Ghostscript: failed to allocate temporary line\n");

					return_error(gs_error_unknownerror);
				}
			}
			else
			{
				eprintf("Ghostscript: failed to allocate temporary raster\n");

				return_error(gs_error_unknownerror);
			}

			amiga_set_pen(dev,DarkPen);

			return(0);
		}
		else
			eprintf("Ghostscript: failed to open screen\n");
	}
	else
		eprintf("Ghostscript: failed to get display mode information\n");

	return_error(gs_error_unknownerror);
}

	/* amiga_output_page(gx_device *dev,int,int):
	 *
	 *	Page is not `buffered', just bring screen/window
	 *	to the front.
	 */

private int
amiga_output_page(gx_device *dev,int num_copies,int flush)
{
	if(xdev -> screen)
		ScreenToFront(xdev -> screen);
	else
	{
		if(xdev -> window)
			WindowToFront(xdev -> window);
	}

	return(0);
}

	/* amiga_close(gx_device *dev):
	 *
	 *	Close the screen and free associated resources.
	 */

private int
amiga_close(gx_device *dev)
{
	if(xdev -> dispatcher)
	{
		const sigset_t trapped = sigmask(SIGINT);
#ifdef IXEMUL
		sigprocmask(SIG_BLOCK,&trapped,NULL);
#endif
		Forbid();

		Signal(xdev -> dispatcher,SIG_KILL);

		SetSignal(0,SIG_HANDSHAKE);

		Wait(SIG_HANDSHAKE);

		Permit();
#ifdef IXEMUL
		sigprocmask(SIG_UNBLOCK,&trapped,NULL);
#endif
	}

	if(xdev -> temp_array)
	{
		FreeVec(xdev -> temp_array);

		xdev -> temp_array = NULL;
	}

	if(xdev -> pens)
	{
		LONG i;

		for(i = 0 ; i < xdev -> cube_size * xdev -> cube_size * xdev -> cube_size ; i++)
		{
			if(xdev -> pens[i] != -1)
				ReleasePen(xdev -> window -> WScreen -> ViewPort . ColorMap,xdev -> pens[i]);
		}

		FreeVec(xdev -> pens);

		xdev -> pens = NULL;
	}

	if(xdev -> temp_rport)
	{
		DeleteTempRPort(xdev -> temp_rport);

		xdev -> temp_rport = NULL;
	}

	if(xdev -> window)
	{
		CloseWindow(xdev -> window);

		xdev -> window = NULL;
	}

	DeleteScrollers(dev);

	if(xdev -> super_bitmap)
	{
		DeleteBitMap(xdev -> super_bitmap,FALSE);

		xdev -> super_bitmap = NULL;
	}

	if(xdev -> screen)
	{
		CloseScreen(xdev -> screen);

		xdev -> screen = NULL;
	}

	return(0);
}

	/* amiga_fill_rectangle(gx_device *dev,int x,int y,int w,int h,gx_color_index color):
	 *
	 *	Fill a rectangle with a given colour. This one is simple as it can
	 *	be done with the Amiga graphics primitives.
	 */

private int
amiga_fill_rectangle(gx_device *dev,int x,int y,int w,int h,gx_color_index color)
{
    fit_fill(dev, x, y, w, h);
	if(color != gx_no_color_index)
	{
		amiga_set_pen(dev,color);

		RectFill(xdev -> rport, x, y, (x + w - 1), (y + h - 1));
	}

	return(0);
}

	/* amiga_copy_mono():
	 *
	 *	Copy a monochrome image. This operation requires a bit of work as
	 *	we cannot simply blit the image into the bitmap.
	 */

private int
amiga_copy_mono(gx_device *dev,const UBYTE *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)
{
	LONG i,j;

    fit_copy(dev, base, sourcex, raster, id, x, y, w, h);

	if(zero == gx_no_color_index)
	{
		if(one != gx_no_color_index)
		{
			do
			{
				ReadPixelLine8(xdev -> rport,x,y,w,xdev -> temp_array,xdev -> temp_rport);

				for(i = sourcex, j = 0 ; i < sourcex + w ; i++, j++)
				{
					if(base[i >> 3] & shift[i & 7])
						xdev -> temp_array[j] = one;
				}

				WritePixelLine8(xdev -> rport,x,y,w,xdev -> temp_array,xdev -> temp_rport);

				base += raster;

				y++;
			}
			while(--h);
		}
	}
	else
	{
		if(one == gx_no_color_index)
		{
			do
			{
				ReadPixelLine8(xdev -> rport,x,y,w,xdev -> temp_array,xdev -> temp_rport);

				for(i = sourcex, j = 0 ; i < w + sourcex ; i++, j++)
				{
					if(!(base[i >> 3] & shift[i & 7]))
						xdev -> temp_array[j] = zero;
				}

				WritePixelLine8(xdev -> rport,x,y,w,xdev -> temp_array,xdev -> temp_rport);

				base += raster;

				y++;
			}
			while(--h);
		}
		else
		{
			do
			{
				for(i = sourcex, j = 0 ; i < w + sourcex ; i++, j++)
				{
					if(base[i >> 3] & shift[i & 7])
						xdev -> temp_array[j] = one;
					else
						xdev -> temp_array[j] = zero;
				}

				WritePixelLine8(xdev -> rport,x,y,w,xdev -> temp_array,xdev -> temp_rport);

				base += raster;

				y++;
			}
			while(--h);
		}
	}

	return(0);
}

	/* amiga_copy_color():
	 *
	 *	Copy a color image (oh well...). This is just the same as the
	 *	copy_mono() routine.
	 */

private int
amiga_copy_color(gx_device *dev,const UBYTE *base,int sourcex,int raster,gx_bitmap_id id,int x,int y,int w,int h)
{
	LONG i,j;

    fit_copy(dev, base, sourcex, raster, id, x, y, w, h);

	do
	{
		for(i = sourcex, j = 0 ; i < w + sourcex ; i++, j++)
		{
			if(base[i >> 3] & shift[i & 7])
				xdev -> temp_array[j] = DarkPen;
			else
				xdev -> temp_array[j] = LightPen;
		}

		WritePixelLine8(xdev -> rport,x,y,w,xdev -> temp_array,xdev -> temp_rport);

		base += raster;

		y++;
	}
	while(--h);

	return(0);
}

	/* amiga_draw_line(gx_device *dev,int x0,int y0,int x1,int y1,gx_color_index color):
	 *
	 *	Draw a line between two points. This one is easy as it can be done
	 *	with the Amiga graphics primitives, the only glitch is having to reset
	 *	the last dot to its original colour.
	 */

private int
amiga_draw_line(gx_device *dev,int x0,int y0,int x1,int y1,gx_color_index color)
{
	if(color != gx_no_color_index && (x0 != x1 || y0 != y1))
	{
		LONG pen;

		pen = ReadPixel(xdev -> rport,x1,y1);

		amiga_set_pen(dev,color);

		Move(xdev -> rport,x0,y0);
		Draw(xdev -> rport,x1,y1);

		if(pen == color)
		{
			amiga_set_pen(dev,pen);

			WritePixel(xdev -> rport,x1,y1);
		}
	}

	return(0);
}

	/* amiga_copy_mono_raw():
	 *
	 *	Copy a monochrome image to a bitmap. Just watch the
	 *	astounding number of case switches.
	 */

private int
amiga_copy_mono_raw(gx_device *dev,const UBYTE *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)
{
	LONG i,j,modulo = xdev -> rport -> BitMap -> BytesPerRow;
	UBYTE *line;

    fit_copy(dev, base, sourcex, raster, id, x, y, w, h);

	w += sourcex;

	line = xdev -> rport -> BitMap -> Planes[0] + y * xdev -> rport -> BitMap -> BytesPerRow;

	if(zero == gx_no_color_index)
	{
		if(one != gx_no_color_index)
		{
			if(one)
			{
				do
				{
					for(i = sourcex, j = x ; i < w ; i++, j++)
					{
						if(base[i >> 3] & shift[i & 7])
							line[j >> 3] |= shift[j & 7];
					}

					base += raster;

					line += modulo;
				}
				while(--h);
			}
			else
			{
				do
				{
					for(i = sourcex, j = x ; i < w ; i++, j++)
					{
						if(base[i >> 3] & shift[i & 7])
							line[j >> 3] &= masks[j & 7];
					}
						base += raster;
						line += modulo;
				}
				while(--h);
			}
		}
	}
	else
	{
		if(one == gx_no_color_index)
		{
			if(zero)
			{
				do
				{
					for(i = sourcex, j = x ; i < w ; i++, j++)
					{
						if(!(base[i >> 3] & shift[i & 7]))
							line[j >> 3] |= shift[j & 7];
					}

					base += raster;

					line += modulo;
				}
				while(--h);
			}
			else
			{
				do
				{
					for(i = sourcex, j = x ; i < w ; i++, j++)
					{
						if(!(base[i >> 3] & shift[i & 7]))
							line[j >> 3] &= masks[j & 7];
					}

					base += raster;

					line += modulo;
				}
				while(--h);
			}
		}
		else
		{
			if(one)
			{
				do
				{
					for(i = sourcex, j = x ; i < w ; i++, j++)
					{
						if(base[i >> 3] & shift[i & 7])
							line[j >> 3] |= shift[j & 7];
						else
							line[j >> 3] &= masks[j & 7];
					}

					base += raster;

					line += modulo;
				}
				while(--h);
			}
			else
			{
				do
				{
					for(i = sourcex, j = x ; i < w ; i++, j++)
					{
						if(base[i >> 3] & shift[i & 7])
							line[j >> 3] &= masks[j & 7];
						else
							line[j >> 3] |= shift[j & 7];
					}

					base += raster;

					line += modulo;
				}
				while(--h);
			}
		}
	}

	return(0);
}

	/* amiga_copy_color_raw():
	 *
	 *	Copy a color image (oh well...). This is just the same as the
	 *	copy_mono() routine.
	 */

private int
amiga_copy_color_raw(gx_device *dev,const UBYTE *base,int sourcex,int raster,gx_bitmap_id id,int x,int y,int w,int h)
{
	LONG i,j,modulo = xdev -> rport -> BitMap -> BytesPerRow;
	UBYTE *line;

    fit_copy(dev, base, sourcex, raster, id, x, y, w, h);

	line = xdev -> rport -> BitMap -> Planes[0] + y * xdev -> rport -> BitMap -> BytesPerRow;

	w += sourcex;

	do
	{
		for(i = sourcex, j = x ; i < w ; i++, j++)
		{
			if(base[i >> 3] & shift[i & 7])
				line[j >> 3] |= shift[j & 7];
			else
				line[j >> 3] &= masks[j & 7];
		}

		base += raster;

		line += modulo;
	}
	while(--h);

	return(0);
}

	/* amiga_fill_rectangle_raw():
	 *
	 *	Fill a rectangular area in a bitmap.
	 */

private int
amiga_fill_rectangle_raw(gx_device *dev,int x,int y,int w,int h,gx_color_index color)
{
    fit_fill(dev, x, y, w, h);

	if(color != gx_no_color_index)
	{
		UBYTE *line,startmask,endmask;
		LONG right,mid,modulo = xdev -> rport -> BitMap -> BytesPerRow;

		right	= x + w;
		mid	= (right >> 3) - (x >> 3);
		line	= xdev -> rport -> BitMap -> Planes[0] + y * xdev -> rport -> BitMap -> BytesPerRow + (x >> 3);

		x	&= 7;
		right	&= 7;

		if(color)
		{
			startmask	= 0xFF >> x;
			endmask		= ~(0xFF >> right);

			if(mid)
			{
				UBYTE *ptr;
				int i;

				do
				{
					ptr = line;

					*ptr++ |= startmask;

					i = mid;

					while(--i > 0)
						*ptr++ = 0xFF;

					*ptr |= endmask;

					line += modulo;
				}
				while(--h);
			}
			else
			{
				startmask &= endmask;

				do
				{
					*line |= startmask;

					line += modulo;
				}
				while(--h);
			}
		}
		else
		{
			startmask	= ~(0xFF >> x);
			endmask		= 0xFF >> right;

			if(mid)
			{
				UBYTE *ptr;
				LONG i;

				do
				{
					ptr = line;

					*ptr++ &= startmask;

					i = mid;

					while(--i > 0)
						*ptr++ = 0x00;

					*ptr &= endmask;

					line += modulo;
				}
				while(--h);
			}
			else
			{
				startmask |= endmask;

				do
				{
					*line &= startmask;

					line += modulo;
				}
				while(--h);
			}
		}
	}

	return(0);
}

	/* amiga_draw_line_raw():
	 *
	 *	Draw a hair line, your basic DDA algorithm;
	 *	keep your fingers crossed.
	 */

private int
amiga_draw_line_raw(gx_device *dev,int x,int y,int x1,int y1,gx_color_index color)
{
	if(color != gx_no_color_index && (x != x1 || y != y1))
	{
		short xstep,ystep,dx,dy,diff,modulo;
		UBYTE *line,*plane,pen;
		LONG last;

		modulo	= xdev -> rport -> BitMap -> BytesPerRow;
		plane	= xdev -> rport -> BitMap -> Planes[0];

		line	= &plane[y1 * modulo];
		last	= y1;
		pen	= line[x1 >> 3] & (x1 & 7);

		dx = x1 - x;
		dy = y1 - y;

		if(dx < 0)
		{
			dx = -dx;
			dy = -dy;

			x = x1;
			y = y1;
		}

		if(y != last)
			line = &plane[(last = y) * modulo];

		if(color)
		{
			line[x >> 3] |= shift[x & 7];

			xstep = ystep = 0;

			if(dy < 0)
			{
				if(dx > -dy)
				{
					diff = -dx / 2;

					do
					{
						xstep++;

						if(diff > 0)
						{
							ystep--;

							diff = diff - dy - dx;
						}
						else
							diff -= dy;

						{
							LONG x1 = x + xstep,y1 = y + ystep;

							if(y1 != last)
								line = &plane[(last = y1) * modulo];

							line[x1 >> 3] |= shift[x1 & 7];
						}
					}
					while(xstep < dx);
				}
				else
				{
					if(dx == -dy)
						diff = 0;
					else
						diff = -dy / 2;

					do
					{
						ystep--;

						if(diff > 0)
							diff -= dx;
						else
						{
							xstep++;

							diff = diff - dy - dx;
						}

						{
							LONG x1 = x + xstep,y1 = y + ystep;

							if(y1 != last)
								line = &plane[(last = y1) * modulo];

							line[x1 >> 3] |= shift[x1 & 7];
						}
					}
					while(ystep > dy);
				}
			}
			else
			{
				if(dx > dy)
				{
					diff = -dx / 2;

					do
					{
						xstep++;

						if(diff > 0)
						{
							ystep++;

							diff = diff + dy - dx;
						}
						else
							diff += dy;

						{
							LONG x1 = x + xstep,y1 = y + ystep;

							if(y1 != last)
								line = &plane[(last = y1) * modulo];

							line[x1 >> 3] |= shift[x1 & 7];
						}
					}
					while(xstep < dx);
				}
				else
				{
					if(dx == dy)
						diff = 0;
					else
						diff = dy / 2;

					do
					{
						ystep++;

						if(diff > 0)
							diff -= dx;
						else
						{
							xstep++;

							diff = diff + dy - dx;
						}

						{
							LONG x1 = x + xstep,y1 = y + ystep;

							if(y1 != last)
								line = &plane[(last = y1) * modulo];

							line[x1 >> 3] |= shift[x1 & 7];
						}
					}
					while(ystep < dy);
				}
			}

			if(!pen)
			{
				if(y1 != last)
					line = &plane[(last = y1) * modulo];

				line[x1 >> 3] &= masks[x1 & 7];
			}
		}
		else
		{
			line[x >> 3] &= masks[x & 7];

			xstep = ystep = 0;

			if(dy < 0)
			{
				if(dx > -dy)
				{
					diff = -dx / 2;

					do
					{
						xstep++;

						if(diff > 0)
						{
							ystep--;

							diff = diff - dy - dx;
						}
						else
							diff -= dy;

						{
							LONG x1 = x + xstep,y1 = y + ystep;

							if(y1 != last)
								line = &plane[(last = y1) * modulo];

							line[x1 >> 3] &= masks[x1 & 7];
						}
					}
					while(xstep < dx);
				}
				else
				{
					if(dx == -dy)
						diff = 0;
					else
						diff = -dy / 2;

					do
					{
						ystep--;

						if(diff > 0)
							diff -= dx;
						else
						{
							xstep++;

							diff = diff - dy - dx;
						}

						{
							LONG x1 = x + xstep,y1 = y + ystep;

							if(y1 != last)
								line = &plane[(last = y1) * modulo];

							line[x1 >> 3] &= masks[x1 & 7];
						}
					}
					while(ystep > dy);
				}
			}
			else
			{
				if(dx > dy)
				{
					diff = -dx / 2;

					do
					{
						xstep++;

						if(diff > 0)
						{
							ystep++;

							diff = diff + dy - dx;
						}
						else
							diff += dy;

						{
							LONG x1 = x + xstep,y1 = y + ystep;

							if(y1 != last)
								line = &plane[(last = y1) * modulo];

							line[x1 >> 3] &= masks[x1 & 7];
						}
					}
					while(xstep < dx);
				}
				else
				{
					if(dx == dy)
						diff = 0;
					else
						diff =  dy / 2;

					do
					{
						ystep++;

						if(diff > 0)
							diff -= dx;
						else
						{
							xstep++;

							diff = diff + dy - dx;
						}

						{
							LONG x1 = x + xstep,y1 = y + ystep;

							if(y1 != last)
								line = &plane[(last = y1) * modulo];

							line[x1 >> 3] &= masks[x1 & 7];
						}
					}
					while(ystep < dy);
				}
			}

			if(pen)
			{
				if(y1 != last)
					line = &plane[(last = y1) * modulo];

				line[x1 >> 3] |= pen;
			}
		}
	}

	return(0);
}

	/* amiga_open_ilbm(gx_device *dev):
	 *
	 *	Open the ilbm device.
	 */

private int
amiga_open_ilbm(gx_device *dev)
{
	xdev -> rport = (struct RastPort *)AllocVec(sizeof(struct RastPort),MEMF_ANY);
	if(xdev -> rport)
	{
		InitRastPort(xdev -> rport);

		xdev -> bitmap = (struct BitMap *)AllocVec(sizeof(struct BitMap),MEMF_ANY);
		if(xdev -> bitmap)
		{
			InitBitMap(xdev -> bitmap,1,xdev -> width,xdev -> height);

			xdev -> bitplane = AllocVec(xdev -> bitmap -> Rows * xdev -> bitmap -> BytesPerRow,MEMF_ANY | MEMF_CLEAR);
			if(xdev -> bitplane)
			{
				xdev -> bitmap -> Planes[0]	= xdev -> bitplane;
				xdev -> rport -> BitMap		= xdev -> bitmap;
				xdev -> page_count		= 1;

				DarkPen		= 0;
				LightPen	= 1;

				return(0);
			}
			else
			{
				char buffer[256];

				sprintf(buffer,"Ghostscript: failed to allocate raster "
					"(wanted %ld, largest %ld)\n", 
					(long)(xdev->bitmap->Rows * xdev->bitmap->BytesPerRow),
					AvailMem(MEMF_ANY | MEMF_LARGEST));

				eprintf(buffer);
			}
		}
		else
			eprintf("Ghostscript: failed to allocate bitmap\n");
	}
	else
		eprintf("Ghostscript: failed to allocate raster port\n");

	return_error(gs_error_unknownerror);
}

	/* amiga_output_page_ilbm(gx_device *dev,int,int):
	 *
	 *	Send a bitmap to an IFF-ILBM file.
	 */

private int
amiga_output_page_ilbm(gx_device *dev,int num_copies,int flush)
{
	const sigset_t trapped = sigmask(SIGINT);
	char buffer[270];

	sprintf(buffer,"%s_%04d.ilbm",xdev -> file_name,xdev -> page_count);

	fprintf(stdout,"\n\033[ASaving page Nš%d to \"%s\"...\033[K",xdev -> page_count,buffer);
	fflush(stdout);
#ifdef IXEMUL
	sigprocmask(SIG_BLOCK,&trapped,NULL);
#endif
	if(SaveBitMap(buffer,xdev -> bitmap,xdev -> width,xdev -> height,(UWORD)xdev -> x_pixels_per_inch,xdev -> y_pixels_per_inch))
	{
		fprintf(stdout,"\n\033[APage saved to file \"%s\".\033[K\n",buffer);

		xdev -> page_count++;

#ifdef IXEMUL
		sigprocmask(SIG_UNBLOCK,&trapped,NULL);
#endif

		return (0);
	}
	else
	{
		eprintf("\n\033[AGhostscript: error saving page\033[K");

#ifdef IXEMUL
		sigprocmask(SIG_UNBLOCK,&trapped,NULL);
#endif

		return_error(gs_error_unknownerror);
	}
}

	/* amiga_close_ilbm(gx_device *dev):
	 *
	 *	Close the ilbm driver.
	 */

private int
amiga_close_ilbm(gx_device *dev)
{
	if(xdev -> bitplane)
	{
		FreeVec(xdev -> bitplane);

		xdev -> bitplane = NULL;
	}

	if(xdev -> bitmap)
	{
		FreeVec(xdev -> bitmap);

		xdev -> bitmap = NULL;
	}

	if(xdev -> rport)
	{
		FreeVec(xdev -> rport);

		xdev -> rport = NULL;
	}

	return(0);
}

private int
amiga_get_params(gx_device *dev,gs_param_list *plist)
{
	int code = gx_default_get_params(dev,plist);
	gs_param_string dms;
	gs_param_string ofs;

	dms.data = (const byte *)xdev->display_mode,
	  dms.size = strlen(xdev->display_mode),
	  dms.persistent = false;

	ofs.data = (const byte *)xdev->file_name,
	  ofs.size = strlen(xdev->file_name),
	  ofs.persistent = false;

	/* Transmit the values. */

	if (code < 0 
		|| (code = param_write_string(plist, "DisplayMode", &dms)) < 0
		|| (code = param_write_string(plist, "OutputFile", &ofs)) < 0
	   )
		return code;

	return 0;
}

private int
amiga_put_params(gx_device *dev,gs_param_list *plist)
{
	int ecode = 0;
	int code;
	const char _ds *param_name;
	gs_param_string dms;
	gs_param_string ofs;
	gs_param_float_array hwra;
	gs_param_int_array hwsa;
	
	switch ( code = param_read_string(plist, (param_name = "DisplayMode"), &dms) )
	{
	case 0:
		if ( dms.size >= (DISPLAYNAMELEN + 1) )	/* see displayinfo.h */
			ecode = gs_note_error(gs_error_limitcheck);
		else if (dms.size >= 11 && '0' == dms.data[0]	/* correct hex val? */
				&& ('x' == dms.data[1] || 'X' == dms.data[1]))
			ecode = gs_note_error(gs_error_limitcheck);
		else
			break;
		goto mide;
	default:
		ecode = code;
mide:		param_signal_error(plist, param_name, ecode);
	case 1:
		dms.data = 0;
		break;
	}

	switch ( code = param_read_string(plist, (param_name = "OutputFile"), &ofs) )
	{
	case 0:
		if ( ofs.size >= 256 )
		  ecode = gs_note_error(gs_error_limitcheck);
		else
		  break;
		goto ofe;
	default:
		ecode = code;
ofe:		param_signal_error(plist, param_name, ecode);
	case 1:
		ofs.data = 0;
		break;
	}

	/* Because the resolution is set after the device has been opened we need
	 * to detect wether user has set -g and/or -r at the command line in 
	 * order to prevent wrong computing of HWSize/MediaSize from HWResolution.
	 * We therefore double the code from gsdparam.c and set global variables
	 * accordingly.
	 * The following code between [BEGIN] and [END ] should always double the 
	 * one in gsdparam.c except those two lines marked with [ADDED].
	 */

/* [BEGIN]: doubled code from gsdparam.c */	 
#define BEGIN_ARRAY_PARAM(pread, pname, pa, psize, e)\
  switch ( code = pread(plist, (param_name = pname), &(pa)) )\
  {\
  case 0:\
	if ( (pa).size != psize )\
	  ecode = gs_note_error(gs_error_rangecheck);\
	else { 
/* The body of the processing code goes here. */
/* If it succeeds, it should do a 'break'; */
/* if it fails, it should set ecode and fall through. */
#define END_ARRAY_PARAM(pa, e)\
	}\
	goto e;\
  default:\
	ecode = code;\
e:	param_signal_error(plist, param_name, ecode);\
  case 1:\
	(pa).data = 0;		/* mark as not filled */\
  }

	/*
	 * The HWResolution, HWSize, and MediaSize parameters interact in
	 * the following way:
	 *	1. Setting HWResolution recomputes HWSize from MediaSize.
	 *	2. Setting HWSize recomputes MediaSize from HWResolution.
	 *	3. Setting MediaSize recomputes HWSize from HWResolution.
	 * If more than one parameter is being set, we apply these rules
	 * in the order 1, 2, 3.  This does the right thing in the most
	 * common case of setting more than one parameter, namely,
	 * setting both HWResolution and HWSize.
	 */

	BEGIN_ARRAY_PARAM(param_read_float_array, "HWResolution", hwra, 2, hwre)
		if ( hwra.data[0] <= 0 || hwra.data[1] <= 0 )
		  ecode = gs_note_error(gs_error_rangecheck);
		else
		{
			ResolutionSwitch = TRUE;	/* [ADDED]: user has set -r */
		  break;
		}
	END_ARRAY_PARAM(hwra, hwre)

	BEGIN_ARRAY_PARAM(param_read_int_array, "HWSize", hwsa, 2, hwsa)
		/* We need a special check to handle the nullpage device, */
		/* whose size is legitimately [0 0]. */
		if ( (hwsa.data[0] <= 0 && hwsa.data[0] != dev->width) ||
		     (hwsa.data[1] <= 0 && hwsa.data[1] != dev->height)
		   )
		  ecode = gs_note_error(gs_error_rangecheck);
#define max_coord (max_fixed / fixed_1)
#if max_coord < max_int
		else if ( hwsa.data[0] > max_coord || hwsa.data[1] > max_coord )
		  ecode = gs_note_error(gs_error_limitcheck);
#endif
#undef max_coord
		else
		{
			GeometrySwitch = TRUE;		/* [ADDED]: user has set -g */
		  break;
		}
	END_ARRAY_PARAM(hwsa, hwse)
/* [END]: doubled code from gsdparam.c */	 

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

	if ( dms.data != 0 )
	  {	if ( dev->is_open )
		  gs_closedevice(dev);
		memcpy(xdev->display_mode, dms.data, dms.size);
		xdev->display_mode[dms.size] = 0;
	  }

	if ( ofs.data != 0 )
	  {
		memcpy(xdev->file_name, ofs.data, ofs.size);
		xdev->file_name[ofs.size] = 0;
	  }
	
	if (code < 0)
		return (code);	
	return (0);	
}

	/* amiga_color_map_rgb_color(gx_device *dev,gx_color_value red,gx_color_value green,gx_color_value blue):
	 *
	 *	Turn an RGB colour into a pen index.
	 */

private gx_color_index
amiga_color_map_rgb_color(gx_device *dev,gx_color_value red,gx_color_value green,gx_color_value blue)
{
	LONG max = xdev -> cube_size - 1,r,g,b;

	r = (max * red)   / gx_max_color_value;
	g = (max * green) / gx_max_color_value;
	b = (max * blue)  / gx_max_color_value;

	return((r * xdev -> cube_size + g) * xdev -> cube_size + b);
}

	/* amiga_color_map_color_rgb(gx_device *dev,gx_color_index color,gx_color_value rgb[3]):
	 *
	 *	Turn a pen index into RGB colour values.
	 */

private int
amiga_color_map_color_rgb(gx_device *dev,gx_color_index color,gx_color_value rgb[3])
{
	LONG i,value,max = xdev -> cube_size - 1;

	for(i = 2 ; i >= 0 ; i--)
	{
		value = color % xdev -> cube_size;

		rgb[i] = (gx_max_color_value * value) / max;

		color /= xdev -> cube_size;
	}

	return(0);
}

	/* amiga_color_map_rgb_color_pen(gx_device *dev,gx_color_value red,gx_color_value green,gx_color_value blue):
	 *
	 *	Turn an RGB colour into a pen index; this routine takes remapped
	 *	pens into account.
	 */

private gx_color_index
amiga_color_map_rgb_color_pen(gx_device *dev,gx_color_value red,gx_color_value green,gx_color_value blue)
{
	LONG max = xdev -> cube_size - 1,r,g,b;

	r = (max * red)   / gx_max_color_value;
	g = (max * green) / gx_max_color_value;
	b = (max * blue)  / gx_max_color_value;

	return(xdev -> pens[(r * xdev -> cube_size + g) * xdev -> cube_size + b]);
}

	/* amiga_color_map_color_rgb_pen(gx_device *dev,gx_color_index color,gx_color_value rgb[3]):
	 *
	 *	Turn a pen index into RGB colour values; this routine takes remapped
	 *	pens into account.
	 */

private int
amiga_color_map_color_rgb_pen(gx_device *dev,gx_color_index color,gx_color_value rgb[3])
{
	LONG i,value,max = xdev -> cube_size - 1;

		/* Find the matching pen... */

	for(i = 0 ; i < xdev -> cube_size * xdev -> cube_size * xdev -> cube_size ; i++)
	{
		if(xdev -> pens[i] == color)
		{
			color = i;

			break;
		}
	}

	for(i = 2 ; i >= 0 ; i--)
	{
		value = color % xdev -> cube_size;

		rgb[i] = (gx_max_color_value * value) / max;

		color /= xdev -> cube_size;
	}

	return(0);
}

	/* amiga_copy_color8():
	 *
	 *	Copy a color image, the source is guaranteed to consist of
	 *	one byte per colour.
	 */

private int
amiga_copy_color8(gx_device *dev,const UBYTE *base,int sourcex,int raster,gx_bitmap_id id,int x,int y,int w,int h)
{
    fit_copy(dev, base, sourcex, raster, id, x, y, w, h);

	base += sourcex;

	if(w > xdev -> width)
		w = xdev -> width;

	do
	{
		CopyMem((UBYTE *)base,xdev -> temp_array,w);

		WritePixelLine8(xdev -> rport,x,y++,w,xdev -> temp_array,xdev -> temp_rport);

		base += raster;
	}
	while(--h);

	return(0);
}

	/* amiga_copy_mono_raw_color():
	 *
	 *	Copy a monochrome image to a bitmap.
	 */

private int
amiga_copy_mono_raw_color(gx_device *dev,const UBYTE *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)
{
    fit_copy(dev, base, sourcex, raster, id, x, y, w, h);

	if(zero != gx_no_color_index && one != gx_no_color_index)
	{
		PLANEPTR line[12];
		LONG i,j,k,modulo = xdev -> rport -> BitMap -> BytesPerRow,depth = xdev -> rport -> BitMap -> Depth;

		for(i = 0 ; i < depth ; i++)
			line[i] = xdev -> rport -> BitMap -> Planes[i] + y * modulo;

		w += sourcex;

		if(zero == gx_no_color_index)
		{
			do
			{
				for(i = sourcex, j = x ; i < w ; i++, j++)
				{
					if(base[i >> 3] & shift[i & 7])
					{
						for(k = 0 ; k < depth ; k++)
						{
							if(one & (1 << k))
								line[k][j >> 3] |= shift[j & 7];
							else
								line[k][j >> 3] &= masks[j & 7];
						}
					}
				}

				base += raster;

				for(k = 0 ; k < depth ; k++)
					line[k] += modulo;
			}
			while(--h);
		}
		else
		{
			if(one == gx_no_color_index)
			{
				do
				{
					for(i = sourcex, j = x ; i < w ; i++, j++)
					{
						if(base[i >> 3] & shift[i & 7])
						{
							for(k = 0 ; k < depth ; k++)
							{
								if(zero & (1 << k))
									line[k][j >> 3] |= shift[j & 7];
								else
									line[k][j >> 3] &= masks[j & 7];
							}
						}
					}

					base += raster;

					for(k = 0 ; k < depth ; k++)
						line[k] += modulo;
				}
				while(--h);
			}
			else
			{
				do
				{
					for(i = sourcex, j = x ; i < w ; i++, j++)
					{
						if(base[i >> 3] & shift[i & 7])
						{
							for(k = 0 ; k < depth ; k++)
							{
								if(one & (1 << k))
									line[k][j >> 3] |= shift[j & 7];
								else
									line[k][j >> 3] &= masks[j & 7];
							}
						}
						else
						{
							for(k = 0 ; k < depth ; k++)
							{
								if(zero & (1 << k))
									line[k][j >> 3] |= shift[j & 7];
								else
									line[k][j >> 3] &= masks[j & 7];
							}
						}
					}

					base += raster;

					for(k = 0 ; k < depth ; k++)
						line[k] += modulo;
				}
				while(--h);
			}
		}
	}

	return(0);
}

	/* amiga_copy_color_raw_color16():
	 *
	 *	Copy a color image, the source data is guaranteed to consist
	 *	of one word per colour.
	 */

private int
amiga_copy_color_raw_color16(gx_device *dev,const UBYTE *base,int sourcex,int raster,gx_bitmap_id id,int x,int y,int w,int h)
{
	PLANEPTR line[12];
	LONG i,j,k;
	LONG modulo = xdev->rport->BitMap->BytesPerRow;
	LONG depth = xdev->rport->BitMap->Depth;
	UWORD *wordbase = (UWORD *)base;

    fit_copy(dev, base, sourcex, raster, id, x, y, w, h);

	for(i = 0 ; i < depth ; i++)
		line[i] = xdev -> rport -> BitMap -> Planes[i] + y * modulo;

		w += sourcex;

	raster /= 2;

	do
	{
		for(i = sourcex, j = x ; i < w ; i++, j++)
		{
			for(k = 0 ; k < depth ; k++)
			{
				if(wordbase[i] & (1 << k))
					line[k][j >> 3] |= shift[j & 7];
				else
					line[k][j >> 3] &= masks[j & 7];
			}
		}

		wordbase += raster;

		for(k = 0 ; k < depth ; k++)
			line[k] += modulo;
	}
	while(--h);

	return(0);
}

	/* amiga_fill_rectangle_raw_color():
	 *
	 *	Fill a rectangular area in a bitmap.
	 */

private int
amiga_fill_rectangle_raw_color(gx_device *dev,int x,int y,int w,int h,gx_color_index color)
{
    fit_fill(dev, x, y, w, h);

	if(color != gx_no_color_index)
	{
		PLANEPTR line[12];
		LONG i,j,right,mid,modulo = xdev -> rport -> BitMap -> BytesPerRow,depth = xdev -> rport -> BitMap -> Depth;

		for(i = 0 ; i < depth ; i++)
			line[i] = xdev -> rport -> BitMap -> Planes[i] + y * modulo + (x >> 3);

		right	= x + w;
		mid	= (right >> 3) - (x >> 3);

		x	&= 7;
		right	&= 7;

		if(mid)
		{
			UBYTE *ptr;

			do
			{
				for(j = 0 ; j < depth ; j++)
				{
					ptr = line[j];

					i = mid;

					if(color & (1 << j))
					{
						*ptr++ |= 0xFF >> x;

						while(--i > 0)
							*ptr++ = 0xFF;

						*ptr |= ~(0xFF >> right);
					}
					else
					{
						*ptr++ &= ~(0xFF >> x);

						while(--i > 0)
							*ptr++ = 0x00;

						*ptr &= 0xFF >> right;
					}

					line[j] += modulo;
				}
			}
			while(--h);
		}
		else
		{
			UBYTE	one_mask	= (0xFF >> x) & ~(0xFF >> right),
				zero_mask	= ~(0xFF >> x) | (0xFF >> right);

			do
			{
				for(j = 0 ; j < depth ; j++)
				{
					if(color & (1 << j))
						*line[j] |= one_mask;
					else
						*line[j] &= zero_mask;

					line[j] += modulo;
				}
			}
			while(--h);
		}
	}

	return(0);
}

	/* amiga_draw_line_raw_color():
	 *
	 *	Draw a hair line, your basic DDA algorithm;
	 *	keep your fingers crossed.
	 */

private int
amiga_draw_line_raw_color(gx_device *dev,int x,int y,int x1,int y1,gx_color_index color)
{
	if(color != gx_no_color_index && (x != x1 || y != y1))
	{
		LONG xstep,ystep,dx,dy,diff,modulo;
		UBYTE *line,*plane,pen;
		LONG last,i,orig_x = x,orig_y = y;

		modulo = xdev -> rport -> BitMap -> BytesPerRow;

		for(i = 0 ; i < xdev -> rport -> BitMap -> Depth ; i++)
		{
			plane	= xdev -> rport -> BitMap -> Planes[i];
			line	= &plane[y1 * modulo];
			last	= y1;
			pen	= line[x1 >> 3] & (x1 & 7);
			x	= orig_x;
			y	= orig_y;

			dx = x1 - x;
			dy = y1 - y;

			if(dx < 0)
			{
				dx = -dx;
				dy = -dy;

				x = x1;
				y = y1;
			}

			if(y != last)
				line = &plane[(last = y) * modulo];

			if(color & (1 << i))
			{
				line[x >> 3] |= shift[x & 7];

				xstep = ystep = 0;

				if(dy < 0)
				{
					if(dx > -dy)
					{
						diff = -dx / 2;

						do
						{
							xstep++;

							if(diff > 0)
							{
								ystep--;

								diff = diff - dy - dx;
							}
							else
								diff -= dy;

							{
								LONG x1 = x + xstep,y1 = y + ystep;

								if(y1 != last)
									line = &plane[(last = y1) * modulo];

								line[x1 >> 3] |= shift[x1 & 7];
							}
						}
						while(xstep < dx);
					}
					else
					{
						if(dx == -dy)
							diff = 0;
						else
							diff = -dy / 2;

						do
						{
							ystep--;

							if(diff > 0)
								diff -= dx;
							else
							{
								xstep++;

								diff = diff - dy - dx;
							}

							{
								LONG x1 = x + xstep,y1 = y + ystep;

								if(y1 != last)
									line = &plane[(last = y1) * modulo];

								line[x1 >> 3] |= shift[x1 & 7];
							}
						}
						while(ystep > dy);
					}
				}
				else
				{
					if(dx > dy)
					{
						diff = -dx / 2;

						do
						{
							xstep++;

							if(diff > 0)
							{
								ystep++;

								diff = diff + dy - dx;
							}
							else
								diff += dy;

							{
								LONG x1 = x + xstep,y1 = y + ystep;

								if(y1 != last)
									line = &plane[(last = y1) * modulo];

								line[x1 >> 3] |= shift[x1 & 7];
							}
						}
						while(xstep < dx);
					}
					else
					{
						if(dx == dy)
							diff = 0;
						else
							diff = dy / 2;

						do
						{
							ystep++;

							if(diff > 0)
								diff -= dx;
							else
							{
								xstep++;

								diff = diff + dy - dx;
							}

							{
								LONG x1 = x + xstep,y1 = y + ystep;

								if(y1 != last)
									line = &plane[(last = y1) * modulo];

								line[x1 >> 3] |= shift[x1 & 7];
							}
						}
						while(ystep < dy);
					}
				}

				if(!pen)
				{
					if(y1 != last)
						line = &plane[(last = y1) * modulo];

					line[x1 >> 3] &= masks[x1 & 7];
				}
			}
			else
			{
				line[x >> 3] &= masks[x & 7];

				xstep = ystep = 0;

				if(dy < 0)
				{
					if(dx > -dy)
					{
						diff = -dx / 2;

						do
						{
							xstep++;

							if(diff > 0)
							{
								ystep--;

								diff = diff - dy - dx;
							}
							else
								diff -= dy;

							{
								LONG x1 = x + xstep,y1 = y + ystep;

								if(y1 != last)
									line = &plane[(last = y1) * modulo];

								line[x1 >> 3] &= masks[x1 & 7];
							}
						}
						while(xstep < dx);
					}
					else
					{
						if(dx == -dy)
							diff = 0;
						else
							diff = -dy / 2;

						do
						{
							ystep--;

							if(diff > 0)
								diff -= dx;
							else
							{
								xstep++;

								diff = diff - dy - dx;
							}

							{
								LONG x1 = x + xstep,y1 = y + ystep;

								if(y1 != last)
									line = &plane[(last = y1) * modulo];

								line[x1 >> 3] &= masks[x1 & 7];
							}
						}
						while(ystep > dy);
					}
				}
				else
				{
					if(dx > dy)
					{
						diff = -dx / 2;

						do
						{
							xstep++;

							if(diff > 0)
							{
								ystep++;

								diff = diff + dy - dx;
							}
							else
								diff += dy;

							{
								LONG x1 = x + xstep,y1 = y + ystep;

								if(y1 != last)
									line = &plane[(last = y1) * modulo];

								line[x1 >> 3] &= masks[x1 & 7];
							}
						}
						while(xstep < dx);
					}
					else
					{
						if(dx == dy)
							diff = 0;
						else
							diff =  dy / 2;

						do
						{
							ystep++;

							if(diff > 0)
								diff -= dx;
							else
							{
								xstep++;

								diff = diff + dy - dx;
							}

							{
								LONG x1 = x + xstep,y1 = y + ystep;

								if(y1 != last)
									line = &plane[(last = y1) * modulo];

								line[x1 >> 3] &= masks[x1 & 7];
							}
						}
						while(ystep < dy);
					}
				}

				if(pen)
				{
					if(y1 != last)
						line = &plane[(last = y1) * modulo];

					line[x1 >> 3] |= pen;
				}
			}
		}
	}

	return(0);
}
