1 //========================================================================
2 //
3 // QtPDFCore.cc
4 //
5 // Copyright 2009-2014 Glyph & Cog, LLC
6 //
7 //========================================================================
8 
9 #include <aconf.h>
10 
11 #ifdef USE_GCC_PRAGMAS
12 #pragma implementation
13 #endif
14 
15 #include <math.h>
16 #include <string.h>
17 #include <QApplication>
18 #include <QClipboard>
19 #include <QDesktopServices>
20 #include <QFileInfo>
21 #include <QImage>
22 #include <QInputDialog>
23 #include <QMessageBox>
24 #include <QPainter>
25 #include <QProcess>
26 #include <QScrollBar>
27 #include <QStyle>
28 #include <QUrl>
29 #include <QWidget>
30 #include "gmem.h"
31 #include "gmempp.h"
32 #include "gfile.h"
33 #include "GString.h"
34 #include "GList.h"
35 #include "Error.h"
36 #include "GlobalParams.h"
37 #include "PDFDoc.h"
38 #include "Link.h"
39 #include "ErrorCodes.h"
40 #include "GfxState.h"
41 #include "PSOutputDev.h"
42 #include "TextOutputDev.h"
43 #include "SplashBitmap.h"
44 #include "DisplayState.h"
45 #include "TileMap.h"
46 #include "QtPDFCore.h"
47 
48 //------------------------------------------------------------------------
49 // QtPDFCore
50 //------------------------------------------------------------------------
51 
QtPDFCore(QWidget * viewportA,QScrollBar * hScrollBarA,QScrollBar * vScrollBarA,SplashColorPtr paperColor,SplashColorPtr matteColor,GBool reverseVideo)52 QtPDFCore::QtPDFCore(QWidget *viewportA,
53 		     QScrollBar *hScrollBarA, QScrollBar *vScrollBarA,
54 		     SplashColorPtr paperColor, SplashColorPtr matteColor,
55 		     GBool reverseVideo):
56   PDFCore(splashModeRGB8, 4, reverseVideo, paperColor)
57 {
58   int dpiX, dpiY;
59 
60   viewport = viewportA;
61   hScrollBar = hScrollBarA;
62   vScrollBar = vScrollBarA;
63   hScrollBar->setRange(0, 0);
64   hScrollBar->setSingleStep(16);
65   vScrollBar->setRange(0, 0);
66   vScrollBar->setSingleStep(16);
67   viewport->setMouseTracking(true);
68 
69   state->setMatteColor(matteColor);
70 
71   oldFirstPage = -1;
72   oldMidPage = -1;
73 
74   linkAction = NULL;
75   lastLinkAction = NULL;
76 
77   dragging = gFalse;
78 
79   panning = gFalse;
80 
81   inUpdateScrollbars = gFalse;
82 
83   updateCbk = NULL;
84   midPageChangedCbk = NULL;
85   preLoadCbk = NULL;
86   postLoadCbk = NULL;
87   actionCbk = NULL;
88   linkCbk = NULL;
89   selectDoneCbk = NULL;
90 
91   // optional features default to on
92   hyperlinksEnabled = gTrue;
93   externalHyperlinksEnabled = gTrue;
94   selectEnabled = gTrue;
95   panEnabled = gTrue;
96   showPasswordDialog = gTrue;
97 
98   // get Qt's HiDPI scale factor
99 #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
100   scaleFactor = viewport->devicePixelRatioF();
101 #elif QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
102   scaleFactor = viewport->devicePixelRatio();
103 #else
104   scaleFactor = 1;
105 #endif
106 
107   // get the display resolution (used for HiDPI scaling)
108   dpiX = viewport->logicalDpiX();
109   dpiY = viewport->logicalDpiY();
110   displayDpi = dpiX < dpiY ? dpiX : dpiY;
111   displayDpi = (int)(displayDpi * scaleFactor);
112 }
113 
~QtPDFCore()114 QtPDFCore::~QtPDFCore() {
115 }
116 
117 //------------------------------------------------------------------------
118 // loadFile / displayPage / displayDest
119 //------------------------------------------------------------------------
120 
loadFile(GString * fileName,GString * ownerPassword,GString * userPassword)121 int QtPDFCore::loadFile(GString *fileName, GString *ownerPassword,
122 			GString *userPassword) {
123   int err;
124 
125   err = PDFCore::loadFile(fileName, ownerPassword, userPassword);
126   if (err == errNone) {
127     // save the modification time
128     modTime = QFileInfo(doc->getFileName()->getCString()).lastModified();
129 
130     // update the parent window
131     if (updateCbk) {
132       (*updateCbk)(updateCbkData, doc->getFileName(), -1,
133 		   doc->getNumPages(), NULL);
134     }
135     oldFirstPage = oldMidPage = -1;
136   }
137   return err;
138 }
139 
140 #ifdef _WIN32
loadFile(wchar_t * fileName,int fileNameLen,GString * ownerPassword,GString * userPassword)141 int QtPDFCore::loadFile(wchar_t *fileName, int fileNameLen,
142 			GString *ownerPassword,
143 			GString *userPassword) {
144   int err;
145 
146   err = PDFCore::loadFile(fileName, fileNameLen, ownerPassword, userPassword);
147   if (err == errNone) {
148     // save the modification time
149     modTime = QFileInfo(doc->getFileName()->getCString()).lastModified();
150 
151     // update the parent window
152     if (updateCbk) {
153       (*updateCbk)(updateCbkData, doc->getFileName(), -1,
154 		   doc->getNumPages(), NULL);
155     }
156     oldFirstPage = oldMidPage = -1;
157   }
158   return err;
159 }
160 #endif
161 
loadFile(BaseStream * stream,GString * ownerPassword,GString * userPassword)162 int QtPDFCore::loadFile(BaseStream *stream, GString *ownerPassword,
163 			GString *userPassword) {
164   int err;
165 
166   err = PDFCore::loadFile(stream, ownerPassword, userPassword);
167   if (err == errNone) {
168     // no file
169     modTime = QDateTime();
170 
171     // update the parent window
172     if (updateCbk) {
173       (*updateCbk)(updateCbkData, doc->getFileName(), -1,
174 		   doc->getNumPages(), NULL);
175     }
176     oldFirstPage = oldMidPage = -1;
177   }
178   return err;
179 }
180 
loadDoc(PDFDoc * docA)181 void QtPDFCore::loadDoc(PDFDoc *docA) {
182   PDFCore::loadDoc(docA);
183 
184   // save the modification time
185   if (doc->getFileName()) {
186     modTime = QFileInfo(doc->getFileName()->getCString()).lastModified();
187   } else {
188     modTime = QDateTime();
189   }
190 
191   // update the parent window
192   if (updateCbk) {
193     (*updateCbk)(updateCbkData, doc->getFileName(), -1,
194 		 doc->getNumPages(), NULL);
195   }
196   oldFirstPage = oldMidPage = -1;
197 }
198 
reload()199 int QtPDFCore::reload() {
200   int err;
201 
202   err = PDFCore::reload();
203   if (err == errNone) {
204     // save the modification time
205     modTime = QFileInfo(doc->getFileName()->getCString()).lastModified();
206 
207     // update the parent window
208     if (updateCbk) {
209       (*updateCbk)(updateCbkData, doc->getFileName(), -1,
210 		   doc->getNumPages(), NULL);
211     }
212     oldFirstPage = oldMidPage = -1;
213   }
214   return err;
215 }
216 
finishUpdate(GBool addToHist,GBool checkForChangedFile)217 void QtPDFCore::finishUpdate(GBool addToHist, GBool checkForChangedFile) {
218   int firstPage, midPage;
219 
220   PDFCore::finishUpdate(addToHist, checkForChangedFile);
221   firstPage = getPageNum();
222   if (doc && firstPage != oldFirstPage && updateCbk) {
223     (*updateCbk)(updateCbkData, NULL, firstPage, -1, "");
224   }
225   oldFirstPage = firstPage;
226   midPage = getMidPageNum();
227   if (doc && midPage != oldMidPage && midPageChangedCbk) {
228     (*midPageChangedCbk)(midPageChangedCbkData, midPage);
229   }
230   oldMidPage = midPage;
231 
232   linkAction = NULL;
233   lastLinkAction = NULL;
234 }
235 
236 //------------------------------------------------------------------------
237 // panning and selection
238 //------------------------------------------------------------------------
239 
startPan(int wx,int wy)240 void QtPDFCore::startPan(int wx, int wy) {
241   if (!panEnabled) {
242     return;
243   }
244   panning = gTrue;
245   panMX = wx;
246   panMY = wy;
247 }
248 
endPan(int wx,int wy)249 void QtPDFCore::endPan(int wx, int wy) {
250   panning = gFalse;
251 }
252 
startSelection(int wx,int wy,GBool extend)253 void QtPDFCore::startSelection(int wx, int wy, GBool extend) {
254   int pg, x, y;
255 
256   takeFocus();
257   if (!doc || doc->getNumPages() == 0 || !selectEnabled) {
258     return;
259   }
260   if (!cvtWindowToDev(wx, wy, &pg, &x, &y)) {
261     return;
262   }
263     if (extend && hasSelection()) {
264       moveSelectionDrag(pg, x, y);
265     } else {
266       startSelectionDrag(pg, x, y);
267     }
268     if (getSelectMode() == selectModeBlock) {
269       doSetCursor(Qt::CrossCursor);
270     }
271     dragging = gTrue;
272 }
273 
endSelection(int wx,int wy)274 void QtPDFCore::endSelection(int wx, int wy) {
275   LinkAction *action;
276   int pg, x, y;
277   double xu, yu;
278   GBool ok;
279 
280   if (!doc || doc->getNumPages() == 0) {
281     return;
282   }
283   ok = cvtWindowToDev(wx, wy, &pg, &x, &y);
284   if (dragging) {
285     dragging = gFalse;
286     doUnsetCursor();
287     if (ok) {
288       moveSelectionDrag(pg, x, y);
289     }
290     finishSelectionDrag();
291     if (selectDoneCbk) {
292       (*selectDoneCbk)(selectDoneCbkData);
293     }
294 #ifndef NO_TEXT_SELECT
295     if (hasSelection()) {
296       copySelection(gFalse);
297     }
298 #endif
299   }
300   if (ok) {
301     if (hasSelection()) {
302       action = NULL;
303     } else {
304       cvtDevToUser(pg, x, y, &xu, &yu);
305       action = findLink(pg, xu, yu);
306     }
307     if (linkCbk && action) {
308       doLinkCbk(action);
309     }
310     if (hyperlinksEnabled && action) {
311       doAction(action);
312     }
313   }
314 }
315 
mouseMove(int wx,int wy)316 void QtPDFCore::mouseMove(int wx, int wy) {
317   LinkAction *action;
318   int pg, x, y;
319   double xu, yu;
320   const char *s;
321   GBool ok, mouseOverText;
322 
323   if (!doc || doc->getNumPages() == 0) {
324     return;
325   }
326   ok = cvtWindowToDev(wx, wy, &pg, &x, &y);
327   if (dragging) {
328     if (ok) {
329       moveSelectionDrag(pg, x, y);
330     }
331   } else {
332     cvtDevToUser(pg, x, y, &xu, &yu);
333 
334     // check for a link
335     action = NULL;
336     if (hyperlinksEnabled && ok) {
337       action = findLink(pg, xu, yu);
338     }
339 
340     // check for text
341     mouseOverText = gFalse;
342     if (!action && getSelectMode() == selectModeLinear && ok) {
343       mouseOverText = overText(pg, x, y);
344     }
345 
346     // update the cursor
347     if (action) {
348       doSetCursor(Qt::PointingHandCursor);
349     } else if (mouseOverText) {
350       doSetCursor(Qt::IBeamCursor);
351     } else {
352       doUnsetCursor();
353     }
354 
355     // update the link info
356     if (action != linkAction) {
357       linkAction = action;
358       if (updateCbk) {
359 	//~ should pass a QString to updateCbk()
360 	if (linkAction) {
361 	  s = getLinkInfo(linkAction).toLocal8Bit().constData();
362 	} else {
363 	  s = "";
364 	}
365 	(*updateCbk)(updateCbkData, NULL, -1, -1, s);
366       }
367     }
368   }
369 
370   if (panning) {
371     scrollTo(getScrollX() - (wx - panMX),
372 	     getScrollY() - (wy - panMY));
373     panMX = wx;
374     panMY = wy;
375   }
376 }
377 
selectWord(int wx,int wy)378 void QtPDFCore::selectWord(int wx, int wy) {
379   int pg, x, y;
380 
381   takeFocus();
382   if (!doc || doc->getNumPages() == 0 || !selectEnabled) {
383     return;
384   }
385   if (getSelectMode() != selectModeLinear) {
386     return;
387   }
388   if (!cvtWindowToDev(wx, wy, &pg, &x, &y)) {
389     return;
390   }
391   PDFCore::selectWord(pg, x, y);
392 }
393 
selectLine(int wx,int wy)394 void QtPDFCore::selectLine(int wx, int wy) {
395   int pg, x, y;
396 
397   takeFocus();
398   if (!doc || doc->getNumPages() == 0 || !selectEnabled) {
399     return;
400   }
401   if (getSelectMode() != selectModeLinear) {
402     return;
403   }
404   if (!cvtWindowToDev(wx, wy, &pg, &x, &y)) {
405     return;
406   }
407   PDFCore::selectLine(pg, x, y);
408 }
409 
doLinkCbk(LinkAction * action)410 void QtPDFCore::doLinkCbk(LinkAction *action) {
411   LinkDest *dest;
412   GString *namedDest;
413   Ref pageRef;
414   int pg;
415   GString *cmd, *params;
416   char *s;
417 
418   if (!linkCbk) {
419     return;
420   }
421 
422   switch (action->getKind()) {
423 
424   case actionGoTo:
425     dest = NULL;
426     if ((dest = ((LinkGoTo *)action)->getDest())) {
427       dest = dest->copy();
428     } else if ((namedDest = ((LinkGoTo *)action)->getNamedDest())) {
429       dest = doc->findDest(namedDest);
430     }
431     pg = 0;
432     if (dest) {
433       if (dest->isPageRef()) {
434 	pageRef = dest->getPageRef();
435 	pg = doc->findPage(pageRef.num, pageRef.gen);
436       } else {
437 	pg = dest->getPageNum();
438       }
439       delete dest;
440     }
441     (*linkCbk)(linkCbkData, "goto", NULL, pg);
442     break;
443 
444   case actionGoToR:
445     (*linkCbk)(linkCbkData, "pdf",
446 	       ((LinkGoToR *)action)->getFileName()->getCString(), 0);
447     break;
448 
449   case actionLaunch:
450     cmd = ((LinkLaunch *)action)->getFileName()->copy();
451     s = cmd->getCString();
452     if (strcmp(s + cmd->getLength() - 4, ".pdf") &&
453 	strcmp(s + cmd->getLength() - 4, ".PDF") &&
454 	(params = ((LinkLaunch *)action)->getParams())) {
455       cmd->append(' ')->append(params);
456     }
457     (*linkCbk)(linkCbkData, "launch", cmd->getCString(), 0);
458     delete cmd;
459     break;
460 
461   case actionURI:
462     (*linkCbk)(linkCbkData, "url",
463 	       ((LinkURI *)action)->getURI()->getCString(), 0);
464     break;
465 
466   case actionNamed:
467     (*linkCbk)(linkCbkData, "named",
468 	       ((LinkNamed *)action)->getName()->getCString(), 0);
469     break;
470 
471   case actionMovie:
472   case actionJavaScript:
473   case actionSubmitForm:
474   case actionHide:
475   case actionUnknown:
476     (*linkCbk)(linkCbkData, "unknown", NULL, 0);
477     break;
478   }
479 }
480 
getSelectedTextQString()481 QString QtPDFCore::getSelectedTextQString() {
482   GString *s, *enc;
483   QString qs;
484   int i;
485 
486   if (!doc->okToCopy()) {
487     return "";
488   }
489   if (!(s = getSelectedText())) {
490     return "";
491   }
492   enc = globalParams->getTextEncodingName();
493   if (!enc->cmp("UTF-8")) {
494     qs = QString::fromUtf8(s->getCString());
495   } else if (!enc->cmp("UCS-2")) {
496     for (i = 0; i+1 < s->getLength(); i += 2) {
497       qs.append((QChar)(((s->getChar(i) & 0xff) << 8) +
498 			(s->getChar(i+1) & 0xff)));
499     }
500   } else {
501     qs = QString(s->getCString());
502   }
503   delete s;
504   delete enc;
505   return qs;
506 }
507 
copySelection(GBool toClipboard)508 void QtPDFCore::copySelection(GBool toClipboard) {
509   QString qs;
510 
511   // only X11 has the copy-on-select behavior
512   if (!toClipboard && !QApplication::clipboard()->supportsSelection()) {
513     return;
514   }
515   if (!doc->okToCopy()) {
516     return;
517   }
518   if (hasSelection()) {
519     QApplication::clipboard()->setText(getSelectedTextQString(),
520 				       toClipboard ? QClipboard::Clipboard
521 					           : QClipboard::Selection);
522   }
523 }
524 
525 //------------------------------------------------------------------------
526 // hyperlinks
527 //------------------------------------------------------------------------
528 
doAction(LinkAction * action)529 GBool QtPDFCore::doAction(LinkAction *action) {
530   LinkActionKind kind;
531   LinkDest *dest;
532   GString *namedDest;
533   char *s;
534   GString *fileName, *fileName2, *params;
535   GString *cmd;
536   GString *actionName;
537   Object movieAnnot, obj1, obj2;
538   GString *msg;
539   int i;
540 
541   switch (kind = action->getKind()) {
542 
543   // GoTo / GoToR action
544   case actionGoTo:
545   case actionGoToR:
546     if (kind == actionGoTo) {
547       dest = NULL;
548       namedDest = NULL;
549       if ((dest = ((LinkGoTo *)action)->getDest())) {
550 	dest = dest->copy();
551       } else if ((namedDest = ((LinkGoTo *)action)->getNamedDest())) {
552 	namedDest = namedDest->copy();
553       }
554     } else {
555       if (!externalHyperlinksEnabled) {
556 	return gFalse;
557       }
558       dest = NULL;
559       namedDest = NULL;
560       if ((dest = ((LinkGoToR *)action)->getDest())) {
561 	dest = dest->copy();
562       } else if ((namedDest = ((LinkGoToR *)action)->getNamedDest())) {
563 	namedDest = namedDest->copy();
564       }
565       s = ((LinkGoToR *)action)->getFileName()->getCString();
566       if (isAbsolutePath(s)) {
567 	fileName = new GString(s);
568       } else {
569 	fileName = appendToPath(grabPath(doc->getFileName()->getCString()), s);
570       }
571       if (loadFile(fileName) != errNone) {
572 	if (dest) {
573 	  delete dest;
574 	}
575 	if (namedDest) {
576 	  delete namedDest;
577 	}
578 	delete fileName;
579 	return gFalse;
580       }
581       delete fileName;
582     }
583     if (namedDest) {
584       dest = doc->findDest(namedDest);
585       delete namedDest;
586     }
587     if (dest) {
588       displayDest(dest);
589       delete dest;
590     } else {
591       if (kind == actionGoToR) {
592 	displayPage(1, gFalse, gFalse, gTrue);
593       }
594     }
595     break;
596 
597   // Launch action
598   case actionLaunch:
599     if (!externalHyperlinksEnabled) {
600       return gFalse;
601     }
602     fileName = ((LinkLaunch *)action)->getFileName();
603     s = fileName->getCString();
604     if (fileName->getLength() >= 4 &&
605 	(!strcmp(s + fileName->getLength() - 4, ".pdf") ||
606 	 !strcmp(s + fileName->getLength() - 4, ".PDF"))) {
607       if (isAbsolutePath(s)) {
608 	fileName = fileName->copy();
609       } else {
610 	fileName = appendToPath(grabPath(doc->getFileName()->getCString()), s);
611       }
612       if (loadFile(fileName) != errNone) {
613 	delete fileName;
614 	return gFalse;
615       }
616       delete fileName;
617       displayPage(1, gFalse, gFalse, gTrue);
618     } else {
619       cmd = fileName->copy();
620       if ((params = ((LinkLaunch *)action)->getParams())) {
621 	cmd->append(' ')->append(params);
622       }
623       if (globalParams->getLaunchCommand()) {
624 	cmd->insert(0, ' ');
625 	cmd->insert(0, globalParams->getLaunchCommand());
626 	QProcess::startDetached(cmd->getCString());
627       } else {
628 	msg = new GString("About to execute the command:\n");
629 	msg->append(cmd);
630 	if (QMessageBox::question(viewport, "PDF Launch Link",
631 				  msg->getCString(),
632 				  QMessageBox::Ok | QMessageBox::Cancel,
633 				  QMessageBox::Ok)
634 	    == QMessageBox::Ok) {
635 	  QProcess::startDetached(cmd->getCString());
636 	}
637 	delete msg;
638       }
639       delete cmd;
640     }
641     break;
642 
643   // URI action
644   case actionURI:
645     if (!externalHyperlinksEnabled) {
646       return gFalse;
647     }
648     QDesktopServices::openUrl(QUrl(((LinkURI *)action)->getURI()->getCString(),
649 				   QUrl::TolerantMode));
650     break;
651 
652   // Named action
653   case actionNamed:
654     actionName = ((LinkNamed *)action)->getName();
655     if (!actionName->cmp("NextPage")) {
656       gotoNextPage(1, gTrue);
657     } else if (!actionName->cmp("PrevPage")) {
658       gotoPrevPage(1, gTrue, gFalse);
659     } else if (!actionName->cmp("FirstPage")) {
660       displayPage(1, gTrue, gFalse, gTrue);
661     } else if (!actionName->cmp("LastPage")) {
662       displayPage(doc->getNumPages(), gTrue, gFalse, gTrue);
663     } else if (!actionName->cmp("GoBack")) {
664       goBackward();
665     } else if (!actionName->cmp("GoForward")) {
666       goForward();
667     } else if (!actionName->cmp("Quit")) {
668       if (actionCbk) {
669 	(*actionCbk)(actionCbkData, actionName->getCString());
670       }
671     } else {
672       error(errSyntaxError, -1,
673 	    "Unknown named action: '{0:t}'", actionName);
674       return gFalse;
675     }
676     break;
677 
678   // Movie action
679   case actionMovie:
680     if (!externalHyperlinksEnabled) {
681       return gFalse;
682     }
683     if (!(cmd = globalParams->getMovieCommand())) {
684       error(errConfig, -1, "No movieCommand defined in config file");
685       return gFalse;
686     }
687     if (((LinkMovie *)action)->hasAnnotRef()) {
688       doc->getXRef()->fetch(((LinkMovie *)action)->getAnnotRef()->num,
689 			    ((LinkMovie *)action)->getAnnotRef()->gen,
690 			    &movieAnnot);
691     } else {
692       //~ need to use the correct page num here
693       doc->getCatalog()->getPage(tileMap->getFirstPage())->getAnnots(&obj1);
694       if (obj1.isArray()) {
695 	for (i = 0; i < obj1.arrayGetLength(); ++i) {
696 	  if (obj1.arrayGet(i, &movieAnnot)->isDict()) {
697 	    if (movieAnnot.dictLookup("Subtype", &obj2)->isName("Movie")) {
698 	      obj2.free();
699 	      break;
700 	    }
701 	    obj2.free();
702 	  }
703 	  movieAnnot.free();
704 	}
705 	obj1.free();
706       }
707     }
708     if (movieAnnot.isDict()) {
709       if (movieAnnot.dictLookup("Movie", &obj1)->isDict()) {
710 	if (obj1.dictLookup("F", &obj2)) {
711 	  if ((fileName = LinkAction::getFileSpecName(&obj2))) {
712 	    if (!isAbsolutePath(fileName->getCString())) {
713 	      fileName2 = appendToPath(
714 			      grabPath(doc->getFileName()->getCString()),
715 			      fileName->getCString());
716 	      delete fileName;
717 	      fileName = fileName2;
718 	    }
719 	    runCommand(cmd, fileName);
720 	    delete fileName;
721 	  }
722 	  obj2.free();
723 	}
724 	obj1.free();
725       }
726     }
727     movieAnnot.free();
728     break;
729 
730   // unimplemented actions
731   case actionJavaScript:
732   case actionSubmitForm:
733   case actionHide:
734     return gFalse;
735 
736   // unknown action type
737   case actionUnknown:
738     error(errSyntaxError, -1, "Unknown link action type: '{0:t}'",
739 	  ((LinkUnknown *)action)->getAction());
740     return gFalse;
741   }
742 
743   return gTrue;
744 }
745 
getLinkInfo(LinkAction * action)746 QString QtPDFCore::getLinkInfo(LinkAction *action) {
747   LinkDest *dest;
748   GString *namedDest;
749   Ref pageRef;
750   int pg;
751   QString info;
752 
753   if (action == lastLinkAction && !lastLinkActionInfo.isEmpty()) {
754     return lastLinkActionInfo;
755   }
756 
757   switch (action->getKind()) {
758   case actionGoTo:
759     dest = NULL;
760     if ((dest = ((LinkGoTo *)action)->getDest())) {
761       dest = dest->copy();
762     } else if ((namedDest = ((LinkGoTo *)action)->getNamedDest())) {
763       dest = doc->findDest(namedDest);
764     }
765     pg = 0;
766     if (dest) {
767       if (dest->isPageRef()) {
768 	pageRef = dest->getPageRef();
769 	pg = doc->findPage(pageRef.num, pageRef.gen);
770       } else {
771 	pg = dest->getPageNum();
772       }
773       delete dest;
774     }
775     if (pg) {
776       info = QString("[page ") + QString::number(pg) + QString("]");
777     } else {
778       info = "[internal]";
779     }
780     break;
781   case actionGoToR:
782     info = QString(((LinkGoToR *)action)->getFileName()->getCString());
783     break;
784   case actionLaunch:
785     info = QString(((LinkLaunch *)action)->getFileName()->getCString());
786     break;
787   case actionURI:
788     info = QString(((LinkURI *)action)->getURI()->getCString());
789     break;
790   case actionNamed:
791     info = QString(((LinkNamed *)action)->getName()->getCString());
792     break;
793   case actionMovie:
794     info = "[movie]";
795     break;
796   case actionJavaScript:
797   case actionSubmitForm:
798   case actionHide:
799   case actionUnknown:
800   default:
801     info = "[unknown]";
802     break;
803   }
804 
805   lastLinkAction = action;
806   lastLinkActionInfo = info;
807 
808   return info;
809 }
810 
811 // Run a command, given a <cmdFmt> string with one '%s' in it, and an
812 // <arg> string to insert in place of the '%s'.
runCommand(GString * cmdFmt,GString * arg)813 void QtPDFCore::runCommand(GString *cmdFmt, GString *arg) {
814   GString *cmd;
815   char *s;
816 
817   if ((s = strstr(cmdFmt->getCString(), "%s"))) {
818     cmd = mungeURL(arg);
819     cmd->insert(0, cmdFmt->getCString(),
820 		(int)(s - cmdFmt->getCString()));
821     cmd->append(s + 2);
822   } else {
823     cmd = cmdFmt->copy();
824   }
825   QProcess::startDetached(cmd->getCString());
826   delete cmd;
827 }
828 
829 // Escape any characters in a URL which might cause problems when
830 // calling system().
mungeURL(GString * url)831 GString *QtPDFCore::mungeURL(GString *url) {
832   static const char *allowed = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
833                                "abcdefghijklmnopqrstuvwxyz"
834                                "0123456789"
835                                "-_.~/?:@&=+,#%";
836   GString *newURL;
837   char c;
838   int i;
839 
840   newURL = new GString();
841   for (i = 0; i < url->getLength(); ++i) {
842     c = url->getChar(i);
843     if (strchr(allowed, c)) {
844       newURL->append(c);
845     } else {
846       newURL->appendf("%{0:02x}", c & 0xff);
847     }
848   }
849   return newURL;
850 }
851 
852 //------------------------------------------------------------------------
853 // find
854 //------------------------------------------------------------------------
855 
find(char * s,GBool caseSensitive,GBool next,GBool backward,GBool wholeWord,GBool onePageOnly)856 GBool QtPDFCore::find(char *s, GBool caseSensitive, GBool next,
857 		      GBool backward, GBool wholeWord, GBool onePageOnly) {
858   if (!PDFCore::find(s, caseSensitive, next,
859 		     backward, wholeWord, onePageOnly)) {
860     return gFalse;
861   }
862 #ifndef NO_TEXT_SELECT
863   copySelection(gFalse);
864 #endif
865   return gTrue;
866 }
867 
findU(Unicode * u,int len,GBool caseSensitive,GBool next,GBool backward,GBool wholeWord,GBool onePageOnly)868 GBool QtPDFCore::findU(Unicode *u, int len, GBool caseSensitive,
869 		       GBool next, GBool backward,
870 		       GBool wholeWord, GBool onePageOnly) {
871   if (!PDFCore::findU(u, len, caseSensitive, next,
872 		      backward, wholeWord, onePageOnly)) {
873     return gFalse;
874   }
875 #ifndef NO_TEXT_SELECT
876   copySelection(gFalse);
877 #endif
878   return gTrue;
879 }
880 
881 //------------------------------------------------------------------------
882 // misc access
883 //------------------------------------------------------------------------
884 
setBusyCursor(GBool busy)885 void QtPDFCore::setBusyCursor(GBool busy) {
886   if (busy) {
887     doSetCursor(Qt::WaitCursor);
888   } else {
889     doUnsetCursor();
890   }
891 }
892 
doSetCursor(const QCursor & cursor)893 void QtPDFCore::doSetCursor(const QCursor &cursor) {
894 #ifndef QT_NO_CURSOR
895   viewport->setCursor(cursor);
896 #endif
897 }
898 
doUnsetCursor()899 void QtPDFCore::doUnsetCursor() {
900 #ifndef QT_NO_CURSOR
901   viewport->unsetCursor();
902 #endif
903 }
904 
takeFocus()905 void QtPDFCore::takeFocus() {
906   viewport->setFocus(Qt::OtherFocusReason);
907 }
908 
getBestSize()909 QSize QtPDFCore::getBestSize() {
910   DisplayMode mode;
911   double zoomPercent;
912   int w, h, pg, rot;
913 
914   if (!doc || doc->getNumPages() == 0) {
915     //~ what should this return?
916     return QSize(612, 792);
917   }
918   mode = state->getDisplayMode();
919   pg = tileMap->getFirstPage();
920   rot = (state->getRotate() + doc->getPageRotate(pg)) % 360;
921   zoomPercent = state->getZoom();
922   if (zoomPercent < 0) {
923     zoomPercent = globalParams->getDefaultFitZoom();
924     if (zoomPercent <= 0) {
925       zoomPercent = (int)((100 * displayDpi) / 72.0 + 0.5);
926       if (zoomPercent < 100) {
927 	zoomPercent = 100;
928       }
929     }
930   }
931   if (rot == 90 || rot == 270) {
932     w = (int)(doc->getPageCropHeight(pg) * 0.01 * zoomPercent + 0.5);
933     h = (int)(doc->getPageCropWidth(pg) * 0.01 * zoomPercent + 0.5);
934   } else {
935     w = (int)(doc->getPageCropWidth(pg) * 0.01 * zoomPercent + 0.5);
936     h = (int)(doc->getPageCropHeight(pg) * 0.01 * zoomPercent + 0.5);
937   }
938   if (mode == displayContinuous) {
939     w += QApplication::style()->pixelMetric(QStyle::PM_ScrollBarExtent);
940     h += tileMap->getContinuousPageSpacing();
941   } else if (mode == displaySideBySideContinuous) {
942     w = w * 2
943         + tileMap->getHorizContinuousPageSpacing()
944         + QApplication::style()->pixelMetric(QStyle::PM_ScrollBarExtent);
945     h += tileMap->getContinuousPageSpacing();
946   } else if (mode == displayHorizontalContinuous) {
947     w += tileMap->getHorizContinuousPageSpacing();
948     h += QApplication::style()->pixelMetric(QStyle::PM_ScrollBarExtent);
949   }  else if (mode == displaySideBySideSingle) {
950     w = w * 2 + tileMap->getHorizContinuousPageSpacing();
951   }
952   //~ these additions are a kludge to make this work -- 2 pixels are
953   //~   padding in the QAbstractScrollArea; not sure where the rest go
954 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
955   w += 6;
956   h += 2;
957 #else
958   w += 10;
959   h += 4;
960 #endif
961   return QSize((int)(w / scaleFactor), (int)(h / scaleFactor));
962 }
963 
964 //------------------------------------------------------------------------
965 // GUI code
966 //------------------------------------------------------------------------
967 
resizeEvent()968 void QtPDFCore::resizeEvent() {
969   setWindowSize((int)(viewport->width() * scaleFactor),
970 		(int)(viewport->height() * scaleFactor));
971 }
972 
paintEvent(int x,int y,int w,int h)973 void QtPDFCore::paintEvent(int x, int y, int w, int h) {
974   SplashBitmap *bitmap;
975   GBool wholeWindow;
976 
977   QPainter painter(viewport);
978   wholeWindow = x == 0 && y == 0 &&
979                 w == viewport->width() && h == viewport->height();
980   bitmap = getWindowBitmap(wholeWindow);
981   QImage image(bitmap->getDataPtr(), bitmap->getWidth(),
982 	       bitmap->getHeight(), QImage::Format_RGB888);
983   if (scaleFactor == 1) {
984     painter.drawImage(QRect(x, y, w, h), image, QRect(x, y, w, h));
985   } else {
986     painter.drawImage(QRectF(x, y, w, h), image,
987 		      QRectF(x * scaleFactor, y * scaleFactor,
988 			     w * scaleFactor, h * scaleFactor));
989   }
990   if (paintDoneCbk) {
991     (*paintDoneCbk)(paintDoneCbkData, (bool)isBitmapFinished());
992   }
993 }
994 
scrollEvent()995 void QtPDFCore::scrollEvent() {
996   // avoid loops, e.g., scrollTo -> finishUpdate -> updateScrollbars ->
997   // hScrollbar.setValue -> scrollContentsBy -> scrollEvent -> scrollTo
998   if (inUpdateScrollbars) {
999     return;
1000   }
1001   scrollTo(hScrollBar->value(), vScrollBar->value());
1002 }
1003 
tick()1004 void QtPDFCore::tick() {
1005   PDFCore::tick();
1006 }
1007 
invalidate(int x,int y,int w,int h)1008 void QtPDFCore::invalidate(int x, int y, int w, int h) {
1009   int xx, yy, ww, hh;
1010 
1011   if (scaleFactor == 1) {
1012     viewport->update(x, y, w, h);
1013   } else {
1014     xx = (int)(x / scaleFactor);
1015     yy = (int)(y / scaleFactor);
1016     ww = (int)ceil((x + w) / scaleFactor) - xx;
1017     hh = (int)ceil((y + h) / scaleFactor) - yy;
1018     viewport->update(xx, yy, ww, hh);
1019   }
1020 }
1021 
updateScrollbars()1022 void QtPDFCore::updateScrollbars() {
1023   int winW, winH, horizLimit, vertLimit, horizMax, vertMax;
1024   bool vScrollBarVisible, hScrollBarVisible;
1025 
1026   inUpdateScrollbars = gTrue;
1027 
1028   winW = state->getWinW();
1029   winH = state->getWinH();
1030   tileMap->getScrollLimits(&horizLimit, &vertLimit);
1031 
1032   if (horizLimit > winW) {
1033     horizMax = horizLimit - winW;
1034   } else {
1035     horizMax = 0;
1036   }
1037   if (vertLimit > winH) {
1038     vertMax = vertLimit - winH;
1039   } else {
1040     vertMax = 0;
1041   }
1042 
1043   // Problem case A: in fixed zoom, there is a case where the page
1044   // just barely fits in the window; if the scrollbars are visible,
1045   // they reduce the available window size enough that they are
1046   // necessary, i.e., the scrollbars are only necessary if they're
1047   // visible -- so check for that situation and force the scrollbars
1048   // to be hidden.
1049   // NB: {h,v}ScrollBar->isVisible() are unreliable at startup, so
1050   //     we compare the viewport size to the ScrollArea size (with
1051   //     some slack for margins)
1052   vScrollBarVisible =
1053       viewport->parentWidget()->width() - viewport->width() > 8;
1054   hScrollBarVisible =
1055       viewport->parentWidget()->height() - viewport->height() > 8;
1056   if (state->getZoom() >= 0 &&
1057       vScrollBarVisible &&
1058       hScrollBarVisible &&
1059       horizMax <= vScrollBar->width() &&
1060       vertMax <= hScrollBar->height()) {
1061     horizMax = 0;
1062     vertMax = 0;
1063   }
1064 
1065   // Problem case B: in fit-to-width mode, with the vertical scrollbar
1066   // visible, if the window is just tall enough to fit the page, then
1067   // the vertical scrollbar will be hidden, resulting in a wider
1068   // window, resulting in a taller page (because of fit-to-width),
1069   // resulting in the scrollbar being unhidden, in an infinite loop --
1070   // so always force the vertical scroll bar to be visible in
1071   // fit-to-width mode (and in fit-to-page cases where the vertical
1072   // scrollbar is potentially visible).
1073   if (state->getZoom() == zoomWidth ||
1074       (state->getZoom() == zoomPage &&
1075        (state->getDisplayMode() == displayContinuous ||
1076 	state->getDisplayMode() == displaySideBySideContinuous))) {
1077     if (vertMax == 0) {
1078       vertMax = 1;
1079     }
1080 
1081   // Problem case C: same as case B, but for fit-to-height mode and
1082   // the horizontal scrollbar.
1083   } else if (state->getZoom() == zoomHeight ||
1084 	     (state->getZoom() == zoomPage &&
1085 	      state->getDisplayMode() == displayHorizontalContinuous)) {
1086     if (horizMax == 0) {
1087       horizMax = 1;
1088     }
1089   }
1090 
1091   hScrollBar->setMaximum(horizMax);
1092   hScrollBar->setPageStep(winW);
1093   hScrollBar->setValue(state->getScrollX());
1094 
1095   vScrollBar->setMaximum(vertMax);
1096   vScrollBar->setPageStep(winH);
1097   vScrollBar->setValue(state->getScrollY());
1098 
1099   inUpdateScrollbars = gFalse;
1100 }
1101 
checkForNewFile()1102 GBool QtPDFCore::checkForNewFile() {
1103   QDateTime newModTime;
1104 
1105   if (doc->getFileName()) {
1106     newModTime = QFileInfo(doc->getFileName()->getCString()).lastModified();
1107     if (newModTime != modTime) {
1108       modTime = newModTime;
1109       return gTrue;
1110     }
1111   }
1112   return gFalse;
1113 }
1114 
preLoad()1115 void QtPDFCore::preLoad() {
1116   if (preLoadCbk) {
1117     (*preLoadCbk)(preLoadCbkData);
1118   }
1119 }
1120 
postLoad()1121 void QtPDFCore::postLoad() {
1122   if (postLoadCbk) {
1123     (*postLoadCbk)(postLoadCbkData);
1124   }
1125 }
1126 
1127 //------------------------------------------------------------------------
1128 // password dialog
1129 //------------------------------------------------------------------------
1130 
getPassword()1131 GString *QtPDFCore::getPassword() {
1132   QString s;
1133   bool ok;
1134 
1135   if (!showPasswordDialog) {
1136     return NULL;
1137   }
1138   s = QInputDialog::getText(viewport, "PDF Password",
1139 			    "This document requires a password",
1140 			    QLineEdit::Password, "", &ok, Qt::Dialog);
1141   if (ok) {
1142     return new GString(s.toLocal8Bit().constData());
1143   } else {
1144     return NULL;
1145   }
1146 }
1147