• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2012 Tobias Hoffmann
2 //
3 // Copyright (c) 2006-2011, BBR Inc.  All rights reserved.
4 // MIT Licensed.
5 
6 #include <config.h>
7 #include <stdio.h>
8 #include <assert.h>
9 #include <cups/cups.h>
10 #include <cups/ppd.h>
11 #if (CUPS_VERSION_MAJOR > 1) || (CUPS_VERSION_MINOR > 6)
12 #define HAVE_CUPS_1_7 1
13 #endif
14 #ifdef HAVE_CUPS_1_7
15 #include <cups/pwg.h>
16 #endif /* HAVE_CUPS_1_7 */
17 #include <iomanip>
18 #include <sstream>
19 #include <memory>
20 #include <unistd.h>
21 #include <fcntl.h>
22 #include <sys/types.h>
23 #include <sys/wait.h>
24 
25 #include "pdftopdf_processor.h"
26 #include "pdftopdf_jcl.h"
27 
28 #include <stdarg.h>
error(const char * fmt,...)29 static void error(const char *fmt,...) // {{{
30 {
31   va_list ap;
32   va_start(ap,fmt);
33 
34   fputs("ERROR: ",stderr);
35   vfprintf(stderr,fmt,ap);
36   fputs("\n",stderr);
37 
38   va_end(ap);
39 }
40 // }}}
41 
42 // namespace {}
43 
setFinalPPD(ppd_file_t * ppd,const ProcessingParameters & param)44 void setFinalPPD(ppd_file_t *ppd,const ProcessingParameters &param)
45 {
46   if ((param.booklet==BOOKLET_ON)&&(ppdFindOption(ppd,"Duplex"))) {
47     // TODO: elsewhere, better
48     ppdMarkOption(ppd,"Duplex","DuplexTumble");
49     // TODO? sides=two-sided-short-edge
50   }
51 
52   // for compatibility
53   if ((param.setDuplex)&&(ppdFindOption(ppd,"Duplex")!=NULL)) {
54     ppdMarkOption(ppd,"Duplex","True");
55     ppdMarkOption(ppd,"Duplex","On");
56   }
57 
58   // we do it, printer should not
59   ppd_choice_t *choice;
60   if ((choice=ppdFindMarkedChoice(ppd,"MirrorPrint")) != NULL) {
61     choice->marked=0;
62   }
63 }
64 
65 // for choice, only overwrites ret if found in ppd
ppdGetInt(ppd_file_t * ppd,const char * name,int * ret)66 static bool ppdGetInt(ppd_file_t *ppd,const char *name,int *ret) // {{{
67 {
68   assert(ret);
69   ppd_choice_t *choice=ppdFindMarkedChoice(ppd,name); // !ppd is ok.
70   if (choice) {
71     *ret=atoi(choice->choice);
72     return true;
73   }
74   return false;
75 }
76 // }}}
77 
optGetInt(const char * name,int num_options,cups_option_t * options,int * ret)78 static bool optGetInt(const char *name,int num_options,cups_option_t *options,int *ret) // {{{
79 {
80   assert(ret);
81   const char *val=cupsGetOption(name,num_options,options);
82   if (val) {
83     *ret=atoi(val);
84     return true;
85   }
86   return false;
87 }
88 // }}}
89 
optGetFloat(const char * name,int num_options,cups_option_t * options,float * ret)90 static bool optGetFloat(const char *name,int num_options,cups_option_t *options,float *ret) // {{{
91 {
92   assert(ret);
93   const char *val=cupsGetOption(name,num_options,options);
94   if (val) {
95     *ret=atof(val);
96     return true;
97   }
98   return false;
99 }
100 // }}}
101 
is_false(const char * value)102 static bool is_false(const char *value) // {{{
103 {
104   if (!value) {
105     return false;
106   }
107   return (strcasecmp(value,"no")==0)||
108     (strcasecmp(value,"off")==0)||
109     (strcasecmp(value,"false")==0);
110 }
111 // }}}
112 
is_true(const char * value)113 static bool is_true(const char *value) // {{{
114 {
115   if (!value) {
116     return false;
117   }
118   return (strcasecmp(value,"yes")==0)||
119     (strcasecmp(value,"on")==0)||
120     (strcasecmp(value,"true")==0);
121 }
122 // }}}
123 
ppdGetDuplex(ppd_file_t * ppd)124 static bool ppdGetDuplex(ppd_file_t *ppd) // {{{
125 {
126   const char **option, **choice;
127   const char *option_names[] = {
128     "Duplex",
129     "JCLDuplex",
130     "EFDuplex",
131     "KD03Duplex",
132     NULL
133   };
134   const char *choice_names[] = {
135     "DuplexNoTumble",
136     "DuplexTumble",
137     "LongEdge",
138     "ShortEdge",
139     "Top",
140     "Bottom",
141     NULL
142   };
143   for (option = option_names; *option; option ++)
144     for (choice = choice_names; *choice; choice ++)
145       if (ppdIsMarked(ppd, *option, *choice))
146 	return 1;
147   return 0;
148 }
149 // }}}
150 
151 // TODO: enum
ppdDefaultOrder(ppd_file_t * ppd)152 static bool ppdDefaultOrder(ppd_file_t *ppd) // {{{  -- is reverse?
153 {
154   ppd_choice_t *choice;
155   ppd_attr_t *attr;
156   const char *val=NULL;
157 
158   // Figure out the right default output order from the PPD file...
159   if ((choice=ppdFindMarkedChoice(ppd,"OutputOrder")) != NULL) {
160     val=choice->choice;
161   } else if (((choice=ppdFindMarkedChoice(ppd,"OutputBin")) != NULL)&&
162 	     ((attr=ppdFindAttr(ppd,"PageStackOrder",choice->choice)) != NULL)) {
163     val=attr->value;
164   } else if ((attr=ppdFindAttr(ppd,"DefaultOutputOrder",0)) != NULL) {
165     val=attr->value;
166   }
167   if ((!val)||(strcasecmp(val,"Normal")==0)||(strcasecmp(val,"same-order")==0)) {
168     return false;
169   } else if (strcasecmp(val,"Reverse")==0||(strcasecmp(val,"reverse-order")==0)) {
170     return true;
171   }
172   error("Unsupported output-order value %s, using 'normal'!",val);
173   return false;
174 }
175 // }}}
176 
optGetCollate(int num_options,cups_option_t * options)177 static bool optGetCollate(int num_options,cups_option_t *options) // {{{
178 {
179   if (is_true(cupsGetOption("Collate",num_options,options))) {
180     return true;
181   }
182 
183   const char *val=NULL;
184   if ((val=cupsGetOption("multiple-document-handling",num_options,options)) != NULL) {
185    /* This IPP attribute is unnecessarily complicated:
186     *   single-document, separate-documents-collated-copies, single-document-new-sheet:
187     *      -> collate (true)
188     *   separate-documents-uncollated-copies:
189     *      -> can be uncollated (false)
190     */
191     return (strcasecmp(val,"separate-documents-uncollated-copies")!=0);
192   }
193 
194   if ((val=cupsGetOption("sheet-collate",num_options,options)) != NULL) {
195     return (strcasecmp(val,"uncollated")!=0);
196   }
197 
198   return false;
199 }
200 // }}}
201 
parsePosition(const char * value,Position & xpos,Position & ypos)202 static bool parsePosition(const char *value,Position &xpos,Position &ypos) // {{{
203 {
204   // ['center','top','left','right','top-left','top-right','bottom','bottom-left','bottom-right']
205   xpos=Position::CENTER;
206   ypos=Position::CENTER;
207   int next=0;
208   if (strcasecmp(value,"center")==0) {
209     return true;
210   } else if (strncasecmp(value,"top",3)==0) {
211     ypos=Position::TOP;
212     next=3;
213   } else if (strncasecmp(value,"bottom",6)==0) {
214     ypos=Position::BOTTOM;
215     next=6;
216   }
217   if (next) {
218     if (value[next]==0) {
219       return true;
220     } else if (value[next]!='-') {
221       return false;
222     }
223     value+=next+1;
224   }
225   if (strcasecmp(value,"left")==0) {
226     xpos=Position::LEFT;
227   } else if (strcasecmp(value,"right")==0) {
228     xpos=Position::RIGHT;
229   } else {
230     return false;
231   }
232   return true;
233 }
234 // }}}
235 
236 #include <ctype.h>
parseRanges(const char * range,IntervalSet & ret)237 static void parseRanges(const char *range,IntervalSet &ret) // {{{
238 {
239   ret.clear();
240   if (!range) {
241     ret.add(1); // everything
242     ret.finish();
243     return;
244   }
245 
246   int lower,upper;
247   while (*range) {
248     if (*range=='-') {
249       range++;
250       upper=strtol(range,(char **)&range,10);
251       if (upper>=2147483647) { // see also   cups/encode.c
252         ret.add(1);
253       } else {
254         ret.add(1,upper+1);
255       }
256     } else {
257       lower=strtol(range,(char **)&range,10);
258       if (*range=='-') {
259         range++;
260         if (!isdigit(*range)) {
261           ret.add(lower);
262         } else {
263           upper=strtol(range,(char **)&range,10);
264           if (upper>=2147483647) {
265             ret.add(lower);
266           } else {
267             ret.add(lower,upper+1);
268           }
269         }
270       } else {
271         ret.add(lower,lower+1);
272       }
273     }
274 
275     if (*range!=',') {
276       break;
277     }
278     range++;
279   }
280   ret.finish();
281 }
282 // }}}
283 
parseBorder(const char * val,BorderType & ret)284 static bool parseBorder(const char *val,BorderType &ret) // {{{
285 {
286   assert(val);
287   if (strcasecmp(val,"none")==0) {
288     ret=BorderType::NONE;
289   } else if (strcasecmp(val,"single")==0) {
290     ret=BorderType::ONE_THIN;
291   } else if (strcasecmp(val,"single-thick")==0) {
292     ret=BorderType::ONE_THICK;
293   } else if (strcasecmp(val,"double")==0) {
294     ret=BorderType::TWO_THIN;
295   } else if (strcasecmp(val,"double-thick")==0) {
296     ret=BorderType::TWO_THICK;
297   } else {
298     return false;
299   }
300   return true;
301 }
302 // }}}
303 
getParameters(ppd_file_t * ppd,int num_options,cups_option_t * options,ProcessingParameters & param)304 void getParameters(ppd_file_t *ppd,int num_options,cups_option_t *options,ProcessingParameters &param) // {{{
305 {
306   const char *val;
307 
308   if ((val = cupsGetOption("copies",num_options,options)) != NULL) {
309     int copies = atoi(val);
310     if (copies > 0)
311       param.numCopies = copies;
312   }
313   // param.numCopies initially from commandline
314   if (param.numCopies==1) {
315     ppdGetInt(ppd,"Copies",&param.numCopies);
316   }
317   if (param.numCopies==0) {
318     param.numCopies=1;
319   }
320 
321   if((val = cupsGetOption("ipp-attribute-fidelity",num_options,options))!=NULL) {
322     if(!strcasecmp(val,"true")||!strcasecmp(val,"yes") ||
323       !strcasecmp(val,"on"))
324       param.fidelity = true;
325   }
326 
327   if((val = cupsGetOption("print-scaling",num_options,options)) != NULL) {
328     if(!strcasecmp(val,"auto"))
329       param.autoprint = true;
330     else if(!strcasecmp(val,"auto-fit"))
331       param.autofit = true;
332     else if(!strcasecmp(val,"fill"))
333       param.fillprint = true;
334     else if(!strcasecmp(val,"fit"))
335       param.fitplot = true;
336     else
337       param.cropfit = true;
338   }
339   else {
340   if ((val=cupsGetOption("fitplot",num_options,options)) == NULL) {
341     if ((val=cupsGetOption("fit-to-page",num_options,options)) == NULL) {
342       val=cupsGetOption("ipp-attribute-fidelity",num_options,options);
343     }
344   }
345   // TODO?  pstops checks =="true", pdftops !is_false  ... pstops says: fitplot only for PS (i.e. not for PDF, cmp. cgpdftopdf)
346   param.fitplot=(val)&&(!is_false(val));
347 
348   if((val = cupsGetOption("fill",num_options,options))!=0) {
349     if(!strcasecmp(val,"true")||!strcasecmp(val,"yes"))
350     {
351       param.fillprint = true;
352     }
353   }
354   if((val = cupsGetOption("crop-to-fit",num_options,options))!= NULL){
355     if(!strcasecmp(val,"true")||!strcasecmp(val,"yes"))
356     {
357       param.cropfit=1;
358     }
359   }
360   if (!param.autoprint && !param.autofit && !param.fitplot &&
361       !param.fillprint && !param.cropfit)
362     param.autoprint = true;
363   }
364 
365   if (ppd && (ppd->landscape < 0)) { // direction the printer rotates landscape (90 or -90)
366     param.normal_landscape=ROT_270;
367   } else {
368     param.normal_landscape=ROT_90;
369   }
370 
371   int ipprot;
372   param.orientation=ROT_0;
373   if ((val=cupsGetOption("landscape",num_options,options)) != NULL) {
374     if (!is_false(val)) {
375       param.orientation=param.normal_landscape;
376     }
377   } else if (optGetInt("orientation-requested",num_options,options,&ipprot)) {
378     /* IPP orientation values are:
379      *   3: 0 degrees,  4: 90 degrees,  5: -90 degrees,  6: 180 degrees
380      */
381     if ((ipprot<3)||(ipprot>6)) {
382       if (ipprot)
383 	error("Bad value (%d) for orientation-requested, using 0 degrees",
384 	      ipprot);
385       param.noOrientation = true;
386     } else {
387       static const Rotation ipp2rot[4]={ROT_0, ROT_90, ROT_270, ROT_180};
388       param.orientation=ipp2rot[ipprot-3];
389     }
390   } else {
391     param.noOrientation = true;
392   }
393 
394   ppd_size_t *pagesize;
395   // param.page default is letter, border 36,18
396   if ((pagesize=ppdPageSize(ppd,0)) != NULL) { // "already rotated"
397     param.page.top=pagesize->top;
398     param.page.left=pagesize->left;
399     param.page.right=pagesize->right;
400     param.page.bottom=pagesize->bottom;
401     param.page.width=pagesize->width;
402     param.page.height=pagesize->length;
403   }
404 #ifdef HAVE_CUPS_1_7
405   else {
406     if ((val = cupsGetOption("media-size", num_options, options)) != NULL ||
407 	(val = cupsGetOption("MediaSize", num_options, options)) != NULL ||
408 	(val = cupsGetOption("page-size", num_options, options)) != NULL ||
409 	(val = cupsGetOption("PageSize", num_options, options)) != NULL) {
410       pwg_media_t *size_found = NULL;
411       fprintf(stderr, "DEBUG: Page size from command line: %s\n", val);
412       if ((size_found = pwgMediaForPWG(val)) == NULL)
413 	if ((size_found = pwgMediaForPPD(val)) == NULL)
414 	  size_found = pwgMediaForLegacy(val);
415       if (size_found != NULL) {
416 	param.page.width = size_found->width * 72.0 / 2540.0;
417         param.page.height = size_found->length * 72.0 / 2540.0;
418 	param.page.top=param.page.bottom=36.0;
419 	param.page.right=param.page.left=18.0;
420 	param.page.right=param.page.width-param.page.right;
421 	param.page.top=param.page.height-param.page.top;
422 	fprintf(stderr, "DEBUG: Width: %f, Length: %f\n", param.page.width, param.page.height);
423       }
424       else
425 	fprintf(stderr, "DEBUG: Unsupported page size %s.\n", val);
426     }
427   }
428 #endif /* HAVE_CUPS_1_7 */
429 
430   param.paper_is_landscape=(param.page.width>param.page.height);
431 
432   PageRect tmp; // borders (before rotation)
433 
434   optGetFloat("page-top",num_options,options,&tmp.top);
435   optGetFloat("page-left",num_options,options,&tmp.left);
436   optGetFloat("page-right",num_options,options,&tmp.right);
437   optGetFloat("page-bottom",num_options,options,&tmp.bottom);
438 
439   if ((val = cupsGetOption("media-top-margin", num_options, options))
440       != NULL)
441     tmp.top = atof(val) * 72.0 / 2540.0;
442   if ((val = cupsGetOption("media-left-margin", num_options, options))
443       != NULL)
444     tmp.left = atof(val) * 72.0 / 2540.0;
445   if ((val = cupsGetOption("media-right-margin", num_options, options))
446       != NULL)
447     tmp.right = atof(val) * 72.0 / 2540.0;
448   if ((val = cupsGetOption("media-bottom-margin", num_options, options))
449       != NULL)
450     tmp.bottom = atof(val) * 72.0 / 2540.0;
451 
452   if ((param.orientation==ROT_90)||(param.orientation==ROT_270)) { // unrotate page
453     // NaN stays NaN
454     tmp.right=param.page.height-tmp.right;
455     tmp.top=param.page.width-tmp.top;
456     tmp.rotate_move(param.orientation,param.page.height,param.page.width);
457   } else {
458     tmp.right=param.page.width-tmp.right;
459     tmp.top=param.page.height-tmp.top;
460     tmp.rotate_move(param.orientation,param.page.width,param.page.height);
461   }
462 
463   param.page.set(tmp); // replace values, where tmp.* != NaN  (because tmp needed rotation, param.page not!)
464 
465   if (ppdGetDuplex(ppd)) {
466     param.duplex=true;
467   } else if (is_true(cupsGetOption("Duplex",num_options,options))) {
468     param.duplex=true;
469     param.setDuplex=true;
470   } else if ((val=cupsGetOption("sides",num_options,options)) != NULL) {
471     if ((strcasecmp(val,"two-sided-long-edge")==0)||
472 	(strcasecmp(val,"two-sided-short-edge")==0)) {
473       param.duplex=true;
474       param.setDuplex=true;
475     } else if (strcasecmp(val,"one-sided")!=0) {
476       error("Unsupported sides value %s, using sides=one-sided!",val);
477     }
478   }
479 
480   // default nup is 1
481   int nup=1;
482   if (optGetInt("number-up",num_options,options,&nup)) {
483     if (!NupParameters::possible(nup)) {
484       error("Unsupported number-up value %d, using number-up=1!",nup);
485       nup=1;
486     }
487 // TODO   ;  TODO? nup enabled? ... fitplot
488 //    NupParameters::calculate(nup,param.nup);
489     NupParameters::preset(nup,param.nup);
490   }
491 
492   if ((val=cupsGetOption("number-up-layout",num_options,options)) != NULL) {
493     if (!parseNupLayout(val,param.nup)) {
494       error("Unsupported number-up-layout %s, using number-up-layout=lrtb!",val);
495       param.nup.first=Axis::X;
496       param.nup.xstart=Position::LEFT;
497       param.nup.ystart=Position::TOP;
498     }
499   }
500 
501   if ((val=cupsGetOption("page-border",num_options,options)) != NULL) {
502     if (!parseBorder(val,param.border)) {
503       error("Unsupported page-border value %s, using page-border=none!",val);
504       param.border=BorderType::NONE;
505     }
506   }
507 
508   if ((val=cupsGetOption("OutputOrder",num_options,options)) != NULL ||
509       (val=cupsGetOption("output-order",num_options,options)) != NULL ||
510       (val=cupsGetOption("page-delivery",num_options,options)) != NULL) {
511     param.reverse = (strcasecmp(val, "Reverse") == 0 ||
512 		     strcasecmp(val, "reverse-order") == 0);
513   } else if (ppd) {
514     param.reverse=ppdDefaultOrder(ppd);
515   }
516 
517   std::string rawlabel;
518   char *classification = getenv("CLASSIFICATION");
519   if (classification)
520     rawlabel.append (classification);
521 
522   if ((val=cupsGetOption("page-label", num_options, options)) != NULL) {
523     if (!rawlabel.empty())
524       rawlabel.append (" - ");
525     rawlabel.append(cupsGetOption("page-label",num_options,options));
526   }
527 
528   std::ostringstream cookedlabel;
529   for (std::string::iterator it = rawlabel.begin();
530        it != rawlabel.end ();
531        ++it) {
532     if (*it < 32 || *it > 126)
533       cookedlabel << "\\" << std::oct << std::setfill('0') << std::setw(3) << (unsigned int) *it;
534     else
535       cookedlabel.put (*it);
536   }
537   param.pageLabel = cookedlabel.str ();
538 
539   if ((val=cupsGetOption("page-set",num_options,options)) != NULL) {
540     if (strcasecmp(val,"even")==0) {
541       param.oddPages=false;
542     } else if (strcasecmp(val,"odd")==0) {
543       param.evenPages=false;
544     } else if (strcasecmp(val,"all")!=0) {
545       error("Unsupported page-set value %s, using page-set=all!",val);
546     }
547   }
548 
549   if ((val=cupsGetOption("page-ranges",num_options,options)) != NULL) {
550     parseRanges(val,param.pageRange);
551   }
552 
553   ppd_choice_t *choice;
554   if ((choice=ppdFindMarkedChoice(ppd,"MirrorPrint")) != NULL) {
555     val=choice->choice;
556   } else {
557     val=cupsGetOption("mirror",num_options,options);
558   }
559   param.mirror=is_true(val);
560 
561   if ((val=cupsGetOption("emit-jcl",num_options,options)) != NULL) {
562     param.emitJCL=!is_false(val)&&(strcmp(val,"0")!=0);
563   }
564 
565   param.booklet=BookletMode::BOOKLET_OFF;
566   if ((val=cupsGetOption("booklet",num_options,options)) != NULL) {
567     if (strcasecmp(val,"shuffle-only")==0) {
568       param.booklet=BookletMode::BOOKLET_JUSTSHUFFLE;
569     } else if (is_true(val)) {
570       param.booklet=BookletMode::BOOKLET_ON;
571     } else if (!is_false(val)) {
572       error("Unsupported booklet value %s, using booklet=off!",val);
573     }
574   }
575   param.bookSignature=-1;
576   if (optGetInt("booklet-signature",num_options,options,&param.bookSignature)) {
577     if (param.bookSignature==0) {
578       error("Unsupported booklet-signature value, using booklet-signature=-1 (all)!",val);
579       param.bookSignature=-1;
580     }
581   }
582 
583   if ((val=cupsGetOption("position",num_options,options)) != NULL) {
584     if (!parsePosition(val,param.xpos,param.ypos)) {
585       error("Unrecognized position value %s, using position=center!",val);
586       param.xpos=Position::CENTER;
587       param.ypos=Position::CENTER;
588     }
589   }
590 
591   param.collate=optGetCollate(num_options,options);
592   // FIXME? pdftopdf also considers if ppdCollate is set (only when cupsGetOption is /not/ given) [and if is_true overrides param.collate=true]  -- pstops does not
593 
594 /*
595   // TODO: scaling
596   // TODO: natural-scaling
597 
598   scaling
599 
600 
601   if ((val = cupsGetOption("scaling",num_options,options)) != 0) {
602     scaling = atoi(val) * 0.01;
603     fitplot = true;
604   } else if (fitplot) {
605     scaling = 1.0;
606   }
607   if ((val = cupsGetOption("natural-scaling",num_options,options)) != 0) {
608     naturalScaling = atoi(val) * 0.01;
609   }
610 
611 bool checkFeature(const char *feature, int num_options, cups_option_t *options) // {{{
612 {
613   const char *val;
614   ppd_attr_t *attr;
615 
616   return ((val=cupsGetOption(feature,num_options,options)) != NULL && is_true(val)) ||
617          ((attr=ppdFindAttr(ppd,feature,0)) != NULL && is_true(attr->val));
618 }
619 // }}}
620 */
621 
622   // make pages a multiple of two (only considered when duplex is on).
623   // i.e. printer has hardware-duplex, but needs pre-inserted filler pages
624   // FIXME? pdftopdf also supports it as cmdline option (via checkFeature())
625   ppd_attr_t *attr;
626   if ((attr=ppdFindAttr(ppd,"cupsEvenDuplex",0)) != NULL) {
627     param.evenDuplex=is_true(attr->value);
628   }
629 
630   // TODO? pdftopdf* ?
631   // TODO?! pdftopdfAutoRotate
632 
633   // TODO?!  choose default by whether pdfautoratate filter has already been run (e.g. by mimetype)
634   param.autoRotate=(!is_false(cupsGetOption("pdfAutoRotate",num_options,options)) &&
635 		    !is_false(cupsGetOption("pdftopdfAutoRotate",num_options,options)));
636 
637   // Do we have to do the page logging in page_log?
638 
639   // CUPS standard is that the last filter (not the backend, usually the
640   // printer driver) does page logging in the /var/log/cups/page_log file
641   // by outputting "PAGE: <# of current page> <# of copies>" to stderr.
642 
643   // pdftopdf would have to do this only for PDF printers as in this case
644   // pdftopdf is the last filter, but some of the other filters are not
645   // able to do the logging because they do not have access to the number
646   // of pages of the file to be printed, so pdftopdf overtakes their logging
647   // duty.
648 
649   // The filters currently are:
650   // - foomatic-rip (lets Ghostscript convert PDF to printer's format via
651   //   built-in drivers, no access to the PDF content)
652   // - gstopxl (uses Ghostscript, like foomatic-rip)
653   // - *toraster on IPP Everywhere printers (then *toraster gets the last
654   //   filter, the case if FINAL_CONTENT_TYPE env var is "image/pwg-raster")
655   // - hpps (bug)
656 
657   // Check whether page logging is forced or suppressed by the command line
658   if ((val=cupsGetOption("page-logging",num_options,options)) != NULL) {
659     if (strcasecmp(val,"auto") == 0) {
660       param.page_logging = -1;
661       fprintf(stderr,
662 	      "DEBUG: pdftopdf: Automatic page logging selected by command line.\n");
663     } else if (is_true(val)) {
664       param.page_logging = 1;
665       fprintf(stderr,
666 	      "DEBUG: pdftopdf: Forced page logging selected by command line.\n");
667     } else if (is_false(val)) {
668       param.page_logging = 0;
669       fprintf(stderr,
670 	      "DEBUG: pdftopdf: Suppressed page logging selected by command line.\n");
671     } else {
672       error("Unsupported page-logging value %s, using page-logging=auto!",val);
673       param.page_logging = -1;
674     }
675   }
676 
677   if (param.page_logging == -1) {
678     // Determine the last filter in the chain via cupsFilter(2) lines of the
679     // PPD file and FINAL_CONTENT_TYPE
680     if (!ppd) {
681       // If there is no PPD do not log when not requested by command line
682       param.page_logging = 0;
683       fprintf(stderr,
684 	      "DEBUG: pdftopdf: No PPD file specified, could not determine whether to log pages or not, so turned off page logging.\n");
685     } else {
686       char *final_content_type = getenv("FINAL_CONTENT_TYPE");
687       char *lastfilter = NULL;
688       if (final_content_type == NULL) {
689 	// No FINAL_CONTENT_TYPE env variable set, we cannot determine
690 	// whether we have to log pages, so do not log.
691 	param.page_logging = 0;
692 	fprintf(stderr,
693 		"DEBUG: pdftopdf: No FINAL_CONTENT_TYPE environment variable, could not determine whether to log pages or not, so turned off page logging.\n");
694       // Proceed depending on number of cupsFilter(2) lines in PPD
695       } else if (ppd->num_filters == 0) {
696 	// No filter line, manufacturer-supplied PostScript PPD
697 	// In this case pstops, called by pdftops, does the logging
698 	param.page_logging = 0;
699       } else if (ppd->num_filters == 1) {
700 	// One filter line, so this one filter is the last filter
701 	lastfilter = ppd->filters[0];
702       } else {
703 	// More than one filter line, determine the one which got
704 	// actually used via FINAL_CONTENT_TYPE
705 	ppd_attr_t *ppd_attr;
706 	if ((ppd_attr = ppdFindAttr(ppd, "cupsFilter2", NULL)) != NULL) {
707 	  // We have cupsFilter2 lines, use only these
708 	  do {
709 	    // Go to the second work, which is the destination MIME type
710 	    char *p = ppd_attr->value;
711 	    while (!isspace(*p)) p ++;
712 	    while (isspace(*p)) p ++;
713 	    // Compare with FINAL_CONTEN_TYPE
714 	    if (!strncasecmp(final_content_type, p,
715 			     strlen(final_content_type))) {
716 	      lastfilter = ppd_attr->value;
717 	      break;
718 	    }
719 	  } while ((ppd_attr = ppdFindNextAttr(ppd, "cupsFilter2", NULL))
720 		   != NULL);
721 	} else {
722 	  // We do not have cupsFilter2 lines, use the cupsFilter lines
723 	  int i;
724 	  for (i = 0; i < ppd->num_filters; i ++) {
725 	    // Compare source MIME type (first word) with FINAL_CONTENT_TYPE
726 	    if (!strncasecmp(final_content_type, ppd->filters[i],
727 			     strlen(final_content_type))) {
728 	      lastfilter = ppd->filters[i];
729 	      break;
730 	    }
731 	  }
732 	}
733       }
734       if (param.page_logging == -1) {
735 	if (lastfilter) {
736 	  // Get the name of the last filter, without mime type and cost
737 	  char *p = lastfilter;
738 	  char *q = p + strlen(p) - 1;
739 	  while(!isspace(*q) && *q != '/') q --;
740 	  lastfilter = q + 1;
741 	  // Check whether we have to log
742 	  if (!strcasecmp(lastfilter, "-")) {
743 	    // No filter defined in the PPD
744 	    // If output data (FINAL_CONTENT_TYPE) is PDF, pdftopdf is last
745 	    // filter (PDF printer) and has to log
746 	    // If output data (FINAL_CONTENT_TYPE) is PWG Raster, *toraster is
747 	    // last filter (IPP Everywhere printer) and pdftopdf has to log
748 	    if (strcasestr(final_content_type, "/pdf") ||
749 		strcasestr(final_content_type, "/vnd.cups-pdf") ||
750 		strcasestr(final_content_type, "/pwg-raster"))
751 	      param.page_logging = 1;
752 	    else
753 	      param.page_logging = 0;
754 	  } else if (!strcasecmp(lastfilter, "pdftopdf")) {
755 	    // pdftopdf is last filter (PDF printer)
756 	    param.page_logging = 1;
757 	  } else if (!strcasecmp(lastfilter, "gstopxl")) {
758 	    // gstopxl is last filter, this is a Ghostscript-based filter
759 	    // without access to the pages of the file to be printed, so we
760 	    // log the pages
761 	    param.page_logging = 1;
762 	  } else if (!strcasecmp(lastfilter + strlen(lastfilter) - 8,
763 				 "toraster")) {
764 	    // On IPP Everywhere printers which accept PWG Raster data one
765 	    // of gstoraster, pdftoraster, or mupdftoraster is the last
766 	    // filter. These filters do not log pages so pdftopdf has to
767 	    // do it
768 	    param.page_logging = 1;
769 	  } else if (!strcasecmp(lastfilter, "foomatic-rip")) {
770 	    // foomatic-rip is last filter, foomatic-rip is mainly used as
771 	    // Ghostscript wrapper to use Ghostscript's built-in printer
772 	    // drivers. Here there is also no access to the pages so that we
773 	    // delegate the logging to pdftopdf
774 	    param.page_logging = 1;
775 	  } else if (!strcasecmp(lastfilter, "hpps")) {
776 	    // hpps is last filter, hpps is part of HPLIP and it is a bug that
777 	    // it does not do the page logging.
778 	    param.page_logging = 1;
779 	  } else {
780 	    // All the other filters log pages as expected.
781 	    param.page_logging = 0;
782 	  }
783 	} else {
784 	  error("pdftopdf: Last filter could not get determined, page logging turned off.");
785 	  param.page_logging = 0;
786 	}
787       }
788       fprintf(stderr,
789 	      "DEBUG: pdftopdf: Last filter determined by the PPD: %s; FINAL_CONTENT_TYPE: %s => pdftopdf will %slog pages in page_log.\n",
790 	      (lastfilter ? lastfilter : "None"), final_content_type,
791 	      (param.page_logging == 0 ? "not " : ""));
792     }
793   }
794 }
795 // }}}
796 
printerWillCollate(ppd_file_t * ppd)797 static bool printerWillCollate(ppd_file_t *ppd) // {{{
798 {
799   ppd_choice_t *choice;
800 
801   if (((choice=ppdFindMarkedChoice(ppd,"Collate")) != NULL)&&
802       (is_true(choice->choice))) {
803 
804     // printer can collate, but also for the currently marked ppd features?
805     ppd_option_t *opt=ppdFindOption(ppd,"Collate");
806     return (opt)&&(!opt->conflicted);
807   }
808   return false;
809 }
810 // }}}
811 
calculate(ppd_file_t * ppd,ProcessingParameters & param)812 void calculate(ppd_file_t *ppd,ProcessingParameters &param) // {{{
813 {
814   if (param.reverse)
815     // Enable evenDuplex or the first page may be empty.
816     param.evenDuplex=true; // disabled later, if non-duplex
817 
818   setFinalPPD(ppd,param);
819 
820   if (param.numCopies==1) {
821     param.deviceCopies=1;
822     // collate is never needed for a single copy
823     param.collate=false; // (does not make a big difference for us)
824   } else if ((ppd)&&(!ppd->manual_copies)) { // hw copy generation available
825     param.deviceCopies=param.numCopies;
826     if (param.collate) { // collate requested by user
827       // Check output format (FINAL_CONTENT_TYPE env variable) whether it is
828       // of a driverless IPP printer (PDF, Apple Raster, PWG Raster, PCLm).
829       // These printers do always hardware collate if they do hardware copies.
830       // https://github.com/apple/cups/issues/5433
831       char *final_content_type = getenv("FINAL_CONTENT_TYPE");
832       if (final_content_type &&
833 	  (strcasestr(final_content_type, "/pdf") ||
834 	   strcasestr(final_content_type, "/vnd.cups-pdf") ||
835 	   strcasestr(final_content_type, "/pwg-raster") ||
836 	   strcasestr(final_content_type, "/urf") ||
837 	   strcasestr(final_content_type, "/PCLm"))) {
838 	param.deviceCollate = true;
839       } else {
840 	// check collate device, with current/final(!) ppd settings
841 	param.deviceCollate=printerWillCollate(ppd);
842 	if (!param.deviceCollate) {
843 	  // printer can't hw collate -> we must copy collated in sw
844 	  param.deviceCopies=1;
845 	}
846       }
847     } // else: printer copies w/o collate and takes care of duplex/evenDuplex
848   } else { // sw copies
849     param.deviceCopies=1;
850     if (param.duplex) { // &&(numCopies>1)
851       // sw collate + evenDuplex must be forced to prevent copies on the backsides
852       param.collate=true;
853       param.deviceCollate=false;
854     }
855   }
856 
857   // TODO? FIXME:  unify code with emitJCLOptions, which does this "by-hand" now (and makes this code superfluous)
858   if (param.deviceCopies==1) {
859     // make sure any hardware copying is disabled
860     ppdMarkOption(ppd,"Copies","1");
861     ppdMarkOption(ppd,"JCLCopies","1");
862   } else { // hw copy
863     param.numCopies=1; // disable sw copy
864   }
865 
866   if ((param.collate)&&(!param.deviceCollate)) { // software collate
867     ppdMarkOption(ppd,"Collate","False"); // disable any hardware-collate (in JCL)
868     param.evenDuplex=true; // fillers always needed
869   }
870 
871   if (!param.duplex) {
872     param.evenDuplex=false;
873   }
874 }
875 // }}}
876 
877 // reads from stdin into temporary file. returns FILE *  or NULL on error
copy_stdin_to_temp()878 FILE *copy_stdin_to_temp() // {{{
879 {
880   char buf[BUFSIZ];
881   int n;
882 
883   // FIXME:  what does >buf mean here?
884   int fd=cupsTempFd(buf,sizeof(buf));
885   if (fd<0) {
886     error("Can't create temporary file");
887     return NULL;
888   }
889   // remove name
890   unlink(buf);
891 
892   // copy stdin to the tmp file
893   while ((n=read(0,buf,BUFSIZ)) > 0) {
894     if (write(fd,buf,n) != n) {
895       error("Can't copy stdin to temporary file");
896       close(fd);
897       return NULL;
898     }
899   }
900   if (lseek(fd,0,SEEK_SET) < 0) {
901     error("Can't rewind temporary file");
902     close(fd);
903     return NULL;
904   }
905 
906   FILE *f;
907   if ((f=fdopen(fd,"rb")) == 0) {
908     error("Can't fdopen temporary file");
909     close(fd);
910     return NULL;
911   }
912   return f;
913 }
914 // }}}
915 
916 // check whether a given file is empty
is_empty(FILE * f)917 bool is_empty(FILE *f) // {{{
918 {
919   char buf[1];
920 
921   // Try to read a single byte of data
922   if (fread(buf, 1, 1, f) == 0)
923     return true;
924 
925   rewind(f);
926 
927   return false;
928 }
929 // }}}
930 
931 static int
sub_process_spawn(const char * filename,cups_array_t * sub_process_args,FILE * fp)932 sub_process_spawn (const char *filename,
933           cups_array_t *sub_process_args,
934           FILE *fp) // {{{
935 {
936   char *argument;
937   char buf[BUFSIZ];
938   char **sub_process_argv;
939   const char* apos;
940   int fds[2];
941   int i;
942   int n;
943   int numargs;
944   int pid;
945   int status = 65536;
946   int wstatus;
947 
948   /* Put sub-process command line argument into an array for the "exec()"
949      call */
950   numargs = cupsArrayCount(sub_process_args);
951   sub_process_argv = (char **)calloc(numargs + 1, sizeof(char *));
952   for (argument = (char *)cupsArrayFirst(sub_process_args), i = 0; argument;
953        argument = (char *)cupsArrayNext(sub_process_args), i++) {
954     sub_process_argv[i] = argument;
955   }
956   sub_process_argv[i] = NULL;
957 
958   /* Debug output: Full sub-process command line */
959   fprintf(stderr, "DEBUG: PDF form flattening command line:");
960   for (i = 0; sub_process_argv[i]; i ++) {
961     if ((strchr(sub_process_argv[i],' ')) || (strchr(sub_process_argv[i],'\t')))
962       apos = "'";
963     else
964       apos = "";
965     fprintf(stderr, " %s%s%s", apos, sub_process_argv[i], apos);
966   }
967   fprintf(stderr, "\n");
968 
969   /* Create a pipe for feeding the job into sub-process */
970   if (pipe(fds))
971   {
972     fds[0] = -1;
973     fds[1] = -1;
974     fprintf(stderr, "ERROR: Unable to establish pipe for sub-process call\n");
975     goto out;
976   }
977 
978   /* Set the "close on exec" flag on each end of the pipe... */
979   if (fcntl(fds[0], F_SETFD, fcntl(fds[0], F_GETFD) | FD_CLOEXEC))
980   {
981     close(fds[0]);
982     close(fds[1]);
983     fds[0] = -1;
984     fds[1] = -1;
985     fprintf(stderr, "ERROR: Unable to set \"close on exec\" flag on read end of the pipe for sub-process call\n");
986     goto out;
987   }
988   if (fcntl(fds[1], F_SETFD, fcntl(fds[1], F_GETFD) | FD_CLOEXEC))
989   {
990     close(fds[0]);
991     close(fds[1]);
992     fprintf(stderr, "ERROR: Unable to set \"close on exec\" flag on write end of the pipe for sub-process call\n");
993     goto out;
994   }
995 
996   if ((pid = fork()) == 0)
997   {
998     /* Couple pipe with STDIN of sub-process */
999     if (fds[0] != 0) {
1000       close(0);
1001       if (fds[0] > 0) {
1002         if (dup(fds[0]) < 0) {
1003 	  fprintf(stderr, "ERROR: Unable to couple pipe with STDIN of sub-process\n");
1004 	  goto out;
1005 	}
1006       } else {
1007         fprintf(stderr, "ERROR: Unable to couple pipe with STDIN of sub-process\n");
1008         goto out;
1009       }
1010     }
1011     close(fds[1]);
1012 
1013     /* Execute sub-process command line ... */
1014     execvp(filename, sub_process_argv);
1015     perror(filename);
1016     close(fds[0]);
1017     goto out;
1018   }
1019 
1020   close(fds[0]);
1021   /* Feed job data into the sub-process */
1022   while ((n = fread(buf, 1, BUFSIZ, fp)) > 0) {
1023     int count;
1024 retry_write:
1025     count = write(fds[1], buf, n);
1026     if (count != n) {
1027       if (count == -1) {
1028         if (errno == EINTR) {
1029           goto retry_write;
1030 	}
1031         fprintf(stderr, "ERROR: write failed: %s\n", strerror(errno));
1032       }
1033       fprintf(stderr, "ERROR: Can't feed job data into the sub-process\n");
1034       goto out;
1035     }
1036   }
1037   close (fds[1]);
1038 
1039 retry_wait:
1040   if (waitpid (pid, &wstatus, 0) == -1) {
1041     if (errno == EINTR)
1042       goto retry_wait;
1043     perror ("sub-process");
1044     goto out;
1045   }
1046 
1047   /* How did the sub-process terminate */
1048   if (WIFEXITED(wstatus))
1049     /* Via exit() anywhere or return() in the main() function */
1050     status = WEXITSTATUS(wstatus);
1051   else if (WIFSIGNALED(wstatus))
1052     /* Via signal */
1053     status = 256 * WTERMSIG(wstatus);
1054 
1055 out:
1056   free(sub_process_argv);
1057   return status;
1058 }
1059 // }}}
1060 
main(int argc,char ** argv)1061 int main(int argc,char **argv)
1062 {
1063   if ((argc<6)||(argc>7)) {
1064     fprintf(stderr,"Usage: %s job-id user title copies options [file]\n",argv[0]);
1065 #ifdef DEBUG
1066     ProcessingParameters param;
1067     std::unique_ptr<PDFTOPDF_Processor> proc1(PDFTOPDF_Factory::processor());
1068     param.page.width=595.276; // A4
1069     param.page.height=841.89;
1070 
1071     param.page.top=param.page.bottom=36.0;
1072     param.page.right=param.page.left=18.0;
1073     param.page.right=param.page.width-param.page.right;
1074     param.page.top=param.page.height-param.page.top;
1075 
1076     //param.nup.calculate(4,0.707,0.707,param.nup);
1077     param.nup.nupX=2;
1078     param.nup.nupY=2;
1079     //param.nup.yalign=TOP;
1080     param.border=BorderType::NONE;
1081     //param.fillprint = true;
1082     //param.mirror=true;
1083     //param.reverse=true;
1084     //param.numCopies=3;
1085     if (!proc1->loadFilename("in.pdf",1)) return 2;
1086     param.dump();
1087     if (!processPDFTOPDF(*proc1,param)) return 3;
1088     emitComment(*proc1,param);
1089     proc1->emitFilename("out.pdf");
1090 #endif
1091     return 1;
1092   }
1093 
1094   try {
1095     ProcessingParameters param;
1096 
1097     param.jobId=atoi(argv[1]);
1098     param.user=argv[2];
1099     param.title=argv[3];
1100     param.numCopies=atoi(argv[4]);
1101     param.copies_to_be_logged=atoi(argv[4]);
1102 
1103     // TODO?! sanity checks
1104 
1105     int num_options=0;
1106     cups_option_t *options=NULL;
1107     num_options=cupsParseOptions(argv[5],num_options,&options);
1108 
1109     ppd_file_t *ppd=NULL;
1110     ppd=ppdOpenFile(getenv("PPD")); // getenv (and thus ppd) may be null. This will not cause problems.
1111     ppdMarkDefaults(ppd);
1112 
1113     cupsMarkOptions(ppd,num_options,options);
1114 
1115     getParameters(ppd,num_options,options,param);
1116     calculate(ppd,param);
1117 
1118 #ifdef DEBUG
1119     param.dump();
1120 #endif
1121 
1122     /* Check with which method we will flatten interactive PDF forms
1123        and annotations so that they get printed also after page
1124        manipulations (scaling, N-up, ...). Flattening means to
1125        integrate the filled in data and the printable annotations into
1126        the pages themselves instead of holding them in an extra
1127        layer. Default method is using QPDF, alternatives are the
1128        external utilities pdftocairo or Ghostscript, but these make
1129        the processing slower, especially due to extra piping of the
1130        data between processes. */
1131     int empty = 0;
1132     int qpdf_flatten = 1;
1133     int pdftocairo_flatten = 0;
1134     int gs_flatten = 0;
1135     int external_auto_flatten = 0;
1136     const char	*val;
1137     if ((val = cupsGetOption("pdftopdf-form-flattening", num_options, options)) != NULL) {
1138       if (strcasecmp(val, "qpdf") == 0 || strcasecmp(val, "internal") == 0 ||
1139 	  strcasecmp(val, "auto") == 0) {
1140 	qpdf_flatten = 1;
1141       } else if (strcasecmp(val, "external") == 0) {
1142 	qpdf_flatten = 0;
1143 	external_auto_flatten = 1;
1144       } else if (strcasecmp(val, "pdftocairo") == 0) {
1145 	qpdf_flatten = 0;
1146 	pdftocairo_flatten = 1;
1147       } else if (strcasecmp(val, "ghostscript") == 0 || strcasecmp(val, "gs") == 0) {
1148 	qpdf_flatten = 0;
1149 	gs_flatten = 1;
1150       } else
1151 	fprintf(stderr,
1152 		"WARNING: Invalid value for \"pdftopdf-form-flattening\": \"%s\"\n", val);
1153     }
1154 
1155     cupsFreeOptions(num_options,options);
1156 
1157     std::unique_ptr<PDFTOPDF_Processor> proc(PDFTOPDF_Factory::processor());
1158 
1159     FILE *tmpfile = NULL;
1160     if (argc==7) {
1161       FILE *f = NULL;
1162       if ((f = fopen(argv[6], "rb")) == NULL) {
1163         ppdClose(ppd);
1164         return 1;
1165       } else if (is_empty(f)) {
1166 	fclose(f);
1167 	ppdClose(ppd);
1168 	empty = 1;
1169       } else if (!proc->loadFilename(argv[6],qpdf_flatten)) {
1170 	fclose(f);
1171         ppdClose(ppd);
1172         return 1;
1173       } else
1174 	fclose(f);
1175     } else {
1176       tmpfile = copy_stdin_to_temp();
1177       if (tmpfile && is_empty(tmpfile)) {
1178 	fclose(tmpfile);
1179 	ppdClose(ppd);
1180 	empty = 1;
1181       } else if ((!tmpfile)||
1182 		 (!proc->loadFile(tmpfile,WillStayAlive,qpdf_flatten))) {
1183         ppdClose(ppd);
1184 	return 1;
1185       }
1186     }
1187 
1188     if(empty)
1189     {
1190       fprintf(stderr, "DEBUG: Input is empty, outputting empty file.\n");
1191       return 0;
1192     }
1193 
1194     /* If the input file contains a PDF form and we opted for not
1195        using QPDF for flattening the form, we pipe the PDF through
1196        pdftocairo or Ghostscript here */
1197     if (!qpdf_flatten && proc->hasAcroForm()) {
1198       /* Prepare the input file for being read by the form flattening
1199 	 process */
1200       FILE *infile = NULL;
1201       if (argc == 7) {
1202 	/* We read from a named file */
1203 	infile = fopen(argv[6], "r");
1204       } else {
1205 	/* We read from a temporary file */
1206 	if (tmpfile) rewind(tmpfile);
1207 	infile = tmpfile;
1208       }
1209       if (infile == NULL) {
1210 	error("Could not open the input file for flattening the PDF form!");
1211 	return 1;
1212       }
1213       /* Create a temporary file for the output of the flattened PDF */
1214       char buf[BUFSIZ];
1215       int fd = cupsTempFd(buf,sizeof(buf));
1216       if (fd<0) {
1217 	error("Can't create temporary file for flattened PDF form!");
1218 	return 1;
1219       }
1220       FILE *outfile = NULL;
1221       if ((outfile=fdopen(fd,"rb")) == 0) {
1222 	error("Can't fdopen temporary file for the flattened PDF form!");
1223 	close(fd);
1224 	return 1;
1225       }
1226       int flattening_done = 0;
1227       const char *command;
1228       cups_array_t *args;
1229       /* Choose the utility to be used and create its command line */
1230       if (pdftocairo_flatten || external_auto_flatten) {
1231 	/* Try pdftocairo first, the preferred utility for form-flattening */
1232 	command = CUPS_POPPLER_PDFTOCAIRO;
1233 	args = cupsArrayNew(NULL, NULL);
1234 	cupsArrayAdd(args, strdup(command));
1235 	cupsArrayAdd(args, strdup("-pdf"));
1236 	cupsArrayAdd(args, strdup("-"));
1237 	cupsArrayAdd(args, strdup(buf));
1238 	/* Run the pdftocairo form flattening process */
1239 	rewind(infile);
1240 	int status = sub_process_spawn (command, args, infile);
1241 	cupsArrayDelete(args);
1242 	if (status == 0)
1243 	  flattening_done = 1;
1244 	else
1245 	  error("Unable to execute pdftocairo for form flattening!");
1246       }
1247       if (flattening_done == 0 &&
1248 	  (gs_flatten || external_auto_flatten)) {
1249 	/* Try Ghostscript */
1250 	command = CUPS_GHOSTSCRIPT;
1251 	args = cupsArrayNew(NULL, NULL);
1252 	cupsArrayAdd(args, strdup(command));
1253 	cupsArrayAdd(args, strdup("-dQUIET"));
1254 	cupsArrayAdd(args, strdup("-dSAFER"));
1255 	cupsArrayAdd(args, strdup("-dNOPAUSE"));
1256 	cupsArrayAdd(args, strdup("-dBATCH"));
1257 	cupsArrayAdd(args, strdup("-dNOINTERPOLATE"));
1258 	cupsArrayAdd(args, strdup("-dNOMEDIAATTRS"));
1259 	cupsArrayAdd(args, strdup("-sDEVICE=pdfwrite"));
1260 	cupsArrayAdd(args, strdup("-dShowAcroForm"));
1261 	cupsArrayAdd(args, strdup("-sstdout=%stderr"));
1262 	memmove(buf + 13, buf, sizeof(buf) - 13);
1263 	memcpy(buf, "-sOutputFile=", 13);
1264 	cupsArrayAdd(args, strdup(buf));
1265 	cupsArrayAdd(args, strdup("-"));
1266 	/* Run the Ghostscript form flattening process */
1267 	rewind(infile);
1268 	int status = sub_process_spawn (command, args, infile);
1269 	cupsArrayDelete(args);
1270 	if (status == 0)
1271 	  flattening_done = 1;
1272 	else
1273 	  error("Unable to execute Ghostscript for form flattening!");
1274       }
1275       if (flattening_done == 0) {
1276 	error("No suitable utility for flattening filled PDF forms available, no flattening performed. Filled in content will possibly not be printed.");
1277 	rewind(infile);
1278       }
1279       /* Clean up */
1280       if (infile != tmpfile)
1281 	fclose(infile);
1282       /* Load the flattened PDF file into our PDF processor */
1283       if (flattening_done) {
1284 	rewind(outfile);
1285 	unlink(buf);
1286 	if (!proc->loadFile(outfile,TakeOwnership,0)) {
1287 	  error("Unable to create a PDF processor on the flattened form!");
1288 	  return 1;
1289 	}
1290       }
1291     } else if (qpdf_flatten)
1292       fprintf(stderr, "DEBUG: PDF interactive form and annotation flattening done via QPDF\n");
1293 
1294 /* TODO
1295     // color management
1296 --- PPD:
1297       copyPPDLine_(fp_dest, fp_src, "*PPD-Adobe: ");
1298       copyPPDLine_(fp_dest, fp_src, "*cupsICCProfile ");
1299       copyPPDLine_(fp_dest, fp_src, "*Manufacturer:");
1300       copyPPDLine_(fp_dest, fp_src, "*ColorDevice:");
1301       copyPPDLine_(fp_dest, fp_src, "*DefaultColorSpace:");
1302     if (cupsICCProfile) {
1303       proc.addCM(...,...);
1304     }
1305 */
1306 
1307     if (!processPDFTOPDF(*proc,param)) {
1308       ppdClose(ppd);
1309       return 2;
1310     }
1311 
1312     emitPreamble(ppd,param); // ppdEmit, JCL stuff
1313     emitComment(*proc,param); // pass information to subsequent filters via PDF comments
1314 
1315     //proc->emitFile(stdout);
1316     proc->emitFilename(NULL);
1317 
1318     emitPostamble(ppd,param);
1319     ppdClose(ppd);
1320     if (tmpfile)
1321       fclose(tmpfile);
1322   } catch (std::exception &e) {
1323     // TODO? exception type
1324     error("Exception: %s",e.what());
1325     return 5;
1326   } catch (...) {
1327     error("Unknown exception caught. Exiting.");
1328     return 6;
1329   }
1330 
1331   return 0;
1332 }
1333