/* -*-C-*-
********************************************************************************
*
* File:         x2jet.c
* RCS:          x2jet.c,v 1.23 89/07/17 12:02:51 lori Exp
* Description:  xpr support for HP LaserJet and PaintJet printers
* Author:       Larry Rupp, HP Graphics Technology Division
* Created:      Fri Jul 15 15:22:26 1988
* Modified:     Thu Sep 15 11:59:34 1988 (Larry Rupp) ler@hpfcler
*               Tue Dec  6 10:04:43 PST 1988 (Marc Ayotte) marca@hp-pcd
* Language:     C
* Package:      N/A
* Status:       Released to MIT
*
* (c) Copyright 1988, Hewlett-Packard Company.
*
********************************************************************************
*/





/********************************************************

Copyright (c) 1988 by Hewlett-Packard Company

Permission to use, copy, modify, and distribute this software
and its documentation for any purpose and without fee is hereby
granted, provided that the above copyright notice appear in all
copies and that both that copyright notice and this permission
notice appear in supporting documentation, and that
Hewlett-Packard not be used in advertising or publicity
pertaining to distribution of the software without specific, written
prior permission.

********************************************************/
/*

Copyright (c) 1988  X Consortium

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

Except as contained in this notice, the name of the X Consortium shall
not be used in advertising or otherwise to promote the sale, use or
other dealings in this Software without prior written authorization
from the X Consortium.

*/


#include <stdio.h>
#include <stdarg.h>
#include <X11/Xlib.h>
#include <X11/XWDFile.h>
#include <X11/Xfuncproto.h>

#include "xpr.h"

#ifdef  NLS16
#ifndef NLS
#define	NLS
#endif
#endif

#ifndef NLS
#define catgets(i, sn,mn,s) (s)
#else /* NLS */
#define NL_SETN 2	/* set number */
#include <nl_types.h>

extern	nl_catd	nlmsg_fd;

#endif /* NLS */

#ifndef	TRUE
#  define	FALSE	0
#  define	TRUE	1
#endif

/* default printable page area (inches) */
#define STDWIDTH 8.0
#define STDHEIGHT 10.5

/* header & trailer character cell size (centipoints) */
#define CHARWIDTH 720
#define CHARHEIGHT 1200

#define XWDHEADERSIZE	(sizeof(XWDFileHeader))
#define XCOLORSIZE  	(sizeof(XColor))


typedef struct { long width, height; } Area;
typedef struct { long x, y; } Location;


static Area limit;	/* image clip limits (dots) */
static Area page;	/* printable page size (centipoints) */

static Location headerloc;	/* centipoint location of header string */
static Location trailerloc;	/* centipoint location of trailer string */
static Location imageloc;	/* centipoint location of image */

static int headerlimit;		/* number of chars which will printed for */
static int trailerlimit;	/*  the image's header/trailer strings    */

static XWDFileHeader xwd_header;

static XColor *xwd_colors;

static char *xwd_image;

static unsigned long Z_pixel_mask;

static int true_scale;

typedef struct {
  unsigned long Rmask, Gmask, Bmask;
  int Rshift, Gshift, Bshift;
} RGBshiftmask;

/* Local prototypes */
static void set_image_limits (  int scale, int density,  enum orientation orient,  Area print_area);
static void set_header_trailer_limits (char *header, char *trailer, long printwidth);
static void set_print_locations (  int scale, int density,  int top, int left,  const char *header, const char *trailer,  enum orientation orient,  int position_on_page);
static int scale_raster (
  int density,
  enum orientation orient,
  Area print_area);
static void scale_and_orient_image (
  int *scale, int *density,
  int width, int height, int left, int top,  /* in 300ths of an inch */
  const char *header, const char *trailer,
  enum orientation *orient,
  int position_on_page,
  enum device device);
static void setup_RGBshiftmask (RGBshiftmask *sm, unsigned long rmask, unsigned long gmask, unsigned long bmask);
static void swap_black_and_white (void);
static void reset_color_mapping (void);
static void prepare_color_mapping (
  int invert, int paintjet,
  unsigned int cutoff,
  FILE *out);
static
void select_grey (int level, int *r, int *g, int *b);
static int load_printer_color (
  long index,
  int nearmatch,
  enum device device);
static int lookup_color_index (long i);
static void select_printer_color (
  long index,
  int *red, int *green, int *blue,
  long *compositeRGB,
  enum device device);
static int color_already_in_printer (long compositeRGB, long *pindex);
static int program_new_printer_color (
  int red, int green, int blue,
  long compositeRGB,
  long *pindex);
static long composite_diff (long x, long y);
static long find_nearest_programmed_color (long compositeRGB);
static void add_index_to_chain (
  long cindex,
  long pindex);
static int load_printer_color_DT (
  long index,
  int nearmatch,
  enum device device);
static int load_line_colors (
  long *line,
  int length, int nearmatch,
  enum device device);
static void download_colors (
  long *line,
  int length,
  enum device device);
static void validate_visual(void);
static void read_xwd_data (FILE *in);
static void write_image_prefix (
  FILE *out,
  int scale, int density,
  const char *header,
  enum device device,
  int position_on_page, int initial_formfeed,
  enum orientation orient,
  float gamma,
  int render,
  int slide);
static void write_image_suffix (
  FILE *out,
  const char *trailer,
  int position_on_page,
  int slide, int render,
  enum device device);
static unsigned long Z_image_pixel (int x, int y);
static unsigned long XY_image_pixel (int x, int y);
static void direct_by_pixel(
  FILE *out,
  long *line,
  int length,
  enum device device);
static void index_by_pixel(
  FILE *out,
  long *line,
  int length);
static void write_raster_line (
  FILE *out,
  int scale,
  enum device device,
  long *line,
  int length);
static void write_portrait_Z_image (
  FILE *out,
  int scale,
  enum device device);
static void write_landscape_Z_image (
  FILE *out,
  int scale,
  enum device device);
static void write_portrait_XY_image (
  FILE *out,
  int scale,
  enum device device);
static void write_landscape_XY_image (
  FILE *out,
  int scale,
  enum device device);
static void write_Z_image (
  FILE *out,
  int scale,
  enum orientation orient,
  enum device device);
static void write_XY_image (
  FILE *out,
  int scale,
  enum orientation orient,
  enum device device);
static void write_image (
  FILE *out,
  int scale,
  enum orientation orient,
  enum device device);
static void fatal_err (const char *s, ...)
  _X_NORETURN _X_ATTRIBUTE_PRINTF(1,2);


/* Computes the centipoint width of one printer dot. */
#define dot_centipoints(s,d)	((7200.0 * (s)) / (d))

static
void set_image_limits (
  int scale, int density,
  enum orientation orient,
  Area print_area)
{
  Area print_dots;
  double dotsize;

  /* Set dotsize to the centipoint width of one printer dot. */
  dotsize = dot_centipoints(scale, density);

  if (orient == PORTRAIT) {
    print_dots.width = print_area.width / dotsize;
    print_dots.height = print_area.height / dotsize;
  } else {
    print_dots.height = print_area.width / dotsize;
    print_dots.width = print_area.height / dotsize;
  }

  limit.width = (print_dots.width < xwd_header.pixmap_width)
		? print_dots.width : xwd_header.pixmap_width;
  limit.height = (print_dots.height < xwd_header.pixmap_height)
		? print_dots.height : xwd_header.pixmap_height;

  if ((limit.width != xwd_header.pixmap_width)
      || (limit.height != xwd_header.pixmap_height))
    fprintf(stderr,(catgets(nlmsg_fd,NL_SETN,1, "%s: Warning: %d x %d image clipped to %ld x %ld.\n")),
	    progname,
	    xwd_header.pixmap_width, xwd_header.pixmap_height,
	    limit.width, limit.height);
}


static
void set_header_trailer_limits (char *header, char *trailer, long printwidth)
{
  /* Determine the number of header and trailer characters
   * that will fit into the available printing area.
   */
  headerlimit = header ? (((strlen(header) * CHARWIDTH) <= printwidth)
			  ? strlen(header) : (printwidth / CHARWIDTH))
		       : 0;
  if (header && headerlimit != strlen(header)) {
    fprintf(stderr,(catgets(nlmsg_fd,NL_SETN,2,
		"%s: Warning: Header string clipped to %d characters.\n")),
		progname, headerlimit);
    header[headerlimit] = '\0';
  }

  trailerlimit = trailer ? (((strlen(trailer) * CHARWIDTH) <= printwidth)
			    ? strlen(trailer) : (printwidth / CHARWIDTH))
			 : 0;
  if (trailer && trailerlimit != strlen(trailer)) {
    fprintf(stderr,(catgets(nlmsg_fd,NL_SETN,3,
		"%s: Warning: Trailer string clipped to %d characters.\n")),
		progname, trailerlimit);
    trailer[headerlimit] = '\0';
  }
}


static
void set_print_locations (
  int scale, int density,
  int top, int left,
  const char *header, const char *trailer,
  enum orientation orient,
  int position_on_page)
{
  Area image;
  double dotsize;

  /* Set dotsize to the centipoint width of one printer dot. */
  dotsize = dot_centipoints(scale, density);

  /* Compute the centipoint size of the clipped image area. */
  if (orient == PORTRAIT) {
    image.width = limit.width * dotsize;
    image.height = limit.height * dotsize;
  } else {
    image.height = limit.width * dotsize;
    image.width = limit.height * dotsize;
  }

  if (position_on_page) {
    /* set vertical positions */
    imageloc.y = (top >= 0)
	      ? top * 24 + ((header) ? CHARHEIGHT : 0)
	      : ((page.height - ((header) ? CHARHEIGHT : 0)
		  - image.height - ((trailer) ? CHARHEIGHT : 0)) / 2)
		+ ((header) ? CHARHEIGHT : 0);
    headerloc.y = imageloc.y - CHARHEIGHT / 4;
    trailerloc.y = imageloc.y + image.height + (3 * CHARHEIGHT) / 4;

    /* set horizontal positions */
    if (left >= 0)
      headerloc.x = imageloc.x = trailerloc.x = left * 24;
    else {
      headerloc.x = (page.width - headerlimit * CHARWIDTH) / 2;
      imageloc.x = (page.width - image.width) / 2;
      trailerloc.x = (page.width - trailerlimit * CHARWIDTH) / 2;
    }
  }
}


static
int scale_raster (
  int density,
  enum orientation orient,
  Area print_area)
{
  Area image;
  int h_scale, v_scale;

  /* Set the image dimensions to the number of centipoints that would be
   * required for printing at the selected density.
   */
  if (orient == PORTRAIT) {
    image.width = xwd_header.pixmap_width * 7200 / density;
    image.height = xwd_header.pixmap_height * 7200 / density;
  } else {
    image.height = xwd_header.pixmap_width * 7200 / density;
    image.width = xwd_header.pixmap_height * 7200 / density;
  }

  /* Calculate the maximum image multiplier along
   * the horizontal and vertical dimensions.
   */
  h_scale = print_area.width / image.width;
  v_scale = print_area.height / image.height;

  /* If the image can be expanded, return the lesser of the horizontal and
   * vertical multipliers.  Otherwise, the image will not completely fit
   * the available print area, so just return 1 as the expansion factor.
   */
  return (((h_scale > 0) && (v_scale > 0))
	  ? ((h_scale<v_scale) ? h_scale : v_scale)
	  : 1);
}


static
void scale_and_orient_image (
  int *scale, int *density,
  int width, int height, int left, int top,  /* in 300ths of an inch */
  const char *header, const char *trailer,
  enum orientation *orient,
  int position_on_page,
  enum device device)
{
  Area usable;

  /* Determine printable area expressed in centipoints.  There are 7200
   * centipoints to the inch.  The width and height parameters passed in
   * are expressed in 300ths of an inch, therefore a 24x conversion factor
   * is used on the parameter values.  The default page dimensions STDWIDTH
   * and STDHEIGHT are expressed in inches so must be multiplied by 7200
   * to convert to centipoints.
   */
  page.width = (width >= 0) ? width * 24 : STDWIDTH * 7200;
  page.height = (height >= 0) ? height * 24 : STDHEIGHT * 7200;

  /* Paintjet Xl has a mechanical form feed, not a strip feed. It has
   * a slop of about 1/4 to 1/2 of an inch at the top and bottom.
   * deduct it from the page height.
   */
  if (device == PJETXL)
     page.height = page.height - 7200;

  /* Determine the area usable for the image.  This area will be smaller
   * than the total printable area if margins or header/trailer strings
   * have been specified.  Margins, like width and height discussed above,
   * are expressed in 300ths of an inch and must be converted to centipoints.
   * Header and trailer strings each reduce the available image height
   * by 1/6 inch, or 1200 centipoints (aka CHARHEIGHT).
   */
  usable.width = page.width - ((left > 0) ? (left * 24) : 0);
  usable.height = page.height - ((top > 0) ? (top * 24) : 0)
		  - ((header) ? CHARHEIGHT : 0)
		  - ((trailer) ? CHARHEIGHT : 0);

  /* Quit here if there is no usable image space. */
  if ((usable.width <= 0) || (usable.height <= 0)) {
    fatal_err((catgets(nlmsg_fd,NL_SETN,4,
				"No space available on page for image.")));
  }

  /* Determine image orientation.  The orientation will only be changed if
   * it was not specified by a command line option.  Portrait mode will be
   * used if either the usable printing area or the image area are square.
   * Portrait mode will also be used if the long dimensions of the usable
   * printing area and the image area match, otherwise landscape mode is
   * used.  Portrait mode really means "don't rotate" and landscape mode
   * means "rotate".
   */
  if (*orient == UNSPECIFIED) {
    if ((usable.width == usable.height)
	|| (xwd_header.pixmap_width == xwd_header.pixmap_height))
      *orient = PORTRAIT;
    else
      *orient = ((usable.width < usable.height)
		 == (xwd_header.pixmap_width < xwd_header.pixmap_height))
	? PORTRAIT : LANDSCAPE;
  }

  /* Set the dots-per-inch print density if it was not specified */
  if (*density <= 0) {
    switch(device) {
       case   LJET: *density = 300;
                    break;
       case   PJET: *density = 90;
                    break;
       case PJETXL:
       default:	    *density = 180;
                    break;
    }
  }

  /* Fit image to available area if scale was not specified */
  if (*scale <= 0)
    *scale = scale_raster(*density, *orient, usable);

  /* Determine image clipping limits */
  set_image_limits(*scale, *density, *orient, usable);

  /* Determine header/trailer string length clipping */
  set_header_trailer_limits((char *)header, (char *)trailer, usable.width);

  /* Calculate locations for page layout */
  set_print_locations(*scale, *density, top, left,
		      header, trailer, *orient, position_on_page);

}


static unsigned short fullintensity;

#define BLACK 1
#define WHITE 0
#define EMPTY -1
#define MAX_PJ_COLOR 16

#define RGBmatch(r,g,b,i)	(r == i) && (g == i) && (b == i)


/* Colormap array is used to map from the Xcolor array (xwd_colors) index
 * numbers into a pjcolor index number.  This style of mapping is done when
 * interpreting non-Direct/TrueColor visual types.
 */
static long *colormap;


/* Pjcolor array is used to hold the scaled RGB triple values
 * programmed into the printer.
 */
static long pjcolor[MAX_PJ_COLOR];


static int color_warning_given = FALSE;


/* Global visual type indicator, used to select color interpretation method. */
static char Direct_or_TrueColor;


/* Color index element definition, these are linked into a circular list
 * for interpretation of DirectColor or TrueColor visual types.
 */
typedef struct colorindex {
  long index;
  long pjcolor_index;
  struct colorindex *next;
} COLORINDEX;


/* Global data for color interpretation.  This structure serves as a home
 * for the color index lists (only used when processing DirectColor or
 * TrueColor visual types).  It also holds color processing switches and a
 * pointer to the output file to reduce parameter passing overhead.
 */
static struct {
  int PaintJet;
  int Invert;
  unsigned int CutOff;
  FILE *OutFile;
  COLORINDEX *indexchain, *freechain;
  RGBshiftmask sm;
} color;


static
void setup_RGBshiftmask (
  RGBshiftmask *sm,
  unsigned long rmask, unsigned long gmask, unsigned long bmask)
{
  sm->Rmask = rmask;  sm->Gmask = gmask;  sm->Bmask = bmask;
  sm->Rshift = 0;     sm->Gshift = 0;     sm->Bshift = 0;

  if (!rmask)
    fatal_err((catgets(nlmsg_fd,NL_SETN,5, "red mask for visual is zero.")));
  if (!gmask)
    fatal_err((catgets(nlmsg_fd,NL_SETN,6, "green mask for visual is zero.")));
  if (!bmask)
    fatal_err((catgets(nlmsg_fd,NL_SETN,7, "blue mask for visual is zero.")));

  for (; !(rmask & 1); sm->Rshift++)
    rmask >>= 1;
  for (; !(gmask & 1); sm->Gshift++)
    gmask >>= 1;
  for (; !(bmask & 1); sm->Bshift++)
    bmask >>= 1;
}


static
void swap_black_and_white (void)
{
  /* Reverse black and white in the Xcolor structure array. */

  XColor *color;
  int n;

  for (n=xwd_header.ncolors, color=xwd_colors;  n>0;  n--, color++)
    if (RGBmatch((color->red & fullintensity), (color->green & fullintensity),
				(color->blue & fullintensity), fullintensity))
      color->red = color->green = color->blue = 0;
    else if (RGBmatch(color->red, color->green, color->blue, 0))
      color->red = color->green = color->blue = fullintensity;
}


static
void reset_color_mapping (void)
{
  int n;
  long *cmap;
  COLORINDEX *splice;

  for (n=0; n<MAX_PJ_COLOR; n++)
    pjcolor[n] = EMPTY;

  if (!color.PaintJet) {
    /* preload for monochrome output */
    pjcolor[0] = WHITE;
    pjcolor[1] = BLACK;
  }

  if (Direct_or_TrueColor) {
    /* move color index chain cells onto the free list */
    if (color.indexchain != NULL) {
      splice = color.indexchain->next;
      color.indexchain->next = color.freechain;
      color.freechain = splice;
      color.indexchain = NULL;
    }
  } else if (color.PaintJet)
    for (n=xwd_header.ncolors, cmap=colormap;  n>0;  n--, cmap++)
      *cmap = EMPTY;
}


#define Intensity(r,g,b)	((r) * 0.30 + (g) * 0.59 + (b) * 0.11)

static
void prepare_color_mapping (
  int invert, int paintjet,
  unsigned int cutoff,
  FILE *out)
{
  int n;
  long *cmap;
  XColor *xcolor;

  for (n = xwd_header.bits_per_rgb, fullintensity = 0; n > 0; n--)
     fullintensity = (fullintensity << 1) | 1;
  for (n = sizeof(short) * 8 - xwd_header.bits_per_rgb; n > 0; n--)
     fullintensity = (fullintensity << 1);

  Direct_or_TrueColor =  (xwd_header.visual_class == DirectColor
			  || xwd_header.visual_class == TrueColor);
  color.PaintJet = paintjet;
  color.Invert = invert;
  color.CutOff = cutoff;
  color.OutFile = out;
  color.indexchain = NULL;
  color.freechain = NULL;

  if (Direct_or_TrueColor)
    setup_RGBshiftmask(&color.sm, xwd_header.red_mask,
		       xwd_header.green_mask, xwd_header.blue_mask);
  else {
    if (!(colormap = (long *) malloc(xwd_header.ncolors * sizeof(long))))
      fatal_err((catgets(nlmsg_fd,NL_SETN,24,
		"Could not allocate memory for X-to-printer colormap.")));

    /* For PaintJet, color map assignment will be done one line at a time.
     * So for now just interchange the Xcolor structure's black and white
     * if the -rv command line option was specified.
     */
    if (paintjet && invert)
      swap_black_and_white();

    /* For LaserJet, map each color to black or white based upon the
     * combined intensity of the RGB components.  Note that the normal
     * non-reversed (-rv) LaserJet mapping will represent light areas
     * of the screen as black on the paper.
     */
    if (!paintjet)
      for (n=xwd_header.ncolors, xcolor=xwd_colors, cmap=colormap;  n>0;
	   n--, xcolor++, cmap++)
	*cmap = (Intensity(xcolor->red, xcolor->green, xcolor->blue) < cutoff)
		? (invert ? BLACK : WHITE)
		: (invert ? WHITE : BLACK);
  }
  reset_color_mapping();
}


/* On a PaintJet printer, the programmable color intensity ranges are:
 *
 *	red:	4..90		green:	4..88		blue:	6..85
 *
 * The following macros map the 0..65535 intensity ranges of X colors
 * into the PaintJet's ranges.
 */

#define fixred(x)	(x / 762 + 4)
#define fixgreen(x)	(x / 780 + 4)
#define fixblue(x)	(x / 829 + 6)

#define is_grey(r,g,b)	((r == g) && (r == b))


static
void select_grey (int level, int *r, int *g, int *b)
{
  /* Forced selection of a grey.  This is done since the PaintJet does
   * not do very well when picking greys, they tend to become pink!
   */
  if (level > 66) {  /* white */
    *r = 90;    *g = 88;    *b = 85;
  } else if (level > 35) {
    *r = 43;    *g = 43;    *b = 45;
  } else if (level > 21) {
    *r = 25;    *g = 25;    *b = 33;
  } else if (level > 15) {
    *r = 15;    *g = 16;    *b = 18;
  } else if (level > 11) {
    *r = 14;    *g = 14;    *b = 18;
  } else if (level > 6) {
    *r =  6;    *g =  7;    *b =  8;
  } else {  /* black */
    *r =  4;    *g =  4;    *b =  6;
  }
}


static
int load_printer_color (
  long index,
  int nearmatch,
  enum device device) /* for non Direct/TrueColor */
{
  int n, red, blue, green, xred, xgreen, xblue;
  long compositeRGB;

  if (colormap[index] != EMPTY)
    /* printer has already been programmed for this color index */
    return(1);	/* "success" */
  else {
    xred   = xwd_colors[index].red;
    xgreen = xwd_colors[index].green;
    xblue  = xwd_colors[index].blue;
    /* determine the scaled RGB PaintJet color values */
    if (device == PJET) {
       red   = fixred(xred);
       green = fixgreen(xgreen);
       blue  = fixblue(xblue);
       if (is_grey(xred, xgreen, xblue))  /* assist grey selection */
          select_grey(red, &red, &green, &blue);
    }
    compositeRGB = (red << 16) | (green << 8) | blue;
    /* search for a matching or unused PaintJet mapping entry */
    for (n=0; n<MAX_PJ_COLOR; n++) {
      if (pjcolor[n] == compositeRGB) {
	/* record mapping for this index */
	colormap[index] = n;
	/* return "success" */
	return(1);
      } else if (pjcolor[n] == EMPTY) {
	/* download color to printer */
	fprintf(color.OutFile,"\033*v%dA", red);
	fprintf(color.OutFile,"\033*v%dB", green);
	fprintf(color.OutFile,"\033*v%dC", blue);
	fprintf(color.OutFile,"\033*v%dI", n);
	/* record that this is now programmed */
	pjcolor[n] = compositeRGB;
	colormap[index] = n;
	/* return "success" */
	return(1);
      }
    }
    /* unable to find or program this color */
    if (nearmatch)
      colormap[index] = find_nearest_programmed_color(compositeRGB);
  }
  return(0);  /* "failure" */
}


/* Lookup the image color index on the color.indexchain list.  If found
 * return the corresponding printer color index, otherwise -1.  The index
 * chain is a singly linked circular list.  Its head pointer is left at
 * the last cell matched on the theory that this will allow faster lookup
 * for runs of color.
 */
static
int lookup_color_index (long i)
{
  COLORINDEX *start, *current;

  start = current = color.indexchain;

  if (current == NULL)
    return(-1);  /* not found */

  do {
    if (current->index == i) {
      color.indexchain = current;
      return(current->pjcolor_index);  /* found */
    }
    current = current->next;
  } while (current != start);

  return(-1);  /* not found */
}


/* Calculate the individual and composite printer RGB values.  (Only the
 * composite value is set for monochrome output.)
 */
static
void select_printer_color (
  long index,
  int *red, int *green, int *blue,
  long *compositeRGB,
  enum device device)
{
  int xred, xgreen, xblue;

  xred   = xwd_colors[((index & color.sm.Rmask) >> color.sm.Rshift)].red;
  xgreen = xwd_colors[((index & color.sm.Gmask) >> color.sm.Gshift)].green;
  xblue  = xwd_colors[((index & color.sm.Bmask) >> color.sm.Bshift)].blue;

  if (color.PaintJet) {
    if (color.Invert) {
      if (RGBmatch((xred & fullintensity), (xgreen & fullintensity),
				(xblue & fullintensity), fullintensity))
	xred = xgreen = xblue = 0;
      else if (RGBmatch(xred, xgreen, xblue, 0))
	xred = xgreen = xblue = fullintensity;
    }
    /* determine the scaled RGB PaintJet color values */
    if (device == PJET) {
       *red   = fixred(xred);
       *green = fixgreen(xgreen);
       *blue  = fixblue(xblue);
       if (is_grey(xred, xgreen, xblue))  /* assist grey selection */
         select_grey(*red, red, green, blue);
    }
    if (device == PJETXL) {
       *red   = xred >> 8;
       *green = xgreen >> 8;
       *blue  = xblue >> 8;
    }
    *compositeRGB = (*red << 16) | (*green << 8) | *blue;
  } else  /* monochrome */
    *compositeRGB = (Intensity(xred, xgreen, xblue) < color.CutOff)
		    ? (color.Invert ? BLACK : WHITE)
		    : (color.Invert ? WHITE : BLACK);
}



/* Search for a color matching the compositeRGB value in the array of
 * colors already programmed into the printer.  Returns 1 if found,
 * 0 otherwise.  The matching array index is returned in pindex.
 */
static
int color_already_in_printer (long compositeRGB, long *pindex)
{
  int n;

  for (n=0; n<MAX_PJ_COLOR; n++)
    if (pjcolor[n] == EMPTY)
      return(0);  /* not found */
    else if (pjcolor[n] == compositeRGB) {
      *pindex = n;
      return(1);  /* found */
    }
  return(0);  /* not found */
}


static
int program_new_printer_color (
  int red, int green, int blue,
  long compositeRGB,
  long *pindex)
{
  int n;

  for (n=0; n<MAX_PJ_COLOR; n++)
    if (pjcolor[n] == EMPTY) {
      /* download color to printer */
      fprintf(color.OutFile,"\033*v%dA", red);
      fprintf(color.OutFile,"\033*v%dB", green);
      fprintf(color.OutFile,"\033*v%dC", blue);
      fprintf(color.OutFile,"\033*v%dI", n);
      /* record that this is now programmed */
      pjcolor[n] = compositeRGB;
      *pindex = n;
      /* return "success" */
      return(1);
    }
  /* unable to program this color, return "failure" */
  return(0);
}


static
long composite_diff (long x, long y)
{
  long r = (x >> 16 & 0xFF) - (y >> 16 & 0xFF);
  long g = (x >> 8 & 0xFF) - (y >> 8 & 0xFF);
  long b = (x & 0xFF) - (y & 0xFF);

  return(r*r + g*g + b*b);
}


static
long find_nearest_programmed_color (long compositeRGB)
{
  int n, nearest = 0;
  long neardiff = composite_diff(pjcolor[0], compositeRGB);
  long diff;

  for (n=1; n<MAX_PJ_COLOR; n++) {
    diff = composite_diff(pjcolor[n], compositeRGB);
    if (diff < neardiff) {
      neardiff = diff;
      nearest = n;
    }
  }
  return(nearest);
}


static
void add_index_to_chain (
  long cindex,
  long pindex)
{
  COLORINDEX *new;

  /* Get a new cell for the color index chain.  Take it from the free list
   * if possible, otherwise malloc space.
   */
  if (color.freechain == NULL) {
    if (!(new = (COLORINDEX *) malloc(sizeof(COLORINDEX))))
      fatal_err((catgets(nlmsg_fd,NL_SETN,8,
			"Could not allocate memory for color translation.")));
  } else {
    new = color.freechain;
    color.freechain = color.freechain->next;
  }

  /* put index values in the new cell */
  new->index = cindex;
  new->pjcolor_index = pindex;

  /* link the new cell into the chain */
  if (color.indexchain == NULL)
    new->next = new;
  else {
    new->next = color.indexchain->next;
    color.indexchain->next = new;
  }
  /* leave head pointer at the new cell */
  color.indexchain = new;
}


static
int load_printer_color_DT (
  long index,
  int nearmatch,
  enum device device) /* for Direct/TrueColor */
{
  int pjred, pjgreen, pjblue;
  long compositeRGB;
  long pindex;

  if (lookup_color_index(index) >= 0)
    return(1);	/* "success" */
  else {
    select_printer_color(index, &pjred, &pjgreen, &pjblue, &compositeRGB,
				device);
    if (color_already_in_printer(compositeRGB, &pindex)) {
      add_index_to_chain(index, pindex);
      return(1);  /* success */
    } else if (program_new_printer_color(pjred, pjgreen, pjblue,
					 compositeRGB, &pindex)) {
      add_index_to_chain(index, pindex);
      return(1);  /* success */
    } else if (nearmatch) {
      add_index_to_chain(index, find_nearest_programmed_color(compositeRGB));
      return(0);  /* failure, sorta... */
    }
  }
  return(0); /* failure */
}


static
int load_line_colors (
  long *line,
  int length, int nearmatch,
  enum device device)
{
  int result = 1;  /* initialized to "success" */

  for (; length>0; length--, line++) {
    result &= Direct_or_TrueColor
	      ? load_printer_color_DT(*line, nearmatch, device)
	      : load_printer_color(*line, nearmatch, device);
    if (!(nearmatch || result))
      break;
  }
  return(result);
}


static
void download_colors (
  long *line,
  int length,
  enum device device)
{
  /* For the first attempt at loading the colors for a line only exact
   * color matches are accepted.  If this fails, the closest colors are
   * accepted on the second attempt.
   *
   * Note: The first "if" test below bypasses the initial color loading
   * attempt for monochrome output (which will only come here for Direct
   * or TrueColor mono).  This forces reset_color_mapping which is
   * necessary to keep the color index chain down to a tolerable length.
   */
  if (!color.PaintJet || !load_line_colors(line, length, FALSE, device)) {
    reset_color_mapping();
    if (!load_line_colors(line, length, TRUE, device) &&
					!color_warning_given) {
      fprintf(stderr,(catgets(nlmsg_fd,NL_SETN,9,
			"%s: Warning: Cannot print all image colors.\n")),
		progname);
      color_warning_given = TRUE;
    }
  }
}


static _X_INLINE _X_NORETURN
void invalid_depth_for_visual(int depth, const char *name)
{
    fatal_err(catgets(nlmsg_fd,NL_SETN,25,
		      "%d bit deep %s bitmap not supported.\n"),
	      depth, name);
}

static
void validate_visual(void)
{
  int depth = xwd_header.pixmap_depth;

  switch (xwd_header.visual_class) {
  case GrayScale:
    if (depth > 8)  invalid_depth_for_visual(depth, "GrayScale");    break;
  case StaticGray:
    if (depth > 8)  invalid_depth_for_visual(depth, "StaticGray");   break;
  case PseudoColor:
    if (depth > 8)  invalid_depth_for_visual(depth, "PseudoColor");  break;
  case StaticColor:
    if (depth > 8)  invalid_depth_for_visual(depth, "StaticColor");  break;
  case DirectColor:
    if (depth != 12 && depth != 24)
	invalid_depth_for_visual(depth, "DirectColor");
    break;
  case TrueColor:
    if (depth != 12 && depth != 24)
	invalid_depth_for_visual(depth, "TrueColor");
    break;
  default:
    fatal_err((catgets(nlmsg_fd,NL_SETN,26,
		       "visual class #%d not supported.\n")),
	      (int)xwd_header.visual_class);
  }
}

static
void read_xwd_data (FILE *in)
{
#   define WINDOW_NAME_ALLOC	32
    unsigned long swaptest = 1;
    int window_name_size;
    int image_size;
    int n;
    char window_name [WINDOW_NAME_ALLOC];

    /* Read in XWDFileHeader structure */
    if (fread((char*) &xwd_header, 1, XWDHEADERSIZE, in) != XWDHEADERSIZE)
    	fatal_err((catgets(nlmsg_fd,NL_SETN,10,
				"Could not read xwd file's header.")));

    if (*(char *) &swaptest)
        _swaplong((char *) &xwd_header, XWDHEADERSIZE);

    validate_visual();

    /* Skip over window name */
    window_name_size = xwd_header.header_size - XWDHEADERSIZE;
    while (window_name_size > 0) {
    	n = window_name_size > WINDOW_NAME_ALLOC
    	    ? WINDOW_NAME_ALLOC : window_name_size;
    	if (fread(window_name, 1, n, in) != n)
    	    fatal_err((catgets(nlmsg_fd,NL_SETN,11,
				"Could not read xwd file's window name.")));
    	window_name_size -= n;
    }

    /* Allocate space for xwd color structures */
    if (!(xwd_colors = (XColor*) malloc(sizeof(XColor) * xwd_header.ncolors)))
    	fatal_err((catgets(nlmsg_fd,NL_SETN,12,
			"Could not allocate memory for xwdfile color table.")));

    /* Read in xwd color structures */
    for (n = 0; n < xwd_header.ncolors; n++)
    	if (fread(&xwd_colors[n], 1, XCOLORSIZE, in) != XCOLORSIZE)
    	    fatal_err((catgets(nlmsg_fd,NL_SETN,13,
				"Could not read xwd file's color table.")));

    if (*(char *) &swaptest) {
        for (n = 0; n < xwd_header.ncolors; n++) {
            _swaplong((char *) &xwd_colors[n].pixel, sizeof(long));
            _swapshort((char *) &xwd_colors[n].red, 3 * sizeof(short));
        }
     }

    /* Allocate space for xwd image */
    if (xwd_header.pixmap_format == ZPixmap)
    	image_size = 1;
    else if (xwd_header.pixmap_format == XYPixmap)
    	image_size = xwd_header.pixmap_depth;
    else
    	fatal_err((catgets(nlmsg_fd,NL_SETN,14,
			"Image in xwd file is not in Z or XY pixmap format.")));
    image_size *= xwd_header.bytes_per_line * xwd_header.pixmap_height;
    if (!(xwd_image = malloc(image_size)))
    	fatal_err((catgets(nlmsg_fd,NL_SETN,15,
			"Could not allocate memory for xwd file's image.")));

    /* Read in xwd image */
    if (fread(xwd_image, 1, image_size, in) != image_size)
    	fatal_err((catgets(nlmsg_fd,NL_SETN,16,
			"Could not read xwd file's image.")));

}


static
void write_image_prefix (
  FILE *out,
  int scale, int density,
  const char *header,
  enum device device,
  int position_on_page, int initial_formfeed,
  enum orientation orient,
  float gamma,
  int render,
  int slide)
{
  if (initial_formfeed)
    fprintf(out,"\014");

  /* Write out header & positioning commands */
  if (header) {
    if (position_on_page)
      fprintf(out,"\033&a%dH\033&a%dV",
	      /* headerloc x & y are written in decipoints */
	      (int) headerloc.x / 10, (int) headerloc.y / 10);
    fprintf(out,"%s\n", header);
  }

  /* Prepare printer for raster graphics: */

  /* Write image positioning commands */
  if (position_on_page)
    fprintf(out,"\033&a%dH\033&a%dV",
	    /* imageloc x & y are written in decipoints */
	    (int) imageloc.x / 10, (int) imageloc.y / 10);

  /* If doing transparencies, tell the printer before raster graphics */
  if (slide && device != LJET)
     fprintf(out, "\033&k3W");

  /* Set printer resolution */
  fprintf(out,"\033*t%dR", density);

  /*
   * do device dependent escape sequences
   */
  if (device == PJET) {
    /* Enable all four "planes" for PaintJet */
    fprintf(out,"\033*r4U");

    /* Set picture width for PaintJet */
    fprintf(out,"\033*r%dS",
	    (int) (((orient == PORTRAIT) ? limit.width : limit.height)
		   * scale));
  }

  /* Enable various options for PaintJet XL */
  if (device == PJETXL) {
    double dotsize;
    int n;

    /* Speed up printing by telling that there
     * will be no negative positioning
     */
    fprintf(out, "\033&a1N");

    if (gamma > 0.009)
       fprintf(out, "\033*t%.2fI", gamma);

    if (render > 0)
       fprintf(out, "\033*t%dJ", render);

    if (Direct_or_TrueColor)
       /* Enable direct by pixel for PaintJet XL */
       fwrite("\033*v6W\000\003\010\010\010\010", 1, 11, out);
    else {
       /* Enable index by pixel for PaintJet XL */
       fwrite("\033*v6W\000\001\010\010\010\010", 1, 11, out);

       /* Program the palette */
       for (n = 0; n < xwd_header.ncolors; n++) {
          fprintf(out,"\033*v%dA", (xwd_colors[n].red >> 8));
          fprintf(out,"\033*v%dB", (xwd_colors[n].green >> 8));
          fprintf(out,"\033*v%dC", (xwd_colors[n].blue >> 8));
          fprintf(out,"\033*v%dI", n);
       }
    }

    /****************************************
     *                                      *
     * PaintJet XL will do its own scaling  *
     *                                      *
     * Set picture width for PaintJet XL    *
     ****************************************/
    fprintf(out,"\033*r%dS",
	    (int) ((orient == PORTRAIT) ? xwd_header.pixmap_width
		   : xwd_header.pixmap_height));
    fprintf(out,"\033*r%dT",
	    (int) ((orient == PORTRAIT) ? xwd_header.pixmap_height
		   : xwd_header.pixmap_width));

    dotsize = dot_centipoints(scale, density);

    fprintf(out,"\033*t%dH",
		(int)(((orient == PORTRAIT) ? xwd_header.pixmap_width
                                        : xwd_header.pixmap_height)
                        * dotsize / 10));

    fprintf(out,"\033*t%dV",
		(int)(((orient == PORTRAIT) ? xwd_header.pixmap_height
                                        : xwd_header.pixmap_width)
                        * dotsize / 10));
  }

  /* Switch to raster graphics mode */
  if (device != PJETXL)
     fprintf(out,"\033*r1A");
  else
     fprintf(out,"\033*r3A");

}


static
void write_image_suffix (
  FILE *out,
  const char *trailer,
  int position_on_page,
  int slide, int render,
  enum device device)
{
  /* Exit raster graphics mode */
  if (device == PJETXL)
     fprintf(out,"\033*rC");
  else
     fprintf(out,"\033*rB");

  /* If doing transparencies, tell it to stop */
  if (slide && device != LJET)
     fprintf(out, "\033&k1W");

  if (device == PJETXL) {
     /* If selected a rendering algorithm, tell it to stop */
     if (render)
        fprintf(out, "\033*t3J");
     fprintf(out, "\033&a0N");
  }

  /* Write out trailer & positioning commands */
  if (trailer) {
    if (position_on_page)
      fprintf(out,"\033&a%dH\033&a%dV",
	      /* trailerloc x & y are written in decipoints */
	      (int) trailerloc.x / 10, (int) trailerloc.y / 10);
    fprintf(out,"%s\n", trailer);
  }
}


static
unsigned long Z_image_pixel (int x, int y)
{
  int pixel_bytes, offset;
  unsigned char *image;
  unsigned long pixel;

  pixel_bytes = xwd_header.bits_per_pixel >> 3;
  offset = ((xwd_header.bits_per_pixel == 4) ? (x / 2)
            : ((xwd_header.bits_per_pixel == 1) ? (x / 8)
               : (x * pixel_bytes)))
           + (y * xwd_header.bytes_per_line);

  image = (unsigned char *) &xwd_image[offset];

  switch (pixel_bytes) {
  case 0:  /* pixel per nibble or bit per pixel packing */
    if (xwd_header.bits_per_pixel == 1) {
      if (xwd_header.byte_order == MSBFirst)
	pixel = *image >> (7 - (x % 8));
      else
	pixel = *image >> (x % 8);
    } else {  /* xwd_header.bits_per_pixel == 4 */
      if (xwd_header.byte_order == MSBFirst)
	pixel = (x & 1) ? *image : (*image >> 4);
      else
	pixel = (x & 1) ? (*image >> 4) : *image;
    }
    break;
  case 1:
    pixel = *image;
    break;
  case 2:
    pixel = (xwd_header.byte_order == MSBFirst)
	     ? ((unsigned long)*image << 8 | *(image + 1))
	     : (*image | (unsigned long)*(image + 1) << 8);
    break;
  case 3:
    pixel = (xwd_header.byte_order == MSBFirst)
	     ? ((unsigned long)*image << 16 |
		(unsigned long)*(image + 1) << 8 |
		(unsigned long)*(image + 2))
	     : (*image |
		(unsigned long)*(image + 1) << 8 |
		(unsigned long)*(image + 2) << 16);
    break;
  case 4:
    pixel = (xwd_header.byte_order == MSBFirst)
	     ? ((unsigned long)*image << 24 |
		(unsigned long)*(image+1) << 16 |
		(unsigned long)*(image+2) << 8 |
		*(image+3))
	     : (*image |
		(unsigned long)*(image+1) << 8 |
		(unsigned long)*(image+2) << 16 |
		(unsigned long)*(image+3) << 24);
    break;
  default:
    pixel = 0;
    break;
  }
  return (pixel & Z_pixel_mask);
}


static
unsigned long XY_image_pixel (int x, int y)
{
  int plane_start, line_start, bytes_per_bitmap_unit, bitmap_unit_start,
      byte_within_bitmap_unit, offset, byte_mask;

  plane_start = (xwd_header.pixmap_depth - 1) * xwd_header.pixmap_height
		* xwd_header.bytes_per_line;
  line_start = xwd_header.bytes_per_line * y;
  bytes_per_bitmap_unit = xwd_header.bitmap_unit >> 3;
  bitmap_unit_start = (x / xwd_header.bitmap_unit) * bytes_per_bitmap_unit;
  byte_within_bitmap_unit = (xwd_header.byte_order == MSBFirst)
    ? (x % xwd_header.bitmap_unit) >> 3
    :  bytes_per_bitmap_unit - ((x % xwd_header.bitmap_unit) >> 3) - 1;

  offset = plane_start + line_start + bitmap_unit_start
	   + byte_within_bitmap_unit;

  byte_mask = (xwd_header.bitmap_bit_order == MSBFirst)
      ? 0x80 >> (x % 8) : 0x01 << (x % 8);

  return(xwd_image[offset] & byte_mask ? 1 : 0);
}


static
void direct_by_pixel(
  FILE *out,
  long *line,
  int length,
  enum device device)
{
   int red, green, blue;
   long compositeRGB;

   fprintf(out, "\033*b%dW", length * 3);
   for (; length>0; length--, line++) {
      select_printer_color(*line, &red, &green, &blue, &compositeRGB, device);
      fprintf(out, "%c%c%c", (char) red, (char) green, (char) blue);
   }
}


static
void index_by_pixel(
  FILE *out,
  long *line,
  int length)
{
   register int n;
   long *lp;
   char *line_pixels;
   register char *lc;

  if (!(line_pixels = malloc(length)))
    fatal_err((catgets(nlmsg_fd,NL_SETN,17,
			"Could not allocate raster line memory.")));

   for (n=0, lc=line_pixels, lp=line;  n<length;  n++)
      *lc++ = (char) *lp++;

   fprintf(out, "\033*b%dW", length);
   fwrite(line_pixels, 1, length, out);

   free(line_pixels);
}


static
void write_raster_line (
  FILE *out,
  int scale,
  enum device device,
  long *line,
  int length)
{
  int planes = (device == PJET) ? 4 : 1;
  int raster_bytes = (length * scale + 7) / 8;
  register int bytebits, n, p, e, bit;
  long *lp;
  char *line_colors, *raster_line;
  register char *lc, *rl, byte = 0;

  if (device == PJETXL) {
     if (Direct_or_TrueColor)
        direct_by_pixel(out, line, length, device);
     else
        index_by_pixel(out, line, length);
     return;
  }

  if (!(line_colors = malloc(length))
      || !(raster_line = malloc(raster_bytes * planes)))
    fatal_err((catgets(nlmsg_fd,NL_SETN,17,
			"Could not allocate raster line memory.")));

  if (device == PJET || Direct_or_TrueColor)
    download_colors(line, length, device);

  /* Map the line's colors into output color index numbers */
  if (Direct_or_TrueColor)
    for (n=0, lc=line_colors, lp=line;  n<length;  n++)
      *lc++ = (char) lookup_color_index(*lp++);
  else
    for (n=0, lc=line_colors, lp=line;  n<length;  n++)
      *lc++ = (char) colormap[*lp++];

  for (p=0;  p<planes;  p++) {
    bytebits = 0;
    n = length;
    lc = line_colors;
    rl = &raster_line[raster_bytes * p];
    while (n-- > 0) {
      bit = (*lc++ >> p) & 0x01;
      e = scale;
      while (e--) {
	byte = (byte << 1) | bit;
	bytebits++;
	if (bytebits == 8) {
	  *rl++ = byte;
	  bytebits = 0;
	}
      }
    }
    if (bytebits)
      *rl = byte << (8 - bytebits);
  }

  e = scale;
  while (e--) {
    for (p=0;  p<planes;  p++) {
      fprintf(out,"\033*b%d%c", raster_bytes, (p+1 == planes) ? 'W' : 'V');
      fwrite(&raster_line[raster_bytes * p], 1, raster_bytes, out);
    }
  }

  free(line_colors);
  free(raster_line);
}


static
void write_portrait_Z_image (
  FILE *out,
  int scale,
  enum device device)
{
  int x, y;
  int width = limit.width;
  int height = limit.height;
  long *line, *lp;

  if (!(line = (long *) malloc(width * sizeof(long))))
    fatal_err((catgets(nlmsg_fd,NL_SETN,18,
			"Could not allocate memory for image line buffer.")));

  for (y=0;  y<height;  y++) {

    for (x=0, lp=line;  x<width;  x++)
      *lp++ = Z_image_pixel(x,y);

    write_raster_line(out, scale, device, line, width);
  }
}


static
void write_landscape_Z_image (
  FILE *out,
  int scale,
  enum device device)
{
  int x, y;
  int width = limit.height;
  int height = limit.width;
  long *line, *lp;

  if (!(line = (long *) malloc(width * sizeof(long))))
    fatal_err((catgets(nlmsg_fd,NL_SETN,19,
			"Could not allocate memory for image line buffer.")));

  for (x=0;  x<height;  x++) {

    for (y=width-1, lp=line;  y>=0;  y--)
      *lp++ = Z_image_pixel(x,y);

    write_raster_line(out, scale, device, line, width);
  }
}


static
void write_portrait_XY_image (
  FILE *out,
  int scale,
  enum device device)
{
  int x, y;
  int width = limit.width;
  int height = limit.height;
  long *line, *lp;

  if (!(line = (long *) malloc(width * sizeof(long))))
    fatal_err((catgets(nlmsg_fd,NL_SETN,20,
			"Could not allocate memory for image line buffer.")));

  for (y=0;  y<height;  y++) {

    for (x=0, lp=line;  x<width;  x++)
      *lp++ = XY_image_pixel(x,y);

    write_raster_line(out, scale, device, line, width);
  }
}


static
void write_landscape_XY_image (
  FILE *out,
  int scale,
  enum device device)
{
  int x, y;
  int width = limit.height;
  int height = limit.width;
  long *line, *lp;

  if (!(line = (long *) malloc(width * sizeof(long))))
    fatal_err((catgets(nlmsg_fd,NL_SETN,21,
			"Could not allocate memory for image line buffer.")));

  for (x=0;  x<height;  x++) {

    for (y=width-1, lp=line;  y>=0;  y--)
      *lp++ = XY_image_pixel(x,y);

    write_raster_line(out, scale, device, line, width);
  }
}


static
void write_Z_image (
  FILE *out,
  int scale,
  enum orientation orient,
  enum device device)
{
  if (orient == PORTRAIT) {
    write_portrait_Z_image(out, scale, device);
  } else
    write_landscape_Z_image(out, scale, device);
}


static
void write_XY_image (
  FILE *out,
  int scale,
  enum orientation orient,
  enum device device)
{
  if (xwd_header.pixmap_depth > 1)
    fatal_err((catgets(nlmsg_fd,NL_SETN,22,
		"XY format image, multiplane images must be Z format.")));

  if (orient == PORTRAIT) {
    write_portrait_XY_image(out, scale, device);
  } else
    write_landscape_XY_image(out, scale, device);
}


static
void write_image (
  FILE *out,
  int scale,
  enum orientation orient,
  enum device device)
{
  switch (xwd_header.pixmap_format) {
  case XYPixmap:
    write_XY_image(out, scale, orient, device);  break;
  case  ZPixmap:
    write_Z_image(out, scale, orient, device);   break;
  default:
    fatal_err((catgets(nlmsg_fd,NL_SETN,23, "image not in XY or Z format.")));
  }
}


void x2jet(
  FILE *in, FILE *out,
  int scale, int density,
  int width, int height, int left, int top,  /* in 300ths of an inch */
  const char *header, const char *trailer,
  enum orientation orient,
  int invert, int initial_formfeed, int position_on_page, int slide,
  enum device device,
  unsigned int cutoff,
  float gamma,
  int render)
{
  int paintjet = FALSE;

  true_scale = scale;

  if (device != LJET)
     paintjet = TRUE;

  read_xwd_data(in);

  Z_pixel_mask = ~(0xFFFFFFFFUL << xwd_header.pixmap_depth);

  prepare_color_mapping(invert, paintjet, cutoff, out);

  scale_and_orient_image(&scale, &density, width, height, left, top,
			 header, trailer,
			 &orient, position_on_page, device);

  write_image_prefix(out, scale, density, header, device, position_on_page,
		     initial_formfeed, orient, gamma, render, slide);

  write_image(out, scale, orient, device);

  write_image_suffix(out, trailer, position_on_page, slide, render, device);

  fclose(out);
}

static
void fatal_err (const char *s, ...)
{
    va_list	ap;

    fprintf(stderr, "%s: ", progname);
    va_start(ap, s);
    vfprintf(stderr, s, ap);
    va_end(ap);
    fputc('\n', stderr);
    exit(EXIT_FAILURE);
}

