/* * PDF to PostScript filter front-end for CUPS. * * Copyright 2007-2011 by Apple Inc. * Copyright 1997-2006 by Easy Software Products. * Copyright 2011-2013 by Till Kamppeter * * These coded instructions, statements, and computer programs are the * property of Apple Inc. and are protected by Federal copyright * law. Distribution and use rights are outlined in the file "COPYING" * which should have been included with this file. * * Contents: * * parsePDFTOPDFComment() - Check whether we are executed after pdftopdf * remove_options() - Remove unwished entries from an option list * log_command_line() - Log the command line of a program which we call * main() - Main entry for filter... * cancel_job() - Flag the job as canceled. */ /* * Include necessary headers... */ #include #include #include #include #include #include #include #include #include #include #include #define MAX_CHECK_COMMENT_LINES 20 /* * Type definitions */ typedef unsigned renderer_t; enum renderer_e {GS = 0, PDFTOPS = 1, ACROREAD = 2, PDFTOCAIRO = 3, MUPDF = 4, HYBRID = 5}; /* * Local functions... */ static void cancel_job(int sig); /* * Local globals... */ static int job_canceled = 0; int pdftopdfapplied = 0; char deviceCopies[32] = "1"; int deviceCollate = 0; char make_model[128] = ""; /* * When calling the "pstops" filter we exclude the following options from its * command line as we have applied these options already to the PDF input, * either on the "pdftops"/Ghostscript call in this filter or by use of the * "pdftopdf" filter before this filter. */ const char *pstops_exclude_general[] = { "fitplot", "fit-to-page", "landscape", "orientation-requested", NULL }; const char *pstops_exclude_page_management[] = { "brightness", "Collate", "cupsEvenDuplex", "gamma", "hue", "ipp-attribute-fidelity", "MirrorPrint", "mirror", "multiple-document-handling", "natural-scaling", "number-up", "number-up-layout", "OutputOrder", "page-border", "page-bottom", "page-label", "page-left", "page-ranges", "page-right", "page-set", "page-top", "position", "saturation", "scaling", NULL }; /* * Check whether we were called after the "pdftopdf" filter and extract * parameters passed over by "pdftopdf" in the header comments of the PDF * file */ static void parsePDFTOPDFComment(char *filename) { char buf[4096]; int i; FILE *fp; if ((fp = fopen(filename,"rb")) == 0) { fprintf(stderr, "ERROR: pdftops - cannot open print file \"%s\"\n", filename); return; } /* skip until PDF start header */ while (fgets(buf,sizeof(buf),fp) != 0) { if (strncmp(buf,"%PDF",4) == 0) { break; } } for (i = 0;i < MAX_CHECK_COMMENT_LINES;i++) { if (fgets(buf,sizeof(buf),fp) == 0) break; if (strncmp(buf,"%%PDFTOPDFNumCopies",19) == 0) { char *p; p = strchr(buf+19,':') + 1; while (*p == ' ' || *p == '\t') p++; strncpy(deviceCopies, p, sizeof(deviceCopies)); deviceCopies[sizeof(deviceCopies) - 1] = '\0'; p = deviceCopies + strlen(deviceCopies) - 1; while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') p--; *(p + 1) = '\0'; pdftopdfapplied = 1; } else if (strncmp(buf,"%%PDFTOPDFCollate",17) == 0) { char *p; p = strchr(buf+17,':') + 1; while (*p == ' ' || *p == '\t') p++; if (strncasecmp(p,"true",4) == 0) { deviceCollate = 1; } else { deviceCollate = 0; } pdftopdfapplied = 1; } else if (strcmp(buf,"% This file was generated by pdftopdf") == 0) { pdftopdfapplied = 1; } } fclose(fp); } /* * Remove all options in option_list from the string option_str, including * option values after an "=" sign and preceded "no" before boolean options */ void remove_options(char *options_str, const char **option_list) { const char **option; /* Option to be removed now */ char *option_start, /* Start of option in string */ *option_end; /* End of option in string */ for (option = option_list; *option; option ++) { option_start = options_str; while ((option_start = strcasestr(option_start, *option)) != NULL) { if (!option_start[strlen(*option)] || isspace(option_start[strlen(*option)] & 255) || option_start[strlen(*option)] == '=') { /* * Strip option... */ option_end = option_start + strlen(*option); /* Remove preceding "no" of boolean option */ if ((option_start - options_str) >= 2 && !strncasecmp(option_start - 2, "no", 2)) option_start -= 2; /* Is match of the searched option name really at the beginning of the name of the option in the command line? */ if ((option_start > options_str) && (!isspace(*(option_start - 1) & 255))) { /* Prevent the same option to be found again. */ option_start += 1; /* Skip */ continue; } /* Remove "=" and value */ while (*option_end && !isspace(*option_end & 255)) option_end ++; /* Remove spaces up to next option */ while (*option_end && isspace(*option_end & 255)) option_end ++; memmove(option_start, option_end, strlen(option_end) + 1); } else { /* Prevent the same option to be found again. */ option_start += 1; } } } } /* * Before calling any command line utility, log its command line in CUPS' * debug mode */ void log_command_line(const char* file, char *const argv[]) { int i; char *apos; /* Debug output: Full command line of program to be called */ fprintf(stderr, "DEBUG: Running command line for %s:", (file ? file : argv[0])); if (file) fprintf(stderr, " %s", file); for (i = (file ? 1 : 0); argv[i]; i ++) { if ((strchr(argv[i],' ')) || (strchr(argv[i],'\t'))) apos = "'"; else apos = ""; fprintf(stderr, " %s%s%s", apos, argv[i], apos); } fprintf(stderr, "\n"); } /* * 'main()' - Main entry for filter... */ int /* O - Exit status */ main(int argc, /* I - Number of command-line args */ char *argv[]) /* I - Command-line arguments */ { renderer_t renderer = CUPS_PDFTOPS_RENDERER; /* Renderer: gs or pdftops or acroread or pdftocairo or hybrid */ int fd = 0; /* Copy file descriptor */ char *filename, /* PDF file to convert */ tempfile[1024]; /* Temporary file */ char buffer[8192]; /* Copy buffer */ int bytes; /* Bytes copied */ int num_options; /* Number of options */ cups_option_t *options; /* Options */ const char *val; /* Option value */ ppd_file_t *ppd; /* PPD file */ char resolution[128] = ""; /* Output resolution */ int xres = 0, yres = 0, /* resolution values */ mres, res, maxres = CUPS_PDFTOPS_MAX_RESOLUTION, /* Maximum image rendering resolution */ numvalues; /* Number of values actually read */ ppd_choice_t *choice; ppd_attr_t *attr; cups_page_header2_t header; cups_file_t *fp; /* Post-processing input file */ int pdf_pid, /* Process ID for pdftops/gs */ pdf_argc = 0, /* Number of args for pdftops/gs */ pstops_pid, /* Process ID of pstops filter */ pstops_pipe[2], /* Pipe to pstops filter */ need_post_proc = 0, /* Post-processing needed? */ post_proc_pid = 0, /* Process ID of post-processing */ post_proc_pipe[2], /* Pipe to post-processing */ wait_children, /* Number of child processes left */ wait_pid, /* Process ID from wait() */ wait_status, /* Status from child */ exit_status = 0; /* Exit status */ int gray_output = 0; /* Checking for monochrome/grayscale PostScript output */ char *pdf_argv[100], /* Arguments for pdftops/gs */ pstops_path[1024], /* Path to pstops program */ *pstops_argv[7], /* Arguments for pstops filter */ *pstops_options, /* Options for pstops filter */ *pstops_end, /* End of pstops filter option */ *ptr; /* Pointer into value */ const char *cups_serverbin; /* CUPS_SERVERBIN environment variable */ int duplex, tumble; /* Duplex settings for PPD-less printing */ #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET) struct sigaction action; /* Actions for POSIX signals */ #endif /* HAVE_SIGACTION && !HAVE_SIGSET */ /* * Make sure status messages are not buffered... */ setbuf(stderr, NULL); /* * Ignore broken pipe signals... */ signal(SIGPIPE, SIG_IGN); /* * Make sure we have the right number of arguments for CUPS! */ if (argc < 6 || argc > 7) { fprintf(stderr, "Usage: %s job user title copies options [file]\n", argv[0]); return (1); } /* * Register a signal handler to cleanly cancel a job. */ #ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */ sigset(SIGTERM, cancel_job); #elif defined(HAVE_SIGACTION) memset(&action, 0, sizeof(action)); sigemptyset(&action.sa_mask); action.sa_handler = cancel_job; sigaction(SIGTERM, &action, NULL); #else signal(SIGTERM, cancel_job); #endif /* HAVE_SIGSET */ /* * Copy stdin if needed... */ if (argc == 6) { /* * Copy stdin to a temp file... */ if ((fd = cupsTempFd(tempfile, sizeof(tempfile))) < 0) { perror("DEBUG: Unable to copy PDF file"); return (1); } fprintf(stderr, "DEBUG: pdftops - copying to temp print file \"%s\"\n", tempfile); while ((bytes = fread(buffer, 1, sizeof(buffer), stdin)) > 0) bytes = write(fd, buffer, bytes); close(fd); filename = tempfile; } else { /* * Use the filename on the command-line... */ filename = argv[6]; tempfile[0] = '\0'; } /* * Read out copy counts and collate setting passed over by pdftopdf */ parsePDFTOPDFComment(filename); /* * Read out the options from the fifth command line argument */ num_options = cupsParseOptions(argv[5], 0, &options); /* * Load the PPD file and mark options... */ ppd = ppdOpenFile(getenv("PPD")); if (ppd) { ppdMarkDefaults(ppd); cupsMarkOptions(ppd, num_options, options); } if ((val = cupsGetOption("make-and-model", num_options, options)) != NULL) { strncpy(make_model, val, sizeof(make_model)); if (strlen(val) > 127) make_model[127] = '\0'; for (ptr = make_model; *ptr; ptr ++) if (*ptr == '-') *ptr = ' '; } else if (ppd) { snprintf(make_model, sizeof(make_model), "%s %s", ppd->manufacturer, ppd->product + 1); make_model[strlen(make_model) - 1] = '\0'; } fprintf(stderr, "DEBUG: Printer make and model: %s\n", make_model); /* * Select the PDF renderer: Ghostscript (gs), Poppler (pdftops), * Adobe Reader (arcoread), Poppler with Cairo (pdftocairo), or * Hybrid (hybrid, Poppler for Brother, Minolta, Konica Minolta, Dell, and * old HP LaserJets and Ghostscript otherwise) */ if ((val = cupsGetOption("pdftops-renderer", num_options, options)) != NULL) { if (strcasecmp(val, "gs") == 0) renderer = GS; else if (strcasecmp(val, "pdftops") == 0) renderer = PDFTOPS; else if (strcasecmp(val, "acroread") == 0) renderer = ACROREAD; else if (strcasecmp(val, "pdftocairo") == 0) renderer = PDFTOCAIRO; else if (strcasecmp(val, "mupdf") == 0) renderer = MUPDF; else if (strcasecmp(val, "hybrid") == 0) renderer = HYBRID; else fprintf(stderr, "WARNING: Invalid value for \"pdftops-renderer\": \"%s\"\n", val); } if (renderer == HYBRID) { if (make_model[0] && (!strncasecmp(make_model, "Brother", 7) || !strncasecmp(make_model, "Dell", 4) || strcasestr(make_model, "Minolta") || (!strncasecmp(make_model, "Apple", 5) && (ptr = strcasestr(make_model, "LaserWriter")) && ((ptr = strcasestr(ptr + 11, "4")) || (ptr = strcasestr(ptr + 11, "12")) || (ptr = strcasestr(ptr + 11, "16"))) && ((ptr = strcasestr(ptr + 2, "600")) || (ptr = strcasestr(ptr + 2, "640")) || (ptr = strcasestr(ptr + 2, "660")))))) { fprintf(stderr, "DEBUG: Switching to Poppler's pdftops instead of Ghostscript for Brother, Minolta, Konica Minolta, Dell, and Apple LaserWriter 16/600, 4/600, 12/640, 12/600, 12/660 to work around bugs in the printer's PS interpreters\n"); renderer = PDFTOPS; } else renderer = GS; /* * Use Poppler instead of Ghostscript for old HP LaserJet printers due to * a bug in their PS interpreters. They are very slow with Ghostscript. * A LaserJet is considered old if its model number does not have a letter * in the beginning, like LaserJet 3 or LaserJet 4000, not LaserJet P2015. * See https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=742765 */ if (make_model[0] && ((!strncasecmp(make_model, "HP", 2) || !strncasecmp(make_model, "Hewlett-Packard", 15) || !strncasecmp(make_model, "Hewlett Packard", 15)) && (ptr = strcasestr(make_model, "LaserJet")))) { for (ptr += 8; *ptr; ptr ++) { if (isspace(*ptr)) continue; if (isdigit(*ptr)) { fprintf(stderr, "DEBUG: Switching to Poppler's pdftops instead of Ghostscript for old HP LaserJet (\"LaserJet \", no letters before ) printers to work around bugs in the printer's PS interpreters\n"); renderer = PDFTOPS; } break; } } } /* * Build the pstops command-line... */ if ((cups_serverbin = getenv("CUPS_SERVERBIN")) == NULL) cups_serverbin = CUPS_SERVERBIN; snprintf(pstops_path, sizeof(pstops_path), "%s/filter/pstops", cups_serverbin); pstops_options = strdup(argv[5]); /* * Strip options which "pstops" does not need to apply any more */ remove_options(pstops_options, pstops_exclude_general); if (pdftopdfapplied) remove_options(pstops_options, pstops_exclude_page_management); if (pdftopdfapplied && deviceCollate) { /* * Add collate option to the pstops call if pdftopdf has found out that the * printer does hardware collate. */ pstops_options = realloc(pstops_options, strlen(pstops_options) + 9); if (!pstops_options) { fprintf(stderr, "ERROR: Can't allocate pstops_options\n"); exit(2); } pstops_end = pstops_options + strlen(pstops_options); strcpy(pstops_end, " Collate"); } pstops_argv[0] = argv[0]; /* Printer */ pstops_argv[1] = argv[1]; /* Job */ pstops_argv[2] = argv[2]; /* User */ pstops_argv[3] = argv[3]; /* Title */ if (pdftopdfapplied) pstops_argv[4] = deviceCopies; /* Copies */ else pstops_argv[4] = argv[4]; /* Copies */ pstops_argv[5] = pstops_options; /* Options */ pstops_argv[6] = NULL; log_command_line("pstops", pstops_argv); /* * Force monochrome/grayscale PostScript output * if job is to be printed in monochrome/grayscale */ if (ppd->color_device == 0) /* Monochrome printer */ gray_output = 1; else /*Color Printer - user option for Grayscale */ { if ((val = cupsGetOption("pwg-raster-document-type", num_options, options)) != NULL || (val = cupsGetOption("PwgRasterDocumentType", num_options, options)) != NULL || (val = cupsGetOption("print-color-mode", num_options, options)) != NULL || (val = cupsGetOption("PrintColorMode", num_options, options)) != NULL || (val = cupsGetOption("color-space", num_options, options)) != NULL || (val = cupsGetOption("ColorSpace", num_options, options)) != NULL || (val = cupsGetOption("color-model", num_options, options)) != NULL || (val = cupsGetOption("ColorModel", num_options, options)) != NULL || (val = cupsGetOption("output-mode", num_options, options)) != NULL || (val = cupsGetOption("OutputMode", num_options, options)) != NULL) { if (strcasestr(val, "Black") || strcasestr(val, "Gray") || strcasestr(val, "Mono")) gray_output = 1; } } /* * Build the command-line for the pdftops, gs, mutool, pdftocairo, or * acroread filter... */ if (renderer == PDFTOPS) { pdf_argv[0] = (char *)"pdftops"; pdf_argc = 1; } else if (renderer == GS) { pdf_argv[0] = (char *)"gs"; pdf_argv[1] = (char *)"-q"; pdf_argv[2] = (char *)"-dNOPAUSE"; pdf_argv[3] = (char *)"-dBATCH"; pdf_argv[4] = (char *)"-dSAFER"; pdf_argv[5] = (char *)"-dNOMEDIAATTRS"; # ifdef HAVE_GHOSTSCRIPT_PS2WRITE pdf_argv[6] = (char *)"-sDEVICE=ps2write"; # else pdf_argv[6] = (char *)"-sDEVICE=pswrite"; # endif /* HAVE_GHOSTSCRIPT_PS2WRITE */ pdf_argv[7] = (char *)"-dShowAcroForm"; pdf_argv[8] = (char *)"-sOUTPUTFILE=%stdout"; if (gray_output == 1) /* Checking for monochrome/grayscale PostScript output */ { pdf_argv[9] = (char *)"-sColorConversionStrategy=Gray"; pdf_argc = 10; } else pdf_argc = 9; } else if (renderer == MUPDF) { pdf_argv[0] = (char *)"mutool"; pdf_argv[1] = (char *)"draw"; pdf_argv[2] = (char *)"-L"; pdf_argv[3] = (char *)"-smtf"; pdf_argv[4] = (char *)"-Fps"; pdf_argv[5] = (char *)"-o-"; if (gray_output == 1) /* Checking for monochrome/grayscale PostScript output */ pdf_argv[6] = (char *)"-cgray"; else pdf_argv[6] = (char *)"-crgb"; pdf_argc = 7; } else if (renderer == PDFTOCAIRO) { pdf_argv[0] = (char *)"pdftocairo"; pdf_argv[1] = (char *)"-ps"; pdf_argc = 2; } else if (renderer == ACROREAD) { pdf_argv[0] = (char *)"acroread"; pdf_argv[1] = (char *)"-toPostScript"; pdf_argc = 2; } /* * Set language level and TrueType font handling... */ if (ppd) { if (ppd->language_level == 1) { if (renderer == PDFTOPS) { pdf_argv[pdf_argc++] = (char *)"-level1"; pdf_argv[pdf_argc++] = (char *)"-noembtt"; } else if (renderer == GS) pdf_argv[pdf_argc++] = (char *)"-dLanguageLevel=1"; else if (renderer == PDFTOCAIRO) fprintf(stderr, "WARNING: Level 1 PostScript not supported by pdftocairo.\n"); else if (renderer == ACROREAD) fprintf(stderr, "WARNING: Level 1 PostScript not supported by acroread.\n"); else if (renderer == MUPDF) fprintf(stderr, "WARNING: Level 1 PostScript not supported by mutool.\n"); } else if (ppd->language_level == 2) { if (renderer == PDFTOPS) { pdf_argv[pdf_argc++] = (char *)"-level2"; if (!ppd->ttrasterizer) pdf_argv[pdf_argc++] = (char *)"-noembtt"; } else if (renderer == GS) pdf_argv[pdf_argc++] = (char *)"-dLanguageLevel=2"; else if (renderer != MUPDF) /* MuPDF is PS level 2 only */ /* PDFTOCAIRO, ACROREAD */ pdf_argv[pdf_argc++] = (char *)"-level2"; } else { if (renderer == PDFTOPS) { /* Do not emit PS Level 3 with Poppler on Brother and HP PostScript laser printers as some do not like it. See https://bugs.launchpad.net/bugs/277404 and https://bugs.launchpad.net/bugs/1306849 comment #42. */ if (!make_model[0] || !strncasecmp(make_model, "Brother", 7) || ((!strncasecmp(make_model, "HP", 2) || !strncasecmp(make_model, "Hewlett-Packard", 15) || !strncasecmp(make_model, "Hewlett Packard", 15)) && (strcasestr(make_model, "LaserJet")))) pdf_argv[pdf_argc++] = (char *)"-level2"; else pdf_argv[pdf_argc++] = (char *)"-level3"; } else if (renderer == GS) { /* Do not emit PS Level 3 with Ghostscript on Brother PostScript laser printers as some do not like it. See https://bugs.launchpad.net/bugs/1306849 comment #42. */ if (!make_model[0] || !strncasecmp(make_model, "Brother", 7)) pdf_argv[pdf_argc++] = (char *)"-dLanguageLevel=2"; else pdf_argv[pdf_argc++] = (char *)"-dLanguageLevel=3"; } else if (renderer == MUPDF) fprintf(stderr, "WARNING: Level 3 PostScript not supported by mutool.\n"); else /* PDFTOCAIRO || ACROREAD */ pdf_argv[pdf_argc++] = (char *)"-level3"; } } else { if (renderer == PDFTOPS) { /* Do not emit PS Level 3 with Poppler on HP PostScript laser printers as some do not like it. See https://bugs.launchpad.net/bugs/277404.*/ if (!make_model[0] || ((!strncasecmp(make_model, "HP", 2) || !strncasecmp(make_model, "Hewlett-Packard", 15) || !strncasecmp(make_model, "Hewlett Packard", 15)) && (strcasestr(make_model, "LaserJet")))) pdf_argv[pdf_argc++] = (char *)"-level2"; else pdf_argv[pdf_argc++] = (char *)"-level3"; pdf_argv[pdf_argc++] = (char *)"-noembtt"; } else if (renderer == GS) pdf_argv[pdf_argc++] = (char *)"-dLanguageLevel=3"; else if (renderer != MUPDF) /* MuPDF is PS level 2 only */ /* PDFTOCAIRO || ACROREAD */ pdf_argv[pdf_argc++] = (char *)"-level3"; } #ifdef HAVE_POPPLER_PDFTOPS_WITH_ORIGPAGESIZES if ((renderer == PDFTOPS) || (renderer == PDFTOCAIRO)) { /* * Use the page sizes of the original PDF document, this way documents * which contain pages of different sizes can be printed correctly */ pdf_argv[pdf_argc++] = (char *)"-origpagesizes"; pdf_argv[pdf_argc++] = (char *)"-nocenter"; } else #endif /* HAVE_POPPLER_PDFTOPS_WITH_ORIGPAGESIZES */ if (renderer == ACROREAD) { /* * Use the page sizes of the original PDF document, this way documents * which contain pages of different sizes can be printed correctly */ pdf_argv[pdf_argc++] = (char *)"-choosePaperByPDFPageSize"; } /* * Set output resolution ... */ if (ppd) { /* Ignore error exits of cupsRasterInterpretPPD(), if it found a resolution setting before erroring it is OK for us */ cupsRasterInterpretPPD(&header, ppd, num_options, options, NULL); /* 100 dpi is default, this means that if we have 100 dpi here this method failed to find the printing resolution */ if (header.HWResolution[0] > 100 && header.HWResolution[1] > 100) { xres = header.HWResolution[0]; yres = header.HWResolution[1]; } else if ((choice = ppdFindMarkedChoice(ppd, "Resolution")) != NULL) strncpy(resolution, choice->choice, sizeof(resolution)); else if ((attr = ppdFindAttr(ppd,"DefaultResolution",NULL)) != NULL) strncpy(resolution, attr->value, sizeof(resolution)); resolution[sizeof(resolution)-1] = '\0'; if ((xres == 0) && (yres == 0) && ((numvalues = sscanf(resolution, "%dx%d", &xres, &yres)) <= 0)) fprintf(stderr, "DEBUG: No resolution information found in the PPD file.\n"); } if ((xres == 0) && (yres == 0)) { if ((val = cupsGetOption("printer-resolution", num_options, options)) != NULL || (val = cupsGetOption("Resolution", num_options, options)) != NULL) { xres = yres = strtol(val, (char **)&ptr, 10); if (ptr > val && xres > 0) { if (*ptr == 'x') yres = strtol(ptr + 1, (char **)&ptr, 10); } if (ptr <= val || xres <= 0 || yres <= 0 || !ptr || (*ptr != '\0' && strcasecmp(ptr, "dpi") && strcasecmp(ptr, "dpc") && strcasecmp(ptr, "dpcm"))) { fprintf(stderr, "DEBUG: Bad resolution value \"%s\".\n", val); } else { if (!strcasecmp(ptr, "dpc") || !strcasecmp(ptr, "dpcm")) { xres = xres * 254 / 100; yres = yres * 254 / 100; } } } } if ((xres > 0) || (yres > 0)) { if (yres == 0) yres = xres; if (xres == 0) xres = yres; if (xres > yres) res = yres; else res = xres; } else res = 300; /* * Get the ceiling for the image rendering resolution */ if ((val = cupsGetOption("pdftops-max-image-resolution", num_options, options)) != NULL) { if ((numvalues = sscanf(val, "%d", &mres)) > 0) maxres = mres; else fprintf(stderr, "WARNING: Invalid value for \"pdftops-max-image-resolution\": \"%s\"\n", val); } /* * Reduce the image rendering resolution to not exceed a given maximum * to make processing of jobs by the PDF->PS converter and the printer faster * * maxres = 0 means no limit */ if (maxres) while (res > maxres) res = res / 2; if ((renderer == PDFTOPS) || (renderer == PDFTOCAIRO)) { #ifdef HAVE_POPPLER_PDFTOPS_WITH_RESOLUTION /* * Set resolution to avoid slow processing by the printer when the * resolution of embedded images does not match the printer's resolution */ pdf_argv[pdf_argc++] = (char *)"-r"; snprintf(resolution, sizeof(resolution), "%d", res); pdf_argv[pdf_argc++] = resolution; fprintf(stderr, "DEBUG: Using image rendering resolution %d dpi\n", res); #endif /* HAVE_POPPLER_PDFTOPS_WITH_RESOLUTION */ if (gray_output == 1) /* Checking for monochrome/grayscale PostScript output */ { pdf_argv[1] = (char *)"-level1"; pdf_argv[pdf_argc++] = (char *)"-optimizecolorspace"; } pdf_argv[pdf_argc++] = filename; pdf_argv[pdf_argc++] = (char *)"-"; } else if (renderer == GS) { /* * Set resolution to avoid slow processing by the printer when the * resolution of embedded images does not match the printer's resolution */ snprintf(resolution, 127, "-r%d", res); pdf_argv[pdf_argc++] = resolution; fprintf(stderr, "DEBUG: Using image rendering resolution %d dpi\n", res); /* * PostScript debug mode: If you send a job with "lpr -o psdebug" Ghostscript * will not compress the pages, so that the PostScript code can get * analysed. This is especially important if a PostScript printer errors or * misbehaves on Ghostscript's output. * On Kyocera and Utax (uses Kyocera hard- and software) printers we always * suppress page compression, to avoid slow processing of raster images. */ val = cupsGetOption("psdebug", num_options, options); if ((val && strcasecmp(val, "no") && strcasecmp(val, "off") && strcasecmp(val, "false")) || (make_model[0] && (!strncasecmp(make_model, "Kyocera", 7) || !strncasecmp(make_model, "Utax", 4)))) { fprintf(stderr, "DEBUG: Deactivated compression of pages in Ghostscript's PostScript output (\"psdebug\" debug mode or Kyocera/Utax printer)\n"); pdf_argv[pdf_argc++] = (char *)"-dCompressPages=false"; } /* * The PostScript interpreters on many printers have bugs which make * the interpreter crash, error out, or otherwise misbehave on too * heavily compressed input files, especially if code with compressed * elements is compressed again. Therefore we reduce compression here. */ pdf_argv[pdf_argc++] = (char *)"-dCompressFonts=false"; pdf_argv[pdf_argc++] = (char *)"-dNoT3CCITT"; if (make_model[0] && !strncasecmp(make_model, "Brother", 7)) { fprintf(stderr, "DEBUG: Deactivation of Ghostscript's image compression for Brother printers to workarounmd PS interpreter bug\n"); pdf_argv[pdf_argc++] = (char *)"-dEncodeMonoImages=false"; pdf_argv[pdf_argc++] = (char *)"-dEncodeColorImages=false"; } /* * Toshiba's PS interpreters have an issue with how we handle * TrueType/Type42 fonts, therefore we add command line options to turn * the TTF outlines into bitmaps, usually Type 3 PostScript fonts, only * large glyphs into normal image data. * See https://bugs.launchpad.net/bugs/978120 */ if (make_model[0] && !strncasecmp(make_model, "Toshiba", 7)) { fprintf(stderr, "DEBUG: To work around a bug in Toshiba's PS interpreters turn TTF font glyphs into bitmaps, usually Type 3 PS fonts, or images for large characters\n"); pdf_argv[pdf_argc++] = (char *)"-dHaveTrueTypes=false"; } pdf_argv[pdf_argc++] = (char *)"-dNOINTERPOLATE"; pdf_argv[pdf_argc++] = (char *)"-c"; if (make_model[0] && !strncasecmp(make_model, "Toshiba", 7)) pdf_argv[pdf_argc++] = (char *)"<< /MaxFontItem 500000 >> setuserparams"; pdf_argv[pdf_argc++] = (char *)"save pop"; pdf_argv[pdf_argc++] = (char *)"-f"; pdf_argv[pdf_argc++] = filename; } else if (renderer == MUPDF) { /* * Add Resolution option to avoid slow processing by the printer when the * resolution of embedded images does not match the printer's resolution */ snprintf(resolution, 127, "-r%dx%d", res, res); pdf_argv[pdf_argc++] = resolution; /* * Add input file name */ pdf_argv[pdf_argc++] = filename; } pdf_argv[pdf_argc] = NULL; log_command_line(NULL, pdf_argv); /* * Do we need post-processing of the PostScript output to work around bugs * of the printer's PostScript interpreter? */ if ((renderer == PDFTOPS) || (renderer == PDFTOCAIRO) || (renderer == MUPDF)) need_post_proc = 0; else if (renderer == GS) need_post_proc = (make_model[0] && (!strncasecmp(make_model, "Kyocera", 7) || !strncasecmp(make_model, "Utax", 4) || !strncasecmp(make_model, "Brother", 7)) ? 1 : 0); else need_post_proc = 1; /* * Do we need post-processing of the PostScript output to apply option * settings when doing PPD-less printing? */ if (!ppd) need_post_proc = 1; /* * Execute "pdftops/gs/mutool [ | PS post-processing ] | pstops"... */ /* * Create a pipe for each pair of subsequent processes. The variables * are named by the receiving process. */ if (pipe(pstops_pipe)) { perror("DEBUG: Unable to create pipe for pstops"); exit_status = 1; goto error; } if (need_post_proc) { if (pipe(post_proc_pipe)) { perror("DEBUG: Unable to create pipe for post-processing"); exit_status = 1; goto error; } } if ((pdf_pid = fork()) == 0) { /* * Child comes here... */ if (need_post_proc) { dup2(post_proc_pipe[1], 1); close(post_proc_pipe[0]); close(post_proc_pipe[1]); } else dup2(pstops_pipe[1], 1); close(pstops_pipe[0]); close(pstops_pipe[1]); if (renderer == PDFTOPS) { execvp(CUPS_POPPLER_PDFTOPS, pdf_argv); perror("DEBUG: Unable to execute pdftops program"); } else if (renderer == GS) { execvp(CUPS_GHOSTSCRIPT, pdf_argv); perror("DEBUG: Unable to execute gs program"); } else if (renderer == PDFTOCAIRO) { execvp(CUPS_POPPLER_PDFTOCAIRO, pdf_argv); perror("DEBUG: Unable to execute pdftocairo program"); } else if (renderer == ACROREAD) { /* * use filename as stdin for acroread to force output to stdout */ if ((fd = open(filename, O_RDONLY))) { dup2(fd, 0); close(fd); } execvp(CUPS_ACROREAD, pdf_argv); perror("DEBUG: Unable to execute acroread program"); } else if (renderer == MUPDF) { execvp(CUPS_MUTOOL, pdf_argv); perror("DEBUG: Unable to execute mutool program"); } exit(1); } else if (pdf_pid < 0) { /* * Unable to fork! */ if (renderer == PDFTOPS) perror("DEBUG: Unable to execute pdftops program"); else if (renderer == GS) perror("DEBUG: Unable to execute gs program"); else if (renderer == PDFTOCAIRO) perror("DEBUG: Unable to execute pdftocairo program"); else if (renderer == ACROREAD) perror("DEBUG: Unable to execute acroread program"); else if (renderer == MUPDF) perror("DEBUG: Unable to execute mutool program"); exit_status = 1; goto error; } fprintf(stderr, "DEBUG: Started filter %s (PID %d)\n", pdf_argv[0], pdf_pid); if (need_post_proc) { if ((post_proc_pid = fork()) == 0) { /* * Child comes here... */ dup2(post_proc_pipe[0], 0); close(post_proc_pipe[0]); close(post_proc_pipe[1]); dup2(pstops_pipe[1], 1); close(pstops_pipe[0]); close(pstops_pipe[1]); fp = cupsFileStdin(); if (renderer == ACROREAD) { /* * Set %Title and %For from filter arguments since acroread inserts * garbage for these when using -toPostScript */ while ((bytes = cupsFileGetLine(fp, buffer, sizeof(buffer))) > 0 && strncmp(buffer, "%%BeginProlog", 13)) { if (strncmp(buffer, "%%Title", 7) == 0) printf("%%%%Title: %s\n", argv[3]); else if (strncmp(buffer, "%%For", 5) == 0) printf("%%%%For: %s\n", argv[2]); else printf("%s", buffer); } /* * Copy the rest of the file */ while ((bytes = cupsFileRead(fp, buffer, sizeof(buffer))) > 0) fwrite(buffer, 1, bytes, stdout); } else { /* * Copy everything until after initial comments (Prolog section) */ while ((bytes = cupsFileGetLine(fp, buffer, sizeof(buffer))) > 0 && strncmp(buffer, "%%BeginProlog", 13) && strncmp(buffer, "%%EndProlog", 11) && strncmp(buffer, "%%BeginSetup", 12) && strncmp(buffer, "%%Page:", 7)) printf("%s", buffer); if (bytes > 0) { /* * Insert PostScript interpreter bug fix code in the beginning of * the Prolog section (before the first active PostScript code) */ if (strncmp(buffer, "%%BeginProlog", 13)) { /* No Prolog section, create one */ fprintf(stderr, "DEBUG: Adding Prolog section for workaround PostScript code\n"); puts("%%BeginProlog"); } else printf("%s", buffer); if (renderer == GS && make_model[0]) { /* * Kyocera (and Utax) printers have a bug in their PostScript * interpreter making them crashing on PostScript input data * generated by Ghostscript's "ps2write" output device. * * The problem can be simply worked around by preceding the * PostScript code with some extra bits. * * See https://bugs.launchpad.net/bugs/951627 * * In addition, at least some of Kyocera's PostScript printers are * very slow on rendering images which request interpolation. So we * also add some code to eliminate interpolation requests. * * See https://bugs.launchpad.net/bugs/1026974 */ if (!strncasecmp(make_model, "Kyocera", 7) || !strncasecmp(make_model, "Utax", 4)) { fprintf(stderr, "DEBUG: Inserted workaround PostScript code for Kyocera and Utax printers\n"); puts("% ===== Workaround insertion by pdftops CUPS filter ====="); puts("% Kyocera's/Utax's PostScript interpreter crashes on early name binding,"); puts("% so eliminate all \"bind\"s by redefining \"bind\" to no-op"); puts("/bind {} bind def"); puts("% Some Kyocera and Utax printers have an unacceptably slow implementation"); puts("% of image interpolation."); puts("/image"); puts("{"); puts(" dup /Interpolate known"); puts(" {"); puts(" dup /Interpolate undef"); puts(" } if"); puts(" systemdict /image get exec"); puts("} def"); puts("% ====="); } /* * Brother printers have a bug in their PostScript interpreter * making them printing one blank page if PostScript input data * generated by Ghostscript's "ps2write" output device is used. * * The problem can be simply worked around by preceding the PostScript * code with some extra bits. * * See https://bugs.launchpad.net/bugs/950713 */ else if (!strncasecmp(make_model, "Brother", 7)) { fprintf(stderr, "DEBUG: Inserted workaround PostScript code for Brother printers\n"); puts("% ===== Workaround insertion by pdftops CUPS filter ====="); puts("% Brother's PostScript interpreter spits out the current page"); puts("% and aborts the job on the \"currenthalftone\" operator, so redefine"); puts("% it to null"); puts("/currenthalftone {//null} bind def"); puts("/orig.sethalftone systemdict /sethalftone get def"); puts("/sethalftone {dup //null eq not {//orig.sethalftone}{pop} ifelse} bind def"); puts("% ====="); } } if (strncmp(buffer, "%%BeginProlog", 13)) { /* Close newly created Prolog section */ if (strncmp(buffer, "%%EndProlog", 11)) puts("%%EndProlog"); printf("%s", buffer); } if (!ppd) { /* * Copy everything until the setup section */ while (bytes > 0 && strncmp(buffer, "%%BeginSetup", 12) && strncmp(buffer, "%%EndSetup", 10) && strncmp(buffer, "%%Page:", 7)) { bytes = cupsFileGetLine(fp, buffer, sizeof(buffer)); if (strncmp(buffer, "%%Page:", 7) && strncmp(buffer, "%%EndSetup", 10)) printf("%s", buffer); } if (bytes > 0) { /* * Insert option PostScript code in Setup section */ if (strncmp(buffer, "%%BeginSetup", 12)) { /* No Setup section, create one */ fprintf(stderr, "DEBUG: Adding Setup section for option PostScript code\n"); puts("%%BeginSetup"); } /* * Duplex */ duplex = 0; tumble = 0; if ((val = cupsGetOption("sides", num_options, options)) != NULL || (val = cupsGetOption("Duplex", num_options, options)) != NULL) { if (!strcasecmp(val, "On") || !strcasecmp(val, "True") || !strcasecmp(val, "Yes") || !strncasecmp(val, "two-sided", 9) || !strncasecmp(val, "TwoSided", 8) || !strncasecmp(val, "Duplex", 6)) { duplex = 1; if (!strncasecmp(val, "DuplexTumble", 12)) tumble = 1; } } if ((val = cupsGetOption("sides", num_options, options)) != NULL || (val = cupsGetOption("Tumble", num_options, options)) != NULL) { if (!strcasecmp(val, "None") || !strcasecmp(val, "Off") || !strcasecmp(val, "False") || !strcasecmp(val, "No") || !strcasecmp(val, "one-sided") || !strcasecmp(val, "OneSided") || !strcasecmp(val, "two-sided-long-edge") || !strcasecmp(val, "TwoSidedLongEdge") || !strcasecmp(val, "DuplexNoTumble")) tumble = 0; else if (!strcasecmp(val, "On") || !strcasecmp(val, "True") || !strcasecmp(val, "Yes") || !strcasecmp(val, "two-sided-short-edge") || !strcasecmp(val, "TwoSidedShortEdge") || !strcasecmp(val, "DuplexTumble")) tumble = 1; } if (duplex) { if (tumble) puts("<> setpagedevice"); else puts("<> setpagedevice"); } else puts("<> setpagedevice"); /* * Resolution */ if ((xres > 0) && (yres > 0)) printf("<> setpagedevice\n", xres, yres); /* * InputSlot/MediaSource */ if ((val = cupsGetOption("media-position", num_options, options)) != NULL || (val = cupsGetOption("MediaPosition", num_options, options)) != NULL || (val = cupsGetOption("media-source", num_options, options)) != NULL || (val = cupsGetOption("MediaSource", num_options, options)) != NULL || (val = cupsGetOption("InputSlot", num_options, options)) != NULL) { if (!strncasecmp(val, "Auto", 4) || !strncasecmp(val, "Default", 7)) puts("<> setpagedevice"); else if (!strcasecmp(val, "Main")) puts("<> setpagedevice"); else if (!strcasecmp(val, "Alternate")) puts("<> setpagedevice"); else if (!strcasecmp(val, "Manual")) puts("<> setpagedevice"); else if (!strcasecmp(val, "Top")) puts("<> setpagedevice"); else if (!strcasecmp(val, "Bottom")) puts("<> setpagedevice"); else if (!strcasecmp(val, "ByPassTray")) puts("<> setpagedevice"); else if (!strcasecmp(val, "Tray1")) puts("<> setpagedevice"); else if (!strcasecmp(val, "Tray2")) puts("<> setpagedevice"); else if (!strcasecmp(val, "Tray3")) puts("<> setpagedevice"); } /* * ColorModel */ if ((val = cupsGetOption("pwg-raster-document-type", num_options, options)) != NULL || (val = cupsGetOption("PwgRasterDocumentType", num_options, options)) != NULL || (val = cupsGetOption("print-color-mode", num_options, options)) != NULL || (val = cupsGetOption("PrintColorMode", num_options, options)) != NULL || (val = cupsGetOption("color-space", num_options, options)) != NULL || (val = cupsGetOption("ColorSpace", num_options, options)) != NULL || (val = cupsGetOption("color-model", num_options, options)) != NULL || (val = cupsGetOption("ColorModel", num_options, options)) != NULL) { if (!strncasecmp(val, "Black", 5)) puts("<> setpagedevice"); else if (!strncasecmp(val, "Cmyk", 4)) puts("<> setpagedevice"); else if (!strncasecmp(val, "Cmy", 3)) puts("<> setpagedevice"); else if (!strncasecmp(val, "Rgb", 3)) puts("<> setpagedevice"); else if (!strncasecmp(val, "Gray", 4)) puts("<> setpagedevice"); else if (!strncasecmp(val, "Color", 5)) puts("<> setpagedevice"); } if (strncmp(buffer, "%%BeginSetup", 12)) { /* Close newly created Setup section */ if (strncmp(buffer, "%%EndSetup", 10)) puts("%%EndSetup"); printf("%s", buffer); } } } /* * Copy the rest of the file */ while ((bytes = cupsFileRead(fp, buffer, sizeof(buffer))) > 0) fwrite(buffer, 1, bytes, stdout); } } exit(0); } else if (post_proc_pid < 0) { /* * Unable to fork! */ perror("DEBUG: Unable to execute post-processing process"); exit_status = 1; goto error; } fprintf(stderr, "DEBUG: Started post-processing (PID %d)\n", post_proc_pid); } if ((pstops_pid = fork()) == 0) { /* * Child comes here... */ dup2(pstops_pipe[0], 0); close(pstops_pipe[0]); close(pstops_pipe[1]); if (need_post_proc) { close(post_proc_pipe[0]); close(post_proc_pipe[1]); } execvp(pstops_path, pstops_argv); perror("DEBUG: Unable to execute pstops program"); exit(1); } else if (pstops_pid < 0) { /* * Unable to fork! */ perror("DEBUG: Unable to execute pstops program"); exit_status = 1; goto error; } fprintf(stderr, "DEBUG: Started filter pstops (PID %d)\n", pstops_pid); close(pstops_pipe[0]); close(pstops_pipe[1]); if (need_post_proc) { close(post_proc_pipe[0]); close(post_proc_pipe[1]); } /* * Wait for the child processes to exit... */ wait_children = 2 + need_post_proc; while (wait_children > 0) { /* * Wait until we get a valid process ID or the job is canceled... */ while ((wait_pid = wait(&wait_status)) < 0 && errno == EINTR) { if (job_canceled) { kill(pdf_pid, SIGTERM); if (need_post_proc) kill(post_proc_pid, SIGTERM); kill(pstops_pid, SIGTERM); job_canceled = 0; } } if (wait_pid < 0) break; wait_children --; /* * Report child status... */ if (wait_status) { if (WIFEXITED(wait_status)) { exit_status = WEXITSTATUS(wait_status); fprintf(stderr, "DEBUG: PID %d (%s) stopped with status %d!\n", wait_pid, wait_pid == pdf_pid ? (renderer == PDFTOPS ? "pdftops" : (renderer == PDFTOCAIRO ? "pdftocairo" : (renderer == GS ? "gs" : (renderer == ACROREAD ? "acroread" : (renderer == MUPDF ? "mutool" : "Unknown renderer"))))) : (wait_pid == pstops_pid ? "pstops" : (wait_pid == post_proc_pid ? "Post-processing" : "Unknown process")), exit_status); } else if (WTERMSIG(wait_status) == SIGTERM) { fprintf(stderr, "DEBUG: PID %d (%s) was terminated normally with signal %d!\n", wait_pid, wait_pid == pdf_pid ? (renderer == PDFTOPS ? "pdftops" : (renderer == PDFTOCAIRO ? "pdftocairo" : (renderer == GS ? "gs" : (renderer == ACROREAD ? "acroread" : (renderer == MUPDF ? "mutool" : "Unknown renderer"))))) : (wait_pid == pstops_pid ? "pstops" : (wait_pid == post_proc_pid ? "Post-processing" : "Unknown process")), exit_status); } else { exit_status = WTERMSIG(wait_status); fprintf(stderr, "DEBUG: PID %d (%s) crashed on signal %d!\n", wait_pid, wait_pid == pdf_pid ? (renderer == PDFTOPS ? "pdftops" : (renderer == PDFTOCAIRO ? "pdftocairo" : (renderer == GS ? "gs" : (renderer == ACROREAD ? "acroread" : (renderer == MUPDF ? "mutool" : "Unknown renderer"))))) : (wait_pid == pstops_pid ? "pstops" : (wait_pid == post_proc_pid ? "Post-processing" : "Unknown process")), exit_status); } } else { fprintf(stderr, "DEBUG: PID %d (%s) exited with no errors.\n", wait_pid, wait_pid == pdf_pid ? (renderer == PDFTOPS ? "pdftops" : (renderer == PDFTOCAIRO ? "pdftocairo" : (renderer == GS ? "gs" : (renderer == ACROREAD ? "acroread" : (renderer == MUPDF ? "mutool" : "Unknown renderer"))))) : (wait_pid == pstops_pid ? "pstops" : (wait_pid == post_proc_pid ? "Post-processing" : "Unknown process"))); } } /* * Cleanup and exit... */ error: if (tempfile[0]) unlink(tempfile); return (exit_status); } /* * 'cancel_job()' - Flag the job as canceled. */ static void cancel_job(int sig) /* I - Signal number (unused) */ { (void)sig; job_canceled = 1; }