1 /*
2  * Generic HP PCL printer command for ippeveprinter/CUPS.
3  *
4  * Copyright © 2019 by Apple Inc.
5  *
6  * Licensed under Apache License v2.0.  See the file "LICENSE" for more
7  * information.
8  */
9 
10 /*
11  * Include necessary headers...
12  */
13 
14 #include "ippevecommon.h"
15 #include "dither.h"
16 
17 
18 /*
19  * Local globals...
20  */
21 
22 static unsigned		pcl_bottom,	/* Bottom line */
23 			pcl_left,	/* Left offset in line */
24 			pcl_right,	/* Right offset in line */
25 			pcl_top,	/* Top line */
26 			pcl_blanks;	/* Number of blank lines to skip */
27 static unsigned char	pcl_white,	/* White color */
28 			*pcl_line,	/* Line buffer */
29 			*pcl_comp;	/* Compression buffer */
30 
31 /*
32  * Local functions...
33  */
34 
35 static void	pcl_end_page(cups_page_header2_t *header, unsigned page);
36 static void	pcl_start_page(cups_page_header2_t *header, unsigned page);
37 static int	pcl_to_pcl(const char *filename);
38 static void	pcl_write_line(cups_page_header2_t *header, unsigned y, const unsigned char *line);
39 static int	raster_to_pcl(const char *filename);
40 
41 
42 /*
43  * 'main()' - Main entry for PCL printer command.
44  */
45 
46 int					/* O - Exit status */
main(int argc,char * argv[])47 main(int  argc,				/* I - Number of command-line arguments */
48      char *argv[])			/* I - Command-line arguments */
49 {
50   const char		*content_type;	/* Content type to print */
51 
52 
53  /*
54   * Print it...
55   */
56 
57   if (argc > 2)
58   {
59     fputs("ERROR: Too many arguments supplied, aborting.\n", stderr);
60     return (1);
61   }
62   else if ((content_type = getenv("CONTENT_TYPE")) == NULL)
63   {
64     fputs("ERROR: CONTENT_TYPE environment variable not set, aborting.\n", stderr);
65     return (1);
66   }
67   else if (!strcasecmp(content_type, "application/vnd.hp-pcl"))
68   {
69     return (pcl_to_pcl(argv[1]));
70   }
71   else if (!strcasecmp(content_type, "image/pwg-raster") || !strcasecmp(content_type, "image/urf"))
72   {
73     return (raster_to_pcl(argv[1]));
74   }
75   else
76   {
77     fprintf(stderr, "ERROR: CONTENT_TYPE %s not supported.\n", content_type);
78     return (1);
79   }
80 }
81 
82 
83 /*
84  * 'pcl_end_page()' - End of PCL page.
85  */
86 
87 static void
pcl_end_page(cups_page_header2_t * header,unsigned page)88 pcl_end_page(
89     cups_page_header2_t *header,	/* I - Page header */
90     unsigned            page)		/* I - Current page */
91 {
92  /*
93   * End graphics...
94   */
95 
96   fputs("\033*r0B", stdout);
97 
98  /*
99   * Formfeed as needed...
100   */
101 
102   if (!(header->Duplex && (page & 1)))
103     putchar('\f');
104 
105  /*
106   * Free the output buffers...
107   */
108 
109   free(pcl_line);
110   free(pcl_comp);
111 }
112 
113 
114 /*
115  * 'pcl_start_page()' - Start a PCL page.
116  */
117 
118 static void
pcl_start_page(cups_page_header2_t * header,unsigned page)119 pcl_start_page(
120     cups_page_header2_t *header,	/* I - Page header */
121     unsigned            page)		/* I - Current page */
122 {
123  /*
124   * Setup margins to be 1/6" top and bottom and 1/4" or .135" on the
125   * left and right.
126   */
127 
128   pcl_top    = header->HWResolution[1] / 6;
129   pcl_bottom = header->cupsHeight - header->HWResolution[1] / 6 - 1;
130 
131   if (header->PageSize[1] == 842)
132   {
133    /* A4 gets special side margins to expose an 8" print area */
134     pcl_left  = (header->cupsWidth - 8 * header->HWResolution[0]) / 2;
135     pcl_right = pcl_left + 8 * header->HWResolution[0] - 1;
136   }
137   else
138   {
139    /* All other sizes get 1/4" margins */
140     pcl_left  = header->HWResolution[0] / 4;
141     pcl_right = header->cupsWidth - header->HWResolution[0] / 4 - 1;
142   }
143 
144   if (!header->Duplex || (page & 1))
145   {
146    /*
147     * Set the media size...
148     */
149 
150     printf("\033&l12D\033&k12H");	/* Set 12 LPI, 10 CPI */
151     printf("\033&l0O");			/* Set portrait orientation */
152 
153     switch (header->PageSize[1])
154     {
155       case 540 : /* Monarch Envelope */
156           printf("\033&l80A");
157 	  break;
158 
159       case 595 : /* A5 */
160           printf("\033&l25A");
161 	  break;
162 
163       case 624 : /* DL Envelope */
164           printf("\033&l90A");
165 	  break;
166 
167       case 649 : /* C5 Envelope */
168           printf("\033&l91A");
169 	  break;
170 
171       case 684 : /* COM-10 Envelope */
172           printf("\033&l81A");
173 	  break;
174 
175       case 709 : /* B5 Envelope */
176           printf("\033&l100A");
177 	  break;
178 
179       case 756 : /* Executive */
180           printf("\033&l1A");
181 	  break;
182 
183       case 792 : /* Letter */
184           printf("\033&l2A");
185 	  break;
186 
187       case 842 : /* A4 */
188           printf("\033&l26A");
189 	  break;
190 
191       case 1008 : /* Legal */
192           printf("\033&l3A");
193 	  break;
194 
195       case 1191 : /* A3 */
196           printf("\033&l27A");
197 	  break;
198 
199       case 1224 : /* Tabloid */
200           printf("\033&l6A");
201 	  break;
202     }
203 
204    /*
205     * Set top margin and turn off perforation skip...
206     */
207 
208     printf("\033&l%uE\033&l0L", 12 * pcl_top / header->HWResolution[1]);
209 
210     if (header->Duplex)
211     {
212       int mode = header->Duplex ? 1 + header->Tumble != 0 : 0;
213 
214       printf("\033&l%dS", mode);	/* Set duplex mode */
215     }
216   }
217   else if (header->Duplex)
218     printf("\033&a2G");			/* Print on back side */
219 
220  /*
221   * Set graphics mode...
222   */
223 
224   printf("\033*t%uR", header->HWResolution[0]);
225 					/* Set resolution */
226   printf("\033*r%uS", pcl_right - pcl_left + 1);
227 					/* Set width */
228   printf("\033*r%uT", pcl_bottom - pcl_top + 1);
229 					/* Set height */
230   printf("\033&a0H\033&a%uV", 720 * pcl_top / header->HWResolution[1]);
231 					/* Set position */
232 
233   printf("\033*b2M");	/* Use PackBits compression */
234   printf("\033*r1A");	/* Start graphics */
235 
236  /*
237   * Allocate the output buffers...
238   */
239 
240   pcl_white  = header->cupsBitsPerColor == 1 ? 0 : 255;
241   pcl_blanks = 0;
242   pcl_line   = malloc(header->cupsWidth / 8 + 1);
243   pcl_comp   = malloc(2 * header->cupsBytesPerLine + 2);
244 
245   fprintf(stderr, "ATTR: job-impressions-completed=%d\n", page);
246 }
247 
248 
249 /*
250  * 'pcl_to_pcl()' - Pass through PCL data.
251  */
252 
253 static int				/* O - Exit status */
pcl_to_pcl(const char * filename)254 pcl_to_pcl(const char *filename)	/* I - File to print or NULL for stdin */
255 {
256   int		fd;			/* File to read from */
257   char		buffer[65536];		/* Copy buffer */
258   ssize_t	bytes;			/* Bytes to write */
259 
260 
261  /*
262   * Open the input file...
263   */
264 
265   if (filename)
266   {
267     if ((fd = open(filename, O_RDONLY)) < 0)
268     {
269       fprintf(stderr, "ERROR: Unable to open \"%s\": %s\n", filename, strerror(errno));
270       return (1);
271     }
272   }
273   else
274   {
275     fd = 0;
276   }
277 
278   fputs("ATTR: job-impressions=unknown\n", stderr);
279 
280  /*
281   * Copy to stdout...
282   */
283 
284   while ((bytes = read(fd, buffer, sizeof(buffer))) > 0)
285     write(1, buffer, (size_t)bytes);
286 
287  /*
288   * Close the input file...
289   */
290 
291   if (fd > 0)
292     close(fd);
293 
294   return (0);
295 }
296 
297 
298 /*
299  * 'pcl_write_line()' - Write a line of raster data.
300  */
301 
302 static void
pcl_write_line(cups_page_header2_t * header,unsigned y,const unsigned char * line)303 pcl_write_line(
304     cups_page_header2_t *header,	/* I - Raster information */
305     unsigned            y,		/* I - Line number */
306     const unsigned char *line)		/* I - Pixels on line */
307 {
308   unsigned	x;			/* Column number */
309   unsigned char	bit,			/* Current bit */
310 		byte,			/* Current byte */
311 		*outptr,		/* Pointer into output buffer */
312 		*outend,		/* End of output buffer */
313 		*start,			/* Start of sequence */
314 		*compptr;		/* Pointer into compression buffer */
315   unsigned	count;			/* Count of bytes for output */
316   const unsigned char	*ditherline;	/* Pointer into dither table */
317 
318 
319   if (line[0] == pcl_white && !memcmp(line, line + 1, header->cupsBytesPerLine - 1))
320   {
321    /*
322     * Skip blank line...
323     */
324 
325     pcl_blanks ++;
326     return;
327   }
328 
329   if (header->cupsBitsPerPixel == 1)
330   {
331    /*
332     * B&W bitmap data can be used directly...
333     */
334 
335     outend = (unsigned char *)line + (pcl_right + 7) / 8;
336     outptr = (unsigned char *)line + pcl_left / 8;
337   }
338   else
339   {
340    /*
341     * Dither 8-bit grayscale to B&W...
342     */
343 
344     y &= 63;
345     ditherline = threshold[y];
346 
347     for (x = pcl_left, bit = 128, byte = 0, outptr = pcl_line; x <= pcl_right; x ++, line ++)
348     {
349       if (*line <= ditherline[x & 63])
350 	byte |= bit;
351 
352       if (bit == 1)
353       {
354 	*outptr++ = byte;
355 	byte      = 0;
356 	bit       = 128;
357       }
358       else
359 	bit >>= 1;
360     }
361 
362     if (bit != 128)
363       *outptr++ = byte;
364 
365     outend = outptr;
366     outptr = pcl_line;
367   }
368 
369  /*
370   * Apply compression...
371   */
372 
373   compptr = pcl_comp;
374 
375   while (outptr < outend)
376   {
377     if ((outptr + 1) >= outend)
378     {
379      /*
380       * Single byte on the end...
381       */
382 
383       *compptr++ = 0x00;
384       *compptr++ = *outptr++;
385     }
386     else if (outptr[0] == outptr[1])
387     {
388      /*
389       * Repeated sequence...
390       */
391 
392       outptr ++;
393       count = 2;
394 
395       while (outptr < (outend - 1) &&
396 	     outptr[0] == outptr[1] &&
397 	     count < 127)
398       {
399 	outptr ++;
400 	count ++;
401       }
402 
403       *compptr++ = (unsigned char)(257 - count);
404       *compptr++ = *outptr++;
405     }
406     else
407     {
408      /*
409       * Non-repeated sequence...
410       */
411 
412       start = outptr;
413       outptr ++;
414       count = 1;
415 
416       while (outptr < (outend - 1) &&
417 	     outptr[0] != outptr[1] &&
418 	     count < 127)
419       {
420 	outptr ++;
421 	count ++;
422       }
423 
424       *compptr++ = (unsigned char)(count - 1);
425 
426       memcpy(compptr, start, count);
427       compptr += count;
428     }
429   }
430 
431  /*
432   * Output the line...
433   */
434 
435   if (pcl_blanks > 0)
436   {
437    /*
438     * Skip blank lines first...
439     */
440 
441     printf("\033*b%dY", pcl_blanks);
442     pcl_blanks = 0;
443   }
444 
445   printf("\033*b%dW", (int)(compptr - pcl_comp));
446   fwrite(pcl_comp, 1, (size_t)(compptr - pcl_comp), stdout);
447 }
448 
449 
450 /*
451  * 'raster_to_pcl()' - Convert raster data to PCL.
452  */
453 
454 static int				/* O - Exit status */
raster_to_pcl(const char * filename)455 raster_to_pcl(const char *filename)	/* I - File to print (NULL for stdin) */
456 {
457   int			fd;		/* Input file */
458   cups_raster_t		*ras;		/* Raster stream */
459   cups_page_header2_t	header;		/* Page header */
460   unsigned		page = 0,	/* Current page */
461 			y;		/* Current line */
462   unsigned char		*line;		/* Line buffer */
463 
464 
465 
466  /*
467   * Open the input file...
468   */
469 
470   if (filename)
471   {
472     if ((fd = open(filename, O_RDONLY)) < 0)
473     {
474       fprintf(stderr, "ERROR: Unable to open \"%s\": %s\n", filename, strerror(errno));
475       return (1);
476     }
477   }
478   else
479   {
480     fd = 0;
481   }
482 
483  /*
484   * Open the raster stream and send pages...
485   */
486 
487   if ((ras = cupsRasterOpen(fd, CUPS_RASTER_READ)) == NULL)
488   {
489     fputs("ERROR: Unable to read raster data, aborting.\n", stderr);
490     return (1);
491   }
492 
493   fputs("\033E", stdout);
494 
495   while (cupsRasterReadHeader2(ras, &header))
496   {
497     page ++;
498 
499     if (header.cupsColorSpace != CUPS_CSPACE_W && header.cupsColorSpace != CUPS_CSPACE_SW && header.cupsColorSpace != CUPS_CSPACE_K)
500     {
501       fputs("ERROR: Unsupported color space, aborting.\n", stderr);
502       break;
503     }
504     else if (header.cupsBitsPerColor != 1 && header.cupsBitsPerColor != 8)
505     {
506       fputs("ERROR: Unsupported bit depth, aborting.\n", stderr);
507       break;
508     }
509 
510     line = malloc(header.cupsBytesPerLine);
511 
512     pcl_start_page(&header, page);
513     for (y = 0; y < header.cupsHeight; y ++)
514     {
515       if (cupsRasterReadPixels(ras, line, header.cupsBytesPerLine))
516         pcl_write_line(&header, y, line);
517       else
518         break;
519     }
520     pcl_end_page(&header, page);
521 
522     free(line);
523   }
524 
525   cupsRasterClose(ras);
526 
527   fprintf(stderr, "ATTR: job-impressions=%d\n", page);
528 
529   return (0);
530 }
531