Tizen 2.1 base
[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::debug(const PageRect &rect,float xpos,float ypos) // {{{
261 {
262   assert(!isExisting());
263   content.append(debug_box(rect,xpos,ypos));
264 }
265 // }}}
266
267
268 void QPDF_PDFTOPDF_Processor::closeFile() // {{{
269 {
270   pdf.reset();
271   hasCM=false;
272 }
273 // }}}
274
275 void QPDF_PDFTOPDF_Processor::error(const char *fmt,...) // {{{
276 {
277   va_list ap;
278
279   va_start(ap,fmt);
280   vfprintf(stderr,fmt,ap);
281   fputs("\n",stderr);
282   va_end(ap);
283 }
284 // }}}
285
286 // TODO?  try/catch for PDF parsing errors?
287
288 bool QPDF_PDFTOPDF_Processor::loadFile(FILE *f,ArgOwnership take) // {{{
289 {
290   closeFile();
291   if (!f) {
292     throw std::invalid_argument("loadFile(NULL,...) not allowed");
293   }
294   try {
295     pdf.reset(new QPDF);
296   } catch (...) {
297     if (take==TakeOwnership) {
298       fclose(f);
299     }
300     throw;
301   }
302   switch (take) {
303   case WillStayAlive:
304     try {
305       pdf->processFile("temp file",f,false);
306     } catch (const std::exception &e) {
307       error("loadFile failed: %s",e.what());
308       return false;
309     }
310     break;
311   case TakeOwnership:
312     try {
313       pdf->processFile("temp file",f,true);
314     } catch (const std::exception &e) {
315       error("loadFile failed: %s",e.what());
316       return false;
317     }
318     break;
319   case MustDuplicate:
320     error("loadFile with MustDuplicate is not supported");
321     return false;
322   }
323   start();
324   return true;
325 }
326 // }}}
327
328 bool QPDF_PDFTOPDF_Processor::loadFilename(const char *name) // {{{
329 {
330   closeFile();
331   try {
332     pdf.reset(new QPDF);
333     pdf->processFile(name);
334   } catch (const std::exception &e) {
335     error("loadFilename failed: %s",e.what());
336     return false;
337   }
338   start();
339   return true;
340 }
341 // }}}
342
343
344 void QPDF_PDFTOPDF_Processor::start() // {{{
345 {
346   assert(pdf);
347
348   pdf->pushInheritedAttributesToPage();
349   orig_pages=pdf->getAllPages();
350
351   // remove them (just unlink, data still there)
352   const int len=orig_pages.size();
353   for (int iA=0;iA<len;iA++) {
354     pdf->removePage(orig_pages[iA]);
355   }
356
357   // we remove stuff that becomes defunct (probably)  TODO
358   pdf->getRoot().removeKey("/PageMode");
359   pdf->getRoot().removeKey("/Outlines");
360   pdf->getRoot().removeKey("/OpenAction");
361   pdf->getRoot().removeKey("/PageLabels");
362 }
363 // }}}
364
365 bool QPDF_PDFTOPDF_Processor::check_print_permissions() // {{{
366 {
367   if (!pdf) {
368     error("No PDF loaded");
369     return false;
370   }
371   return pdf->allowPrintHighRes() || pdf->allowPrintLowRes(); // from legacy pdftopdf
372 }
373 // }}}
374
375
376 std::vector<std::shared_ptr<PDFTOPDF_PageHandle>> QPDF_PDFTOPDF_Processor::get_pages() // {{{
377 {
378   std::vector<std::shared_ptr<PDFTOPDF_PageHandle>> ret;
379   if (!pdf) {
380     error("No PDF loaded");
381     assert(0);
382     return ret;
383   }
384   const int len=orig_pages.size();
385   ret.reserve(len);
386   for (int iA=0;iA<len;iA++) {
387     ret.push_back(std::shared_ptr<PDFTOPDF_PageHandle>(new QPDF_PDFTOPDF_PageHandle(orig_pages[iA],iA+1)));
388   }
389   return ret;
390 }
391 // }}}
392
393 std::shared_ptr<PDFTOPDF_PageHandle> QPDF_PDFTOPDF_Processor::new_page(float width,float height) // {{{
394 {
395   if (!pdf) {
396     error("No PDF loaded");
397     assert(0);
398     return std::shared_ptr<PDFTOPDF_PageHandle>();
399   }
400   return std::shared_ptr<QPDF_PDFTOPDF_PageHandle>(new QPDF_PDFTOPDF_PageHandle(pdf.get(),width,height));
401   // return std::make_shared<QPDF_PDFTOPDF_PageHandle>(pdf.get(),width,height);  // problem: make_shared not friend
402 }
403 // }}}
404
405 void QPDF_PDFTOPDF_Processor::add_page(std::shared_ptr<PDFTOPDF_PageHandle> page,bool front) // {{{
406 {
407   assert(pdf);
408   auto qpage=dynamic_cast<QPDF_PDFTOPDF_PageHandle *>(page.get());
409   if (qpage) {
410     pdf->addPage(qpage->get(),front);
411   }
412 }
413 // }}}
414
415 #if 0
416   // we remove stuff now probably defunct  TODO
417   pdf->getRoot().removeKey("/PageMode");
418   pdf->getRoot().removeKey("/Outlines");
419   pdf->getRoot().removeKey("/OpenAction");
420   pdf->getRoot().removeKey("/PageLabels");
421 #endif
422
423 void QPDF_PDFTOPDF_Processor::multiply(int copies,bool collate) // {{{
424 {
425   assert(pdf);
426   assert(copies>0); 
427
428   std::vector<QPDFObjectHandle> pages=pdf->getAllPages(); // need copy
429   const int len=pages.size();
430
431   if (collate) {
432     for (int iA=1;iA<copies;iA++) {
433       for (int iB=0;iB<len;iB++) {
434         pdf->addPage(pages[iB].shallowCopy(),false);
435       }
436     }
437   } else {
438     for (int iB=0;iB<len;iB++) {
439       for (int iA=1;iA<copies;iA++) {
440         pdf->addPageAt(pages[iB].shallowCopy(),false,pages[iB]);
441       }
442     }
443   }
444 }
445 // }}}
446
447 // TODO? elsewhere?
448 void QPDF_PDFTOPDF_Processor::autoRotateAll(bool dst_lscape,Rotation normal_landscape) // {{{
449 {
450   assert(pdf);
451
452   const int len=orig_pages.size();
453   for (int iA=0;iA<len;iA++) {
454     QPDFObjectHandle page=orig_pages[iA];
455
456     Rotation src_rot=getRotate(page);
457
458     // copy'n'paste from QPDF_PDFTOPDF_PageHandle::getRect
459     PageRect ret=getBoxAsRect(getTrimBox(page));
460 //    ret.translate(-ret.left,-ret.bottom);
461     ret.rotate_move(src_rot,ret.width,ret.height);
462 //    ret.scale(getUserUnit(page));
463
464     const bool src_lscape=(ret.width>ret.height);
465     if (src_lscape!=dst_lscape) {
466       Rotation rotation=normal_landscape;
467       // TODO? other rotation direction, e.g. if (src_rot==ROT_0)&&(param.orientation==ROT_270) ... etc.
468       // rotation=ROT_270;
469
470       page.replaceOrRemoveKey("/Rotate",makeRotate(src_rot+rotation));
471     }
472   }
473 }
474 // }}}
475
476 #include "qpdf_cm.h"
477
478 // TODO
479 void QPDF_PDFTOPDF_Processor::addCM(const char *defaulticc,const char *outputicc) // {{{
480 {
481   assert(pdf);
482
483   if (hasOutputIntent(*pdf)) {
484     return; // nothing to do
485   }
486
487   QPDFObjectHandle srcicc=setDefaultICC(*pdf,defaulticc);
488   addDefaultRGB(*pdf,srcicc);
489
490   addOutputIntent(*pdf,outputicc);
491
492   hasCM=true;
493 }
494 // }}}
495
496
497 void QPDF_PDFTOPDF_Processor::setComments(const std::vector<std::string> &comments) // {{{
498 {
499   extraheader.clear();
500   const int len=comments.size();
501   for (int iA=0;iA<len;iA++) {
502     assert(comments[iA].at(0)=='%');
503     extraheader.append(comments[iA]);
504     extraheader.push_back('\n');
505   }
506 }
507 // }}}
508
509 void QPDF_PDFTOPDF_Processor::emitFile(FILE *f,ArgOwnership take) // {{{
510 {
511   if (!pdf) {
512     return;
513   }
514   QPDFWriter out(*pdf);
515   switch (take) {
516   case WillStayAlive:
517     out.setOutputFile("temp file",f,false);
518     break;
519   case TakeOwnership:
520     out.setOutputFile("temp file",f,true);
521     break;
522   case MustDuplicate:
523     error("emitFile with MustDuplicate is not supported");
524     return;
525   }
526   if (hasCM) {
527     out.setMinimumPDFVersion("1.4");
528   } else {
529     out.setMinimumPDFVersion("1.2");
530   }
531   if (!extraheader.empty()) {
532     out.setExtraHeaderText(extraheader);
533   }
534   out.write();
535 }
536 // }}}
537
538 void QPDF_PDFTOPDF_Processor::emitFilename(const char *name) // {{{
539 {
540   if (!pdf) {
541     return;
542   }
543   // special case: name==NULL -> stdout
544   QPDFWriter out(*pdf,name);
545   if (hasCM) {
546     out.setMinimumPDFVersion("1.4");
547   } else {
548     out.setMinimumPDFVersion("1.2");
549   }
550   if (!extraheader.empty()) {
551     out.setExtraHeaderText(extraheader);
552   }
553   out.write();
554 }
555 // }}}
556
557   // TODO:
558   //   loadPDF();   success?