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