/* * Generic Adobe PostScript printer command for ippeveprinter/CUPS. * * Copyright © 2021-2022 by OpenPrinting. * Copyright © 2019 by Apple Inc. * * Licensed under Apache License v2.0. See the file "LICENSE" for more * information. */ /* * Include necessary headers... */ #include "ippevecommon.h" #if !CUPS_LITE # include #endif /* !CUPS_LITE */ #include #include #ifdef __APPLE__ # define PDFTOPS CUPS_SERVERBIN "/filter/cgpdftops" #else # define PDFTOPS CUPS_SERVERBIN "/filter/pdftops" #endif /* __APPLE__ */ /* * Local globals... */ #if !CUPS_LITE static ppd_file_t *ppd = NULL; /* PPD file data */ static _ppd_cache_t *ppd_cache = NULL; /* IPP to PPD cache data */ #endif /* !CUPS_LITE */ /* * Local functions... */ static void ascii85(const unsigned char *data, int length, int eod); static void dsc_header(int num_pages); static void dsc_page(int page); static void dsc_trailer(int num_pages); static int get_options(cups_option_t **options); static int jpeg_to_ps(const char *filename, int copies); static int pdf_to_ps(const char *filename, int copies, int num_options, cups_option_t *options); static int ps_to_ps(const char *filename, int copies); static int raster_to_ps(const char *filename); /* * 'main()' - Main entry for PostScript 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 */ *ipp_copies; /* IPP_COPIES value */ int copies; /* Number of copies */ int num_options; /* Number of options */ cups_option_t *options; /* Options */ /* * Get print options... */ num_options = get_options(&options); if ((ipp_copies = getenv("IPP_COPIES")) != NULL) { if ((copies = atoi(ipp_copies)) < 1) copies = 1; } else copies = 1; /* * 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/pdf")) { return (pdf_to_ps(argv[1], copies, num_options, options)); } else if (!strcasecmp(content_type, "application/postscript")) { return (ps_to_ps(argv[1], copies)); } else if (!strcasecmp(content_type, "image/jpeg")) { return (jpeg_to_ps(argv[1], copies)); } else if (!strcasecmp(content_type, "image/pwg-raster") || !strcasecmp(content_type, "image/urf")) { return (raster_to_ps(argv[1])); } else { fprintf(stderr, "ERROR: CONTENT_TYPE %s not supported.\n", content_type); return (1); } } /* * 'ascii85()' - Write binary data using a Base85 encoding... */ static void ascii85(const unsigned char *data, /* I - Data to write */ int length, /* I - Number of bytes to write */ int eod) /* I - 1 if this is the end, 0 otherwise */ { unsigned b = 0; /* Current 32-bit word */ unsigned char c[5]; /* Base-85 encoded characters */ static int col = 0; /* Column */ static unsigned char leftdata[4]; /* Leftover data at the end */ static int leftcount = 0; /* Size of leftover data */ length += leftcount; while (length > 3) { switch (leftcount) { case 0 : b = (unsigned)((((((data[0] << 8) | data[1]) << 8) | data[2]) << 8) | data[3]); break; case 1 : b = (unsigned)((((((leftdata[0] << 8) | data[0]) << 8) | data[1]) << 8) | data[2]); break; case 2 : b = (unsigned)((((((leftdata[0] << 8) | leftdata[1]) << 8) | data[0]) << 8) | data[1]); break; case 3 : b = (unsigned)((((((leftdata[0] << 8) | leftdata[1]) << 8) | leftdata[2]) << 8) | data[0]); break; } if (col >= 76) { col = 0; putchar('\n'); } if (b == 0) { putchar('z'); col ++; } else { c[4] = (b % 85) + '!'; b /= 85; c[3] = (b % 85) + '!'; b /= 85; c[2] = (b % 85) + '!'; b /= 85; c[1] = (b % 85) + '!'; b /= 85; c[0] = (unsigned char)(b + '!'); fwrite(c, 1, 5, stdout); col += 5; } data += 4 - leftcount; length -= 4 - leftcount; leftcount = 0; } if (length > 0) { // Copy any remainder into the leftdata array... if (length > leftcount) memcpy(leftdata + leftcount, data, (size_t)(length - leftcount)); memset(leftdata + length, 0, (size_t)(4 - length)); leftcount = length; } if (eod) { // Do the end-of-data dance... if (col >= 76) { col = 0; putchar('\n'); } if (leftcount > 0) { // Write the remaining bytes as needed... b = (unsigned)((((((leftdata[0] << 8) | leftdata[1]) << 8) | leftdata[2]) << 8) | leftdata[3]); c[4] = (b % 85) + '!'; b /= 85; c[3] = (b % 85) + '!'; b /= 85; c[2] = (b % 85) + '!'; b /= 85; c[1] = (b % 85) + '!'; b /= 85; c[0] = (unsigned char)(b + '!'); fwrite(c, (size_t)(leftcount + 1), 1, stdout); leftcount = 0; } puts("~>"); col = 0; } } /* * 'dsc_header()' - Write out a standard Document Structuring Conventions * PostScript header. */ static void dsc_header(int num_pages) /* I - Number of pages or 0 if not known */ { const char *job_name = getenv("IPP_JOB_NAME"); /* job-name value */ #if !CUPS_LITE const char *job_id = getenv("IPP_JOB_ID"); /* job-id value */ ppdEmitJCL(ppd, stdout, job_id ? atoi(job_id) : 0, cupsUser(), job_name ? job_name : "Unknown"); #endif /* !CUPS_LITE */ puts("%!PS-Adobe-3.0"); puts("%%LanguageLevel: 2"); printf("%%%%Creator: ippeveps/%d.%d.%d\n", CUPS_VERSION_MAJOR, CUPS_VERSION_MINOR, CUPS_VERSION_PATCH); if (job_name) { fputs("%%Title: ", stdout); while (*job_name) { if (*job_name >= 0x20 && *job_name < 0x7f) putchar(*job_name); else putchar('?'); job_name ++; } putchar('\n'); } if (num_pages > 0) printf("%%%%Pages: %d\n", num_pages); else puts("%%Pages: (atend)"); puts("%%EndComments"); #if !CUPS_LITE if (ppd) { puts("%%BeginProlog"); if (ppd->patches) { puts("%%BeginFeature: *JobPatchFile 1"); puts(ppd->patches); puts("%%EndFeature"); } ppdEmit(ppd, stdout, PPD_ORDER_PROLOG); puts("%%EndProlog"); puts("%%BeginSetup"); ppdEmit(ppd, stdout, PPD_ORDER_DOCUMENT); ppdEmit(ppd, stdout, PPD_ORDER_ANY); puts("%%EndSetup"); } #endif /* !CUPS_LITE */ } /* * 'dsc_page()' - Mark the start of a page. */ static void dsc_page(int page) /* I - Page numebr (1-based) */ { printf("%%%%Page: (%d) %d\n", page, page); fprintf(stderr, "ATTR: job-impressions-completed=%d\n", page); #if !CUPS_LITE if (ppd) { puts("%%BeginPageSetup"); ppdEmit(ppd, stdout, PPD_ORDER_PAGE); puts("%%EndPageSetup"); } #endif /* !CUPS_LITE */ } /* * 'dsc_trailer()' - Mark the end of the document. */ static void dsc_trailer(int num_pages) /* I - Number of pages */ { if (num_pages > 0) { puts("%%Trailer"); printf("%%%%Pages: %d\n", num_pages); puts("%%EOF"); } #if !CUPS_LITE if (ppd && ppd->jcl_end) ppdEmitJCLEnd(ppd, stdout); else #endif /* !CUPS_LITE */ putchar(0x04); } /* * 'get_options()' - Get the PPD options corresponding to the IPP Job Template * attributes. */ static int /* O - Number of options */ get_options(cups_option_t **options) /* O - Options */ { int num_options = 0; /* Number of options */ const char *value; /* Option value */ pwg_media_t *media = NULL; /* Media mapping */ int num_media_col = 0; /* Number of media-col values */ cups_option_t *media_col = NULL; /* media-col values */ #if !CUPS_LITE const char *choice; /* PPD choice */ #endif /* !CUPS_LITE */ /* * No options to start... */ *options = NULL; /* * Media... */ if ((value = getenv("IPP_MEDIA")) == NULL) if ((value = getenv("IPP_MEDIA_COL")) == NULL) if ((value = getenv("IPP_MEDIA_DEFAULT")) == NULL) value = getenv("IPP_MEDIA_COL_DEFAULT"); if (value) { if (*value == '{') { /* * media-col value... */ num_media_col = cupsParseOptions(value, 0, &media_col); } else { /* * media value - map to media-col.media-size-name... */ num_media_col = cupsAddOption("media-size-name", value, 0, &media_col); } } if ((value = cupsGetOption("media-size-name", num_media_col, media_col)) != NULL) { media = pwgMediaForPWG(value); } else if ((value = cupsGetOption("media-size", num_media_col, media_col)) != NULL) { int num_media_size; /* Number of media-size values */ cups_option_t *media_size; /* media-size values */ const char *x_dimension, /* x-dimension value */ *y_dimension; /* y-dimension value */ num_media_size = cupsParseOptions(value, 0, &media_size); if ((x_dimension = cupsGetOption("x-dimension", num_media_size, media_size)) != NULL && (y_dimension = cupsGetOption("y-dimension", num_media_size, media_size)) != NULL) media = pwgMediaForSize(atoi(x_dimension), atoi(y_dimension)); cupsFreeOptions(num_media_size, media_size); } if (media) num_options = cupsAddOption("PageSize", media->ppd, num_options, options); #if !CUPS_LITE /* * Load PPD file and the corresponding IPP <-> PPD cache data... */ if ((ppd = ppdOpenFile(getenv("PPD"))) != NULL && (ppd_cache = _ppdCacheCreateWithPPD(ppd)) != NULL) { /* TODO: Fix me - values are names, not numbers... Also need to support finishings-col */ if ((value = getenv("IPP_FINISHINGS")) == NULL) value = getenv("IPP_FINISHINGS_DEFAULT"); if (value) { char *ptr; /* Pointer into value */ int fin; /* Current value */ for (fin = strtol(value, &ptr, 10); fin > 0; fin = strtol(ptr + 1, &ptr, 10)) { num_options = _ppdCacheGetFinishingOptions(ppd_cache, NULL, (ipp_finishings_t)fin, num_options, options); if (*ptr != ',') break; } } if ((value = cupsGetOption("media-source", num_media_col, media_col)) != NULL) { if ((choice = _ppdCacheGetInputSlot(ppd_cache, NULL, value)) != NULL) num_options = cupsAddOption("InputSlot", choice, num_options, options); } if ((value = cupsGetOption("media-type", num_media_col, media_col)) != NULL) { if ((choice = _ppdCacheGetMediaType(ppd_cache, NULL, value)) != NULL) num_options = cupsAddOption("MediaType", choice, num_options, options); } if ((value = getenv("IPP_OUTPUT_BIN")) == NULL) value = getenv("IPP_OUTPUT_BIN_DEFAULT"); if (value) { if ((choice = _ppdCacheGetOutputBin(ppd_cache, value)) != NULL) num_options = cupsAddOption("OutputBin", choice, num_options, options); } if ((value = getenv("IPP_SIDES")) == NULL) value = getenv("IPP_SIDES_DEFAULT"); if (value && ppd_cache->sides_option) { if (!strcmp(value, "one-sided") && ppd_cache->sides_1sided) num_options = cupsAddOption(ppd_cache->sides_option, ppd_cache->sides_1sided, num_options, options); else if (!strcmp(value, "two-sided-long-edge") && ppd_cache->sides_2sided_long) num_options = cupsAddOption(ppd_cache->sides_option, ppd_cache->sides_2sided_long, num_options, options); else if (!strcmp(value, "two-sided-short-edge") && ppd_cache->sides_2sided_short) num_options = cupsAddOption(ppd_cache->sides_option, ppd_cache->sides_2sided_short, num_options, options); } if ((value = getenv("IPP_PRINT_QUALITY")) == NULL) value = getenv("IPP_PRINT_QUALITY_DEFAULT"); if (value) { int i; /* Looping var */ int pq; /* Print quality (0-2) */ int pcm = 1; /* Print color model (0 = mono, 1 = color) */ int num_presets; /* Number of presets */ cups_option_t *presets; /* Presets */ if (!strcmp(value, "draft")) pq = 0; else if (!strcmp(value, "high")) pq = 2; else pq = 1; if ((value = getenv("IPP_PRINT_COLOR_MODE")) == NULL) value = getenv("IPP_PRINT_COLOR_MODE_DEFAULT"); if (value && !strcmp(value, "monochrome")) pcm = 0; num_presets = ppd_cache->num_presets[pcm][pq]; presets = ppd_cache->presets[pcm][pq]; for (i = 0; i < num_presets; i ++) num_options = cupsAddOption(presets[i].name, presets[i].value, num_options, options); } /* * Mark the PPD with the options... */ ppdMarkDefaults(ppd); cupsMarkOptions(ppd, num_options, *options); } #endif /* !CUPS_LITE */ cupsFreeOptions(num_media_col, media_col); return (num_options); } /* * 'jpeg_to_ps()' - Convert a JPEG file to PostScript. */ static int /* O - Exit status */ jpeg_to_ps(const char *filename, /* I - Filename */ int copies) /* I - Number of copies */ { int fd; /* JPEG file descriptor */ int copy; /* Current copy */ int width = 0, /* Width */ height = 0, /* Height */ depth = 0, /* Number of colors */ length; /* Length of marker */ unsigned char buffer[65536], /* Copy buffer */ *bufptr, /* Pointer info buffer */ *bufend; /* End of buffer */ ssize_t bytes; /* Bytes in buffer */ const char *decode; /* Decode array */ float page_left, /* Left margin */ page_top, /* Top margin */ page_width, /* Page width in points */ page_height, /* Page heigth in points */ x_factor, /* X image scaling factor */ y_factor, /* Y image scaling factor */ page_scaling; /* Image scaling factor */ #if !CUPS_LITE ppd_size_t *page_size; /* Current page size */ #endif /* !CUPS_LITE */ /* * 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; copies = 1; } /* * Read the JPEG dimensions... */ bytes = read(fd, buffer, sizeof(buffer)); if (bytes < 3 || memcmp(buffer, "\377\330\377", 3)) { fputs("ERROR: Not a JPEG image.\n", stderr); if (fd > 0) close(fd); return (1); } for (bufptr = buffer + 2, bufend = buffer + bytes; bufptr < bufend;) { /* * Scan the file for a SOFn marker, then we can get the dimensions... */ if (*bufptr == 0xff) { bufptr ++; if (bufptr >= bufend) { /* * If we are at the end of the current buffer, re-fill and continue... */ if ((bytes = read(fd, buffer, sizeof(buffer))) <= 0) break; bufptr = buffer; bufend = buffer + bytes; } if (*bufptr == 0xff) continue; if ((bufptr + 16) >= bufend) { /* * Read more of the marker... */ bytes = bufend - bufptr; memmove(buffer, bufptr, (size_t)bytes); bufptr = buffer; bufend = buffer + bytes; if ((bytes = read(fd, bufend, sizeof(buffer) - (size_t)bytes)) <= 0) break; bufend += bytes; } length = (size_t)((bufptr[1] << 8) | bufptr[2]); if ((*bufptr >= 0xc0 && *bufptr <= 0xc3) || (*bufptr >= 0xc5 && *bufptr <= 0xc7) || (*bufptr >= 0xc9 && *bufptr <= 0xcb) || (*bufptr >= 0xcd && *bufptr <= 0xcf)) { /* * SOFn marker, look for dimensions... */ width = (bufptr[6] << 8) | bufptr[7]; height = (bufptr[4] << 8) | bufptr[5]; depth = bufptr[8]; break; } /* * Skip past this marker... */ bufptr ++; bytes = bufend - bufptr; while (length >= bytes) { length -= bytes; if ((bytes = read(fd, buffer, sizeof(buffer))) <= 0) break; bufptr = buffer; bufend = buffer + bytes; } if (length > bytes) break; bufptr += length; } } fprintf(stderr, "DEBUG: JPEG dimensions are %dx%dx%d\n", width, height, depth); if (width <= 0 || height <= 0 || depth <= 0) { fputs("ERROR: No valid image data in JPEG file.\n", stderr); if (fd > 0) close(fd); return (1); } fputs("ATTR: job-impressions=1\n", stderr); /* * Figure out the dimensions/scaling of the final image... */ #if CUPS_LITE page_left = 18.0f; page_top = 756.0f; page_width = 576.0f; page_height = 720.0f; #else if ((page_size = ppdPageSize(ppd, NULL)) != NULL) { page_left = page_size->left; page_top = page_size->top; page_width = page_size->right - page_left; page_height = page_top - page_size->bottom; } else { page_left = 18.0f; page_top = 756.0f; page_width = 576.0f; page_height = 720.0f; } #endif /* CUPS_LITE */ fprintf(stderr, "DEBUG: page_left=%.2f, page_top=%.2f, page_width=%.2f, page_height=%.2f\n", page_left, page_top, page_width, page_height); /* TODO: Support orientation/rotation, different print-scaling modes */ x_factor = page_width / width; y_factor = page_height / height; if (x_factor > y_factor && (height * x_factor) <= page_height) page_scaling = x_factor; else page_scaling = y_factor; fprintf(stderr, "DEBUG: Scaled dimensions are %.2fx%.2f\n", width * page_scaling, height * page_scaling); /* * Write pages... */ dsc_header(copies); for (copy = 1; copy <= copies; copy ++) { dsc_page(copy); if (depth == 1) { puts("/DeviceGray setcolorspace"); decode = "0 1"; } else if (depth == 3) { puts("/DeviceRGB setcolorspace"); decode = "0 1 0 1 0 1"; } else { puts("/DeviceCMYK setcolorspace"); decode = "0 1 0 1 0 1 0 1"; } printf("gsave %.3f %.3f translate %.3f %.3f scale\n", page_left + 0.5f * (page_width - width * page_scaling), page_top - 0.5f * (page_height - height * page_scaling), page_scaling, page_scaling); printf("<>image\n", width, height, decode); if (fd > 0) lseek(fd, 0, SEEK_SET); while ((bytes = read(fd, buffer, sizeof(buffer))) > 0) ascii85(buffer, (int)bytes, 0); ascii85(buffer, 0, 1); puts("grestore showpage"); } dsc_trailer(0); return (0); } /* * 'pdf_to_ps()' - Convert a PDF file to PostScript. */ static int /* O - Exit status */ pdf_to_ps(const char *filename, /* I - Filename */ int copies, /* I - Number of copies */ int num_options, /* I - Number of options */ cups_option_t *options) /* I - options */ { int status; /* Exit status */ char tempfile[1024]; /* Temporary file */ int tempfd; /* Temporary file descriptor */ int pid; /* Process ID */ const char *pdf_argv[8]; /* Command-line arguments */ char pdf_options[1024]; /* Options */ const char *value; /* Option value */ const char *job_id, /* job-id value */ *job_name; /* job-name value */ /* * Create a temporary file for the PostScript version... */ if ((tempfd = cupsTempFd(tempfile, sizeof(tempfile))) < 0) { fprintf(stderr, "ERROR: Unable to create temporary file: %s\n", strerror(errno)); return (1); } /* * Run cgpdftops or pdftops in the filter directory... */ if ((value = cupsGetOption("PageSize", num_options, options)) != NULL) snprintf(pdf_options, sizeof(pdf_options), "PageSize=%s", value); else pdf_options[0] = '\0'; if ((job_id = getenv("IPP_JOB_ID")) == NULL) job_id = "42"; if ((job_name = getenv("IPP_JOB_NAME")) == NULL) job_name = "untitled"; pdf_argv[0] = "printer"; pdf_argv[1] = job_id; pdf_argv[2] = cupsUser(); pdf_argv[3] = job_name; pdf_argv[4] = "1"; pdf_argv[5] = pdf_options; pdf_argv[6] = filename; pdf_argv[7] = NULL; if ((pid = fork()) == 0) { /* * Child comes here... */ close(1); dup2(tempfd, 1); close(tempfd); execv(PDFTOPS, (char * const *)pdf_argv); exit(errno); } else if (pid < 0) { /* * Unable to fork process... */ perror("ERROR: Unable to start PDF filter"); close(tempfd); unlink(tempfile); return (1); } else { /* * Wait for the filter to complete... */ close(tempfd); # ifdef HAVE_WAITPID while (waitpid(pid, &status, 0) < 0); # else while (wait(&status) < 0); # endif /* HAVE_WAITPID */ if (status) { if (WIFEXITED(status)) fprintf(stderr, "ERROR: " PDFTOPS " exited with status %d.\n", WEXITSTATUS(status)); else fprintf(stderr, "ERROR: " PDFTOPS " terminated with signal %d.\n", WTERMSIG(status)); unlink(tempfile); return (1); } } /* * Copy the PostScript output from the command... */ status = ps_to_ps(tempfile, copies); unlink(tempfile); return (status); } /* * 'ps_to_ps()' - Copy PostScript to the standard output. */ static int /* O - Exit status */ ps_to_ps(const char *filename, /* I - Filename */ int copies) /* I - Number of copies */ { FILE *fp; /* File to read from */ int copy, /* Current copy */ page, /* Current page number */ num_pages = 0, /* Total number of pages */ first_page, /* First page */ last_page; /* Last page */ const char *page_ranges; /* page-ranges option */ long first_pos = -1; /* Offset for first page */ char line[1024]; /* Line from file */ /* * Open the print file... */ if (filename) { if ((fp = fopen(filename, "rb")) == NULL) { fprintf(stderr, "ERROR: Unable to open \"%s\": %s\n", filename, strerror(errno)); return (1); } } else { copies = 1; fp = stdin; } /* * Check page ranges... */ if ((page_ranges = getenv("IPP_PAGE_RANGES")) != NULL) { if (sscanf(page_ranges, "%d-%d", &first_page, &last_page) != 2) { first_page = 1; last_page = INT_MAX; } } else { first_page = 1; last_page = INT_MAX; } /* * Write the PostScript header for the document... */ dsc_header(0); first_pos = 0; while (fgets(line, sizeof(line), fp)) { if (!strncmp(line, "%%Page:", 7)) break; if ((first_pos = ftell(fp)) < 0) { perror("DEBUG: ftell failed"); first_pos = 0; } if (line[0] != '%') fputs(line, stdout); } if (!strncmp(line, "%%Page:", 7)) { for (copy = 0; copy < copies; copy ++) { int copy_page = 0; /* Do we copy the page data? */ if (fp != stdin) { if (fseek(fp, first_pos, SEEK_SET) < 0) { perror("ERROR: Unable to seek within PostScript file"); break; } } page = 0; while (fgets(line, sizeof(line), fp)) { if (!strncmp(line, "%%Page:", 7)) { page ++; copy_page = page >= first_page && page <= last_page; if (copy_page) { num_pages ++; dsc_page(num_pages); } } else if (copy_page) fputs(line, stdout); } } } dsc_trailer(num_pages); fprintf(stderr, "ATTR: job-impressions=%d\n", num_pages / copies); if (fp != stdin) fclose(fp); return (0); } /* * 'raster_to_ps()' - Convert PWG Raster/Apple Raster to PostScript. * * The current implementation locally-decodes the raster data and then writes * whole, non-blank lines as 1-line high images with base-85 encoding, resulting * in between 10 and 20 times larger output. A alternate implementation (if it * is deemed necessary) would be to implement a PostScript decode procedure that * handles the modified packbits decompression so that we just have the base-85 * encoding overhead (25%). Furthermore, Level 3 PostScript printers also * support Flate compression. * * That said, the most efficient path with the highest quality is for Clients * to supply PDF files and us to use the existing PDF to PostScript conversion * filters. */ static int /* O - Exit status */ raster_to_ps(const char *filename) /* I - Filename */ { int fd; /* Input file */ cups_raster_t *ras; /* Raster stream */ cups_page_header2_t header; /* Page header */ int page = 0; /* Current page */ unsigned y; /* Current line */ unsigned char *line; /* Line buffer */ unsigned char white; /* White color */ const char *decode; /* Image decode array */ /* * 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); } dsc_header(0); while (cupsRasterReadHeader2(ras, &header)) { page ++; fprintf(stderr, "DEBUG: Page %d: %ux%ux%u\n", page, header.cupsWidth, header.cupsHeight, header.cupsBitsPerPixel); if (header.cupsColorSpace != CUPS_CSPACE_W && header.cupsColorSpace != CUPS_CSPACE_SW && header.cupsColorSpace != CUPS_CSPACE_K && header.cupsColorSpace != CUPS_CSPACE_RGB && header.cupsColorSpace != CUPS_CSPACE_SRGB) { 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); dsc_page(page); puts("gsave"); printf("%.6f %.6f scale\n", 72.0f / header.HWResolution[0], 72.0f / header.HWResolution[1]); switch (header.cupsColorSpace) { case CUPS_CSPACE_W : case CUPS_CSPACE_SW : decode = "0 1"; puts("/DeviceGray setcolorspace"); white = 255; break; case CUPS_CSPACE_K : decode = "0 1"; puts("/DeviceGray setcolorspace"); white = 0; break; default : decode = "0 1 0 1 0 1"; puts("/DeviceRGB setcolorspace"); white = 255; break; } printf("gsave /L{grestore gsave 0 exch translate <>image}bind def\n", header.cupsWidth, header.cupsBitsPerColor, decode); for (y = header.cupsHeight; y > 0; y --) { if (cupsRasterReadPixels(ras, line, header.cupsBytesPerLine)) { if (line[0] != white || memcmp(line, line + 1, header.cupsBytesPerLine - 1)) { printf("%d L\n", y - 1); ascii85(line, (int)header.cupsBytesPerLine, 1); } } else break; } fprintf(stderr, "DEBUG: y=%d at end...\n", y); puts("grestore grestore"); puts("showpage"); free(line); } cupsRasterClose(ras); dsc_trailer(page); fprintf(stderr, "ATTR: job-impressions=%d\n", page); return (0); }