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