/*
 * Label printer filter for CUPS.
 *
 * Copyright © 2007-2019 by Apple Inc.
 * Copyright © 2001-2007 by Easy Software Products.
 *
 * Licensed under Apache License v2.0.  See the file "LICENSE" for more
 * information.
 */

/*
 * Include necessary headers...
 */

#include <cups/cups.h>
#include <cups/ppd.h>
#include <cups/string-private.h>
#include <cups/language-private.h>
#include <cups/raster.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>


/*
 * This driver filter currently supports DYMO, Intellitech, and Zebra
 * label printers.
 *
 * The DYMO portion of the driver has been tested with the 300, 330,
 * 330 Turbo, and 450 Twin Turbo label printers; it may also work with other
 * models.  The DYMO printers support printing at 136, 203, and 300 DPI.
 *
 * The Intellitech portion of the driver has been tested with the
 * Intellibar 408, 412, and 808 and supports their PCL variant.
 *
 * The Zebra portion of the driver has been tested with the LP-2844,
 * LP-2844Z, QL-320, and QL-420 label printers; it may also work with
 * other models.  The driver supports EPL line mode, EPL page mode,
 * ZPL, and CPCL as defined in Zebra's online developer documentation.
 */

/*
 * Model number constants...
 */

#define DYMO_3x0	0		/* DYMO Labelwriter 300/330/330 Turbo */

#define ZEBRA_EPL_LINE	0x10		/* Zebra EPL line mode printers */
#define ZEBRA_EPL_PAGE	0x11		/* Zebra EPL page mode printers */
#define ZEBRA_ZPL	0x12		/* Zebra ZPL-based printers */
#define ZEBRA_CPCL	0x13		/* Zebra CPCL-based printers */

#define INTELLITECH_PCL	0x20		/* Intellitech PCL-based printers */


/*
 * Globals...
 */

unsigned char	*Buffer;		/* Output buffer */
unsigned char	*CompBuffer;		/* Compression buffer */
unsigned char	*LastBuffer;		/* Last buffer */
unsigned	Feed;			/* Number of lines to skip */
int		LastSet;		/* Number of repeat characters */
int		ModelNumber,		/* cupsModelNumber attribute */
		Page,			/* Current page */
		Canceled;		/* Non-zero if job is canceled */


/*
 * Prototypes...
 */

void	Setup(ppd_file_t *ppd);
void	StartPage(ppd_file_t *ppd, cups_page_header2_t *header);
void	EndPage(ppd_file_t *ppd, cups_page_header2_t *header);
void	CancelJob(int sig);
void	OutputLine(ppd_file_t *ppd, cups_page_header2_t *header, unsigned y);
void	PCLCompress(unsigned char *line, unsigned length);
void	ZPLCompress(unsigned char repeat_char, unsigned repeat_count);


/*
 * 'Setup()' - Prepare the printer for printing.
 */

void
Setup(ppd_file_t *ppd)			/* I - PPD file */
{
  int		i;			/* Looping var */


 /*
  * Get the model number from the PPD file...
  */

  if (ppd)
    ModelNumber = ppd->model_number;

 /*
  * Initialize based on the model number...
  */

  switch (ModelNumber)
  {
    case DYMO_3x0 :
       /*
	* Clear any remaining data...
	*/

	for (i = 0; i < 100; i ++)
	  putchar(0x1b);

       /*
	* Reset the printer...
	*/

	fputs("\033@", stdout);
	break;

    case ZEBRA_EPL_LINE :
	break;

    case ZEBRA_EPL_PAGE :
	break;

    case ZEBRA_ZPL :
        break;

    case ZEBRA_CPCL :
        break;

    case INTELLITECH_PCL :
       /*
	* Send a PCL reset sequence.
	*/

	putchar(0x1b);
	putchar('E');
        break;
  }
}


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

void
StartPage(ppd_file_t         *ppd,	/* I - PPD file */
          cups_page_header2_t *header)	/* I - Page header */
{
  ppd_choice_t	*choice;		/* Marked choice */
  unsigned	length;			/* Actual label length */


 /*
  * Show page device dictionary...
  */

  fprintf(stderr, "DEBUG: StartPage...\n");
  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: 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: NumCopies = %d\n", header->NumCopies);
  fprintf(stderr, "DEBUG: Orientation = %d\n", header->Orientation);
  fprintf(stderr, "DEBUG: PageSize = [ %d %d ]\n", header->PageSize[0], header->PageSize[1]);
  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);

  switch (ModelNumber)
  {
    case DYMO_3x0 :
       /*
	* Setup printer/job attributes...
	*/

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

	printf("\033L%c%c", length >> 8, length);
	printf("\033D%c", header->cupsBytesPerLine);

	printf("\033%c", header->cupsCompression + 'c'); /* Darkness */
	printf("\033q%d", header->MediaPosition + 1);	 /* Roll Select */
	break;

    case ZEBRA_EPL_LINE :
       /*
        * Set print rate...
	*/

	if ((choice = ppdFindMarkedChoice(ppd, "zePrintRate")) != NULL &&
	    strcmp(choice->choice, "Default"))
	  printf("\033S%.0f", atof(choice->choice) * 2.0 - 2.0);

       /*
        * Set darkness...
	*/

        if (header->cupsCompression > 0 && header->cupsCompression <= 100)
	  printf("\033D%d", 7 * header->cupsCompression / 100);

       /*
        * Set left margin to 0...
	*/

	fputs("\033M01", stdout);

       /*
        * Start buffered output...
	*/

        fputs("\033B", stdout);
        break;

    case ZEBRA_EPL_PAGE :
       /*
        * Start a new label...
	*/

        puts("");
	puts("N");

       /*
        * Set hardware options...
	*/

	if (!strcmp(header->MediaType, "Direct"))
	  puts("OD");

       /*
        * Set print rate...
	*/

	if ((choice = ppdFindMarkedChoice(ppd, "zePrintRate")) != NULL &&
	    strcmp(choice->choice, "Default"))
	{
	  double val = atof(choice->choice);

	  if (val >= 3.0)
	    printf("S%.0f\n", val);
	  else
	    printf("S%.0f\n", val * 2.0 - 2.0);
        }

       /*
        * Set darkness...
	*/

        if (header->cupsCompression > 0 && header->cupsCompression <= 100)
	  printf("D%u\n", 15 * header->cupsCompression / 100);

       /*
        * Set label size...
	*/

        printf("q%u\n", (header->cupsWidth + 7) & ~7U);
        break;

    case ZEBRA_ZPL :
       /*
        * Set darkness...
	*/

        if (header->cupsCompression > 0 && header->cupsCompression <= 100)
	  printf("~SD%02u\n", 30 * header->cupsCompression / 100);

       /*
        * Start bitmap graphics...
	*/

        printf("~DGR:CUPS.GRF,%u,%u,\n",
	       header->cupsHeight * header->cupsBytesPerLine,
	       header->cupsBytesPerLine);

       /*
        * Allocate compression buffers...
	*/

	CompBuffer = malloc(2 * header->cupsBytesPerLine + 1);
	LastBuffer = malloc(header->cupsBytesPerLine);
	LastSet    = 0;
        break;

    case ZEBRA_CPCL :
       /*
        * Start label...
	*/

        printf("! 0 %u %u %u %u\r\n", header->HWResolution[0],
	       header->HWResolution[1], header->cupsHeight,
	       header->NumCopies);
	printf("PAGE-WIDTH %u\r\n", header->cupsWidth);
	printf("PAGE-HEIGHT %u\r\n", header->cupsHeight);
        break;

    case INTELLITECH_PCL :
       /*
        * Set the media size...
	*/

	printf("\033&l6D\033&k12H");	/* Set 6 LPI, 10 CPI */
	printf("\033&l0O");		/* Set portrait orientation */

	switch (header->PageSize[1])
	{
	  case 540 : /* Monarch Envelope */
              printf("\033&l80A");	/* Set page size */
	      break;

	  case 624 : /* DL Envelope */
              printf("\033&l90A");	/* Set page size */
	      break;

	  case 649 : /* C5 Envelope */
              printf("\033&l91A");	/* Set page size */
	      break;

	  case 684 : /* COM-10 Envelope */
              printf("\033&l81A");	/* Set page size */
	      break;

	  case 756 : /* Executive */
              printf("\033&l1A");	/* Set page size */
	      break;

	  case 792 : /* Letter */
              printf("\033&l2A");	/* Set page size */
	      break;

	  case 842 : /* A4 */
              printf("\033&l26A");	/* Set page size */
	      break;

	  case 1008 : /* Legal */
              printf("\033&l3A");	/* Set page size */
	      break;

          default : /* Custom size */
	      printf("\033!f%uZ", header->PageSize[1] * 300 / 72);
	      break;
	}

	printf("\033&l%uP",		/* Set page length */
               header->PageSize[1] / 12);
	printf("\033&l0E");		/* Set top margin to 0 */
        if (header->NumCopies)
	  printf("\033&l%uX", header->NumCopies);
					/* Set number copies */
        printf("\033&l0L");		/* Turn off perforation skip */

       /*
        * Print settings...
	*/

	if (Page == 1)
	{
          if (header->cupsRowFeed)	/* inPrintRate */
	    printf("\033!p%uS", header->cupsRowFeed);

          if (header->cupsCompression != ~0U)
	  				/* inPrintDensity */
	    printf("\033&d%dA", 30 * header->cupsCompression / 100 - 15);

	  if ((choice = ppdFindMarkedChoice(ppd, "inPrintMode")) != NULL)
	  {
	    if (!strcmp(choice->choice, "Standard"))
	      fputs("\033!p0M", stdout);
	    else if (!strcmp(choice->choice, "Tear"))
	    {
	      fputs("\033!p1M", stdout);

              if (header->cupsRowCount)	/* inTearInterval */
		printf("\033!n%uT", header->cupsRowCount);
            }
	    else
	    {
	      fputs("\033!p2M", stdout);

              if (header->cupsRowStep)	/* inCutInterval */
		printf("\033!n%uC", header->cupsRowStep);
            }
	  }
        }

       /*
	* Setup graphics...
	*/

	printf("\033*t%uR", header->HWResolution[0]);
					/* Set resolution */

	printf("\033*r%uS", header->cupsWidth);
					/* Set width */
	printf("\033*r%uT", header->cupsHeight);
					/* Set height */

	printf("\033&a0H");		/* Set horizontal position */
	printf("\033&a0V");		/* Set vertical position */
        printf("\033*r1A");		/* Start graphics */
        printf("\033*b3M");		/* Set compression */

       /*
        * Allocate compression buffers...
	*/

	CompBuffer = malloc(2 * header->cupsBytesPerLine + 1);
	LastBuffer = malloc(header->cupsBytesPerLine);
	LastSet    = 0;
        break;
  }

 /*
  * Allocate memory for a line of graphics...
  */

  Buffer = malloc(header->cupsBytesPerLine);
  Feed   = 0;
}


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

void
EndPage(ppd_file_t          *ppd,	/* I - PPD file */
        cups_page_header2_t *header)	/* I - Page header */
{
  int		val;			/* Option value */
  ppd_choice_t	*choice;		/* Marked choice */


  switch (ModelNumber)
  {
    case DYMO_3x0 :
       /*
	* Eject the current page...
	*/

	fputs("\033E", stdout);
	break;

    case ZEBRA_EPL_LINE :
       /*
        * End buffered output, eject the label...
	*/

        fputs("\033E\014", stdout);
	break;

    case ZEBRA_EPL_PAGE :
       /*
        * Print the label...
	*/

        puts("P1");

       /*
        * Cut the label as needed...
        */

      	if (header->CutMedia)
	  puts("C");
	break;

    case ZEBRA_ZPL :
        if (Canceled)
	{
	 /*
	  * Cancel bitmap download...
	  */

	  puts("~DN");
	  break;
	}

       /*
        * Start label...
	*/

        puts("^XA");

       /*
        * Rotate 180 degrees so that the top of the label/page is at the
	* leading edge...
	*/

	puts("^POI");

       /*
        * Set print width...
	*/

        printf("^PW%u\n", header->cupsWidth);

       /*
        * Set print rate...
	*/

	if ((choice = ppdFindMarkedChoice(ppd, "zePrintRate")) != NULL &&
	    strcmp(choice->choice, "Default"))
	{
	  val = atoi(choice->choice);
	  printf("^PR%d,%d,%d\n", val, val, val);
	}

       /*
        * Put label home in default position (0,0)...
        */

	printf("^LH0,0\n");

       /*
        * Set media tracking...
	*/

	if (ppdIsMarked(ppd, "zeMediaTracking", "Continuous"))
	{
         /*
	  * Add label length command for continuous...
	  */

	  printf("^LL%d\n", header->cupsHeight);
	  printf("^MNN\n");
	}
	else if (ppdIsMarked(ppd, "zeMediaTracking", "Web"))
          printf("^MNY\n");
	else if (ppdIsMarked(ppd, "zeMediaTracking", "Mark"))
	  printf("^MNM\n");

       /*
        * Set label top
	*/

	if (header->cupsRowStep != 200)
	  printf("^LT%d\n", header->cupsRowStep);

       /*
        * Set media type...
	*/

	if (!strcmp(header->MediaType, "Thermal"))
	  printf("^MTT\n");
	else if (!strcmp(header->MediaType, "Direct"))
	  printf("^MTD\n");

       /*
        * Set print mode...
	*/

	if ((choice = ppdFindMarkedChoice(ppd, "zePrintMode")) != NULL &&
	    strcmp(choice->choice, "Saved"))
	{
	  printf("^MM");

	  if (!strcmp(choice->choice, "Tear"))
	    printf("T,Y\n");
	  else if (!strcmp(choice->choice, "Peel"))
	    printf("P,Y\n");
	  else if (!strcmp(choice->choice, "Rewind"))
	    printf("R,Y\n");
	  else if (!strcmp(choice->choice, "Applicator"))
	    printf("A,Y\n");
	  else
	    printf("C,Y\n");
	}

       /*
        * Set tear-off adjust position...
	*/

	if (header->AdvanceDistance != 1000)
	{
	  if ((int)header->AdvanceDistance < 0)
	    printf("~TA%04d\n", (int)header->AdvanceDistance);
	  else
	    printf("~TA%03d\n", (int)header->AdvanceDistance);
	}

       /*
        * Allow for reprinting after an error...
	*/

	if (ppdIsMarked(ppd, "zeErrorReprint", "Always"))
	  printf("^JZY\n");
	else if (ppdIsMarked(ppd, "zeErrorReprint", "Never"))
	  printf("^JZN\n");

       /*
        * Print multiple copies
	*/

	if (header->NumCopies > 1)
	  printf("^PQ%d, 0, 0, N\n", header->NumCopies);

       /*
        * Display the label image...
	*/

	puts("^FO0,0^XGR:CUPS.GRF,1,1^FS");

       /*
        * End the label and eject...
	*/

	puts("^XZ");

       /*
        * Delete the label image...
        */

	puts("^XA");
        puts("^IDR:CUPS.GRF^FS");
	puts("^XZ");

       /*
        * Cut the label as needed...
        */

      	if (header->CutMedia)
	  puts("^CN1");
        break;

    case ZEBRA_CPCL :
       /*
        * Set tear-off adjust position...
	*/

	if (header->AdvanceDistance != 1000)
          printf("PRESENT-AT %d 1\r\n", (int)header->AdvanceDistance);

       /*
        * Allow for reprinting after an error...
	*/

	if (ppdIsMarked(ppd, "zeErrorReprint", "Always"))
	  puts("ON-OUT-OF-PAPER WAIT\r");
	else if (ppdIsMarked(ppd, "zeErrorReprint", "Never"))
	  puts("ON-OUT-OF-PAPER PURGE\r");

       /*
        * Cut label?
	*/

	if (header->CutMedia)
	  puts("CUT\r");

       /*
        * Set darkness...
	*/

	if (header->cupsCompression > 0)
	  printf("TONE %u\r\n", 2 * header->cupsCompression);

       /*
        * Set print rate...
	*/

	if ((choice = ppdFindMarkedChoice(ppd, "zePrintRate")) != NULL &&
	    strcmp(choice->choice, "Default"))
	{
	  val = atoi(choice->choice);
	  printf("SPEED %d\r\n", val);
	}

       /*
        * Print the label...
	*/

	if ((choice = ppdFindMarkedChoice(ppd, "zeMediaTracking")) == NULL ||
	    strcmp(choice->choice, "Continuous"))
          puts("FORM\r");

	puts("PRINT\r");
	break;

    case INTELLITECH_PCL :
        printf("\033*rB");		/* End GFX */
        printf("\014");			/* Eject current page */
        break;
  }

  fflush(stdout);

 /*
  * Free memory...
  */

  free(Buffer);

  if (CompBuffer)
  {
    free(CompBuffer);
    CompBuffer = NULL;
  }

  if (LastBuffer)
  {
    free(LastBuffer);
    LastBuffer = NULL;
  }
}


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

void
CancelJob(int sig)			/* I - Signal */
{
 /*
  * Tell the main loop to stop...
  */

  (void)sig;

  Canceled = 1;
}


/*
 * 'OutputLine()' - Output a line of graphics...
 */

void
OutputLine(ppd_file_t         *ppd,	/* I - PPD file */
           cups_page_header2_t *header,	/* I - Page header */
           unsigned           y)	/* I - Line number */
{
  unsigned	i;			/* Looping var */
  unsigned char	*ptr;			/* Pointer into buffer */
  unsigned char	*compptr;		/* Pointer into compression buffer */
  unsigned char	repeat_char;		/* Repeated character */
  unsigned	repeat_count;		/* Number of repeated characters */
  static const unsigned char *hex = (const unsigned char *)"0123456789ABCDEF";
					/* Hex digits */


  (void)ppd;

  switch (ModelNumber)
  {
    case DYMO_3x0 :
       /*
	* See if the line is blank; if not, write it to the printer...
	*/

	if (Buffer[0] ||
            memcmp(Buffer, Buffer + 1, header->cupsBytesPerLine - 1))
	{
          if (Feed)
	  {
	    while (Feed > 255)
	    {
	      printf("\033f\001%c", 255);
	      Feed -= 255;
	    }

	    printf("\033f\001%c", Feed);
	    Feed = 0;
          }

          putchar(0x16);
	  fwrite(Buffer, header->cupsBytesPerLine, 1, stdout);
	  fflush(stdout);
	}
	else
          Feed ++;
	break;

    case ZEBRA_EPL_LINE :
        printf("\033g%03d", header->cupsBytesPerLine);
	fwrite(Buffer, 1, header->cupsBytesPerLine, stdout);
	fflush(stdout);
        break;

    case ZEBRA_EPL_PAGE :
        if (Buffer[0] || memcmp(Buffer, Buffer + 1, header->cupsBytesPerLine))
	{
          printf("GW0,%d,%d,1\n", y, header->cupsBytesPerLine);
	  for (i = header->cupsBytesPerLine, ptr = Buffer; i > 0; i --, ptr ++)
	    putchar(~*ptr);
	  putchar('\n');
	  fflush(stdout);
	}
        break;

    case ZEBRA_ZPL :
       /*
	* Determine if this row is the same as the previous line.
        * If so, output a ':' and return...
        */

        if (LastSet)
	{
	  if (!memcmp(Buffer, LastBuffer, header->cupsBytesPerLine))
	  {
	    putchar(':');
	    return;
	  }
	}

       /*
        * Convert the line to hex digits...
	*/

	for (ptr = Buffer, compptr = CompBuffer, i = header->cupsBytesPerLine;
	     i > 0;
	     i --, ptr ++)
        {
	  *compptr++ = hex[*ptr >> 4];
	  *compptr++ = hex[*ptr & 15];
	}

        *compptr = '\0';

       /*
        * Run-length compress the graphics...
	*/

	for (compptr = CompBuffer + 1, repeat_char = CompBuffer[0], repeat_count = 1;
	     *compptr;
	     compptr ++)
	  if (*compptr == repeat_char)
	    repeat_count ++;
	  else
	  {
	    ZPLCompress(repeat_char, repeat_count);
	    repeat_char  = *compptr;
	    repeat_count = 1;
	  }

        if (repeat_char == '0')
	{
	 /*
	  * Handle 0's on the end of the line...
	  */

	  if (repeat_count & 1)
	  {
	    repeat_count --;
	    putchar('0');
	  }

          if (repeat_count > 0)
	    putchar(',');
	}
	else
	  ZPLCompress(repeat_char, repeat_count);

	fflush(stdout);

       /*
        * Save this line for the next round...
	*/

	memcpy(LastBuffer, Buffer, header->cupsBytesPerLine);
	LastSet = 1;
        break;

    case ZEBRA_CPCL :
        if (Buffer[0] || memcmp(Buffer, Buffer + 1, header->cupsBytesPerLine))
	{
	  printf("CG %u 1 0 %d ", header->cupsBytesPerLine, y);
          fwrite(Buffer, 1, header->cupsBytesPerLine, stdout);
	  puts("\r");
	  fflush(stdout);
	}
	break;

    case INTELLITECH_PCL :
	if (Buffer[0] ||
            memcmp(Buffer, Buffer + 1, header->cupsBytesPerLine - 1))
        {
	  if (Feed)
	  {
	    printf("\033*b%dY", Feed);
	    Feed    = 0;
	    LastSet = 0;
	  }

          PCLCompress(Buffer, header->cupsBytesPerLine);
	}
	else
	  Feed ++;
        break;
  }
}


/*
 * 'PCLCompress()' - Output a PCL (mode 3) compressed line.
 */

void
PCLCompress(unsigned char *line,	/* I - Line to compress */
            unsigned      length)	/* I - Length of line */
{
  unsigned char	*line_ptr,		/* Current byte pointer */
        	*line_end,		/* End-of-line byte pointer */
        	*comp_ptr,		/* Pointer into compression buffer */
        	*start,			/* Start of compression sequence */
		*seed;			/* Seed buffer pointer */
  unsigned	count,			/* Count of bytes for output */
		offset;			/* Offset of bytes for output */


 /*
  * Do delta-row compression...
  */

  line_ptr = line;
  line_end = line + length;

  comp_ptr = CompBuffer;
  seed     = LastBuffer;

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

    start = line_ptr;

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

      offset = 0;

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

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

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

      if (line_ptr == line_end)
        break;

      offset = (unsigned)(line_ptr - start);

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

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

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

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

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

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

      *comp_ptr++ = (unsigned char)offset;
    }
    else
    {
     /*
      * Output single-byte offset...
      */

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

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

 /*
  * Set the length of the data and write it...
  */

  printf("\033*b%dW", (int)(comp_ptr - CompBuffer));
  fwrite(CompBuffer, (size_t)(comp_ptr - CompBuffer), 1, stdout);

 /*
  * Save this line as a "seed" buffer for the next...
  */

  memcpy(LastBuffer, line, length);
  LastSet = 1;
}


/*
 * 'ZPLCompress()' - Output a run-length compression sequence.
 */

void
ZPLCompress(unsigned char repeat_char,	/* I - Character to repeat */
	    unsigned      repeat_count)	/* I - Number of repeated characters */
{
  if (repeat_count > 1)
  {
   /*
    * Print as many z's as possible - they are the largest denomination
    * representing 400 characters (zC stands for 400 adjacent C's)
    */

    while (repeat_count >= 400)
    {
      putchar('z');
      repeat_count -= 400;
    }

   /*
    * Then print 'g' through 'y' as multiples of 20 characters...
    */

    if (repeat_count >= 20)
    {
      putchar((int)('f' + repeat_count / 20));
      repeat_count %= 20;
    }

   /*
    * Finally, print 'G' through 'Y' as 1 through 19 characters...
    */

    if (repeat_count > 0)
      putchar((int)('F' + repeat_count));
  }

 /*
  * Then the character to be repeated...
  */

  putchar((int)repeat_char);
}


/*
 * '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 */
  unsigned		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)
  {
   /*
    * We don't have the correct number of arguments; write an error message
    * and return.
    */

    _cupsLangPrintFilter(stderr, "ERROR",
                         _("%s job-id user title copies options [file]"),
			 "rastertolabel");
    return (1);
  }

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

  if (argc == 7)
  {
    if ((fd = open(argv[6], O_RDONLY)) == -1)
    {
      _cupsLangPrintError("ERROR", _("Unable to open raster file"));
      sleep(1);
      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 */

 /*
  * Open the PPD file and apply options...
  */

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

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

    _cupsLangPrintFilter(stderr, "ERROR",
                         _("The PPD file could not be opened."));

    status = ppdLastError(&linenum);

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

    return (1);
  }

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

 /*
  * 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);
    _cupsLangPrintFilter(stderr, "INFO", _("Starting page %d."), Page);

   /*
    * Start the page...
    */

    StartPage(ppd, &header);

   /*
    * Loop for each line on the page...
    */

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

      if (Canceled)
	break;

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

     /*
      * Read a line of graphics...
      */

      if (cupsRasterReadPixels(ras, Buffer, header.cupsBytesPerLine) < 1)
        break;

     /*
      * Write it to the printer...
      */

      OutputLine(ppd, &header, y);
    }

   /*
    * Eject the page...
    */

    _cupsLangPrintFilter(stderr, "INFO", _("Finished page %d."), Page);

    EndPage(ppd, &header);

    if (Canceled)
      break;
  }

 /*
  * Close the raster stream...
  */

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

 /*
  * Close the PPD file and free the options...
  */

  ppdClose(ppd);
  cupsFreeOptions(num_options, options);

 /*
  * If no pages were printed, send an error message...
  */

  if (Page == 0)
  {
    _cupsLangPrintFilter(stderr, "ERROR", _("No pages were found."));
    return (1);
  }
  else
    return (0);
}
