1 #include "qpdf_pdftopdf_processor.h"
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"
12 // Use: content.append(debug_box(pe.sub,xpos,ypos));
13 static std::string debug_box(const PageRect &box,float xshift,float yshift) // {{{
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 "+
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 "+
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";
27 QPDF_PDFTOPDF_PageHandle::QPDF_PDFTOPDF_PageHandle(QPDFObjectHandle page,int orig_no) // {{{
35 QPDF_PDFTOPDF_PageHandle::QPDF_PDFTOPDF_PageHandle(QPDF *pdf,float width,float height) // {{{
40 page=QPDFObjectHandle::parse(
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
54 page=pdf->makeIndirectObject(page); // stores *pdf
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 // {{{
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));
70 bool QPDF_PDFTOPDF_PageHandle::isExisting() const // {{{
72 page.assertInitialized();
73 return content.empty();
77 QPDFObjectHandle QPDF_PDFTOPDF_PageHandle::get() // {{{
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));
86 Rotation rot=getRotate(page)+rotation;
87 page.replaceOrRemoveKey("/Rotate",makeRotate(rot));
89 page=QPDFObjectHandle(); // i.e. uninitialized
94 // TODO: we probably need a function "ungetRect()" to transform to page/form space
96 static PageRect ungetRect(PageRect rect,const QPDF_PDFTOPDF_PageHandle &ph,Rotation rotation,QPDFObjectHandle page)
98 PageRect pg1=ph.getRect();
99 PageRect pg2=getBoxAsRect(getTrimBox(page));
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);
109 rect.rotate_move(-getRotate(page),pg1.width,pg1.height);
110 rect.scale(1.0/getUserUnit(page));
112 // PageRect pg2=getBoxAsRect(getTrimBox(page));
113 rect.translate(pg2.left,pg2.bottom);
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) // {{{
123 assert(isExisting());
124 assert(border!=BorderType::NONE);
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...
133 PageRect rect=ungetRect(_rect,*this,rotation,page);
135 assert(rect.left<=rect.right);
136 assert(rect.bottom<=rect.top);
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";
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";
149 // if (!isExisting()) {
150 // // TODO: only after
154 assert(page.getOwningQPDF()); // existing pages are always indirect
155 #ifdef DEBUG // draw it on top
156 static const char *pre="%pdftopdf q\n"
158 *post="%pdftopdf Q\n"
161 QPDFObjectHandle stm1=QPDFObjectHandle::newStream(page.getOwningQPDF(),pre),
162 stm2=QPDFObjectHandle::newStream(page.getOwningQPDF(),std::string(post)+boxcmd);
164 page.addPageContents(stm1,true); // before
165 page.addPageContents(stm2,false); // after
167 QPDFObjectHandle stm=QPDFObjectHandle::newStream(page.getOwningQPDF(),boxcmd);
168 page.addPageContents(stm,true); // before
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) // {{{
177 auto qsub=dynamic_cast<QPDF_PDFTOPDF_PageHandle *>(sub.get());
180 std::string xoname="/X"+QUtil::int_to_string((qsub->no!=-1)?qsub->no:++no);
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?)
188 // TODO: we need to obey page./Rotate
189 if (pg.width<tmp.width) {
190 pg.right=pg.left+tmp.width;
192 if (pg.height<tmp.height) {
193 pg.top=pg.bottom+tmp.height;
196 PageRect rect=ungetRect(pg,*qsub,ROT_0,qsub->page);
198 qsub->page.replaceKey("/TrimBox",makeBox(rect.left,rect.bottom,rect.right,rect.top));
199 // TODO? do everything for cropping here?
201 xobjs[xoname]=makeXObject(qsub->page.getOwningQPDF(),qsub->page); // trick: should be the same as page->getOwningQPDF() [only after it's made indirect]
204 mtx.translate(xpos,ypos);
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);
212 content.append("q\n ");
213 content.append(mtx.get_string()+" cm\n ");
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 ");
218 content.append(xoname+" Do\n");
219 content.append("Q\n");
223 void QPDF_PDFTOPDF_PageHandle::mirror() // {{{
225 PageRect orig=getRect();
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);
232 QPDFObjectHandle subpage=get(); // this->page, with rotation
234 // replace all our data
235 *this=QPDF_PDFTOPDF_PageHandle(subpage.getOwningQPDF(),orig.width,orig.height);
237 xobjs[xoname]=makeXObject(subpage.getOwningQPDF(),subpage); // we can only now set this->xobjs
239 // content.append(std::string("1 0 0 1 0 0 cm\n ");
240 content.append(xoname+" Do\n");
242 assert(!isExisting());
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");
250 content.insert(0,std::string(pre)+mrcmd);
254 void QPDF_PDFTOPDF_PageHandle::rotate(Rotation rot) // {{{
256 rotation=rot; // "rotation += rot;" ?
260 void QPDF_PDFTOPDF_PageHandle::debug(const PageRect &rect,float xpos,float ypos) // {{{
262 assert(!isExisting());
263 content.append(debug_box(rect,xpos,ypos));
268 void QPDF_PDFTOPDF_Processor::closeFile() // {{{
275 void QPDF_PDFTOPDF_Processor::error(const char *fmt,...) // {{{
280 vfprintf(stderr,fmt,ap);
286 // TODO? try/catch for PDF parsing errors?
288 bool QPDF_PDFTOPDF_Processor::loadFile(FILE *f,ArgOwnership take) // {{{
292 throw std::invalid_argument("loadFile(NULL,...) not allowed");
297 if (take==TakeOwnership) {
305 pdf->processFile("temp file",f,false);
306 } catch (const std::exception &e) {
307 error("loadFile failed: %s",e.what());
313 pdf->processFile("temp file",f,true);
314 } catch (const std::exception &e) {
315 error("loadFile failed: %s",e.what());
320 error("loadFile with MustDuplicate is not supported");
328 bool QPDF_PDFTOPDF_Processor::loadFilename(const char *name) // {{{
333 pdf->processFile(name);
334 } catch (const std::exception &e) {
335 error("loadFilename failed: %s",e.what());
344 void QPDF_PDFTOPDF_Processor::start() // {{{
348 pdf->pushInheritedAttributesToPage();
349 orig_pages=pdf->getAllPages();
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]);
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");
365 bool QPDF_PDFTOPDF_Processor::check_print_permissions() // {{{
368 error("No PDF loaded");
371 return pdf->allowPrintHighRes() || pdf->allowPrintLowRes(); // from legacy pdftopdf
376 std::vector<std::shared_ptr<PDFTOPDF_PageHandle>> QPDF_PDFTOPDF_Processor::get_pages() // {{{
378 std::vector<std::shared_ptr<PDFTOPDF_PageHandle>> ret;
380 error("No PDF loaded");
384 const int len=orig_pages.size();
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)));
393 std::shared_ptr<PDFTOPDF_PageHandle> QPDF_PDFTOPDF_Processor::new_page(float width,float height) // {{{
396 error("No PDF loaded");
398 return std::shared_ptr<PDFTOPDF_PageHandle>();
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
405 void QPDF_PDFTOPDF_Processor::add_page(std::shared_ptr<PDFTOPDF_PageHandle> page,bool front) // {{{
408 auto qpage=dynamic_cast<QPDF_PDFTOPDF_PageHandle *>(page.get());
410 pdf->addPage(qpage->get(),front);
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");
423 void QPDF_PDFTOPDF_Processor::multiply(int copies,bool collate) // {{{
428 std::vector<QPDFObjectHandle> pages=pdf->getAllPages(); // need copy
429 const int len=pages.size();
432 for (int iA=1;iA<copies;iA++) {
433 for (int iB=0;iB<len;iB++) {
434 pdf->addPage(pages[iB].shallowCopy(),false);
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]);
448 void QPDF_PDFTOPDF_Processor::autoRotateAll(bool dst_lscape,Rotation normal_landscape) // {{{
452 const int len=orig_pages.size();
453 for (int iA=0;iA<len;iA++) {
454 QPDFObjectHandle page=orig_pages[iA];
456 Rotation src_rot=getRotate(page);
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));
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.
470 page.replaceOrRemoveKey("/Rotate",makeRotate(src_rot+rotation));
479 void QPDF_PDFTOPDF_Processor::addCM(const char *defaulticc,const char *outputicc) // {{{
483 if (hasOutputIntent(*pdf)) {
484 return; // nothing to do
487 QPDFObjectHandle srcicc=setDefaultICC(*pdf,defaulticc);
488 addDefaultRGB(*pdf,srcicc);
490 addOutputIntent(*pdf,outputicc);
497 void QPDF_PDFTOPDF_Processor::setComments(const std::vector<std::string> &comments) // {{{
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');
509 void QPDF_PDFTOPDF_Processor::emitFile(FILE *f,ArgOwnership take) // {{{
514 QPDFWriter out(*pdf);
517 out.setOutputFile("temp file",f,false);
520 out.setOutputFile("temp file",f,true);
523 error("emitFile with MustDuplicate is not supported");
527 out.setMinimumPDFVersion("1.4");
529 out.setMinimumPDFVersion("1.2");
531 if (!extraheader.empty()) {
532 out.setExtraHeaderText(extraheader);
538 void QPDF_PDFTOPDF_Processor::emitFilename(const char *name) // {{{
543 // special case: name==NULL -> stdout
544 QPDFWriter out(*pdf,name);
546 out.setMinimumPDFVersion("1.4");
548 out.setMinimumPDFVersion("1.2");
550 if (!extraheader.empty()) {
551 out.setExtraHeaderText(extraheader);
558 // loadPDF(); success?