//
// Advanced EPSON ESC/P raster driver for cups-filters.
//
// Copyright 2007-2011 by Apple Inc.
// Copyright 1993-2005 by Easy Software Products.
//
// Licensed under Apache License v2.0.  See the file "LICENSE" for more
// information.
//
// 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 <ppd/ppd.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...
//

cf_rgb_t	*RGB;			// RGB color separation data
cf_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
cf_lut_t	*DitherLuts[7];		// Lookup tables for dithering
cf_dither_t	*DitherStates[7];	// Dither state tables
int		OutputFeed;		// Number of lines to skip
int		Canceled;		// Is the job canceled?
cf_logfunc_t logfunc;               // Log function
void            *ld;                    // Log function data


//
// 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)
    cfWritePrintData("\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 = ppdRGBLoad(ppd, colormodel, header->MediaType, resolution,
		     logfunc, ld);
  else
    RGB = NULL;

  CMYK = ppdCMYKLoad(ppd, colormodel, header->MediaType, resolution,
		     logfunc, ld);

  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 = cfCMYKNew(4);
  }

  PrinterPlanes = CMYK->num_channels;

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

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

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

    case 2 : // Kk
        DitherLuts[0] = ppdLutLoad(ppd, colormodel, header->MediaType,
	                            resolution, "Black", logfunc, ld);
        DitherLuts[1] = ppdLutLoad(ppd, colormodel, header->MediaType,
	                            resolution, "LightBlack", logfunc, ld);
        break;

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

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

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

    case 7 : // CcMmYKk
        DitherLuts[0] = ppdLutLoad(ppd, colormodel, header->MediaType,
	                            resolution, "Cyan", logfunc, ld);
        DitherLuts[1] = ppdLutLoad(ppd, colormodel, header->MediaType,
	                            resolution, "LightCyan", logfunc, ld);
        DitherLuts[2] = ppdLutLoad(ppd, colormodel, header->MediaType,
	                            resolution, "Magenta", logfunc, ld);
        DitherLuts[3] = ppdLutLoad(ppd, colormodel, header->MediaType,
	                            resolution, "LightMagenta", logfunc, ld);
        DitherLuts[4] = ppdLutLoad(ppd, colormodel, header->MediaType,
	                            resolution, "Yellow", logfunc, ld);
        DitherLuts[5] = ppdLutLoad(ppd, colormodel, header->MediaType,
	                            resolution, "Black", logfunc, ld);
        DitherLuts[6] = ppdLutLoad(ppd, colormodel, header->MediaType,
	                            resolution, "LightBlack", logfunc, ld);
        break;
    default : // ERROR
        fputs("ERROR: Unexpected number of channels\n", stderr);
        exit(1);
  }

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

    if (!DitherLuts[plane])
      DitherLuts[plane] = cfLutNew(2, default_lut, logfunc, ld);
  }

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

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

    //
    // Disable status reporting...
    //

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

      cfWritePrintData("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...
	//

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

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

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

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

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

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

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

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

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

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

	cfWritePrintData("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.
	//

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

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

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

	cfWritePrintData("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...
      //

      cfWritePrintData("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.
      //

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

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

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

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

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

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

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

    //
    // Exit remote mode...
    //

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

  //
  // Enter graphics mode...
  //

  cfWritePrintData("\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)
  {
    cfWritePrintData("\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
  {
    cfWritePrintData("\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)...
    //

    cfWritePrintData("\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
  {
    cfWritePrintData("\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)
  {
    cfWritePrintData("\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
  {
    cfWritePrintData("\033(c\004\000", 5);

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

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

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

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

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

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

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

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

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

  if ((attr = ppdFindColorAttr(ppd, "cupsESCPDotSize", colormodel,
                           header->MediaType, resolution, spec,
			   sizeof(spec), logfunc, ld)) != 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.
      //

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

      cfWritePrintData("\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...
  //

  cfWritePrintData("\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 ++)
  {
    cfDitherDelete(DitherStates[i]);
    cfLutDelete(DitherLuts[i]);
  }

  free(OutputBuffers[0]);

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

  cfCMYKDelete(CMYK);

  if (RGB)
  {
    cfRGBDelete(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...
    //

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

    //
    // LoadXS defaults...
    //

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

    //
    // Exit remote mode...
    //

    cfWritePrintData("\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)
      cfWritePrintData("\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);
  }

  cfWritePrintData(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)
  {
    cfWritePrintData("\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)
	{
	  cfRGBDoGray(RGB, PixelBuffer, CMYKBuffer, width);
	  cfCMYKDoCMYK(CMYK, CMYKBuffer, InputBuffer, width);
	}
	else
          cfCMYKDoGray(CMYK, PixelBuffer, InputBuffer, width);
	break;

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

    default :
    case CUPS_CSPACE_RGB :
        if (RGB)
	{
	  cfRGBDoRGB(RGB, PixelBuffer, CMYKBuffer, width);
	  cfCMYKDoCMYK(CMYK, CMYKBuffer, InputBuffer, width);
	}
	else
          cfCMYKDoRGB(CMYK, PixelBuffer, InputBuffer, width);
	break;

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

  //
  // Dither the pixels...
  //

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

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

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

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

      if (OutputFeed > 0)
      {
	cfWritePrintData("\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)
	  cfPackHorizontal(OutputBuffers[plane] + pass,
	                     band->buffer + offset, subwidth, 0, DotColStep);
        else
	  cfPackHorizontal2(OutputBuffers[plane] + pass,
	                      band->buffer + offset, subwidth, DotColStep);

        band->row ++;
	band->dirty |= !cfCheckBytes(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
  int empty = 1;
  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


  //
  // Log function for the library functions, standard CUPS logging to stderr...
  //

  logfunc = cfCUPSLogFunc;
  ld = NULL;

  //
  // 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);
  ppdMarkOptions(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

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

  page = 0;

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

    if (empty)
    {
      //
      // Initialize the print device...
      //

      Setup(ppd);
      empty = 0;
    }

    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;
  }

  if (!empty)
    Shutdown(ppd);

  cupsFreeOptions(num_options, options);

  cupsRasterClose(ras);

  if (fd != 0)
    close(fd);
  
  if (DotBuffers[0] != NULL)
    free(DotBuffers[0]);

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