1 /*
2  * Hewlett-Packard Page Control Language filter for CUPS.
3  *
4  * Copyright 2007-2015 by Apple Inc.
5  * Copyright 1993-2007 by Easy Software Products.
6  *
7  * Licensed under Apache License v2.0.  See the file "LICENSE" for more information.
8  */
9 
10 /*
11  * Include necessary headers...
12  */
13 
14 #include <cups/cups.h>
15 #include <cups/ppd.h>
16 #include <cups/string-private.h>
17 #include <cups/language-private.h>
18 #include <cups/raster.h>
19 #include <unistd.h>
20 #include <fcntl.h>
21 #include <signal.h>
22 
23 
24 /*
25  * Globals...
26  */
27 
28 unsigned char	*Planes[4],		/* Output buffers */
29 		*CompBuffer,		/* Compression buffer */
30 		*BitBuffer;		/* Buffer for output bits */
31 unsigned 	NumPlanes,		/* Number of color planes */
32 		ColorBits,		/* Number of bits per color */
33 		Feed;			/* Number of lines to skip */
34 cups_bool_t	Duplex;			/* Current duplex mode */
35 int		Page,			/* Current page number */
36 		Canceled;		/* Has the current job been canceled? */
37 
38 
39 /*
40  * Prototypes...
41  */
42 
43 void	Setup(void);
44 void	StartPage(ppd_file_t *ppd, cups_page_header2_t *header);
45 void	EndPage(void);
46 void	Shutdown(void);
47 
48 void	CancelJob(int sig);
49 void	CompressData(unsigned char *line, unsigned length, unsigned plane, unsigned type);
50 void	OutputLine(cups_page_header2_t *header);
51 
52 
53 /*
54  * 'Setup()' - Prepare the printer for printing.
55  */
56 
57 void
Setup(void)58 Setup(void)
59 {
60  /*
61   * Send a PCL reset sequence.
62   */
63 
64   putchar(0x1b);
65   putchar('E');
66 }
67 
68 
69 /*
70  * 'StartPage()' - Start a page of graphics.
71  */
72 
73 void
StartPage(ppd_file_t * ppd,cups_page_header2_t * header)74 StartPage(ppd_file_t         *ppd,	/* I - PPD file */
75           cups_page_header2_t *header)	/* I - Page header */
76 {
77   unsigned	plane;			/* Looping var */
78 
79 
80  /*
81   * Show page device dictionary...
82   */
83 
84   fprintf(stderr, "DEBUG: StartPage...\n");
85   fprintf(stderr, "DEBUG: Duplex = %d\n", header->Duplex);
86   fprintf(stderr, "DEBUG: HWResolution = [ %d %d ]\n", header->HWResolution[0], header->HWResolution[1]);
87   fprintf(stderr, "DEBUG: ImagingBoundingBox = [ %d %d %d %d ]\n", header->ImagingBoundingBox[0], header->ImagingBoundingBox[1], header->ImagingBoundingBox[2], header->ImagingBoundingBox[3]);
88   fprintf(stderr, "DEBUG: Margins = [ %d %d ]\n", header->Margins[0], header->Margins[1]);
89   fprintf(stderr, "DEBUG: ManualFeed = %d\n", header->ManualFeed);
90   fprintf(stderr, "DEBUG: MediaPosition = %d\n", header->MediaPosition);
91   fprintf(stderr, "DEBUG: NumCopies = %d\n", header->NumCopies);
92   fprintf(stderr, "DEBUG: Orientation = %d\n", header->Orientation);
93   fprintf(stderr, "DEBUG: PageSize = [ %d %d ]\n", header->PageSize[0], header->PageSize[1]);
94   fprintf(stderr, "DEBUG: cupsWidth = %d\n", header->cupsWidth);
95   fprintf(stderr, "DEBUG: cupsHeight = %d\n", header->cupsHeight);
96   fprintf(stderr, "DEBUG: cupsMediaType = %d\n", header->cupsMediaType);
97   fprintf(stderr, "DEBUG: cupsBitsPerColor = %d\n", header->cupsBitsPerColor);
98   fprintf(stderr, "DEBUG: cupsBitsPerPixel = %d\n", header->cupsBitsPerPixel);
99   fprintf(stderr, "DEBUG: cupsBytesPerLine = %d\n", header->cupsBytesPerLine);
100   fprintf(stderr, "DEBUG: cupsColorOrder = %d\n", header->cupsColorOrder);
101   fprintf(stderr, "DEBUG: cupsColorSpace = %d\n", header->cupsColorSpace);
102   fprintf(stderr, "DEBUG: cupsCompression = %d\n", header->cupsCompression);
103 
104  /*
105   * Setup printer/job attributes...
106   */
107 
108   Duplex    = header->Duplex;
109   ColorBits = header->cupsBitsPerColor;
110 
111   if ((!Duplex || (Page & 1)) && header->MediaPosition)
112     printf("\033&l%dH",				/* Set media position */
113            header->MediaPosition);
114 
115   if (Duplex && ppd && ppd->model_number == 2)
116   {
117    /*
118     * Handle duplexing on new DeskJet printers...
119     */
120 
121     printf("\033&l-2H");			/* Load media */
122 
123     if (Page & 1)
124       printf("\033&l2S");			/* Set duplex mode */
125   }
126 
127   if (!Duplex || (Page & 1) || (ppd && ppd->model_number == 2))
128   {
129    /*
130     * Set the media size...
131     */
132 
133     printf("\033&l6D\033&k12H");		/* Set 6 LPI, 10 CPI */
134     printf("\033&l0O");				/* Set portrait orientation */
135 
136     switch (header->PageSize[1])
137     {
138       case 540 : /* Monarch Envelope */
139           printf("\033&l80A");			/* Set page size */
140 	  break;
141 
142       case 595 : /* A5 */
143           printf("\033&l25A");			/* Set page size */
144 	  break;
145 
146       case 624 : /* DL Envelope */
147           printf("\033&l90A");			/* Set page size */
148 	  break;
149 
150       case 649 : /* C5 Envelope */
151           printf("\033&l91A");			/* Set page size */
152 	  break;
153 
154       case 684 : /* COM-10 Envelope */
155           printf("\033&l81A");			/* Set page size */
156 	  break;
157 
158       case 709 : /* B5 Envelope */
159           printf("\033&l100A");			/* Set page size */
160 	  break;
161 
162       case 756 : /* Executive */
163           printf("\033&l1A");			/* Set page size */
164 	  break;
165 
166       case 792 : /* Letter */
167           printf("\033&l2A");			/* Set page size */
168 	  break;
169 
170       case 842 : /* A4 */
171           printf("\033&l26A");			/* Set page size */
172 	  break;
173 
174       case 1008 : /* Legal */
175           printf("\033&l3A");			/* Set page size */
176 	  break;
177 
178       case 1191 : /* A3 */
179           printf("\033&l27A");			/* Set page size */
180 	  break;
181 
182       case 1224 : /* Tabloid */
183           printf("\033&l6A");			/* Set page size */
184 	  break;
185     }
186 
187     printf("\033&l%dP",				/* Set page length */
188            header->PageSize[1] / 12);
189     printf("\033&l0E");				/* Set top margin to 0 */
190   }
191 
192   if (!Duplex || (Page & 1))
193   {
194    /*
195     * Set other job options...
196     */
197 
198     printf("\033&l%dX", header->NumCopies);	/* Set number copies */
199 
200     if (header->cupsMediaType &&
201         (!ppd || ppd->model_number != 2 || header->HWResolution[0] == 600))
202       printf("\033&l%dM",			/* Set media type */
203              header->cupsMediaType);
204 
205     if (!ppd || ppd->model_number != 2)
206     {
207       int mode = Duplex ? 1 + header->Tumble != 0 : 0;
208 
209       printf("\033&l%dS", mode);		/* Set duplex mode */
210       printf("\033&l0L");			/* Turn off perforation skip */
211     }
212   }
213   else if (!ppd || ppd->model_number != 2)
214     printf("\033&a2G");				/* Set back side */
215 
216  /*
217   * Set graphics mode...
218   */
219 
220   if (ppd && ppd->model_number == 2)
221   {
222    /*
223     * Figure out the number of color planes...
224     */
225 
226     if (header->cupsColorSpace == CUPS_CSPACE_KCMY)
227       NumPlanes = 4;
228     else
229       NumPlanes = 1;
230 
231    /*
232     * Set the resolution and top-of-form...
233     */
234 
235     printf("\033&u%dD", header->HWResolution[0]);
236 						/* Resolution */
237     printf("\033&l0e0L");			/* Reset top and don't skip */
238     printf("\033*p0Y\033*p0X");			/* Set top of form */
239 
240    /*
241     * Send 26-byte configure image data command with horizontal and
242     * vertical resolutions as well as a color count...
243     */
244 
245     printf("\033*g26W");
246     putchar(2);					/* Format 2 */
247     putchar((int)NumPlanes);			/* Output planes */
248 
249     putchar((int)(header->HWResolution[0] >> 8));/* Black resolution */
250     putchar((int)header->HWResolution[0]);
251     putchar((int)(header->HWResolution[1] >> 8));
252     putchar((int)header->HWResolution[1]);
253     putchar(0);
254     putchar(1 << ColorBits);			/* # of black levels */
255 
256     putchar((int)(header->HWResolution[0] >> 8));/* Cyan resolution */
257     putchar((int)header->HWResolution[0]);
258     putchar((int)(header->HWResolution[1] >> 8));
259     putchar((int)header->HWResolution[1]);
260     putchar(0);
261     putchar(1 << ColorBits);			/* # of cyan levels */
262 
263     putchar((int)(header->HWResolution[0] >> 8));/* Magenta resolution */
264     putchar((int)header->HWResolution[0]);
265     putchar((int)(header->HWResolution[1] >> 8));
266     putchar((int)header->HWResolution[1]);
267     putchar(0);
268     putchar(1 << ColorBits);			/* # of magenta levels */
269 
270     putchar((int)(header->HWResolution[0] >> 8));/* Yellow resolution */
271     putchar((int)header->HWResolution[0]);
272     putchar((int)(header->HWResolution[1] >> 8));
273     putchar((int)header->HWResolution[1]);
274     putchar(0);
275     putchar(1 << ColorBits);			/* # of yellow levels */
276 
277     printf("\033&l0H");				/* Set media position */
278   }
279   else
280   {
281     printf("\033*t%uR", header->HWResolution[0]);
282 						/* Set resolution */
283 
284     if (header->cupsColorSpace == CUPS_CSPACE_KCMY)
285     {
286       NumPlanes = 4;
287       printf("\033*r-4U");			/* Set KCMY graphics */
288     }
289     else if (header->cupsColorSpace == CUPS_CSPACE_CMY)
290     {
291       NumPlanes = 3;
292       printf("\033*r-3U");			/* Set CMY graphics */
293     }
294     else
295       NumPlanes = 1;				/* Black&white graphics */
296 
297    /*
298     * Set size and position of graphics...
299     */
300 
301     printf("\033*r%uS", header->cupsWidth);	/* Set width */
302     printf("\033*r%uT", header->cupsHeight);	/* Set height */
303 
304     printf("\033&a0H");				/* Set horizontal position */
305 
306     if (ppd)
307       printf("\033&a%.0fV", 			/* Set vertical position */
308              10.0 * (ppd->sizes[0].length - ppd->sizes[0].top));
309     else
310       printf("\033&a0V");			/* Set top-of-page */
311   }
312 
313   printf("\033*r1A");				/* Start graphics */
314 
315   if (header->cupsCompression)
316     printf("\033*b%uM",				/* Set compression */
317            header->cupsCompression);
318 
319   Feed = 0;					/* No blank lines yet */
320 
321  /*
322   * Allocate memory for a line of graphics...
323   */
324 
325   if ((Planes[0] = malloc(header->cupsBytesPerLine + NumPlanes)) == NULL)
326   {
327     fputs("ERROR: Unable to allocate memory\n", stderr);
328     exit(1);
329   }
330 
331   for (plane = 1; plane < NumPlanes; plane ++)
332     Planes[plane] = Planes[0] + plane * header->cupsBytesPerLine / NumPlanes;
333 
334   if (ColorBits > 1)
335     BitBuffer = malloc((size_t)ColorBits * (((size_t)header->cupsWidth + 7) / 8));
336   else
337     BitBuffer = NULL;
338 
339   if (header->cupsCompression)
340     CompBuffer = malloc(header->cupsBytesPerLine * 2 + 2);
341   else
342     CompBuffer = NULL;
343 }
344 
345 
346 /*
347  * 'EndPage()' - Finish a page of graphics.
348  */
349 
350 void
EndPage(void)351 EndPage(void)
352 {
353  /*
354   * Eject the current page...
355   */
356 
357   if (NumPlanes > 1)
358   {
359      printf("\033*rC");			/* End color GFX */
360 
361      if (!(Duplex && (Page & 1)))
362        printf("\033&l0H");		/* Eject current page */
363   }
364   else
365   {
366      printf("\033*r0B");		/* End GFX */
367 
368      if (!(Duplex && (Page & 1)))
369        printf("\014");			/* Eject current page */
370   }
371 
372   fflush(stdout);
373 
374  /*
375   * Free memory...
376   */
377 
378   free(Planes[0]);
379 
380   if (BitBuffer)
381     free(BitBuffer);
382 
383   if (CompBuffer)
384     free(CompBuffer);
385 }
386 
387 
388 /*
389  * 'Shutdown()' - Shutdown the printer.
390  */
391 
392 void
Shutdown(void)393 Shutdown(void)
394 {
395  /*
396   * Send a PCL reset sequence.
397   */
398 
399   putchar(0x1b);
400   putchar('E');
401 }
402 
403 
404 /*
405  * 'CancelJob()' - Cancel the current job...
406  */
407 
408 void
CancelJob(int sig)409 CancelJob(int sig)			/* I - Signal */
410 {
411   (void)sig;
412 
413   Canceled = 1;
414 }
415 
416 
417 /*
418  * 'CompressData()' - Compress a line of graphics.
419  */
420 
421 void
CompressData(unsigned char * line,unsigned length,unsigned plane,unsigned type)422 CompressData(unsigned char *line,	/* I - Data to compress */
423              unsigned      length,	/* I - Number of bytes */
424 	     unsigned      plane,	/* I - Color plane */
425 	     unsigned      type)	/* I - Type of compression */
426 {
427   unsigned char	*line_ptr,		/* Current byte pointer */
428         	*line_end,		/* End-of-line byte pointer */
429         	*comp_ptr,		/* Pointer into compression buffer */
430         	*start;			/* Start of compression sequence */
431   unsigned	count;			/* Count of bytes for output */
432 
433 
434   switch (type)
435   {
436     default :
437        /*
438 	* Do no compression...
439 	*/
440 
441 	line_ptr = line;
442 	line_end = line + length;
443 	break;
444 
445     case 1 :
446        /*
447         * Do run-length encoding...
448         */
449 
450 	line_end = line + length;
451 	for (line_ptr = line, comp_ptr = CompBuffer;
452 	     line_ptr < line_end;
453 	     comp_ptr += 2, line_ptr += count)
454 	{
455 	  for (count = 1;
456                (line_ptr + count) < line_end &&
457 	           line_ptr[0] == line_ptr[count] &&
458         	   count < 256;
459                count ++);
460 
461 	  comp_ptr[0] = (unsigned char)(count - 1);
462 	  comp_ptr[1] = line_ptr[0];
463 	}
464 
465         line_ptr = CompBuffer;
466         line_end = comp_ptr;
467 	break;
468 
469     case 2 :
470        /*
471         * Do TIFF pack-bits encoding...
472         */
473 
474 	line_ptr = line;
475 	line_end = line + length;
476 	comp_ptr = CompBuffer;
477 
478 	while (line_ptr < line_end)
479 	{
480 	  if ((line_ptr + 1) >= line_end)
481 	  {
482 	   /*
483 	    * Single byte on the end...
484 	    */
485 
486 	    *comp_ptr++ = 0x00;
487 	    *comp_ptr++ = *line_ptr++;
488 	  }
489 	  else if (line_ptr[0] == line_ptr[1])
490 	  {
491 	   /*
492 	    * Repeated sequence...
493 	    */
494 
495 	    line_ptr ++;
496 	    count = 2;
497 
498 	    while (line_ptr < (line_end - 1) &&
499         	   line_ptr[0] == line_ptr[1] &&
500         	   count < 127)
501 	    {
502               line_ptr ++;
503               count ++;
504 	    }
505 
506 	    *comp_ptr++ = (unsigned char)(257 - count);
507 	    *comp_ptr++ = *line_ptr++;
508 	  }
509 	  else
510 	  {
511 	   /*
512 	    * Non-repeated sequence...
513 	    */
514 
515 	    start    = line_ptr;
516 	    line_ptr ++;
517 	    count    = 1;
518 
519 	    while (line_ptr < (line_end - 1) &&
520         	   line_ptr[0] != line_ptr[1] &&
521         	   count < 127)
522 	    {
523               line_ptr ++;
524               count ++;
525 	    }
526 
527 	    *comp_ptr++ = (unsigned char)(count - 1);
528 
529 	    memcpy(comp_ptr, start, count);
530 	    comp_ptr += count;
531 	  }
532 	}
533 
534         line_ptr = CompBuffer;
535         line_end = comp_ptr;
536 	break;
537   }
538 
539  /*
540   * Set the length of the data and write a raster plane...
541   */
542 
543   printf("\033*b%d%c", (int)(line_end - line_ptr), plane);
544   fwrite(line_ptr, (size_t)(line_end - line_ptr), 1, stdout);
545 }
546 
547 
548 /*
549  * 'OutputLine()' - Output a line of graphics.
550  */
551 
552 void
OutputLine(cups_page_header2_t * header)553 OutputLine(cups_page_header2_t *header)	/* I - Page header */
554 {
555   unsigned	plane,			/* Current plane */
556 		bytes,			/* Bytes to write */
557 		count;			/* Bytes to convert */
558   unsigned char	bit,			/* Current plane data */
559 		bit0,			/* Current low bit data */
560 		bit1,			/* Current high bit data */
561 		*plane_ptr,		/* Pointer into Planes */
562 		*bit_ptr;		/* Pointer into BitBuffer */
563 
564 
565  /*
566   * Output whitespace as needed...
567   */
568 
569   if (Feed > 0)
570   {
571     printf("\033*b%dY", Feed);
572     Feed = 0;
573   }
574 
575  /*
576   * Write bitmap data as needed...
577   */
578 
579   bytes = (header->cupsWidth + 7) / 8;
580 
581   for (plane = 0; plane < NumPlanes; plane ++)
582     if (ColorBits == 1)
583     {
584      /*
585       * Send bits as-is...
586       */
587 
588       CompressData(Planes[plane], bytes, plane < (NumPlanes - 1) ? 'V' : 'W',
589 		   header->cupsCompression);
590     }
591     else
592     {
593      /*
594       * Separate low and high bit data into separate buffers.
595       */
596 
597       for (count = header->cupsBytesPerLine / NumPlanes,
598                plane_ptr = Planes[plane], bit_ptr = BitBuffer;
599 	   count > 0;
600 	   count -= 2, plane_ptr += 2, bit_ptr ++)
601       {
602         bit = plane_ptr[0];
603 
604         bit0 = (unsigned char)(((bit & 64) << 1) | ((bit & 16) << 2) | ((bit & 4) << 3) | ((bit & 1) << 4));
605         bit1 = (unsigned char)((bit & 128) | ((bit & 32) << 1) | ((bit & 8) << 2) | ((bit & 2) << 3));
606 
607         if (count > 1)
608 	{
609 	  bit = plane_ptr[1];
610 
611           bit0 |= (unsigned char)((bit & 1) | ((bit & 4) >> 1) | ((bit & 16) >> 2) | ((bit & 64) >> 3));
612           bit1 |= (unsigned char)(((bit & 2) >> 1) | ((bit & 8) >> 2) | ((bit & 32) >> 3) | ((bit & 128) >> 4));
613 	}
614 
615         bit_ptr[0]     = bit0;
616 	bit_ptr[bytes] = bit1;
617       }
618 
619      /*
620       * Send low and high bits...
621       */
622 
623       CompressData(BitBuffer, bytes, 'V', header->cupsCompression);
624       CompressData(BitBuffer + bytes, bytes, plane < (NumPlanes - 1) ? 'V' : 'W',
625 		   header->cupsCompression);
626     }
627 
628   fflush(stdout);
629 }
630 
631 
632 /*
633  * 'main()' - Main entry and processing of driver.
634  */
635 
636 int					/* O - Exit status */
main(int argc,char * argv[])637 main(int  argc,				/* I - Number of command-line arguments */
638      char *argv[])			/* I - Command-line arguments */
639 {
640   int			fd;		/* File descriptor */
641   cups_raster_t		*ras;		/* Raster stream for printing */
642   cups_page_header2_t	header;		/* Page header from file */
643   unsigned		y;		/* Current line */
644   ppd_file_t		*ppd;		/* PPD file */
645 #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
646   struct sigaction action;		/* Actions for POSIX signals */
647 #endif /* HAVE_SIGACTION && !HAVE_SIGSET */
648 
649 
650  /*
651   * Make sure status messages are not buffered...
652   */
653 
654   setbuf(stderr, NULL);
655 
656  /*
657   * Check command-line...
658   */
659 
660   if (argc < 6 || argc > 7)
661   {
662    /*
663     * We don't have the correct number of arguments; write an error message
664     * and return.
665     */
666 
667     _cupsLangPrintFilter(stderr, "ERROR",
668                          _("%s job-id user title copies options [file]"),
669 			 "rastertohp");
670     return (1);
671   }
672 
673  /*
674   * Open the page stream...
675   */
676 
677   if (argc == 7)
678   {
679     if ((fd = open(argv[6], O_RDONLY)) == -1)
680     {
681       _cupsLangPrintError("ERROR", _("Unable to open raster file"));
682       sleep(1);
683       return (1);
684     }
685   }
686   else
687     fd = 0;
688 
689   ras = cupsRasterOpen(fd, CUPS_RASTER_READ);
690 
691  /*
692   * Register a signal handler to eject the current page if the
693   * job is cancelled.
694   */
695 
696   Canceled = 0;
697 
698 #ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */
699   sigset(SIGTERM, CancelJob);
700 #elif defined(HAVE_SIGACTION)
701   memset(&action, 0, sizeof(action));
702 
703   sigemptyset(&action.sa_mask);
704   action.sa_handler = CancelJob;
705   sigaction(SIGTERM, &action, NULL);
706 #else
707   signal(SIGTERM, CancelJob);
708 #endif /* HAVE_SIGSET */
709 
710  /*
711   * Initialize the print device...
712   */
713 
714   ppd = ppdOpenFile(getenv("PPD"));
715   if (!ppd)
716   {
717     ppd_status_t	status;		/* PPD error */
718     int			linenum;	/* Line number */
719 
720     _cupsLangPrintFilter(stderr, "ERROR",
721                          _("The PPD file could not be opened."));
722 
723     status = ppdLastError(&linenum);
724 
725     fprintf(stderr, "DEBUG: %s on line %d.\n", ppdErrorString(status), linenum);
726 
727     return (1);
728   }
729 
730   Setup();
731 
732  /*
733   * Process pages as needed...
734   */
735 
736   Page = 0;
737 
738   while (cupsRasterReadHeader2(ras, &header))
739   {
740    /*
741     * Write a status message with the page number and number of copies.
742     */
743 
744     if (Canceled)
745       break;
746 
747     Page ++;
748 
749     fprintf(stderr, "PAGE: %d %d\n", Page, header.NumCopies);
750     _cupsLangPrintFilter(stderr, "INFO", _("Starting page %d."), Page);
751 
752    /*
753     * Start the page...
754     */
755 
756     StartPage(ppd, &header);
757 
758    /*
759     * Loop for each line on the page...
760     */
761 
762     for (y = 0; y < header.cupsHeight; y ++)
763     {
764      /*
765       * Let the user know how far we have progressed...
766       */
767 
768       if (Canceled)
769 	break;
770 
771       if ((y & 127) == 0)
772       {
773         _cupsLangPrintFilter(stderr, "INFO",
774 	                     _("Printing page %d, %u%% complete."),
775 			     Page, 100 * y / header.cupsHeight);
776         fprintf(stderr, "ATTR: job-media-progress=%u\n",
777 		100 * y / header.cupsHeight);
778       }
779 
780      /*
781       * Read a line of graphics...
782       */
783 
784       if (cupsRasterReadPixels(ras, Planes[0], header.cupsBytesPerLine) < 1)
785         break;
786 
787      /*
788       * See if the line is blank; if not, write it to the printer...
789       */
790 
791       if (Planes[0][0] ||
792           memcmp(Planes[0], Planes[0] + 1, header.cupsBytesPerLine - 1))
793         OutputLine(&header);
794       else
795         Feed ++;
796     }
797 
798    /*
799     * Eject the page...
800     */
801 
802     _cupsLangPrintFilter(stderr, "INFO", _("Finished page %d."), Page);
803 
804     EndPage();
805 
806     if (Canceled)
807       break;
808   }
809 
810  /*
811   * Shutdown the printer...
812   */
813 
814   Shutdown();
815 
816   if (ppd)
817     ppdClose(ppd);
818 
819  /*
820   * Close the raster stream...
821   */
822 
823   cupsRasterClose(ras);
824   if (fd != 0)
825     close(fd);
826 
827  /*
828   * If no pages were printed, send an error message...
829   */
830 
831   if (Page == 0)
832   {
833     _cupsLangPrintFilter(stderr, "ERROR", _("No pages were found."));
834     return (1);
835   }
836   else
837     return (0);
838 }
839