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