1 //========================================================================
2 //
3 // XpdfWidgetPrint.cc
4 //
5 // Copyright 2012 Glyph & Cog, LLC
6 //
7 //========================================================================
8 
9 #if XPDFWIDGET_PRINTING
10 
11 #include <aconf.h>
12 
13 #include <stdlib.h>
14 #include <QPrinter>
15 #include "gfile.h"
16 #include "PDFDoc.h"
17 #include "ErrorCodes.h"
18 #include "XpdfWidget.h"
19 
20 #if defined(_WIN32)
21 #elif defined(__APPLE__)
22 #  include <CoreFoundation/CoreFoundation.h>
23 #  include <ApplicationServices/ApplicationServices.h>
24 #elif defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__)
25 #  include "PSOutputDev.h"
26 #  include <cups/cups.h>
27 #endif
28 
29 #include "gmempp.h"
30 
31 //------------------------------------------------------------------------
32 // Windows
33 //------------------------------------------------------------------------
34 
35 #if defined(_WIN32)
36 
37 //------------------------------------------------------------------------
38 // Mac OS X
39 //------------------------------------------------------------------------
40 
41 #elif defined(__APPLE__)
42 
printPDF(PDFDoc * doc,QPrinter * prt,int hDPI,int vDPI,XpdfWidget * widget)43 XpdfWidget::ErrorCode printPDF(PDFDoc *doc, QPrinter *prt,
44 			       int hDPI, int vDPI,
45 			       XpdfWidget *widget) {
46   GString *pdfFileName;
47   CFStringRef s;
48   CFURLRef url;
49   CGPDFDocumentRef pdfDoc;
50   CGPDFPageRef pdfPage;
51   GString *printerName;
52   CFArrayRef printerList, pageFormatList;
53   char prtName[512];
54   PMPrinter printer;
55   PMPrintSession session;
56   PMPageFormat pageFormat;
57   PMPrintSettings printSettings;
58   PMRect paperRect;
59   CGRect paperRect2;
60   CGContextRef ctx;
61   CGAffineTransform pageTransform;
62   QPrinter::ColorMode colorMode;
63   QSizeF paperSize;
64   QPrinter::PaperSource paperSource;
65   QPageLayout::Orientation pageOrientation;
66   FILE *f;
67   GBool deletePDFFile;
68   int startPage, endPage, pg, i;
69 
70   //--- get PDF file name
71 
72   deletePDFFile = gFalse;
73   if (doc->getFileName()) {
74     pdfFileName = doc->getFileName()->copy();
75   } else {
76     if (!openTempFile(&pdfFileName, &f, "wb", ".pdf")) {
77       goto err0;
78     }
79     fclose(f);
80     deletePDFFile = gTrue;
81     if (!doc->saveAs(pdfFileName)) {
82       goto err1;
83     }
84   }
85 
86   //--- load the PDF file
87 
88   s = CFStringCreateWithCString(NULL, pdfFileName->getCString(),
89 				kCFStringEncodingUTF8);
90   url = CFURLCreateWithFileSystemPath(NULL, s, kCFURLPOSIXPathStyle, false);
91   CFRelease(s);
92   pdfDoc = CGPDFDocumentCreateWithURL(url);
93   CFRelease(url);
94   if (!pdfDoc) {
95     goto err1;
96   }
97 
98   //--- get page range
99 
100   startPage = prt->fromPage();
101   endPage = prt->toPage();
102   if (startPage == 0) {
103     startPage = 1;
104   }
105   if (endPage == 0) {
106     endPage = doc->getNumPages();
107   }
108   if (startPage > endPage) {
109     CFRelease(pdfDoc);
110     if (deletePDFFile) {
111       unlink(pdfFileName->getCString());
112     }
113     delete pdfFileName;
114     return XpdfWidget::pdfErrBadPageNum;
115   }
116 
117   //--- get other parameters
118 
119   colorMode = prt->colorMode();
120   paperSize = prt->paperSize(QPrinter::Point);
121   paperSource = prt->paperSource();
122   pageOrientation = prt->pageLayout().orientation();
123 
124   //--- create the Session and PrintSettings
125 
126   if (PMCreateSession(&session)) {
127     goto err2;
128   }
129   if (PMCreatePrintSettings(&printSettings)) {
130     goto err3;
131   }
132   if (PMSessionDefaultPrintSettings(session, printSettings)) {
133     goto err4;
134   }
135   s = CFStringCreateWithCString(NULL, pdfFileName->getCString(),
136 				kCFStringEncodingUTF8);
137   PMPrintSettingsSetJobName(printSettings, s);
138   CFRelease(s);
139 
140   //--- set up for print-to-file
141 
142   if (!prt->outputFileName().isEmpty()) {
143 
144     s = CFStringCreateWithCString(NULL,
145 				  prt->outputFileName().toUtf8().constData(),
146 				  kCFStringEncodingUTF8);
147     url = CFURLCreateWithFileSystemPath(NULL, s, kCFURLPOSIXPathStyle, false);
148     CFRelease(s);
149     if (PMSessionSetDestination(session, printSettings, kPMDestinationFile,
150 				kPMDocumentFormatPDF, url)) {
151       CFRelease(url);
152       goto err4;
153     }
154     CFRelease(url);
155 
156   //--- set the printer
157 
158   } else {
159 
160     if (PMServerCreatePrinterList(kPMServerLocal, &printerList)) {
161       goto err4;
162     }
163     printer = NULL;
164     printerName = new GString(prt->printerName().toUtf8().constData());
165     for (i = 0; i < CFArrayGetCount(printerList); ++i) {
166       printer = (PMPrinter)CFArrayGetValueAtIndex(printerList, i);
167 #if QT_VERSION >= 0x050000
168       s = PMPrinterGetID(printer);
169 #else
170       s = PMPrinterGetName(printer);
171 #endif
172       if (CFStringGetCString(s, prtName, sizeof(prtName),
173 			     kCFStringEncodingUTF8)) {
174 	if (!strcmp(prtName, printerName->getCString())) {
175 	  break;
176 	}
177       }
178     }
179     delete printerName;
180     if (i >= CFArrayGetCount(printerList)) {
181       CFRelease(printerList);
182       PMRelease(printSettings);
183       PMRelease(session);
184       CFRelease(pdfDoc);
185       return XpdfWidget::pdfErrBadPrinter;
186     }
187     if (PMSessionSetCurrentPMPrinter(session, printer)) {
188       CFRelease(printerList);
189       goto err4;
190     }
191     CFRelease(printerList);
192   }
193 
194   //--- set color mode
195 
196 #if 0
197   if (colorMode == QPrinter::GrayScale) {
198     // this is deprecated, with no replacement
199     PMSetColorMode(printSettings, kPMGray);
200   }
201 #endif
202 
203   //--- set paper size
204 
205   if (PMSessionGetCurrentPrinter(session, &printer)) {
206     goto err4;
207   }
208   if (PMSessionCreatePageFormatList(session, printer, &pageFormatList)) {
209     goto err4;
210   }
211   pageFormat = NULL;
212   for (i = 0; i < CFArrayGetCount(pageFormatList); ++i) {
213     pageFormat = (PMPageFormat)CFArrayGetValueAtIndex(pageFormatList, i);
214     PMGetUnadjustedPaperRect(pageFormat, &paperRect);
215     if (fabs((paperRect.right - paperRect.left) - paperSize.width()) < 2 &&
216 	fabs((paperRect.bottom - paperRect.top) - paperSize.height()) < 2) {
217       PMRetain(pageFormat);
218       break;
219     }
220     pageFormat = NULL;
221   }
222   CFRelease(pageFormatList);
223   if (!pageFormat) {
224     if (PMCreatePageFormat(&pageFormat)) {
225       goto err4;
226     }
227     if (PMSessionDefaultPageFormat(session, pageFormat)) {
228       goto err5;
229     }
230   }
231 
232   //--- set page orientation
233 
234   PMGetAdjustedPaperRect(pageFormat, &paperRect);
235   if (pageOrientation == QPageLayout::Landscape) {
236     PMSetOrientation(pageFormat, kPMLandscape, kPMUnlocked);
237     paperRect2 = CGRectMake(paperRect.top,
238 			    paperRect.left,
239 			    paperRect.bottom - paperRect.top,
240 			    paperRect.right - paperRect.left);
241   } else {
242     PMSetOrientation(pageFormat, kPMPortrait, kPMUnlocked);
243     paperRect2 = CGRectMake(paperRect.left,
244 			    paperRect.top,
245 			    paperRect.right - paperRect.left,
246 			    paperRect.bottom - paperRect.top);
247   }
248 
249   //--- print
250 
251   if (PMSetPageRange(printSettings, startPage, endPage)) {
252     goto err5;
253   }
254   if (PMSessionBeginCGDocumentNoDialog(session, printSettings, pageFormat)) {
255     goto err5;
256   }
257   for (pg = startPage; pg <= endPage; ++pg) {
258     widget->updatePrintStatus(pg, startPage, endPage);
259     if (widget->isPrintCanceled()) {
260       goto err6;
261     }
262     if (PMSessionBeginPageNoDialog(session, pageFormat, NULL)) {
263       goto err6;
264     }
265     if (PMSessionGetCGGraphicsContext(session, &ctx)) {
266       goto err6;
267     }
268     if (!(pdfPage = CGPDFDocumentGetPage(pdfDoc, pg))) {
269       goto err6;
270     }
271     pageTransform = CGPDFPageGetDrawingTransform(pdfPage, kCGPDFMediaBox,
272 						 paperRect2, 0, true);
273     CGContextSaveGState(ctx);
274     CGContextConcatCTM(ctx, pageTransform);
275     CGContextDrawPDFPage(ctx, pdfPage);
276     CGContextRestoreGState(ctx);
277     if (PMSessionEndPageNoDialog(session)) {
278       goto err6;
279     }
280   }
281   widget->updatePrintStatus(endPage + 1, startPage, endPage);
282   PMSessionEndDocumentNoDialog(session);
283   PMRelease(pageFormat);
284   PMRelease(printSettings);
285   PMRelease(session);
286 
287   CFRelease(pdfDoc);
288   if (deletePDFFile) {
289     unlink(pdfFileName->getCString());
290   }
291   delete pdfFileName;
292 
293   return XpdfWidget::pdfOk;
294 
295  err6:
296   PMSessionEndDocumentNoDialog(session);
297  err5:
298   PMRelease(pageFormat);
299  err4:
300   PMRelease(printSettings);
301  err3:
302   PMRelease(session);
303  err2:
304   CFRelease(pdfDoc);
305  err1:
306   if (deletePDFFile) {
307     unlink(pdfFileName->getCString());
308   }
309   delete pdfFileName;
310  err0:
311   return XpdfWidget::pdfErrPrinting;
312 }
313 
314 //------------------------------------------------------------------------
315 // Linux
316 //------------------------------------------------------------------------
317 
318 #elif defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__)
319 
fileOut(void * stream,const char * data,int len)320 static void fileOut(void *stream, const char *data, int len) {
321   fwrite(data, 1, len, (FILE *)stream);
322 }
323 
printPDF(PDFDoc * doc,QPrinter * prt,int hDPI,int vDPI,XpdfWidget * widget)324 XpdfWidget::ErrorCode printPDF(PDFDoc *doc, QPrinter *prt,
325 			       int hDPI, int vDPI,
326 			       XpdfWidget *widget) {
327   int startPage, endPage;
328   QPrinter::PaperSize paperSize;
329   QSizeF paperSizePts;
330   QPrinter::PaperSource paperSource;
331   QPrinter::DuplexMode duplex;
332   GString *psFileName;
333   FILE *psFile;
334   PSOutputDev *psOut;
335   GString *printerName;
336   cups_dest_t *dests, *dest;
337   cups_option_t *options;
338   const char *paperSizeStr, *paperSourceStr;
339   GString *s;
340   int nDests, nOptions;
341   int pg;
342 
343   //--- get page range
344 
345   startPage = prt->fromPage();
346   endPage = prt->toPage();
347   if (startPage == 0) {
348     startPage = 1;
349   }
350   if (endPage == 0) {
351     endPage = doc->getNumPages();
352   }
353   if (startPage > endPage) {
354     return XpdfWidget::pdfErrBadPageNum;
355   }
356 
357   //--- get other parameters
358 
359   paperSize = prt->paperSize();
360   paperSizePts = prt->paperSize(QPrinter::Point);
361   paperSource = prt->paperSource();
362   duplex = prt->duplex();
363 
364   //--- print to file
365 
366   if (!prt->outputFileName().isEmpty()) {
367     psFileName = NULL;  // don't delete the PS file
368     if (!(psFile = fopen(prt->outputFileName().toUtf8().constData(), "wb"))) {
369       goto err0;
370     }
371 
372   //--- open temporary PS file
373 
374   } else {
375     if (!openTempFile(&psFileName, &psFile, "wb", ".ps")) {
376       goto err0;
377     }
378   }
379 
380   //--- generate the PS file
381 
382   globalParams->setPSPaperWidth((int)(paperSizePts.width() + 0.5));
383   globalParams->setPSPaperHeight((int)(paperSizePts.height() + 0.5));
384 
385   widget->updatePrintStatus(startPage, startPage, endPage);
386   psOut = new PSOutputDev(fileOut, psFile, doc, startPage, endPage,
387 			  psModePS);
388   if (!psOut->isOk()) {
389     delete psOut;
390     goto err1;
391   }
392   for (pg = startPage; pg <= endPage; ++pg) {
393     if (widget->isPrintCanceled()) {
394       delete psOut;
395       fclose(psFile);
396       goto err1;
397     }
398     doc->displayPage(psOut, pg, 72, 72, 0,
399 		     !globalParams->getPSUseCropBoxAsPage(),
400 		     gTrue, gTrue);
401     widget->updatePrintStatus(pg + 1, startPage, endPage);
402   }
403   delete psOut;
404   fclose(psFile);
405 
406   //--- print the PS file
407 
408   if (psFileName) {
409 
410     if (!prt->printerName().isEmpty()) {
411       printerName = new GString(prt->printerName().toLocal8Bit().constData());
412     } else {
413       nDests = cupsGetDests(&dests);
414       if (!(dest = cupsGetDest(NULL, NULL, nDests, dests))) {
415 	unlink(psFileName->getCString());
416 	delete psFileName;
417 	cupsFreeDests(nDests, dests);
418 	return XpdfWidget::pdfErrBadPrinter;
419       }
420       printerName = new GString(dest->name);
421       cupsFreeDests(nDests, dests);
422     }
423 
424     options = NULL;
425     nOptions = 0;
426 
427     switch (paperSize) {
428     case QPrinter::A4:      paperSizeStr = "A4";     break;
429     case QPrinter::Comm10E: paperSizeStr = "COM10";  break;
430     case QPrinter::DLE:     paperSizeStr = "DL";     break;
431     case QPrinter::Legal:   paperSizeStr = "Legal";  break;
432     case QPrinter::Letter:  paperSizeStr = "Letter"; break;
433     default:                paperSizeStr = NULL;     break;
434     }
435     switch (paperSource) {
436     case QPrinter::LargeCapacity: paperSourceStr = "LargeCapacity"; break;
437     case QPrinter::Lower:         paperSourceStr = "Lower";         break;
438     default:                      paperSourceStr = NULL;            break;
439     }
440     if (paperSizeStr && paperSourceStr) {
441       s = GString::format("{0:s},{1:s}", paperSizeStr, paperSourceStr);
442       cupsAddOption("media", s->getCString(), nOptions, &options);
443       delete s;
444       ++nOptions;
445     } else if (paperSizeStr) {
446       cupsAddOption("media", paperSizeStr, nOptions, &options);
447       ++nOptions;
448     } else if (paperSourceStr) {
449       cupsAddOption("media", paperSourceStr, nOptions, &options);
450       ++nOptions;
451     }
452 
453     switch (duplex) {
454     case QPrinter::DuplexNone:
455       cupsAddOption("sides", "one-sided", nOptions, &options);
456       ++nOptions;
457       break;
458     case QPrinter::DuplexAuto:
459       break;
460     case QPrinter::DuplexLongSide:
461       cupsAddOption("sides", "two-sided-long-edge", nOptions, &options);
462       ++nOptions;
463       break;
464     case QPrinter::DuplexShortSide:
465       cupsAddOption("sides", "two-sided-short-edge", nOptions, &options);
466       ++nOptions;
467       break;
468     }
469 
470     if (!cupsPrintFile(printerName->getCString(),
471 		       psFileName->getCString(),
472 		       doc->getFileName() ? doc->getFileName()->getCString()
473 		                          : "Xpdf printing",
474 		       nOptions, options)) {
475       cupsFreeOptions(nOptions, options);
476       delete printerName;
477       goto err1;
478     }
479     cupsFreeOptions(nOptions, options);
480 
481     delete printerName;
482 
483     unlink(psFileName->getCString());
484     delete psFileName;
485   }
486 
487   return XpdfWidget::pdfOk;
488 
489  err1:
490   if (psFileName) {
491     unlink(psFileName->getCString());
492     delete psFileName;
493   }
494  err0:
495   return XpdfWidget::pdfErrPrinting;
496 }
497 
498 #endif
499 
500 #endif // XPDFWIDGET_PRINTING
501