Fixed build error by poppler-cpp
[platform/upstream/cups-filters.git] / filter / pdftopdf / qpdf_pdftopdf_processor.cc
1 #include "qpdf_pdftopdf_processor.h"
2 #include <stdio.h>
3 #include <stdarg.h>
4 #include <assert.h>
5 #include <stdexcept>
6 #include <qpdf/QPDFWriter.hh>
7 #include <qpdf/QUtil.hh>
8 #include "qpdf_tools.h"
9 #include "qpdf_xobject.h"
10 #include "qpdf_pdftopdf.h"
11
12 // Use: content.append(debug_box(pe.sub,xpos,ypos));
13 static std::string debug_box(const PageRect &box,float xshift,float yshift) // {{{ 
14 {
15   return std::string("q 1 w 0.1 G\n ")+
16          QUtil::double_to_string(box.left+xshift)+" "+QUtil::double_to_string(box.bottom+yshift)+" m  "+
17          QUtil::double_to_string(box.right+xshift)+" "+QUtil::double_to_string(box.top+yshift)+" l "+"S \n "+
18
19          QUtil::double_to_string(box.right+xshift)+" "+QUtil::double_to_string(box.bottom+yshift)+" m  "+
20          QUtil::double_to_string(box.left+xshift)+" "+QUtil::double_to_string(box.top+yshift)+" l "+"S \n "+
21
22          QUtil::double_to_string(box.left+xshift)+" "+QUtil::double_to_string(box.bottom+yshift)+"  "+
23          QUtil::double_to_string(box.right-box.left)+" "+QUtil::double_to_string(box.top-box.bottom)+" re "+"S Q\n";
24 }
25 // }}}
26
27 QPDF_PDFTOPDF_PageHandle::QPDF_PDFTOPDF_PageHandle(QPDFObjectHandle page,int orig_no) // {{{
28   : page(page),
29     no(orig_no),
30     rotation(ROT_0)
31 {
32 }
33 // }}}
34
35 QPDF_PDFTOPDF_PageHandle::QPDF_PDFTOPDF_PageHandle(QPDF *pdf,float width,float height) // {{{
36   : no(0),
37     rotation(ROT_0)
38 {
39   assert(pdf);
40   page=QPDFObjectHandle::parse(
41     "<<"
42     "  /Type /Page"
43     "  /Resources <<"
44     "    /XObject null "
45     "  >>"
46     "  /MediaBox null "
47     "  /Contents null "
48     ">>");
49   page.replaceKey("/MediaBox",makeBox(0,0,width,height));
50   page.replaceKey("/Contents",QPDFObjectHandle::newStream(pdf));
51   // xobjects: later (in get())
52   content.assign("q\n");  // TODO? different/not needed
53
54   page=pdf->makeIndirectObject(page); // stores *pdf 
55 }
56 // }}}
57
58 // Note: PDFTOPDF_Processor always works with "/Rotate"d and "/UserUnit"-scaled pages/coordinates/..., having 0,0 at left,bottom of the TrimBox
59 PageRect QPDF_PDFTOPDF_PageHandle::getRect() const // {{{
60 {
61   page.assertInitialized();
62   PageRect ret=getBoxAsRect(getTrimBox(page));
63   ret.translate(-ret.left,-ret.bottom);
64   ret.rotate_move(getRotate(page),ret.width,ret.height);
65   ret.scale(getUserUnit(page));
66   return ret;
67 }
68 // }}}
69
70 bool QPDF_PDFTOPDF_PageHandle::isExisting() const // {{{
71 {
72   page.assertInitialized();
73   return content.empty();
74 }
75 // }}}
76
77 QPDFObjectHandle QPDF_PDFTOPDF_PageHandle::get() // {{{
78 {
79   QPDFObjectHandle ret=page;
80   if (!isExisting()) { // finish up page
81     page.getKey("/Resources").replaceKey("/XObject",QPDFObjectHandle::newDictionary(xobjs));
82     content.append("Q\n");
83     page.getKey("/Contents").replaceStreamData(content,QPDFObjectHandle::newNull(),QPDFObjectHandle::newNull());
84     page.replaceOrRemoveKey("/Rotate",makeRotate(rotation));
85   } else {
86     Rotation rot=getRotate(page)+rotation;
87     page.replaceOrRemoveKey("/Rotate",makeRotate(rot));
88   }
89   page=QPDFObjectHandle(); // i.e. uninitialized
90   return ret;
91 }
92 // }}}
93
94 // TODO: we probably need a function "ungetRect()"  to transform to page/form space
95 // TODO: as member
96 static PageRect ungetRect(PageRect rect,const QPDF_PDFTOPDF_PageHandle &ph,Rotation rotation,QPDFObjectHandle page)
97 {
98   PageRect pg1=ph.getRect();
99   PageRect pg2=getBoxAsRect(getTrimBox(page));
100
101   // we have to invert /Rotate, /UserUnit and the left,bottom (TrimBox) translation
102 //Rotation_dump(rotation);
103 //Rotation_dump(getRotate(page));
104   rect.width=pg1.width;
105   rect.height=pg1.height;
106 //std::swap(rect.width,rect.height);
107 //rect.rotate_move(-rotation,rect.width,rect.height);
108
109   rect.rotate_move(-getRotate(page),pg1.width,pg1.height);
110   rect.scale(1.0/getUserUnit(page));
111
112 //  PageRect pg2=getBoxAsRect(getTrimBox(page));
113   rect.translate(pg2.left,pg2.bottom);
114 //rect.dump();
115
116   return rect;
117 }
118
119   // TODO FIXME rotations are strange  ... (via ungetRect)
120 // TODO? for non-existing (either drop comment or facility to create split streams needed)
121 void QPDF_PDFTOPDF_PageHandle::add_border_rect(const PageRect &_rect,BorderType border,float fscale) // {{{
122 {
123   assert(isExisting());
124   assert(border!=BorderType::NONE);
125
126   // straight from pstops
127   const double lw=(border&THICK)?0.5:0.24;
128   double line_width=lw*fscale;
129   double margin=2.25*fscale;
130 // (PageLeft+margin,PageBottom+margin) rect (PageRight-PageLeft-2*margin,...)   ... for nup>1: PageLeft=0,etc.
131    //  if (double)  margin+=2*fscale ...rect...
132
133   PageRect rect=ungetRect(_rect,*this,rotation,page);
134
135   assert(rect.left<=rect.right);
136   assert(rect.bottom<=rect.top);
137
138   std::string boxcmd="q\n";
139   boxcmd+="  "+QUtil::double_to_string(line_width)+" w 0 G \n";
140   boxcmd+="  "+QUtil::double_to_string(rect.left+margin)+" "+QUtil::double_to_string(rect.bottom+margin)+"  "+
141                QUtil::double_to_string(rect.right-rect.left-2*margin)+" "+QUtil::double_to_string(rect.top-rect.bottom-2*margin)+" re S \n";
142   if (border&TWO) {
143     margin+=2*fscale;
144     boxcmd+="  "+QUtil::double_to_string(rect.left+margin)+" "+QUtil::double_to_string(rect.bottom+margin)+"  "+
145                  QUtil::double_to_string(rect.right-rect.left-2*margin)+" "+QUtil::double_to_string(rect.top-rect.bottom-2*margin)+" re S \n";
146   }
147   boxcmd+="Q\n";
148
149 // if (!isExisting()) {
150 //   // TODO: only after 
151 //   return;
152 // }
153
154   assert(page.getOwningQPDF()); // existing pages are always indirect
155 #ifdef DEBUG  // draw it on top
156   static const char *pre="%pdftopdf q\n"
157                          "q\n",
158                     *post="%pdftopdf Q\n"
159                           "Q\n";
160
161   QPDFObjectHandle stm1=QPDFObjectHandle::newStream(page.getOwningQPDF(),pre),
162                    stm2=QPDFObjectHandle::newStream(page.getOwningQPDF(),std::string(post)+boxcmd);
163
164   page.addPageContents(stm1,true); // before
165   page.addPageContents(stm2,false); // after
166 #else
167   QPDFObjectHandle stm=QPDFObjectHandle::newStream(page.getOwningQPDF(),boxcmd);
168   page.addPageContents(stm,true); // before
169 #endif
170 }
171 // }}}
172
173 // TODO: better cropping
174 // TODO: test/fix with qsub rotation
175 void QPDF_PDFTOPDF_PageHandle::add_subpage(const std::shared_ptr<PDFTOPDF_PageHandle> &sub,float xpos,float ypos,float scale,const PageRect *crop) // {{{
176 {
177   auto qsub=dynamic_cast<QPDF_PDFTOPDF_PageHandle *>(sub.get());
178   assert(qsub);
179
180   std::string xoname="/X"+QUtil::int_to_string((qsub->no!=-1)?qsub->no:++no);
181   if (crop) {
182     PageRect pg=qsub->getRect(),tmp=*crop;
183     // we need to fix a too small cropbox. 
184     tmp.width=tmp.right-tmp.left;
185     tmp.height=tmp.top-tmp.bottom;
186     tmp.rotate_move(-getRotate(qsub->page),tmp.width,tmp.height); // TODO TODO (pg.width? / unneeded?)
187     // TODO: better
188     // TODO: we need to obey page./Rotate
189     if (pg.width<tmp.width) {
190       pg.right=pg.left+tmp.width;
191     }
192     if (pg.height<tmp.height) {
193       pg.top=pg.bottom+tmp.height;
194     }
195     
196     PageRect rect=ungetRect(pg,*qsub,ROT_0,qsub->page);
197
198     qsub->page.replaceKey("/TrimBox",makeBox(rect.left,rect.bottom,rect.right,rect.top));
199     // TODO? do everything for cropping here?
200   }
201   xobjs[xoname]=makeXObject(qsub->page.getOwningQPDF(),qsub->page); // trick: should be the same as page->getOwningQPDF() [only after it's made indirect]
202
203   Matrix mtx;
204   mtx.translate(xpos,ypos);
205   mtx.scale(scale);
206   mtx.rotate(qsub->rotation); // TODO? -sub.rotation ?  // TODO FIXME: this might need another translation!?
207   if (crop) { // TODO? other technique: set trim-box before makeXObject (but this modifies original page)
208     mtx.translate(crop->left,crop->bottom);
209 //    crop->dump();
210   }
211
212   content.append("q\n  ");
213   content.append(mtx.get_string()+" cm\n  ");
214   if (crop) {
215     content.append("0 0 "+QUtil::double_to_string(crop->right-crop->left)+" "+QUtil::double_to_string(crop->top-crop->bottom)+" re W n\n  ");
216 //    content.append("0 0 "+QUtil::double_to_string(crop->right-crop->left)+" "+QUtil::double_to_string(crop->top-crop->bottom)+" re S\n  ");
217   }
218   content.append(xoname+" Do\n");
219   content.append("Q\n");
220 }
221 // }}} 
222
223 void QPDF_PDFTOPDF_PageHandle::mirror() // {{{
224 {
225   PageRect orig=getRect();
226
227   if (isExisting()) {
228     // need to wrap in XObject to keep patterns correct
229     // TODO? refactor into internal ..._subpage fn ?
230     std::string xoname="/X"+QUtil::int_to_string(no);
231
232     QPDFObjectHandle subpage=get();  // this->page, with rotation
233
234     // replace all our data
235     *this=QPDF_PDFTOPDF_PageHandle(subpage.getOwningQPDF(),orig.width,orig.height);
236
237     xobjs[xoname]=makeXObject(subpage.getOwningQPDF(),subpage); // we can only now set this->xobjs
238
239 //    content.append(std::string("1 0 0 1 0 0 cm\n  ");
240     content.append(xoname+" Do\n");
241
242     assert(!isExisting());
243   }
244
245   static const char *pre="%pdftopdf cm\n";
246   // Note: we don't change (TODO need to?) the media box
247   std::string mrcmd("-1 0 0 1 "+ 
248                     QUtil::double_to_string(orig.right)+" 0 cm\n");
249
250   content.insert(0,std::string(pre)+mrcmd);
251 }
252 // }}}
253
254 void QPDF_PDFTOPDF_PageHandle::rotate(Rotation rot) // {{{
255 {
256   rotation=rot; // "rotation += rot;" ?
257 }
258 // }}}
259
260 void QPDF_PDFTOPDF_PageHandle::add_label(const PageRect &_rect, const std::string label) // {{{
261 {
262   assert(isExisting());
263
264   PageRect rect = ungetRect (_rect, *this, rotation, page);
265
266   assert (rect.left <= rect.right);
267   assert (rect.bottom <= rect.top);
268
269   // TODO: Only add in the font once, not once per page.
270   QPDFObjectHandle font = page.getOwningQPDF()->makeIndirectObject (
271     QPDFObjectHandle::parse(
272       "<<"
273       " /Type /Font"
274       " /Subtype /Type1"
275       " /Name /pagelabel-font"
276       " /BaseFont /Helvetica" // TODO: support UTF-8 labels?
277       ">>"));
278   QPDFObjectHandle resources = page.getKey ("/Resources");
279   QPDFObjectHandle rfont = resources.getKey ("/Font");
280   rfont.replaceKey ("/pagelabel-font", font);
281
282   double margin = 2.25;
283   double height = 12;
284
285   std::string boxcmd = "q\n";
286
287   // White filled rectangle (top)
288   boxcmd += "  1 1 1 rg\n";
289   boxcmd += "  " + QUtil::double_to_string(rect.left + margin) + " " +
290                    QUtil::double_to_string(rect.top - height - 2 * margin) + " " +
291                    QUtil::double_to_string(rect.right - rect.left - 2 * margin) + " " +
292                    QUtil::double_to_string(height + 2 * margin) + " re f\n";
293
294   // White filled rectangle (bottom)
295   boxcmd += "  " + QUtil::double_to_string(rect.left + margin) + " " +
296                    QUtil::double_to_string(rect.bottom + height + margin) + " " +
297                    QUtil::double_to_string(rect.right - rect.left - 2 * margin) + " " +
298                    QUtil::double_to_string(height + 2 * margin) + " re f\n";
299
300   // Black outline (top)
301   boxcmd += "  0 0 0 RG\n";
302   boxcmd += "  " + QUtil::double_to_string(rect.left + margin) + " " +
303                    QUtil::double_to_string(rect.top - height - 2 * margin) + " " +
304                    QUtil::double_to_string(rect.right - rect.left - 2 * margin) + " " +
305                    QUtil::double_to_string(height + 2 * margin) + " re S\n";
306
307   // Black outline (bottom)
308   boxcmd += "  " + QUtil::double_to_string(rect.left + margin) + " " +
309                    QUtil::double_to_string(rect.bottom + height + margin) + " " +
310                    QUtil::double_to_string(rect.right - rect.left - 2 * margin) + " " +
311                    QUtil::double_to_string(height + 2 * margin) + " re S\n";
312
313   // Black text (top)
314   boxcmd += "  0 0 0 rg\n";
315   boxcmd += "  BT\n";
316   boxcmd += "  /pagelabel-font 12 Tf\n";
317   boxcmd += "  " + QUtil::double_to_string(rect.left + 2 * margin) + " " +
318                    QUtil::double_to_string(rect.top - height - margin) + " Td\n";
319   boxcmd += "  (" + label + ") Tj\n";
320   boxcmd += "  ET\n";
321
322   // Black text (bottom)
323   boxcmd += "  BT\n";
324   boxcmd += "  /pagelabel-font 12 Tf\n";
325   boxcmd += "  " + QUtil::double_to_string(rect.left + 2 * margin) + " " +
326                    QUtil::double_to_string(rect.bottom + height + 2 * margin) + " Td\n";
327   boxcmd += "  (" + label + ") Tj\n";
328   boxcmd += "  ET\n";
329
330   boxcmd += "Q\n";
331
332   assert(page.getOwningQPDF()); // existing pages are always indirect
333   static const char *pre="%pdftopdf q\n"
334                          "q\n",
335                     *post="%pdftopdf Q\n"
336                           "Q\n";
337
338   QPDFObjectHandle stm1=QPDFObjectHandle::newStream(page.getOwningQPDF(),
339                                                     std::string(pre)),
340                    stm2=QPDFObjectHandle::newStream(page.getOwningQPDF(),
341                                                     std::string(post) + boxcmd);
342
343   page.addPageContents(stm1,true); // before
344   page.addPageContents(stm2,false); // after
345 }
346 // }}}
347
348 void QPDF_PDFTOPDF_PageHandle::debug(const PageRect &rect,float xpos,float ypos) // {{{
349 {
350   assert(!isExisting());
351   content.append(debug_box(rect,xpos,ypos));
352 }
353 // }}}
354
355 // }}}
356 void QPDF_PDFTOPDF_Processor::closeFile() // {{{
357 {
358   pdf.reset();
359   hasCM=false;
360 }
361 // }}}
362
363 void QPDF_PDFTOPDF_Processor::error(const char *fmt,...) // {{{
364 {
365   va_list ap;
366
367   va_start(ap,fmt);
368   fputs("ERROR: ",stderr);
369   vfprintf(stderr,fmt,ap);
370   fputs("\n",stderr);
371   va_end(ap);
372 }
373 // }}}
374
375 // TODO?  try/catch for PDF parsing errors?
376
377 bool QPDF_PDFTOPDF_Processor::loadFile(FILE *f,ArgOwnership take) // {{{
378 {
379   closeFile();
380   if (!f) {
381     throw std::invalid_argument("loadFile(NULL,...) not allowed");
382   }
383   try {
384     pdf.reset(new QPDF);
385   } catch (...) {
386     if (take==TakeOwnership) {
387       fclose(f);
388     }
389     throw;
390   }
391   switch (take) {
392   case WillStayAlive:
393     try {
394       pdf->processFile("temp file",f,false);
395     } catch (const std::exception &e) {
396       error("loadFile failed: %s",e.what());
397       return false;
398     }
399     break;
400   case TakeOwnership:
401     try {
402       pdf->processFile("temp file",f,true);
403     } catch (const std::exception &e) {
404       error("loadFile failed: %s",e.what());
405       return false;
406     }
407     break;
408   case MustDuplicate:
409     error("loadFile with MustDuplicate is not supported");
410     return false;
411   }
412   start();
413   return true;
414 }
415 // }}}
416
417 bool QPDF_PDFTOPDF_Processor::loadFilename(const char *name) // {{{
418 {
419   closeFile();
420   try {
421     pdf.reset(new QPDF);
422     pdf->processFile(name);
423   } catch (const std::exception &e) {
424     error("loadFilename failed: %s",e.what());
425     return false;
426   }
427   start();
428   return true;
429 }
430 // }}}
431
432
433 void QPDF_PDFTOPDF_Processor::start() // {{{
434 {
435   assert(pdf);
436
437   pdf->pushInheritedAttributesToPage();
438   orig_pages=pdf->getAllPages();
439
440   // remove them (just unlink, data still there)
441   const int len=orig_pages.size();
442   for (int iA=0;iA<len;iA++) {
443     pdf->removePage(orig_pages[iA]);
444   }
445
446   // we remove stuff that becomes defunct (probably)  TODO
447   pdf->getRoot().removeKey("/PageMode");
448   pdf->getRoot().removeKey("/Outlines");
449   pdf->getRoot().removeKey("/OpenAction");
450   pdf->getRoot().removeKey("/PageLabels");
451 }
452 // }}}
453
454 bool QPDF_PDFTOPDF_Processor::check_print_permissions() // {{{
455 {
456   if (!pdf) {
457     error("No PDF loaded");
458     return false;
459   }
460   return pdf->allowPrintHighRes() || pdf->allowPrintLowRes(); // from legacy pdftopdf
461 }
462 // }}}
463
464
465 std::vector<std::shared_ptr<PDFTOPDF_PageHandle>> QPDF_PDFTOPDF_Processor::get_pages() // {{{
466 {
467   std::vector<std::shared_ptr<PDFTOPDF_PageHandle>> ret;
468   if (!pdf) {
469     error("No PDF loaded");
470     assert(0);
471     return ret;
472   }
473   const int len=orig_pages.size();
474   ret.reserve(len);
475   for (int iA=0;iA<len;iA++) {
476     ret.push_back(std::shared_ptr<PDFTOPDF_PageHandle>(new QPDF_PDFTOPDF_PageHandle(orig_pages[iA],iA+1)));
477   }
478   return ret;
479 }
480 // }}}
481
482 std::shared_ptr<PDFTOPDF_PageHandle> QPDF_PDFTOPDF_Processor::new_page(float width,float height) // {{{
483 {
484   if (!pdf) {
485     error("No PDF loaded");
486     assert(0);
487     return std::shared_ptr<PDFTOPDF_PageHandle>();
488   }
489   return std::shared_ptr<QPDF_PDFTOPDF_PageHandle>(new QPDF_PDFTOPDF_PageHandle(pdf.get(),width,height));
490   // return std::make_shared<QPDF_PDFTOPDF_PageHandle>(pdf.get(),width,height);  // problem: make_shared not friend
491 }
492 // }}}
493
494 void QPDF_PDFTOPDF_Processor::add_page(std::shared_ptr<PDFTOPDF_PageHandle> page,bool front) // {{{
495 {
496   assert(pdf);
497   auto qpage=dynamic_cast<QPDF_PDFTOPDF_PageHandle *>(page.get());
498   if (qpage) {
499     pdf->addPage(qpage->get(),front);
500   }
501 }
502 // }}}
503
504 #if 0
505   // we remove stuff now probably defunct  TODO
506   pdf->getRoot().removeKey("/PageMode");
507   pdf->getRoot().removeKey("/Outlines");
508   pdf->getRoot().removeKey("/OpenAction");
509   pdf->getRoot().removeKey("/PageLabels");
510 #endif
511
512 void QPDF_PDFTOPDF_Processor::multiply(int copies,bool collate) // {{{
513 {
514   assert(pdf);
515   assert(copies>0); 
516
517   std::vector<QPDFObjectHandle> pages=pdf->getAllPages(); // need copy
518   const int len=pages.size();
519
520   if (collate) {
521     for (int iA=1;iA<copies;iA++) {
522       for (int iB=0;iB<len;iB++) {
523         pdf->addPage(pages[iB].shallowCopy(),false);
524       }
525     }
526   } else {
527     for (int iB=0;iB<len;iB++) {
528       for (int iA=1;iA<copies;iA++) {
529         pdf->addPageAt(pages[iB].shallowCopy(),false,pages[iB]);
530       }
531     }
532   }
533 }
534 // }}}
535
536 // TODO? elsewhere?
537 void QPDF_PDFTOPDF_Processor::autoRotateAll(bool dst_lscape,Rotation normal_landscape) // {{{
538 {
539   assert(pdf);
540
541   const int len=orig_pages.size();
542   for (int iA=0;iA<len;iA++) {
543     QPDFObjectHandle page=orig_pages[iA];
544
545     Rotation src_rot=getRotate(page);
546
547     // copy'n'paste from QPDF_PDFTOPDF_PageHandle::getRect
548     PageRect ret=getBoxAsRect(getTrimBox(page));
549 //    ret.translate(-ret.left,-ret.bottom);
550     ret.rotate_move(src_rot,ret.width,ret.height);
551 //    ret.scale(getUserUnit(page));
552
553     const bool src_lscape=(ret.width>ret.height);
554     if (src_lscape!=dst_lscape) {
555       Rotation rotation=normal_landscape;
556       // TODO? other rotation direction, e.g. if (src_rot==ROT_0)&&(param.orientation==ROT_270) ... etc.
557       // rotation=ROT_270;
558
559       page.replaceOrRemoveKey("/Rotate",makeRotate(src_rot+rotation));
560     }
561   }
562 }
563 // }}}
564
565 #include "qpdf_cm.h"
566
567 // TODO
568 void QPDF_PDFTOPDF_Processor::addCM(const char *defaulticc,const char *outputicc) // {{{
569 {
570   assert(pdf);
571
572   if (hasOutputIntent(*pdf)) {
573     return; // nothing to do
574   }
575
576   QPDFObjectHandle srcicc=setDefaultICC(*pdf,defaulticc); // TODO? rename to putDefaultICC?
577   addDefaultRGB(*pdf,srcicc);
578
579   addOutputIntent(*pdf,outputicc);
580
581   hasCM=true;
582 }
583 // }}}
584
585
586 void QPDF_PDFTOPDF_Processor::setComments(const std::vector<std::string> &comments) // {{{
587 {
588   extraheader.clear();
589   const int len=comments.size();
590   for (int iA=0;iA<len;iA++) {
591     assert(comments[iA].at(0)=='%');
592     extraheader.append(comments[iA]);
593     extraheader.push_back('\n');
594   }
595 }
596 // }}}
597
598 void QPDF_PDFTOPDF_Processor::emitFile(FILE *f,ArgOwnership take) // {{{
599 {
600   if (!pdf) {
601     return;
602   }
603   QPDFWriter out(*pdf);
604   switch (take) {
605   case WillStayAlive:
606     out.setOutputFile("temp file",f,false);
607     break;
608   case TakeOwnership:
609     out.setOutputFile("temp file",f,true);
610     break;
611   case MustDuplicate:
612     error("emitFile with MustDuplicate is not supported");
613     return;
614   }
615   if (hasCM) {
616     out.setMinimumPDFVersion("1.4");
617   } else {
618     out.setMinimumPDFVersion("1.2");
619   }
620   if (!extraheader.empty()) {
621     out.setExtraHeaderText(extraheader);
622   }
623   out.write();
624 }
625 // }}}
626
627 void QPDF_PDFTOPDF_Processor::emitFilename(const char *name) // {{{
628 {
629   if (!pdf) {
630     return;
631   }
632   // special case: name==NULL -> stdout
633   QPDFWriter out(*pdf,name);
634   if (hasCM) {
635     out.setMinimumPDFVersion("1.4");
636   } else {
637     out.setMinimumPDFVersion("1.2");
638   }
639   if (!extraheader.empty()) {
640     out.setExtraHeaderText(extraheader);
641   }
642   out.write();
643 }
644 // }}}
645
646   // TODO:
647   //   loadPDF();   success?