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 ¶m)
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 ¶m) // {{{
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",¶m.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,¶m.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 ¶m) // {{{
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