| /* |
| * Generic HP PCL printer command for ippeveprinter/CUPS. |
| * |
| * Copyright © 2019 by Apple Inc. |
| * |
| * Licensed under Apache License v2.0. See the file "LICENSE" for more |
| * information. |
| */ |
| |
| /* |
| * Include necessary headers... |
| */ |
| |
| #include "ippevecommon.h" |
| #include "dither.h" |
| |
| |
| /* |
| * Local globals... |
| */ |
| |
| static unsigned pcl_bottom, /* Bottom line */ |
| pcl_left, /* Left offset in line */ |
| pcl_right, /* Right offset in line */ |
| pcl_top, /* Top line */ |
| pcl_blanks; /* Number of blank lines to skip */ |
| static unsigned char pcl_white, /* White color */ |
| *pcl_line, /* Line buffer */ |
| *pcl_comp; /* Compression buffer */ |
| |
| /* |
| * Local functions... |
| */ |
| |
| static void pcl_end_page(cups_page_header2_t *header, unsigned page); |
| static void pcl_start_page(cups_page_header2_t *header, unsigned page); |
| static int pcl_to_pcl(const char *filename); |
| static void pcl_write_line(cups_page_header2_t *header, unsigned y, const unsigned char *line); |
| static int raster_to_pcl(const char *filename); |
| |
| |
| /* |
| * 'main()' - Main entry for PCL printer command. |
| */ |
| |
| int /* O - Exit status */ |
| main(int argc, /* I - Number of command-line arguments */ |
| char *argv[]) /* I - Command-line arguments */ |
| { |
| const char *content_type; /* Content type to print */ |
| |
| |
| /* |
| * Print it... |
| */ |
| |
| if (argc > 2) |
| { |
| fputs("ERROR: Too many arguments supplied, aborting.\n", stderr); |
| return (1); |
| } |
| else if ((content_type = getenv("CONTENT_TYPE")) == NULL) |
| { |
| fputs("ERROR: CONTENT_TYPE environment variable not set, aborting.\n", stderr); |
| return (1); |
| } |
| else if (!strcasecmp(content_type, "application/vnd.hp-pcl")) |
| { |
| return (pcl_to_pcl(argv[1])); |
| } |
| else if (!strcasecmp(content_type, "image/pwg-raster") || !strcasecmp(content_type, "image/urf")) |
| { |
| return (raster_to_pcl(argv[1])); |
| } |
| else |
| { |
| fprintf(stderr, "ERROR: CONTENT_TYPE %s not supported.\n", content_type); |
| return (1); |
| } |
| } |
| |
| |
| /* |
| * 'pcl_end_page()' - End of PCL page. |
| */ |
| |
| static void |
| pcl_end_page( |
| cups_page_header2_t *header, /* I - Page header */ |
| unsigned page) /* I - Current page */ |
| { |
| /* |
| * End graphics... |
| */ |
| |
| fputs("\033*r0B", stdout); |
| |
| /* |
| * Formfeed as needed... |
| */ |
| |
| if (!(header->Duplex && (page & 1))) |
| putchar('\f'); |
| |
| /* |
| * Free the output buffers... |
| */ |
| |
| free(pcl_line); |
| free(pcl_comp); |
| } |
| |
| |
| /* |
| * 'pcl_start_page()' - Start a PCL page. |
| */ |
| |
| static void |
| pcl_start_page( |
| cups_page_header2_t *header, /* I - Page header */ |
| unsigned page) /* I - Current page */ |
| { |
| /* |
| * Setup margins to be 1/6" top and bottom and 1/4" or .135" on the |
| * left and right. |
| */ |
| |
| pcl_top = header->HWResolution[1] / 6; |
| pcl_bottom = header->cupsHeight - header->HWResolution[1] / 6 - 1; |
| |
| if (header->PageSize[1] == 842) |
| { |
| /* A4 gets special side margins to expose an 8" print area */ |
| pcl_left = (header->cupsWidth - 8 * header->HWResolution[0]) / 2; |
| pcl_right = pcl_left + 8 * header->HWResolution[0] - 1; |
| } |
| else |
| { |
| /* All other sizes get 1/4" margins */ |
| pcl_left = header->HWResolution[0] / 4; |
| pcl_right = header->cupsWidth - header->HWResolution[0] / 4 - 1; |
| } |
| |
| if (!header->Duplex || (page & 1)) |
| { |
| /* |
| * Set the media size... |
| */ |
| |
| printf("\033&l12D\033&k12H"); /* Set 12 LPI, 10 CPI */ |
| printf("\033&l0O"); /* Set portrait orientation */ |
| |
| switch (header->PageSize[1]) |
| { |
| case 540 : /* Monarch Envelope */ |
| printf("\033&l80A"); |
| break; |
| |
| case 595 : /* A5 */ |
| printf("\033&l25A"); |
| break; |
| |
| case 624 : /* DL Envelope */ |
| printf("\033&l90A"); |
| break; |
| |
| case 649 : /* C5 Envelope */ |
| printf("\033&l91A"); |
| break; |
| |
| case 684 : /* COM-10 Envelope */ |
| printf("\033&l81A"); |
| break; |
| |
| case 709 : /* B5 Envelope */ |
| printf("\033&l100A"); |
| break; |
| |
| case 756 : /* Executive */ |
| printf("\033&l1A"); |
| break; |
| |
| case 792 : /* Letter */ |
| printf("\033&l2A"); |
| break; |
| |
| case 842 : /* A4 */ |
| printf("\033&l26A"); |
| break; |
| |
| case 1008 : /* Legal */ |
| printf("\033&l3A"); |
| break; |
| |
| case 1191 : /* A3 */ |
| printf("\033&l27A"); |
| break; |
| |
| case 1224 : /* Tabloid */ |
| printf("\033&l6A"); |
| break; |
| } |
| |
| /* |
| * Set top margin and turn off perforation skip... |
| */ |
| |
| printf("\033&l%uE\033&l0L", 12 * pcl_top / header->HWResolution[1]); |
| |
| if (header->Duplex) |
| { |
| int mode = header->Duplex ? 1 + header->Tumble != 0 : 0; |
| |
| printf("\033&l%dS", mode); /* Set duplex mode */ |
| } |
| } |
| else if (header->Duplex) |
| printf("\033&a2G"); /* Print on back side */ |
| |
| /* |
| * Set graphics mode... |
| */ |
| |
| printf("\033*t%uR", header->HWResolution[0]); |
| /* Set resolution */ |
| printf("\033*r%uS", pcl_right - pcl_left + 1); |
| /* Set width */ |
| printf("\033*r%uT", pcl_bottom - pcl_top + 1); |
| /* Set height */ |
| printf("\033&a0H\033&a%uV", 720 * pcl_top / header->HWResolution[1]); |
| /* Set position */ |
| |
| printf("\033*b2M"); /* Use PackBits compression */ |
| printf("\033*r1A"); /* Start graphics */ |
| |
| /* |
| * Allocate the output buffers... |
| */ |
| |
| pcl_white = header->cupsBitsPerColor == 1 ? 0 : 255; |
| pcl_blanks = 0; |
| pcl_line = malloc(header->cupsWidth / 8 + 1); |
| pcl_comp = malloc(2 * header->cupsBytesPerLine + 2); |
| |
| fprintf(stderr, "ATTR: job-impressions-completed=%d\n", page); |
| } |
| |
| |
| /* |
| * 'pcl_to_pcl()' - Pass through PCL data. |
| */ |
| |
| static int /* O - Exit status */ |
| pcl_to_pcl(const char *filename) /* I - File to print or NULL for stdin */ |
| { |
| int fd; /* File to read from */ |
| char buffer[65536]; /* Copy buffer */ |
| ssize_t bytes; /* Bytes to write */ |
| |
| |
| /* |
| * Open the input file... |
| */ |
| |
| if (filename) |
| { |
| if ((fd = open(filename, O_RDONLY)) < 0) |
| { |
| fprintf(stderr, "ERROR: Unable to open \"%s\": %s\n", filename, strerror(errno)); |
| return (1); |
| } |
| } |
| else |
| { |
| fd = 0; |
| } |
| |
| fputs("ATTR: job-impressions=unknown\n", stderr); |
| |
| /* |
| * Copy to stdout... |
| */ |
| |
| while ((bytes = read(fd, buffer, sizeof(buffer))) > 0) |
| write(1, buffer, (size_t)bytes); |
| |
| /* |
| * Close the input file... |
| */ |
| |
| if (fd > 0) |
| close(fd); |
| |
| return (0); |
| } |
| |
| |
| /* |
| * 'pcl_write_line()' - Write a line of raster data. |
| */ |
| |
| static void |
| pcl_write_line( |
| cups_page_header2_t *header, /* I - Raster information */ |
| unsigned y, /* I - Line number */ |
| const unsigned char *line) /* I - Pixels on line */ |
| { |
| unsigned x; /* Column number */ |
| unsigned char bit, /* Current bit */ |
| byte, /* Current byte */ |
| *outptr, /* Pointer into output buffer */ |
| *outend, /* End of output buffer */ |
| *start, /* Start of sequence */ |
| *compptr; /* Pointer into compression buffer */ |
| unsigned count; /* Count of bytes for output */ |
| const unsigned char *ditherline; /* Pointer into dither table */ |
| |
| |
| if (line[0] == pcl_white && !memcmp(line, line + 1, header->cupsBytesPerLine - 1)) |
| { |
| /* |
| * Skip blank line... |
| */ |
| |
| pcl_blanks ++; |
| return; |
| } |
| |
| if (header->cupsBitsPerPixel == 1) |
| { |
| /* |
| * B&W bitmap data can be used directly... |
| */ |
| |
| outend = (unsigned char *)line + (pcl_right + 7) / 8; |
| outptr = (unsigned char *)line + pcl_left / 8; |
| } |
| else |
| { |
| /* |
| * Dither 8-bit grayscale to B&W... |
| */ |
| |
| y &= 63; |
| ditherline = threshold[y]; |
| |
| for (x = pcl_left, bit = 128, byte = 0, outptr = pcl_line; x <= pcl_right; x ++, line ++) |
| { |
| if (*line <= ditherline[x & 63]) |
| byte |= bit; |
| |
| if (bit == 1) |
| { |
| *outptr++ = byte; |
| byte = 0; |
| bit = 128; |
| } |
| else |
| bit >>= 1; |
| } |
| |
| if (bit != 128) |
| *outptr++ = byte; |
| |
| outend = outptr; |
| outptr = pcl_line; |
| } |
| |
| /* |
| * Apply compression... |
| */ |
| |
| compptr = pcl_comp; |
| |
| while (outptr < outend) |
| { |
| if ((outptr + 1) >= outend) |
| { |
| /* |
| * Single byte on the end... |
| */ |
| |
| *compptr++ = 0x00; |
| *compptr++ = *outptr++; |
| } |
| else if (outptr[0] == outptr[1]) |
| { |
| /* |
| * Repeated sequence... |
| */ |
| |
| outptr ++; |
| count = 2; |
| |
| while (outptr < (outend - 1) && |
| outptr[0] == outptr[1] && |
| count < 127) |
| { |
| outptr ++; |
| count ++; |
| } |
| |
| *compptr++ = (unsigned char)(257 - count); |
| *compptr++ = *outptr++; |
| } |
| else |
| { |
| /* |
| * Non-repeated sequence... |
| */ |
| |
| start = outptr; |
| outptr ++; |
| count = 1; |
| |
| while (outptr < (outend - 1) && |
| outptr[0] != outptr[1] && |
| count < 127) |
| { |
| outptr ++; |
| count ++; |
| } |
| |
| *compptr++ = (unsigned char)(count - 1); |
| |
| memcpy(compptr, start, count); |
| compptr += count; |
| } |
| } |
| |
| /* |
| * Output the line... |
| */ |
| |
| if (pcl_blanks > 0) |
| { |
| /* |
| * Skip blank lines first... |
| */ |
| |
| printf("\033*b%dY", pcl_blanks); |
| pcl_blanks = 0; |
| } |
| |
| printf("\033*b%dW", (int)(compptr - pcl_comp)); |
| fwrite(pcl_comp, 1, (size_t)(compptr - pcl_comp), stdout); |
| } |
| |
| |
| /* |
| * 'raster_to_pcl()' - Convert raster data to PCL. |
| */ |
| |
| static int /* O - Exit status */ |
| raster_to_pcl(const char *filename) /* I - File to print (NULL for stdin) */ |
| { |
| int fd; /* Input file */ |
| cups_raster_t *ras; /* Raster stream */ |
| cups_page_header2_t header; /* Page header */ |
| unsigned page = 0, /* Current page */ |
| y; /* Current line */ |
| unsigned char *line; /* Line buffer */ |
| |
| |
| |
| /* |
| * Open the input file... |
| */ |
| |
| if (filename) |
| { |
| if ((fd = open(filename, O_RDONLY)) < 0) |
| { |
| fprintf(stderr, "ERROR: Unable to open \"%s\": %s\n", filename, strerror(errno)); |
| return (1); |
| } |
| } |
| else |
| { |
| fd = 0; |
| } |
| |
| /* |
| * Open the raster stream and send pages... |
| */ |
| |
| if ((ras = cupsRasterOpen(fd, CUPS_RASTER_READ)) == NULL) |
| { |
| fputs("ERROR: Unable to read raster data, aborting.\n", stderr); |
| return (1); |
| } |
| |
| fputs("\033E", stdout); |
| |
| while (cupsRasterReadHeader2(ras, &header)) |
| { |
| page ++; |
| |
| if (header.cupsColorSpace != CUPS_CSPACE_W && header.cupsColorSpace != CUPS_CSPACE_SW && header.cupsColorSpace != CUPS_CSPACE_K) |
| { |
| fputs("ERROR: Unsupported color space, aborting.\n", stderr); |
| break; |
| } |
| else if (header.cupsBitsPerColor != 1 && header.cupsBitsPerColor != 8) |
| { |
| fputs("ERROR: Unsupported bit depth, aborting.\n", stderr); |
| break; |
| } |
| |
| line = malloc(header.cupsBytesPerLine); |
| |
| pcl_start_page(&header, page); |
| for (y = 0; y < header.cupsHeight; y ++) |
| { |
| if (cupsRasterReadPixels(ras, line, header.cupsBytesPerLine)) |
| pcl_write_line(&header, y, line); |
| else |
| break; |
| } |
| pcl_end_page(&header, page); |
| |
| free(line); |
| } |
| |
| cupsRasterClose(ras); |
| |
| fprintf(stderr, "ATTR: job-impressions=%d\n", page); |
| |
| return (0); |
| } |