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 ¶m) // {{{
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