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