blob: f15ea51d74be772ac722fedf1a60816f7b66400f [file] [log] [blame]
/*
* 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);
}