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