/*
 *   Advanced HP Page Control Language and Raster Transfer Language
 *   filter for CUPS.
 *
 *   Copyright 2007-2011 by Apple Inc.
 *   Copyright 1993-2005 by Easy Software Products
 *
 *   These coded instructions, statements, and computer programs are the
 *   property of Apple Inc. and are protected by Federal copyright
 *   law.  Distribution and use rights are outlined in the file "COPYING"
 *   which should have been included with this file.
 *
 * Contents:
 *
 *   StartPage()    - Start a page of graphics.
 *   EndPage()      - Finish a page of graphics.
 *   Shutdown()     - Shutdown a printer.
 *   CancelJob()    - Cancel the current job...
 *   CompressData() - Compress a line of graphics.
 *   OutputLine()   - Output the specified number of lines of graphics.
 *   ReadLine()     - Read graphics from the page stream.
 *   main()         - Main entry and processing of driver.
 */

/*
 * Include necessary headers...
 */

#include <cupsfilters/colormanager.h>
#include <cupsfilters/driver.h>
#include "pcl-common.h"
#include <signal.h>


/*
 * Output modes...
 */

typedef enum
{
  OUTPUT_BITMAP,			/* Output bitmap data from RIP */
  OUTPUT_INVERBIT,			/* Output inverted bitmap data */
  OUTPUT_RGB,				/* Output 24-bit RGB data from RIP */
  OUTPUT_DITHERED			/* Output dithered data */
} pcl_output_t;


/*
 * Globals...
 */

cups_rgb_t	*RGB;			/* RGB color separation data */
cups_cmyk_t	*CMYK;			/* CMYK color separation data */
unsigned char	*PixelBuffer,		/* Pixel buffer */
		*CMYKBuffer,		/* CMYK buffer */
		*OutputBuffers[6],	/* Output buffers */
		*DotBuffers[6],		/* Bit buffers */
		*CompBuffer,		/* Compression buffer */
		*SeedBuffer,		/* Mode 3 seed buffers */
		BlankValue;		/* The blank value */
short		*InputBuffer;		/* Color separation buffer */
cups_lut_t	*DitherLuts[6];		/* Lookup tables for dithering */
cups_dither_t	*DitherStates[6];	/* Dither state tables */
int		PrinterPlanes,		/* Number of color planes */
		SeedInvalid,		/* Contents of seed buffer invalid? */
		DotBits[6],		/* Number of bits per color */
		DotBufferSizes[6],	/* Size of one row of color dots */
		DotBufferSize,		/* Size of complete line */
		OutputFeed,		/* Number of lines to skip */
		Page;			/* Current page number */
pcl_output_t	OutputMode;		/* Output mode - see OUTPUT_ consts */
const int	ColorOrders[7][7] =	/* Order of color planes */
		{
		  { 0, 0, 0, 0, 0, 0, 0 },	/* Black */
		  { 0, 0, 0, 0, 0, 0, 0 },
		  { 0, 1, 2, 0, 0, 0, 0 },	/* CMY */
		  { 3, 0, 1, 2, 0, 0, 0 },	/* KCMY */
		  { 0, 0, 0, 0, 0, 0, 0 },
		  { 5, 0, 1, 2, 3, 4, 0 },	/* KCMYcm */
		  { 5, 0, 1, 2, 3, 4, 6 }	/* KCMYcmk */
		};
int		Canceled;		/* Is the job canceled? */


/*
 * Prototypes...
 */

void	StartPage(ppd_file_t *ppd, cups_page_header2_t *header, int job_id,
	          const char *user, const char *title, int num_options,
		  cups_option_t *options);
void	EndPage(ppd_file_t *ppd, cups_page_header2_t *header);
void	Shutdown(ppd_file_t *ppd, int job_id, const char *user,
	         const char *title, int num_options, cups_option_t *options);

void	CancelJob(int sig);
void	CompressData(unsigned char *line, int length, int plane, int pend,
	             int type);
void	OutputLine(ppd_file_t *ppd, cups_page_header2_t *header);
int	ReadLine(cups_raster_t *ras, cups_page_header2_t *header);


/*
 * 'StartPage()' - Start a page of graphics.
 */

void
StartPage(ppd_file_t         *ppd,	/* I - PPD file */
          cups_page_header2_t *header,	/* I - Page header */
	  int                job_id,	/* I - Job ID */
	  const char         *user,	/* I - User printing job */
	  const char         *title,	/* I - Title of job */
	  int                num_options,
					/* I - Number of command-line options */
	  cups_option_t      *options)	/* I - Command-line options */
{
  int		i;			/* Temporary/looping var */
  int		plane;			/* Current plane */
  int		cm_disabled;	/* Device Color Inhibited */
  char		s[255];			/* Temporary value */
  const char	*colormodel;		/* Color model string */
  char		resolution[PPD_MAX_NAME],
					/* Resolution string */
		spec[PPD_MAX_NAME];	/* PPD attribute name */
  ppd_attr_t	*attr;			/* Attribute from PPD file */
  ppd_choice_t	*choice;		/* Selected option */
  const int	*order;			/* Order to use */
  int		xorigin,		/* X origin of page */
		yorigin;		/* Y origin of page */
  static const float default_lut[2] =	/* Default dithering lookup table */
		{
		  0.0,
		  1.0
		};
  cm_calibration_t cm_calibrate;	/* Color calibration mode */

 /*
  * Debug info...
  */

  fprintf(stderr, "DEBUG: StartPage...\n");
  fprintf(stderr, "DEBUG: MediaClass = \"%s\"\n", header->MediaClass);
  fprintf(stderr, "DEBUG: MediaColor = \"%s\"\n", header->MediaColor);
  fprintf(stderr, "DEBUG: MediaType = \"%s\"\n", header->MediaType);
  fprintf(stderr, "DEBUG: OutputType = \"%s\"\n", header->OutputType);

  fprintf(stderr, "DEBUG: AdvanceDistance = %d\n", header->AdvanceDistance);
  fprintf(stderr, "DEBUG: AdvanceMedia = %d\n", header->AdvanceMedia);
  fprintf(stderr, "DEBUG: Collate = %d\n", header->Collate);
  fprintf(stderr, "DEBUG: CutMedia = %d\n", header->CutMedia);
  fprintf(stderr, "DEBUG: Duplex = %d\n", header->Duplex);
  fprintf(stderr, "DEBUG: HWResolution = [ %d %d ]\n", header->HWResolution[0],
          header->HWResolution[1]);
  fprintf(stderr, "DEBUG: ImagingBoundingBox = [ %d %d %d %d ]\n",
          header->ImagingBoundingBox[0], header->ImagingBoundingBox[1],
          header->ImagingBoundingBox[2], header->ImagingBoundingBox[3]);
  fprintf(stderr, "DEBUG: InsertSheet = %d\n", header->InsertSheet);
  fprintf(stderr, "DEBUG: Jog = %d\n", header->Jog);
  fprintf(stderr, "DEBUG: LeadingEdge = %d\n", header->LeadingEdge);
  fprintf(stderr, "DEBUG: Margins = [ %d %d ]\n", header->Margins[0],
          header->Margins[1]);
  fprintf(stderr, "DEBUG: ManualFeed = %d\n", header->ManualFeed);
  fprintf(stderr, "DEBUG: MediaPosition = %d\n", header->MediaPosition);
  fprintf(stderr, "DEBUG: MediaWeight = %d\n", header->MediaWeight);
  fprintf(stderr, "DEBUG: MirrorPrint = %d\n", header->MirrorPrint);
  fprintf(stderr, "DEBUG: NegativePrint = %d\n", header->NegativePrint);
  fprintf(stderr, "DEBUG: NumCopies = %d\n", header->NumCopies);
  fprintf(stderr, "DEBUG: Orientation = %d\n", header->Orientation);
  fprintf(stderr, "DEBUG: OutputFaceUp = %d\n", header->OutputFaceUp);
  fprintf(stderr, "DEBUG: PageSize = [ %d %d ]\n", header->PageSize[0],
          header->PageSize[1]);
  fprintf(stderr, "DEBUG: Separations = %d\n", header->Separations);
  fprintf(stderr, "DEBUG: TraySwitch = %d\n", header->TraySwitch);
  fprintf(stderr, "DEBUG: Tumble = %d\n", header->Tumble);
  fprintf(stderr, "DEBUG: cupsWidth = %d\n", header->cupsWidth);
  fprintf(stderr, "DEBUG: cupsHeight = %d\n", header->cupsHeight);
  fprintf(stderr, "DEBUG: cupsMediaType = %d\n", header->cupsMediaType);
  fprintf(stderr, "DEBUG: cupsBitsPerColor = %d\n", header->cupsBitsPerColor);
  fprintf(stderr, "DEBUG: cupsBitsPerPixel = %d\n", header->cupsBitsPerPixel);
  fprintf(stderr, "DEBUG: cupsBytesPerLine = %d\n", header->cupsBytesPerLine);
  fprintf(stderr, "DEBUG: cupsColorOrder = %d\n", header->cupsColorOrder);
  fprintf(stderr, "DEBUG: cupsColorSpace = %d\n", header->cupsColorSpace);
  fprintf(stderr, "DEBUG: cupsCompression = %d\n", header->cupsCompression);

#ifdef __APPLE__
 /*
  * MacOS X 10.2.x doesn't set most of the page device attributes, so check
  * the options and set them accordingly...
  */

  if (ppd && ppdIsMarked(ppd, "Duplex", "DuplexNoTumble"))
  {
    header->Duplex = CUPS_TRUE;
    header->Tumble = CUPS_FALSE;
  }
  else if (ppd && ppdIsMarked(ppd, "Duplex", "DuplexTumble"))
  {
    header->Duplex = CUPS_TRUE;
    header->Tumble = CUPS_TRUE;
  }

  fprintf(stderr, "DEBUG: num_options=%d\n", num_options);

  for (i = 0; i < num_options; i ++)
    fprintf(stderr, "DEBUG: options[%d]=[\"%s\" \"%s\"]\n", i,
            options[i].name, options[i].value);
#endif /* __APPLE__ */

 /*
  * Figure out the color model and spec strings...
  */

  switch (header->cupsColorSpace)
  {
    case CUPS_CSPACE_K :
        colormodel = "Black";
	break;
    case CUPS_CSPACE_W :
        colormodel = "Gray";
	break;
    default :
    case CUPS_CSPACE_RGB :
        colormodel = "RGB";
	break;
    case CUPS_CSPACE_CMY :
        colormodel = "CMY";
	break;
    case CUPS_CSPACE_CMYK :
        colormodel = "CMYK";
	break;
  }  

  cm_disabled = 0;

  if (header->HWResolution[0] != header->HWResolution[1])
    snprintf(resolution, sizeof(resolution), "%dx%ddpi",
             header->HWResolution[0], header->HWResolution[1]);
  else
    snprintf(resolution, sizeof(resolution), "%ddpi",
             header->HWResolution[0]);

  if (!header->MediaType[0])
    strcpy(header->MediaType, "PLAIN");

 /*
  * Get the dithering parameters...
  */

  BlankValue = 0x00;

  if (header->cupsBitsPerColor == 1)
  {
   /*
    * Use raw bitmap mode...
    */

    switch (header->cupsColorSpace)
    {
      case CUPS_CSPACE_K :
          OutputMode    = OUTPUT_BITMAP;
	  PrinterPlanes = 1;
	  break;
      case CUPS_CSPACE_W :
          OutputMode    = OUTPUT_INVERBIT;
	  PrinterPlanes = 1;
	  break;
      default :
      case CUPS_CSPACE_RGB :
          OutputMode    = OUTPUT_INVERBIT;
	  PrinterPlanes = 3;
	  break;
      case CUPS_CSPACE_CMY :
          OutputMode    = OUTPUT_BITMAP;
	  PrinterPlanes = 3;
	  break;
      case CUPS_CSPACE_CMYK :
          OutputMode    = OUTPUT_BITMAP;
	  PrinterPlanes = 4;
	  break;
    }

    if (OutputMode == OUTPUT_INVERBIT)
      BlankValue = 0xff;

    DotBufferSize = header->cupsBytesPerLine;

    memset(DitherLuts, 0, sizeof(DitherLuts));
    memset(DitherStates, 0, sizeof(DitherStates));
  }
  else if (header->cupsColorSpace == CUPS_CSPACE_RGB &&
           (!ppd || (ppd->model_number & PCL_RASTER_RGB24)))
  {
   /*
    * Use 24-bit RGB output mode...
    */

    OutputMode    = OUTPUT_RGB;
    PrinterPlanes = 3;
    DotBufferSize = header->cupsBytesPerLine;

    if (header->cupsCompression == 10)
      BlankValue = 0xff;

    memset(DitherLuts, 0, sizeof(DitherLuts));
    memset(DitherStates, 0, sizeof(DitherStates));
  }
  else if ((header->cupsColorSpace == CUPS_CSPACE_K ||
            header->cupsColorSpace == CUPS_CSPACE_W) &&
           (ppd && (ppd->model_number & PCL_RASTER_RGB24)) &&
	   header->cupsCompression == 10)
  {
   /*
    * Use 24-bit RGB output mode for grayscale/black output...
    */

    OutputMode    = OUTPUT_RGB;
    PrinterPlanes = 1;
    DotBufferSize = header->cupsBytesPerLine;

    if (header->cupsColorSpace == CUPS_CSPACE_W)
      BlankValue = 0xff;

    memset(DitherLuts, 0, sizeof(DitherLuts));
    memset(DitherStates, 0, sizeof(DitherStates));
  }
  else
  {
   /*
    * Use dithered output mode...
    */

    OutputMode = OUTPUT_DITHERED;

   /*
    * Load the appropriate color profiles...
    */

    RGB  = NULL;
    CMYK = NULL;

    fputs("DEBUG: Attempting to load color profiles using the following values:\n", stderr);
    fprintf(stderr, "DEBUG: ColorModel = %s\n", colormodel);
    fprintf(stderr, "DEBUG: MediaType = %s\n", header->MediaType);
    fprintf(stderr, "DEBUG: Resolution = %s\n", resolution);

    /* support the "cm-calibration" option */
    cm_calibrate = cmGetCupsColorCalibrateMode(options, num_options);

    if (cm_calibrate == CM_CALIBRATION_ENABLED)
      cm_disabled = 1;
    else
      cm_disabled = cmIsPrinterCmDisabled(getenv("PRINTER"));

    if (ppd && !cm_disabled)
    {
      if (header->cupsColorSpace == CUPS_CSPACE_RGB ||
	  header->cupsColorSpace == CUPS_CSPACE_W)
	RGB = cupsRGBLoad(ppd, colormodel, header->MediaType, resolution);

      CMYK = cupsCMYKLoad(ppd, colormodel, header->MediaType, resolution);
    }

    if (RGB)
      fputs("DEBUG: Loaded RGB separation from PPD.\n", stderr);

    if (CMYK)
      fputs("DEBUG: Loaded CMYK separation from PPD.\n", stderr);
    else
    {
      if (header->cupsColorSpace == CUPS_CSPACE_KCMY ||
	  header->cupsColorSpace == CUPS_CSPACE_CMYK)
	PrinterPlanes = 4;
      else if (header->cupsColorSpace == CUPS_CSPACE_CMY)
	PrinterPlanes = 3;
      else
	PrinterPlanes = 1;
      /*fputs("DEBUG: Loading default K separation.\n", stderr);*/
      fprintf(stderr, "DEBUG: Color Space: %d; Color Planes %d\n", header->cupsColorSpace, PrinterPlanes);
      CMYK = cupsCMYKNew(PrinterPlanes);
    }

    PrinterPlanes = CMYK->num_channels;

   /*
    * Use dithered mode...
    */

    switch (PrinterPlanes)
    {
      case 1 : /* K */
          DitherLuts[0] = cupsLutLoad(ppd, colormodel, header->MediaType,
	                              resolution, "Black");
          break;

      case 3 : /* CMY */
          DitherLuts[0] = cupsLutLoad(ppd, colormodel, header->MediaType,
	                              resolution, "Cyan");
          DitherLuts[1] = cupsLutLoad(ppd, colormodel, header->MediaType,
	                              resolution, "Magenta");
          DitherLuts[2] = cupsLutLoad(ppd, colormodel, header->MediaType,
	                              resolution, "Yellow");
          break;

      case 4 : /* CMYK */
          DitherLuts[0] = cupsLutLoad(ppd, colormodel, header->MediaType,
	                              resolution, "Cyan");
          DitherLuts[1] = cupsLutLoad(ppd, colormodel, header->MediaType,
	                              resolution, "Magenta");
          DitherLuts[2] = cupsLutLoad(ppd, colormodel, header->MediaType,
	                              resolution, "Yellow");
          DitherLuts[3] = cupsLutLoad(ppd, colormodel, header->MediaType,
	                              resolution, "Black");
          break;

      case 6 : /* CcMmYK */
          DitherLuts[0] = cupsLutLoad(ppd, colormodel, header->MediaType,
	                              resolution, "Cyan");
          DitherLuts[1] = cupsLutLoad(ppd, colormodel, header->MediaType,
	                              resolution, "LightCyan");
          DitherLuts[2] = cupsLutLoad(ppd, colormodel, header->MediaType,
	                              resolution, "Magenta");
          DitherLuts[3] = cupsLutLoad(ppd, colormodel, header->MediaType,
	                              resolution, "LightMagenta");
          DitherLuts[4] = cupsLutLoad(ppd, colormodel, header->MediaType,
	                              resolution, "Yellow");
          DitherLuts[5] = cupsLutLoad(ppd, colormodel, header->MediaType,
	                              resolution, "Black");
          break;
    }

    for (plane = 0; plane < PrinterPlanes; plane ++)
    {
      if (!DitherLuts[plane])
        DitherLuts[plane] = cupsLutNew(2, default_lut);

      if (DitherLuts[plane][4095].pixel > 1)
	DotBits[plane] = 2;
      else
	DotBits[plane] = 1;

      DitherStates[plane] = cupsDitherNew(header->cupsWidth);

      if (!DitherLuts[plane])
	DitherLuts[plane] = cupsLutNew(2, default_lut);
    }
  }

  fprintf(stderr, "DEBUG: PrinterPlanes = %d\n", PrinterPlanes);

 /*
  * Initialize the printer...
  */

  if (ppd && ((attr = ppdFindAttr(ppd, "cupsInitialNulls", NULL)) != NULL))
    for (i = atoi(attr->value); i > 0; i --)
      putchar(0);

  if (Page == 1 && (!ppd || ppd->model_number & PCL_PJL))
  {
    pjl_escape();

   /*
    * PJL job setup...
    */

    pjl_set_job(job_id, user, title);

    if (ppd && ((attr = ppdFindAttr(ppd, "cupsPJL", "StartJob")) != NULL))
      pjl_write(attr->value, NULL, job_id, user, title, num_options,
                options);

    snprintf(spec, sizeof(spec), "RENDERMODE.%s", colormodel);
    if (ppd && ((attr = ppdFindAttr(ppd, "cupsPJL", spec)) != NULL))
      printf("@PJL SET RENDERMODE=%s\r\n", attr->value);

    snprintf(spec, sizeof(spec), "COLORSPACE.%s", colormodel);
    if (ppd && ((attr = ppdFindAttr(ppd, "cupsPJL", spec)) != NULL))
      printf("@PJL SET COLORSPACE=%s\r\n", attr->value);
    if (!ppd)
      printf("@PJL SET COLORSPACE=%s\r\n", colormodel);

    snprintf(spec, sizeof(spec), "RENDERINTENT.%s", colormodel);
    if (ppd && ((attr = ppdFindAttr(ppd, "cupsPJL", spec)) != NULL))
      printf("@PJL SET RENDERINTENT=%s\r\n", attr->value);

    if (ppd && ((attr = ppdFindAttr(ppd, "cupsPJL", "Duplex")) != NULL))
    {
      sprintf(s, "%d", header->Duplex);
      pjl_write(attr->value, s, job_id, user, title, num_options, options);
    }

    if (ppd && ((attr = ppdFindAttr(ppd, "cupsPJL", "Tumble")) != NULL))
    {
      sprintf(s, "%d", header->Tumble);
      pjl_write(attr->value, s, job_id, user, title, num_options, options);
    }

    if (ppd && ((attr = ppdFindAttr(ppd, "cupsPJL", "MediaClass")) != NULL))
      pjl_write(attr->value, header->MediaClass, job_id, user, title,
                num_options, options);

    if (ppd && ((attr = ppdFindAttr(ppd, "cupsPJL", "MediaColor")) != NULL))
      pjl_write(attr->value, header->MediaColor, job_id, user, title,
                num_options, options);

    if (ppd && ((attr = ppdFindAttr(ppd, "cupsPJL", "MediaType")) != NULL))
      pjl_write(attr->value, header->MediaType, job_id, user, title,
                num_options, options);

    if (ppd && ((attr = ppdFindAttr(ppd, "cupsPJL", "OutputType")) != NULL))
      pjl_write(attr->value, header->OutputType, job_id, user, title,
                num_options, options);

    if (ppd && ((attr = ppdFindAttr(ppd, "cupsPJL", "cupsBooklet")) != NULL &&
		(choice = ppdFindMarkedChoice(ppd, "cupsBooklet")) != NULL))
      pjl_write(attr->value, choice->choice, job_id, user, title,
                num_options, options);

    if (ppd && ((attr = ppdFindAttr(ppd, "cupsPJL", "Jog")) != NULL))
    {
      sprintf(s, "%d", header->Jog);
      pjl_write(attr->value, s, job_id, user, title, num_options, options);
    }

    if (ppd && ((attr = ppdFindAttr(ppd, "cupsPJL", "cupsPunch")) != NULL &&
		(choice = ppdFindMarkedChoice(ppd, "cupsPunch")) != NULL))
      pjl_write(attr->value, choice->choice, job_id, user, title,
                num_options, options);

    if (ppd && ((attr = ppdFindAttr(ppd, "cupsPJL", "cupsStaple")) != NULL &&
		(choice = ppdFindMarkedChoice(ppd, "cupsStaple")) != NULL))
      pjl_write(attr->value, choice->choice, job_id, user, title,
                num_options, options);

    if (ppd && ((attr = ppdFindAttr(ppd, "cupsPJL", "cupsRET")) != NULL &&
		(choice = ppdFindMarkedChoice(ppd, "cupsRET")) != NULL))
      pjl_write(attr->value, choice->choice, job_id, user, title,
                num_options, options);

    if (ppd && ((attr = ppdFindAttr(ppd, "cupsPJL", "cupsTonerSave")) != NULL &&
		(choice = ppdFindMarkedChoice(ppd, "cupsTonerSave")) != NULL))
      pjl_write(attr->value, choice->choice, job_id, user, title,
                num_options, options);

    if (!ppd || ppd->model_number & PCL_PJL_PAPERWIDTH)
    {
      printf("@PJL SET PAPERLENGTH=%d\r\n", header->PageSize[1] * 10);
      printf("@PJL SET PAPERWIDTH=%d\r\n", header->PageSize[0] * 10);
    }

    if (!ppd || ppd->model_number & PCL_PJL_RESOLUTION)
      printf("@PJL SET RESOLUTION=%d\r\n", header->HWResolution[0]);

    if (ppd)
      ppdEmit(ppd, stdout, PPD_ORDER_JCL);
    if (ppd && ppd->model_number & PCL_PJL_HPGL2)
      pjl_enter_language("HPGL2");
    else if (ppd && ppd->model_number & PCL_PJL_PCL3GUI)
      pjl_enter_language("PCL3GUI");
    else
      pjl_enter_language("PCL");
  }

  if (Page == 1)
  {
    pcl_reset();
  }

  if (ppd && ppd->model_number & PCL_PJL_HPGL2)
  {
    if (Page == 1)
    {
     /*
      * HP-GL/2 initialization...
      */

      printf("IN;");
      printf("MG\"%d %s %s\";", job_id, user, title);
    }

   /*
    * Set media size, position, type, etc...
    */

    printf("BP5,0;");
    printf("PS%.0f,%.0f;",
	   header->cupsHeight * 1016.0 / header->HWResolution[1],
	   header->cupsWidth * 1016.0 / header->HWResolution[0]);
    printf("PU;");
    printf("PA0,0");

    printf("MT%d;", header->cupsMediaType);

    if (header->CutMedia == CUPS_CUT_PAGE)
      printf("EC;");
    else
      printf("EC0;");

   /*
    * Set graphics mode...
    */

    pcl_set_pcl_mode(0);
    pcl_set_negative_motion();
  }
  else
  {
   /*
    * Set media size, position, type, etc...
    */

    if (!header->Duplex || (Page & 1))
    {
      pcl_set_media_size(ppd, header->PageSize[0], header->PageSize[1]);

      if (header->MediaPosition)
        pcl_set_media_source(header->MediaPosition);

      pcl_set_media_type(header->cupsMediaType);

      if (!ppd || ppdFindAttr(ppd, "cupsPJL", "Duplex") == NULL)
        pcl_set_duplex(header->Duplex, header->Tumble);

     /*
      * Set the number of copies...
      */

      if (!ppd || !ppd->manual_copies)
	pcl_set_copies(header->NumCopies);

     /*
      * Set the output order/bin...
      */

      if ((!ppd || ppdFindAttr(ppd, "cupsPJL", "Jog") == NULL) && header->Jog)
        printf("\033&l%dG", header->Jog);
    }
    else
    {
     /*
      * Print on the back side...
      */

      printf("\033&a2G");
    }

    if (header->Duplex && (ppd && (ppd->model_number & PCL_RASTER_CRD)))
    {
     /*
      * Reload the media...
      */

      pcl_set_media_source(-2);
    }

   /*
    * Set the units for cursor positioning and go to the top of the form.
    */

    printf("\033&u%dD", header->HWResolution[0]);
    printf("\033*p0Y\033*p0X");
  }

  if (ppd && ((attr = cupsFindAttr(ppd, "cupsPCLQuality", colormodel,
				   header->MediaType, resolution, spec,
				   sizeof(spec))) != NULL))
  {
   /*
    * Set the print quality...
    */

    if (ppd && (ppd->model_number & PCL_PJL_HPGL2))
      printf("QM%d", atoi(attr->value));
    else
      printf("\033*o%dM", atoi(attr->value));
  }

 /*
  * Enter graphics mode...
  */

  if (ppd && (ppd->model_number & PCL_RASTER_CRD))
  {
   /*
    * Use configure raster data command...
    */

    if (OutputMode == OUTPUT_RGB)
    {
     /*
      * Send 12-byte configure raster data command with horizontal and
      * vertical resolutions as well as a color count...
      */

      if (ppd && ((attr = cupsFindAttr(ppd, "cupsPCLCRDMode", colormodel,
				       header->MediaType, resolution, spec,
				       sizeof(spec))) != NULL))
        i = atoi(attr->value);
      else
        i = 31;

      printf("\033*g12W");
      putchar(6);			/* Format 6 */
      putchar(i);			/* Set pen mode */
      putchar(0x00);			/* Number components */
      putchar(0x01);			/* (1 for RGB) */

      putchar(header->HWResolution[0] >> 8);
      putchar(header->HWResolution[0]);
      putchar(header->HWResolution[1] >> 8);
      putchar(header->HWResolution[1]);

      putchar(header->cupsCompression);	/* Compression mode 3 or 10 */
      putchar(0x01);			/* Portrait orientation */
      putchar(0x20);			/* Bits per pixel (32 = RGB) */
      putchar(0x01);			/* Planes per pixel (1 = chunky RGB) */
    }
    else
    {
     /*
      * Send the configure raster data command with horizontal and
      * vertical resolutions as well as a color count...
      */

      printf("\033*g%dW", PrinterPlanes * 6 + 2);
      putchar(2);			/* Format 2 */
      putchar(PrinterPlanes);		/* Output planes */

      order = ColorOrders[PrinterPlanes - 1];

      for (i = 0; i < PrinterPlanes; i ++)
      {
        plane = order[i];

	putchar(header->HWResolution[0] >> 8);
	putchar(header->HWResolution[0]);
	putchar(header->HWResolution[1] >> 8);
	putchar(header->HWResolution[1]);
	putchar(0);
	putchar(1 << DotBits[plane]);
      }
    }
  }
  else if ((!ppd || (ppd->model_number & PCL_RASTER_CID)) &&
	   OutputMode == OUTPUT_RGB)
  {
   /*
    * Use configure image data command...
    */

    pcl_set_simple_resolution(header->HWResolution[0]);
					/* Set output resolution */

    cupsWritePrintData("\033*v6W\2\3\0\10\10\10", 11);
					/* 24-bit sRGB */
  }
  else
  {
   /*
    * Use simple raster commands...
    */

    pcl_set_simple_resolution(header->HWResolution[0]);
					/* Set output resolution */

    if (PrinterPlanes == 3)
      pcl_set_simple_cmy();
    else if (PrinterPlanes == 4)
      pcl_set_simple_kcmy();
  }

  if (ppd && ((attr = ppdFindAttr(ppd, "cupsPCLOrigin", "X")) != NULL))
    xorigin = atoi(attr->value);
  else
    xorigin = 0;

  if (ppd && ((attr = ppdFindAttr(ppd, "cupsPCLOrigin", "Y")) != NULL))
    yorigin = atoi(attr->value);
  else
    yorigin = 120;

  printf("\033&a%dH\033&a%dV", xorigin, yorigin);
  printf("\033*r%dS", header->cupsWidth);
  printf("\033*r%dT", header->cupsHeight);
  printf("\033*r1A");

  if (header->cupsCompression && header->cupsCompression != 10)
    printf("\033*b%dM", header->cupsCompression);

  OutputFeed = 0;

 /*
  * Allocate memory for the page...
  */

  PixelBuffer = malloc(header->cupsBytesPerLine);

  if (OutputMode == OUTPUT_DITHERED)
  {
    InputBuffer      = malloc(header->cupsWidth * PrinterPlanes * 2);
    OutputBuffers[0] = malloc(PrinterPlanes * header->cupsWidth);

    for (i = 1; i < PrinterPlanes; i ++)
      OutputBuffers[i] = OutputBuffers[0] + i * header->cupsWidth;

    if (RGB)
      CMYKBuffer = malloc(header->cupsWidth * PrinterPlanes);

    for (plane = 0, DotBufferSize = 0; plane < PrinterPlanes; plane ++)
    {
      DotBufferSizes[plane] = (header->cupsWidth + 7) / 8 * DotBits[plane];
      DotBufferSize         += DotBufferSizes[plane];
    }

    DotBuffers[0] = malloc(DotBufferSize);
    for (plane = 1; plane < PrinterPlanes; plane ++)
      DotBuffers[plane] = DotBuffers[plane - 1] + DotBufferSizes[plane - 1];
  }

  if (header->cupsCompression)
    CompBuffer = malloc(DotBufferSize * 4);

  if (header->cupsCompression >= 3)
    SeedBuffer = malloc(DotBufferSize);

  SeedInvalid = 1;

  fprintf(stderr, "BlankValue=%d\n", BlankValue);
}


/*
 * 'EndPage()' - Finish a page of graphics.
 */

void
EndPage(ppd_file_t         *ppd,	/* I - PPD file */
        cups_page_header2_t *header)	/* I - Page header */
{
  int	plane;				/* Current plane */


 /*
  * End graphics mode...
  */

  if (ppd && (ppd->model_number & PCL_RASTER_END_COLOR))
    printf("\033*rC");			/* End color GFX */
  else
    printf("\033*r0B");			/* End B&W GFX */

 /*
  * Output a page eject sequence...
  */

  if (ppd && (ppd->model_number & PCL_PJL_HPGL2))
  {
     pcl_set_hpgl_mode(0);		/* Back to HP-GL/2 mode */
     printf("PG;");			/* Eject the current page */
  }
  else if (!(header->Duplex && (Page & 1)))
    printf("\014");			/* Eject current page */

 /*
  * Free memory for the page...
  */

  free(PixelBuffer);

  if (OutputMode == OUTPUT_DITHERED)
  {
    for (plane = 0; plane < PrinterPlanes; plane ++)
    {
      cupsDitherDelete(DitherStates[plane]);
      cupsLutDelete(DitherLuts[plane]);
    }

    free(DotBuffers[0]);
    free(InputBuffer);
    free(OutputBuffers[0]);

    cupsCMYKDelete(CMYK);

    if (RGB)
    {
      cupsRGBDelete(RGB);
      free(CMYKBuffer);
    }
  }

  if (header->cupsCompression)
    free(CompBuffer);

  if (header->cupsCompression >= 3)
    free(SeedBuffer);
}


/*
 * 'Shutdown()' - Shutdown a printer.
 */

void
Shutdown(ppd_file_t         *ppd,	/* I - PPD file */
	 int                job_id,	/* I - Job ID */
	 const char         *user,	/* I - User printing job */
	 const char         *title,	/* I - Title of job */
	 int                num_options,/* I - Number of command-line options */
	 cups_option_t      *options)	/* I - Command-line options */
{
  ppd_attr_t	*attr;			/* Attribute from PPD file */


  if (ppd && ((attr = ppdFindAttr(ppd, "cupsPCL", "EndJob")) != NULL))
  {
   /*
    * Tell the printer how many pages were in the job...
    */

    putchar(0x1b);
    printf(attr->value, Page);
  }
  else
  {
   /*
    * Return the printer to the default state...
    */

    pcl_reset();
  }

  if (!ppd || (ppd->model_number & PCL_PJL))
  {
    pjl_escape();

    if (ppd && ((attr = ppdFindAttr(ppd, "cupsPJL", "EndJob")) != NULL))
      pjl_write(attr->value, NULL, job_id, user, title, num_options,
                options);
    else
      printf("@PJL EOJ\r\n");

    pjl_escape();
  }
}


/*
 * 'CancelJob()' - Cancel the current job...
 */

void
CancelJob(int sig)			/* I - Signal */
{
  (void)sig;

  Canceled = 1;
}


/*
 * 'CompressData()' - Compress a line of graphics.
 */

void
CompressData(unsigned char *line,	/* I - Data to compress */
             int           length,	/* I - Number of bytes */
	     int           plane,	/* I - Color plane */
	     int           pend,	/* I - End character for data */
	     int           type)	/* I - Type of compression */
{
  unsigned char	*line_ptr,		/* Current byte pointer */
        	*line_end,		/* End-of-line byte pointer */
        	*comp_ptr,		/* Pointer into compression buffer */
        	*start,			/* Start of compression sequence */
		*seed;			/* Seed buffer pointer */
  int           count,			/* Count of bytes for output */
		offset,			/* Offset of bytes for output */
		temp;			/* Temporary count */
  int		r, g, b;		/* RGB deltas for mode 10 compression */


  switch (type)
  {
    default :
       /*
	* Do no compression; with a mode-0 only printer, we can compress blank
	* lines...
	*/

	line_ptr = line;

        if (cupsCheckBytes(line, length))
          line_end = line;		/* Blank line */
        else
	  line_end = line + length;	/* Non-blank line */
	break;

    case 1 :
       /*
        * Do run-length encoding...
        */

	line_end = line + length;
	for (line_ptr = line, comp_ptr = CompBuffer;
	     line_ptr < line_end;
	     comp_ptr += 2, line_ptr += count)
	{
	  for (count = 1;
               (line_ptr + count) < line_end &&
	           line_ptr[0] == line_ptr[count] &&
        	   count < 256;
               count ++);

	  comp_ptr[0] = count - 1;
	  comp_ptr[1] = line_ptr[0];
	}

        line_ptr = CompBuffer;
        line_end = comp_ptr;
	break;

    case 2 :
       /*
        * Do TIFF pack-bits encoding...
        */

	line_ptr = line;
	line_end = line + length;
	comp_ptr = CompBuffer;

	while (line_ptr < line_end)
	{
	  if ((line_ptr + 1) >= line_end)
	  {
	   /*
	    * Single byte on the end...
	    */

	    *comp_ptr++ = 0x00;
	    *comp_ptr++ = *line_ptr++;
	  }
	  else if (line_ptr[0] == line_ptr[1])
	  {
	   /*
	    * Repeated sequence...
	    */

	    line_ptr ++;
	    count = 2;

	    while (line_ptr < (line_end - 1) &&
        	   line_ptr[0] == line_ptr[1] &&
        	   count < 127)
	    {
              line_ptr ++;
              count ++;
	    }

	    *comp_ptr++ = 257 - count;
	    *comp_ptr++ = *line_ptr++;
	  }
	  else
	  {
	   /*
	    * Non-repeated sequence...
	    */

	    start    = line_ptr;
	    line_ptr ++;
	    count    = 1;

	    while (line_ptr < (line_end - 1) &&
        	   line_ptr[0] != line_ptr[1] &&
        	   count < 127)
	    {
              line_ptr ++;
              count ++;
	    }

	    *comp_ptr++ = count - 1;

	    memcpy(comp_ptr, start, count);
	    comp_ptr += count;
	  }
	}

        line_ptr = CompBuffer;
        line_end = comp_ptr;
	break;

    case 3 :
       /*
	* Do delta-row compression...
	*/

	line_ptr = line;
	line_end = line + length;

	comp_ptr = CompBuffer;
	seed     = SeedBuffer + plane * length;

	while (line_ptr < line_end)
        {
         /*
          * Find the next non-matching sequence...
          */

          start = line_ptr;

	  if (SeedInvalid)
	  {
	   /*
	    * The seed buffer is invalid, so do the next 8 bytes, max...
	    */

	    offset = 0;

	    if ((count = line_end - line_ptr) > 8)
	      count = 8;

	    line_ptr += count;
	  }
	  else
	  {
	   /*
	    * The seed buffer is valid, so compare against it...
	    */

            while (*line_ptr == *seed &&
                   line_ptr < line_end)
            {
              line_ptr ++;
              seed ++;
            }

            if (line_ptr == line_end)
              break;

            offset = line_ptr - start;

           /*
            * Find up to 8 non-matching bytes...
            */

            start = line_ptr;
            count = 0;
            while (*line_ptr != *seed &&
                   line_ptr < line_end &&
                   count < 8)
            {
              line_ptr ++;
              seed ++;
              count ++;
            }
	  }

         /*
          * Place mode 3 compression data in the buffer; see HP manuals
          * for details...
          */

          if (offset >= 31)
          {
           /*
            * Output multi-byte offset...
            */

            *comp_ptr++ = ((count - 1) << 5) | 31;

            offset -= 31;
            while (offset >= 255)
            {
              *comp_ptr++ = 255;
              offset    -= 255;
            }

            *comp_ptr++ = offset;
          }
          else
          {
           /*
            * Output single-byte offset...
            */

            *comp_ptr++ = ((count - 1) << 5) | offset;
          }

          memcpy(comp_ptr, start, count);
          comp_ptr += count;
        }

	line_ptr = CompBuffer;
	line_end = comp_ptr;

        memcpy(SeedBuffer + plane * length, line, length);
	break;

    case 10 :
       /*
        * Mode 10 "near lossless" RGB compression...
	*/

	line_ptr = line;
	line_end = line + length;

	comp_ptr = CompBuffer;
	seed     = SeedBuffer;

        if (PrinterPlanes == 1)
	{
	 /*
	  * Do grayscale compression to RGB...
	  */

	  while (line_ptr < line_end)
          {
           /*
            * Find the next non-matching sequence...
            */

            start = line_ptr;
            while (line_ptr < line_end &&
	           *line_ptr == *seed)
            {
              line_ptr ++;
              seed ++;
            }

            if (line_ptr == line_end)
              break;

            offset = line_ptr - start;

           /*
            * Find non-matching grayscale pixels...
            */

            start = line_ptr;
            while (line_ptr < line_end &&
	           *line_ptr != *seed)
            {
              line_ptr ++;
              seed ++;
            }

            count = line_ptr - start;

#if 0
            fprintf(stderr, "DEBUG: offset=%d, count=%d, comp_ptr=%p(%d of %d)...\n",
	            offset, count, comp_ptr, comp_ptr - CompBuffer,
		    BytesPerLine * 5);
#endif /* 0 */

           /*
            * Place mode 10 compression data in the buffer; each sequence
	    * starts with a command byte that looks like:
	    *
	    *     CMD SRC SRC OFF OFF CNT CNT CNT
	    *
	    * For the purpose of this driver, CMD and SRC are always 0.
	    *
	    * If the offset >= 3 then additional offset bytes follow the
	    * first command byte, each byte == 255 until the last one.
	    *
	    * If the count >= 7, then additional count bytes follow each
	    * group of pixels, each byte == 255 until the last one.
	    *
	    * The offset and count are in RGB tuples (not bytes, as for
	    * Mode 3 and 9)...
            */

            if (offset >= 3)
            {
             /*
              * Output multi-byte offset...
              */

              if (count > 7)
		*comp_ptr++ = 0x1f;
	      else
		*comp_ptr++ = 0x18 | (count - 1);

              offset -= 3;
              while (offset >= 255)
              {
        	*comp_ptr++ = 255;
        	offset      -= 255;
              }

              *comp_ptr++ = offset;
            }
            else
            {
             /*
              * Output single-byte offset...
              */

              if (count > 7)
		*comp_ptr++ = (offset << 3) | 0x07;
	      else
		*comp_ptr++ = (offset << 3) | (count - 1);
            }

	    temp = count - 8;
	    seed -= count;

            while (count > 0)
	    {
	      if (count <= temp)
	      {
	       /*
		* This is exceedingly lame...  The replacement counts
		* are intermingled with the data...
		*/

        	if (temp >= 255)
        	  *comp_ptr++ = 255;
        	else
        	  *comp_ptr++ = temp;

        	temp -= 255;
	      }

             /*
	      * Get difference between current and see pixels...
	      */

              r = *start - *seed;
	      g = r;
	      b = ((*start & 0xfe) - (*seed & 0xfe)) / 2;

              if (r < -16 || r > 15 || g < -16 || g > 15 || b < -16 || b > 15)
	      {
	       /*
		* Pack 24-bit RGB into 23 bits...  Lame...
		*/

                g = *start;

		*comp_ptr++ = g >> 1;

		if (g & 1)
		  *comp_ptr++ = 0x80 | (g >> 1);
		else
		  *comp_ptr++ = g >> 1;

		if (g & 1)
		  *comp_ptr++ = 0x80 | (g >> 1);
		else
		  *comp_ptr++ = g >> 1;
              }
	      else
	      {
	       /*
		* Pack 15-bit RGB difference...
		*/

        	*comp_ptr++ = 0x80 | ((r << 2) & 0x7c) | ((g >> 3) & 0x03);
		*comp_ptr++ = ((g << 5) & 0xe0) | (b & 0x1f);
	      }

              count --;
	      start ++;
	      seed ++;
            }

           /*
	    * Make sure we have the ending count if the replacement count
	    * was exactly 8 + 255n...
	    */

	    if (temp == 0)
	      *comp_ptr++ = 0;
          }
	}
	else
	{
	 /*
	  * Do RGB compression...
	  */

	  while (line_ptr < line_end)
          {
           /*
            * Find the next non-matching sequence...
            */

            start = line_ptr;
            while (line_ptr[0] == seed[0] &&
                   line_ptr[1] == seed[1] &&
                   line_ptr[2] == seed[2] &&
                   (line_ptr + 2) < line_end)
            {
              line_ptr += 3;
              seed += 3;
            }

            if (line_ptr == line_end)
              break;

            offset = (line_ptr - start) / 3;

           /*
            * Find non-matching RGB tuples...
            */

            start = line_ptr;
            while ((line_ptr[0] != seed[0] ||
                    line_ptr[1] != seed[1] ||
                    line_ptr[2] != seed[2]) &&
                   (line_ptr + 2) < line_end)
            {
              line_ptr += 3;
              seed += 3;
            }

            count = (line_ptr - start) / 3;

           /*
            * Place mode 10 compression data in the buffer; each sequence
	    * starts with a command byte that looks like:
	    *
	    *     CMD SRC SRC OFF OFF CNT CNT CNT
	    *
	    * For the purpose of this driver, CMD and SRC are always 0.
	    *
	    * If the offset >= 3 then additional offset bytes follow the
	    * first command byte, each byte == 255 until the last one.
	    *
	    * If the count >= 7, then additional count bytes follow each
	    * group of pixels, each byte == 255 until the last one.
	    *
	    * The offset and count are in RGB tuples (not bytes, as for
	    * Mode 3 and 9)...
            */

            if (offset >= 3)
            {
             /*
              * Output multi-byte offset...
              */

              if (count > 7)
		*comp_ptr++ = 0x1f;
	      else
		*comp_ptr++ = 0x18 | (count - 1);

              offset -= 3;
              while (offset >= 255)
              {
        	*comp_ptr++ = 255;
        	offset      -= 255;
              }

              *comp_ptr++ = offset;
            }
            else
            {
             /*
              * Output single-byte offset...
              */

              if (count > 7)
		*comp_ptr++ = (offset << 3) | 0x07;
	      else
		*comp_ptr++ = (offset << 3) | (count - 1);
            }

	    temp = count - 8;
	    seed -= count * 3;

            while (count > 0)
	    {
	      if (count <= temp)
	      {
	       /*
		* This is exceedingly lame...  The replacement counts
		* are intermingled with the data...
		*/

        	if (temp >= 255)
        	  *comp_ptr++ = 255;
        	else
        	  *comp_ptr++ = temp;

        	temp -= 255;
	      }

             /*
	      * Get difference between current and see pixels...
	      */

              r = start[0] - seed[0];
	      g = start[1] - seed[1];
	      b = ((start[2] & 0xfe) - (seed[2] & 0xfe)) / 2;

              if (r < -16 || r > 15 || g < -16 || g > 15 || b < -16 || b > 15)
	      {
	       /*
		* Pack 24-bit RGB into 23 bits...  Lame...
		*/

		*comp_ptr++ = start[0] >> 1;

		if (start[0] & 1)
		  *comp_ptr++ = 0x80 | (start[1] >> 1);
		else
		  *comp_ptr++ = start[1] >> 1;

		if (start[1] & 1)
		  *comp_ptr++ = 0x80 | (start[2] >> 1);
		else
		  *comp_ptr++ = start[2] >> 1;
              }
	      else
	      {
	       /*
		* Pack 15-bit RGB difference...
		*/

        	*comp_ptr++ = 0x80 | ((r << 2) & 0x7c) | ((g >> 3) & 0x03);
		*comp_ptr++ = ((g << 5) & 0xe0) | (b & 0x1f);
	      }

              count --;
	      start += 3;
	      seed += 3;
            }

           /*
	    * Make sure we have the ending count if the replacement count
	    * was exactly 8 + 255n...
	    */

	    if (temp == 0)
	      *comp_ptr++ = 0;
          }
        }

	line_ptr = CompBuffer;
	line_end = comp_ptr;

        memcpy(SeedBuffer, line, length);
	break;
  }

 /*
  * Set the length of the data and write a raster plane...
  */

  printf("\033*b%d%c", (int)(line_end - line_ptr), pend);
  cupsWritePrintData(line_ptr, line_end - line_ptr);
}


/*
 * 'OutputLine()' - Output the specified number of lines of graphics.
 */

void
OutputLine(ppd_file_t         *ppd,	/* I - PPD file */
           cups_page_header2_t *header)	/* I - Page header */
{
  int			i, j;		/* Looping vars */
  int			plane;		/* Current plane */
  unsigned char		bit;		/* Current bit */
  int			bytes;		/* Number of bytes/plane */
  int			width;		/* Width of line in pixels */
  const int		*order;		/* Order to use */
  unsigned char		*ptr;		/* Pointer into buffer */


 /*
  * Output whitespace as needed...
  */

  if (OutputFeed > 0)
  {
    if (header->cupsCompression < 3)
    {
     /*
      * Send blank raster lines...
      */

      while (OutputFeed > 0)
      {
	printf("\033*b0W");
	OutputFeed --;
      }
    }
    else
    {
     /*
      * Send Y offset command and invalidate the seed buffer...
      */

      printf("\033*b%dY", OutputFeed);
      OutputFeed  = 0;
      SeedInvalid = 1;
    }
  }

 /*
  * Write bitmap data as needed...
  */

  switch (OutputMode)
  {
    case OUTPUT_BITMAP :		/* Send 1-bit bitmap data... */
	order = ColorOrders[PrinterPlanes - 1];
	bytes = header->cupsBytesPerLine / PrinterPlanes;

	for (i = 0; i < PrinterPlanes; i ++)
	{
	  plane = order[i];

	  CompressData(PixelBuffer + i * bytes, bytes, plane,
	               (i < (PrinterPlanes - 1)) ? 'V' : 'W',
		       header->cupsCompression);
        }
        break;

    case OUTPUT_INVERBIT :		/* Send inverted 1-bit bitmap data... */
	order = ColorOrders[PrinterPlanes - 1];
	bytes = header->cupsBytesPerLine / PrinterPlanes;

        for (i = header->cupsBytesPerLine, ptr = PixelBuffer;
	     i > 0;
	     i --, ptr ++)
	  *ptr = ~*ptr;

	for (i = 0; i < PrinterPlanes; i ++)
	{
	  plane = order[i];

	  CompressData(PixelBuffer + i * bytes, bytes, plane,
	               (i < (PrinterPlanes - 1)) ? 'V' : 'W',
		       header->cupsCompression);
        }
        break;

    case OUTPUT_RGB :			/* Send 24-bit RGB data... */
        if (PrinterPlanes == 1 && !BlankValue)
	{
	 /*
	  * Invert black to grayscale...
	  */

          for (i = header->cupsBytesPerLine, ptr = PixelBuffer;
	       i > 0;
	       i --, ptr ++)
	    *ptr = ~*ptr;
	}

       /*
	* Compress the output...
	*/

	CompressData(PixelBuffer, header->cupsBytesPerLine, 0, 'W',
	             header->cupsCompression);
        break;

    default :
	order = ColorOrders[PrinterPlanes - 1];
	width = header->cupsWidth;

	for (i = 0, j = 0; i < PrinterPlanes; i ++)
	{
	  plane = order[i];
	  bytes = DotBufferSizes[plane] / DotBits[plane];

	  for (bit = 1, ptr = DotBuffers[plane];
	       bit <= DotBits[plane];
	       bit <<= 1, ptr += bytes, j ++)
	  {
	    cupsPackHorizontalBit(OutputBuffers[plane], DotBuffers[plane],
	                          width, 0, bit);
            CompressData(ptr, bytes, j,
	                 i == (PrinterPlanes - 1) &&
			     bit == DotBits[plane] ? 'W' : 'V',
			 header->cupsCompression);
          }
	}
	break;
  }

 /*
  * The seed buffer, if any, now should contain valid data...
  */

  SeedInvalid = 0;
}


/*
 * 'ReadLine()' - Read graphics from the page stream.
 */

int					/* O - Number of lines (0 if blank) */
ReadLine(cups_raster_t      *ras,	/* I - Raster stream */
         cups_page_header2_t *header)	/* I - Page header */
{
  int	plane,				/* Current color plane */
	width;				/* Width of line */


 /*
  * Read raster data...
  */

  cupsRasterReadPixels(ras, PixelBuffer, header->cupsBytesPerLine);

 /*
  * See if it is blank; if so, return right away...
  */

  if (cupsCheckValue(PixelBuffer, header->cupsBytesPerLine, BlankValue))
    return (0);

 /*
  * If we aren't dithering, return immediately...
  */

  if (OutputMode != OUTPUT_DITHERED)
    return (1);

 /*
  * Perform the color separation...
  */

  width = header->cupsWidth;

  switch (header->cupsColorSpace)
  {
    case CUPS_CSPACE_W :
        if (RGB)
	{
	  cupsRGBDoGray(RGB, PixelBuffer, CMYKBuffer, width);

	  if (RGB->num_channels == 1)
	    cupsCMYKDoBlack(CMYK, CMYKBuffer, InputBuffer, width);
	  else
	    cupsCMYKDoCMYK(CMYK, CMYKBuffer, InputBuffer, width);
	}
	else
          cupsCMYKDoGray(CMYK, PixelBuffer, InputBuffer, width);
	break;

    case CUPS_CSPACE_K :
        cupsCMYKDoBlack(CMYK, PixelBuffer, InputBuffer, width);
	break;

    default :
    case CUPS_CSPACE_RGB :
        if (RGB)
	{
	  cupsRGBDoRGB(RGB, PixelBuffer, CMYKBuffer, width);

	  if (RGB->num_channels == 1)
	    cupsCMYKDoBlack(CMYK, CMYKBuffer, InputBuffer, width);
	  else
	    cupsCMYKDoCMYK(CMYK, CMYKBuffer, InputBuffer, width);
	}
	else
          cupsCMYKDoRGB(CMYK, PixelBuffer, InputBuffer, width);
	break;

    case CUPS_CSPACE_CMYK :
        cupsCMYKDoCMYK(CMYK, PixelBuffer, InputBuffer, width);
	break;
  }

 /*
  * Dither the pixels...
  */

  for (plane = 0; plane < PrinterPlanes; plane ++)
    cupsDitherLine(DitherStates[plane], DitherLuts[plane], InputBuffer + plane,
                   PrinterPlanes, OutputBuffers[plane]);

 /*
  * Return 1 to indicate that we have non-blank output...
  */

  return (1);
}


/*
 * 'main()' - Main entry and processing of driver.
 */

int					/* O - Exit status */
main(int  argc,				/* I - Number of command-line arguments */
     char *argv[])			/* I - Command-line arguments */
{
  int			fd;		/* File descriptor */
  int empty = 1;
  cups_raster_t		*ras;		/* Raster stream for printing */
  cups_page_header2_t	header;		/* Page header from file */
  int			y;		/* Current line */
  ppd_file_t		*ppd;		/* PPD file */
  int			job_id;		/* Job ID */
  int			num_options;	/* Number of options */
  cups_option_t		*options;	/* Options */
#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
  struct sigaction action;		/* Actions for POSIX signals */
#endif /* HAVE_SIGACTION && !HAVE_SIGSET */


 /*
  * Make sure status messages are not buffered...
  */

  setbuf(stderr, NULL);

 /*
  * Check command-line...
  */

  if (argc < 6 || argc > 7)
  {
    fprintf(stderr, "Usage: %s job-id user title copies options [file]\n",
	    "rastertopclx");
    return (1);
  }

  num_options = cupsParseOptions(argv[5], 0, &options);

 /*
  * Open the PPD file...
  */

  ppd = ppdOpenFile(getenv("PPD"));

  if (ppd)
  {
    ppdMarkDefaults(ppd);
    cupsMarkOptions(ppd, num_options, options);
  }
  else
  {
    ppd_status_t	status;		/* PPD error */
    int			linenum;	/* Line number */

    fputs("DEBUG: The PPD file could not be opened.\n", stderr);

    status = ppdLastError(&linenum);

    fprintf(stderr, "DEBUG: %s on line %d.\n", ppdErrorString(status), linenum);
  }

 /*
  * Open the page stream...
  */

  if (argc == 7)
  {
    if ((fd = open(argv[6], O_RDONLY)) == -1)
    {
      perror("ERROR: Unable to open raster file");
      return (1);
    }
  }
  else
    fd = 0;

  ras = cupsRasterOpen(fd, CUPS_RASTER_READ);

 /*
  * Register a signal handler to eject the current page if the
  * job is cancelled.
  */

  Canceled = 0;

#ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */
  sigset(SIGTERM, CancelJob);
#elif defined(HAVE_SIGACTION)
  memset(&action, 0, sizeof(action));

  sigemptyset(&action.sa_mask);
  action.sa_handler = CancelJob;
  sigaction(SIGTERM, &action, NULL);
#else
  signal(SIGTERM, CancelJob);
#endif /* HAVE_SIGSET */

 /*
  * Process pages as needed...
  */

  job_id = atoi(argv[1]);

  Page = 0;

  while (cupsRasterReadHeader2(ras, &header))
  {
   /*
    * Write a status message with the page number and number of copies.
    */

    if (empty)
      empty = 0;

    if (Canceled)
      break;

    Page ++;

    fprintf(stderr, "PAGE: %d %d\n", Page, header.NumCopies);
    fprintf(stderr, "INFO: Starting page %d.\n", Page);

    StartPage(ppd, &header, atoi(argv[1]), argv[2], argv[3],
              num_options, options);

    for (y = 0; y < (int)header.cupsHeight; y ++)
    {
     /*
      * Let the user know how far we have progressed...
      */

      if (Canceled)
	break;

      if ((y & 127) == 0)
      {
        fprintf(stderr, "INFO: Printing page %d, %d%% complete.\n",
		Page, 100 * y / header.cupsHeight);
        fprintf(stderr, "ATTR: job-media-progress=%d\n",
		100 * y / header.cupsHeight);
      }

     /*
      * Read and write a line of graphics or whitespace...
      */

      if (ReadLine(ras, &header))
        OutputLine(ppd, &header);
      else
        OutputFeed ++;
    }

   /*
    * Eject the page...
    */

    fprintf(stderr, "INFO: Finished page %d.\n", Page);

    EndPage(ppd, &header);

    if (Canceled)
      break;
  }

  if (!empty)
    Shutdown(ppd, job_id, argv[2], argv[3], num_options, options);

  cupsFreeOptions(num_options, options);

  cupsRasterClose(ras);

  if (fd != 0)
    close(fd);

  if (empty)
  {
    fprintf(stderr, "DEBUG: Input is empty, outputting empty file.\n");
    return 0;
  }
  return (Page == 0);
}

