1 //========================================================================
2 //
3 // Link.cc
4 //
5 // Copyright 1996-2003 Glyph & Cog, LLC
6 //
7 //========================================================================
8 
9 #include <aconf.h>
10 
11 #ifdef USE_GCC_PRAGMAS
12 #pragma implementation
13 #endif
14 
15 #include <stddef.h>
16 #include <string.h>
17 #include "gmem.h"
18 #include "GString.h"
19 #include "Error.h"
20 #include "Object.h"
21 #include "Array.h"
22 #include "Dict.h"
23 #include "Link.h"
24 
25 //------------------------------------------------------------------------
26 // LinkAction
27 //------------------------------------------------------------------------
28 
parseDest(Object * obj)29 LinkAction *LinkAction::parseDest(Object *obj) {
30   LinkAction *action;
31 
32   action = new LinkGoTo(obj);
33   if (!action->isOk()) {
34     delete action;
35     return NULL;
36   }
37   return action;
38 }
39 
parseAction(Object * obj,GString * baseURI)40 LinkAction *LinkAction::parseAction(Object *obj, GString *baseURI) {
41   LinkAction *action;
42   Object obj2, obj3, obj4;
43 
44   if (!obj->isDict()) {
45     error(-1, "Bad annotation action");
46     return NULL;
47   }
48 
49   obj->dictLookup("S", &obj2);
50 
51   // GoTo action
52   if (obj2.isName("GoTo")) {
53     obj->dictLookup("D", &obj3);
54     action = new LinkGoTo(&obj3);
55     obj3.free();
56 
57   // GoToR action
58   } else if (obj2.isName("GoToR")) {
59     obj->dictLookup("F", &obj3);
60     obj->dictLookup("D", &obj4);
61     action = new LinkGoToR(&obj3, &obj4);
62     obj3.free();
63     obj4.free();
64 
65   // Launch action
66   } else if (obj2.isName("Launch")) {
67     action = new LinkLaunch(obj);
68 
69   // URI action
70   } else if (obj2.isName("URI")) {
71     obj->dictLookup("URI", &obj3);
72     action = new LinkURI(&obj3, baseURI);
73     obj3.free();
74 
75   // Named action
76   } else if (obj2.isName("Named")) {
77     obj->dictLookup("N", &obj3);
78     action = new LinkNamed(&obj3);
79     obj3.free();
80 
81   // Movie action
82   } else if (obj2.isName("Movie")) {
83     obj->dictLookupNF("Annot", &obj3);
84     obj->dictLookup("T", &obj4);
85     action = new LinkMovie(&obj3, &obj4);
86     obj3.free();
87     obj4.free();
88 
89   // unknown action
90   } else if (obj2.isName()) {
91     action = new LinkUnknown(obj2.getName());
92 
93   // action is missing or wrong type
94   } else {
95     error(-1, "Bad annotation action");
96     action = NULL;
97   }
98 
99   obj2.free();
100 
101   if (action && !action->isOk()) {
102     delete action;
103     return NULL;
104   }
105   return action;
106 }
107 
getFileSpecName(Object * fileSpecObj)108 GString *LinkAction::getFileSpecName(Object *fileSpecObj) {
109   GString *name;
110   Object obj1;
111 
112   name = NULL;
113 
114   // string
115   if (fileSpecObj->isString()) {
116     name = fileSpecObj->getString()->copy();
117 
118   // dictionary
119   } else if (fileSpecObj->isDict()) {
120 #ifdef WIN32
121     if (!fileSpecObj->dictLookup("DOS", &obj1)->isString()) {
122 #else
123     if (!fileSpecObj->dictLookup("Unix", &obj1)->isString()) {
124 #endif
125       obj1.free();
126       fileSpecObj->dictLookup("F", &obj1);
127     }
128     if (obj1.isString()) {
129       name = obj1.getString()->copy();
130     } else {
131       error(-1, "Illegal file spec in link");
132     }
133     obj1.free();
134 
135   // error
136   } else {
137     error(-1, "Illegal file spec in link");
138   }
139 
140   // system-dependent path manipulation
141   if (name) {
142 #ifdef WIN32
143     int i, j;
144 
145     // "//...."             --> "\...."
146     // "/x/...."            --> "x:\...."
147     // "/server/share/...." --> "\\server\share\...."
148     // convert escaped slashes to slashes and unescaped slashes to backslashes
149     i = 0;
150     if (name->getChar(0) == '/') {
151       if (name->getLength() >= 2 && name->getChar(1) == '/') {
152 	name->del(0);
153 	i = 0;
154       } else if (name->getLength() >= 2 &&
155 		 ((name->getChar(1) >= 'a' && name->getChar(1) <= 'z') ||
156 		  (name->getChar(1) >= 'A' && name->getChar(1) <= 'Z')) &&
157 		 (name->getLength() == 2 || name->getChar(2) == '/')) {
158 	name->setChar(0, name->getChar(1));
159 	name->setChar(1, ':');
160 	i = 2;
161       } else {
162 	for (j = 2; j < name->getLength(); ++j) {
163 	  if (name->getChar(j-1) != '\\' &&
164 	      name->getChar(j) == '/') {
165 	    break;
166 	  }
167 	}
168 	if (j < name->getLength()) {
169 	  name->setChar(0, '\\');
170 	  name->insert(0, '\\');
171 	  i = 2;
172 	}
173       }
174     }
175     for (; i < name->getLength(); ++i) {
176       if (name->getChar(i) == '/') {
177 	name->setChar(i, '\\');
178       } else if (name->getChar(i) == '\\' &&
179 		 i+1 < name->getLength() &&
180 		 name->getChar(i+1) == '/') {
181 	name->del(i);
182       }
183     }
184 #else
185     // no manipulation needed for Unix
186 #endif
187   }
188 
189   return name;
190 }
191 
192 //------------------------------------------------------------------------
193 // LinkDest
194 //------------------------------------------------------------------------
195 
196 LinkDest::LinkDest(Array *a) {
197   Object obj1, obj2;
198 
199   // initialize fields
200   left = bottom = right = top = zoom = 0;
201   ok = gFalse;
202 
203   // get page
204   if (a->getLength() < 2) {
205     error(-1, "Annotation destination array is too short");
206     return;
207   }
208   a->getNF(0, &obj1);
209   if (obj1.isInt()) {
210     pageNum = obj1.getInt() + 1;
211     pageIsRef = gFalse;
212   } else if (obj1.isRef()) {
213     pageRef.num = obj1.getRefNum();
214     pageRef.gen = obj1.getRefGen();
215     pageIsRef = gTrue;
216   } else {
217     error(-1, "Bad annotation destination");
218     goto err2;
219   }
220   obj1.free();
221 
222   // get destination type
223   a->get(1, &obj1);
224 
225   // XYZ link
226   if (obj1.isName("XYZ")) {
227     kind = destXYZ;
228     if (a->getLength() < 3) {
229       changeLeft = gFalse;
230     } else {
231       a->get(2, &obj2);
232       if (obj2.isNull()) {
233 	changeLeft = gFalse;
234       } else if (obj2.isNum()) {
235 	changeLeft = gTrue;
236 	left = obj2.getNum();
237       } else {
238 	error(-1, "Bad annotation destination position");
239 	goto err1;
240       }
241       obj2.free();
242     }
243     if (a->getLength() < 4) {
244       changeTop = gFalse;
245     } else {
246       a->get(3, &obj2);
247       if (obj2.isNull()) {
248 	changeTop = gFalse;
249       } else if (obj2.isNum()) {
250 	changeTop = gTrue;
251 	top = obj2.getNum();
252       } else {
253 	error(-1, "Bad annotation destination position");
254 	goto err1;
255       }
256       obj2.free();
257     }
258     if (a->getLength() < 5) {
259       changeZoom = gFalse;
260     } else {
261       a->get(4, &obj2);
262       if (obj2.isNull()) {
263 	changeZoom = gFalse;
264       } else if (obj2.isNum()) {
265 	changeZoom = gTrue;
266 	zoom = obj2.getNum();
267       } else {
268 	error(-1, "Bad annotation destination position");
269 	goto err1;
270       }
271       obj2.free();
272     }
273 
274   // Fit link
275   } else if (obj1.isName("Fit")) {
276     if (a->getLength() < 2) {
277       error(-1, "Annotation destination array is too short");
278       goto err2;
279     }
280     kind = destFit;
281 
282   // FitH link
283   } else if (obj1.isName("FitH")) {
284     if (a->getLength() < 3) {
285       error(-1, "Annotation destination array is too short");
286       goto err2;
287     }
288     kind = destFitH;
289     if (!a->get(2, &obj2)->isNum()) {
290       error(-1, "Bad annotation destination position");
291       kind = destFit;
292     }
293     top = obj2.getNum();
294     obj2.free();
295 
296   // FitV link
297   } else if (obj1.isName("FitV")) {
298     if (a->getLength() < 3) {
299       error(-1, "Annotation destination array is too short");
300       goto err2;
301     }
302     kind = destFitV;
303     if (!a->get(2, &obj2)->isNum()) {
304       error(-1, "Bad annotation destination position");
305       kind = destFit;
306     }
307     left = obj2.getNum();
308     obj2.free();
309 
310   // FitR link
311   } else if (obj1.isName("FitR")) {
312     if (a->getLength() < 6) {
313       error(-1, "Annotation destination array is too short");
314       goto err2;
315     }
316     kind = destFitR;
317     if (!a->get(2, &obj2)->isNum()) {
318       error(-1, "Bad annotation destination position");
319       kind = destFit;
320     }
321     left = obj2.getNum();
322     obj2.free();
323     if (!a->get(3, &obj2)->isNum()) {
324       error(-1, "Bad annotation destination position");
325       kind = destFit;
326     }
327     bottom = obj2.getNum();
328     obj2.free();
329     if (!a->get(4, &obj2)->isNum()) {
330       error(-1, "Bad annotation destination position");
331       kind = destFit;
332     }
333     right = obj2.getNum();
334     obj2.free();
335     if (!a->get(5, &obj2)->isNum()) {
336       error(-1, "Bad annotation destination position");
337       kind = destFit;
338     }
339     top = obj2.getNum();
340     obj2.free();
341 
342   // FitB link
343   } else if (obj1.isName("FitB")) {
344     if (a->getLength() < 2) {
345       error(-1, "Annotation destination array is too short");
346       goto err2;
347     }
348     kind = destFitB;
349 
350   // FitBH link
351   } else if (obj1.isName("FitBH")) {
352     if (a->getLength() < 3) {
353       error(-1, "Annotation destination array is too short");
354       goto err2;
355     }
356     kind = destFitBH;
357     if (!a->get(2, &obj2)->isNum()) {
358       error(-1, "Bad annotation destination position");
359       kind = destFit;
360     }
361     top = obj2.getNum();
362     obj2.free();
363 
364   // FitBV link
365   } else if (obj1.isName("FitBV")) {
366     if (a->getLength() < 3) {
367       error(-1, "Annotation destination array is too short");
368       goto err2;
369     }
370     kind = destFitBV;
371     if (!a->get(2, &obj2)->isNum()) {
372       error(-1, "Bad annotation destination position");
373       kind = destFit;
374     }
375     left = obj2.getNum();
376     obj2.free();
377 
378   // unknown link kind
379   } else {
380     error(-1, "Unknown annotation destination type");
381     goto err2;
382   }
383 
384   obj1.free();
385   ok = gTrue;
386   return;
387 
388  err1:
389   obj2.free();
390  err2:
391   obj1.free();
392 }
393 
394 LinkDest::LinkDest(LinkDest *dest) {
395   kind = dest->kind;
396   pageIsRef = dest->pageIsRef;
397   if (pageIsRef)
398     pageRef = dest->pageRef;
399   else
400     pageNum = dest->pageNum;
401   left = dest->left;
402   bottom = dest->bottom;
403   right = dest->right;
404   top = dest->top;
405   zoom = dest->zoom;
406   changeLeft = dest->changeLeft;
407   changeTop = dest->changeTop;
408   changeZoom = dest->changeZoom;
409   ok = gTrue;
410 }
411 
412 //------------------------------------------------------------------------
413 // LinkGoTo
414 //------------------------------------------------------------------------
415 
416 LinkGoTo::LinkGoTo(Object *destObj) {
417   dest = NULL;
418   namedDest = NULL;
419 
420   // named destination
421   if (destObj->isName()) {
422     namedDest = new GString(destObj->getName());
423   } else if (destObj->isString()) {
424     namedDest = destObj->getString()->copy();
425 
426   // destination dictionary
427   } else if (destObj->isArray()) {
428     dest = new LinkDest(destObj->getArray());
429     if (!dest->isOk()) {
430       delete dest;
431       dest = NULL;
432     }
433   // error
434   } else {
435     error(-1, "Illegal annotation destination %d", destObj->getType());
436   }
437 }
438 
439 LinkGoTo::~LinkGoTo() {
440   if (dest)
441     delete dest;
442   if (namedDest)
443     delete namedDest;
444 }
445 
446 //------------------------------------------------------------------------
447 // LinkGoToR
448 //------------------------------------------------------------------------
449 
450 LinkGoToR::LinkGoToR(Object *fileSpecObj, Object *destObj) {
451   dest = NULL;
452   namedDest = NULL;
453 
454   // get file name
455   fileName = getFileSpecName(fileSpecObj);
456 
457   // named destination
458   if (destObj->isName()) {
459     namedDest = new GString(destObj->getName());
460   } else if (destObj->isString()) {
461     namedDest = destObj->getString()->copy();
462 
463   // destination dictionary
464   } else if (destObj->isArray()) {
465     dest = new LinkDest(destObj->getArray());
466     if (!dest->isOk()) {
467       delete dest;
468       dest = NULL;
469     }
470   // error
471   } else {
472     error(-1, "Illegal annotation destination %d", destObj->getType());
473   }
474 }
475 
476 LinkGoToR::~LinkGoToR() {
477   if (fileName)
478     delete fileName;
479   if (dest)
480     delete dest;
481   if (namedDest)
482     delete namedDest;
483 }
484 
485 
486 //------------------------------------------------------------------------
487 // LinkLaunch
488 //------------------------------------------------------------------------
489 
490 LinkLaunch::LinkLaunch(Object *actionObj) {
491   Object obj1, obj2;
492 
493   fileName = NULL;
494   params = NULL;
495 
496   if (actionObj->isDict()) {
497     if (!actionObj->dictLookup("F", &obj1)->isNull()) {
498       fileName = getFileSpecName(&obj1);
499     } else {
500       obj1.free();
501 #ifdef WIN32
502       if (actionObj->dictLookup("Win", &obj1)->isDict()) {
503 	obj1.dictLookup("F", &obj2);
504 	fileName = getFileSpecName(&obj2);
505 	obj2.free();
506 	if (obj1.dictLookup("P", &obj2)->isString()) {
507 	  params = obj2.getString()->copy();
508 	}
509 	obj2.free();
510       } else {
511 	error(-1, "Bad launch-type link action");
512       }
513 #else
514       //~ This hasn't been defined by Adobe yet, so assume it looks
515       //~ just like the Win dictionary until they say otherwise.
516       if (actionObj->dictLookup("Unix", &obj1)->isDict()) {
517 	obj1.dictLookup("F", &obj2);
518 	fileName = getFileSpecName(&obj2);
519 	obj2.free();
520 	if (obj1.dictLookup("P", &obj2)->isString()) {
521 	  params = obj2.getString()->copy();
522 	}
523 	obj2.free();
524       } else {
525 	error(-1, "Bad launch-type link action");
526       }
527 #endif
528     }
529     obj1.free();
530   }
531 }
532 
533 LinkLaunch::~LinkLaunch() {
534   if (fileName)
535     delete fileName;
536   if (params)
537     delete params;
538 }
539 
540 //------------------------------------------------------------------------
541 // LinkURI
542 //------------------------------------------------------------------------
543 
544 LinkURI::LinkURI(Object *uriObj, GString *baseURI) {
545   GString *uri2;
546   int n;
547   char c;
548 
549   uri = NULL;
550   if (uriObj->isString()) {
551     uri2 = uriObj->getString()->copy();
552     if (baseURI && baseURI->getLength() > 0) {
553       n = strcspn(uri2->getCString(), "/:");
554       if (n == uri2->getLength() || uri2->getChar(n) == '/') {
555 	uri = baseURI->copy();
556 	c = uri->getChar(uri->getLength() - 1);
557 	if (c == '/' || c == '?') {
558 	  if (uri2->getChar(0) == '/') {
559 	    uri2->del(0);
560 	  }
561 	} else {
562 	  if (uri2->getChar(0) != '/') {
563 	    uri->append('/');
564 	  }
565 	}
566 	uri->append(uri2);
567 	delete uri2;
568       } else {
569 	uri = uri2;
570       }
571     } else {
572       uri = uri2;
573     }
574   } else {
575     error(-1, "Illegal URI-type link");
576   }
577 }
578 
579 LinkURI::~LinkURI() {
580   if (uri)
581     delete uri;
582 }
583 
584 //------------------------------------------------------------------------
585 // LinkNamed
586 //------------------------------------------------------------------------
587 
588 LinkNamed::LinkNamed(Object *nameObj) {
589   name = NULL;
590   if (nameObj->isName()) {
591     name = new GString(nameObj->getName());
592   }
593 }
594 
595 LinkNamed::~LinkNamed() {
596   if (name) {
597     delete name;
598   }
599 }
600 
601 //------------------------------------------------------------------------
602 // LinkMovie
603 //------------------------------------------------------------------------
604 
605 LinkMovie::LinkMovie(Object *annotObj, Object *titleObj) {
606   annotRef.num = -1;
607   title = NULL;
608   if (annotObj->isRef()) {
609     annotRef = annotObj->getRef();
610   } else if (titleObj->isString()) {
611     title = titleObj->getString()->copy();
612   } else {
613     error(-1, "Movie action is missing both the Annot and T keys");
614   }
615 }
616 
617 LinkMovie::~LinkMovie() {
618   if (title) {
619     delete title;
620   }
621 }
622 
623 //------------------------------------------------------------------------
624 // LinkUnknown
625 //------------------------------------------------------------------------
626 
627 LinkUnknown::LinkUnknown(char *actionA) {
628   action = new GString(actionA);
629 }
630 
631 LinkUnknown::~LinkUnknown() {
632   delete action;
633 }
634 
635 //------------------------------------------------------------------------
636 // Link
637 //------------------------------------------------------------------------
638 
639 Link::Link(Dict *dict, GString *baseURI) {
640   Object obj1, obj2;
641   double t;
642 
643   action = NULL;
644   ok = gFalse;
645 
646   // get rectangle
647   if (!dict->lookup("Rect", &obj1)->isArray()) {
648     error(-1, "Annotation rectangle is wrong type");
649     goto err2;
650   }
651   if (!obj1.arrayGet(0, &obj2)->isNum()) {
652     error(-1, "Bad annotation rectangle");
653     goto err1;
654   }
655   x1 = obj2.getNum();
656   obj2.free();
657   if (!obj1.arrayGet(1, &obj2)->isNum()) {
658     error(-1, "Bad annotation rectangle");
659     goto err1;
660   }
661   y1 = obj2.getNum();
662   obj2.free();
663   if (!obj1.arrayGet(2, &obj2)->isNum()) {
664     error(-1, "Bad annotation rectangle");
665     goto err1;
666   }
667   x2 = obj2.getNum();
668   obj2.free();
669   if (!obj1.arrayGet(3, &obj2)->isNum()) {
670     error(-1, "Bad annotation rectangle");
671     goto err1;
672   }
673   y2 = obj2.getNum();
674   obj2.free();
675   obj1.free();
676   if (x1 > x2) {
677     t = x1;
678     x1 = x2;
679     x2 = t;
680   }
681   if (y1 > y2) {
682     t = y1;
683     y1 = y2;
684     y2 = t;
685   }
686 
687   // look for destination
688   if (!dict->lookup("Dest", &obj1)->isNull()) {
689     action = LinkAction::parseDest(&obj1);
690 
691   // look for action
692   } else {
693     obj1.free();
694     if (dict->lookup("A", &obj1)->isDict()) {
695       action = LinkAction::parseAction(&obj1, baseURI);
696     }
697   }
698   obj1.free();
699 
700   // check for bad action
701   if (action) {
702     ok = gTrue;
703   }
704 
705   return;
706 
707  err1:
708   obj2.free();
709  err2:
710   obj1.free();
711 }
712 
713 Link::~Link() {
714   if (action) {
715     delete action;
716   }
717 }
718 
719 //------------------------------------------------------------------------
720 // Links
721 //------------------------------------------------------------------------
722 
723 Links::Links(Object *annots, GString *baseURI) {
724   Link *link;
725   Object obj1, obj2;
726   int size;
727   int i;
728 
729   links = NULL;
730   size = 0;
731   numLinks = 0;
732 
733   if (annots->isArray()) {
734     for (i = 0; i < annots->arrayGetLength(); ++i) {
735       if (annots->arrayGet(i, &obj1)->isDict()) {
736 	if (obj1.dictLookup("Subtype", &obj2)->isName("Link")) {
737 	  link = new Link(obj1.getDict(), baseURI);
738 	  if (link->isOk()) {
739 	    if (numLinks >= size) {
740 	      size += 16;
741 	      links = (Link **)greallocn(links, size, sizeof(Link *));
742 	    }
743 	    links[numLinks++] = link;
744 	  } else {
745 	    delete link;
746 	  }
747 	}
748 	obj2.free();
749       }
750       obj1.free();
751     }
752   }
753 }
754 
755 Links::~Links() {
756   int i;
757 
758   for (i = 0; i < numLinks; ++i)
759     delete links[i];
760   gfree(links);
761 }
762 
763 LinkAction *Links::find(double x, double y) {
764   int i;
765 
766   for (i = numLinks - 1; i >= 0; --i) {
767     if (links[i]->inRect(x, y)) {
768       return links[i]->getAction();
769     }
770   }
771   return NULL;
772 }
773 
774 GBool Links::onLink(double x, double y) {
775   int i;
776 
777   for (i = 0; i < numLinks; ++i) {
778     if (links[i]->inRect(x, y))
779       return gTrue;
780   }
781   return gFalse;
782 }
783