/*
 *   Advanced EPSON ESC/P raster driver 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:
 *
 *   Setup()           - Prepare the printer for graphics output.
 *   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.
 *   OutputBand()      - Output a band of graphics.
 *   ProcessLine()     - Read graphics from the page stream and output
 *                       as needed.
 *   main()            - Main entry and processing of driver.
 */

/*
 * Include necessary headers...
 */

#include <cupsfilters/driver.h>
#include "escp.h"
#include <signal.h>
#include <string.h>
#include <ctype.h>


/*
 * Softweave data...
 */

typedef struct cups_weave_str
{
  struct cups_weave_str	*prev,			/* Previous band */
			*next;			/* Next band */
  int			x, y,			/* Column/Line on the page */
			plane,			/* Color plane */
			dirty,			/* Is this buffer dirty? */
			row,			/* Row in the buffer */
			count;			/* Max rows this pass */
  unsigned char		*buffer;		/* Data buffer */
} cups_weave_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[7],	/* Output buffers */
		*DotBuffers[7],		/* Dot buffers */
		*CompBuffer;		/* Compression buffer */
short		*InputBuffer;		/* Color separation buffer */
cups_weave_t	*DotAvailList,		/* Available buffers */
		*DotUsedList,		/* Used buffers */
		*DotBands[128][7];	/* Buffers in use */
int		DotBufferSize,		/* Size of dot buffers */
		DotRowMax,		/* Maximum row number in buffer */
		DotColStep,		/* Step for each output column */
		DotRowStep,		/* Step for each output line */
		DotRowFeed,		/* Amount to feed for interleave */
		DotRowCount,		/* Number of rows to output */
		DotRowOffset[7],	/* Offset for each color on print head */
		DotRowCurrent,		/* Current row */
		DotSize;		/* Dot size (Pro 5000 only) */
int		PrinterPlanes,		/* # of color planes */
		BitPlanes,		/* # of bit planes per color */
		PrinterTop,		/* Top of page */
		PrinterLength;		/* Length of page */
cups_lut_t	*DitherLuts[7];		/* Lookup tables for dithering */
cups_dither_t	*DitherStates[7];	/* Dither state tables */
int		OutputFeed;		/* Number of lines to skip */
int		Canceled;		/* Is the job canceled? */


/*
 * Prototypes...
 */

void	Setup(ppd_file_t *);
void	StartPage(ppd_file_t *, cups_page_header2_t *);
void	EndPage(ppd_file_t *, cups_page_header2_t *);
void	Shutdown(ppd_file_t *);

void	AddBand(cups_weave_t *band);
void	CancelJob(int sig);
void	CompressData(ppd_file_t *, const unsigned char *, const int,
	             int, int, const int, const int, const int,
		     const int);
void	OutputBand(ppd_file_t *, cups_page_header2_t *,
	           cups_weave_t *band);
void	ProcessLine(ppd_file_t *, cups_raster_t *,
	            cups_page_header2_t *, const int y);


/*
 * 'Setup()' - Prepare a printer for graphics output.
 */

void
Setup(ppd_file_t *ppd)		/* I - PPD file */
{
 /*
  * Some EPSON printers need an additional command issued at the
  * beginning of each job to exit from USB "packet" mode...
  */

  if (ppd->model_number & ESCP_USB)
    cupsWritePrintData("\000\000\000\033\001@EJL 1284.4\n@EJL     \n\033@", 29);
}


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

void
StartPage(ppd_file_t         *ppd,	/* I - PPD file */
          cups_page_header2_t *header)	/* I - Page header */
{
  int		i, y;			/* Looping vars */
  int		subrow,			/* Current subrow */
		modrow,			/* Subrow modulus */
		plane;			/* Current color plane */
  unsigned char	*ptr;			/* Pointer into dot buffer */
  int		bands;			/* Number of bands to allocate */
  int		units;			/* Units for resolution */
  cups_weave_t	*band;			/* Current band */
  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 */
  const float	default_lut[2] =	/* Default dithering lookup table */
		{
		  0.0,
		  1.0
		};


  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);
  fprintf(stderr, "DEBUG: cupsRowCount = %d\n", header->cupsRowCount);
  fprintf(stderr, "DEBUG: cupsRowFeed = %d\n", header->cupsRowFeed);
  fprintf(stderr, "DEBUG: cupsRowStep = %d\n", header->cupsRowStep);

 /*
  * 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_CMYK :
        colormodel = "CMYK";
	break;
  }

  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");

 /*
  * 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);

  if (header->cupsColorSpace == CUPS_CSPACE_RGB ||
      header->cupsColorSpace == CUPS_CSPACE_W)
    RGB = cupsRGBLoad(ppd, colormodel, header->MediaType, resolution);
  else
    RGB = NULL;

  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
  {
    fputs("DEBUG: Loading default CMYK separation.\n", stderr);
    CMYK = cupsCMYKNew(4);
  }

  PrinterPlanes = CMYK->num_channels;

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

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

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

    case 2 : /* Kk */
        DitherLuts[0] = cupsLutLoad(ppd, colormodel, header->MediaType,
	                            resolution, "Black");
        DitherLuts[1] = cupsLutLoad(ppd, colormodel, header->MediaType,
	                            resolution, "LightBlack");
        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;

    case 7 : /* CcMmYKk */
        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");
        DitherLuts[6] = cupsLutLoad(ppd, colormodel, header->MediaType,
	                            resolution, "LightBlack");
        break;
  }

  for (plane = 0; plane < PrinterPlanes; plane ++)
  {
    DitherStates[plane] = cupsDitherNew(header->cupsWidth);

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

  if (DitherLuts[0][4095].pixel > 1)
    BitPlanes = 2;
  else
    BitPlanes = 1;

 /*
  * Initialize the printer...
  */

  printf("\033@");

  if (ppd->model_number & ESCP_REMOTE)
  {
   /*
    * Go into remote mode...
    */

    cupsWritePrintData("\033(R\010\000\000REMOTE1", 13);

   /*
    * Disable status reporting...
    */

    cupsWritePrintData("ST\002\000\000\000", 6);

   /*
    * Enable borderless printing...
    */

    if ((attr = ppdFindAttr(ppd, "cupsESCPFP", NULL)) != NULL && attr->value)
    {
     /*
      * Set horizontal offset...
      */

      i = atoi(attr->value);

      cupsWritePrintData("FP\003\000\000", 5);
      putchar(i & 255);
      putchar(i >> 8);
    }

   /*
    * Set media type...
    */

    if (header->cupsMediaType)
    {
      sprintf(spec, "%d", header->cupsMediaType);

      if ((attr = ppdFindAttr(ppd, "cupsESCPSN0", spec)) != NULL && attr->value)
      {
       /*
        * Set feed sequence...
	*/

	cupsWritePrintData("SN\003\000\000\000", 6);
	putchar(atoi(attr->value));
      }

      if ((attr = ppdFindAttr(ppd, "cupsESCPSN1", spec)) != NULL && attr->value)
      {
       /*
        * Set platten gap...
	*/

	cupsWritePrintData("SN\003\000\000\001", 6);
	putchar(atoi(attr->value));
      }

      if ((attr = ppdFindAttr(ppd, "cupsESCPSN2", spec)) != NULL && attr->value)
      {
       /*
        * Paper feeding/ejecting sequence...
	*/

	cupsWritePrintData("SN\003\000\000\002", 6);
	putchar(atoi(attr->value));
      }

      if ((attr = ppdFindAttr(ppd, "cupsESCPSN6", spec)) != NULL && attr->value)
      {
       /*
        * Eject delay...
	*/

        cupsWritePrintData("SN\003\000\000\006", 6);
        putchar(atoi(attr->value));
      }

      if ((attr = ppdFindAttr(ppd, "cupsESCPMT", spec)) != NULL && attr->value)
      {
       /*
        * Set media type.
	*/

	cupsWritePrintData("MT\003\000\000\000", 6);
        putchar(atoi(attr->value));
      }

      if ((attr = ppdFindAttr(ppd, "cupsESCPPH", spec)) != NULL && attr->value)
      {
       /*
        * Set paper thickness.
        */

	cupsWritePrintData("PH\002\000\000", 5);
        putchar(atoi(attr->value));
      }
    }

    sprintf(spec, "%d", header->MediaPosition);

    if (header->MediaPosition)
    {
      if ((attr = ppdFindAttr(ppd, "cupsESCPPC", spec)) != NULL && attr->value)
      {
       /*
	* Paper check.
	*/

	cupsWritePrintData("PC\002\000\000", 5);
        putchar(atoi(attr->value));
      }

      if ((attr = ppdFindAttr(ppd, "cupsESCPPP", spec)) != NULL && attr->value)
      {
       /*
	* Paper path.
	*/

        int a, b;

        a = b = 0;
        sscanf(attr->value, "%d%d", &a, &b);

	cupsWritePrintData("PP\003\000\000", 5);
        putchar(a);
        putchar(b);
      }

      if ((attr = ppdFindAttr(ppd, "cupsESCPEX", spec)) != NULL && attr->value)
      {
       /*
	* Set media position.
	*/

	cupsWritePrintData("EX\006\000\000\000\000\000\005", 9);
        putchar(atoi(attr->value));
      }
    }

    if ((attr = ppdFindAttr(ppd, "cupsESCPMS", spec)) != NULL && attr->value)
    {
     /*
      * Set media size...
      */

      cupsWritePrintData("MS\010\000\000", 5);
      putchar(atoi(attr->value));

      switch (header->PageSize[1])
      {
        case 1191 :	/* A3 */
	    putchar(0x01);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    break;
	case 1032 :	/* B4 */
	    putchar(0x02);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    break;
	case 842 :	/* A4 */
	    putchar(0x03);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    break;
	case 595 :	/* A4.Transverse */
	    putchar(0x03);
	    putchar(0x01);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    break;
	case 729 :	/* B5 */
	    putchar(0x04);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    break;
	case 516 :	/* B5.Transverse */
	    putchar(0x04);
	    putchar(0x01);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    break;
	case 1369 :	/* Super A3/B */
	    putchar(0x20);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    break;
	case 792 :	/* Letter */
	    putchar(0x08);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    break;
	case 612 :	/* Letter.Transverse */
	    putchar(0x08);
	    putchar(0x01);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    break;
	case 1004 :	/* Legal */
	    putchar(0x0a);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    break;
	case 1224 :	/* Tabloid */
	    putchar(0x2d);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    putchar(0x00);
	    break;
	default :	/* Custom size */
	    putchar(0xff);
	    putchar(0xff);
	    i = 360 * header->PageSize[0] / 72;
	    putchar(i);
	    putchar(i >> 8);
	    i = 360 * header->PageSize[1] / 72;
	    putchar(i);
	    putchar(i >> 8);
	    break;
      }
    }

    sprintf(spec, "%d", header->CutMedia);

    if ((attr = ppdFindAttr(ppd, "cupsESCPAC", spec)) != NULL && attr->value)
    {
     /*
      * Enable/disable cutter.
      */

      cupsWritePrintData("AC\002\000\000", 5);
      putchar(atoi(attr->value));

      if ((attr = ppdFindAttr(ppd, "cupsESCPSN80", header->MediaType)) != NULL && attr->value)
      {
       /*
	* Cutting method...
	*/

	cupsWritePrintData("SN\003\000\000\200", 6);
	putchar(atoi(attr->value));
      }

      if ((attr = ppdFindAttr(ppd, "cupsESCPSN81", header->MediaType)) != NULL && attr->value)
      {
       /*
	* Cutting pressure...
	*/

	cupsWritePrintData("SN\003\000\000\201", 6);
	putchar(atoi(attr->value));
      }
    }

    if ((attr = ppdFindAttr(ppd, "cupsESCPCO", spec)) != NULL && attr->value)
    {
     /*
      * Enable/disable cutter.
      */

      cupsWritePrintData("CO\010\000\000\000", 6);
      putchar(atoi(attr->value));
      cupsWritePrintData("\000\000\000\000\000", 5);
    }

   /*
    * Exit remote mode...
    */

    cupsWritePrintData("\033\000\000\000", 4);
  }

 /*
  * Enter graphics mode...
  */

  cupsWritePrintData("\033(G\001\000\001", 6);

 /*
  * Set the line feed increment...
  */

  /* TODO: get this from the PPD file... */
  for (units = 1440; units < header->HWResolution[0]; units *= 2);

  if (ppd->model_number & ESCP_EXT_UNITS)
  {
    cupsWritePrintData("\033(U\005\000", 5);
    putchar(units / header->HWResolution[1]);
    putchar(units / header->HWResolution[1]);
    putchar(units / header->HWResolution[0]);
    putchar(units);
    putchar(units >> 8);
  }
  else
  {
    cupsWritePrintData("\033(U\001\000", 5);
    putchar(3600 / header->HWResolution[1]);
  }

 /*
  * Set the page length...
  */

  PrinterLength = header->PageSize[1] * header->HWResolution[1] / 72;

  if (ppd->model_number & ESCP_PAGE_SIZE)
  {
   /*
    * Set page size (expands bottom margin)...
    */

    cupsWritePrintData("\033(S\010\000", 5);

    i = header->PageSize[0] * header->HWResolution[1] / 72;
    putchar(i);
    putchar(i >> 8);
    putchar(i >> 16);
    putchar(i >> 24);

    i = header->PageSize[1] * header->HWResolution[1] / 72;
    putchar(i);
    putchar(i >> 8);
    putchar(i >> 16);
    putchar(i >> 24);
  }
  else
  {
    cupsWritePrintData("\033(C\002\000", 5);
    putchar(PrinterLength & 255);
    putchar(PrinterLength >> 8);
  }

 /*
  * Set the top and bottom margins...
  */

  PrinterTop = (int)((ppd->sizes[1].length - ppd->sizes[1].top) *
                     header->HWResolution[1] / 72.0);

  if (ppd->model_number & ESCP_EXT_MARGINS)
  {
    cupsWritePrintData("\033(c\010\000", 5);

    putchar(PrinterTop);
    putchar(PrinterTop >> 8);
    putchar(PrinterTop >> 16);
    putchar(PrinterTop >> 24);

    putchar(PrinterLength);
    putchar(PrinterLength >> 8);
    putchar(PrinterLength >> 16);
    putchar(PrinterLength >> 24);
  }
  else
  {
    cupsWritePrintData("\033(c\004\000", 5);

    putchar(PrinterTop & 255);
    putchar(PrinterTop >> 8);

    putchar(PrinterLength & 255);
    putchar(PrinterLength >> 8);
  }

 /*
  * Set the top position...
  */

  cupsWritePrintData("\033(V\002\000\000\000", 7);

 /*
  * Enable unidirectional printing depending on the mode...
  */

  if ((attr = cupsFindAttr(ppd, "cupsESCPDirection", colormodel,
                           header->MediaType, resolution, spec,
			   sizeof(spec))) != NULL)
    printf("\033U%c", atoi(attr->value));

 /*
  * Enable/disable microweaving as needed...
  */

  if ((attr = cupsFindAttr(ppd, "cupsESCPMicroWeave", colormodel,
                           header->MediaType, resolution, spec,
			   sizeof(spec))) != NULL)
    printf("\033(i\001%c%c", 0, atoi(attr->value));

 /*
  * Set the dot size and print speed as needed...
  */

  if ((attr = cupsFindAttr(ppd, "cupsESCPDotSize", colormodel,
                           header->MediaType, resolution, spec,
			   sizeof(spec))) != NULL)
    printf("\033(e\002%c%c%c", 0, 0, atoi(attr->value));

  if (ppd->model_number & ESCP_ESCK)
  {
   /*
    * Set the print mode...
    */

    if (PrinterPlanes == 1)
    {
     /*
      * Fast black printing.
      */

      cupsWritePrintData("\033(K\002\000\000\001", 7);
    }
    else
    {
     /*
      * Color printing.
      */

      cupsWritePrintData("\033(K\002\000\000\002", 7);
    }
  }

 /*
  * Get softweave settings from header...
  */

  if (header->cupsRowCount <= 1)
  {
    DotRowCount = 1;
    DotColStep  = 1;
    DotRowStep  = 1;
    DotRowFeed  = 1;
  }
  else
  {
    DotRowCount = header->cupsRowCount;
    DotRowFeed  = header->cupsRowFeed;
    DotRowStep  = header->cupsRowStep % 100;
    DotColStep  = header->cupsRowStep / 100;

    if (DotColStep == 0)
      DotColStep ++;
  }

 /*
  * Setup softweave parameters...
  */

  DotRowCurrent = 0;
  DotRowMax     = DotRowCount * DotRowStep;
  DotBufferSize = (header->cupsWidth / DotColStep * BitPlanes + 7) / 8;

  fprintf(stderr, "DEBUG: DotBufferSize = %d\n", DotBufferSize);
  fprintf(stderr, "DEBUG: DotColStep = %d\n", DotColStep);
  fprintf(stderr, "DEBUG: DotRowMax = %d\n", DotRowMax);
  fprintf(stderr, "DEBUG: DotRowStep = %d\n", DotRowStep);
  fprintf(stderr, "DEBUG: DotRowFeed = %d\n", DotRowFeed);
  fprintf(stderr, "DEBUG: DotRowCount = %d\n", DotRowCount);

  DotAvailList  = NULL;
  DotUsedList   = NULL;
  DotBuffers[0] = NULL;

  fprintf(stderr, "DEBUG: model_number = %x\n", ppd->model_number);

  if (DotRowMax > 1)
  {
   /*
    * Compute offsets for the color jets on the print head...
    */

    bands = DotRowStep * DotColStep * PrinterPlanes * 4;

    memset(DotRowOffset, 0, sizeof(DotRowOffset));

    if (PrinterPlanes == 1)
    {
     /*
      * Use full height of print head...
      */

      if ((attr = ppdFindAttr(ppd, "cupsESCPBlack", resolution)) != NULL &&
          attr->value)
      {
       /*
        * Use custom black head data...
	*/

        sscanf(attr->value, "%d%d", &DotRowCount, &DotRowStep);
      }
    }
    else if (ppd->model_number & ESCP_STAGGER)
    {
     /*
      * Use staggered print head...
      */

      fputs("DEBUG: Offset head detected...\n", stderr);

      if ((attr = ppdFindAttr(ppd, "cupsESCPOffsets", resolution)) != NULL &&
          attr->value)
      {
       /*
        * Use only 1/3 of the print head when printing color...
	*/

        sscanf(attr->value, "%d%d%d%d", DotRowOffset + 0,
	       DotRowOffset + 1, DotRowOffset + 2, DotRowOffset + 3);
      }
    }

    for (i = 0; i < PrinterPlanes; i ++)
      fprintf(stderr, "DEBUG: DotRowOffset[%d] = %d\n", i, DotRowOffset[i]);

   /*
    * Allocate bands...
    */

    for (i = 0; i < bands; i ++)
    {
      band         = (cups_weave_t *)calloc(1, sizeof(cups_weave_t));
      band->next   = DotAvailList;
      DotAvailList = band;

      band->buffer = calloc(DotRowCount, DotBufferSize);
    }

    if (!DotAvailList)
    {
      fputs("ERROR: Unable to allocate band list\n", stderr);
      exit(1);
    }

    fputs("DEBUG: Pointer list at start of page...\n", stderr);

    for (band = DotAvailList; band != NULL; band = band->next)
      fprintf(stderr, "DEBUG: %p\n", (void*)band);

    fputs("DEBUG: ----END----\n", stderr);

   /*
    * Fill the initial bands...
    */

    modrow = DotColStep * DotRowStep;

    if (DotRowFeed == 0)
    {
     /*
      * Automatically compute the optimal feed value...
      */

      DotRowFeed = DotRowCount / DotColStep - DotRowStep;

      while ((((DotRowFeed % 2) == 0) == ((DotRowCount % 2) == 0) ||
              ((DotRowFeed % 3) == 0) == ((DotRowCount % 3) == 0) ||
              ((DotRowFeed % 5) == 0) == ((DotRowCount % 5) == 0)) &&
	     DotRowFeed > 1)
	DotRowFeed --;

      if (DotRowFeed < 1)
	DotRowFeed = 1;

      fprintf(stderr, "DEBUG: Auto DotRowFeed = %d, modrow=%d...\n",
              DotRowFeed, modrow);
    }

    memset(DotBands, 0, sizeof(DotBands));

    for (i = modrow, subrow = modrow - 1, y = DotRowFeed;
	 i > 0;
	 i --, y += DotRowFeed)
    {
      while (DotBands[subrow][0])
      {
       /*
        * This subrow is already used, move to another one...
	*/

	subrow = (subrow + 1) % modrow;
      }

      for (plane = 0; plane < PrinterPlanes; plane ++)
      {
       /*
        * Pull the next available band from the list...
	*/

        band                    = DotAvailList;
	DotAvailList            = DotAvailList->next;
	DotBands[subrow][plane] = band;

       /*
        * Start the band in the first few passes, with the number of rows
	* varying to allow for a nice interleaved pattern...
	*/

        band->x     = subrow / DotRowStep;
        band->y     = (subrow % DotRowStep) + DotRowOffset[plane];
	band->plane = plane;
	band->row   = 0;
	band->count = DotRowCount - y / DotRowStep;

        if (band->count < 1)
	  band->count = 1;
	else if (band->count > DotRowCount)
	  band->count = DotRowCount;

	fprintf(stderr, "DEBUG: DotBands[%d][%d] = %p, x = %d, y = %d, plane = %d, count = %d\n",
	        subrow, plane, (void*)band, band->x, band->y, band->plane, band->count);
      }

      subrow = (subrow + DotRowFeed) % modrow;
    }
  }
  else
  {
   /*
    * Allocate memory for a single line of graphics...
    */

    ptr = calloc(PrinterPlanes, DotBufferSize);

    for (plane = 0; plane < PrinterPlanes; plane ++, ptr += DotBufferSize)
      DotBuffers[plane] = ptr;
  }

 /*
  * Set the output resolution...
  */

  cupsWritePrintData("\033(D\004\000", 5);
  putchar(units);
  putchar(units >> 8);
  putchar(units * DotRowStep / header->HWResolution[1]);
  putchar(units * DotColStep / header->HWResolution[0]);

 /*
  * Set the top of form...
  */

  OutputFeed = 0;

 /*
  * Allocate buffers as needed...
  */

  PixelBuffer      = malloc(header->cupsBytesPerLine);
  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);

  CompBuffer = malloc(10 * DotBufferSize * DotRowMax);
}


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

void
EndPage(ppd_file_t         *ppd,	/* I - PPD file */
        cups_page_header2_t *header)	/* I - Page header */
{
  int		i;			/* Looping var */
  cups_weave_t	*band,			/* Current band */
		*next;			/* Next band in list */
  int		plane;			/* Current plane */
  int		subrow;			/* Current subrow */
  int		subrows;		/* Number of subrows */


 /*
  * Output the last bands of print data as necessary...
  */

  if (DotRowMax > 1)
  {
   /*
    * Move the remaining bands to the used or avail lists...
    */

    subrows = DotRowStep * DotColStep;

    for (subrow = 0; subrow < subrows; subrow ++)
      for (plane = 0; plane < PrinterPlanes; plane ++)
      {
        if (DotBands[subrow][plane]->dirty)
	{
	 /*
	  * Insert into the used list...
	  */

          DotBands[subrow][plane]->count = DotBands[subrow][plane]->row;

          AddBand(DotBands[subrow][plane]);
	}
	else
	{
	 /*
	  * Nothing here, so move it to the available list...
	  */

	  DotBands[subrow][plane]->next = DotAvailList;
	  DotAvailList                  = DotBands[subrow][plane];
	}

	DotBands[subrow][plane] = NULL;
      }

   /*
    * Loop until all bands are written...
    */

    fputs("DEBUG: Pointer list at end of page...\n", stderr);

    for (band = DotUsedList; band != NULL; band = band->next)
      fprintf(stderr, "DEBUG: %p (used)\n", (void*)band);
    for (band = DotAvailList; band != NULL; band = band->next)
      fprintf(stderr, "DEBUG: %p (avail)\n", (void*)band);

    fputs("DEBUG: ----END----\n", stderr);

    for (band = DotUsedList; band != NULL; band = next)
    {
      next = band->next;

      OutputBand(ppd, header, band);

      fprintf(stderr, "DEBUG: freeing used band %p, prev = %p, next = %p\n",
              (void*)band, (void*)band->prev, (void*)band->next);

      free(band->buffer);
      free(band);
    }

   /*
    * Free memory for the available bands, if any...
    */

    for (band = DotAvailList; band != NULL; band = next)
    {
      next = band->next;

      fprintf(stderr, "DEBUG: freeing avail band %p, prev = %p,  next = %p\n",
              (void*)band, (void*)band->prev, (void*)band->next);

      free(band->buffer);
      free(band);
    }
  }
  else
  {
    free(DotBuffers[0]);
    DotBuffers[0] = NULL;
  }

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

  putchar(12);

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

  for (i = 0; i < PrinterPlanes; i ++)
  {
    cupsDitherDelete(DitherStates[i]);
    cupsLutDelete(DitherLuts[i]);
  }

  free(OutputBuffers[0]);

  free(PixelBuffer);
  free(InputBuffer);
  free(CompBuffer);

  cupsCMYKDelete(CMYK);

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


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

void
Shutdown(ppd_file_t *ppd)		/* I - PPD file */
{
 /*
  * Reset the printer...
  */

  printf("\033@");

  if (ppd->model_number & ESCP_REMOTE)
  {
   /*
    * Go into remote mode...
    */

    cupsWritePrintData("\033(R\010\000\000REMOTE1", 13);

   /*
    * Load defaults...
    */

    cupsWritePrintData("LD\000\000", 4);

   /*
    * Exit remote mode...
    */

    cupsWritePrintData("\033\000\000\000", 4);
  }
}


/*
 * 'AddBand()' - Add a band of data to the used list.
 */

void
AddBand(cups_weave_t *band)			/* I - Band to add */
{
  cups_weave_t	*current,			/* Current band */
		*prev;				/* Previous band */


  if (band->count < 1)
    return;

  for (current = DotUsedList, prev = NULL;
       current != NULL;
       prev = current, current = current->next)
    if (band->y < current->y ||
        (band->y == current->y && band->x < current->x) ||
	(band->y == current->y && band->x == current->x &&
	 band->plane < current->plane))
      break;

  if (current != NULL)
  {
   /*
    * Insert the band...
    */

    band->next    = current;
    band->prev    = prev;
    current->prev = band;

    if (prev != NULL)
      prev->next = band;
    else
      DotUsedList = band;
  }
  else if (prev != NULL)
  {
   /*
    * Append the band to the end...
    */

    band->prev = prev;
    prev->next = band;
    band->next = NULL;
  }
  else
  {
   /*
    * First band in list...
    */

    DotUsedList = band;
    band->prev  = NULL;
    band->next  = NULL;
  }
}


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

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

  Canceled = 1;
}


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

void
CompressData(ppd_file_t          *ppd,	/* I - PPD file information */
             const unsigned char *line,	/* I - Data to compress */
             const int           length,/* I - Number of bytes */
	     int                 plane,	/* I - Color plane */
	     int                 type,	/* I - Type of compression */
	     const int           rows,	/* I - Number of lines to write */
	     const int           xstep,	/* I - Spacing between columns */
	     const int           ystep,	/* I - Spacing between lines */
	     const int           offset)/* I - Head offset */
{
  register const unsigned char *line_ptr,
					/* Current byte pointer */
        	*line_end,		/* End-of-line byte pointer */
        	*start;			/* Start of compression sequence */
  register unsigned char *comp_ptr;	/* Pointer into compression buffer */
  register int  count;			/* Count of bytes for output */
  register int	bytes;			/* Number of bytes per row */
  static int	ctable[7][7] =		/* Colors */
		{
		  {  0,  0,  0,  0,  0,  0,  0 },	/* K */
		  {  0, 16,  0,  0,  0,  0,  0 },	/* Kk */
		  {  2,  1,  4,  0,  0,  0,  0 },	/* CMY */
		  {  2,  1,  4,  0,  0,  0,  0 },	/* CMYK */
		  {  0,  0,  0,  0,  0,  0,  0 },
		  {  2, 18,  1, 17,  4,  0,  0 },	/* CcMmYK */
		  {  2, 18,  1, 17,  4,  0, 16 },	/* CcMmYKk */
		};


  switch (type)
  {
    case 0 :
       /*
	* Do no compression...
	*/

	line_ptr = (const unsigned char *)line;
	line_end = (const unsigned char *)line + length;
	break;

    default :
       /*
        * Do TIFF pack-bits encoding...
        */

	line_ptr = (const unsigned char *)line;
	line_end = (const unsigned char *)line + length;
	comp_ptr = CompBuffer;

	while (line_ptr < line_end && (comp_ptr - CompBuffer) < length)
	{
	  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;
	  }
	}

        if ((comp_ptr - CompBuffer) < length)
	{
          line_ptr = (const unsigned char *)CompBuffer;
          line_end = (const unsigned char *)comp_ptr;
	}
	else
	{
	  type     = 0;
	  line_ptr = (const unsigned char *)line;
	  line_end = (const unsigned char *)line + length;
	}
	break;
  }

 /*
  * Position the print head...
  */

  putchar(0x0d);

  if (offset)
  {
    if (BitPlanes == 1)
      cupsWritePrintData("\033(\\\004\000\240\005", 7);
    else
      printf("\033\\");

    putchar(offset);
    putchar(offset >> 8);
  }

 /*
  * Send the graphics...
  */

  bytes = length / rows;

  if (ppd->model_number & ESCP_RASTER_ESCI)
  {
   /*
    * Send graphics with ESC i command.
    */

    printf("\033i");
    putchar(ctable[PrinterPlanes - 1][plane]);
    putchar((type != 0) ? '1': '0');
    putchar(BitPlanes);
    putchar(bytes & 255);
    putchar(bytes >> 8);
    putchar(rows & 255);
    putchar(rows >> 8);
  }
  else
  {
   /*
    * Set the color if necessary...
    */

    if (PrinterPlanes > 1)
    {
      plane = ctable[PrinterPlanes - 1][plane];

      if (plane & 0x10)
	printf("\033(r%c%c%c%c", 2, 0, 1, plane & 0x0f);
      else
	printf("\033r%c", plane);
    }

   /*
    * Send graphics with ESC . command.
    */

    bytes *= 8;

    printf("\033.");
    putchar((type != 0) ? '1': '0');
    putchar(ystep);
    putchar(xstep);
    putchar(rows);
    putchar(bytes & 255);
    putchar(bytes >> 8);
  }

  cupsWritePrintData(line_ptr, line_end - line_ptr);
}


/*
 * 'OutputBand()' - Output a band of graphics.
 */

void
OutputBand(ppd_file_t         *ppd,	/* I - PPD file */
           cups_page_header2_t *header,	/* I - Page header */
           cups_weave_t       *band)	/* I - Current band */
{
  int	xstep,				/* Spacing between columns */
	ystep;				/* Spacing between rows */


 /*
  * Interleaved ESC/P2 graphics...
  */

  OutputFeed    = band->y - DotRowCurrent;
  DotRowCurrent = band->y;

  fprintf(stderr, "DEBUG: Printing band %p, x = %d, y = %d, plane = %d, count = %d, OutputFeed = %d\n",
          (void*)band, band->x, band->y, band->plane, band->count, OutputFeed);

 /*
  * Compute step values...
  */

  xstep = 3600 * DotColStep / header->HWResolution[0];
  ystep = 3600 * DotRowStep / header->HWResolution[1];

 /*
  * Output the band...
  */

  if (OutputFeed > 0)
  {
    cupsWritePrintData("\033(v\002\000", 5);
    putchar(OutputFeed & 255);
    putchar(OutputFeed >> 8);

    OutputFeed = 0;
  }

  CompressData(ppd, band->buffer, band->count * DotBufferSize, band->plane,
	       header->cupsCompression, band->count, xstep, ystep, band->x);

 /*
  * Clear the band...
  */

  memset(band->buffer, 0, band->count * DotBufferSize);
  band->dirty = 0;

 /*
  * Flush the output buffers...
  */

  fflush(stdout);
}


/*
 * 'ProcessLine()' - Read graphics from the page stream and output as needed.
 */

void
ProcessLine(ppd_file_t         *ppd,	/* I - PPD file */
            cups_raster_t      *ras,	/* I - Raster stream */
            cups_page_header2_t *header,	/* I - Page header */
            const int          y)	/* I - Current scanline */
{
  int		plane,			/* Current color plane */
		width,			/* Width of line */
		subwidth,		/* Width of interleaved row */
		subrow,			/* Subrow for interleaved output */
		offset,			/* Offset to current line */
		pass,			/* Pass number */
		xstep,			/* X step value */
		ystep;			/* Y step value */
  cups_weave_t	*band;			/* Current band */


 /*
  * Read a row of graphics...
  */

  if (!cupsRasterReadPixels(ras, PixelBuffer, header->cupsBytesPerLine))
    return;

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

  width    = header->cupsWidth;
  subwidth = header->cupsWidth / DotColStep;
  xstep    = 3600 / header->HWResolution[0];
  ystep    = 3600 / header->HWResolution[1];

  switch (header->cupsColorSpace)
  {
    case CUPS_CSPACE_W :
        if (RGB)
	{
	  cupsRGBDoGray(RGB, PixelBuffer, CMYKBuffer, width);
	  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);
	  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]);

    if (DotRowMax == 1)
    {
     /*
      * Handle microweaved output...
      */

      if (cupsCheckBytes(OutputBuffers[plane], width))
	continue;

      if (BitPlanes == 1)
	cupsPackHorizontal(OutputBuffers[plane], DotBuffers[plane],
	                   width, 0, 1);
      else
	cupsPackHorizontal2(OutputBuffers[plane], DotBuffers[plane],
                	    width, 1);

      if (OutputFeed > 0)
      {
	cupsWritePrintData("\033(v\002\000", 5);
	putchar(OutputFeed & 255);
	putchar(OutputFeed >> 8);
	OutputFeed = 0;
      }

      CompressData(ppd, DotBuffers[plane], DotBufferSize, plane, 1, 1,
                   xstep, ystep, 0);
      fflush(stdout);
    }
    else
    {
     /*
      * Handle softweaved output...
      */

      for (pass = 0, subrow = y % DotRowStep;
           pass < DotColStep;
	   pass ++, subrow += DotRowStep)
      {
       /*
	* See if we need to output the band...
	*/

        band   = DotBands[subrow][plane];
	offset = band->row * DotBufferSize;

        if (BitPlanes == 1)
	  cupsPackHorizontal(OutputBuffers[plane] + pass,
	                     band->buffer + offset, subwidth, 0, DotColStep);
        else
	  cupsPackHorizontal2(OutputBuffers[plane] + pass,
	                      band->buffer + offset, subwidth, DotColStep);

        band->row ++;
	band->dirty |= !cupsCheckBytes(band->buffer + offset, DotBufferSize);
	if (band->row >= band->count)
	{
	  if (band->dirty)
	  {
	   /*
	    * Dirty band needs to be added to the used list...
	    */

	    AddBand(band);

           /*
	    * Then find a new band...
	    */

	    if (DotAvailList == NULL)
	    {
	      OutputBand(ppd, header, DotUsedList);

	      DotBands[subrow][plane] = DotUsedList;
	      DotUsedList->x          = band->x;
	      DotUsedList->y          = band->y + band->count * DotRowStep;
	      DotUsedList->plane      = band->plane;
	      DotUsedList->row        = 0;
	      DotUsedList->count      = DotRowCount;
	      DotUsedList             = DotUsedList->next;
	    }
	    else
	    {
	      DotBands[subrow][plane] = DotAvailList;
	      DotAvailList->x         = band->x;
	      DotAvailList->y         = band->y + band->count * DotRowStep;
	      DotAvailList->plane     = band->plane;
	      DotAvailList->row       = 0;
	      DotAvailList->count     = DotRowCount;
	      DotAvailList            = DotAvailList->next;
	    }
	  }
	  else
	  {
	   /*
	    * This band isn't dirty, so reuse it...
	    */

            fprintf(stderr, "DEBUG: Blank band %p, x = %d, y = %d, plane = %d, count = %d\n",
	            (void*)band, band->x, band->y, band->plane, band->count);

	    band->y     += band->count * DotRowStep;
	    band->row   = 0;
	    band->count = DotRowCount;
	  }
        }
      }
    }
  }

  if (DotRowMax == 1)
    OutputFeed ++;
}


/*
 * '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 */
  cups_raster_t		*ras;		/* Raster stream for printing */
  cups_page_header2_t	header;		/* Page header from file */
  int			page;		/* Current page */
  int			y;		/* Current line */
  ppd_file_t		*ppd;		/* PPD file */
  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",
	    argv[0]);
    return (1);
  }

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

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

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

  if (!ppd)
  {
    ppd_status_t	status;		/* PPD error */
    int			linenum;	/* Line number */

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

    status = ppdLastError(&linenum);

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

    return (1);
  }

  ppdMarkDefaults(ppd);
  cupsMarkOptions(ppd, num_options, options);

 /*
  * 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 */

 /*
  * Initialize the print device...
  */

  Setup(ppd);

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

  page = 0;

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

    if (Canceled)
      break;

    page ++;

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

    StartPage(ppd, &header);

    for (y = 0; y < 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...
      */

      ProcessLine(ppd, ras, &header, y);
    }

   /*
    * Eject the page...
    */

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

    EndPage(ppd, &header);

    if (Canceled)
      break;
  }

  Shutdown(ppd);

  cupsFreeOptions(num_options, options);

  cupsRasterClose(ras);

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

  for (int i = 0; i < 7; i++)
    if (DotBuffers[i] != NULL)
      free(DotBuffers[i]);

  return (page == 0);
}

