• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2 
3 Copyright (c) 2008-2016, Till Kamppeter
4 Copyright (c) 2011, Tim Waugh
5 Copyright (c) 2011-2013, Richard Hughes
6 
7 Permission is hereby granted, free of charge, to any person obtaining
8 a copy of this software and associated documentation files (the
9 "Software"), to deal in the Software without restriction, including
10 without limitation the rights to use, copy, modify, merge, publish,
11 distribute, sublicense, and/or sell copies of the Software, and to
12 permit persons to whom the Software is furnished to do so, subject to
13 the following conditions:
14 
15 The above copyright notice and this permission notice shall be included
16 in all copies or substantial portions of the Software.
17 
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23 TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 
26 MIT Open Source License  -  http://www.opensource.org/
27 
28 */
29 
30 
31 /* PS/PDF to CUPS Raster filter based on Ghostscript */
32 
33 #include <config.h>
34 #include <cups/cups.h>
35 #if (CUPS_VERSION_MAJOR > 1) || (CUPS_VERSION_MINOR > 6)
36 #define HAVE_CUPS_1_7 1
37 #endif
38 
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <stdarg.h>
42 #include <string.h>
43 #include <fcntl.h>
44 #include <cups/raster.h>
45 #include <cupsfilters/colormanager.h>
46 #include <cupsfilters/raster.h>
47 #include <sys/types.h>
48 #include <sys/wait.h>
49 #include <signal.h>
50 #include <errno.h>
51 #include "pdf.h"
52 
53 #define PDF_MAX_CHECK_COMMENT_LINES	20
54 
55 typedef enum {
56   GS_DOC_TYPE_PDF,
57   GS_DOC_TYPE_PS,
58   GS_DOC_TYPE_UNKNOWN
59 } GsDocType;
60 
61 typedef enum {
62   OUTPUT_FORMAT_RASTER,
63   OUTPUT_FORMAT_PDF,
64   OUTPUT_FORMAT_PXL
65 } OutFormatType;
66 
67 #ifdef CUPS_RASTER_SYNCv1
68 typedef cups_page_header2_t gs_page_header;
69 #else
70 typedef cups_page_header_t gs_page_header;
71 #endif /* CUPS_RASTER_SYNCv1 */
72 
73 static GsDocType
parse_doc_type(FILE * fp)74 parse_doc_type(FILE *fp)
75 {
76   char buf[5];
77 
78   /* get the first few bytes of the file */
79   rewind(fp);
80 /* skip until PDF/PS start header */
81  while (fgets(buf,sizeof(buf),fp) != 0) {
82    if (strncmp(buf,"%PDF",4) == 0) return GS_DOC_TYPE_PDF;
83    if (strncmp(buf,"%!",2) == 0) return GS_DOC_TYPE_PS;
84   }
85   return GS_DOC_TYPE_UNKNOWN;
86 }
87 
88 static void
parse_pdf_header_options(FILE * fp,gs_page_header * h)89 parse_pdf_header_options(FILE *fp, gs_page_header *h)
90 {
91   char buf[4096];
92   int i;
93 
94   rewind(fp);
95   /* skip until PDF start header */
96   while (fgets(buf,sizeof(buf),fp) != 0) {
97     if (strncmp(buf,"%PDF",4) == 0) {
98       break;
99     }
100   }
101   for (i = 0;i < PDF_MAX_CHECK_COMMENT_LINES;i++) {
102     if (fgets(buf,sizeof(buf),fp) == 0) break;
103     if (strncmp(buf,"%%PDFTOPDFNumCopies",19) == 0) {
104       char *p;
105 
106       p = strchr(buf+19,':');
107       h->NumCopies = atoi(p+1);
108     } else if (strncmp(buf,"%%PDFTOPDFCollate",17) == 0) {
109       char *p;
110 
111       p = strchr(buf+17,':');
112       while (*p == ' ' || *p == '\t') p++;
113       if (strncasecmp(p,"true",4) == 0) {
114         h->Collate = CUPS_TRUE;
115       } else {
116         h->Collate = CUPS_FALSE;
117       }
118     }
119   }
120 }
121 
122 static void
add_pdf_header_options(gs_page_header * h,cups_array_t * gs_args,OutFormatType outformat,int pxlcolor)123 add_pdf_header_options(gs_page_header *h, cups_array_t *gs_args,
124 		       OutFormatType outformat, int pxlcolor)
125 {
126   int i;
127   char tmpstr[1024];
128 
129   /* Simple boolean, enumerated choice, numerical, and string parameters */
130   if (outformat == OUTPUT_FORMAT_RASTER) {
131     if (h->MediaClass[0] |= '\0') {
132       snprintf(tmpstr, sizeof(tmpstr), "-sMediaClass=%s", h->MediaClass);
133       cupsArrayAdd(gs_args, strdup(tmpstr));
134     }
135     if (h->MediaColor[0] |= '\0') {
136       snprintf(tmpstr, sizeof(tmpstr), "-sMediaColor=%s", h->MediaColor);
137       cupsArrayAdd(gs_args, strdup(tmpstr));
138     }
139     if (h->MediaType[0] |= '\0') {
140       snprintf(tmpstr, sizeof(tmpstr), "-sMediaType=%s", h->MediaType);
141       cupsArrayAdd(gs_args, strdup(tmpstr));
142     }
143     if (h->OutputType[0] |= '\0') {
144       snprintf(tmpstr, sizeof(tmpstr), "-sOutputType=%s", h->OutputType);
145       cupsArrayAdd(gs_args, strdup(tmpstr));
146     }
147     if (h->AdvanceDistance) {
148       snprintf(tmpstr, sizeof(tmpstr), "-dAdvanceDistance=%d",
149 	       (unsigned)(h->AdvanceDistance));
150       cupsArrayAdd(gs_args, strdup(tmpstr));
151     }
152     if (h->AdvanceMedia) {
153       snprintf(tmpstr, sizeof(tmpstr), "-dAdvanceMedia=%d",
154 	       (unsigned)(h->AdvanceMedia));
155       cupsArrayAdd(gs_args, strdup(tmpstr));
156     }
157     if (h->Collate) {
158       cupsArrayAdd(gs_args, strdup("-dCollate"));
159     }
160     if (h->CutMedia) {
161       snprintf(tmpstr, sizeof(tmpstr), "-dCutMedia=%d",
162 	       (unsigned)(h->CutMedia));
163       cupsArrayAdd(gs_args, strdup(tmpstr));
164     }
165   }
166   if (outformat == OUTPUT_FORMAT_RASTER ||
167       outformat == OUTPUT_FORMAT_PXL) {
168     /* PDF output is only for turning PostScript input data into PDF
169        not for sending PDF to a PDF printer (this is done by pdftopdf)
170        therefore we do not apply duplex/tumble here. */
171     if (h->Duplex) {
172       cupsArrayAdd(gs_args, strdup("-dDuplex"));
173     }
174   }
175   snprintf(tmpstr, sizeof(tmpstr), "-r%dx%d",h->HWResolution[0], h->HWResolution[1]);
176   cupsArrayAdd(gs_args, strdup(tmpstr));
177   if (outformat == OUTPUT_FORMAT_RASTER) {
178     if (h->InsertSheet) {
179       cupsArrayAdd(gs_args, strdup("-dInsertSheet"));
180     }
181     if (h->Jog) {
182       snprintf(tmpstr, sizeof(tmpstr), "-dJog=%d",
183 	       (unsigned)(h->Jog));
184       cupsArrayAdd(gs_args, strdup(tmpstr));
185     }
186     if (h->LeadingEdge) {
187       snprintf(tmpstr, sizeof(tmpstr), "-dLeadingEdge=%d",
188 	       (unsigned)(h->LeadingEdge));
189       cupsArrayAdd(gs_args, strdup(tmpstr));
190     }
191     if (h->ManualFeed) {
192       cupsArrayAdd(gs_args, strdup("-dManualFeed"));
193     }
194   }
195   if (outformat == OUTPUT_FORMAT_RASTER ||
196       outformat == OUTPUT_FORMAT_PXL) {
197     if (h->MediaPosition) {
198       int mediapos;
199       if (outformat == OUTPUT_FORMAT_PXL) {
200 	/* Convert PWG MediaPosition values to PXL-ones */
201 	if (h->MediaPosition == 1) /* Main */
202 	  mediapos = 4;
203 	else if (h->MediaPosition == 2) /* Alternate */
204 	  mediapos = 5;
205 	else if (h->MediaPosition == 3) /* Large Capacity */
206 	  mediapos = 7;
207 	else if (h->MediaPosition == 4) /* Manual */
208 	  mediapos = 2;
209 	else if (h->MediaPosition == 5) /* Envelope */
210 	  mediapos = 6;
211 	else if (h->MediaPosition == 11) /* Top */
212 	  mediapos = 4;
213 	else if (h->MediaPosition == 12) /* Middle */
214 	  mediapos = 5;
215 	else if (h->MediaPosition == 13) /* Bottom */
216 	  mediapos = 7;
217 	else if (h->MediaPosition == 19) /* Bypass */
218 	  mediapos = 3;
219 	else if (h->MediaPosition == 20) /* Tray 1 */
220 	  mediapos = 3;
221 	else if (h->MediaPosition == 21) /* Tray 2 */
222 	  mediapos = 4;
223 	else if (h->MediaPosition == 22) /* Tray 3 */
224 	  mediapos = 5;
225 	else if (h->MediaPosition == 23) /* Tray 4 */
226 	  mediapos = 7;
227 	else
228 	  mediapos = 0;
229       } else
230 	mediapos = h->MediaPosition;
231       snprintf(tmpstr, sizeof(tmpstr), "-dMediaPosition=%d",
232 	       (unsigned)(mediapos));
233       cupsArrayAdd(gs_args, strdup(tmpstr));
234     }
235   }
236   if (outformat == OUTPUT_FORMAT_RASTER) {
237     if (h->MediaWeight) {
238       snprintf(tmpstr, sizeof(tmpstr), "-dMediaWeight=%d",
239 	       (unsigned)(h->MediaWeight));
240       cupsArrayAdd(gs_args, strdup(tmpstr));
241     }
242     if (h->MirrorPrint) {
243       cupsArrayAdd(gs_args, strdup("-dMirrorPrint"));
244     }
245     if (h->NegativePrint) {
246       cupsArrayAdd(gs_args, strdup("-dNegativePrint"));
247     }
248     if (h->NumCopies != 1) {
249       snprintf(tmpstr, sizeof(tmpstr), "-dNumCopies=%d",
250 	       (unsigned)(h->NumCopies));
251       cupsArrayAdd(gs_args, strdup(tmpstr));
252     }
253     if (h->Orientation) {
254       snprintf(tmpstr, sizeof(tmpstr), "-dOrientation=%d",
255 	       (unsigned)(h->Orientation));
256       cupsArrayAdd(gs_args, strdup(tmpstr));
257     }
258     if (h->OutputFaceUp) {
259       cupsArrayAdd(gs_args, strdup("-dOutputFaceUp"));
260     }
261   }
262   snprintf(tmpstr, sizeof(tmpstr), "-dDEVICEWIDTHPOINTS=%d",h->PageSize[0]);
263   cupsArrayAdd(gs_args, strdup(tmpstr));
264   snprintf(tmpstr, sizeof(tmpstr), "-dDEVICEHEIGHTPOINTS=%d",h->PageSize[1]);
265   cupsArrayAdd(gs_args, strdup(tmpstr));
266   if (outformat == OUTPUT_FORMAT_RASTER) {
267     if (h->Separations) {
268       cupsArrayAdd(gs_args, strdup("-dSeparations"));
269     }
270     if (h->TraySwitch) {
271       cupsArrayAdd(gs_args, strdup("-dTraySwitch"));
272     }
273   }
274   if (outformat == OUTPUT_FORMAT_RASTER ||
275       outformat == OUTPUT_FORMAT_PXL) {
276     /* PDF output is only for turning PostScript input data into PDF
277        not for sending PDF to a PDF printer (this is done by pdftopdf)
278        therefore we do not apply duplex/tumble here. */
279     if (h->Tumble) {
280       cupsArrayAdd(gs_args, strdup("-dTumble"));
281     }
282   }
283   if (outformat == OUTPUT_FORMAT_RASTER) {
284     if (h->cupsMediaType) {
285       snprintf(tmpstr, sizeof(tmpstr), "-dcupsMediaType=%d",
286 	       (unsigned)(h->cupsMediaType));
287       cupsArrayAdd(gs_args, strdup(tmpstr));
288     }
289     snprintf(tmpstr, sizeof(tmpstr), "-dcupsBitsPerColor=%d",h->cupsBitsPerColor);
290     cupsArrayAdd(gs_args, strdup(tmpstr));
291     snprintf(tmpstr, sizeof(tmpstr), "-dcupsColorOrder=%d",h->cupsColorOrder);
292     cupsArrayAdd(gs_args, strdup(tmpstr));
293     snprintf(tmpstr, sizeof(tmpstr), "-dcupsColorSpace=%d",h->cupsColorSpace);
294     cupsArrayAdd(gs_args, strdup(tmpstr));
295   }
296 
297   if (outformat == OUTPUT_FORMAT_PXL) {
298     if (h->cupsColorSpace == CUPS_CSPACE_W ||
299 	h->cupsColorSpace == CUPS_CSPACE_K ||
300 	h->cupsColorSpace == CUPS_CSPACE_WHITE ||
301 	h->cupsColorSpace == CUPS_CSPACE_GOLD ||
302 	h->cupsColorSpace == CUPS_CSPACE_SILVER ||
303 	h->cupsColorSpace == CUPS_CSPACE_SW ||
304 	h->cupsColorSpace == CUPS_CSPACE_ICC1 ||
305 	h->cupsColorSpace == CUPS_CSPACE_DEVICE1)
306       /* Monochrome color spaces -> use "pxlmono" device */
307       pxlcolor = 0;
308     if (pxlcolor == 1)
309       cupsArrayAdd(gs_args, strdup("-sDEVICE=pxlcolor"));
310     else
311       cupsArrayAdd(gs_args, strdup("-sDEVICE=pxlmono"));
312   }
313   if (outformat == OUTPUT_FORMAT_RASTER) {
314     if (h->cupsCompression) {
315       snprintf(tmpstr, sizeof(tmpstr), "-dcupsCompression=%d",
316 	       (unsigned)(h->cupsCompression));
317       cupsArrayAdd(gs_args, strdup(tmpstr));
318     }
319     if (h->cupsRowCount) {
320       snprintf(tmpstr, sizeof(tmpstr), "-dcupsRowCount=%d",
321 	       (unsigned)(h->cupsRowCount));
322       cupsArrayAdd(gs_args, strdup(tmpstr));
323     }
324     if (h->cupsRowFeed) {
325       snprintf(tmpstr, sizeof(tmpstr), "-dcupsRowFeed=%d",
326 	       (unsigned)(h->cupsRowFeed));
327       cupsArrayAdd(gs_args, strdup(tmpstr));
328     }
329     if (h->cupsRowStep) {
330       snprintf(tmpstr, sizeof(tmpstr), "-dcupsRowStep=%d",
331 	       (unsigned)(h->cupsRowStep));
332       cupsArrayAdd(gs_args, strdup(tmpstr));
333     }
334   }
335 #ifdef CUPS_RASTER_SYNCv1
336   if (outformat == OUTPUT_FORMAT_RASTER) {
337     if (h->cupsBorderlessScalingFactor != 1.0f) {
338       snprintf(tmpstr, sizeof(tmpstr), "-dcupsBorderlessScalingFactor=%.4f",
339 	       h->cupsBorderlessScalingFactor);
340       cupsArrayAdd(gs_args, strdup(tmpstr));
341     }
342     for (i=0; i <= 15; i ++)
343       if (h->cupsInteger[i]) {
344 	snprintf(tmpstr, sizeof(tmpstr), "-dcupsInteger%d=%d",
345 		 i, (unsigned)(h->cupsInteger[i]));
346 	cupsArrayAdd(gs_args, strdup(tmpstr));
347       }
348     for (i=0; i <= 15; i ++)
349       if (h->cupsReal[i]) {
350 	snprintf(tmpstr, sizeof(tmpstr), "-dcupsReal%d=%.4f",
351 		 i, h->cupsReal[i]);
352 	cupsArrayAdd(gs_args, strdup(tmpstr));
353       }
354     for (i=0; i <= 15; i ++)
355       if (h->cupsString[i][0] != '\0') {
356 	snprintf(tmpstr, sizeof(tmpstr), "-scupsString%d=%s",
357 		 i, h->cupsString[i]);
358 	cupsArrayAdd(gs_args, strdup(tmpstr));
359       }
360     if (h->cupsMarkerType[0] != '\0') {
361       snprintf(tmpstr, sizeof(tmpstr), "-scupsMarkerType=%s",
362 	       h->cupsMarkerType);
363       cupsArrayAdd(gs_args, strdup(tmpstr));
364     }
365     if (h->cupsRenderingIntent[0] != '\0') {
366       snprintf(tmpstr, sizeof(tmpstr), "-scupsRenderingIntent=%s",
367 	       h->cupsRenderingIntent);
368       cupsArrayAdd(gs_args, strdup(tmpstr));
369     }
370     if (h->cupsPageSizeName[0] != '\0') {
371       snprintf(tmpstr, sizeof(tmpstr), "-scupsPageSizeName=%s",
372 	       h->cupsPageSizeName);
373       cupsArrayAdd(gs_args, strdup(tmpstr));
374     }
375   }
376 #endif /* CUPS_RASTER_SYNCv1 */
377 }
378 
379 static int
gs_spawn(const char * filename,cups_array_t * gs_args,char ** envp,FILE * fp)380 gs_spawn (const char *filename,
381           cups_array_t *gs_args,
382           char **envp,
383           FILE *fp)
384 {
385   char *argument;
386   char buf[BUFSIZ];
387   char **gsargv;
388   const char* apos;
389   int fds[2];
390   int i;
391   int n;
392   int numargs;
393   int pid;
394   int status = 65536;
395   int wstatus;
396 
397   /* Put Ghostscript command line argument into an array for the "exec()"
398      call */
399   numargs = cupsArrayCount(gs_args);
400   gsargv = calloc(numargs + 1, sizeof(char *));
401   for (argument = (char *)cupsArrayFirst(gs_args), i = 0; argument;
402        argument = (char *)cupsArrayNext(gs_args), i++) {
403     gsargv[i] = argument;
404   }
405   gsargv[i] = NULL;
406 
407   /* Debug output: Full Ghostscript command line and environment variables */
408   fprintf(stderr, "DEBUG: Ghostscript command line:");
409   for (i = 0; gsargv[i]; i ++) {
410     if ((strchr(gsargv[i],' ')) || (strchr(gsargv[i],'\t')))
411       apos = "'";
412     else
413       apos = "";
414     fprintf(stderr, " %s%s%s", apos, gsargv[i], apos);
415   }
416   fprintf(stderr, "\n");
417 
418   for (i = 0; envp[i]; i ++)
419     fprintf(stderr, "DEBUG: envp[%d]=\"%s\"\n", i, envp[i]);
420 
421   /* Create a pipe for feeding the job into Ghostscript */
422   if (pipe(fds))
423   {
424     fds[0] = -1;
425     fds[1] = -1;
426     fprintf(stderr, "ERROR: Unable to establish pipe for Ghostscript call\n");
427     goto out;
428   }
429 
430   /* Set the "close on exec" flag on each end of the pipe... */
431   if (fcntl(fds[0], F_SETFD, fcntl(fds[0], F_GETFD) | FD_CLOEXEC))
432   {
433     close(fds[0]);
434     close(fds[1]);
435     fds[0] = -1;
436     fds[1] = -1;
437     fprintf(stderr, "ERROR: Unable to set \"close on exec\" flag on read end of the pipe for Ghostscript call\n");
438     goto out;
439   }
440   if (fcntl(fds[1], F_SETFD, fcntl(fds[1], F_GETFD) | FD_CLOEXEC))
441   {
442     close(fds[0]);
443     close(fds[1]);
444     fprintf(stderr, "ERROR: Unable to set \"close on exec\" flag on write end of the pipe for Ghostscript call\n");
445     goto out;
446   }
447 
448   if ((pid = fork()) == 0)
449   {
450     /* Couple pipe with STDIN of Ghostscript process */
451     if (fds[0] != 0) {
452       close(0);
453       if (fds[0] > 0) {
454         if (dup(fds[0]) < 0) {
455 	  fprintf(stderr, "ERROR: Unable to couple pipe with STDIN of Ghostscript process\n");
456 	  goto out;
457 	}
458       } else {
459         fprintf(stderr, "ERROR: Unable to couple pipe with STDIN of Ghostscript process\n");
460         goto out;
461       }
462     }
463 
464     /* Execute Ghostscript command line ... */
465     execvpe(filename, gsargv, envp);
466     fprintf(stderr, "ERROR: Unable to launch Ghostscript: %s: %s\n", filename, strerror(errno));
467     goto out;
468   }
469 
470   /* Feed job data into Ghostscript */
471   while ((n = fread(buf, 1, BUFSIZ, fp)) > 0) {
472     int count;
473 retry_write:
474     count = write(fds[1], buf, n);
475     if (count != n) {
476       if (count == -1) {
477         if (errno == EINTR)
478           goto retry_write;
479         fprintf(stderr, "ERROR: write failed: %s\n", strerror(errno));
480       }
481       fprintf(stderr, "ERROR: Can't feed job data into Ghostscript\n");
482       goto out;
483     }
484   }
485   close (fds[1]);
486 
487 retry_wait:
488   if (waitpid (pid, &wstatus, 0) == -1) {
489     if (errno == EINTR)
490       goto retry_wait;
491     perror ("gs");
492     goto out;
493   }
494 
495   /* How did Ghostscript terminate */
496   if (WIFEXITED(wstatus))
497     /* Via exit() anywhere or return() in the main() function */
498     status = WEXITSTATUS(wstatus);
499   else if (WIFSIGNALED(wstatus))
500     /* Via signal */
501     status = 256 * WTERMSIG(wstatus);
502 
503 out:
504   free(gsargv);
505   return status;
506 }
507 
508 #if 0
509 static char *
510 get_ppd_icc_fallback (ppd_file_t *ppd, char **qualifier)
511 {
512   char full_path[1024];
513   char *icc_profile = NULL;
514   char qualifer_tmp[1024];
515   const char *profile_key;
516   ppd_attr_t *attr;
517   char *datadir;
518 
519   /* get profile attr, falling back to CUPS */
520   profile_key = "APTiogaProfile";
521   attr = ppdFindAttr(ppd, profile_key, NULL);
522   if (attr == NULL) {
523     profile_key = "cupsICCProfile";
524     attr = ppdFindAttr(ppd, profile_key, NULL);
525   }
526 
527   /* create a string for a quick comparion */
528   snprintf(qualifer_tmp, sizeof(qualifer_tmp),
529            "%s.%s.%s",
530            qualifier[0],
531            qualifier[1],
532            qualifier[2]);
533 
534   /* neither */
535   if (attr == NULL) {
536     fprintf(stderr, "INFO: no profiles specified in PPD\n");
537     goto out;
538   }
539 
540   if ((datadir = getenv("CUPS_DATADIR")) == NULL)
541     datadir = CUPS_DATADIR;
542 
543   /* try to find a profile that matches the qualifier exactly */
544   for (;attr != NULL; attr = ppdFindNextAttr(ppd, profile_key, NULL)) {
545     fprintf(stderr, "INFO: found profile %s in PPD with qualifier '%s'\n",
546             attr->value, attr->spec);
547 
548     /* invalid entry */
549     if (attr->spec == NULL || attr->value == NULL)
550       continue;
551 
552     /* expand to a full path if not already specified */
553     if (attr->value[0] != '/')
554       snprintf(full_path, sizeof(full_path),
555                "%s/profiles/%s", datadir, attr->value);
556     else
557       strncpy(full_path, attr->value, sizeof(full_path));
558 
559     /* check the file exists */
560     if (access(full_path, 0)) {
561       fprintf(stderr, "INFO: found profile %s in PPD that does not exist\n",
562               full_path);
563       continue;
564     }
565 
566     /* matches the qualifier */
567     if (strcmp(qualifer_tmp, attr->spec) == 0) {
568       icc_profile = strdup(full_path);
569       goto out;
570     }
571   }
572 
573   /* no match */
574   if (attr == NULL) {
575     fprintf(stderr, "INFO: no profiles in PPD for qualifier '%s'\n",
576             qualifer_tmp);
577     goto out;
578   }
579 
580 out:
581   return icc_profile;
582 }
583 #endif
584 
585 // Returns the number of pages in the document |filename|. Returns -1 if there was an error.
586 static int
count_pages(char * filename,GsDocType doc_type)587 count_pages(char* filename, GsDocType doc_type) {
588   int pagecount = 0;
589 
590   if (doc_type == GS_DOC_TYPE_PDF) {
591     return pdf_pages(filename);
592   }
593 
594   // All other content needs to be rendered.
595   char gscommand[65536];
596   char output[31] = "";
597   size_t bytes;
598   /* Ghostscript runs too long while printing PDF fikes converted from
599      djvu files. Using -dDEVICEWIDTHPOINTS=1 -dDEVICEHEIGHTPOINTS=1
600      solves the problem */
601   snprintf(gscommand, 65536, "%s -q -dNOPAUSE -dBATCH -sDEVICE=bbox -dDEVICEWIDTHPOINTS=1 -dDEVICEHEIGHTPOINTS=1 %s 2>&1 | grep -c HiResBoundingBox",
602       CUPS_GHOSTSCRIPT, filename);
603 
604   FILE *pd = popen(gscommand, "r");
605   if (!pd) {
606     fprintf(stderr, "Failed to execute ghostscript to determine number of input pages!\n");
607     return -1;
608   }
609 
610   bytes = fread(output, 1, 31, pd);
611   pclose(pd);
612 
613   if (bytes <= 0 || sscanf(output, "%d", &pagecount) < 1)
614     return -1;
615 
616   return pagecount;
617 }
618 
619 int
main(int argc,char ** argv,char * envp[])620 main (int argc, char **argv, char *envp[])
621 {
622   char *outformat_env = NULL;
623   OutFormatType outformat;
624   char buf[BUFSIZ];
625   char *filename;
626   char *icc_profile = NULL;
627   /*char **qualifier = NULL;*/
628   char *tmp;
629   char tmpstr[1024];
630   const char *t = NULL;
631   cups_array_t *gs_args = NULL;
632   cups_option_t *options = NULL;
633   FILE *fp = NULL;
634   GsDocType doc_type;
635   gs_page_header h;
636   int fd;
637   int cm_disabled;
638   int n;
639   int num_options;
640   int status = 1;
641   ppd_file_t *ppd = NULL;
642   struct sigaction sa;
643   cm_calibration_t cm_calibrate;
644   int pxlcolor = 1;
645 #ifdef HAVE_CUPS_1_7
646   int pwgraster = 0;
647   ppd_attr_t *attr;
648 #endif /* HAVE_CUPS_1_7 */
649 
650   if (argc < 6 || argc > 7) {
651     fprintf(stderr, "ERROR: %s job-id user title copies options [file]\n",
652       argv[0]);
653     goto out;
654   }
655 
656   memset(&sa, 0, sizeof(sa));
657   /* Ignore SIGPIPE and have write return an error instead */
658   sa.sa_handler = SIG_IGN;
659   sigaction(SIGPIPE, &sa, NULL);
660 
661   /* Determine the output format via an environment variable set by a wrapper
662      script */
663   outformat_env = getenv("OUTFORMAT");
664   if (outformat_env == NULL || strcasestr(outformat_env, "raster"))
665     outformat = OUTPUT_FORMAT_RASTER;
666   else if (strcasestr(outformat_env, "pdf"))
667     outformat = OUTPUT_FORMAT_PDF;
668   else if (strcasestr(outformat_env, "xl"))
669     outformat = OUTPUT_FORMAT_PXL;
670   else {
671     fprintf(stderr, "ERROR: OUTFORMAT=\"%s\", cannot determine output format\n",
672       outformat_env);
673     goto out;
674   }
675   fprintf(stderr, "DEBUG: OUTFORMAT=\"%s\", so output format will be %s\n",
676 	  (outformat_env ? outformat_env : "<none>"),
677 	  (outformat == OUTPUT_FORMAT_RASTER ? "CUPS/PWG Raster" :
678 	   (outformat == OUTPUT_FORMAT_PDF ? "PDF" :
679 	    "PCL XL")));
680 
681   num_options = cupsParseOptions(argv[5], 0, &options);
682 
683   t = getenv("PPD");
684   if (t && t[0] != '\0')
685     if ((ppd = ppdOpenFile(t)) == NULL) {
686       fprintf(stderr, "ERROR: Failed to open PPD: %s\n", t);
687     }
688 
689   if (ppd) {
690     ppdMarkDefaults (ppd);
691     cupsMarkOptions (ppd, num_options, options);
692   }
693 
694   if (argc == 6) {
695     /* stdin */
696 
697     fd = cupsTempFd(buf,BUFSIZ);
698     if (fd < 0) {
699       fprintf(stderr, "ERROR: Can't create temporary file\n");
700       goto out;
701     }
702 
703     filename = strdup(buf);
704 
705     /* copy stdin to the tmp file */
706     while ((n = read(0,buf,BUFSIZ)) > 0) {
707       if (write(fd,buf,n) != n) {
708         fprintf(stderr, "ERROR: Can't copy stdin to temporary file\n");
709         close(fd);
710         goto out;
711       }
712     }
713     if (lseek(fd,0,SEEK_SET) < 0) {
714         fprintf(stderr, "ERROR: Can't rewind temporary file\n");
715         close(fd);
716         goto out;
717     }
718 
719     if ((fp = fdopen(fd,"rb")) == 0) {
720         fprintf(stderr, "ERROR: Can't fdopen temporary file\n");
721         close(fd);
722         goto out;
723     }
724   } else {
725     /* argc == 7 filename is specified */
726 
727     if ((fp = fopen(argv[6],"rb")) == 0) {
728         fprintf(stderr, "ERROR: Can't open input file %s\n",argv[6]);
729         goto out;
730     }
731     filename = argv[6];
732   }
733 
734   /* find out file type */
735   doc_type = parse_doc_type(fp);
736   if (doc_type == GS_DOC_TYPE_UNKNOWN) {
737     char buf[1];
738     rewind(fp);
739     if (fread(buf, 1, 1, fp) == 0) {
740       fprintf(stderr, "DEBUG: Input is empty, outputting empty file.\n");
741       status = 0;
742       if (outformat == OUTPUT_FORMAT_RASTER)
743         fprintf(stdout, "RaS2");
744       goto out;
745     }
746     fprintf(stderr, "ERROR: Can't detect file type\n");
747     goto out;
748   }
749 
750   // Determine how many pages we have and if we have something valid to print.
751   int pagecount = count_pages(filename, doc_type);
752   if (pagecount == 0) {
753     fprintf(stderr, "DEBUG: No pages left, outputting empty file.\n");
754     status = 0;
755     if (outformat == OUTPUT_FORMAT_RASTER)
756       fprintf(stdout, "RaS2");
757     goto out;
758   }
759   if (pagecount < 0) {
760     fprintf(stderr, "DEBUG: Unexpected page count\n");
761     goto out;
762   }
763 
764   if (pwgraster) {
765     // Set job-impressions for later embedding as TotalPageCount.
766     num_options = cupsAddIntegerOption("job-impressions", pagecount, num_options, &options);
767   }
768 
769   if (argc == 6) {
770     /* input from stdin */
771     /* remove name of temp file*/
772     unlink(filename);
773     free(filename);
774   }
775 
776   /*  Check status of color management in CUPS */
777   cm_calibrate = cmGetCupsColorCalibrateMode(options, num_options);
778 
779   if (cm_calibrate == CM_CALIBRATION_ENABLED)
780     cm_disabled = 1;
781   else
782     cm_disabled = cmIsPrinterCmDisabled(getenv("PRINTER"));
783 
784   if (!cm_disabled)
785     cmGetPrinterIccProfile(getenv("PRINTER"), &icc_profile, ppd);
786 
787   /* Ghostscript parameters */
788   gs_args = cupsArrayNew(NULL, NULL);
789   if (!gs_args) {
790     fprintf(stderr, "ERROR: Unable to allocate memory for Ghostscript arguments array\n");
791     goto out;
792   }
793 
794   /* Part of Ghostscript command line which is not dependent on the job and/or
795      the driver */
796   snprintf(tmpstr, sizeof(tmpstr), "%s", CUPS_GHOSTSCRIPT);
797   cupsArrayAdd(gs_args, strdup(tmpstr));
798   cupsArrayAdd(gs_args, strdup("-dQUIET"));
799   /*cupsArrayAdd(gs_args, strdup("-dDEBUG"));*/
800   cupsArrayAdd(gs_args, strdup("-dSAFER"));
801   cupsArrayAdd(gs_args, strdup("-dNOPAUSE"));
802   cupsArrayAdd(gs_args, strdup("-dBATCH"));
803   cupsArrayAdd(gs_args, strdup("-dNOINTERPOLATE"));
804   cupsArrayAdd(gs_args, strdup("-dNOMEDIAATTRS"));
805   if (cm_disabled)
806     cupsArrayAdd(gs_args, strdup("-dUseFastColor"));
807   if (doc_type == GS_DOC_TYPE_PDF)
808     cupsArrayAdd(gs_args, strdup("-dShowAcroForm"));
809   cupsArrayAdd(gs_args, strdup("-sstdout=%stderr"));
810   cupsArrayAdd(gs_args, strdup("-sOutputFile=%stdout"));
811 
812   /* Ghostscript output device */
813   if (outformat == OUTPUT_FORMAT_RASTER)
814     cupsArrayAdd(gs_args, strdup("-sDEVICE=cups"));
815   else if (outformat == OUTPUT_FORMAT_PDF)
816     cupsArrayAdd(gs_args, strdup("-sDEVICE=pdfwrite"));
817   /* In case of PCL XL output we determine later whether we will have
818      to use the "pxlmono" or "pxlcolor" output device */
819 
820   /* Special Ghostscript options for PDF output */
821   if (outformat == OUTPUT_FORMAT_PDF) {
822     /* If we output PDF we are running as a PostScript-to-PDF filter
823        for incoming PostScript jobs. If the client embeds a command
824        for multiple copies in the PostScript job instead of using the
825        CUPS argument for the number of copies, we need to run
826        Ghostscript with the "-dDoNumCopies" option so that it respects
827        the embedded command for the number of copies.
828 
829        We always supply this option if the number of copies CUPS got
830        told about is 1, as this is the case if a client sets the
831        number of copies as embedded PostScript command, and it is also
832        not doing the wrong thing if the command is missing when the
833        client only wants a single copy, independent how the client
834        actually triggers multiple copies. If the CUPS arguments tells
835        us that the clients wants more than one copy we do not supply
836        "-dDoNumCopies" as the client does the right, modern CUPS way,
837        and if the client got a "dirty" PostScript file with an
838        embedded multi-copy setting, he does not get unwished copies.
839        also a buggy client supplying the number of copies both via
840        PostScript and CUPS will not cause an unwished number of copies
841        this way.
842 
843        See https://github.com/OpenPrinting/cups-filters/issues/255
844 
845        This was already correctly implemented in the former pdftops
846        shell-script-based filter but overlooked when the filter's
847        functionality got folded into this gstoraster.c filter. It was
848        not seen for long time as clients sending PostScript jobs with
849        embedded number of copies are rare. */
850     if (atoi(argv[4]) <= 1)
851       cupsArrayAdd(gs_args, strdup("-dDoNumCopies"));
852 
853     cupsArrayAdd(gs_args, strdup("-dCompatibilityLevel=1.3"));
854     cupsArrayAdd(gs_args, strdup("-dAutoRotatePages=/None"));
855     cupsArrayAdd(gs_args, strdup("-dAutoFilterColorImages=false"));
856     cupsArrayAdd(gs_args, strdup("-dNOPLATFONTS"));
857     cupsArrayAdd(gs_args, strdup("-dColorImageFilter=/FlateEncode"));
858     cupsArrayAdd(gs_args, strdup("-dPDFSETTINGS=/default"));
859     cupsArrayAdd(gs_args, strdup("-dColorConversionStrategy=/LeaveColorUnchanged"));
860   }
861 
862 #ifdef HAVE_CUPS_1_7
863   if (outformat == OUTPUT_FORMAT_RASTER)
864   {
865     t = getenv("FINAL_CONTENT_TYPE");
866     if (t && strcasestr(t, "pwg"))
867       pwgraster = 1;
868   }
869 #endif /* HAVE_CUPS_1_7 */
870 
871   if (ppd)
872   {
873     cupsRasterInterpretPPD(&h,ppd,num_options,options,0);
874 #ifdef HAVE_CUPS_1_7
875     if (outformat == OUTPUT_FORMAT_RASTER)
876     {
877       if ((attr = ppdFindAttr(ppd,"PWGRaster",0)) != 0 &&
878 	  (!strcasecmp(attr->value, "true") ||
879 	   !strcasecmp(attr->value, "on") ||
880 	   !strcasecmp(attr->value, "yes")))
881 	pwgraster = 1;
882       if (pwgraster == 1)
883 	cupsRasterParseIPPOptions(&h, num_options, options, pwgraster, 0);
884     }
885 #endif /* HAVE_CUPS_1_7 */
886     if (outformat == OUTPUT_FORMAT_PXL)
887     {
888       if ((attr = ppdFindAttr(ppd,"ColorDevice",0)) != 0 &&
889 	  (!strcasecmp(attr->value, "false") ||
890 	   !strcasecmp(attr->value, "off") ||
891 	   !strcasecmp(attr->value, "no")))
892 	/* Monochrome PCL XL printer, according to PPD */
893 	pxlcolor = 0;
894     }
895   }
896   else
897   {
898 #ifdef HAVE_CUPS_1_7
899     if (outformat == OUTPUT_FORMAT_RASTER)
900     {
901       pwgraster = 1;
902       t = cupsGetOption("media-class", num_options, options);
903       if (t == NULL)
904 	t = cupsGetOption("MediaClass", num_options, options);
905       if (t != NULL)
906       {
907 	if (strcasestr(t, "pwg"))
908 	  pwgraster = 1;
909 	else
910 	  pwgraster = 0;
911       }
912     }
913     cupsRasterParseIPPOptions(&h, num_options, options, pwgraster, 1);
914 
915     /*
916      * cupsRasterParseIPPOptions() would populate the TotalPageCount field
917      * (h.cupsInteger[0]) if CUPS passed "job-impressions" to this filter.
918      * CUPS does not do so, so we set it manually here. */
919     if (pagecount > 0 && pwgraster) {
920       h.cupsInteger[0] = pagecount;
921     }
922 #else
923     fprintf(stderr, "ERROR: No PPD file specified.\n");
924     goto out;
925 #endif /* HAVE_CUPS_1_7 */
926   }
927 
928   if ((h.HWResolution[0] == 100) && (h.HWResolution[1] == 100)) {
929     /* No "Resolution" option */
930     if (ppd && (attr = ppdFindAttr(ppd, "DefaultResolution", 0)) != NULL) {
931       /* "*DefaultResolution" keyword in the PPD */
932       const char *p = attr->value;
933       h.HWResolution[0] = atoi(p);
934       if ((p = strchr(p, 'x')) != NULL)
935 	h.HWResolution[1] = atoi(p);
936       else
937 	h.HWResolution[1] = h.HWResolution[0];
938       if (h.HWResolution[0] <= 0)
939 	h.HWResolution[0] = 300;
940       if (h.HWResolution[1] <= 0)
941 	h.HWResolution[1] = h.HWResolution[0];
942     } else {
943       h.HWResolution[0] = 300;
944       h.HWResolution[1] = 300;
945     }
946     h.cupsWidth = h.HWResolution[0] * h.PageSize[0] / 72;
947     h.cupsHeight = h.HWResolution[1] * h.PageSize[1] / 72;
948   }
949 
950   /* set PDF-specific options */
951   if (doc_type == GS_DOC_TYPE_PDF) {
952     parse_pdf_header_options(fp, &h);
953   }
954 
955   /* fixed other values that pdftopdf handles */
956   h.MirrorPrint = CUPS_FALSE;
957   h.Orientation = CUPS_ORIENT_0;
958 
959   /* get all the data from the header and pass it to ghostscript */
960   add_pdf_header_options (&h, gs_args, outformat, pxlcolor);
961 
962   /* CUPS font path */
963   if ((t = getenv("CUPS_FONTPATH")) == NULL)
964     t = CUPS_FONTPATH;
965   snprintf(tmpstr, sizeof(tmpstr), "-I%s", t);
966   cupsArrayAdd(gs_args, strdup(tmpstr));
967 
968   /* set the device output ICC profile */
969   if(icc_profile != NULL && icc_profile[0] != '\0') {
970     snprintf(tmpstr, sizeof(tmpstr), "-sOutputICCProfile=%s", icc_profile);
971     cupsArrayAdd(gs_args, strdup(tmpstr));
972   }
973 
974   /* Switch to taking PostScript commands on the Ghostscript command line */
975   cupsArrayAdd(gs_args, strdup("-c"));
976 
977   /* Set margins if we have a bounding box defined and output format
978      is not PDF, as PDF output we have only in the PostScript-to-PDF
979      filtering case which happens for converting PostScript input
980      files before pdftopdf so margins will be handled later, whereas
981      the other output formats for PDF-to-something filtering after
982      pdftopdf, to format the pages for the printer, so margins are
983      important. */
984   if (h.cupsImagingBBox[3] > 0.0 && outformat != OUTPUT_FORMAT_PDF) {
985     snprintf(tmpstr, sizeof(tmpstr),
986 	     "<</.HWMargins[%f %f %f %f] /Margins[0 0]>>setpagedevice",
987 	     h.cupsImagingBBox[0], h.cupsImagingBBox[1],
988 	     h.cupsPageSize[0] - h.cupsImagingBBox[2],
989 	     h.cupsPageSize[1] - h.cupsImagingBBox[3]);
990     cupsArrayAdd(gs_args, strdup(tmpstr));
991   }
992 
993   if ((t = cupsGetOption("profile", num_options, options)) != NULL) {
994     snprintf(tmpstr, sizeof(tmpstr), "<</cupsProfile(%s)>>setpagedevice", t);
995     cupsArrayAdd(gs_args, strdup(tmpstr));
996   }
997 
998   /* Do we have a "center-of-pixel" command line option or
999      "CenterOfPixel" PPD option set to "true"? In this case let
1000      Ghostscript use the center-of-pixel rule instead of the
1001      PostScript-standard any-part-of-pixel rule when filling a
1002      path. This improves the accuracy of graphics (like bar codes for
1003      example) on low-resolution printers (like label printers with
1004      typically 203 dpi). See
1005      https://bugs.linuxfoundation.org/show_bug.cgi?id=1373 */
1006   if (((t = cupsGetOption("CenterOfPixel", num_options, options)) == NULL &&
1007        (t = cupsGetOption("center-of-pixel", num_options, options)) == NULL &&
1008        ppd && (attr = ppdFindAttr(ppd,"DefaultCenterOfPixel", NULL)) != NULL &&
1009        (!strcasecmp(attr->value, "true") ||
1010 	!strcasecmp(attr->value, "on") ||
1011 	!strcasecmp(attr->value, "yes"))) ||
1012       (t && (!strcasecmp(t, "true") || !strcasecmp(t, "on") ||
1013 	     !strcasecmp(t, "yes")))) {
1014     fprintf(stderr, "DEBUG: Ghostscript using Center-of-Pixel method to fill paths.\n");
1015     cupsArrayAdd(gs_args, strdup("0 0 .setfilladjust2"));
1016   } else
1017     fprintf(stderr, "DEBUG: Ghostscript using Any-Part-of-Pixel method to fill paths.\n");
1018 
1019   /* Mark the end of PostScript commands supplied on the Ghostscript command
1020      line (with the "-c" option), so that we can supply the input file name */
1021   cupsArrayAdd(gs_args, strdup("-f"));
1022 
1023   /* Let Ghostscript read from STDIN */
1024   cupsArrayAdd(gs_args, strdup("-_"));
1025 
1026   /* Execute Ghostscript command line ... */
1027   snprintf(tmpstr, sizeof(tmpstr), "%s", CUPS_GHOSTSCRIPT);
1028 
1029   /* call Ghostscript */
1030   rewind(fp);
1031   status = gs_spawn (tmpstr, gs_args, envp, fp);
1032   if (status != 0) status = 1;
1033 out:
1034   if (fp)
1035     fclose(fp);
1036   if (gs_args) {
1037     while ((tmp = cupsArrayFirst(gs_args)) != NULL) {
1038       cupsArrayRemove(gs_args,tmp);
1039       free(tmp);
1040     }
1041     cupsArrayDelete(gs_args);
1042   }
1043   free(icc_profile);
1044   if (ppd)
1045     ppdClose(ppd);
1046   return status;
1047 }
1048