• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #include "pdftopdf_processor.h"
2 #include "qpdf_pdftopdf_processor.h"
3 #include <stdio.h>
4 #include <assert.h>
5 #include <numeric>
6 
BookletMode_dump(BookletMode bkm)7 void BookletMode_dump(BookletMode bkm) // {{{
8 {
9   static const char *bstr[3]={"Off","On","Shuffle-Only"};
10   if ((bkm<BOOKLET_OFF) || (bkm>BOOKLET_JUSTSHUFFLE)) {
11     fprintf(stderr,"(bad booklet mode: %d)",bkm);
12   } else {
13     fputs(bstr[bkm],stderr);
14   }
15 }
16 // }}}
17 
withPage(int outno) const18 bool ProcessingParameters::withPage(int outno) const // {{{
19 {
20   if (outno%2 == 0) { // 1-based
21     if (!evenPages) {
22       return false;
23     }
24   } else if (!oddPages) {
25     return false;
26   }
27   return pageRange.contains(outno);
28 }
29 // }}}
30 
dump() const31 void ProcessingParameters::dump() const // {{{
32 {
33   fprintf(stderr,"jobId: %d, numCopies: %d\n",
34 	  jobId,numCopies);
35   fprintf(stderr,"user: %s, title: %s\n",
36 	  (user)?user:"(null)",(title)?title:"(null)");
37   fprintf(stderr,"fitplot: %s\n",
38 	  (fitplot)?"true":"false");
39 
40   page.dump();
41 
42   fprintf(stderr,"Rotation(CCW): ");
43   Rotation_dump(orientation);
44   fprintf(stderr,"\n");
45 
46   fprintf(stderr,"paper_is_landscape: %s\n",
47 	  (paper_is_landscape)?"true":"false");
48 
49   fprintf(stderr,"duplex: %s\n",
50 	  (duplex)?"true":"false");
51 
52   fprintf(stderr,"Border: ");
53   BorderType_dump(border);
54   fprintf(stderr,"\n");
55 
56   nup.dump();
57 
58   fprintf(stderr,"reverse: %s\n",
59 	  (reverse)?"true":"false");
60 
61   fprintf(stderr,"evenPages: %s, oddPages: %s\n",
62 	  (evenPages)?"true":"false",
63 	  (oddPages)?"true":"false");
64 
65   fprintf(stderr,"page range: ");
66   pageRange.dump();
67 
68   fprintf(stderr,"mirror: %s\n",
69 	  (mirror)?"true":"false");
70 
71   fprintf(stderr,"Position: ");
72   Position_dump(xpos,Axis::X);
73   fprintf(stderr,"/");
74   Position_dump(ypos,Axis::Y);
75   fprintf(stderr,"\n");
76 
77   fprintf(stderr,"collate: %s\n",
78 	  (collate)?"true":"false");
79 
80   fprintf(stderr,"evenDuplex: %s\n",
81 	  (evenDuplex)?"true":"false");
82 
83   fprintf(stderr,"pageLabel: %s\n",
84 	  pageLabel.empty () ? "(none)" : pageLabel.c_str());
85 
86   fprintf(stderr,"bookletMode: ");
87   BookletMode_dump(booklet);
88   fprintf(stderr,"\nbooklet signature: %d\n",
89 	  bookSignature);
90 
91   fprintf(stderr,"autoRotate: %s\n",
92 	  (autoRotate)?"true":"false");
93 
94   fprintf(stderr,"emitJCL: %s\n",
95 	  (emitJCL)?"true":"false");
96   fprintf(stderr,"deviceCopies: %d\n",
97 	  deviceCopies);
98   fprintf(stderr,"deviceCollate: %s\n",
99 	  (deviceCollate)?"true":"false");
100   fprintf(stderr,"setDuplex: %s\n",
101 	  (setDuplex)?"true":"false");
102 }
103 // }}}
104 
105 
processor()106 PDFTOPDF_Processor *PDFTOPDF_Factory::processor()
107 {
108   return new QPDF_PDFTOPDF_Processor();
109 }
110 
111 // (1-based)
112 //   9: [*] [1] [2] [*]  [*] [3] [4] [9]  [8] [5] [6] [7]   -> signature = 12 = 3*4 = ((9+3)/4)*4
113 //       1   2   3   4    5   6   7   8    9   10  11  12
114 // NOTE: psbook always fills the sig completely (results in completely white pages (4-set), depending on the input)
115 
116 // empty pages must be added for output values >=numPages
bookletShuffle(int numPages,int signature)117 std::vector<int> bookletShuffle(int numPages,int signature) // {{{
118 {
119   if (signature<0) {
120     signature=(numPages+3)&~0x3;
121   }
122   assert(signature%4==0);
123 
124   std::vector<int> ret;
125   ret.reserve(numPages+signature-1);
126 
127   int curpage=0;
128   while (curpage<numPages) {
129     // as long as pages to be done -- i.e. multiple times the signature
130     int firstpage=curpage,
131       lastpage=curpage+signature-1;
132     // one signature
133     while (firstpage<lastpage) {
134       ret.push_back(lastpage--);
135       ret.push_back(firstpage++);
136       ret.push_back(firstpage++);
137       ret.push_back(lastpage--);
138     }
139     curpage+=signature;
140   }
141   return ret;
142 }
143 // }}}
144 
processPDFTOPDF(PDFTOPDF_Processor & proc,ProcessingParameters & param)145 bool processPDFTOPDF(PDFTOPDF_Processor &proc,ProcessingParameters &param) // {{{
146 {
147   if (!proc.check_print_permissions()) {
148     fprintf(stderr,"Not allowed to print\n");
149     return false;
150   }
151 
152   const bool dst_lscape =
153     (param.paper_is_landscape ==
154      ((param.orientation == ROT_0) || (param.orientation == ROT_180)));
155 
156   if (param.paper_is_landscape)
157     std::swap(param.nup.nupX, param.nup.nupY);
158 
159   if (param.autoRotate)
160     proc.autoRotateAll(dst_lscape,param.normal_landscape);
161 
162   std::vector<std::shared_ptr<PDFTOPDF_PageHandle>> pages=proc.get_pages();
163   const int numOrigPages=pages.size();
164 
165   // TODO FIXME? elsewhere
166   std::vector<int> shuffle;
167   if (param.booklet!=BOOKLET_OFF) {
168     shuffle=bookletShuffle(numOrigPages,param.bookSignature);
169     if (param.booklet==BOOKLET_ON) { // override options
170       // TODO? specifically "sides=two-sided-short-edge" / DuplexTumble
171       // param.duplex=true;
172       // param.setDuplex=true;  ?    currently done in setFinalPPD()
173       NupParameters::preset(2,param.nup); // TODO?! better
174     }
175   } else { // 0 1 2 3 ...
176     shuffle.resize(numOrigPages);
177     std::iota(shuffle.begin(),shuffle.end(),0);
178   }
179   const int numPages=std::max(shuffle.size(),pages.size());
180 
181   fprintf(stderr, "DEBUG: pdftopdf: \"print-scaling\" IPP attribute: %s\n",
182 	  (param.autoprint ? "auto" :
183 	   (param.autofit ? "auto-fit" :
184 	    (param.fitplot ? "fit" :
185 	     (param.fillprint ? "fill" :
186 	      (param.cropfit ? "none" :
187 	       "Not defined, should never happen"))))));
188 
189   if(param.autoprint||param.autofit){
190     bool margin_defined = true;
191     bool document_large = false;
192     int pw = param.page.right-param.page.left;
193     int ph = param.page.top-param.page.bottom;
194 
195     if ((param.page.width == pw) && (param.page.height == ph))
196       margin_defined = false;
197 
198     for (int i = 0; i < (int)pages.size(); i ++)
199     {
200       PageRect r = pages[i]->getRect();
201       int w = r.width * 100 / 102; // 2% of tolerance
202       int h = r.height * 100 / 102;
203       if ((w > param.page.width || h > param.page.height) &&
204 	  (h > param.page.width || w > param.page.height))
205       {
206 	fprintf(stderr,
207 		"DEBUG: pdftopdf: Page %d too large for output page size, scaling pages to fit.\n",
208 		i + 1);
209 	document_large = true;
210       }
211     }
212     if (param.fidelity)
213       fprintf(stderr,
214 	      "DEBUG: pdftopdf: \"ipp-attribute-fidelity\" IPP attribute is set, scaling pages to fit.\n");
215 
216     if (param.autoprint)
217     {
218       if (param.fidelity || document_large)
219       {
220         if (margin_defined)
221           param.fitplot = true;
222         else
223           param.fillprint = true;
224       }
225       else
226         param.cropfit = true;
227     }
228     else{
229       if(param.fidelity||document_large)
230         param.fitplot = true;
231       else
232         param.cropfit = true;
233     }
234   }
235 
236   fprintf(stderr, "DEBUG: pdftopdf: Print scaling mode: %s\n",
237 	  (param.fitplot ?
238 	   "Scale to fit printable area" :
239 	   (param.fillprint ?
240 	    "Scale to fill page and crop" :
241 	    (param.cropfit ?
242 	     "Do not scale, center, crop if needed" :
243 	     "Not defined, should never happen"))));
244 
245   // In Crop mode we do not scale the original document, it should keep the
246   // exact same size. With N-Up it should be scaled to fit exacly the halves,
247   // quarters, ... of the sheet, regardless of unprintable margins.
248   // Therefore we remove the unprintable margins to do all the math without
249   // them.
250   if (param.cropfit)
251   {
252     param.page.left = 0;
253     param.page.bottom = 0;
254     param.page.right = param.page.width;
255     param.page.top = param.page.height;
256   }
257 
258   if(param.fillprint||param.cropfit){
259     for(int i=0;i<(int)pages.size();i++)
260     {
261       std::shared_ptr<PDFTOPDF_PageHandle> page = pages[i];
262       Rotation orientation;
263       if (page->is_landscape(param.orientation))
264 	orientation = param.normal_landscape;
265       else
266 	orientation = ROT_0;
267       page->crop(param.page, orientation, param.orientation,
268 		 param.xpos, param.ypos,
269 		 !param.cropfit, param.autoRotate);
270     }
271     if (param.fillprint)
272       param.fitplot = true;
273   }
274 
275   std::shared_ptr<PDFTOPDF_PageHandle> curpage;
276   int outputpage=0;
277   int outputno=0;
278 
279   if ((param.nup.nupX == 1) && (param.nup.nupY == 1) && !param.fitplot)
280   {
281     param.nup.width = param.page.width;
282     param.nup.height = param.page.height;
283   }
284   else
285   {
286     param.nup.width = param.page.right - param.page.left;
287     param.nup.height = param.page.top - param.page.bottom;
288   }
289 
290   if ((param.orientation == ROT_90) || (param.orientation == ROT_270))
291   {
292     std::swap(param.nup.nupX, param.nup.nupY);
293     param.nup.landscape = !param.nup.landscape;
294     param.orientation = param.orientation - param.normal_landscape;
295   }
296 
297   double xpos = 0, ypos = 0;
298   if (param.nup.landscape)
299   {
300     // pages[iA]->rotate(param.normal_landscape);
301     param.orientation = param.orientation + param.normal_landscape;
302     // TODO? better
303     if (param.nup.nupX != 1 || param.nup.nupY != 1 || param.fitplot)
304     {
305       xpos = param.page.height - param.page.top;
306       ypos = param.page.left;
307     }
308     std::swap(param.page.width, param.page.height);
309     std::swap(param.nup.width, param.nup.height);
310   }
311   else
312   {
313     if (param.nup.nupX != 1 || param.nup.nupY != 1 || param.fitplot)
314     {
315       xpos = param.page.left;
316       ypos = param.page.bottom; // for whole page... TODO from position...
317     }
318   }
319 
320   NupState nupstate(param.nup);
321   NupPageEdit pgedit;
322   for (int iA=0;iA<numPages;iA++) {
323     std::shared_ptr<PDFTOPDF_PageHandle> page;
324     if (shuffle[iA] >= numOrigPages)
325       // add empty page as filler
326       page=proc.new_page(param.page.width,param.page.height);
327     else
328       page=pages[shuffle[iA]];
329 
330     PageRect rect;
331     rect = page->getRect();
332     //rect.dump();
333 
334     bool newPage=nupstate.nextPage(rect.width,rect.height,pgedit);
335     if (newPage) {
336       if ((curpage)&&(param.withPage(outputpage))) {
337 	curpage->rotate(param.orientation);
338 	if (param.mirror)
339 	  curpage->mirror();
340 	// TODO? update rect? --- not needed any more
341 	proc.add_page(curpage,param.reverse); // reverse -> insert at beginning
342 	// Log page in /var/log/cups/page_log
343 	outputno++;
344 	if (param.page_logging == 1)
345 	  fprintf(stderr, "PAGE: %d %d\n", outputno,
346 		  param.copies_to_be_logged);
347       }
348       curpage=proc.new_page(param.page.width,param.page.height);
349       outputpage++;
350     }
351     if (shuffle[iA]>=numOrigPages) {
352       continue;
353     }
354 
355     if (param.border!=BorderType::NONE) {
356       // TODO FIXME... border gets cutted away, if orignal page had wrong size
357       // page->"uncrop"(rect);  // page->setMedia()
358       // Note: currently "fixed" in add_subpage(...&rect);
359       page->add_border_rect(rect,param.border,1.0/pgedit.scale);
360     }
361 
362     if (!param.pageLabel.empty()) {
363       page->add_label(param.page, param.pageLabel);
364     }
365 
366     if (param.cropfit)
367     {
368       if ((param.nup.nupX == 1) && (param.nup.nupY == 1))
369       {
370 	double xpos2, ypos2;
371 	if ((param.page.height - param.page.width) *
372 	    (page->getRect().height - page->getRect().width) < 0)
373 	{
374 	  xpos2 = (param.page.width - (page->getRect().height)) / 2;
375 	  ypos2 = (param.page.height - (page->getRect().width)) / 2;
376 	  curpage->add_subpage(page, ypos2 + xpos, xpos2 + ypos, 1);
377 	}
378 	else
379 	{
380 	  xpos2 = (param.page.width - (page->getRect().width)) / 2;
381 	  ypos2 = (param.page.height - (page->getRect().height)) / 2;
382 	  curpage->add_subpage(page, xpos2 + xpos, ypos2 + ypos, 1);
383 	}
384       }
385       else
386 	curpage->add_subpage(page,pgedit.xpos+xpos,pgedit.ypos+ypos,pgedit.scale);
387     }
388     else
389       curpage->add_subpage(page, pgedit.xpos + xpos, pgedit.ypos + ypos,
390 			   pgedit.scale);
391 
392 #ifdef DEBUG
393     if (auto dbg=dynamic_cast<QPDF_PDFTOPDF_PageHandle *>(curpage.get())) {
394       dbg->debug(pgedit.sub,xpos,ypos);
395     }
396 #endif
397 
398     // pgedit.dump();
399   }
400   if ((curpage)&&(param.withPage(outputpage))) {
401     curpage->rotate(param.orientation);
402     if (param.mirror) {
403       curpage->mirror();
404     }
405     proc.add_page(curpage,param.reverse); // reverse -> insert at beginning
406     // Log page in /var/log/cups/page_log
407     outputno ++;
408     if (param.page_logging == 1)
409       fprintf(stderr, "PAGE: %d %d\n", outputno, param.copies_to_be_logged);
410   }
411 
412   if ((param.evenDuplex || !param.oddPages) && (outputno & 1)) {
413     // need to output empty page to not confuse duplex
414     proc.add_page(proc.new_page(param.page.width,param.page.height),param.reverse);
415     // Log page in /var/log/cups/page_log
416     if (param.page_logging == 1)
417       fprintf(stderr, "PAGE: %d %d\n", outputno + 1, param.copies_to_be_logged);
418   }
419 
420   proc.multiply(param.numCopies,param.collate);
421 
422   return true;
423 }
424 // }}}
425