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