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